feat(CourtDrawingDialog, DiaryView, i18n): add diary fields for duration and group selection

- Introduced new diary fields in the CourtDrawingDialog for inputting duration as text and minutes, along with a group selection dropdown.
- Implemented logic to calculate duration from text input, enhancing user experience in managing diary entries.
- Updated the DiaryView to support the new diary fields, ensuring proper data handling and integration with existing functionalities.
- Expanded localization files for English and German to include new keys related to the diary fields, improving accessibility for users.
This commit is contained in:
Torsten Schulz (local)
2026-03-17 14:26:47 +01:00
parent afe51f399c
commit b16743d27d
4 changed files with 172 additions and 7 deletions

View File

@@ -14,6 +14,31 @@
@update-drawing-data="handleDrawingDataUpdate"
@update-fields="handleFieldsUpdate"
/>
<div v-if="showDiaryFields" class="diary-fields">
<label class="diary-field">
<span>{{ $t('courtDrawing.durationText') }}</span>
<input
type="text"
v-model="diaryFields.durationText"
:placeholder="$t('courtDrawing.durationTextPlaceholder')"
@input="calculateDurationFromText"
/>
</label>
<label class="diary-field">
<span>{{ $t('courtDrawing.durationMinutes') }}</span>
<input type="number" min="0" :value="diaryFields.duration" readonly />
</label>
<label v-if="showGroupSelect" class="diary-field">
<span>{{ $t('courtDrawing.group') }}</span>
<select v-model="diaryFields.groupId">
<option value="">{{ $t('courtDrawing.selectGroup') }}</option>
<option v-for="group in groups" :key="group.id" :value="String(group.id)">
{{ group.name }}
</option>
</select>
</label>
</div>
<template #footer>
<button class="btn-secondary" @click="handleClose">{{ $t('courtDrawing.cancel') }}</button>
@@ -58,23 +83,57 @@ export default {
initialDescription: {
type: String,
default: null
},
showDiaryFields: {
type: Boolean,
default: false
},
showGroupSelect: {
type: Boolean,
default: false
},
groups: {
type: Array,
default: () => []
},
initialDuration: {
type: [Number, String, null],
default: null
},
initialDurationText: {
type: String,
default: ''
},
initialGroupId: {
type: [String, Number, null],
default: ''
}
},
emits: ['update:modelValue', 'close', 'ok'],
data() {
return {
currentDrawingData: null,
currentFields: null
currentFields: null,
diaryFields: {
duration: null,
durationText: '',
groupId: ''
}
};
},
computed: {
isValid() {
// Mindestens Aufschlag und Zielposition müssen gesetzt sein
return this.currentDrawingData &&
const drawingValid = this.currentDrawingData &&
this.currentDrawingData.selectedStartPosition &&
this.currentDrawingData.strokeType &&
this.currentDrawingData.spinType &&
this.currentDrawingData.targetPosition;
if (!drawingValid) return false;
if (this.showGroupSelect) {
return Boolean(this.diaryFields.groupId);
}
return true;
}
},
watch: {
@@ -98,6 +157,14 @@ export default {
description: this.initialDescription || ''
};
}
this.diaryFields = {
duration: this.initialDuration ?? null,
durationText: this.initialDurationText || '',
groupId: this.initialGroupId !== null && this.initialGroupId !== undefined ? String(this.initialGroupId) : ''
};
if (this.diaryFields.durationText) {
this.calculateDurationFromText();
}
this.$nextTick(() => {
// Warte bis der Dialog vollständig gerendert ist
setTimeout(() => {
@@ -109,6 +176,11 @@ export default {
} else {
// Dialog wird geschlossen - Felder zurücksetzen
this.currentFields = null;
this.diaryFields = {
duration: null,
durationText: '',
groupId: ''
};
}
}
},
@@ -125,6 +197,33 @@ export default {
}
},
methods: {
calculateDurationFromText() {
const input = this.diaryFields.durationText;
if (!input || !input.trim()) {
this.diaryFields.duration = null;
return;
}
const normalized = input.replace(/\s+/g, '').replace(/\*/g, 'x');
let total = 0;
for (const part of normalized.split('+')) {
if (!part) continue;
if (part.includes('x')) {
const [count, minutes] = part.split('x').map(Number);
if (Number.isFinite(count) && Number.isFinite(minutes)) {
total += count * minutes;
}
} else {
const minutes = Number(part);
if (Number.isFinite(minutes)) {
total += minutes;
}
}
}
this.diaryFields.duration = total > 0 ? total : null;
},
handleDrawingDataUpdate(data) {
this.currentDrawingData = { ...data };
},
@@ -140,7 +239,16 @@ export default {
// Sammle alle Daten zusammen
const result = {
drawingData: { ...this.currentDrawingData },
fields: this.currentFields ? { ...this.currentFields } : null,
fields: this.currentFields ? {
...this.currentFields,
duration: this.diaryFields.duration,
durationText: this.diaryFields.durationText,
groupId: this.diaryFields.groupId
} : {
duration: this.diaryFields.duration,
durationText: this.diaryFields.durationText,
groupId: this.diaryFields.groupId
},
code: this.currentDrawingData.code || (this.currentFields ? this.currentFields.code : ''),
name: this.currentFields ? this.currentFields.name : '',
description: this.currentFields ? this.currentFields.description : ''
@@ -190,4 +298,27 @@ export default {
background: var(--surface-muted);
border-color: var(--primary-soft);
}
.diary-fields {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
}
.diary-field {
display: flex;
flex-direction: column;
gap: 0.35rem;
color: var(--text-color);
font-size: 0.875rem;
font-weight: 600;
}
.diary-field input,
.diary-field select {
width: 100%;
}
</style>

View File

@@ -1840,7 +1840,12 @@
"courtDrawing": {
"title": "Tischtennis-Übung konfigurieren",
"cancel": "Abbrechen",
"ok": "OK"
"ok": "OK",
"durationMinutes": "Dauer (Minuten)",
"durationText": "Dauer (Text)",
"durationTextPlaceholder": "z.B. 2x5",
"group": "Gruppe",
"selectGroup": "Gruppe auswählen..."
},
"imageDialog": {
"title": "Bild",

View File

@@ -141,6 +141,16 @@
"filterStandard": "Standard activities",
"filterCustom": "Custom"
},
"courtDrawing": {
"title": "Configure table tennis exercise",
"cancel": "Cancel",
"ok": "OK",
"durationMinutes": "Duration (minutes)",
"durationText": "Duration (text)",
"durationTextPlaceholder": "e.g. 2x5",
"group": "Group",
"selectGroup": "Select group..."
},
"tournaments": {
"numberOfTables": "Number of tables",
"table": "Table",

View File

@@ -299,7 +299,7 @@
<label>{{ $t('diary.activityOrTimeblock') }}</label>
<div class="plan-composer-activity-input">
<button
v-if="addNewItem"
v-if="addNewItem || addNewGroupActivity"
type="button"
class="btn-palette"
@click="showDrawingDialog = true"
@@ -734,6 +734,12 @@
: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"
:show-diary-fields="true"
:show-group-select="addNewGroupActivity && !editingGroupActivity"
:groups="groups"
:initial-duration="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? editingGroupActivity.groupPredefinedActivity.duration : newPlanItem.duration"
:initial-duration-text="editingGroupActivity && editingGroupActivity.groupPredefinedActivity ? editingGroupActivity.groupPredefinedActivity.durationText : newPlanItem.durationText"
:initial-group-id="addNewGroupActivity && !editingGroupActivity ? newPlanItem.groupId : ''"
@ok="handleDrawingDialogOkForDiary"
@close="editingGroupActivity = null"
/>
@@ -2819,6 +2825,8 @@ export default {
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: (result.fields && result.fields.durationText) || this.editingGroupActivity.groupPredefinedActivity.durationText || '',
duration: (result.fields && result.fields.duration) || this.editingGroupActivity.groupPredefinedActivity.duration || null,
drawingData: result.drawingData || null
};
@@ -2841,8 +2849,8 @@ export default {
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,
durationText: (result.fields && result.fields.durationText) || this.editingGroupActivity.groupPredefinedActivity.durationText || '',
duration: (result.fields && result.fields.duration) || this.editingGroupActivity.groupPredefinedActivity.duration || null,
imageLink: this.editingGroupActivity.groupPredefinedActivity.imageLink || '',
drawingData: result.drawingData || null
};
@@ -2900,6 +2908,8 @@ export default {
name: result.name || (result.fields && result.fields.name) || '',
code: code,
description: result.description || (result.fields && result.fields.description) || '',
durationText: (result.fields && result.fields.durationText) || '',
duration: (result.fields && result.fields.duration) || null,
drawingData: result.drawingData || null
};
@@ -2909,6 +2919,15 @@ export default {
// Setze die Aktivität im Formular
this.chooseNewItemSuggestion(activityToUse);
if (result.fields && result.fields.duration !== undefined && result.fields.duration !== null && result.fields.duration !== '') {
this.newPlanItem.duration = result.fields.duration;
}
if (result.fields && typeof result.fields.durationText === 'string') {
this.newPlanItem.durationText = result.fields.durationText;
}
if (this.addNewGroupActivity && result.fields && result.fields.groupId) {
this.newPlanItem.groupId = String(result.fields.groupId);
}
// Erstelle automatisch den Plan-Eintrag
await this.addPlanItem();