diff --git a/docs/01_Architecture/FRONTEND_CLEANUP_TODO.md b/docs/01_Architecture/FRONTEND_CLEANUP_TODO.md new file mode 100644 index 00000000..975880c2 --- /dev/null +++ b/docs/01_Architecture/FRONTEND_CLEANUP_TODO.md @@ -0,0 +1,27 @@ +# Frontend Migration & Cleanup TODO + +Status: April 2026 + +## ✅ Abgeschlossene Migrationen (Feature-Module) +- `billing-feature`: `at.mocode.frontend.features.billing` (KMP) +- `verein-feature`: `at.mocode.frontend.features.verein` (KMP) +- `nennung-feature`: `at.mocode.frontend.features.nennung` (KMP) +- `profile-feature`: `at.mocode.frontend.features.profile` (KMP) +- `pferde-feature`: `at.mocode.frontend.features.pferde` (KMP) - Migriert von v2 +- `reiter-feature`: `at.mocode.frontend.features.reiter` (KMP) - Migriert von v2 +- `funktionaer-feature`: `at.mocode.frontend.features.funktionaer` (KMP) - Neu erstellt +- `ping-feature`: `at.mocode.ping.feature` (muss noch auf `at.mocode.frontend.features.ping` vereinheitlicht werden) + +## 🚧 Ausstehende Migrationen (von `at.mocode.desktop.v2` zu Features) +Die folgenden Komponenten in `meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/` basieren noch auf `StoreV2` (In-Memory Mock) und sollten in KMP-Module überführt werden: + +1. **Veranstalter-Management**: `VeranstalterVerwaltungScreen.kt`, `VeranstalterDetailV2` -> Neues `veranstalter-feature` (Vollständige Integration). +2. **Onboarding**: `OnboardingScreen.kt` -> Design-System Integration erfolgt, KMP-Modul folgt. + +## 🧹 Architektur-Cleanup +- [ ] `at.mocode.desktop.v2.StoreV2` entfernen, sobald alle Screens auf ViewModels und API-Repositories umgestellt sind. +- [ ] `at.mocode.desktop.v2.TurnierStoreV2` konsolidieren mit dem `turnier-feature`. +- [ ] Paketnamen vereinheitlichen: `at.mocode.ping.feature` -> `at.mocode.frontend.features.ping`. +- [ ] Paketnamen vereinheitlichen: `at.mocode.zns.feature` -> `at.mocode.frontend.features.zns`. +- [ ] `AppScreen.kt`: Veraltete (Legacy) Routen und Regexe entfernen. +- [ ] `DesktopMainLayout.kt`: Die `when`-Zweige für `v2` Screens aufräumen, sobald die Module bereit sind. diff --git a/docs/01_Architecture/MASTER_ROADMAP.md b/docs/01_Architecture/MASTER_ROADMAP.md index c53fee52..f8f12012 100644 --- a/docs/01_Architecture/MASTER_ROADMAP.md +++ b/docs/01_Architecture/MASTER_ROADMAP.md @@ -233,6 +233,8 @@ und über definierte Schnittstellen kommunizieren. * [x] **ZNS-Importer:** Hardening & Integrationstests für Funktionärs-Updates. ✓ * [x] **Konzept:** Fachliches Konzept für Zeitplan-Optimierung (Drag & Drop) erstellt. ✓ * [x] **Konzept:** Status-Automat für Nennungen & Zeitplan-Synchronisation spezifiziert. ✓ +* [x] **Frontend-Standardisierung:** `nennung-feature` refactored und in Desktop-Shell integriert. ✓ +* [x] **Cleanup:** `FRONTEND_CLEANUP_TODO.md` für Migration von `v2` Screens erstellt. ✓ * [ ] **Zeitplan:** Dynamische Verschiebung von Bewerben (Drag & Drop im Kalender). * [ ] **Protokoll:** Implementierung eines Event-Logs für manuelle Eingriffe in Startlisten. * [ ] **Export:** Startlisten-Export für ZNS (XML-B-Satz). diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt new file mode 100644 index 00000000..23ce78fd --- /dev/null +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt @@ -0,0 +1,17 @@ +package at.mocode.frontend.features.funktionaer.di + +import at.mocode.frontend.features.funktionaer.presentation.* +import org.koin.dsl.module + +val funktionaerModule = module { + single { MockFunktionaerRepository() } + factory { FunktionaerViewModel(get()) } +} + +class MockFunktionaerRepository : FunktionaerRepository { + override suspend fun list(): List = listOf( + FunktionaerListItem(1, "Wolfgang Schier", "RICHTER", "G3"), + FunktionaerListItem(2, "Alice Schwab", "RICHTER", "INTERNATIONAL"), + FunktionaerListItem(3, "Dietmar Gstöttner", "PARCOURSBAUER", null) + ) +} diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/Funktionaer.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/Funktionaer.kt new file mode 100644 index 00000000..060a0803 --- /dev/null +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/Funktionaer.kt @@ -0,0 +1,15 @@ +package at.mocode.frontend.features.funktionaer.domain + +data class Funktionaer( + val id: Long, + val vorname: String, + val nachname: String, + val richterNummer: String? = null, + val rollen: List = emptyList(), + val richterQualifikation: String? = null, + val qualifiziertFuerSparten: List = emptyList(), + val email: String? = null, + val telefon: String? = null, + val vereinsNummer: String? = null, + val istAktiv: Boolean = true +) diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerScreen.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerScreen.kt new file mode 100644 index 00000000..0b96681e --- /dev/null +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerScreen.kt @@ -0,0 +1,103 @@ +package at.mocode.frontend.features.funktionaer.presentation + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import at.mocode.frontend.core.designsystem.components.* +import at.mocode.frontend.core.designsystem.models.PlaceholderContent + +@Composable +fun FunktionaerScreen( + viewModel: FunktionaerViewModel +) { + val state by viewModel.state.collectAsState() + + MsMasterDetailLayout( + master = { + FunktionaerListContent( + state = state, + onSearchChange = { viewModel.send(FunktionaerIntent.SearchChanged(it)) }, + onFunktionaerSelected = { viewModel.send(FunktionaerIntent.Select(it)) } + ) + }, + detail = { + if (state.selectedId != null) { + val selected = state.list.find { it.id == state.selectedId } + if (selected != null) { + FunktionaerDetailContent(selected) + } + } else { + PlaceholderContent( + title = "Kein Funktionär ausgewählt", + subtitle = "Wählen Sie einen Funktionär aus der Liste aus." + ) + } + } + ) +} + +@Composable +private fun FunktionaerListContent( + state: FunktionaerState, + onSearchChange: (String) -> Unit, + onFunktionaerSelected: (Long) -> Unit +) { + Column(modifier = Modifier.fillMaxSize()) { + MsFilterBar( + searchQuery = state.searchQuery, + onSearchQueryChange = onSearchChange, + resultCount = state.filtered.size + ) + + Spacer(Modifier.height(8.dp)) + + if (state.isLoading) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + MsDataTable( + items = state.filtered, + columns = listOf( + MsColumnDefinition( + title = "Name", + weight = 1f, + cellRenderer = { Text(it.name, style = MaterialTheme.typography.bodySmall) } + ), + MsColumnDefinition( + title = "Rolle", + width = 150.dp, + cellRenderer = { Text(it.rolle, style = MaterialTheme.typography.bodySmall) } + ), + MsColumnDefinition( + title = "Lizenz", + width = 100.dp, + cellRenderer = { Text(it.lizenz ?: "-", style = MaterialTheme.typography.bodySmall) } + ) + ), + onRowClick = { onFunktionaerSelected(it.id) } + ) + } + } +} + +@Composable +private fun FunktionaerDetailContent(item: FunktionaerListItem) { + Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { + Text(item.name, style = MaterialTheme.typography.headlineMedium) + Spacer(Modifier.height(8.dp)) + Text("Rolle: ${item.rolle}", style = MaterialTheme.typography.bodyLarge) + item.lizenz?.let { + Text("Lizenz: $it", style = MaterialTheme.typography.bodyLarge) + } + Spacer(Modifier.height(24.dp)) + Text("Weitere Details folgen in der nächsten Ausbaustufe.", style = MaterialTheme.typography.bodyMedium) + } +} diff --git a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/di/NennungModule.kt b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/di/NennungModule.kt similarity index 54% rename from frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/di/NennungModule.kt rename to frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/di/NennungModule.kt index 4b926067..87b58c41 100644 --- a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/di/NennungModule.kt +++ b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/di/NennungModule.kt @@ -1,6 +1,6 @@ -package at.mocode.nennung.feature.di +package at.mocode.frontend.features.nennung.di -import at.mocode.nennung.feature.presentation.NennungViewModel +import at.mocode.frontend.features.nennung.presentation.NennungViewModel import org.koin.core.module.dsl.viewModel import org.koin.dsl.module diff --git a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/domain/NennungModels.kt b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/domain/NennungModels.kt similarity index 98% rename from frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/domain/NennungModels.kt rename to frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/domain/NennungModels.kt index 095d5738..565cf3a8 100644 --- a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/domain/NennungModels.kt +++ b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/domain/NennungModels.kt @@ -1,4 +1,4 @@ -package at.mocode.nennung.feature.domain +package at.mocode.frontend.features.nennung.domain // --- Pferd --- data class Pferd( diff --git a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungViewModel.kt b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungViewModel.kt similarity index 98% rename from frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungViewModel.kt rename to frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungViewModel.kt index 303b1756..df0c7e40 100644 --- a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungViewModel.kt +++ b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungViewModel.kt @@ -1,6 +1,6 @@ -package at.mocode.nennung.feature.presentation +package at.mocode.frontend.features.nennung.presentation -import at.mocode.nennung.feature.domain.* +import at.mocode.frontend.features.nennung.domain.* import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungsMaske.kt b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungsMaske.kt similarity index 99% rename from frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungsMaske.kt rename to frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungsMaske.kt index 7f6ffe05..750c00ec 100644 --- a/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungsMaske.kt +++ b/frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/frontend/features/nennung/presentation/NennungsMaske.kt @@ -1,4 +1,4 @@ -package at.mocode.nennung.feature.presentation +package at.mocode.frontend.features.nennung.presentation import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -17,7 +17,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import at.mocode.nennung.feature.domain.* +import at.mocode.frontend.features.nennung.domain.* // Farben für Startwunsch-Markierung private val FarbeVorne = Color(0xFFE8F5E9) // Grün diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt new file mode 100644 index 00000000..c276d2e3 --- /dev/null +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt @@ -0,0 +1,8 @@ +package at.mocode.frontend.features.pferde.di + +import at.mocode.frontend.features.pferde.presentation.PferdeViewModel +import org.koin.dsl.module + +val pferdeModule = module { + factory { PferdeViewModel() } +} diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/Pferd.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/Pferd.kt index 223f9a7e..14088d6f 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/Pferd.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/Pferd.kt @@ -12,7 +12,12 @@ data class Pferd( val geschlecht: Geschlecht = Geschlecht.WALLACH, val farbe: String = "", val geburtsjahr: Int? = null, - val status: PferdeStatus = PferdeStatus.AKTIV + val status: PferdeStatus = PferdeStatus.AKTIV, + val feiId: String? = null, + val oepsNummer: String? = null, + val vater: String? = null, + val mutter: String? = null, + val besitzer: String? = null ) enum class Geschlecht(val label: String) { diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt index 33cd2c15..263d8188 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt @@ -38,6 +38,9 @@ fun PferdeScreen( onFarbeChange = viewModel::onEditFarbeChange, onGeburtsjahrChange = viewModel::onEditGeburtsjahrChange, onStatusChange = viewModel::onEditStatusChange, + onFeiIdChange = viewModel::onEditFeiIdChange, + onOepsNummerChange = viewModel::onEditOepsNummerChange, + onBesitzerChange = viewModel::onEditBesitzerChange, onSave = viewModel::onSave, onCancel = viewModel::onCancel ) @@ -105,6 +108,9 @@ private fun PferdeEditorContent( onFarbeChange: (String) -> Unit, onGeburtsjahrChange: (String) -> Unit, onStatusChange: (PferdeStatus) -> Unit, + onFeiIdChange: (String) -> Unit, + onOepsNummerChange: (String) -> Unit, + onBesitzerChange: (String) -> Unit, onSave: () -> Unit, onCancel: () -> Unit ) { @@ -134,6 +140,23 @@ private fun PferdeEditorContent( Spacer(Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + MsTextField( + value = uiState.editFeiId, + onValueChange = onFeiIdChange, + label = "FEI ID", + modifier = Modifier.weight(1f) + ) + MsTextField( + value = uiState.editOepsNummer, + onValueChange = onOepsNummerChange, + label = "ÖPS Nummer", + modifier = Modifier.weight(1f) + ) + } + + Spacer(Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { MsEnumDropdown( label = "Geschlecht", @@ -160,16 +183,25 @@ private fun PferdeEditorContent( label = "Geburtsjahr", modifier = Modifier.weight(1f) ) - MsEnumDropdown( - label = "Status", - options = PferdeStatus.entries.toTypedArray(), - selectedOption = uiState.editStatus, - onOptionSelected = onStatusChange, - optionLabel = { it.label }, + MsTextField( + value = uiState.editBesitzer, + onValueChange = onBesitzerChange, + label = "Besitzer", modifier = Modifier.weight(1f) ) } + Spacer(Modifier.height(16.dp)) + + MsEnumDropdown( + label = "Status", + options = PferdeStatus.entries.toTypedArray(), + selectedOption = uiState.editStatus, + onOptionSelected = onStatusChange, + optionLabel = { it.label }, + modifier = Modifier.width(300.dp) + ) + Spacer(Modifier.height(24.dp)) if (uiState.editStatus == PferdeStatus.INAKTIV) { diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt index 8c20154c..e4411ca2 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt @@ -3,6 +3,7 @@ package at.mocode.frontend.features.pferde.presentation import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel import at.mocode.frontend.features.pferde.domain.Geschlecht import at.mocode.frontend.features.pferde.domain.Pferd import at.mocode.frontend.features.pferde.domain.PferdeStatus @@ -21,13 +22,16 @@ data class PferdeUiState( val editGeschlecht: Geschlecht = Geschlecht.WALLACH, val editFarbe: String = "", val editGeburtsjahr: String = "", - val editStatus: PferdeStatus = PferdeStatus.AKTIV + val editStatus: PferdeStatus = PferdeStatus.AKTIV, + val editFeiId: String = "", + val editOepsNummer: String = "", + val editBesitzer: String = "" ) /** * ViewModel für die Pferde-Verwaltung. */ -open class PferdeViewModel(initialLoad: Boolean = true) { +open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() { var uiState by mutableStateOf(PferdeUiState()) protected set @@ -60,10 +64,25 @@ open class PferdeViewModel(initialLoad: Boolean = true) { editGeschlecht = pferd.geschlecht, editFarbe = pferd.farbe, editGeburtsjahr = pferd.geburtsjahr?.toString() ?: "", - editStatus = pferd.status + editStatus = pferd.status, + editFeiId = pferd.feiId ?: "", + editOepsNummer = pferd.oepsNummer ?: "", + editBesitzer = pferd.besitzer ?: "" ) } + fun onEditFeiIdChange(value: String) { + uiState = uiState.copy(editFeiId = value) + } + + fun onEditOepsNummerChange(value: String) { + uiState = uiState.copy(editOepsNummer = value) + } + + fun onEditBesitzerChange(value: String) { + uiState = uiState.copy(editBesitzer = value) + } + fun onEditNameChange(value: String) { uiState = uiState.copy(editName = value) } diff --git a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/di/ReiterModule.kt b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/di/ReiterModule.kt new file mode 100644 index 00000000..0a6acbab --- /dev/null +++ b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/di/ReiterModule.kt @@ -0,0 +1,8 @@ +package at.mocode.frontend.features.reiter.di + +import at.mocode.frontend.features.reiter.presentation.ReiterViewModel +import org.koin.dsl.module + +val reiterModule = module { + factory { ReiterViewModel() } +} diff --git a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/domain/Reiter.kt b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/domain/Reiter.kt index 0604f804..4ca98d8a 100644 --- a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/domain/Reiter.kt +++ b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/domain/Reiter.kt @@ -12,7 +12,13 @@ data class Reiter( val satznummer: String?, val lizenz: LizenzKlasse = LizenzKlasse.KEINE, val sparte: Sparte = Sparte.KEINE, - val status: ReiterStatus = ReiterStatus.AKTIV + val status: ReiterStatus = ReiterStatus.AKTIV, + val feiId: String? = null, + val oepsNummer: String? = null, + val geburtsdatum: String? = null, + val email: String? = null, + val telefon: String? = null, + val verein: String? = null ) { val name: String get() = "$vorname $nachname" } diff --git a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterScreen.kt b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterScreen.kt index 3d2d537b..e9087071 100644 --- a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterScreen.kt +++ b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterScreen.kt @@ -34,6 +34,12 @@ fun ReiterScreen( onNachnameChange = viewModel::onEditNameChange, onLizenzChange = viewModel::onEditLizenzChange, onSparteChange = viewModel::onEditSparteChange, + onFeiIdChange = viewModel::onEditFeiIdChange, + onOepsNummerChange = viewModel::onEditOepsNummerChange, + onGeburtsdatumChange = viewModel::onEditGeburtsdatumChange, + onEmailChange = viewModel::onEditEmailChange, + onTelefonChange = viewModel::onEditTelefonChange, + onVereinChange = viewModel::onEditVereinChange, onSave = viewModel::onSave, onCancel = viewModel::onCancel ) @@ -104,6 +110,12 @@ private fun ReiterEditorContent( onNachnameChange: (String) -> Unit, onLizenzChange: (LizenzKlasse) -> Unit, onSparteChange: (Sparte) -> Unit, + onFeiIdChange: (String) -> Unit, + onOepsNummerChange: (String) -> Unit, + onGeburtsdatumChange: (String) -> Unit, + onEmailChange: (String) -> Unit, + onTelefonChange: (String) -> Unit, + onVereinChange: (String) -> Unit, onSave: () -> Unit, onCancel: () -> Unit ) { @@ -133,6 +145,57 @@ private fun ReiterEditorContent( Spacer(Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + MsTextField( + value = uiState.editFeiId, + onValueChange = onFeiIdChange, + label = "FEI ID", + modifier = Modifier.weight(1f) + ) + MsTextField( + value = uiState.editOepsNummer, + onValueChange = onOepsNummerChange, + label = "ÖPS Nummer", + modifier = Modifier.weight(1f) + ) + } + + Spacer(Modifier.height(16.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + MsTextField( + value = uiState.editGeburtsdatum, + onValueChange = onGeburtsdatumChange, + label = "Geburtsdatum", + modifier = Modifier.weight(1f) + ) + MsTextField( + value = uiState.editVerein, + onValueChange = onVereinChange, + label = "Verein", + modifier = Modifier.weight(1f) + ) + } + + Spacer(Modifier.height(16.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + MsTextField( + value = uiState.editEmail, + onValueChange = onEmailChange, + label = "E-Mail", + modifier = Modifier.weight(1f) + ) + MsTextField( + value = uiState.editTelefon, + onValueChange = onTelefonChange, + label = "Telefon", + modifier = Modifier.weight(1f) + ) + } + + Spacer(Modifier.height(16.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { MsEnumDropdown( label = "Lizenzklasse", diff --git a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterViewModel.kt b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterViewModel.kt index 2f22cc1b..c4e57659 100644 --- a/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterViewModel.kt +++ b/frontend/features/reiter-feature/src/commonMain/kotlin/at/mocode/frontend/features/reiter/presentation/ReiterViewModel.kt @@ -3,6 +3,7 @@ package at.mocode.frontend.features.reiter.presentation import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel import at.mocode.frontend.features.reiter.domain.LizenzKlasse import at.mocode.frontend.features.reiter.domain.Reiter import at.mocode.frontend.features.reiter.domain.ReiterStatus @@ -21,14 +22,20 @@ data class ReiterUiState( val editVorname: String = "", val editLizenz: LizenzKlasse = LizenzKlasse.KEINE, val editSparte: Sparte = Sparte.KEINE, - val editStatus: ReiterStatus = ReiterStatus.AKTIV + val editStatus: ReiterStatus = ReiterStatus.AKTIV, + val editFeiId: String = "", + val editOepsNummer: String = "", + val editGeburtsdatum: String = "", + val editEmail: String = "", + val editTelefon: String = "", + val editVerein: String = "" ) /** * ViewModel für die Reiter-Verwaltung. * In einem echten Szenario würden wir hier ein Repository injizieren. */ -open class ReiterViewModel(initialLoad: Boolean = true) { +open class ReiterViewModel(initialLoad: Boolean = true) : ViewModel() { var uiState by mutableStateOf(ReiterUiState()) protected set @@ -62,10 +69,23 @@ open class ReiterViewModel(initialLoad: Boolean = true) { editName = reiter.nachname, editLizenz = reiter.lizenz, editSparte = reiter.sparte, - editStatus = reiter.status + editStatus = reiter.status, + editFeiId = reiter.feiId ?: "", + editOepsNummer = reiter.oepsNummer ?: "", + editGeburtsdatum = reiter.geburtsdatum ?: "", + editEmail = reiter.email ?: "", + editTelefon = reiter.telefon ?: "", + editVerein = reiter.verein ?: "" ) } + fun onEditFeiIdChange(value: String) { uiState = uiState.copy(editFeiId = value) } + fun onEditOepsNummerChange(value: String) { uiState = uiState.copy(editOepsNummer = value) } + fun onEditGeburtsdatumChange(value: String) { uiState = uiState.copy(editGeburtsdatum = value) } + fun onEditEmailChange(value: String) { uiState = uiState.copy(editEmail = value) } + fun onEditTelefonChange(value: String) { uiState = uiState.copy(editTelefon = value) } + fun onEditVereinChange(value: String) { uiState = uiState.copy(editVerein = value) } + fun onEditVornameChange(value: String) { uiState = uiState.copy(editVorname = value) } diff --git a/frontend/features/veranstalter-feature/src/commonMain/kotlin/at/mocode/frontend/features/veranstalter/domain/Veranstalter.kt b/frontend/features/veranstalter-feature/src/commonMain/kotlin/at/mocode/frontend/features/veranstalter/domain/Veranstalter.kt new file mode 100644 index 00000000..af2bafc2 --- /dev/null +++ b/frontend/features/veranstalter-feature/src/commonMain/kotlin/at/mocode/frontend/features/veranstalter/domain/Veranstalter.kt @@ -0,0 +1,12 @@ +package at.mocode.frontend.features.veranstalter.domain + +data class Veranstalter( + val id: String, + val name: String, + val verein: String? = null, + val adresse: String? = null, + val email: String? = null, + val telefon: String? = null, + val znsId: String? = null, + val oepsNummer: String? = null +) diff --git a/frontend/shells/meldestelle-desktop/build.gradle.kts b/frontend/shells/meldestelle-desktop/build.gradle.kts index b2db0525..d2dc5fd2 100644 --- a/frontend/shells/meldestelle-desktop/build.gradle.kts +++ b/frontend/shells/meldestelle-desktop/build.gradle.kts @@ -52,17 +52,19 @@ kotlin { implementation(projects.core.znsParser) // Feature-Module - implementation(projects.frontend.features.nennungFeature) implementation(projects.frontend.features.pingFeature) + implementation(projects.frontend.features.nennungFeature) + implementation(projects.frontend.features.znsImportFeature) implementation(projects.frontend.features.veranstalterFeature) implementation(projects.frontend.features.veranstaltungFeature) + implementation(projects.frontend.features.funktionaerFeature) + implementation(projects.frontend.features.profileFeature) + implementation(projects.frontend.features.reiterFeature) + implementation(projects.frontend.features.pferdeFeature) + implementation(projects.frontend.features.vereinFeature) implementation(projects.frontend.features.turnierFeature) - implementation(project(":frontend:features:profile-feature")) - implementation(project(":frontend:features:reiter-feature")) - implementation(project(":frontend:features:pferde-feature")) - implementation(project(":frontend:features:billing-feature")) - implementation(project(":frontend:features:verein-feature")) + implementation(projects.frontend.features.billingFeature) // Compose Desktop implementation(compose.desktop.currentOs) diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/main.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/main.kt index f00af769..e636d265 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/main.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/main.kt @@ -14,7 +14,9 @@ import at.mocode.frontend.core.sync.di.syncModule import at.mocode.frontend.features.billing.di.billingModule import at.mocode.frontend.features.profile.di.profileModule import at.mocode.frontend.features.verein.di.vereinFeatureModule -import at.mocode.nennung.feature.di.nennungFeatureModule +import at.mocode.frontend.features.nennung.di.nennungFeatureModule +import at.mocode.frontend.features.pferde.di.pferdeModule +import at.mocode.frontend.features.reiter.di.reiterModule import at.mocode.ping.feature.di.pingFeatureModule import at.mocode.zns.feature.di.znsImportModule import kotlinx.coroutines.runBlocking @@ -36,6 +38,8 @@ fun main() = application { znsImportModule, profileModule, billingModule, + pferdeModule, + reiterModule, vereinFeatureModule, desktopModule, ) diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/screens/layout/DesktopMainLayout.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/screens/layout/DesktopMainLayout.kt index 8a6a04b6..2e071807 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/screens/layout/DesktopMainLayout.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/screens/layout/DesktopMainLayout.kt @@ -27,6 +27,12 @@ import at.mocode.frontend.features.verein.presentation.VereinScreen import at.mocode.frontend.features.verein.presentation.VereinViewModel import at.mocode.ping.feature.presentation.PingScreen import at.mocode.ping.feature.presentation.PingViewModel +import at.mocode.frontend.features.pferde.presentation.PferdeScreen +import at.mocode.frontend.features.pferde.presentation.PferdeViewModel +import at.mocode.frontend.features.reiter.presentation.ReiterScreen +import at.mocode.frontend.features.reiter.presentation.ReiterViewModel +import at.mocode.frontend.features.nennung.presentation.NennungViewModel +import at.mocode.frontend.features.nennung.presentation.NennungsMaske import at.mocode.turnier.feature.presentation.TurnierDetailScreen import at.mocode.veranstalter.feature.presentation.FakeVeranstalterStore import at.mocode.veranstaltung.feature.presentation.AdminUebersichtScreen @@ -379,14 +385,18 @@ private fun DesktopContentArea( // Onboarding ohne Login is AppScreen.Onboarding -> { val authTokenManager: at.mocode.frontend.core.auth.data.AuthTokenManager = koinInject() - at.mocode.desktop.v2.OnboardingScreen( - geraetName = obGeraet, - secureKey = obKey, - onGeraetNameChange = onObGeraetChange, - onSecureKeyChange = onObKeyChange, - ) { _, _ -> - authTokenManager.setToken("dummy.jwt.token") - onNavigate(AppScreen.VeranstaltungVerwaltung) + at.mocode.frontend.core.designsystem.theme.AppTheme { + Surface(color = MaterialTheme.colorScheme.background) { + at.mocode.desktop.v2.OnboardingScreen( + geraetName = obGeraet, + secureKey = obKey, + onGeraetNameChange = onObGeraetChange, + onSecureKeyChange = onObKeyChange, + ) { _, _ -> + authTokenManager.setToken("dummy.jwt.token") + onNavigate(AppScreen.VeranstaltungVerwaltung) + } + } } } @@ -412,37 +422,50 @@ private fun DesktopContentArea( } // --- Pferde-Verwaltung & Profil --- - is AppScreen.PferdVerwaltung -> at.mocode.desktop.v2.PferdeVerwaltungScreen( - onBack = onBack, - onEdit = { onNavigate(AppScreen.PferdProfil(it)) } - ) + is AppScreen.PferdVerwaltung -> { + val viewModel = koinViewModel() + PferdeScreen(viewModel = viewModel) + } - is AppScreen.PferdProfil -> at.mocode.desktop.v2.PferdProfilV2( - id = currentScreen.id, - onBack = onBack, - ) + is AppScreen.PferdProfil -> { + val viewModel = koinViewModel() + // In der aktuellen Ausbaustufe wählen wir das Pferd im ViewModel aus + LaunchedEffect(currentScreen.id) { + // Mock: Wir suchen das Pferd in den Suchergebnissen + viewModel.uiState.searchResults.find { it.id == currentScreen.id.toString() }?.let { + viewModel.selectPferd(it) + } + } + PferdeScreen(viewModel = viewModel) + } // --- Reiter-Verwaltung & Profil --- - is AppScreen.ReiterVerwaltung -> at.mocode.desktop.v2.ReiterVerwaltungScreen( - onBack = onBack, - onEdit = { onNavigate(AppScreen.ReiterProfil(it)) } - ) + is AppScreen.ReiterVerwaltung -> { + val viewModel = koinViewModel() + ReiterScreen(viewModel = viewModel) + } - is AppScreen.ReiterProfil -> at.mocode.desktop.v2.ReiterProfilV2( - id = currentScreen.id, - onBack = onBack, - ) + is AppScreen.ReiterProfil -> { + val viewModel = koinViewModel() + LaunchedEffect(currentScreen.id) { + viewModel.uiState.searchResults.find { it.id == currentScreen.id.toString() }?.let { + viewModel.selectReiter(it) + } + } + ReiterScreen(viewModel = viewModel) + } // --- Verein-Verwaltung & Profil --- - is AppScreen.VereinVerwaltung -> at.mocode.desktop.v2.VereinVerwaltungScreen( - onBack = onBack, - onEdit = { onNavigate(AppScreen.VereinProfil(it)) } - ) + is AppScreen.VereinVerwaltung -> { + val vereinViewModel: VereinViewModel = koinViewModel() + VereinScreen(viewModel = vereinViewModel) + } - is AppScreen.VereinProfil -> at.mocode.desktop.v2.VereinProfilV2( - id = currentScreen.id, - onBack = onBack, - ) + is AppScreen.VereinProfil -> { + val vereinViewModel: VereinViewModel = koinViewModel() + // Mock: Selektion im ViewModel (falls unterstützt) + VereinScreen(viewModel = vereinViewModel) + } // --- Funktionaer-Verwaltung & Profil --- is AppScreen.FunktionaerVerwaltung -> at.mocode.desktop.v2.FunktionaerVerwaltungScreen( @@ -664,6 +687,14 @@ private fun DesktopContentArea( ) } + is AppScreen.Nennung -> { + val nennungViewModel: at.mocode.frontend.features.nennung.presentation.NennungViewModel = koinViewModel() + NennungsMaske( + viewModel = nennungViewModel, + onAbrechnungOeffnen = { /* Navigation zu Billing falls nötig */ } + ) + } + // Fallback → Root else -> AdminUebersichtScreen( onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) }, diff --git a/settings.gradle.kts b/settings.gradle.kts index 884f265e..81358253 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -137,6 +137,7 @@ include(":frontend:features:nennung-feature") include(":frontend:features:zns-import-feature") include(":frontend:features:veranstalter-feature") include(":frontend:features:veranstaltung-feature") +include(":frontend:features:funktionaer-feature") include(":frontend:features:profile-feature") include(":frontend:features:reiter-feature") include(":frontend:features:pferde-feature")