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:
@@ -34,8 +34,8 @@ Erweiterung der Stammdaten-Infrastruktur um Schreibzugriffe (Detail-Editoren), F
|
||||
- **DI-Check:** Verifikation der `znsImportModule` Registrierung in `main.kt`.
|
||||
|
||||
## 📝 Notizen & Next Steps
|
||||
- Implementierung der weiteren Funktionärs-Rollen (Richter, PC) im Organisations-Tab.
|
||||
- Erweiterung der `MasterdataEditDialogs` um Validierungs-Feedback (z.B. OEPS-Formatprüfung).
|
||||
- Implementierung der weiteren Funktionärs-Rollen (Richter, PC) im Organisations-Tab. ✓ (Abgeschlossen am 12.04.2026 16:30)
|
||||
- Erweiterung der `MasterdataEditDialogs` um Validierungs-Feedback (z.B. OEPS-Formatprüfung). ✓ (Abgeschlossen am 12.04.2026 16:30)
|
||||
- Vorbereitung Phase 10.3: Series-Context (Cups/Meisterschaften).
|
||||
|
||||
---
|
||||
|
||||
+70
-24
@@ -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")
|
||||
}
|
||||
},
|
||||
|
||||
+192
-15
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user