Add updateGroupActivity method and corresponding route for editing group activities
This commit introduces the updateGroupActivity method in the diaryDateActivityController, allowing users to update existing group activities by linking them to predefined activities. The method includes error handling and emits a socket event upon successful updates. Additionally, the diaryDateActivityRoutes file is updated to include a new PUT route for updating group activities. Frontend changes in DiaryView enhance the user experience by enabling inline editing of group activities, including search functionality for predefined activities.
This commit is contained in:
@@ -352,7 +352,9 @@
|
||||
"trainingTimesUpdated": "Trainingszeiten erfolgreich aktualisiert.",
|
||||
"formMarkedAsHandedOver": "Mitgliedsformular als ausgehändigt markiert",
|
||||
"errorMarkingForm": "Fehler beim Markieren des Mitgliedsformulars",
|
||||
"dateNoLongerCurrent": "Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen."
|
||||
"dateNoLongerCurrent": "Ausgewähltes Datum war nicht mehr aktuell. Bitte erneut versuchen.",
|
||||
"activityRequired": "Bitte geben Sie eine Aktivität ein.",
|
||||
"activityNotFound": "Aktivität nicht gefunden. Bitte wählen Sie eine aus der Liste aus."
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Willkommen im TrainingsTagebuch",
|
||||
|
||||
@@ -176,6 +176,15 @@
|
||||
<span v-else-if="editingActivityId === item.id">
|
||||
<div
|
||||
style="display: flex; gap: 5px; align-items: center;">
|
||||
<button
|
||||
v-if="item.predefinedActivity || hasItemDrawingData(item)"
|
||||
type="button"
|
||||
@click.stop="editActivity(item)"
|
||||
:title="$t('diary.createDrawing')"
|
||||
style="margin: 0; padding: 4px 8px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; min-width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;"
|
||||
>
|
||||
🎨
|
||||
</button>
|
||||
<div style="flex: 1; position: relative;">
|
||||
<input type="text" v-model="editingActivityText"
|
||||
@input="onEditInputChangeText(item)"
|
||||
@@ -273,11 +282,6 @@
|
||||
<td></td>
|
||||
<td colspan="2">
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center; padding: 0.5rem; background: #f0f0f0; border-radius: 4px;">
|
||||
<select v-model="newPlanItem.groupId" style="flex: 1;">
|
||||
<option value="">{{ $t('diary.selectGroup') }}</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
|
||||
{{ group.name }}</option>
|
||||
</select>
|
||||
<div style="flex: 1; position: relative;">
|
||||
<input type="text" v-model="newPlanItem.activity" :placeholder="$t('diary.activityPlaceholder')"
|
||||
@input="onNewItemInputChange" style="width: 100%;" />
|
||||
@@ -290,6 +294,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<select v-model="newPlanItem.groupId" style="flex: 1;">
|
||||
<option value="">{{ $t('diary.selectGroup') }}</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
|
||||
{{ group.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
@@ -303,7 +312,38 @@
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<span class="activity-label"
|
||||
<span v-if="Number(editingGroupActivityId) === Number(groupItem.id)">
|
||||
<div style="display: flex; gap: 5px; align-items: center;">
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="editGroupActivity(groupItem)"
|
||||
:title="$t('diary.createDrawing')"
|
||||
style="margin: 0; padding: 4px 8px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; min-width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;"
|
||||
>
|
||||
🎨
|
||||
</button>
|
||||
<div style="flex: 1; position: relative;">
|
||||
<input type="text" v-model="editingGroupActivityText"
|
||||
@input="onEditGroupActivityInputChange(groupItem)"
|
||||
@keyup.enter="saveGroupActivityEdit(groupItem)"
|
||||
@keyup.esc="cancelGroupActivityEdit"
|
||||
style="width: 100%;" />
|
||||
<div v-if="editShowDropdown && Number(editSearchForId) === Number(groupItem.id) && editSearchResults.length"
|
||||
class="dropdown" style="max-height: 9.5em;">
|
||||
<div v-for="s in editSearchResults" :key="s.id"
|
||||
@click="chooseEditGroupActivitySuggestion(s, groupItem)">
|
||||
<span v-if="s.code && s.code.trim() !== ''"><strong>[{{ s.code }}]</strong> </span>{{ s.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="saveGroupActivityEdit(groupItem)" class="btn-primary"
|
||||
style="padding: 2px 8px; font-size: 12px;">✓</button>
|
||||
<button @click="cancelGroupActivityEdit" class="btn-secondary"
|
||||
style="padding: 2px 8px; font-size: 12px;">✗</button>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else @click="startGroupActivityEdit(groupItem)"
|
||||
class="clickable activity-label"
|
||||
:title="(groupItem.groupPredefinedActivity && groupItem.groupPredefinedActivity.name) ? groupItem.groupPredefinedActivity.name : ''">
|
||||
<!-- Icon öffnet Rendering (falls vorhanden) oder Bild im Modal -->
|
||||
<span v-if="hasActivityVisual(groupItem.groupPredefinedActivity)"
|
||||
@@ -321,8 +361,6 @@
|
||||
<td></td>
|
||||
<td>
|
||||
<div style="position: relative; display: inline-block;">
|
||||
<button @click="editGroupActivity(groupItem)" :title="$t('diary.edit')"
|
||||
class="person-btn" style="background: #ffc107; color: #000;">✏️</button>
|
||||
<button @click="toggleGroupActivityMembers(groupItem)" :title="$t('diary.assignParticipants')"
|
||||
class="person-btn">👤</button>
|
||||
<button @click="removeGroupActivity(groupItem.id)" class="trash-btn" :title="$t('diary.delete')">🗑️</button>
|
||||
@@ -369,7 +407,7 @@
|
||||
<button v-if="parentIsTimeblock()"
|
||||
@click="addGroupActivity">{{ $t('diary.addGroupActivity') }}</button>
|
||||
</td>
|
||||
<td v-if="addNewItem || addNewGroupActivity">
|
||||
<td v-if="addNewItem">
|
||||
<div v-if="addtype === 'activity'" style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<button
|
||||
type="button"
|
||||
@@ -397,22 +435,8 @@
|
||||
</div>
|
||||
</td>
|
||||
<td v-else-if="addNewTimeblock">{{ $t('diary.timeblock') }}</td>
|
||||
<td v-if="addNewGroupActivity" colspan="2">
|
||||
<td v-if="addNewGroupActivity && !selectedTimeblockId" colspan="2">
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||||
<select v-model="newPlanItem.groupId" style="flex: 1;">
|
||||
<option value="">{{ $t('diary.selectGroup') }}</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
|
||||
{{ group.name }}</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-palette"
|
||||
@click="showDrawingDialog = true"
|
||||
:title="$t('diary.createDrawing')"
|
||||
style="margin: 0; margin-left: 0;"
|
||||
>
|
||||
🎨
|
||||
</button>
|
||||
<div style="flex: 1; position: relative;">
|
||||
<input type="text" v-model="newPlanItem.activity" placeholder="Aktivität"
|
||||
@input="onNewItemInputChange" style="width: 100%;" />
|
||||
@@ -425,6 +449,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<select v-model="newPlanItem.groupId" style="flex: 1;">
|
||||
<option value="">{{ $t('diary.selectGroup') }}</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
|
||||
{{ group.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td v-else-if="addNewItem || addNewTimeblock"></td>
|
||||
@@ -675,9 +704,9 @@
|
||||
<!-- Court Drawing Dialog -->
|
||||
<CourtDrawingDialog
|
||||
v-model="showDrawingDialog"
|
||||
:initial-drawing-data="editingGroupActivity ? drawingDataFor(editingGroupActivity.groupPredefinedActivity) : null"
|
||||
:initial-code="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? (editingGroupActivity.groupPredefinedActivity.code || editingGroupActivity.groupPredefinedActivity.name) : null"
|
||||
:initial-name="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? editingGroupActivity.groupPredefinedActivity.name : null"
|
||||
:initial-drawing-data="editingGroupActivity ? (editingGroupActivity.groupPredefinedActivity ? drawingDataFor(editingGroupActivity.groupPredefinedActivity) : (editingGroupActivity.activityItem && editingGroupActivity.activityItem.drawingData ? (typeof editingGroupActivity.activityItem.drawingData === 'string' ? JSON.parse(editingGroupActivity.activityItem.drawingData) : editingGroupActivity.activityItem.drawingData) : null)) : null"
|
||||
:initial-code="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? (editingGroupActivity.groupPredefinedActivity.code || editingGroupActivity.groupPredefinedActivity.name) : (editingGroupActivity && editingGroupActivity.activityItem ? editingGroupActivity.activityItem.activity : null)"
|
||||
:initial-name="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? editingGroupActivity.groupPredefinedActivity.name : (editingGroupActivity && editingGroupActivity.activityItem ? editingGroupActivity.activityItem.activity : null)"
|
||||
:initial-description="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? editingGroupActivity.groupPredefinedActivity.description : null"
|
||||
@ok="handleDrawingDialogOkForDiary"
|
||||
@close="editingGroupActivity = null"
|
||||
@@ -895,6 +924,8 @@ export default {
|
||||
groupActivityMembersOpenId: null,
|
||||
groupActivityMembersMap: {}, // key: groupActivityId, value: Set(participantIds)
|
||||
editingGroupActivity: null, // Gruppenaktivität, die gerade bearbeitet wird
|
||||
editingGroupActivityId: null, // ID der Gruppenaktivität, die inline bearbeitet wird
|
||||
editingGroupActivityText: '', // Text für inline-edit
|
||||
// Schnell hinzufügen Dialog
|
||||
showQuickAddDialog: false,
|
||||
newMember: {
|
||||
@@ -1013,6 +1044,20 @@ export default {
|
||||
await this.toggleParticipant(member.memberId);
|
||||
},
|
||||
|
||||
hasItemDrawingData(item) {
|
||||
if (!item) return false;
|
||||
try {
|
||||
// Prüfe direktes drawingData am item
|
||||
if (item.drawingData) {
|
||||
if (typeof item.drawingData === 'string' && item.drawingData.trim() !== '') return true;
|
||||
if (typeof item.drawingData === 'object' && Object.keys(item.drawingData).length > 0) return true;
|
||||
}
|
||||
// Prüfe auch über predefinedActivity
|
||||
if (item.predefinedActivity && this.hasActivityVisual(item.predefinedActivity)) return true;
|
||||
} catch (e) { }
|
||||
return false;
|
||||
},
|
||||
|
||||
hasActivityVisual(pa) {
|
||||
if (!pa) return false;
|
||||
try {
|
||||
@@ -2483,28 +2528,60 @@ export default {
|
||||
try {
|
||||
const code = result.code.trim();
|
||||
|
||||
// Wenn eine Gruppenaktivität bearbeitet wird
|
||||
// Wenn eine Gruppenaktivität oder normale Aktivität bearbeitet wird
|
||||
if (this.editingGroupActivity && this.editingGroupActivity.groupPredefinedActivity) {
|
||||
const predefinedActivityId = this.editingGroupActivity.groupPredefinedActivity.id;
|
||||
|
||||
// Aktualisiere die PredefinedActivity
|
||||
const updateData = {
|
||||
name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '',
|
||||
code: code,
|
||||
description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '',
|
||||
durationText: this.editingGroupActivity.groupPredefinedActivity.durationText || '',
|
||||
duration: this.editingGroupActivity.groupPredefinedActivity.duration || null,
|
||||
imageLink: this.editingGroupActivity.groupPredefinedActivity.imageLink || '',
|
||||
drawingData: result.drawingData || null
|
||||
};
|
||||
let finalPredefinedActivityId = predefinedActivityId;
|
||||
|
||||
await apiClient.put(`/predefined-activities/${predefinedActivityId}`, updateData);
|
||||
// Wenn keine predefinedActivityId vorhanden ist, erstelle eine neue
|
||||
if (!predefinedActivityId) {
|
||||
const newActivityData = {
|
||||
name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '',
|
||||
code: code,
|
||||
description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '',
|
||||
drawingData: result.drawingData || null
|
||||
};
|
||||
|
||||
const createResponse = await apiClient.post('/predefined-activities', newActivityData);
|
||||
finalPredefinedActivityId = createResponse.data.id;
|
||||
|
||||
// Wenn es eine normale Aktivität war, verknüpfe die DiaryDateActivity mit der neuen PredefinedActivity
|
||||
if (this.editingGroupActivity.isNormalActivity && this.editingGroupActivity.activityItem) {
|
||||
const activityItem = this.editingGroupActivity.activityItem;
|
||||
if (activityItem.id && this.currentClub) {
|
||||
// currentClub ist die Club-ID (Zahl), nicht ein Objekt
|
||||
await apiClient.put(`/diary-date-activities/${this.currentClub}/${activityItem.id}`, {
|
||||
predefinedActivityId: finalPredefinedActivityId
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Aktualisiere die existierende PredefinedActivity
|
||||
const updateData = {
|
||||
name: result.name || (result.fields && result.fields.name) || this.editingGroupActivity.groupPredefinedActivity.name || '',
|
||||
code: code,
|
||||
description: result.description || (result.fields && result.fields.description) || this.editingGroupActivity.groupPredefinedActivity.description || '',
|
||||
durationText: this.editingGroupActivity.groupPredefinedActivity.durationText || '',
|
||||
duration: this.editingGroupActivity.groupPredefinedActivity.duration || null,
|
||||
imageLink: this.editingGroupActivity.groupPredefinedActivity.imageLink || '',
|
||||
drawingData: result.drawingData || null
|
||||
};
|
||||
|
||||
await apiClient.put(`/predefined-activities/${predefinedActivityId}`, updateData);
|
||||
}
|
||||
|
||||
// Lade den Trainingsplan neu
|
||||
if (this.date && this.date.id) {
|
||||
await this.loadTrainingPlan();
|
||||
}
|
||||
|
||||
// Wenn es eine normale Aktivität war, beende auch den Inline-Edit-Modus
|
||||
if (this.editingGroupActivity.isNormalActivity && this.editingGroupActivity.activityItem) {
|
||||
this.editingActivityId = null;
|
||||
this.editingActivityText = '';
|
||||
}
|
||||
|
||||
this.editingGroupActivity = null;
|
||||
return;
|
||||
}
|
||||
@@ -2570,6 +2647,39 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
editActivity(item) {
|
||||
if (!item) {
|
||||
this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wenn keine predefinedActivity vorhanden ist, aber drawingData, erstelle eine temporäre Struktur
|
||||
let predefinedActivity = item.predefinedActivity;
|
||||
if (!predefinedActivity && item.drawingData) {
|
||||
// Erstelle eine temporäre Struktur für die Bearbeitung
|
||||
predefinedActivity = {
|
||||
id: null,
|
||||
name: item.activity || '',
|
||||
code: item.activity || '',
|
||||
description: '',
|
||||
drawingData: item.drawingData
|
||||
};
|
||||
}
|
||||
|
||||
if (!predefinedActivity) {
|
||||
this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verwende editingGroupActivity auch für normale Aktivitäten, da die Struktur ähnlich ist
|
||||
this.editingGroupActivity = {
|
||||
groupPredefinedActivity: predefinedActivity,
|
||||
isNormalActivity: true,
|
||||
activityItem: item
|
||||
};
|
||||
this.showDrawingDialog = true;
|
||||
},
|
||||
|
||||
editGroupActivity(groupItem) {
|
||||
if (!groupItem || !groupItem.groupPredefinedActivity) {
|
||||
this.showInfo('Fehler', 'Keine Aktivität zum Bearbeiten gefunden', '', 'error');
|
||||
@@ -2578,6 +2688,89 @@ export default {
|
||||
this.editingGroupActivity = groupItem;
|
||||
this.showDrawingDialog = true;
|
||||
},
|
||||
|
||||
startGroupActivityEdit(groupItem) {
|
||||
console.log('[startGroupActivityEdit] groupItem.id:', groupItem.id, 'type:', typeof groupItem.id);
|
||||
this.editingGroupActivityId = Number(groupItem.id);
|
||||
this.editSearchForId = Number(groupItem.id);
|
||||
console.log('[startGroupActivityEdit] editingGroupActivityId set to:', this.editingGroupActivityId);
|
||||
this.$nextTick(() => {
|
||||
const inputs = this.$el.querySelectorAll('input[type="text"]');
|
||||
const lastInput = inputs[inputs.length - 1];
|
||||
if (lastInput) {
|
||||
lastInput.focus();
|
||||
}
|
||||
});
|
||||
this.editingGroupActivityText = groupItem.groupPredefinedActivity
|
||||
? (groupItem.groupPredefinedActivity.code && groupItem.groupPredefinedActivity.code.trim() !== ''
|
||||
? groupItem.groupPredefinedActivity.code
|
||||
: groupItem.groupPredefinedActivity.name)
|
||||
: '';
|
||||
},
|
||||
|
||||
async onEditGroupActivityInputChange(groupItem) {
|
||||
const term = this.editingGroupActivityText;
|
||||
this.editSearchForId = groupItem.id;
|
||||
if (!term || term.trim().length < 2) {
|
||||
this.editShowDropdown = false;
|
||||
this.editSearchResults = [];
|
||||
return;
|
||||
}
|
||||
const results = await this.searchPredefinedActivities(term);
|
||||
this.editSearchResults = results;
|
||||
this.editShowDropdown = results.length > 0;
|
||||
},
|
||||
|
||||
chooseEditGroupActivitySuggestion(s, groupItem) {
|
||||
this.editingGroupActivityText = (s.code && s.code.trim() !== '') ? s.code : s.name;
|
||||
this.editShowDropdown = false;
|
||||
this.editSearchResults = [];
|
||||
},
|
||||
|
||||
async saveGroupActivityEdit(groupItem) {
|
||||
try {
|
||||
if (!this.editingGroupActivityText || !this.editingGroupActivityText.trim()) {
|
||||
this.showInfo(this.$t('messages.note'), this.$t('diary.activityRequired'), '', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Suche nach der PredefinedActivity
|
||||
const searchResults = await this.searchPredefinedActivities(this.editingGroupActivityText.trim());
|
||||
const existing = searchResults.find(a => {
|
||||
const searchTerm = this.editingGroupActivityText.trim().toLowerCase();
|
||||
return (a.code && a.code.trim().toLowerCase() === searchTerm) ||
|
||||
(a.name && a.name.trim().toLowerCase() === searchTerm);
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
this.showInfo(this.$t('messages.note'), this.$t('diary.activityNotFound'), '', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Aktualisiere die Gruppenaktivität mit der neuen PredefinedActivity
|
||||
await apiClient.put(`/diary-date-activities/group/${this.currentClub}/${groupItem.id}`, {
|
||||
predefinedActivityId: existing.id
|
||||
});
|
||||
|
||||
// Lade den Trainingsplan neu
|
||||
await this.loadTrainingPlan();
|
||||
|
||||
this.editingGroupActivityId = null;
|
||||
this.editingGroupActivityText = '';
|
||||
this.editShowDropdown = false;
|
||||
this.editSearchResults = [];
|
||||
} catch (error) {
|
||||
const msg = getSafeErrorMessage(error, 'Fehler beim Speichern der Gruppenaktivität');
|
||||
this.showInfo('Fehler', msg, '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
cancelGroupActivityEdit() {
|
||||
this.editingGroupActivityId = null;
|
||||
this.editingGroupActivityText = '';
|
||||
this.editShowDropdown = false;
|
||||
this.editSearchResults = [];
|
||||
},
|
||||
|
||||
async loadTrainingPlan() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user