From c20899752a0827ca37f932f7bd590ff7a9a99cff Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Tue, 31 Mar 2026 10:07:10 +0200 Subject: [PATCH] refactor(design-system): consolidate components and standardize naming with `Ms` prefix - Removed redundant files: `AppFooter`, `AppHeader`, `AppScaffold`, `LoadingIndicator`, `MeldestelleButton`, `MeldestelleTextField`, `DashboardCard`. - Introduced `MsFooter`, `MsHeader`, `MsScaffold`, `MsLoadingIndicator`, `MsButton`, `MsTextField`, `MsCard` with standardized implementation and naming. - Updated references in `profile-feature` and `ping-feature` to use the new components. - Aligned with roadmap goals for a consistent, reusable, and high-density design system. Signed-off-by: Stefan Mogeritsch --- .../Frontend_Komponenten_Roadmap.md | 64 +++++++++++++++++++ .../core/designsystem/components/Buttons.kt | 44 ------------- .../{MeldestelleButton.kt => MsButton.kt} | 6 +- .../components/{Cards.kt => MsCard.kt} | 2 +- .../components/{AppFooter.kt => MsFooter.kt} | 2 +- .../components/{AppHeader.kt => MsHeader.kt} | 2 +- ...dingIndicator.kt => MsLoadingIndicator.kt} | 11 ++-- .../{AppScaffold.kt => MsScaffold.kt} | 6 +- ...MeldestelleTextField.kt => MsTextField.kt} | 6 +- .../ping/feature/presentation/PingScreen.kt | 10 +-- .../profile/presentation/ProfileScreen.kt | 16 ++--- 11 files changed, 96 insertions(+), 73 deletions(-) create mode 100644 docs/01_Architecture/Frontend_Komponenten_Roadmap.md delete mode 100644 frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Buttons.kt rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{MeldestelleButton.kt => MsButton.kt} (97%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{Cards.kt => MsCard.kt} (98%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{AppFooter.kt => MsFooter.kt} (98%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{AppHeader.kt => MsHeader.kt} (99%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{LoadingIndicator.kt => MsLoadingIndicator.kt} (89%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{AppScaffold.kt => MsScaffold.kt} (88%) rename frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/{MeldestelleTextField.kt => MsTextField.kt} (98%) diff --git a/docs/01_Architecture/Frontend_Komponenten_Roadmap.md b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md new file mode 100644 index 00000000..d681fb08 --- /dev/null +++ b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md @@ -0,0 +1,64 @@ +# Frontend-Komponenten Roadmap: Meldestelle-Biest + +🏗️ **[Lead Architect]** | 31. März 2026 + +Diese Roadmap definiert den Weg von der aktuellen Prototypen-Phase hin zu einer professionellen, konsistenten und +performanten Desktop-App. Wir setzen auf einen komponentengetriebenen Ansatz (High-Density UI), um die komplexe +Datenverwaltung der Turniermeldestelle effizient abzubilden. + +--- + +## Phase 1: Cleanup & Konsolidierung (Das Fundament) ✅ [IN ARBEIT] + +Bevor wir neue Features bauen, räumen wir die bestehenden Entwürfe auf, um Redundanzen zu vermeiden. + +* [ ] **Design-System Refactoring:** + * [ ] `Buttons.kt` (DenseButton) in `MeldestelleButton.kt` (MsButton) überführen. + * [ ] Einheitliches Naming: Alle Basis-Komponenten erhalten das Präfix `Ms` (z.B. `MsButton.kt`, `MsTextField.kt`). + * [ ] Redundante Placeholder-Dateien entfernen oder in `core/design-system/models/` bündeln. +* [ ] **Theme-Check:** + * [ ] Sicherstellen, dass alle Farben aus `AppColors` kommen und nicht hart codiert sind. + * [ ] Typografie-Skalen für High-Density optimieren (LabelSmall für Tabellen). + +## Phase 2: Daten-Visualisierungs-Komponenten (Das Herzstück) 🔵 [GEPLANT] + +Turniermanagement bedeutet Arbeit mit Listen. Wir benötigen mächtige, aber kompakte Anzeige-Komponenten. + +* [ ] **`MsDataTable`:** + * [ ] KMP-kompatible Tabelle mit Sticky Header. + * [ ] Sortier- und Filter-Logik (in-memory & API-driven). + * [ ] Zeilen-Selektion (Einzel/Mehrfach) und Kontextmenüs. +* [ ] **`MsStatusBadge`:** + * [ ] Farbliche Kodierung für Nennungsstatus, Lizenzstatus und Prüfungsstatus. + * [ ] Kompaktes Design für die Nutzung innerhalb von Tabellenzellen. +* [ ] **`MsFilterBar`:** + * [ ] Suchfeld mit Debounce. + * [ ] Filter-Chips für schnelle Status-Wechsel. + +## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) ⚪ [ZUKUNFT] + +Eingabe von Stammdaten muss schnell und fehlerfrei erfolgen. + +* [ ] **`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). + +## Phase 4: Layout-Patterns & Navigation ⚪ [ZUKUNFT] + +Hier bringen wir alles zusammen, bevor das finale Routing implementiert wird. + +* [ ] **`MsMasterDetailLayout`:** Standard-Layout für alle Stammdaten-Screens. +* [ ] **`MsActionToolbar`:** Einheitliche Platzierung von Hauptaktionen (Neu, Speichern, Drucken). +* [ ] **`MsDialogShell`:** Standardisierter Rahmen für Modale und Bestätigungsdialoge. + +--- + +## Erfolgs-Metriken + +* **Wiederverwendbarkeit:** > 80% der UI besteht aus `Ms`-Komponenten. +* **Density:** Alle relevanten Daten eines Reiters/Pferdes sind ohne Scrollen in der Detailansicht sichtbar. +* **Performance:** `MsDataTable` rendert 500+ Zeilen flüssig auf ARM64 (Zora/Mac/Linux). + +--- +🧹 **[Curator]** | 2026-03-31 +*Dieses Dokument dient als Single Source of Truth für die Frontend-Entwicklung.* diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Buttons.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Buttons.kt deleted file mode 100644 index e643aba0..00000000 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Buttons.kt +++ /dev/null @@ -1,44 +0,0 @@ -package at.mocode.frontend.core.designsystem.components - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import at.mocode.frontend.core.designsystem.theme.Dimens - -/** - * Ein kompakter Button für unsere High-Density UI. - * - * Warum ein eigener Button? - * Der Standard Material3 Button ist sehr hoch (40dp+) und hat viel Padding. - * Das verschwendet Platz in Tabellen oder Toolbars. - * Unser 'DenseButton' ist fix 32dp hoch- und hat weniger Innenabstand. - */ -@Composable -fun DenseButton( - text: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - containerColor: Color = MaterialTheme.colorScheme.primary -) { - Button( - onClick = onClick, - enabled = enabled, - modifier = modifier.height(32.dp), // Fixe, kompakte Höhe - shape = MaterialTheme.shapes.small, // Nutzt unsere 4dp Rundung - colors = ButtonDefaults.buttonColors(containerColor = containerColor), - contentPadding = PaddingValues(horizontal = Dimens.SpacingM, vertical = 0.dp) // Wenig Padding - ) { - Text( - text = text, - style = MaterialTheme.typography.labelMedium // Kleinere Schrift - ) - } -} diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleButton.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt similarity index 97% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleButton.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt index 91cbac63..6e48f5e0 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleButton.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt @@ -17,7 +17,7 @@ enum class ButtonSize { } @Composable -fun MeldestelleButton( +fun MsButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -96,7 +96,7 @@ fun PrimaryButton( enabled: Boolean = true, isLoading: Boolean = false, fullWidth: Boolean = false -) = MeldestelleButton( +) = MsButton( text = text, onClick = onClick, modifier = modifier, @@ -115,7 +115,7 @@ fun SecondaryButton( enabled: Boolean = true, isLoading: Boolean = false, fullWidth: Boolean = false -) = MeldestelleButton( +) = MsButton( text = text, onClick = onClick, modifier = modifier, diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Cards.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsCard.kt similarity index 98% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Cards.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsCard.kt index 83918413..0ae37b72 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/Cards.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsCard.kt @@ -20,7 +20,7 @@ import at.mocode.frontend.core.designsystem.theme.Dimens * Im Enterprise-Kontext sind flache Cards mit dünnem Border (1px) oft sauberer. */ @Composable -fun DashboardCard( +fun MsCard( modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit ) { diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppFooter.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsFooter.kt similarity index 98% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppFooter.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsFooter.kt index a8d288b1..3fa01266 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppFooter.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsFooter.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @Composable -fun AppFooter() { +fun MsFooter() { Box( modifier = Modifier .fillMaxWidth() diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsHeader.kt similarity index 99% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsHeader.kt index c1f33e3a..71d8da53 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppHeader.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsHeader.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @Composable -fun AppHeader( +fun MsHeader( isAuthenticated: Boolean, username: String?, onNavigateToLogin: (() -> Unit)? = null, diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/LoadingIndicator.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsLoadingIndicator.kt similarity index 89% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/LoadingIndicator.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsLoadingIndicator.kt index 6ad1106f..1da5ab2e 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/LoadingIndicator.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsLoadingIndicator.kt @@ -1,7 +1,10 @@ package at.mocode.frontend.core.designsystem.components import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -13,7 +16,7 @@ enum class LoadingSize { } @Composable -fun LoadingIndicator( +fun MsLoadingIndicator( modifier: Modifier = Modifier, size: LoadingSize = LoadingSize.MEDIUM, message: String? = null @@ -61,7 +64,7 @@ fun FullScreenLoading( modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - LoadingIndicator( + MsLoadingIndicator( size = LoadingSize.LARGE, message = message ) @@ -79,7 +82,7 @@ fun InlineLoading( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { - LoadingIndicator( + MsLoadingIndicator( size = LoadingSize.SMALL, message = message ) diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppScaffold.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsScaffold.kt similarity index 88% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppScaffold.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsScaffold.kt index 535d6a48..ab54e686 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/AppScaffold.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsScaffold.kt @@ -10,13 +10,13 @@ import androidx.compose.ui.Modifier @Suppress("unused") @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AppScaffold( +fun MsScaffold( header: @Composable () -> Unit = { - AppHeader(isAuthenticated = false, username = null) + MsHeader(isAuthenticated = false, username = null) }, content: @Composable (PaddingValues) -> Unit, footer: @Composable () -> Unit = { - AppFooter() + MsFooter() }, ) { Scaffold( diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleTextField.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsTextField.kt similarity index 98% rename from frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleTextField.kt rename to frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsTextField.kt index f6d76bee..9fe53e3a 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MeldestelleTextField.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsTextField.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp @Composable -fun MeldestelleTextField( +fun MsTextField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, @@ -109,7 +109,7 @@ fun MeldestellePasswordField( ) { var passwordVisible by remember { mutableStateOf(false) } - MeldestelleTextField( + MsTextField( value = value, onValueChange = onValueChange, modifier = modifier, @@ -152,7 +152,7 @@ fun MeldestelleEmailField( imeAction: ImeAction = ImeAction.Next, keyboardActions: KeyboardActions = KeyboardActions.Default ) { - MeldestelleTextField( + MsTextField( value = value, onValueChange = onValueChange, modifier = modifier, diff --git a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt index 24dc5f28..92c46b97 100644 --- a/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt +++ b/frontend/features/ping-feature/src/commonMain/kotlin/at/mocode/ping/feature/presentation/PingScreen.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import at.mocode.frontend.core.designsystem.components.DashboardCard import at.mocode.frontend.core.designsystem.components.DenseButton +import at.mocode.frontend.core.designsystem.components.MsCard import at.mocode.frontend.core.designsystem.theme.Dimens @Composable @@ -58,7 +58,7 @@ fun PingScreen( // Right Panel: Terminal Log (40%) // Hier nutzen wir bewusst einen dunklen "Terminal"-Look, unabhängig vom Theme - DashboardCard( + MsCard( modifier = Modifier .weight(0.4f) .fillMaxHeight() @@ -156,7 +156,7 @@ private fun StatusGrid(uiState: PingUiState) { Column(verticalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) { // Row 1 Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) { - DashboardCard(modifier = Modifier.weight(1f)) { + MsCard(modifier = Modifier.weight(1f)) { StatusHeader("SIMPLE / SECURE PING") if (uiState.simplePingResponse != null) { KeyValueRow("Status", uiState.simplePingResponse.status) @@ -167,7 +167,7 @@ private fun StatusGrid(uiState: PingUiState) { } } - DashboardCard(modifier = Modifier.weight(1f)) { + MsCard(modifier = Modifier.weight(1f)) { StatusHeader("HEALTH CHECK") if (uiState.healthResponse != null) { KeyValueRow("Status", uiState.healthResponse.status) @@ -180,7 +180,7 @@ private fun StatusGrid(uiState: PingUiState) { } // Row 2 - DashboardCard(modifier = Modifier.fillMaxWidth()) { + MsCard(modifier = Modifier.fillMaxWidth()) { StatusHeader("ENHANCED PING (RESILIENCE)") if (uiState.enhancedPingResponse != null) { Row(modifier = Modifier.fillMaxWidth()) { diff --git a/frontend/features/profile-feature/src/commonMain/kotlin/at/mocode/frontend/features/profile/presentation/ProfileScreen.kt b/frontend/features/profile-feature/src/commonMain/kotlin/at/mocode/frontend/features/profile/presentation/ProfileScreen.kt index 4c9c06ba..20fa91e6 100644 --- a/frontend/features/profile-feature/src/commonMain/kotlin/at/mocode/frontend/features/profile/presentation/ProfileScreen.kt +++ b/frontend/features/profile-feature/src/commonMain/kotlin/at/mocode/frontend/features/profile/presentation/ProfileScreen.kt @@ -13,9 +13,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import at.mocode.frontend.core.designsystem.components.LoadingIndicator -import at.mocode.frontend.core.designsystem.components.MeldestelleButton -import at.mocode.frontend.core.designsystem.components.MeldestelleTextField +import at.mocode.frontend.core.designsystem.components.MsButton +import at.mocode.frontend.core.designsystem.components.MsLoadingIndicator +import at.mocode.frontend.core.designsystem.components.MsTextField @Composable fun ProfileScreen( @@ -37,7 +37,7 @@ fun ProfileScreen( ) if (uiState.isLoading) { - LoadingIndicator(modifier = Modifier.align(Alignment.CenterHorizontally)) + MsLoadingIndicator(modifier = Modifier.align(Alignment.CenterHorizontally)) } else { // Fehleranzeige uiState.errorMessage?.let { error -> @@ -100,7 +100,7 @@ fun ZnsLinkSection( Spacer(Modifier.height(16.dp)) - MeldestelleTextField( + MsTextField( value = satznummer, onValueChange = { satznummer = it }, label = "ZNS Satznummer", @@ -112,7 +112,7 @@ fun ZnsLinkSection( Spacer(Modifier.height(16.dp)) - MeldestelleButton( + MsButton( onClick = { onLink(satznummer) }, text = "Jetzt verknüpfen", isLoading = isLinking, @@ -154,7 +154,7 @@ fun ProfileDetailsSection( Spacer(Modifier.height(12.dp)) if (isEditing) { - MeldestelleTextField( + MsTextField( value = contactEmail, onValueChange = { contactEmail = it }, label = "Kontakt E-Mail", @@ -162,7 +162,7 @@ fun ProfileDetailsSection( leadingIcon = Icons.Default.Email ) Spacer(Modifier.height(12.dp)) - MeldestelleTextField( + MsTextField( value = bio, onValueChange = { bio = it }, label = "Info / Bio",