diff --git a/docs/01_Architecture/Frontend_Komponenten_Roadmap.md b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md index d38c0f37..8ed64444 100644 --- a/docs/01_Architecture/Frontend_Komponenten_Roadmap.md +++ b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md @@ -39,11 +39,11 @@ Turniermanagement bedeutet Arbeit mit Listen. Wir benötigen mächtige, aber kom * [x] Filter-Chips für schnelle Status-Wechsel. * [x] Anzeige der Trefferanzahl (Result Count). -## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) ⚪ [ZUKUNFT] +## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) 🔵 [IN ARBEIT] Eingabe von Stammdaten muss schnell und fehlerfrei erfolgen. -* [ ] **`MsEnumDropdown`:** Automatisches Mapping von Backend-Enums (ÖTO) auf UI-Auswahl. +* [x] **`MsEnumDropdown`:** Automatisches Mapping von Backend-Enums (ÖTO) auf UI-Auswahl. * [ ] **`MsValidationWrapper`:** Konsistente Anzeige von Fehlern (z.B. "Lizenz für diese Klasse nicht ausreichend"). * [ ] **`MsSearchableSelect`:** Für die Verknüpfung von Reitern/Pferden (Autocomplete). diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEnumDropdown.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEnumDropdown.kt new file mode 100644 index 00000000..4cb1d57a --- /dev/null +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEnumDropdown.kt @@ -0,0 +1,92 @@ +package at.mocode.frontend.core.designsystem.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * Ein generisches Dropdown zur Auswahl von Enum-Werten. + * + * @param label Das Label über dem Dropdown. + * @param options Alle verfügbaren Enum-Optionen (z.B. SparteE.values()). + * @param selectedOption Der aktuell gewählte Wert. + * @param onOptionSelected Callback bei Auswahl einer Option. + * @param optionLabel Transformation des Enums in einen lesbaren Text (Standard: toString()). + * @param modifier Modifier für die gesamte Komponente. + * @param enabled Ob das Dropdown bearbeitbar ist. + * @param isError Ob ein Fehler vorliegt. + * @param errorMessage Die anzuzeigende Fehlermeldung. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun > MsEnumDropdown( + label: String, + options: Array, + selectedOption: T?, + onOptionSelected: (T) -> Unit, + modifier: Modifier = Modifier, + optionLabel: (T) -> String = { it.name }, + enabled: Boolean = true, + isError: Boolean = false, + errorMessage: String? = null +) { + var expanded by remember { mutableStateOf(false) } + + Column(modifier = modifier) { + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { if (enabled) expanded = it } + ) { + OutlinedTextField( + value = selectedOption?.let { optionLabel(it) } ?: "", + onValueChange = {}, + readOnly = true, + label = { Text(label, style = MaterialTheme.typography.bodySmall) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + modifier = Modifier + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, enabled) + .fillMaxWidth(), + isError = isError, + enabled = enabled, + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium, + shape = MaterialTheme.shapes.small + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + options.forEach { option -> + DropdownMenuItem( + text = { + Text( + text = optionLabel(option), + style = MaterialTheme.typography.bodyMedium + ) + }, + onClick = { + onOptionSelected(option) + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding + ) + } + } + } + + if (isError && errorMessage != null) { + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) + } + } +}