Umfangreiche Änderungen für Trainingslogging
This commit is contained in:
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"axios": "^1.7.3",
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13",
|
||||
"vue-multiselect": "^3.0.0",
|
||||
"vue-router": "^4.4.0",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
@@ -753,9 +754,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
|
||||
"integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -2422,6 +2423,15 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-multiselect": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0.tgz",
|
||||
"integrity": "sha512-uupKdINgz7j83lQToCL7KkgQQxvG43el++hsR39YT9pCe1DwzUGmKzPxjVP6rqskXed5P6DtUASYAlCliW740Q==",
|
||||
"engines": {
|
||||
"node": ">= 14.18.1",
|
||||
"npm": ">= 6.14.15"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"axios": "^1.7.3",
|
||||
"core-js": "^3.8.3",
|
||||
"vue": "^3.2.13",
|
||||
"vue-multiselect": "^3.0.0",
|
||||
"vue-router": "^4.4.0",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
|
||||
@@ -34,7 +34,6 @@ import apiClient from './apiClient.js';
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
console.log(import.meta.env);
|
||||
return {
|
||||
selectedClub: null,
|
||||
};
|
||||
|
||||
468
frontend/src/assets/css/vue-multiselect.css
Normal file
468
frontend/src/assets/css/vue-multiselect.css
Normal file
@@ -0,0 +1,468 @@
|
||||
|
||||
|
||||
fieldset[disabled] .multiselect {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.multiselect__spinner {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
width: 40px;
|
||||
height: 38px;
|
||||
background: #fff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.multiselect__spinner::before,
|
||||
.multiselect__spinner::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -8px 0 0 -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100%;
|
||||
border-color: #41b883 transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
box-shadow: 0 0 0 1px transparent;
|
||||
}
|
||||
|
||||
.multiselect__spinner::before {
|
||||
animation: spinning 2.4s cubic-bezier(0.41, 0.26, 0.2, 0.62);
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.multiselect__spinner::after {
|
||||
animation: spinning 2.4s cubic-bezier(0.51, 0.09, 0.21, 0.8);
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.multiselect__loading-enter-active,
|
||||
.multiselect__loading-leave-active {
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.multiselect__loading-enter,
|
||||
.multiselect__loading-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.multiselect,
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
box-sizing: content-box;
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
text-align: left;
|
||||
color: #35495e;
|
||||
}
|
||||
|
||||
.multiselect * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.multiselect:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.multiselect--disabled {
|
||||
background: #ededed;
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.multiselect--active {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__current,
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__input,
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__tags {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__select {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.multiselect--above.multiselect--active .multiselect__current,
|
||||
.multiselect--above.multiselect--active .multiselect__input,
|
||||
.multiselect--above.multiselect--active .multiselect__tags {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 0 0 0 5px;
|
||||
width: calc(100%);
|
||||
transition: border 0.1s ease;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.multiselect__input::placeholder {
|
||||
color: #35495e;
|
||||
}
|
||||
|
||||
.multiselect__tag ~ .multiselect__input,
|
||||
.multiselect__tag ~ .multiselect__single {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.multiselect__input:hover,
|
||||
.multiselect__single:hover {
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
|
||||
.multiselect__input:focus,
|
||||
.multiselect__single:focus {
|
||||
border-color: #a8a8a8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
padding-left: 5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.multiselect__tags-wrap {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
min-height: 40px;
|
||||
display: block;
|
||||
padding: 8px 40px 0 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e8e8e8;
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 4px 26px 4px 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 10px;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
background: #41b883;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
cursor: pointer;
|
||||
margin-left: 7px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-weight: 700;
|
||||
font-style: initial;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
line-height: 22px;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon::after {
|
||||
content: "×";
|
||||
color: #266d4d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* // Remove these lines to avoid green closing button
|
||||
//.multiselect__tag-icon:focus,
|
||||
//.multiselect__tag-icon:hover {
|
||||
// background: #369a6e;
|
||||
//} */
|
||||
|
||||
.multiselect__tag-icon:focus::after,
|
||||
.multiselect__tag-icon:hover::after {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multiselect__current {
|
||||
line-height: 16px;
|
||||
min-height: 40px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px 0;
|
||||
padding-right: 30px;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e8e8e8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.multiselect__select {
|
||||
line-height: 16px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 38px;
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
padding: 4px 8px;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.multiselect__select::before {
|
||||
position: relative;
|
||||
right: 0;
|
||||
top: 65%;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
border-style: solid;
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: #999 transparent transparent transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
color: #adadad;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-top: none;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
z-index: 50;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.multiselect__content {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
bottom: 100%;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom: none;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.multiselect__content::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multiselect__element {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.multiselect__option {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
min-height: 40px;
|
||||
line-height: 16px;
|
||||
text-decoration: none;
|
||||
text-transform: none;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.multiselect__option::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
line-height: 40px;
|
||||
padding-right: 12px;
|
||||
padding-left: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight {
|
||||
background: #41b883;
|
||||
outline: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight::after {
|
||||
content: attr(data-select);
|
||||
background: #41b883;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background: #f3f3f3;
|
||||
color: #35495e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.multiselect__option--selected::after {
|
||||
content: attr(data-selected);
|
||||
color: silver;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.multiselect__option--selected.multiselect__option--highlight {
|
||||
background: #ff6a6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect__option--selected.multiselect__option--highlight::after {
|
||||
background: #ff6a6a;
|
||||
content: attr(data-deselect);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect--disabled .multiselect__current,
|
||||
.multiselect--disabled .multiselect__select {
|
||||
background: #ededed;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.multiselect__option--disabled {
|
||||
background: #ededed !important;
|
||||
color: #a6a6a6 !important;
|
||||
cursor: text;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.multiselect__option--group {
|
||||
background: #ededed;
|
||||
color: #35495e;
|
||||
}
|
||||
|
||||
.multiselect__option--group.multiselect__option--highlight {
|
||||
background: #35495e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect__option--group.multiselect__option--highlight::after {
|
||||
background: #35495e;
|
||||
}
|
||||
|
||||
.multiselect__option--disabled.multiselect__option--highlight {
|
||||
background: #dedede;
|
||||
}
|
||||
|
||||
.multiselect__option--group-selected.multiselect__option--highlight {
|
||||
background: #ff6a6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect__option--group-selected.multiselect__option--highlight::after {
|
||||
background: #ff6a6a;
|
||||
content: attr(data-deselect);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect-enter-active,
|
||||
.multiselect-leave-active {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.multiselect-enter,
|
||||
.multiselect-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.multiselect__strong {
|
||||
margin-bottom: 8px;
|
||||
line-height: 20px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__select {
|
||||
right: auto;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__tags {
|
||||
padding: 8px 8px 0 40px;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__option::after {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__clear {
|
||||
right: auto;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
*[dir="rtl"] .multiselect__spinner {
|
||||
right: auto;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
@keyframes spinning {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(2turn);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import '@/assets/css/main.scss';
|
||||
import './assets/css/vue-multiselect.css';
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
|
||||
@@ -47,7 +47,6 @@ const store = createStore({
|
||||
router.push("/");
|
||||
},
|
||||
setCurrentClub({ commit }, club) {
|
||||
console.log('action', club);
|
||||
commit('setClub', club);
|
||||
},
|
||||
setClubs({ commit }, clubs) {
|
||||
|
||||
@@ -62,7 +62,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async requestAccess() {
|
||||
console.log('start request');
|
||||
const response = await apiClient.get(`/clubs/request/${this.currentClub}`);
|
||||
if (response.status === 200) {
|
||||
alert('Zugriff wurde angefragt');
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Teilnehmer und Aktivitäten -->
|
||||
<div v-if="date !== 'new' && date !== null">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
@@ -55,6 +54,7 @@
|
||||
:checked="isParticipant(member.id)">
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
</label>
|
||||
<button @click="openNotesModal(member)">Notizen</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -62,6 +62,9 @@
|
||||
<h3>Aktivitäten</h3>
|
||||
<textarea v-model="newActivity"></textarea>
|
||||
<button @click="addActivity">Aktivität hinzufügen</button>
|
||||
<multiselect v-model="selectedActivityTags" :options="availableTags" placeholder="Tags auswählen"
|
||||
label="name" track-by="id" multiple :close-on-select="true" @tag="addNewTag"
|
||||
@remove="removeActivityTag" @input="updateActivityTags" :allow-empty="false" />
|
||||
<ul>
|
||||
<li v-for="activity in activities" :key="activity.id">
|
||||
{{ activity.description }}
|
||||
@@ -70,15 +73,37 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showNotesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeNotesModal">×</span>
|
||||
<h3>Notizen für {{ selectedMember.firstName }} {{ selectedMember.lastName }}</h3>
|
||||
<multiselect v-model="selectedMemberTags" :options="availableTags" placeholder="Tags auswählen"
|
||||
label="name" track-by="id" multiple :close-on-select="true" @tag="addNewTagForMember"
|
||||
@remove="removeMemberTag" @input="updateMemberTags" :allow-empty="false" />
|
||||
<div>
|
||||
<textarea v-model="newNoteContent" placeholder="Neue Notiz" rows="4" cols="30"></textarea>
|
||||
<button @click="addMemberNote">Hinzufügen</button>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id">
|
||||
<button @click="deleteNote(note.id)">Löschen</button>
|
||||
{{ note.content }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
|
||||
export default {
|
||||
name: 'DiaryView',
|
||||
components: { Multiselect },
|
||||
data() {
|
||||
return {
|
||||
date: null,
|
||||
@@ -91,8 +116,29 @@ export default {
|
||||
participants: [],
|
||||
newActivity: '',
|
||||
activities: [],
|
||||
notes: [],
|
||||
newNoteContent: '',
|
||||
selectedMember: null,
|
||||
showNotesModal: false,
|
||||
selectedActivityTags: [],
|
||||
selectedMemberTags: [],
|
||||
availableTags: [],
|
||||
previousActivityTags: [],
|
||||
previousMemberTags: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedMemberTags(newTags, oldTags) {
|
||||
this.updateMemberTags(newTags);
|
||||
},
|
||||
selectedMemberNotes(newNotes, oldNotes) {
|
||||
const removedNotes = oldNotes.filter(note => !newNotes.includes(note));
|
||||
removedNotes.forEach(note => this.removeMemberNote(note.content));
|
||||
},
|
||||
selectedActivityTags(newTags, oldTags) {
|
||||
this.updateActivityTags(newTags);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub']),
|
||||
},
|
||||
@@ -101,6 +147,17 @@ export default {
|
||||
if (this.isAuthenticated && this.currentClub) {
|
||||
const response = await apiClient.get(`/diary/${this.currentClub}`);
|
||||
this.dates = response.data.map(entry => ({ id: entry.id, date: entry.date }));
|
||||
this.loadTags();
|
||||
}
|
||||
},
|
||||
handleEnterKey(event) {
|
||||
const newTagName = event.target.value.trim();
|
||||
if (newTagName) {
|
||||
if (this.showNotesModal) {
|
||||
this.addNewTagForMember(newTagName);
|
||||
} else {
|
||||
this.addNewTag(newTagName);
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleDateChange() {
|
||||
@@ -111,10 +168,15 @@ export default {
|
||||
const dateData = response.data.find(entry => entry.id === dateId);
|
||||
this.trainingStart = dateData.trainingStart;
|
||||
this.trainingEnd = dateData.trainingEnd;
|
||||
this.selectedActivityTags = dateData.diaryTags.map(tag => ({
|
||||
id: tag.id,
|
||||
name: tag.name
|
||||
}));
|
||||
this.previousActivityTags = [...this.selectedActivityTags]; // Hier setzen
|
||||
|
||||
await this.loadMembers();
|
||||
await this.loadParticipants(dateId);
|
||||
await this.loadActivities(dateId);
|
||||
await this.loadActivities(dateId);
|
||||
} else {
|
||||
this.newDate = '';
|
||||
this.trainingStart = '';
|
||||
@@ -139,7 +201,6 @@ export default {
|
||||
this.newDate = '';
|
||||
this.trainingStart = '';
|
||||
this.trainingEnd = '';
|
||||
console.log(this.date);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Datums:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
@@ -147,13 +208,12 @@ export default {
|
||||
},
|
||||
async updateTrainingTimes() {
|
||||
try {
|
||||
const dateId = this.date.id;
|
||||
const response = await apiClient.put(`/diary/${this.currentClub}`, {
|
||||
const dateId = this.date.id;
|
||||
await apiClient.put(`/diary/${this.currentClub}`, {
|
||||
dateId,
|
||||
trainingStart: this.trainingStart || null,
|
||||
trainingEnd: this.trainingEnd || null,
|
||||
});
|
||||
|
||||
alert('Trainingszeiten erfolgreich aktualisiert.');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Trainingszeiten:', error);
|
||||
@@ -168,12 +228,20 @@ export default {
|
||||
const response = await apiClient.get(`/participants/${dateId}`);
|
||||
this.participants = response.data.map(participant => participant.memberId);
|
||||
},
|
||||
async loadActivities(dateId) {
|
||||
const response = await apiClient.get(`/activities/${dateId}`);
|
||||
this.activities = response.data;
|
||||
},
|
||||
async loadTags() {
|
||||
const response = await apiClient.get('/tags');
|
||||
this.availableTags = response.data;
|
||||
},
|
||||
isParticipant(memberId) {
|
||||
return this.participants.includes(memberId);
|
||||
},
|
||||
async toggleParticipant(memberId) {
|
||||
const isParticipant = this.isParticipant(memberId);
|
||||
const dateId = this.date.id;
|
||||
const dateId = this.date.id;
|
||||
if (isParticipant) {
|
||||
await apiClient.post('/participants/remove', {
|
||||
diaryDateId: dateId,
|
||||
@@ -188,21 +256,176 @@ export default {
|
||||
this.participants.push(memberId);
|
||||
}
|
||||
},
|
||||
async loadActivities(dateId) {
|
||||
const response = await apiClient.get(`/activities/${dateId}`);
|
||||
this.activities = response.data;
|
||||
},
|
||||
async addActivity() {
|
||||
const dateId = this.date.id;
|
||||
const dateId = this.date.id;
|
||||
if (this.newActivity) {
|
||||
const response = await apiClient.post('/activities/add', {
|
||||
diaryDateId: dateId,
|
||||
description: this.newActivity
|
||||
description: this.newActivity,
|
||||
tags: this.selectedActivityTags.map(tag => tag.id)
|
||||
});
|
||||
this.activities.push(response.data);
|
||||
this.newActivity = '';
|
||||
this.selectedActivityTags = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
openNotesModal(member) {
|
||||
this.selectedMember = member;
|
||||
this.loadMemberNotesAndTags(this.date.id, member.id);
|
||||
this.showNotesModal = true;
|
||||
},
|
||||
async loadMemberNotesAndTags(diaryDateId, memberId) {
|
||||
try {
|
||||
const notesResponse = await apiClient.get(`/diarymember/${this.currentClub}/note`, {
|
||||
params: { diaryDateId, memberId }
|
||||
});
|
||||
this.notes = notesResponse.data;
|
||||
const tagsResponse = await apiClient.get(`/diarymember/${this.currentClub}/tag`, {
|
||||
params: { diaryDateId, memberId }
|
||||
});
|
||||
this.selectedMemberTags = tagsResponse.data.map(tag => ({
|
||||
id: tag.tag.id,
|
||||
name: tag.tag.name
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error loading member notes and tags:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async addMemberNote() {
|
||||
if (this.newNoteContent) {
|
||||
const response = await apiClient.post(`/diarymember/${this.currentClub}/notes`, {
|
||||
memberId: this.selectedMember.id,
|
||||
diaryDateId: this.date.id,
|
||||
content: this.newNoteContent
|
||||
});
|
||||
this.notes = response.data;
|
||||
this.newNoteContent = '';
|
||||
this.selectedTagsNotes = [];
|
||||
}
|
||||
},
|
||||
async deleteNote(noteId) {
|
||||
const response = await apiClient.delete(`/diarymember/note/${noteId}`, {
|
||||
data: { clubId: this.currentClub }
|
||||
});
|
||||
this.notes = response.data;
|
||||
},
|
||||
closeNotesModal() {
|
||||
this.showNotesModal = false;
|
||||
},
|
||||
async addNewTag(newTagName) {
|
||||
try {
|
||||
const response = await apiClient.post('/tags', { name: newTagName });
|
||||
const newTag = response.data;
|
||||
this.availableTags.push(newTag);
|
||||
this.selectedActivityTags.push(newTag);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen eines neuen Tags:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async addNewTagForMember(newTagName) {
|
||||
try {
|
||||
const response = await apiClient.post('/tags', { name: newTagName });
|
||||
const newTag = response.data;
|
||||
this.availableTags.push(newTag);
|
||||
this.selectedMemberTags.push(newTag);
|
||||
await this.linkTagToMemberAndDate(newTag.id);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen eines neuen Tags für das Mitglied:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async linkTagToDiaryDate(tag) {
|
||||
try {
|
||||
const tagId = tag.id;
|
||||
await apiClient.post(`/diary/tag/${this.currentClub}/add-tag`, {
|
||||
diaryDateId: this.date.id,
|
||||
tagId: tagId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Verknüpfen des Tags mit dem Trainingstag:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async linkTagToMemberAndDate(tag) {
|
||||
try {
|
||||
const tagId = tag.id;
|
||||
await apiClient.post(`/diarymember/${this.currentClub}/tag`, {
|
||||
diaryDateId: this.date.id,
|
||||
memberId: this.selectedMember.id,
|
||||
tagId: tagId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Verknüpfen des Tags mit dem Mitglied und Datum:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async updateActivityTags(selectedTags) {
|
||||
console.log('test');
|
||||
try {
|
||||
for (let tag of selectedTags) {
|
||||
if (!this.previousActivityTags.includes(tag)) {
|
||||
await this.linkTagToDiaryDate(tag);
|
||||
}
|
||||
}
|
||||
this.previousActivityTags = [...selectedTags];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Verknüpfen der Tags mit dem Trainingstag:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async updateMemberTags(selectedTags) {
|
||||
try {
|
||||
for (let tag of selectedTags) {
|
||||
if (!this.previousMemberTags.includes(tag)) {
|
||||
await this.linkTagToMemberAndDate(tag);
|
||||
}
|
||||
}
|
||||
this.previousMemberTags = [...selectedTags];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Verknüpfen der Tags mit dem Mitglied und Datum:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async removeMemberTag(tagId) {
|
||||
try {
|
||||
await apiClient.post(`/diarymember/${this.currentClub}/tag/remove`, {
|
||||
diaryDateId: this.date.id,
|
||||
memberId: this.selectedMember.id,
|
||||
tagId: tagId
|
||||
});
|
||||
this.selectedMemberTags = this.selectedMemberTags.filter(tag => tag.id !== tagId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen des Tags:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async removeMemberNote(noteContent) {
|
||||
try {
|
||||
await apiClient.post(`/diarymember/${this.currentClub}/note/remove`, {
|
||||
diaryDateId: this.date.id,
|
||||
memberId: this.selectedMember.id,
|
||||
content: noteContent
|
||||
});
|
||||
this.notes = this.notes.filter(note => note.content !== noteContent);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen der Notiz:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
async removeActivityTag(tag) {
|
||||
try {
|
||||
const tagId = tag.id;
|
||||
await apiClient.delete(`/diary/${this.currentClub}/tag`, {
|
||||
params: { tagId }
|
||||
});
|
||||
this.selectedActivityTags = this.selectedActivityTags.filter(t => t.id !== tagId);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Entfernen des Tags:', error);
|
||||
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.init();
|
||||
@@ -254,4 +477,59 @@ ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
overflow: auto;
|
||||
background-color: rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
padding: 20px;
|
||||
border: 1px solid #555;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
box-shadow: 4px 3px 2px #999;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,7 +24,6 @@ export default {
|
||||
...mapActions(['login']),
|
||||
async executeLogin() {
|
||||
try {
|
||||
console.log(import.meta.env.VITE_BACKEND);
|
||||
const response = await axios.post(`${import.meta.env.VITE_BACKEND}/api/auth/login`, { email: this.email, password: this.password }, {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h2>Mitglieder</h2>
|
||||
<div class="newmember">
|
||||
<div class="toggle-new-member"><span @click="toggleNewMember"><span class="add">{{ memberFormIsOpen ? '-' :
|
||||
'+' }}</span>{{ memberToEdit === null ? "Neues Mitglied" : "Mitglied bearbeiten" }}</span>
|
||||
'+' }}</span>{{ memberToEdit === null ? "Neues Mitglied" : "Mitglied bearbeiten" }}</span>
|
||||
<button v-if="memberToEdit !== null" @click="resetToNewMember">Neues Mitglied anlegen</button>
|
||||
</div>
|
||||
<div v-if="memberFormIsOpen" class="new-member-form">
|
||||
@@ -39,10 +39,28 @@
|
||||
<td>{{ member.birthDate }}</td>
|
||||
<td>{{ member.phone }}</td>
|
||||
<td>{{ member.email }}</td>
|
||||
<td><button @click.stop="openNotesModal(member)">Notizen</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="showNotesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeNotesModal">×</span>
|
||||
<h3>Notizen für {{ memberToEdit.firstName }} {{ memberToEdit.lastName }}</h3>
|
||||
<div>
|
||||
<textarea v-model="newNoteContent" placeholder="Neue Notiz" rows="4" cols="30"></textarea>
|
||||
<button @click="addNote">Hinzufügen</button>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="note in notes" :key="note.id">
|
||||
<button @click="deleteNote(note.id)">Löschen</button>
|
||||
{{ note.content }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -66,8 +84,11 @@ export default {
|
||||
newBirthdate: '01.01.2010',
|
||||
newPhone: '',
|
||||
newEmail: '',
|
||||
newActive: true, // Neues Feld für Active-Status
|
||||
memberToEdit: null
|
||||
newActive: true,
|
||||
memberToEdit: null,
|
||||
notes: [],
|
||||
newNoteContent: '',
|
||||
showNotesModal: false,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -89,7 +110,7 @@ export default {
|
||||
this.newBirthdate = '01.01.2010';
|
||||
this.newPhone = '';
|
||||
this.newEmail = '';
|
||||
this.newActive = true; // Standardmäßig aktiv
|
||||
this.newActive = true;
|
||||
},
|
||||
async addNewMember() {
|
||||
const response = await apiClient.post(`/clubmembers/${this.currentClub}`, {
|
||||
@@ -100,7 +121,7 @@ export default {
|
||||
birthdate: this.newBirthdate,
|
||||
phone: this.newPhone,
|
||||
email: this.newEmail,
|
||||
active: this.newActive, // Übermitteln des Active-Status
|
||||
active: this.newActive,
|
||||
id: this.memberToEdit ? this.memberToEdit.id : null,
|
||||
});
|
||||
this.members = response.data;
|
||||
@@ -116,7 +137,8 @@ export default {
|
||||
this.newCity = member.city;
|
||||
this.newPhone = member.phone;
|
||||
this.newEmail = member.email;
|
||||
this.newActive = member.active; // Den aktuellen Status laden
|
||||
this.newActive = member.active;
|
||||
this.loadNotes(member);
|
||||
},
|
||||
resetToNewMember() {
|
||||
this.memberToEdit = null;
|
||||
@@ -127,7 +149,38 @@ export default {
|
||||
this.newBirthdate = '01.01.2010';
|
||||
this.newPhone = '';
|
||||
this.newEmail = '';
|
||||
this.newActive = true; // Standardmäßig aktiv
|
||||
this.newActive = true;
|
||||
this.memberNotes = [];
|
||||
},
|
||||
async loadNotes(member) {
|
||||
this.selectedMember = member;
|
||||
const response = await apiClient.get(`/membernotes/${member.id}`, {
|
||||
params: { clubId: this.currentClub }
|
||||
});
|
||||
this.notes = response.data;
|
||||
this.showNotesModal = true;
|
||||
},
|
||||
async addNote() {
|
||||
const response = await apiClient.post('/membernotes', {
|
||||
memberId: this.selectedMember.id,
|
||||
content: this.newNoteContent,
|
||||
clubId: this.currentClub
|
||||
});
|
||||
this.notes = response.data;
|
||||
this.newNoteContent = '';
|
||||
},
|
||||
async deleteNote(noteId) {
|
||||
await apiClient.delete(`/membernotes/${noteId}`, {
|
||||
data: { clubId: this.currentClub }
|
||||
});
|
||||
this.notes = response.data;
|
||||
},
|
||||
openNotesModal(member) {
|
||||
this.editMember(member);
|
||||
this.showNotesModal = true;
|
||||
},
|
||||
closeNotesModal() {
|
||||
this.showNotesModal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +191,7 @@ table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead > tr> th {
|
||||
thead>tr>th {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
@@ -172,12 +225,65 @@ table td {
|
||||
border: 1px solid #999;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.new-member-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.new-member-form > label > span {
|
||||
|
||||
.new-member-form>label>span {
|
||||
width: 10em;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
padding: 20px;
|
||||
border: 1px solid #555;
|
||||
width: 50%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
box-shadow: 4px 3px 2px #999;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0;
|
||||
/* Kein Padding für Listenelemente */
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user