feat(TrainingGroup): add excludeFromQuickDiaryCreate feature and update related logic
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Introduced a new boolean field `excludeFromQuickDiaryCreate` in the TrainingGroup model to control group visibility in quick diary creation. - Updated the `updateTrainingGroup` service method to handle the new field, allowing for dynamic updates based on user input. - Enhanced the TrainingTimesTab component to include a checkbox for excluding groups from quick diary creation, improving user interaction. - Updated localization files to include new strings related to the exclude feature, ensuring clarity in multiple languages. - Refactored logic in DiaryView and mobile app to consider the new exclusion criteria when suggesting training slots.
This commit is contained in:
@@ -10,12 +10,22 @@
|
||||
>
|
||||
<div class="group-header">
|
||||
<h3>{{ group.name }}</h3>
|
||||
<button
|
||||
@click="showAddTimeForm(group.id)"
|
||||
class="btn-primary btn-small"
|
||||
>
|
||||
{{ $t('trainingTimesTab.addTime') }}
|
||||
</button>
|
||||
<div class="group-header-actions">
|
||||
<label class="exclude-quick-diary-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="Boolean(group.excludeFromQuickDiaryCreate)"
|
||||
@change="onExcludeQuickChange(group, $event)"
|
||||
/>
|
||||
<span>{{ $t('trainingTimesTab.excludeFromQuickDiaryCreate') }}</span>
|
||||
</label>
|
||||
<button
|
||||
@click="showAddTimeForm(group.id)"
|
||||
class="btn-primary btn-small"
|
||||
>
|
||||
{{ $t('trainingTimesTab.addTime') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Time Form -->
|
||||
@@ -165,7 +175,12 @@ export default {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await apiClient.get(`/training-times/${this.currentClub}`);
|
||||
this.groups = Array.isArray(response.data) ? response.data : [];
|
||||
this.groups = Array.isArray(response.data)
|
||||
? response.data.map((g) => ({
|
||||
...g,
|
||||
excludeFromQuickDiaryCreate: Boolean(g.excludeFromQuickDiaryCreate),
|
||||
}))
|
||||
: [];
|
||||
} catch (error) {
|
||||
console.error('[loadTrainingTimes] Error:', error);
|
||||
const msg = getSafeErrorMessage(error, 'Fehler beim Laden der Trainingszeiten');
|
||||
@@ -176,6 +191,23 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async onExcludeQuickChange(group, event) {
|
||||
const exclude = Boolean(event.target.checked);
|
||||
try {
|
||||
await apiClient.put(`/training-groups/${this.currentClub}/${group.id}`, {
|
||||
name: group.name,
|
||||
sortOrder: group.sortOrder,
|
||||
excludeFromQuickDiaryCreate: exclude,
|
||||
});
|
||||
group.excludeFromQuickDiaryCreate = exclude;
|
||||
} catch (error) {
|
||||
console.error('[onExcludeQuickChange]', error);
|
||||
event.target.checked = !exclude;
|
||||
const msg = getSafeErrorMessage(error, this.$t('trainingTimesTab.excludeFromQuickDiarySaveError'));
|
||||
alert(msg);
|
||||
}
|
||||
},
|
||||
|
||||
showAddTimeForm(groupId) {
|
||||
this.addTimeFormGroupId = groupId;
|
||||
this.newTime = {
|
||||
@@ -267,7 +299,8 @@ export default {
|
||||
|
||||
getWeekdayName(weekday) {
|
||||
const days = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
|
||||
return days[weekday] || '';
|
||||
const i = Number(weekday);
|
||||
return days[i] || '';
|
||||
},
|
||||
|
||||
formatTime(time) {
|
||||
@@ -311,11 +344,37 @@ export default {
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.group-header-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.exclude-quick-diary-label {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: #444;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
max-width: 22rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.exclude-quick-diary-label input {
|
||||
margin-top: 0.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
|
||||
@@ -2402,7 +2402,9 @@
|
||||
"noTimes": "Keine Trainingszeiten definiert",
|
||||
"editTime": "Trainingszeit bearbeiten",
|
||||
"save": "Speichern",
|
||||
"saveError": "Fehler beim Speichern der Trainingszeit"
|
||||
"saveError": "Fehler beim Speichern der Trainingszeit",
|
||||
"excludeFromQuickDiaryCreate": "Vom Schnellanlegen (Tagebuch) ausnehmen",
|
||||
"excludeFromQuickDiarySaveError": "Die Einstellung konnte nicht gespeichert werden."
|
||||
},
|
||||
"trainingGroupsTab": {
|
||||
"groups": "Gruppen",
|
||||
|
||||
@@ -2464,7 +2464,9 @@
|
||||
"noTimes": "Keine Trainingszeiten definiert",
|
||||
"editTime": "Trainingszeit bearbeiten",
|
||||
"save": "Speichern",
|
||||
"saveError": "Fehler beim Speichern der Trainingszeit"
|
||||
"saveError": "Fehler beim Speichern der Trainingszeit",
|
||||
"excludeFromQuickDiaryCreate": "Vom Schnellanlegen (Tagebuch) ausnehmen",
|
||||
"excludeFromQuickDiarySaveError": "Die Einstellung konnte nicht gespeichert werden."
|
||||
},
|
||||
"trainingGroupsTab": {
|
||||
"groups": "Gruppen",
|
||||
|
||||
@@ -1640,14 +1640,27 @@ export default {
|
||||
return `${y}-${m}-${day}`;
|
||||
},
|
||||
|
||||
/** Wochentag aus API (0=So … 6=Sa) zuverlässig als Zahl – vermeidet Strict-===-Fehler bei String-Werten. */
|
||||
trainingWeekdayNumber(tt) {
|
||||
if (tt == null || tt.weekday === undefined || tt.weekday === null) return NaN;
|
||||
const w = Number(tt.weekday);
|
||||
return Number.isFinite(w) ? w : parseInt(String(tt.weekday), 10);
|
||||
},
|
||||
|
||||
trainingStartToMinutes(start) {
|
||||
const s = String(start || '').trim();
|
||||
const base = s.length >= 5 ? s.substring(0, 5) : s;
|
||||
const parts = base.split(':');
|
||||
if (parts.length < 2) return null;
|
||||
const h = parseInt(parts[0], 10);
|
||||
const m = parseInt(parts[1], 10);
|
||||
if (!Number.isFinite(h) || !Number.isFinite(m)) return null;
|
||||
return h * 60 + m;
|
||||
},
|
||||
|
||||
findNextQuickSlotAcrossGroups(groups) {
|
||||
if (!Array.isArray(groups) || groups.length === 0) return null;
|
||||
const sorted = [...groups].sort((a, b) => {
|
||||
const oa = Number.isFinite(Number(a.sortOrder)) ? Number(a.sortOrder) : 1e9;
|
||||
const ob = Number.isFinite(Number(b.sortOrder)) ? Number(b.sortOrder) : 1e9;
|
||||
if (oa !== ob) return oa - ob;
|
||||
return (Number(a.id) || 0) - (Number(b.id) || 0);
|
||||
});
|
||||
const eligible = groups.filter((g) => !g.excludeFromQuickDiaryCreate);
|
||||
const existing = new Set((this.dates || []).map((d) => this.diaryDateKey(d)));
|
||||
const today = new Date();
|
||||
for (let dayOffset = 0; dayOffset <= 366; dayOffset++) {
|
||||
@@ -1655,19 +1668,37 @@ export default {
|
||||
const checkWeekday = checkDate.getDay();
|
||||
const dateStr = this.localYyyyMmDd(checkDate);
|
||||
if (existing.has(dateStr)) continue;
|
||||
for (const g of sorted) {
|
||||
const times = (g.trainingTimes || [])
|
||||
.filter((tt) => tt.weekday === checkWeekday && tt.startTime && String(tt.startTime).trim());
|
||||
if (!times.length) continue;
|
||||
times.sort((a, b) => String(a.startTime).localeCompare(String(b.startTime))
|
||||
|| (Number(a.id) || 0) - (Number(b.id) || 0));
|
||||
const time = times[0];
|
||||
return {
|
||||
date: dateStr,
|
||||
startTime: this.formatTimeForInput(time.startTime),
|
||||
endTime: this.formatTimeForInput(time.endTime),
|
||||
};
|
||||
const candidates = [];
|
||||
for (const g of eligible) {
|
||||
for (const tt of g.trainingTimes || []) {
|
||||
if (this.trainingWeekdayNumber(tt) !== checkWeekday) continue;
|
||||
if (!tt.startTime || !String(tt.startTime).trim()) continue;
|
||||
const startMin = this.trainingStartToMinutes(tt.startTime);
|
||||
if (startMin == null) continue;
|
||||
candidates.push({
|
||||
startMin,
|
||||
dateStr,
|
||||
sortOrder: Number.isFinite(Number(g.sortOrder)) ? Number(g.sortOrder) : 1e9,
|
||||
groupId: Number(g.id) || 0,
|
||||
timeId: Number(tt.id) || 0,
|
||||
startTime: this.formatTimeForInput(tt.startTime),
|
||||
endTime: this.formatTimeForInput(tt.endTime),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!candidates.length) continue;
|
||||
candidates.sort((a, b) => {
|
||||
if (a.startMin !== b.startMin) return a.startMin - b.startMin;
|
||||
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder;
|
||||
if (a.groupId !== b.groupId) return a.groupId - b.groupId;
|
||||
return a.timeId - b.timeId;
|
||||
});
|
||||
const best = candidates[0];
|
||||
return {
|
||||
date: best.dateStr,
|
||||
startTime: best.startTime,
|
||||
endTime: best.endTime,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -1848,7 +1879,7 @@ export default {
|
||||
const checkWeekday = checkDate.getDay();
|
||||
|
||||
// Finde Trainingszeiten für diesen Wochentag
|
||||
const timesForWeekday = sortedTimes.filter(tt => tt.weekday === checkWeekday);
|
||||
const timesForWeekday = sortedTimes.filter(tt => this.trainingWeekdayNumber(tt) === checkWeekday);
|
||||
|
||||
if (timesForWeekday.length > 0) {
|
||||
// Nimm die erste Trainingszeit für diesen Tag
|
||||
|
||||
Reference in New Issue
Block a user