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`.
|
- **DI-Check:** Verifikation der `znsImportModule` Registrierung in `main.kt`.
|
||||||
|
|
||||||
## 📝 Notizen & Next Steps
|
## 📝 Notizen & Next Steps
|
||||||
- Implementierung der weiteren Funktionärs-Rollen (Richter, PC) im Organisations-Tab.
|
- 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).
|
- 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).
|
- Vorbereitung Phase 10.3: Series-Context (Cups/Meisterschaften).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+70
-24
@@ -20,29 +20,52 @@ fun ReiterEditDialog(
|
|||||||
var verein by remember { mutableStateOf(reiter.verein ?: "") }
|
var verein by remember { mutableStateOf(reiter.verein ?: "") }
|
||||||
var feiId by remember { mutableStateOf(reiter.feiId ?: "") }
|
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(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text("Reiter bearbeiten") },
|
title = { Text("Reiter bearbeiten") },
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
OutlinedTextField(value = vorname, onValueChange = { vorname = it }, label = { Text("Vorname") })
|
OutlinedTextField(
|
||||||
OutlinedTextField(value = nachname, onValueChange = { nachname = it }, label = { Text("Nachname") })
|
value = vorname,
|
||||||
OutlinedTextField(value = oepsNummer, onValueChange = { oepsNummer = it }, label = { Text("OEPS-Nr.") })
|
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 = verein, onValueChange = { verein = it }, label = { Text("Verein") })
|
||||||
OutlinedTextField(value = feiId, onValueChange = { feiId = it }, label = { Text("FEI-ID") })
|
OutlinedTextField(value = feiId, onValueChange = { feiId = it }, label = { Text("FEI-ID") })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Button(onClick = {
|
Button(
|
||||||
onSave(reiter.copy(
|
onClick = {
|
||||||
vorname = vorname,
|
onSave(reiter.copy(
|
||||||
nachname = nachname,
|
vorname = vorname,
|
||||||
oepsNummer = oepsNummer,
|
nachname = nachname,
|
||||||
satznummer = oepsNummer,
|
oepsNummer = oepsNummer,
|
||||||
verein = verein,
|
satznummer = oepsNummer,
|
||||||
feiId = feiId
|
verein = verein,
|
||||||
))
|
feiId = feiId
|
||||||
}) {
|
))
|
||||||
|
},
|
||||||
|
enabled = isVornameValid && isNachnameValid && isOepsValid
|
||||||
|
) {
|
||||||
Text("Speichern")
|
Text("Speichern")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -65,26 +88,49 @@ fun PferdEditDialog(
|
|||||||
var oepsNummer by remember { mutableStateOf(pferd.oepsNummer ?: "") }
|
var oepsNummer by remember { mutableStateOf(pferd.oepsNummer ?: "") }
|
||||||
var geburtsjahr by remember { mutableStateOf(pferd.geburtsjahr?.toString() ?: "") }
|
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(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text("Pferd bearbeiten") },
|
title = { Text("Pferd bearbeiten") },
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") })
|
OutlinedTextField(
|
||||||
OutlinedTextField(value = lebensnummer, onValueChange = { lebensnummer = it }, label = { Text("Lebensnummer") })
|
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 = 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 = {
|
confirmButton = {
|
||||||
Button(onClick = {
|
Button(
|
||||||
onSave(pferd.copy(
|
onClick = {
|
||||||
name = name,
|
onSave(pferd.copy(
|
||||||
lebensnummer = lebensnummer,
|
name = name,
|
||||||
oepsNummer = oepsNummer,
|
lebensnummer = lebensnummer,
|
||||||
geburtsjahr = geburtsjahr.toIntOrNull()
|
oepsNummer = oepsNummer,
|
||||||
))
|
geburtsjahr = geburtsjahr.toIntOrNull()
|
||||||
}) {
|
))
|
||||||
|
},
|
||||||
|
enabled = isNameValid && isLebensnrValid && isJahrValid
|
||||||
|
) {
|
||||||
Text("Speichern")
|
Text("Speichern")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+192
-15
@@ -40,6 +40,15 @@ fun OrganisationTabContent(viewModel: NennungViewModel) {
|
|||||||
var schmied by remember { mutableStateOf("") }
|
var schmied by remember { mutableStateOf("") }
|
||||||
var steward 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 {
|
var richter by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
listOf(
|
listOf(
|
||||||
@@ -68,18 +77,162 @@ fun OrganisationTabContent(viewModel: NennungViewModel) {
|
|||||||
// ── Funktionäre & Offizielle ─────────────────────────────────────────
|
// ── Funktionäre & Offizielle ─────────────────────────────────────────
|
||||||
OrgSectionCard(title = "Funktionäre & Offizielle (C-Satz)") {
|
OrgSectionCard(title = "Funktionäre & Offizielle (C-Satz)") {
|
||||||
OrgSubSection("Turnier-Organisation") {
|
OrgSubSection("Turnier-Organisation") {
|
||||||
OrgSearchField("Turnierleiter:", turnierleiter) {
|
Box {
|
||||||
turnierleiter = it
|
OrgSearchField("Turnierleiter:", turnierleiter) {
|
||||||
// In einem echten Szenario würde hier die Masterdata-Suche getriggert
|
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") {
|
OrgSubSection("Support-Team") {
|
||||||
OrgSearchField("Tierarzt:", tierarzt) { tierarzt = it }
|
Box {
|
||||||
OrgSearchField("Schmied:", schmied) { schmied = it }
|
OrgSearchField("Tierarzt:", tierarzt) {
|
||||||
OrgSearchField("Steward:", steward) { steward = it }
|
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),
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
// Name-Suche mit Dropdown
|
||||||
value = r.name,
|
var showRichterDropdown by remember { mutableStateOf(false) }
|
||||||
onValueChange = { v -> richter = richter.toMutableList().also { it[idx] = r.copy(name = v) } },
|
Box(modifier = Modifier.weight(3f).padding(end = 8.dp)) {
|
||||||
modifier = Modifier.weight(3f).height(44.dp).padding(end = 8.dp),
|
OutlinedTextField(
|
||||||
singleLine = true,
|
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
|
// Qualifikation-Dropdown
|
||||||
var qualExpanded by remember { mutableStateOf(false) }
|
var qualExpanded by remember { mutableStateOf(false) }
|
||||||
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user