From 7942e6108a0a3dd31ae5b1724799628c7d068435 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 28 May 2026 17:40:23 +0200 Subject: [PATCH] feat: Add home/away game labels and participant count to friendly match schedule --- frontend/src/i18n/locales/de.json | 10 +++ frontend/src/views/ScheduleView.vue | 73 ++++++++++++++++++- .../de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt | 58 +++++++-------- 3 files changed, 109 insertions(+), 32 deletions(-) diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index a49453ba..fc000846 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1484,6 +1484,16 @@ "homeTeam": "Heimmannschaft", "guestTeam": "Gastmannschaft", "result": "Ergebnis", + "homeGame": "Heimspiel", + "participants": "Teilnehmer", + "swapButton": "Tauschen", + "away": "Auswärts", + "homeLabel": "Heimspiel", + "awayLabel": "Auswärtsspiel", + "swapSuccess": "Heim/Auswärts getauscht", + "swapFailed": "Heim/Auswärts konnte nicht getauscht werden.", + "plannedShort": "geplant", + "readyShort": "bereit", "ageClass": "Altersklasse", "code": "Code", "homePin": "Heim-PIN", diff --git a/frontend/src/views/ScheduleView.vue b/frontend/src/views/ScheduleView.vue index 99754608..82fb440e 100644 --- a/frontend/src/views/ScheduleView.vue +++ b/frontend/src/views/ScheduleView.vue @@ -103,12 +103,14 @@ {{ $t('schedule.code') }} {{ friendlyOnly ? 'Aktionen' : $t('schedule.homePin') }} {{ $t('schedule.guestPin') }} - + {{ $t('schedule.homeGame') || 'Heimspiel' }} + {{ $t('schedule.participants') || 'Teilnehmer' }} +   @@ -152,6 +154,25 @@ :title="$t('schedule.copyCode') + ': ' + match.code">{{ match.code }} - + + + + + +
+ + {{ isOurClubPlayingHome(match) ? $t('schedule.homeLabel') || $t('schedule.homeGame') : $t('schedule.awayLabel') || $t('schedule.away') }} + + +
+ +
@@ -803,6 +824,20 @@ export default { return teamName.startsWith(this.currentClubName); }, + isOurClubPlayingHome(match) { + const club = (this.currentClubName || '').toString().trim().toLowerCase(); + if (!club || !match) return false; + const homeName = (match.homeTeam?.name || '').toString().toLowerCase(); + const guestName = (match.guestTeam?.name || '').toString().toLowerCase(); + const homeHas = homeName.includes(club); + const guestHas = guestName.includes(club); + if (homeHas && guestHas) return true; // both teams from our club -> treat as home + if (homeHas) return true; + if (guestHas) return false; + // Fallback: if neither includes club name, prefer original startsWith heuristic + return homeName.startsWith(this.currentClubName || '') || false; + }, + // Dialog Helper Methods async showInfo(title, message, details = '', type = 'info') { this.infoDialog = { @@ -1426,6 +1461,40 @@ export default { await this.showInfo('Fehler', getSafeErrorMessage(error, 'Freundschaftsspiel konnte nicht gespeichert werden.'), '', 'error'); } }, + async toggleHomeAway(match) { + if (!match || !match.id) return; + const originalHome = match.homeTeam ? { ...match.homeTeam } : { name: '' }; + const originalGuest = match.guestTeam ? { ...match.guestTeam } : { name: '' }; + // Optimistic UI update: swap locally + try { + if (match.homeTeam && match.guestTeam) { + const tmp = match.homeTeam.name; + match.homeTeam.name = match.guestTeam.name; + match.guestTeam.name = tmp; + } else { + match.homeTeam = { name: originalGuest.name }; + match.guestTeam = { name: originalHome.name }; + } + // Trigger reactivity: replace in matches array + const idx = this.matches.findIndex(m => m.id === match.id); + if (idx !== -1) this.matches.splice(idx, 1, { ...match }); + + const payload = { + homeTeamName: match.homeTeam?.name || '', + guestTeamName: match.guestTeam?.name || '' + }; + await apiClient.put(`/friendly-matches/${this.currentClub}/${match.id}`, payload); + } catch (error) { + console.error('toggleHomeAway error:', error); + // Revert optimistic change + const idx = this.matches.findIndex(m => m.id === match.id); + if (idx !== -1) { + const reverted = { ...match, homeTeam: originalHome, guestTeam: originalGuest }; + this.matches.splice(idx, 1, reverted); + } + await this.showInfo(this.$t('messages.error'), getSafeErrorMessage(error, this.$t('schedule.swapFailed') || 'Heim/Auswärts konnte nicht getauscht werden.'), '', 'error'); + } + }, async deleteFriendlyMatch() { if (!this.friendlyMatchDialog.editingId) return; const confirmed = await this.showConfirm('Freundschaftsspiel löschen', 'Soll dieses Freundschaftsspiel gelöscht werden?', '', 'warning'); diff --git a/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt b/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt index a8df30b8..c3984626 100644 --- a/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt +++ b/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt @@ -7586,6 +7586,20 @@ private fun DiaryPlanEditableCard( modifier = Modifier.width(DiaryPlanColStart), ) Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { + if (hasMainVisual) { + DiaryPlanQuickIconAction( + imageVector = Icons.Filled.Visibility, + contentDescription = tr("diary.showImage", "Bild/Zeichnung anzeigen"), + enabled = !planMutating, + onClick = { + if (drawingRaw != null) { + onViewDrawing(drawingRaw, title) + } else { + mainImageUrl?.let(onOpenImage) + } + }, + ) + } Text( title, fontWeight = FontWeight.SemiBold, @@ -7602,22 +7616,6 @@ private fun DiaryPlanEditableCard( maxLines = 1, ) } - // Always show a quick icon action for viewing/creating a drawing. - DiaryPlanQuickIconAction( - imageVector = Icons.Filled.Visibility, - contentDescription = tr("diary.showImage", "Bild/Zeichnung anzeigen"), - enabled = !planMutating, - onClick = { - if (drawingRaw != null) { - onViewDrawing(drawingRaw, title) - } else if (mainImageUrl != null) { - onOpenImage(mainImageUrl) - } else { - // no visual present yet -> open drawing editor to create one - onOpenDrawing() - } - }, - ) } Text( groupLine ?: "—", @@ -7702,6 +7700,20 @@ private fun DiaryPlanEditableCard( modifier = Modifier.fillMaxWidth().padding(start = DiaryPlanColStart, top = 0.dp, bottom = 0.dp), verticalAlignment = Alignment.CenterVertically, ) { + if (hasNestedVisual) { + DiaryPlanQuickIconAction( + imageVector = Icons.Filled.Visibility, + contentDescription = tr("diary.showImage", "Bild/Zeichnung anzeigen"), + enabled = !planMutating, + onClick = { + if (nestedDrawingRaw != null) { + onViewNestedDrawing(nestedDrawingRaw, line) + } else { + nestedImageUrl?.let(onOpenImage) + } + }, + ) + } Text( "· $line", style = MaterialTheme.typography.caption, @@ -7720,20 +7732,6 @@ private fun DiaryPlanEditableCard( } val nid = ga.id if (nid != null) { - if (hasNestedVisual) { - DiaryPlanQuickIconAction( - imageVector = Icons.Filled.Visibility, - contentDescription = tr("diary.showImage", "Bild/Zeichnung anzeigen"), - enabled = !planMutating, - onClick = { - if (nestedDrawingRaw != null) { - onViewNestedDrawing(nestedDrawingRaw, line) - } else { - nestedImageUrl?.let(onOpenImage) - } - }, - ) - } DiaryPlanQuickIconAction( imageVector = Icons.Filled.Edit, contentDescription = tr("common.edit", "Bearbeiten"),