Enhance edit dialogs with validation and error handling, implement dropdown searches for Funktionäre, and update Masterdata logs with completion notes.

This commit is contained in:
2026-04-12 16:00:30 +02:00
parent f82d06f3e7
commit b07d5d7386
3 changed files with 264 additions and 41 deletions
@@ -20,29 +20,52 @@ fun ReiterEditDialog(
var verein by remember { mutableStateOf(reiter.verein ?: "") }
var feiId by remember { mutableStateOf(reiter.feiId ?: "") }
val isVornameValid = vorname.isNotBlank()
val isNachnameValid = nachname.isNotBlank()
val isOepsValid = oepsNummer.isBlank() || oepsNummer.all { it.isDigit() || it.isLetter() } // Einfache Prüfung
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Reiter bearbeiten") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = vorname, onValueChange = { vorname = it }, label = { Text("Vorname") })
OutlinedTextField(value = nachname, onValueChange = { nachname = it }, label = { Text("Nachname") })
OutlinedTextField(value = oepsNummer, onValueChange = { oepsNummer = it }, label = { Text("OEPS-Nr.") })
OutlinedTextField(
value = vorname,
onValueChange = { vorname = it },
label = { Text("Vorname*") },
isError = !isVornameValid
)
OutlinedTextField(
value = nachname,
onValueChange = { nachname = it },
label = { Text("Nachname*") },
isError = !isNachnameValid
)
OutlinedTextField(
value = oepsNummer,
onValueChange = { oepsNummer = it },
label = { Text("OEPS-Nr.") },
isError = !isOepsValid,
supportingText = { if(!isOepsValid) Text("Ungültiges Format") }
)
OutlinedTextField(value = verein, onValueChange = { verein = it }, label = { Text("Verein") })
OutlinedTextField(value = feiId, onValueChange = { feiId = it }, label = { Text("FEI-ID") })
}
},
confirmButton = {
Button(onClick = {
onSave(reiter.copy(
vorname = vorname,
nachname = nachname,
oepsNummer = oepsNummer,
satznummer = oepsNummer,
verein = verein,
feiId = feiId
))
}) {
Button(
onClick = {
onSave(reiter.copy(
vorname = vorname,
nachname = nachname,
oepsNummer = oepsNummer,
satznummer = oepsNummer,
verein = verein,
feiId = feiId
))
},
enabled = isVornameValid && isNachnameValid && isOepsValid
) {
Text("Speichern")
}
},
@@ -65,26 +88,49 @@ fun PferdEditDialog(
var oepsNummer by remember { mutableStateOf(pferd.oepsNummer ?: "") }
var geburtsjahr by remember { mutableStateOf(pferd.geburtsjahr?.toString() ?: "") }
val isNameValid = name.isNotBlank()
val isLebensnrValid = lebensnummer.isNotBlank()
val isJahrValid = geburtsjahr.isBlank() || (geburtsjahr.toIntOrNull() != null && geburtsjahr.length == 4)
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Pferd bearbeiten") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") })
OutlinedTextField(value = lebensnummer, onValueChange = { lebensnummer = it }, label = { Text("Lebensnummer") })
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name*") },
isError = !isNameValid
)
OutlinedTextField(
value = lebensnummer,
onValueChange = { lebensnummer = it },
label = { Text("Lebensnummer*") },
isError = !isLebensnrValid
)
OutlinedTextField(value = oepsNummer, onValueChange = { oepsNummer = it }, label = { Text("OEPS-Nr.") })
OutlinedTextField(value = geburtsjahr, onValueChange = { geburtsjahr = it }, label = { Text("Geburtsjahr") })
OutlinedTextField(
value = geburtsjahr,
onValueChange = { geburtsjahr = it },
label = { Text("Geburtsjahr") },
isError = !isJahrValid,
supportingText = { if(!isJahrValid) Text("4-stellige Jahreszahl") }
)
}
},
confirmButton = {
Button(onClick = {
onSave(pferd.copy(
name = name,
lebensnummer = lebensnummer,
oepsNummer = oepsNummer,
geburtsjahr = geburtsjahr.toIntOrNull()
))
}) {
Button(
onClick = {
onSave(pferd.copy(
name = name,
lebensnummer = lebensnummer,
oepsNummer = oepsNummer,
geburtsjahr = geburtsjahr.toIntOrNull()
))
},
enabled = isNameValid && isLebensnrValid && isJahrValid
) {
Text("Speichern")
}
},
@@ -40,6 +40,15 @@ fun OrganisationTabContent(viewModel: NennungViewModel) {
var schmied by remember { mutableStateOf("") }
var steward by remember { mutableStateOf("") }
// --- Dropdown-States für die Suche ---
var showTLDropdown by remember { mutableStateOf(false) }
var showTBDropdown by remember { mutableStateOf(false) }
var showTDDropdown by remember { mutableStateOf(false) }
var showPCDropdown by remember { mutableStateOf(false) }
var showTADropdown by remember { mutableStateOf(false) }
var showSMDropdown by remember { mutableStateOf(false) }
var showSTDropdown by remember { mutableStateOf(false) }
var richter by remember {
mutableStateOf(
listOf(
@@ -68,18 +77,162 @@ fun OrganisationTabContent(viewModel: NennungViewModel) {
// ── Funktionäre & Offizielle ─────────────────────────────────────────
OrgSectionCard(title = "Funktionäre & Offizielle (C-Satz)") {
OrgSubSection("Turnier-Organisation") {
OrgSearchField("Turnierleiter:", turnierleiter) {
turnierleiter = it
// In einem echten Szenario würde hier die Masterdata-Suche getriggert
Box {
OrgSearchField("Turnierleiter:", turnierleiter) {
turnierleiter = it
viewModel.searchFunktionaere(it)
showTLDropdown = it.length >= 2
}
DropdownMenu(
expanded = showTLDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showTLDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
turnierleiter = f.name
showTLDropdown = false
}
)
}
}
}
Box {
OrgSearchField("Turnierbeauftragter:", turnierbeauftragter) {
turnierbeauftragter = it
viewModel.searchFunktionaere(it)
showTBDropdown = it.length >= 2
}
DropdownMenu(
expanded = showTBDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showTBDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
turnierbeauftragter = f.name
showTBDropdown = false
}
)
}
}
}
Box {
OrgSearchField("Technischer Delegierter:", technischerDelegierter) {
technischerDelegierter = it
viewModel.searchFunktionaere(it)
showTDDropdown = it.length >= 2
}
DropdownMenu(
expanded = showTDDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showTDDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
technischerDelegierter = f.name
showTDDropdown = false
}
)
}
}
}
Box {
OrgSearchField("Parcourschef:", parcourschef) {
parcourschef = it
viewModel.searchFunktionaere(it)
showPCDropdown = it.length >= 2
}
DropdownMenu(
expanded = showPCDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showPCDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
parcourschef = f.name
showPCDropdown = false
}
)
}
}
}
OrgSearchField("Turnierbeauftragter:", turnierbeauftragter) { turnierbeauftragter = it }
OrgSearchField("Technischer Delegierter:", technischerDelegierter) { technischerDelegierter = it }
OrgSearchField("Parcourschef:", parcourschef) { parcourschef = it }
}
OrgSubSection("Support-Team") {
OrgSearchField("Tierarzt:", tierarzt) { tierarzt = it }
OrgSearchField("Schmied:", schmied) { schmied = it }
OrgSearchField("Steward:", steward) { steward = it }
Box {
OrgSearchField("Tierarzt:", tierarzt) {
tierarzt = it
viewModel.searchFunktionaere(it)
showTADropdown = it.length >= 2
}
DropdownMenu(
expanded = showTADropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showTADropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
tierarzt = f.name
showTADropdown = false
}
)
}
}
}
Box {
OrgSearchField("Schmied:", schmied) {
schmied = it
viewModel.searchFunktionaere(it)
showSMDropdown = it.length >= 2
}
DropdownMenu(
expanded = showSMDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showSMDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
schmied = f.name
showSMDropdown = false
}
)
}
}
}
Box {
OrgSearchField("Steward:", steward) {
steward = it
viewModel.searchFunktionaere(it)
showSTDropdown = it.length >= 2
}
DropdownMenu(
expanded = showSTDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showSTDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
steward = f.name
showSTDropdown = false
}
)
}
}
}
}
}
@@ -109,12 +262,36 @@ fun OrganisationTabContent(viewModel: NennungViewModel) {
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedTextField(
value = r.name,
onValueChange = { v -> richter = richter.toMutableList().also { it[idx] = r.copy(name = v) } },
modifier = Modifier.weight(3f).height(44.dp).padding(end = 8.dp),
singleLine = true,
)
// Name-Suche mit Dropdown
var showRichterDropdown by remember { mutableStateOf(false) }
Box(modifier = Modifier.weight(3f).padding(end = 8.dp)) {
OutlinedTextField(
value = r.name,
onValueChange = { v ->
richter = richter.toMutableList().also { it[idx] = r.copy(name = v) }
viewModel.searchFunktionaere(v)
showRichterDropdown = v.length >= 2
},
modifier = Modifier.fillMaxWidth().height(44.dp),
singleLine = true,
placeholder = { Text("Name suchen...", fontSize = 12.sp) }
)
DropdownMenu(
expanded = showRichterDropdown && state.searchResultsFunktionaere.isNotEmpty(),
onDismissRequest = { showRichterDropdown = false },
properties = androidx.compose.ui.window.PopupProperties(focusable = false)
) {
state.searchResultsFunktionaere.forEach { f ->
DropdownMenuItem(
text = { Text("${f.name} (${f.qualifikationen.joinToString(", ")})") },
onClick = {
richter = richter.toMutableList().also { it[idx] = r.copy(name = f.name) }
showRichterDropdown = false
}
)
}
}
}
// Qualifikation-Dropdown
var qualExpanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {