diff --git a/backend/controllers/diaryMemberController.js b/backend/controllers/diaryMemberController.js index 337bba0..ba9886f 100644 --- a/backend/controllers/diaryMemberController.js +++ b/backend/controllers/diaryMemberController.js @@ -5,6 +5,7 @@ const getMemberTags = async (req, res) => { const { diaryDateId, memberId } = req.query; const { clubId } = req.params; const { authcode: userToken } = req.headers; + console.log(diaryDateId, memberId, clubId); const tags = await DiaryMemberService.getTagsForMemberAndDate(userToken, clubId, diaryDateId, memberId); res.status(200).json(tags); } catch (error) { diff --git a/backend/controllers/diaryTagController.js b/backend/controllers/diaryTagController.js index a5b3763..49ddf9c 100644 --- a/backend/controllers/diaryTagController.js +++ b/backend/controllers/diaryTagController.js @@ -12,9 +12,11 @@ export const getTags = async (req, res) => { export const createTag = async (req, res) => { try { const { name } = req.body; - const newTag = await DiaryTag.create({ name }); + console.log(name); + const newTag = await DiaryTag.findOrCreate({ where: { name }, defaults: { name } }); res.status(201).json(newTag); } catch (error) { + console.log('[createTag] - Error:', error); res.status(500).json({ error: 'Error creating tag' }); } }; diff --git a/backend/models/index.js b/backend/models/index.js index a0fda39..d3bdfad 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -108,6 +108,9 @@ GroupActivity.belongsTo(Group, { foreignKey: 'groupId', as: 'groupsGroupActivity GroupActivity.belongsTo(PredefinedActivity, { foreignKey: 'customActivity', as: 'groupPredefinedActivity' }); PredefinedActivity.hasMany(GroupActivity, { foreignKey: 'predefinedActivityId', as: 'groupPredefinedActivities' }); +DiaryTag.hasMany(DiaryDateTag, { foreignKey: 'tagId', as: 'diaryDateTags' }); +DiaryDateTag.belongsTo(DiaryTag, { foreignKey: 'tagId', as: 'tag' }); + export { User, Log, diff --git a/backend/routes/diaryTagRoutes.js b/backend/routes/diaryTagRoutes.js index 25f4ff5..b2ed8fa 100644 --- a/backend/routes/diaryTagRoutes.js +++ b/backend/routes/diaryTagRoutes.js @@ -4,8 +4,10 @@ import { authenticate } from '../middleware/authMiddleware.js'; const router = express.Router(); -router.get('/', authenticate, getTags); -router.post('/', authenticate, createTag); -router.delete('/:tagId', authenticate, deleteTag); +router.use(authenticate); + +router.get('/', getTags); +router.post('/', createTag); +router.delete('/:tagId', deleteTag); export default router; diff --git a/backend/services/diaryMemberService.js b/backend/services/diaryMemberService.js index 86c5d1b..98cbd78 100644 --- a/backend/services/diaryMemberService.js +++ b/backend/services/diaryMemberService.js @@ -14,6 +14,7 @@ class DiaryMemberService { async getNotesForMember(userToken, clubId, diaryDateId, memberId) { await checkAccess(userToken, clubId); + console.log(clubId, diaryDateId, memberId); return await DiaryMemberNote.findAll({ where: { diaryDateId, memberId }, order: [['createdAt', 'DESC']] }); } diff --git a/backend/services/diaryService.js b/backend/services/diaryService.js index 0b97255..c0f236d 100644 --- a/backend/services/diaryService.js +++ b/backend/services/diaryService.js @@ -109,21 +109,33 @@ class DiaryService { if (!diaryDate) { throw new HttpError('DiaryDate not found', 404); } + console.log('[DiaryService::addTagToDiaryDate] - Add tag to diary date'); const existingEntry = await DiaryDateTag.findOne({ where: { diaryDateId, tagId } }); if (existingEntry) { return; } + console.log('[DiaryService::addTagToDiaryDate] - Tag not found, creating new entry'); const tag = await DiaryTag.findByPk(tagId); if (!tag) { throw new HttpError('Tag not found', 404); } + console.log('[DiaryService::addTagToDiaryDate] - Add tag to diary date'); await DiaryDateTag.create({ diaryDateId, tagId - }) - return diaryDate.getDiaryTags(); + }); + console.log('[DiaryService::addTagToDiaryDate] - Get tags'); + const tags = await DiaryDateTag.findAll({ where: { + diaryDateId: diaryDateId }, + include: { + model: DiaryTag, + as: 'tag' + } + }); + console.log(tags); + return tags.map(tag => tag.tag); } async getDiaryNotesForDateAndMember(diaryDateId, memberId) { diff --git a/frontend/src/assets/css/main.scss b/frontend/src/assets/css/main.scss index 5057895..c90c0f0 100644 --- a/frontend/src/assets/css/main.scss +++ b/frontend/src/assets/css/main.scss @@ -63,4 +63,7 @@ button.cancel-action { button.cancel-action:hover { background-color: #f2f2f2; color: #45a049; +} +.pointer { + cursor: pointer; } \ No newline at end of file diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 9c2e4e6..ec3bc3f 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -158,17 +158,6 @@
-

Teilnehmer

-

Aktivitäten

@@ -179,11 +168,36 @@ + @remove="removeActivityTag" :allow-empty="false" @keydown.enter.prevent="addNewTagFromInput" /> +

Teilnehmer

+
+ + +
+ +
@@ -212,6 +226,7 @@ export default { activities: [], notes: [], newNoteContent: '', + noteMember: null, selectedMember: null, showNotesModal: false, selectedActivityTags: [], @@ -241,6 +256,9 @@ export default { addNewTimeblock: false, showGeneralData: false, editingGroupId: null, + doMemberTagUpdates: true, + showTagHistoryModal: false, + tagHistoryMember: null, }; }, watch: { @@ -409,18 +427,25 @@ export default { description: this.newActivity, tags: this.selectedActivityTags.map(tag => tag.id) }); - this.activities.push(response.data); + this.activities.push(response.data[0]); this.newActivity = ''; this.selectedActivityTags = []; } }, - async openNotesModal(member) { + async selectMember(member) { this.selectedMember = member; - await this.loadMemberImage(member); + }, + async openNotesModal(member) { + this.noteMember = member; + try { + await this.loadMemberImage(member); + } catch (error) { + } this.loadMemberNotesAndTags(this.date.id, member.id); this.showNotesModal = true; }, async loadMemberNotesAndTags(diaryDateId, memberId) { + this.doMemberTagUpdates = false; try { const notesResponse = await apiClient.get(`/diarymember/${this.currentClub}/note`, { params: { diaryDateId, memberId } @@ -431,15 +456,16 @@ export default { }); this.selectedMemberTags = tagsResponse.data.map(tag => ({ id: tag.tag.id, - name: tag.tag.name + label: tag.tag.label })); } catch (error) { console.error('Error loading member notes and tags:', error); alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.'); } + this.doMemberTagUpdates = true; }, async addMemberNote() { - if (this.newNoteContent) { + if (this.newNoteContent && this.selectedMember) { const response = await apiClient.post(`/diarymember/${this.currentClub}/note`, { memberId: this.selectedMember.id, diaryDateId: this.date.id, @@ -448,6 +474,8 @@ export default { this.notes = response.data; this.newNoteContent = ''; this.selectedTagsNotes = []; + } else { + alert('Bitte wählen Sie einen Teilnehmer aus und geben Sie einen Notiztext ein.'); } }, async deleteNote(noteId) { @@ -468,7 +496,7 @@ export default { async addNewTag(newTagName) { try { const response = await apiClient.post('/tags', { name: newTagName }); - const newTag = response.data; + const newTag = response.data[0]; this.availableTags.push(newTag); this.selectedActivityTags.push(newTag); } catch (error) { @@ -495,9 +523,13 @@ export default { } }, async linkTagToDiaryDate(tag) { + if (!tag || !tag.id) { + console.warn("Ungültiges Tag-Objekt:", tag); + return; + } try { const tagId = tag.id; - await apiClient.post(`/diary/tag/${this.currentClub}/add-tag`, { + const response = await apiClient.post(`/diary/tag/${this.currentClub}/add-tag`, { diaryDateId: this.date.id, tagId: tagId }); @@ -522,17 +554,14 @@ export default { async updateActivityTags() { try { const selectedTags = this.selectedActivityTags; - - if (!selectedTags || !Array.isArray(selectedTags)) { + if (!Array.isArray(selectedTags)) { throw new TypeError('Expected selectedTags to be an array'); } - for (let tag of selectedTags) { - if (!this.previousActivityTags.includes(tag)) { + if (tag && tag.id && !this.previousActivityTags.some(prevTag => prevTag.id === tag.id)) { await this.linkTagToDiaryDate(tag); } } - this.previousActivityTags = [...selectedTags]; } catch (error) { console.error('Fehler beim Verknüpfen der Tags mit dem Trainingstag:', error); @@ -540,6 +569,9 @@ export default { } }, async updateMemberTags() { + if (!this.doMemberTagUpdates || !this.selectedMember) { + return; + } try { for (let tag of this.selectedMemberTags) { if (!this.previousMemberTags.includes(tag)) { @@ -590,12 +622,12 @@ export default { alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.'); } }, - handleActivityInput() { - if (this.newPlanItem.activity) { - this.showDropdown = true; - } else { - this.showDropdown = false; + async handleActivityTagInput(tags) { + const newTags = tags.filter(tag => !this.previousActivityTags.some(prevTag => prevTag.id === tag.id)); + for (const tag of newTags) { + await this.linkTagToDiaryDate(tag); } + this.previousActivityTags = [...tags]; }, selectPredefinedActivity(activity) { this.newPlanItem.activity = activity.name; @@ -744,7 +776,7 @@ export default { alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.'); } }, - async loadMemberImage(member) { +/* async loadMemberImage(member) { try { const response = await apiClient.get(`/clubmembers/image/${this.currentClub}/${member.id}`, { responseType: 'blob', @@ -752,10 +784,9 @@ export default { const imageUrl = URL.createObjectURL(response.data); member.imageUrl = imageUrl; } catch (error) { - console.error("Failed to load member image:", error); member.imageUrl = null; } - }, + },*/ async generatePDF() { const pdf = new PDFGenerator(); pdf.addTrainingPlan(this.currentClubName, this.date.date, this.trainingStart, this.trainingEnd, this.trainingPlan); @@ -786,7 +817,6 @@ export default { this.imageUrl = URL.createObjectURL(response.data); this.showImage = true; } catch (error) { - console.error("Failed to load member image:", error); this.imageUrl = null; } }, @@ -838,7 +868,6 @@ export default { this.showGeneralData = !this.showGeneralData; }, getFormattedDate(date) { - console.log(date, typeof date); return (new Date(date)).toLocaleDateString('de-DE', { year: 'numeric', month: '2-digit', day: '2-digit'}); }, editGroup(groupId) { @@ -861,6 +890,14 @@ export default { cancelEditGroup() { this.editingGroupId = null; }, + async openTagInfos(member) { + this.showTagHistoryModal = true; + this.tagHistoryMember = member; + }, + closeTagHistoryModal() { + this.showTagHistoryModal = false; + this.tagHistoryMember = null; + }, }, async mounted() { await this.init(); @@ -1025,6 +1062,13 @@ input[type="number"] { color: #45a049; } +.highlighted { + background-color: #45a049; + color: white; + padding: 0.2em; + border-radius: 4px; +} + .add-plan-item { border: 1px solid black; cursor: pointer; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index a912ac9..2e3703e 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], + mode: 'development', resolve: { alias: { '@': '/src'