feat: füge Unterstützung für importierte Spielplan-Teams hinzu und verbessere Teamfilter-Logik
This commit is contained in:
@@ -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) },
|
||||
)
|
||||
@@ -742,12 +746,32 @@ fun CmsSportbetriebScreen(navController: NavController, showBackNavigation: Bool
|
||||
@Composable
|
||||
private fun MannschaftEditorCard(
|
||||
row: CmsMannschaftRow,
|
||||
spielplanTeams: List<ImportedSpielplanTeam>,
|
||||
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())
|
||||
@@ -763,6 +787,35 @@ private fun MannschaftEditorCard(
|
||||
}
|
||||
}
|
||||
|
||||
private data class ImportedSpielplanTeam(val id: String, val label: String)
|
||||
|
||||
private fun importedSpielplanTeams(headers: List<String>, rows: List<List<String>>): List<ImportedSpielplanTeam> {
|
||||
fun index(name: String) = headers.indexOf(name)
|
||||
fun value(row: List<String>, column: String): String {
|
||||
val columnIndex = index(column)
|
||||
return row.getOrElse(columnIndex) { "" }.trim()
|
||||
}
|
||||
|
||||
val teams = linkedMapOf<String, ImportedSpielplanTeam>()
|
||||
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<String>, rows: List<List<String>>): String {
|
||||
if (headers.isEmpty()) return ""
|
||||
return listOf(headers).plus(rows).joinToString("\n") { row -> row.joinToString(";") { it.csvCell(";") } }
|
||||
|
||||
@@ -362,6 +362,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { rowMatchesTeamFilter } from '../../utils/spielplan-filter.js'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -563,45 +564,7 @@ const isSpielForMannschaft = (row, cmsMannschaft) => {
|
||||
String(row.GastMannschaftId || '').trim() === configuredTeamId
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -39,6 +39,38 @@ function isErwachsenenTeam(teamKey) {
|
||||
return teamKey === 'erwachsene' || teamKey.startsWith('erwachsene_')
|
||||
}
|
||||
|
||||
function romanToNumber(value) {
|
||||
const roman = String(value || '').toUpperCase()
|
||||
if (!/^[IVX]+$/.test(roman)) return null
|
||||
|
||||
const values = { I: 1, V: 5, X: 10 }
|
||||
let total = 0
|
||||
for (let index = 0; index < roman.length; index += 1) {
|
||||
const current = values[roman[index]] || 0
|
||||
const next = values[roman[index + 1]] || 0
|
||||
total += current < next ? -current : current
|
||||
}
|
||||
return total || null
|
||||
}
|
||||
|
||||
function parseJugendTeamKey(teamKey) {
|
||||
const match = String(teamKey || '').match(/^jugend_?j?(\d{1,2})(?:_([ivx]+|\d+))?$/i)
|
||||
if (!match) return null
|
||||
|
||||
const teamNumber = match[2]
|
||||
? (Number(match[2]) || romanToNumber(match[2]))
|
||||
: null
|
||||
|
||||
return { age: match[1], teamNumber: teamNumber ? String(teamNumber) : '' }
|
||||
}
|
||||
|
||||
function sideMatchesJugendTeam({ isHarheimer, ageClass, teamNumber }, team) {
|
||||
if (!isHarheimer) return false
|
||||
const agePattern = new RegExp(`(?:jugend\\s*|j)${team.age}\\b`, 'i')
|
||||
if (!agePattern.test(ageClass)) return false
|
||||
return !team.teamNumber || String(teamNumber || '').trim() === team.teamNumber
|
||||
}
|
||||
|
||||
export function rowMatchesWettbewerbFilter(row, rawWettbewerb) {
|
||||
const wettbewerb = normalizeText(rawWettbewerb || 'punktrunde')
|
||||
const runde = normalizeText(row?.Runde)
|
||||
@@ -79,6 +111,19 @@ export function rowMatchesTeamFilter(row, rawTeam) {
|
||||
return isJugendHeim || isJugendGast
|
||||
}
|
||||
|
||||
const jugendTeam = parseJugendTeamKey(teamKey)
|
||||
if (jugendTeam) {
|
||||
return sideMatchesJugendTeam({
|
||||
isHarheimer: isHarheimerHeim,
|
||||
ageClass: heimAltersklasse,
|
||||
teamNumber: row?.HeimMannschaftNr
|
||||
}, jugendTeam) || sideMatchesJugendTeam({
|
||||
isHarheimer: isHarheimerGast,
|
||||
ageClass: gastAltersklasse,
|
||||
teamNumber: row?.GastMannschaftNr
|
||||
}, jugendTeam)
|
||||
}
|
||||
|
||||
const csvVariants = MANNSCHAFT_MAPPING[teamKey] || []
|
||||
if (csvVariants.length === 0) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user