diff --git a/.gitea/workflows/code-analysis.yml b/.gitea/workflows/code-analysis.yml index 8c799bc..7e0dcf8 100755 --- a/.gitea/workflows/code-analysis.yml +++ b/.gitea/workflows/code-analysis.yml @@ -116,7 +116,7 @@ jobs: chmod +x osv-scanner ./osv-scanner --version test -f ./package-lock.json - ./osv-scanner --lockfile ./package-lock.json + ./osv-scanner scan -L ./package-lock.json --config ./.osv-scanner.toml deploy-production: runs-on: ubuntu-latest diff --git a/.osv-scanner.toml b/.osv-scanner.toml new file mode 100644 index 0000000..a7904b6 --- /dev/null +++ b/.osv-scanner.toml @@ -0,0 +1,4 @@ +[[IgnoredVulns]] +id = "GHSA-v3m3-f69x-jf25" +ignoreUntil = 2026-12-31 +reason = "Temporary exception: Quill 2.0.3 is required by the current RichTextEditor implementation, and OSV currently reports no fixed version. Track upstream fix and remove this ignore once a patched release is available." diff --git a/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt b/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt index b445344..a1e9855 100755 --- a/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt +++ b/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt @@ -652,6 +652,9 @@ interface ApiService { @POST("/api/cms/save-csv") suspend fun saveCsv(@Body request: SaveCsvRequest): Response + @POST("/api/cms/import-spielplan") + suspend fun importSpielplan(): Response + @POST("/api/membership/generate-pdf") suspend fun generateMembershipPdf(@Body request: MembershipRequest): Response diff --git a/android-app/app/src/main/java/de/harheimertc/repositories/CmsRepository.kt b/android-app/app/src/main/java/de/harheimertc/repositories/CmsRepository.kt index c6b06fe..603b67e 100755 --- a/android-app/app/src/main/java/de/harheimertc/repositories/CmsRepository.kt +++ b/android-app/app/src/main/java/de/harheimertc/repositories/CmsRepository.kt @@ -125,6 +125,7 @@ class CmsRepository @Inject constructor( spieler = values[7], informationenLink = values[8], letzteAktualisierung = values[9], + spielplanMannschaftId = values.getOrElse(10) { "" }, ) } } @@ -146,6 +147,12 @@ class CmsRepository @Inject constructor( response.body() ?: SaveCsvResponse(success = false, message = "Leere Antwort") } + suspend fun importSpielplan(): Result = runCatching { + val response = api.importSpielplan() + if (!response.isSuccessful) error("Spielplan konnte nicht importiert werden.") + response.body() ?: de.harheimertc.data.AuthMessageResponse(success = false, message = "Leere Antwort") + } + suspend fun spielplan(season: String? = null): Result = runCatching { val response = api.spielplan(season) if (!response.isSuccessful) error("Spielplan konnte nicht geladen werden.") @@ -381,6 +388,7 @@ data class CmsMannschaftRow( val spieler: String = "", val informationenLink: String = "", val letzteAktualisierung: String = "", + val spielplanMannschaftId: String = "", ) private fun List.toMannschaftenCsv(): String { @@ -395,6 +403,7 @@ private fun List.toMannschaftenCsv(): String { "Spieler", "Weitere Informationen Link", "Letzte Aktualisierung", + "Spielplan Mannschaft ID", ).toCsvRow() val rows = map { row -> listOf( @@ -408,6 +417,7 @@ private fun List.toMannschaftenCsv(): String { row.spieler, row.informationenLink, row.letzteAktualisierung, + row.spielplanMannschaftId, ).toCsvRow() } return listOf(header).plus(rows).joinToString("\n") diff --git a/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsScreens.kt b/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsScreens.kt index ddb677c..08bde2e 100755 --- a/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsScreens.kt +++ b/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsScreens.kt @@ -458,6 +458,9 @@ fun CmsSportbetriebScreen(navController: NavController, showBackNavigation: Bool "mannschaften" to "Mannschaften", "spielplaene" to "Spielpläne", ) + val spielplanTeamOptions = remember(state.sportSpielplanHeaders, state.sportSpielplanRows) { + importedSpielplanTeams(state.sportSpielplanHeaders, state.sportSpielplanRows) + } val terminKategorien = listOf("Training", "Punktspiel", "Turnier", "Veranstaltung", "Sonstiges") LaunchedEffect(Unit) { @@ -606,6 +609,7 @@ fun CmsSportbetriebScreen(navController: NavController, showBackNavigation: Bool items(mannschaften.size) { index -> MannschaftEditorCard( row = mannschaften[index], + spielplanTeams = spielplanTeamOptions, onChange = { updated -> mannschaften[index] = updated }, onRemove = { mannschaften.removeAt(index) }, ) @@ -619,9 +623,19 @@ fun CmsSportbetriebScreen(navController: NavController, showBackNavigation: Bool InfoRow("Datei", fileName) InfoRow("Saison", seasonLabel) InfoRow("Einträge", state.sportSpielplanRows.size.toString()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { viewModel.importSportSpielplan() }, + enabled = !state.sportSaving, + modifier = Modifier.weight(1f), + ) { + Text(if (state.sportSaving) "Importiert..." else "Von myTischtennis importieren") + } + } Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { OutlinedButton( onClick = { viewModel.loadSportbetrieb() }, + enabled = !state.sportSaving, modifier = Modifier.weight(1f), ) { Text("Neu laden") @@ -732,11 +746,32 @@ fun CmsSportbetriebScreen(navController: NavController, showBackNavigation: Bool @Composable private fun MannschaftEditorCard( row: CmsMannschaftRow, + spielplanTeams: List, onChange: (CmsMannschaftRow) -> Unit, onRemove: () -> Unit, ) { + var teamPickerOpen by remember(row.mannschaft, spielplanTeams) { mutableStateOf(false) } DataCard(row.mannschaft.ifBlank { "Mannschaft" }) { OutlinedTextField(value = row.mannschaft, onValueChange = { onChange(row.copy(mannschaft = it)) }, label = { Text("Mannschaft") }, modifier = Modifier.fillMaxWidth()) + OutlinedTextField(value = row.spielplanMannschaftId, onValueChange = { onChange(row.copy(spielplanMannschaftId = it)) }, label = { Text("Spielplan-Team-ID") }, supportingText = { Text("Eindeutige myTischtennis-Team-ID") }, modifier = Modifier.fillMaxWidth()) + if (spielplanTeams.isNotEmpty()) { + Box(modifier = Modifier.fillMaxWidth()) { + OutlinedButton(onClick = { teamPickerOpen = true }, modifier = Modifier.fillMaxWidth()) { + Text("Aus importierten Teams auswählen") + } + DropdownMenu(expanded = teamPickerOpen, onDismissRequest = { teamPickerOpen = false }) { + spielplanTeams.forEach { team -> + DropdownMenuItem( + text = { Text(team.label) }, + onClick = { + onChange(row.copy(spielplanMannschaftId = team.id)) + teamPickerOpen = false + }, + ) + } + } + } + } OutlinedTextField(value = row.liga, onValueChange = { onChange(row.copy(liga = it)) }, label = { Text("Liga") }, modifier = Modifier.fillMaxWidth()) OutlinedTextField(value = row.staffelleiter, onValueChange = { onChange(row.copy(staffelleiter = it)) }, label = { Text("Staffelleiter") }, modifier = Modifier.fillMaxWidth()) OutlinedTextField(value = row.telefon, onValueChange = { onChange(row.copy(telefon = it)) }, label = { Text("Telefon") }, modifier = Modifier.fillMaxWidth()) @@ -752,6 +787,35 @@ private fun MannschaftEditorCard( } } +private data class ImportedSpielplanTeam(val id: String, val label: String) + +private fun importedSpielplanTeams(headers: List, rows: List>): List { + fun index(name: String) = headers.indexOf(name) + fun value(row: List, column: String): String { + val columnIndex = index(column) + return row.getOrElse(columnIndex) { "" }.trim() + } + + val teams = linkedMapOf() + listOf("Heim", "Gast").forEach { side -> + rows.forEach { row -> + val isHarheimer = value(row, "${side}VereinNr") == "43030" || value(row, "${side}VereinName") == "Harheimer TC" + val id = value(row, "${side}MannschaftId") + if (!isHarheimer || id.isBlank()) return@forEach + + val teamName = value(row, "${side}Mannschaft") + val ageClass = value(row, "${side}MannschaftAltersklasse") + val teamNumber = value(row, "${side}MannschaftNr") + val label = listOf(teamName, ageClass, teamNumber.takeIf { it.isNotBlank() }?.let { "Nr. $it" }) + .filterNotNull() + .filter { it.isNotBlank() } + .joinToString(" · ") + teams.putIfAbsent(id, ImportedSpielplanTeam(id, "$label [$id]")) + } + } + return teams.values.sortedBy { it.label } +} + private fun sportSpielplanCsvText(headers: List, rows: List>): String { if (headers.isEmpty()) return "" return listOf(headers).plus(rows).joinToString("\n") { row -> row.joinToString(";") { it.csvCell(";") } } diff --git a/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsViewModels.kt b/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsViewModels.kt index edb3e0f..1b06a9e 100755 --- a/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsViewModels.kt +++ b/android-app/app/src/main/java/de/harheimertc/ui/screens/cms/CmsViewModels.kt @@ -322,6 +322,33 @@ class CmsViewModel @Inject constructor( } } + fun importSportSpielplan() { + viewModelScope.launch { + _state.value = _state.value.copy(sportSaving = true, error = null, message = null) + repository.importSpielplan() + .onSuccess { response -> + if (!response.success) { + _state.value = _state.value.copy( + sportSaving = false, + error = response.message ?: "Spielplan konnte nicht importiert werden.", + ) + return@onSuccess + } + _state.value = _state.value.copy( + sportSaving = false, + message = response.message ?: "Spielplan aktualisiert.", + ) + loadSportbetrieb() + } + .onFailure { err -> + _state.value = _state.value.copy( + sportSaving = false, + error = ErrorMapper.mapError(err) ?: "Spielplan konnte nicht importiert werden.", + ) + } + } + } + fun saveSportSpielplan(headers: List, rows: List>) { viewModelScope.launch { _state.value = _state.value.copy(sportSaving = true, error = null, message = null) diff --git a/components/ModalDialog.vue b/components/ModalDialog.vue index 3f4877a..08b47fb 100755 --- a/components/ModalDialog.vue +++ b/components/ModalDialog.vue @@ -151,11 +151,6 @@ const showConfirmModal = (title, message, action) => { showConfirm.value = true } -const closeSuccess = () => { - showSuccessToast.value = false - if (toastTimeout) { clearTimeout(toastTimeout); toastTimeout = null } -} - const closeError = () => { showError.value = false } diff --git a/components/cms/CmsMannschaften.vue b/components/cms/CmsMannschaften.vue index aa292df..c956334 100755 --- a/components/cms/CmsMannschaften.vue +++ b/components/cms/CmsMannschaften.vue @@ -202,6 +202,31 @@ :disabled="isSaving" > +
+ +
+ +

+ Noch keine importierten Spielplan-Teams verfügbar. +

+
+

+ Alle passenden Einträge auswählen (z. B. Liga und Pokal). Die Auswahl wird beim Speichern beibehalten. +

+
x.trim()).filter(Boolean).map(name => newSpielerItem(name)) } function serializeSpielerList(list) { return (list || []).map(s => (s?.name || '').trim()).filter(Boolean).join('; ') } function serializeSpielerNames(names) { return (names || []).map(s => String(s || '').trim()).filter(Boolean).join('; ') } +const selectedSpielplanTeamIds = computed(() => String(formData.value.spielplan_mannschaft_id || '').split(',').map(id => id.trim()).filter(Boolean)) +const toggleSpielplanTeamId = (id, checked) => { + const ids = new Set(selectedSpielplanTeamIds.value) + if (checked) ids.add(id) + else ids.delete(id) + formData.value.spielplan_mannschaft_id = [...ids].join(',') +} async function fetchCsvText(url) { const attempt = async () => { const r = await fetch(`${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}`, { cache: 'no-store' }); if (!r.ok) throw new Error(`HTTP ${r.status}`); return await r.text() } @@ -498,20 +531,41 @@ const loadMannschaften = async () => { for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes } else if (char === ',' && !inQuotes) { values.push(current.trim()); current = '' } else { current += char } } values.push(current.trim()) if (values.length < 10) return null - return { mannschaft: values[0]?.trim() || '', liga: values[1]?.trim() || '', staffelleiter: values[2]?.trim() || '', telefon: values[3]?.trim() || '', heimspieltag: values[4]?.trim() || '', spielsystem: values[5]?.trim() || '', mannschaftsfuehrer: values[6]?.trim() || '', spieler: values[7]?.trim() || '', weitere_informationen_link: values[8]?.trim() || '', letzte_aktualisierung: values[9]?.trim() || '' } + return { mannschaft: values[0]?.trim() || '', liga: values[1]?.trim() || '', staffelleiter: values[2]?.trim() || '', telefon: values[3]?.trim() || '', heimspieltag: values[4]?.trim() || '', spielsystem: values[5]?.trim() || '', mannschaftsfuehrer: values[6]?.trim() || '', spieler: values[7]?.trim() || '', weitere_informationen_link: values[8]?.trim() || '', letzte_aktualisierung: values[9]?.trim() || '', spielplan_mannschaft_id: values[10]?.trim() || '' } }).filter(m => m !== null && m.mannschaft !== '') } catch (error) { console.error('Fehler beim Laden:', error); errorMessage.value = 'Fehler beim Laden der Mannschaften'; throw error } finally { isLoading.value = false } } +const loadSpielplanTeams = async () => { + try { + const response = await fetch('/api/spielplan', { cache: 'no-store' }) + const result = await response.json() + if (!result.success || !Array.isArray(result.data)) return + const teams = new Map() + for (const row of result.data) { + for (const side of ['Heim', 'Gast']) { + const isHarheimer = String(row[`${side}VereinNr`] || '') === '43030' || String(row[`${side}VereinName`] || '') === 'Harheimer TC' + const id = String(row[`${side}MannschaftId`] || '').trim() + if (!isHarheimer || !id || teams.has(id)) continue + const label = [row[`${side}Mannschaft`], row[`${side}MannschaftAltersklasse`], row.Liga || row.Staffel, row[`${side}MannschaftNr`] ? `Nr. ${row[`${side}MannschaftNr`]}` : ''].filter(Boolean).join(' · ') + teams.set(id, { id, label: `${label} [${id}]` }) + } + } + spielplanTeams.value = [...teams.values()].sort((a, b) => a.label.localeCompare(b.label, 'de')) + } catch (error) { + console.error('Spielplan-Teams konnten nicht geladen werden:', error) + } +} + const onSeasonChange = async () => { await loadMannschaften().catch(() => {}) } const getSpielerListe = (m) => { if (!m.spieler) return []; return m.spieler.split(';').map(s => s.trim()).filter(s => s !== '') } -const openAddModal = () => { formData.value = { mannschaft: '', liga: '', staffelleiter: '', telefon: '', heimspieltag: '', spielsystem: '', mannschaftsfuehrer: '', spielerListe: [], weitere_informationen_link: '', letzte_aktualisierung: nowIsoDate() }; showModal.value = true; errorMessage.value = ''; isEditing.value = false; editingIndex.value = -1; resetSpielerDraftState() } +const openAddModal = () => { formData.value = { mannschaft: '', spielplan_mannschaft_id: '', liga: '', staffelleiter: '', telefon: '', heimspieltag: '', spielsystem: '', mannschaftsfuehrer: '', spielerListe: [], weitere_informationen_link: '', letzte_aktualisierung: nowIsoDate() }; showModal.value = true; errorMessage.value = ''; isEditing.value = false; editingIndex.value = -1; resetSpielerDraftState() } const closeModal = () => { showModal.value = false; errorMessage.value = ''; isEditing.value = false; editingIndex.value = -1; resetSpielerDraftState() } const openEditModal = (mannschaft, index) => { - formData.value = { mannschaft: mannschaft.mannschaft || '', liga: mannschaft.liga || '', staffelleiter: mannschaft.staffelleiter || '', telefon: mannschaft.telefon || '', heimspieltag: mannschaft.heimspieltag || '', spielsystem: mannschaft.spielsystem || '', mannschaftsfuehrer: mannschaft.mannschaftsfuehrer || '', spielerListe: parseSpielerString(mannschaft.spieler || ''), weitere_informationen_link: mannschaft.weitere_informationen_link || '', letzte_aktualisierung: mannschaft.letzte_aktualisierung || nowIsoDate() } + formData.value = { mannschaft: mannschaft.mannschaft || '', spielplan_mannschaft_id: mannschaft.spielplan_mannschaft_id || '', liga: mannschaft.liga || '', staffelleiter: mannschaft.staffelleiter || '', telefon: mannschaft.telefon || '', heimspieltag: mannschaft.heimspieltag || '', spielsystem: mannschaft.spielsystem || '', mannschaftsfuehrer: mannschaft.mannschaftsfuehrer || '', spielerListe: parseSpielerString(mannschaft.spieler || ''), weitere_informationen_link: mannschaft.weitere_informationen_link || '', letzte_aktualisierung: mannschaft.letzte_aktualisierung || nowIsoDate() } isEditing.value = true; editingIndex.value = index; showModal.value = true; errorMessage.value = ''; resetSpielerDraftState() const currentTeam = (formData.value.mannschaft || '').trim() for (const s of formData.value.spielerListe) { @@ -569,7 +623,7 @@ const saveMannschaft = async () => { try { if (!applySelectedSpielerTransfers()) return const spielerString = serializeSpielerList(formData.value.spielerListe) - const updated = { mannschaft: formData.value.mannschaft || '', liga: formData.value.liga || '', staffelleiter: formData.value.staffelleiter || '', telefon: formData.value.telefon || '', heimspieltag: formData.value.heimspieltag || '', spielsystem: formData.value.spielsystem || '', mannschaftsfuehrer: formData.value.mannschaftsfuehrer || '', spieler: spielerString, weitere_informationen_link: formData.value.weitere_informationen_link || '', letzte_aktualisierung: formData.value.letzte_aktualisierung || nowIsoDate() } + const updated = { mannschaft: formData.value.mannschaft || '', spielplan_mannschaft_id: formData.value.spielplan_mannschaft_id || '', liga: formData.value.liga || '', staffelleiter: formData.value.staffelleiter || '', telefon: formData.value.telefon || '', heimspieltag: formData.value.heimspieltag || '', spielsystem: formData.value.spielsystem || '', mannschaftsfuehrer: formData.value.mannschaftsfuehrer || '', spieler: spielerString, weitere_informationen_link: formData.value.weitere_informationen_link || '', letzte_aktualisierung: formData.value.letzte_aktualisierung || nowIsoDate() } if (isEditing.value && editingIndex.value >= 0) { mannschaften.value[editingIndex.value] = { ...updated } } else { mannschaften.value.push({ ...updated }) } const touchedTeamIndexes = Object.keys(pendingSpielerNamesByTeamIndex.value) if (touchedTeamIndexes.length > 0) { const ts = nowIsoDate(); for (const idxStr of touchedTeamIndexes) { const idx = Number(idxStr); if (!Number.isFinite(idx)) continue; const existing = mannschaften.value[idx]; if (!existing) continue; mannschaften.value[idx] = { ...existing, spieler: serializeSpielerNames(pendingSpielerNamesByTeamIndex.value[idx]), letzte_aktualisierung: ts } } } @@ -579,10 +633,10 @@ const saveMannschaft = async () => { } const saveCSV = async () => { - const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung' + const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung,Spielplan Mannschaft ID' const rows = mannschaften.value.map(m => { const esc = (v) => { if (!v) return ''; const s = String(v); if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s.replace(/"/g, '""')}"`; return s } - return [esc(m.mannschaft), esc(m.liga), esc(m.staffelleiter), esc(m.telefon), esc(m.heimspieltag), esc(m.spielsystem), esc(m.mannschaftsfuehrer), esc(m.spieler), esc(m.weitere_informationen_link), esc(m.letzte_aktualisierung)].join(',') + return [esc(m.mannschaft), esc(m.liga), esc(m.staffelleiter), esc(m.telefon), esc(m.heimspieltag), esc(m.spielsystem), esc(m.mannschaftsfuehrer), esc(m.spieler), esc(m.weitere_informationen_link), esc(m.letzte_aktualisierung), esc(m.spielplan_mannschaft_id)].join(',') }) await $fetch('/api/cms/save-csv', { method: 'POST', @@ -639,10 +693,10 @@ const createNextSeason = async () => { isCreatingSeason.value = true errorMessage.value = '' try { - const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung' + const header = 'Mannschaft,Liga,Staffelleiter,Telefon,Heimspieltag,Spielsystem,Mannschaftsführer,Spieler,Weitere Informationen Link,Letzte Aktualisierung,Spielplan Mannschaft ID' const rows = mannschaften.value.map(m => { const esc = (v) => { if (!v) return ''; const s = String(v); if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s.replace(/"/g, '""')}"`; return s } - return [esc(m.mannschaft), esc(m.liga), esc(m.staffelleiter), esc(m.telefon), esc(m.heimspieltag), esc(m.spielsystem), esc(m.mannschaftsfuehrer), esc(m.spieler), esc(m.weitere_informationen_link), esc(m.letzte_aktualisierung || nowIsoDate())].join(',') + return [esc(m.mannschaft), esc(m.liga), esc(m.staffelleiter), esc(m.telefon), esc(m.heimspieltag), esc(m.spielsystem), esc(m.mannschaftsfuehrer), esc(m.spieler), esc(m.weitere_informationen_link), esc(m.letzte_aktualisierung || nowIsoDate()), esc(m.spielplan_mannschaft_id)].join(',') }) await $fetch('/api/cms/save-csv', { @@ -678,7 +732,7 @@ const confirmDelete = (mannschaft, index) => { onMounted(async () => { await loadSeasons() - await loadMannschaften().catch(() => {}) + await Promise.all([loadMannschaften().catch(() => {}), loadSpielplanTeams()]) }) // Expose load function to parent components diff --git a/components/cms/CmsSpielplaene.vue b/components/cms/CmsSpielplaene.vue index dca8d34..e625dfe 100755 --- a/components/cms/CmsSpielplaene.vue +++ b/components/cms/CmsSpielplaene.vue @@ -6,6 +6,13 @@ Spielpläne bearbeiten
+
+
import { ref, computed, onMounted } from 'vue' -import { Users } from 'lucide-vue-next' +import { rowMatchesTeamFilter } from '../../utils/spielplan-filter.js' const route = useRoute() @@ -473,6 +473,7 @@ const loadMannschaften = async () => { spieler: values[7].trim(), weitere_informationen_link: values[8].trim(), letzte_aktualisierung: values[9].trim(), + spielplan_mannschaft_id: values[10] ? values[10].trim() : '', slug: values[0].trim().toLowerCase().replace(/\s+/g, '-') } }).filter(mannschaft => mannschaft !== null) @@ -557,45 +558,16 @@ const isExactHarheimTeam = (teamName, variant) => { } const isSpielForMannschaft = (row, cmsMannschaft) => { - const variants = getTeamVariants(cmsMannschaft) - if (!variants.length) return false - - const heimMannschaft = (row.HeimMannschaft || '').toLowerCase() - const gastMannschaft = (row.GastMannschaft || '').toLowerCase() - const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase() - const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase() - const isHarheimerHeim = heimMannschaft.includes('harheimer tc') - const isHarheimerGast = gastMannschaft.includes('harheimer tc') - - if (!isHarheimerHeim && !isHarheimerGast) return false - - const mannschaftMatch = variants.some((variant) => { - if (isHarheimerHeim && isExactHarheimTeam(heimMannschaft, variant)) return true - if (isHarheimerGast && isExactHarheimTeam(gastMannschaft, variant)) return true - return false - }) - - if (!mannschaftMatch) return false - - if (cmsMannschaft.startsWith('Erwachsene')) { - const isErwachsenenHeim = isHarheimerHeim && - heimAltersklasse.includes('erwachsene') && - !heimAltersklasse.includes('jugend') - const isErwachsenenGast = isHarheimerGast && - gastAltersklasse.includes('erwachsene') && - !gastAltersklasse.includes('jugend') - return isErwachsenenHeim || isErwachsenenGast + const configuredTeamIds = String(mannschaft.value?.spielplan_mannschaft_id || '') + .split(',') + .map(id => id.trim()) + .filter(Boolean) + if (configuredTeamIds.length) { + return configuredTeamIds.includes(String(row.HeimMannschaftId || '').trim()) || + configuredTeamIds.includes(String(row.GastMannschaftId || '').trim()) } - if (cmsMannschaft === 'Jugendmannschaft') { - const isJugendHeim = isHarheimerHeim && - (heimAltersklasse.includes('jugend') || heimMannschaft.includes('jugend')) - const isJugendGast = isHarheimerGast && - (gastAltersklasse.includes('jugend') || gastMannschaft.includes('jugend')) - return isJugendHeim || isJugendGast - } - - return true + return rowMatchesTeamFilter(row, cmsMannschaft) } const parseTerminTimestamp = (row) => { diff --git a/pages/mannschaften/spielplaene.vue b/pages/mannschaften/spielplaene.vue index 263fe07..2b18db9 100755 --- a/pages/mannschaften/spielplaene.vue +++ b/pages/mannschaften/spielplaene.vue @@ -360,6 +360,7 @@