feat(matches): add fixture type handling and notes to manual match creation; update localization for match types
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s

This commit is contained in:
Torsten Schulz (local)
2026-08-20 08:39:52 +02:00
parent 18ba120927
commit eb1c4d5842
7 changed files with 140 additions and 11 deletions

View File

@@ -1482,7 +1482,14 @@
"addMatch": "Spiel hinzufügen",
"manualMatchDescription": "Manuell gepflegte Spiele erscheinen neben den importierten Click-TT-Terminen.",
"fixtureType": "Spieltyp:",
"regularMatch": "Reguläres Spiel",
"cupMatch": "Pokalspiel",
"friendlyMatch": "Freundschaftsspiel",
"fixtureTypeDescription": {
"league": "Reguläre und Pokalspiele werden im Spielplan gespeichert.",
"cup": "Reguläre und Pokalspiele werden im Spielplan gespeichert.",
"friendly": "Freundschaftsspiele werden separat verwaltet; die Angaben werden in das passende Formular übernommen."
},
"ownTeam": "Eigene Mannschaft",
"selectTeam": "Mannschaft wählen",
"opponent": "Gegner",
@@ -1495,7 +1502,10 @@
"notes": "Notizen",
"saving": "Speichert…",
"manualMatchRequired": "Bitte Mannschaft, Gegner, Datum und Uhrzeit ausfüllen.",
"manualMatchSaved": "Pokalspiel wurde hinzugefügt.",
"manualMatchSaved": {
"league": "Reguläres Spiel wurde hinzugefügt.",
"cup": "Pokalspiel wurde hinzugefügt."
},
"manualMatchSaveFailed": "Pokalspiel konnte nicht gespeichert werden.",
"galleryLoading": "Galerie wird geladen…",
"gallery": "Mitglieder-Galerie",

View File

@@ -1459,7 +1459,14 @@
"addMatch": "Add match",
"manualMatchDescription": "Manually maintained matches appear alongside imported Click-TT fixtures.",
"fixtureType": "Fixture type:",
"regularMatch": "Regular match",
"cupMatch": "Cup match",
"friendlyMatch": "Friendly match",
"fixtureTypeDescription": {
"league": "Regular and cup matches are stored in the schedule.",
"cup": "Regular and cup matches are stored in the schedule.",
"friendly": "Friendly matches are managed separately; the details are transferred to the appropriate form."
},
"ownTeam": "Own team",
"selectTeam": "Select team",
"opponent": "Opponent",
@@ -1472,7 +1479,10 @@
"notes": "Notes",
"saving": "Saving…",
"manualMatchRequired": "Please fill in the team, opponent, date and time.",
"manualMatchSaved": "Cup match added.",
"manualMatchSaved": {
"league": "Regular match added.",
"cup": "Cup match added."
},
"manualMatchSaveFailed": "Cup match could not be saved.",
"galleryLoading": "Loading gallery…",
"gallery": "Member gallery",

View File

@@ -275,7 +275,13 @@
<BaseDialog v-model="manualMatchDialog.isOpen" :title="$t('schedule.addMatch')" :max-width="620" @close="closeManualMatchDialog">
<form class="manual-match-form" @submit.prevent="saveManualMatch">
<p class="manual-match-intro">{{ $t('schedule.manualMatchDescription') }}</p>
<div class="manual-match-type">{{ $t('schedule.fixtureType') }} <strong>{{ $t('schedule.cupMatch') }}</strong></div>
<fieldset class="manual-match-type">
<legend>{{ $t('schedule.fixtureType') }}</legend>
<label><input v-model="manualMatchDialog.form.fixtureType" type="radio" value="league"> {{ $t('schedule.regularMatch') }}</label>
<label><input v-model="manualMatchDialog.form.fixtureType" type="radio" value="cup"> {{ $t('schedule.cupMatch') }}</label>
<label><input v-model="manualMatchDialog.form.fixtureType" type="radio" value="friendly"> {{ $t('schedule.friendlyMatch') }}</label>
</fieldset>
<p class="manual-match-type-hint">{{ $t(`schedule.fixtureTypeDescription.${manualMatchDialog.form.fixtureType}`) }}</p>
<p v-if="manualMatchDialog.error" class="manual-match-error" role="alert">{{ manualMatchDialog.error }}</p>
<div class="manual-match-grid">
<label><span>{{ $t('schedule.ownTeam') }} *</span><select v-model="manualMatchDialog.form.clubTeamId" required><option value="">{{ $t('schedule.selectTeam') }}</option><option v-for="team in teams" :key="team.id" :value="String(team.id)">{{ team.name }} <template v-if="team.league?.name">({{ team.league.name }})</template></option></select></label>
@@ -888,7 +894,7 @@ export default {
showImportModal: false,
manualMatchDialog: {
isOpen: false, saving: false, error: '',
form: { clubTeamId: '', opponentName: '', date: new Date().toISOString().slice(0, 10), time: '', homeAway: 'home', locationName: '', locationAddress: '', locationZip: '', locationCity: '', notes: '' }
form: { fixtureType: 'league', clubTeamId: '', opponentName: '', date: new Date().toISOString().slice(0, 10), time: '', homeAway: 'home', locationName: '', locationAddress: '', locationZip: '', locationCity: '', notes: '' }
},
selectedFile: null,
teams: [],
@@ -984,7 +990,7 @@ export default {
return (Array.isArray(matches) ? matches : []).filter((match) => !match?.isFriendly);
},
resetManualMatchDialogForm() {
this.manualMatchDialog.form = { clubTeamId: this.selectedTeam?.id ? String(this.selectedTeam.id) : '', opponentName: '', date: new Date().toISOString().slice(0, 10), time: '', homeAway: 'home', locationName: '', locationAddress: '', locationZip: '', locationCity: '', notes: '' };
this.manualMatchDialog.form = { fixtureType: 'league', clubTeamId: this.selectedTeam?.id ? String(this.selectedTeam.id) : '', opponentName: '', date: new Date().toISOString().slice(0, 10), time: '', homeAway: 'home', locationName: '', locationAddress: '', locationZip: '', locationCity: '', notes: '' };
this.manualMatchDialog.error = '';
},
openManualMatchDialog() {
@@ -1005,11 +1011,33 @@ export default {
this.manualMatchDialog.saving = true;
this.manualMatchDialog.error = '';
try {
if (form.fixtureType === 'friendly') {
const ownTeam = this.teams.find((team) => String(team.id) === String(form.clubTeamId));
if (!ownTeam?.name) {
this.manualMatchDialog.error = this.$t('schedule.manualMatchRequired');
return;
}
const prefill = {
...this.emptyFriendlyMatchForm(),
date: form.date,
time: form.time,
homeTeamName: form.homeAway === 'home' ? ownTeam.name : form.opponentName,
guestTeamName: form.homeAway === 'home' ? form.opponentName : ownTeam.name,
locationName: form.locationName,
locationAddress: form.locationAddress,
locationZip: form.locationZip,
locationCity: form.locationCity,
};
this.manualMatchDialog.isOpen = false;
this.resetManualMatchDialogForm();
await this.openFriendlyMatchDialog(null, prefill);
return;
}
await apiClient.post(`/matches/${this.currentClub}`, { ...form, clubTeamId: Number(form.clubTeamId) });
this.manualMatchDialog.isOpen = false;
this.resetManualMatchDialogForm();
await this.refreshScheduleData();
this.showInfo(this.$t('messages.success'), this.$t('schedule.manualMatchSaved'), '', 'success');
this.showInfo(this.$t('messages.success'), this.$t(`schedule.manualMatchSaved.${form.fixtureType}`), '', 'success');
} catch (error) {
this.manualMatchDialog.error = getSafeErrorMessage(error, this.$t('schedule.manualMatchSaveFailed'));
} finally {
@@ -1730,7 +1758,7 @@ export default {
this.friendlyMatchDialog.homeMembers = members;
this.friendlyMatchDialog.guestMembers = [];
},
async openFriendlyMatchDialog(match = null) {
async openFriendlyMatchDialog(match = null, prefill = null) {
await this.loadFriendlyMembers(match);
this.friendlyMatchDialog.match = match?.isFriendly ? match : null;
this.friendlyMatchDialog.editingId = match?.isFriendly ? match.id : null;
@@ -1756,7 +1784,7 @@ export default {
guestParticipants: [...this.parseFriendlyArray(match.guestParticipants)],
resultDetails: [...this.parseFriendlyArray(match.resultDetails)]
}
: this.emptyFriendlyMatchForm();
: (prefill || this.emptyFriendlyMatchForm());
this.syncFriendlyEditDoubleRows();
await this.loadFriendlyVenues(match);
const selectedVenue = this.findFriendlyVenueForForm();
@@ -3892,8 +3920,10 @@ li {
.manual-match-form { display: flex; flex-direction: column; gap: 1rem; }
.manual-match-intro { margin: 0; color: var(--text-muted, #6c757d); }
.manual-match-type { padding: .65rem .8rem; border-left: 3px solid var(--primary-color); background: var(--surface-muted, #f5f7f6); font-size: .92rem; }
.manual-match-type strong { margin-left: .4rem; }
.manual-match-type { display: flex; flex-wrap: wrap; gap: .6rem 1rem; margin: 0; padding: .65rem .8rem; border: 1px solid var(--border-color); border-left: 3px solid var(--primary-color); background: var(--surface-muted, #f5f7f6); font-size: .92rem; }
.manual-match-type legend { width: 100%; padding: 0 .2rem; font-size: .86rem; font-weight: 600; }
.manual-match-type label { font-weight: 600; cursor: pointer; }
.manual-match-type-hint { margin: -.35rem 0 0; color: var(--text-muted, #6c757d); font-size: .86rem; }
.manual-match-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .85rem; }
.manual-match-grid label, .manual-match-notes { display: grid; gap: .35rem; font-weight: 600; font-size: .9rem; }
.manual-match-grid input, .manual-match-grid select, .manual-match-notes textarea { width: 100%; box-sizing: border-box; padding: .6rem .7rem; border: 1px solid var(--border-color); border-radius: 4px; background: var(--background-light, #fff); color: inherit; font: inherit; }