diff --git a/backend/services/masterdata/masterdata-domain/build.gradle.kts b/backend/services/masterdata/masterdata-domain/build.gradle.kts index 9e1396e5..203f1fed 100644 --- a/backend/services/masterdata/masterdata-domain/build.gradle.kts +++ b/backend/services/masterdata/masterdata-domain/build.gradle.kts @@ -21,10 +21,5 @@ kotlin { implementation(kotlin("test")) } } - val jvmTest by getting { - dependencies { - implementation(projects.platform.platformTesting) - } - } } } diff --git a/backend/services/masterdata/masterdata-domain/src/commonTest/kotlin/at/mocode/masterdata/domain/service/AltersklasseRechnerTest.kt b/backend/services/masterdata/masterdata-domain/src/commonTest/kotlin/at/mocode/masterdata/domain/service/AltersklasseRechnerTest.kt index e914dab0..7dadde69 100644 --- a/backend/services/masterdata/masterdata-domain/src/commonTest/kotlin/at/mocode/masterdata/domain/service/AltersklasseRechnerTest.kt +++ b/backend/services/masterdata/masterdata-domain/src/commonTest/kotlin/at/mocode/masterdata/domain/service/AltersklasseRechnerTest.kt @@ -72,7 +72,7 @@ class AltersklasseRechnerTest { } @Test - fun `ermittleAltersklassen berücksichtigt SpartenFilter`() { + fun `ermittleAltersklassen beruecksichtigt SpartenFilter`() { val reiter = DomReiter( personId = Uuid.random(), satznummer = "123456", diff --git a/docs/06_Frontend/Veranstalter-Card-v01.png b/docs/06_Frontend/Veranstalter-Card-v01.png new file mode 100644 index 00000000..0bf5184f Binary files /dev/null and b/docs/06_Frontend/Veranstalter-Card-v01.png differ diff --git a/docs/06_Frontend/Veranstalter-Profil-Card-v01.png b/docs/06_Frontend/Veranstalter-Profil-Card-v01.png new file mode 100644 index 00000000..4e626208 Binary files /dev/null and b/docs/06_Frontend/Veranstalter-Profil-Card-v01.png differ diff --git a/frontend/core/navigation/src/commonTest/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandlerTest.kt b/frontend/core/navigation/src/commonTest/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandlerTest.kt index b623a351..3d402af3 100644 --- a/frontend/core/navigation/src/commonTest/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandlerTest.kt +++ b/frontend/core/navigation/src/commonTest/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandlerTest.kt @@ -17,6 +17,9 @@ private class FakeNav : NavigationPort { override fun navigateToScreen(screen: AppScreen) { last = screen.route } + override fun navigateBack() { + // no-op for tests + } } private class FakeUserProvider(private val user: User?) : CurrentUserProvider { 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 2c748f7b..0604f804 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 @@ -35,8 +35,7 @@ enum class Sparte(val label: String) { SPRINGEN("Springen"), VIELSEITIGKEIT("Vielseitigkeit"), VOLTIGIEREN("Voltigieren"), - FAHREN("Fahren"), - REINING("Reining") + FAHREN("Fahren") } enum class ReiterStatus(val label: String, val color: Color) { diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/PreviewMain.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/PreviewMain.kt index bca81531..5b1a6a34 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/PreviewMain.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/PreviewMain.kt @@ -5,6 +5,8 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.window.singleWindowApplication +import at.mocode.frontend.features.reiter.presentation.ReiterScreen +import at.mocode.frontend.features.reiter.presentation.ReiterViewModel /** * Hot-Reload Preview Entry Point @@ -27,7 +29,7 @@ private fun PreviewContent() { Surface { // --- REITER --- - // ReiterScreen(viewModel = ReiterViewModel()) + ReiterScreen(viewModel = ReiterViewModel()) // --- PFERDE --- // PferdeScreen(viewModel = PferdeViewModel()) 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 2427269a..c8174e12 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 @@ -11,8 +11,11 @@ import androidx.compose.material.icons.filled.Wifi import androidx.compose.material.icons.filled.WifiOff import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -56,6 +59,10 @@ fun DesktopMainLayout( onBack: () -> Unit, onLogout: () -> Unit, ) { + // Onboarding-Eingaben zwischen Navigationswechseln behalten → State hier (außerhalb des when) hosten + var obGeraet by rememberSaveable { mutableStateOf("") } + var obKey by rememberSaveable { mutableStateOf("") } + Column(modifier = Modifier.fillMaxSize()) { DesktopTopBar( currentScreen = currentScreen, @@ -69,6 +76,10 @@ fun DesktopMainLayout( currentScreen = currentScreen, onNavigate = onNavigate, onBack = onBack, + obGeraet = obGeraet, + obKey = obKey, + onObGeraetChange = { obGeraet = it }, + onObKeyChange = { obKey = it }, ) } DesktopFooterBar() @@ -102,8 +113,8 @@ private fun DesktopTopBar( horizontalArrangement = Arrangement.SpaceBetween, ) { Row(verticalAlignment = Alignment.CenterVertically) { - // Zurück-Pfeil (nur wenn nicht Root) - if (currentScreen !is AppScreen.VeranstaltungVerwaltung) { + // Zurück-Pfeil: für alle außer Onboarding anzeigen (damit man von "Verwaltung" zurück kommt) + if (currentScreen !is AppScreen.Onboarding) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück", @@ -138,10 +149,10 @@ private fun DesktopTopBar( is AppScreen.VeranstalterNeu -> { BreadcrumbSeparator() Text( - text = "Veranstalter auswählen", + text = "Veranstalter-Verwaltung", color = TopBarTextColor.copy(alpha = 0.75f), fontSize = 14.sp, - modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) }, + modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) }, ) BreadcrumbSeparator() Text( @@ -154,10 +165,10 @@ private fun DesktopTopBar( is AppScreen.VeranstalterDetail -> { BreadcrumbSeparator() Text( - text = "Veranstalter auswählen", + text = "Veranstalter-Verwaltung", color = TopBarTextColor.copy(alpha = 0.75f), fontSize = 14.sp, - modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) }, + modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) }, ) BreadcrumbSeparator() Text( @@ -170,10 +181,10 @@ private fun DesktopTopBar( is AppScreen.VeranstaltungProfil -> { BreadcrumbSeparator() Text( - text = "Veranstalter auswählen", + text = "Veranstalter-Verwaltung", color = TopBarTextColor.copy(alpha = 0.75f), fontSize = 14.sp, - modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) }, + modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) }, ) BreadcrumbSeparator() Text( @@ -335,12 +346,21 @@ private fun DesktopContentArea( currentScreen: AppScreen, onNavigate: (AppScreen) -> Unit, onBack: () -> Unit, + obGeraet: String, + obKey: String, + onObGeraetChange: (String) -> Unit, + onObKeyChange: (String) -> Unit, ) { when (currentScreen) { // Onboarding ohne Login is AppScreen.Onboarding -> { val authTokenManager: at.mocode.frontend.core.auth.data.AuthTokenManager = koinInject() - at.mocode.desktop.v2.OnboardingScreen { _, _ -> + at.mocode.desktop.v2.OnboardingScreen( + geraetName = obGeraet, + secureKey = obKey, + onGeraetNameChange = onObGeraetChange, + onSecureKeyChange = onObKeyChange, + ) { _, _ -> authTokenManager.setToken("dummy.jwt.token") onNavigate(AppScreen.VeranstaltungVerwaltung) } @@ -373,11 +393,9 @@ private fun DesktopContentArea( onEdit = { onNavigate(AppScreen.PferdProfil(it)) } ) - is AppScreen.PferdProfil -> PlaceholderScreen( - "Pferde-Profil #${currentScreen.id}", + is AppScreen.PferdProfil -> at.mocode.desktop.v2.PferdProfilV2( + id = currentScreen.id, onBack = onBack, - onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) }, - actionLabel = "Zurück zur Zentrale" ) // --- Reiter-Verwaltung & Profil --- @@ -386,11 +404,9 @@ private fun DesktopContentArea( onEdit = { onNavigate(AppScreen.ReiterProfil(it)) } ) - is AppScreen.ReiterProfil -> PlaceholderScreen( - "Reiter-Profil #${currentScreen.id}", + is AppScreen.ReiterProfil -> at.mocode.desktop.v2.ReiterProfilV2( + id = currentScreen.id, onBack = onBack, - onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) }, - actionLabel = "Zurück zur Zentrale" ) // --- Verein-Verwaltung & Profil --- @@ -399,11 +415,9 @@ private fun DesktopContentArea( onEdit = { onNavigate(AppScreen.VereinProfil(it)) } ) - is AppScreen.VereinProfil -> PlaceholderScreen( - "Verein-Profil #${currentScreen.id}", + is AppScreen.VereinProfil -> at.mocode.desktop.v2.VereinProfilV2( + id = currentScreen.id, onBack = onBack, - onAction = { onNavigate(AppScreen.VereinVerwaltung) }, - actionLabel = "Zurück zur Zentrale" ) // --- Funktionaer-Verwaltung & Profil --- @@ -412,24 +426,23 @@ private fun DesktopContentArea( onEdit = { onNavigate(AppScreen.FunktionaerProfil(it)) } ) - is AppScreen.FunktionaerProfil -> PlaceholderScreen( - "Funktionär-Profil #${currentScreen.id}", + is AppScreen.FunktionaerProfil -> at.mocode.desktop.v2.FunktionaerProfilV2( + id = currentScreen.id, onBack = onBack, - onAction = { onNavigate(AppScreen.FunktionaerVerwaltung) }, - actionLabel = "Zurück zur Zentrale" ) // --- Veranstalter-Verwaltung & Profil --- is AppScreen.VeranstalterVerwaltung -> at.mocode.desktop.v2.VeranstalterVerwaltungScreen( onBack = onBack, + onNew = { onNavigate(AppScreen.VeranstalterNeu) }, onEdit = { onNavigate(AppScreen.VeranstalterProfil(it)) } ) - is AppScreen.VeranstalterProfil -> PlaceholderScreen( - "Veranstalter-Profil #${currentScreen.id}", + is AppScreen.VeranstalterProfil -> at.mocode.desktop.v2.VeranstalterDetailV2( + veranstalterId = currentScreen.id, onBack = onBack, - onAction = { onNavigate(AppScreen.PferdProfil(1L)) }, - actionLabel = "Pferde-Profil öffnen" + onZurVeranstaltung = { evtId -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) }, + onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(currentScreen.id)) }, ) /* @@ -445,9 +458,9 @@ private fun DesktopContentArea( onNeu = { onNavigate(AppScreen.VeranstalterNeu) }, ) - is AppScreen.VeranstalterNeu -> VeranstalterNeuScreen( - onAbbrechen = onBack, - onSpeichern = { _, _, _ -> onBack() }, + is AppScreen.VeranstalterNeu -> at.mocode.desktop.v2.VeranstalterAnlegenWizard( + onCancel = onBack, + onVereinCreated = { newId -> onNavigate(AppScreen.VeranstalterProfil(newId)) } ) is AppScreen.VeranstalterDetail -> { val vId = currentScreen.veranstalterId diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/ManagementScreens.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/ManagementScreens.kt index 6efe6f2c..ec892e3a 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/ManagementScreens.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/ManagementScreens.kt @@ -5,8 +5,10 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.* import androidx.compose.material3.* +import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,7 +37,7 @@ fun ManagementTableScreen( ) { Row(verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = onBack) { - Icon(Icons.Default.ArrowBack, contentDescription = "Zurück") + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") } Text(title, style = MaterialTheme.typography.headlineMedium) } @@ -87,7 +89,7 @@ fun ManagementTableScreen( } } - Divider() + HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color) // Table Body LazyColumn(modifier = Modifier.fillMaxSize()) { @@ -121,7 +123,7 @@ fun ManagementTableScreen( } } } - Divider() + HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color) } } } @@ -248,7 +250,7 @@ fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) { } @Composable -fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) { +fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onNew: () -> Unit, onEdit: (Long) -> Unit) { // Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten // Wir nutzen hier die 'vereine' Liste aus dem Store. val vereine = StoreV2.vereine @@ -268,7 +270,7 @@ fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) { TableColumn("Email", { it.email ?: "-" }, weight = 1f) ), onBack = onBack, - onNew = { }, + onNew = onNew, onEdit = { onEdit(it.id) }, onDelete = { }, onSearch = { filter = it } diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/Screens.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/Screens.kt index 77985491..a5d744e1 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/Screens.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/Screens.kt @@ -1,12 +1,14 @@ package at.mocode.desktop.v2 import androidx.compose.foundation.clickable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.* @@ -14,30 +16,66 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type import androidx.compose.ui.text.font.FontWeight +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager @Composable -fun OnboardingScreen(onContinue: (String, String) -> Unit) { +fun OnboardingScreen( + geraetName: String, + secureKey: String, + onGeraetNameChange: (String) -> Unit, + onSecureKeyChange: (String) -> Unit, + onContinue: (String, String) -> Unit, +) { DesktopThemeV2 { Surface(color = MaterialTheme.colorScheme.background) { Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { Text("Onboarding", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold) - var geraetName by remember { mutableStateOf("") } - var key by remember { mutableStateOf("") } var showPw by remember { mutableStateOf(false) } + val focusManager = LocalFocusManager.current + val frName = remember { FocusRequester() } + val frKey = remember { FocusRequester() } + val frBtn = remember { FocusRequester() } OutlinedTextField( value = geraetName, - onValueChange = { geraetName = it }, + onValueChange = { onGeraetNameChange(it) }, label = { Text("Gerätename (Pflicht)") }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .focusRequester(frName) + .onKeyEvent { e -> + if (e.type == KeyEventType.KeyUp) { + when (e.key) { + Key.Tab, Key.Enter -> { + focusManager.moveFocus(FocusDirection.Next) + true + } + else -> false + } + } else false + } + , + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }) ) OutlinedTextField( - value = key, - onValueChange = { key = it }, + value = secureKey, + onValueChange = { onSecureKeyChange(it) }, label = { Text("Sicherheitsschlüssel (Pflicht)") }, trailingIcon = { IconButton(onClick = { showPw = !showPw }) { @@ -45,12 +83,53 @@ fun OnboardingScreen(onContinue: (String, String) -> Unit) { } }, visualTransformation = if (showPw) VisualTransformation.None else PasswordVisualTransformation(), - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .focusRequester(frKey) + .onKeyEvent { e -> + if (e.type == KeyEventType.KeyUp) { + when (e.key) { + Key.Tab -> { + focusManager.moveFocus(FocusDirection.Next) + true + } + Key.Enter -> { + if (geraetName.trim().length >= 3 && secureKey.trim().length >= 8) { + onContinue(geraetName, secureKey) + } else { + focusManager.moveFocus(FocusDirection.Next) + } + true + } + else -> false + } + } else false + } + , + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { + if (geraetName.trim().length >= 3 && secureKey.trim().length >= 8) { + onContinue(geraetName, secureKey) + } else { + focusManager.moveFocus(FocusDirection.Next) + } + }) ) - val enabled = geraetName.trim().length >= 3 && key.trim().length >= 8 - Button(onClick = { onContinue(geraetName, key) }, enabled = enabled) { - Text("Weiter zum Veranstalter‑Flow") + val enabled = geraetName.trim().length >= 3 && secureKey.trim().length >= 8 + Button( + onClick = { onContinue(geraetName, secureKey) }, + enabled = enabled, + modifier = Modifier + .focusRequester(frBtn) + .onKeyEvent { e -> + if (e.type == KeyEventType.KeyUp && (e.key == Key.Enter)) { + if (enabled) onContinue(geraetName, secureKey) + true + } else false + } + ) { + Text("Zu den Veranstaltungen") } if (!enabled) Text("Mind. 3 Zeichen für Namen und 8 Zeichen für Schlüssel", color = Color(0xFFB00020)) } @@ -58,6 +137,298 @@ fun OnboardingScreen(onContinue: (String, String) -> Unit) { } } +@Composable +fun PferdProfilV2(id: Long, onBack: () -> Unit) { + DesktopThemeV2 { + val pferd = remember(id) { StoreV2.pferde.firstOrNull { it.id == id } } + if (pferd == null) { Text("Pferd nicht gefunden"); return@DesktopThemeV2 } + Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") } + Text("Pferde-Profil", style = MaterialTheme.typography.titleLarge) + } + + var editOpen by remember { mutableStateOf(false) } + Card(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.size(56.dp).background(Color(0xFF374151), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) { + Text(pferd.name.take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text(pferd.name, style = MaterialTheme.typography.titleMedium) + val l2 = listOfNotNull(pferd.oepsNummer?.let { "OEPS: $it" }, pferd.feiId?.let { "FEI: $it" }).joinToString(" · ") + if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280)) + val l3 = listOfNotNull(pferd.geburtsdatum?.let { "geb. $it" }, pferd.farbe).joinToString(" · ") + if (l3.isNotBlank()) Text(l3, color = Color(0xFF6B7280)) + } + Button(onClick = { editOpen = true }) { Text("bearbeiten") } + } + } + + if (editOpen) { + var name by remember { mutableStateOf(pferd.name) } + var oeps by remember { mutableStateOf(pferd.oepsNummer ?: "") } + var fei by remember { mutableStateOf(pferd.feiId ?: "") } + var geb by remember { mutableStateOf(pferd.geburtsdatum ?: "") } + var farbe by remember { mutableStateOf(pferd.farbe ?: "") } + + AlertDialog( + onDismissRequest = { editOpen = false }, + confirmButton = { + TextButton(onClick = { + pferd.name = name + pferd.oepsNummer = oeps.ifBlank { null } + pferd.feiId = fei.ifBlank { null } + pferd.geburtsdatum = geb.ifBlank { null } + pferd.farbe = farbe.ifBlank { null } + editOpen = false + }) { Text("Speichern") } + }, + dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } }, + title = { Text("Pferd bearbeiten") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f)) + OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(geb, { geb = it }, label = { Text("Geburtsdatum") }, modifier = Modifier.weight(1f)) + OutlinedTextField(farbe, { farbe = it }, label = { Text("Farbe") }, modifier = Modifier.weight(1f)) + } + } + } + ) + } + } + } +} + +@Composable +fun ReiterProfilV2(id: Long, onBack: () -> Unit) { + DesktopThemeV2 { + val r = remember(id) { StoreV2.reiter.firstOrNull { it.id == id } } + if (r == null) { Text("Reiter nicht gefunden"); return@DesktopThemeV2 } + Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") } + Text("Reiter-Profil", style = MaterialTheme.typography.titleLarge) + } + + var editOpen by remember { mutableStateOf(false) } + Card(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.size(56.dp).background(Color(0xFF4B5563), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) { + val initials = (r.vorname + " " + r.nachname).trim().split(" ").mapNotNull { it.firstOrNull()?.toString() }.take(2).joinToString("") + Text(initials.uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text("${r.vorname} ${r.nachname}", style = MaterialTheme.typography.titleMedium) + val l2 = listOfNotNull(r.oepsNummer?.let { "OEPS: $it" }, r.feiId?.let { "FEI: $it" }, r.lizenzKlasse.takeIf { it.isNotBlank() } ).joinToString(" · ") + if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280)) + r.verein?.let { Text(it, color = Color(0xFF6B7280)) } + } + Button(onClick = { editOpen = true }) { Text("bearbeiten") } + } + } + + if (editOpen) { + var vor by remember { mutableStateOf(r.vorname) } + var nach by remember { mutableStateOf(r.nachname) } + var oeps by remember { mutableStateOf(r.oepsNummer ?: "") } + var fei by remember { mutableStateOf(r.feiId ?: "") } + var liz by remember { mutableStateOf(r.lizenzKlasse) } + var verein by remember { mutableStateOf(r.verein ?: "") } + + AlertDialog( + onDismissRequest = { editOpen = false }, + confirmButton = { + TextButton(onClick = { + r.vorname = vor + r.nachname = nach + r.oepsNummer = oeps.ifBlank { null } + r.feiId = fei.ifBlank { null } + r.lizenzKlasse = liz + r.verein = verein.ifBlank { null } + editOpen = false + }) { Text("Speichern") } + }, + dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } }, + title = { Text("Reiter bearbeiten") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(vor, { vor = it }, label = { Text("Vorname") }, modifier = Modifier.weight(1f)) + OutlinedTextField(nach, { nach = it }, label = { Text("Nachname") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f)) + OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(liz, { liz = it }, label = { Text("Lizenzklasse") }, modifier = Modifier.weight(1f)) + OutlinedTextField(verein, { verein = it }, label = { Text("Verein") }, modifier = Modifier.weight(1f)) + } + } + } + ) + } + } + } +} + +@Composable +fun VereinProfilV2(id: Long, onBack: () -> Unit) { + DesktopThemeV2 { + val v = remember(id) { StoreV2.vereine.firstOrNull { it.id == id } } + if (v == null) { Text("Verein nicht gefunden"); return@DesktopThemeV2 } + Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") } + Text("Vereins-Profil", style = MaterialTheme.typography.titleLarge) + } + + var editOpen by remember { mutableStateOf(false) } + Card(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.size(56.dp).background(Color(0xFF1F2937), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) { + Text((v.kurzname ?: v.name).take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text(v.name, style = MaterialTheme.typography.titleMedium) + val l2 = listOfNotNull("OEPS: ${v.oepsNummer}", v.ort, v.plz, v.strasse).filter { it.isNotBlank() }.joinToString(" · ") + if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280)) + val l3 = listOfNotNull(v.email, v.telefon).filter { !it.isNullOrBlank() }.joinToString(" · ") + if (l3.isNotBlank()) Text(l3, color = Color(0xFF6B7280)) + } + Button(onClick = { editOpen = true }) { Text("bearbeiten") } + } + } + + if (editOpen) { + var name by remember { mutableStateOf(v.name) } + var oeps by remember { mutableStateOf(v.oepsNummer) } + var ort by remember { mutableStateOf(v.ort ?: "") } + var plz by remember { mutableStateOf(v.plz ?: "") } + var strasse by remember { mutableStateOf(v.strasse ?: "") } + var email by remember { mutableStateOf(v.email ?: "") } + var tel by remember { mutableStateOf(v.telefon ?: "") } + var logo by remember { mutableStateOf(v.logoUrl ?: "") } + + AlertDialog( + onDismissRequest = { editOpen = false }, + confirmButton = { + TextButton(onClick = { + v.name = name + v.oepsNummer = oeps + v.ort = ort.ifBlank { null } + v.plz = plz.ifBlank { null } + v.strasse = strasse.ifBlank { null } + v.email = email.ifBlank { null } + v.telefon = tel.ifBlank { null } + v.logoUrl = logo.ifBlank { null } + editOpen = false + }) { Text("Speichern") } + }, + dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } }, + title = { Text("Verein bearbeiten") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(oeps, { oeps = it }, label = { Text("OEPS-Nummer") }, modifier = Modifier.weight(1f)) + OutlinedTextField(logo, { logo = it }, label = { Text("Logo-URL") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(ort, { ort = it }, label = { Text("Ort") }, modifier = Modifier.weight(1f)) + OutlinedTextField(plz, { plz = it }, label = { Text("PLZ") }, modifier = Modifier.weight(1f)) + } + OutlinedTextField(strasse, { strasse = it }, label = { Text("Straße / Adresse") }, modifier = Modifier.fillMaxWidth()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(email, { email = it }, label = { Text("E-Mail") }, modifier = Modifier.weight(1f)) + OutlinedTextField(tel, { tel = it }, label = { Text("Telefon") }, modifier = Modifier.weight(1f)) + } + } + } + ) + } + } + } +} + +@Composable +fun FunktionaerProfilV2(id: Long, onBack: () -> Unit) { + DesktopThemeV2 { + val f = remember(id) { StoreV2.funktionaere.firstOrNull { it.id == id } } + if (f == null) { Text("Funktionär nicht gefunden"); return@DesktopThemeV2 } + Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") } + Text("Funktionärs-Profil", style = MaterialTheme.typography.titleLarge) + } + + var editOpen by remember { mutableStateOf(false) } + Card(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.size(56.dp).background(Color(0xFF111827), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) { + val initials = (f.vorname + " " + f.nachname).trim().split(" ").mapNotNull { it.firstOrNull()?.toString() }.take(2).joinToString("") + Text(initials.uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text("${f.vorname} ${f.nachname}", style = MaterialTheme.typography.titleMedium) + val l2 = listOfNotNull(f.richterNummer?.let { "Nr. $it" }, f.richterQualifikation?.let { "Qual.: $it" }).joinToString(" · ") + if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280)) + f.email?.let { Text(it, color = Color(0xFF6B7280)) } + } + Button(onClick = { editOpen = true }) { Text("bearbeiten") } + } + } + + if (editOpen) { + var vor by remember { mutableStateOf(f.vorname) } + var nach by remember { mutableStateOf(f.nachname) } + var num by remember { mutableStateOf(f.richterNummer ?: "") } + var qual by remember { mutableStateOf(f.richterQualifikation ?: "") } + var email by remember { mutableStateOf(f.email ?: "") } + + AlertDialog( + onDismissRequest = { editOpen = false }, + confirmButton = { + TextButton(onClick = { + f.vorname = vor + f.nachname = nach + f.richterNummer = num.ifBlank { null } + f.richterQualifikation = qual.ifBlank { null } + f.email = email.ifBlank { null } + editOpen = false + }) { Text("Speichern") } + }, + dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } }, + title = { Text("Funktionär bearbeiten") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(vor, { vor = it }, label = { Text("Vorname") }, modifier = Modifier.weight(1f)) + OutlinedTextField(nach, { nach = it }, label = { Text("Nachname") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(num, { num = it }, label = { Text("Nummer") }, modifier = Modifier.weight(1f)) + OutlinedTextField(qual, { qual = it }, label = { Text("Qualifikation") }, modifier = Modifier.weight(1f)) + } + OutlinedTextField(email, { email = it }, label = { Text("E-Mail") }, modifier = Modifier.fillMaxWidth()) + } + } + ) + } + } + } +} + @Composable fun VeranstalterAuswahlV2( onBack: () -> Unit, @@ -125,62 +496,106 @@ fun VeranstalterDetailV2( Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") } } - // Profil-Bereich (Logo URL, Ansprechpartner, Kontakt, Adresse) + // Veranstalter Vorschau-Karte mit Bearbeiten-Dialog val verein = remember(veranstalterId) { StoreV2.vereine.firstOrNull { it.id == veranstalterId } } if (verein != null) { - Card { - Column(Modifier.fillMaxWidth().padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Veranstalter‑Profil", style = MaterialTheme.typography.titleMedium) - OutlinedTextField( - value = verein.logoUrl ?: "", - onValueChange = { verein.logoUrl = it.ifBlank { null } }, - label = { Text("Logo‑URL (optional)") }, - modifier = Modifier.fillMaxWidth() - ) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField( - value = verein.ort ?: "", - onValueChange = { verein.ort = it.ifBlank { null } }, - label = { Text("Ansprechpartner / Ort (optional)") }, - modifier = Modifier.weight(1f) - ) - OutlinedTextField( - value = verein.telefon ?: "", - onValueChange = { verein.telefon = it.ifBlank { null } }, - label = { Text("Telefon (optional)") }, - modifier = Modifier.weight(1f) - ) + var editOpen by remember { mutableStateOf(false) } + Card(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + // Logo/Avatar + Box( + modifier = Modifier.size(56.dp).background(Color(0xFF1F2937), shape = MaterialTheme.shapes.small), + contentAlignment = Alignment.Center + ) { + Text((verein.kurzname ?: verein.name).take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold) } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField( - value = verein.email ?: "", - onValueChange = { verein.email = it.ifBlank { null } }, - label = { Text("E‑Mail (optional)") }, - modifier = Modifier.weight(1f) - ) - OutlinedTextField( - value = verein.oepsNummer, - onValueChange = { verein.oepsNummer = it }, - label = { Text("OEPS‑Nummer") }, - modifier = Modifier.weight(1f) - ) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text(verein.name, style = MaterialTheme.typography.titleMedium) + val line2 = listOfNotNull("OEPS: ${verein.oepsNummer}", verein.ort, verein.plz, verein.strasse).filter { it.isNotBlank() }.joinToString(" · ") + if (line2.isNotBlank()) Text(line2, color = Color(0xFF6B7280)) + val line3 = listOfNotNull(verein.email, verein.telefon).filter { !it.isNullOrBlank() }.joinToString(" · ") + if (line3.isNotBlank()) Text(line3, color = Color(0xFF6B7280)) } - OutlinedTextField( - value = verein.strasse ?: "", - onValueChange = { verein.strasse = it.ifBlank { null } }, - label = { Text("Adresse / Straße (optional)") }, - modifier = Modifier.fillMaxWidth(), - minLines = 2 - ) + Button(onClick = { editOpen = true }) { Text("bearbeiten") } } } + + if (editOpen) { + // Lokale Edit-Felder + var name by remember { mutableStateOf(verein.name) } + var oeps by remember { mutableStateOf(verein.oepsNummer) } + var ort by remember { mutableStateOf(verein.ort ?: "") } + var plz by remember { mutableStateOf(verein.plz ?: "") } + var strasse by remember { mutableStateOf(verein.strasse ?: "") } + var email by remember { mutableStateOf(verein.email ?: "") } + var tel by remember { mutableStateOf(verein.telefon ?: "") } + var logo by remember { mutableStateOf(verein.logoUrl ?: "") } + + AlertDialog( + onDismissRequest = { editOpen = false }, + confirmButton = { + TextButton(onClick = { + // Speichern in Store + verein.name = name + verein.oepsNummer = oeps + verein.ort = ort.ifBlank { null } + verein.plz = plz.ifBlank { null } + verein.strasse = strasse.ifBlank { null } + verein.email = email.ifBlank { null } + verein.telefon = tel.ifBlank { null } + verein.logoUrl = logo.ifBlank { null } + editOpen = false + }) { Text("Speichern") } + }, + dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } }, + title = { Text("Veranstalter bearbeiten") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(value = oeps, onValueChange = { oeps = it }, label = { Text("OEPS-Nummer") }, modifier = Modifier.weight(1f)) + OutlinedTextField(value = logo, onValueChange = { logo = it }, label = { Text("Logo-URL") }, modifier = Modifier.weight(1f)) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(value = ort, onValueChange = { ort = it }, label = { Text("Ort") }, modifier = Modifier.weight(1f)) + OutlinedTextField(value = plz, onValueChange = { plz = it }, label = { Text("PLZ") }, modifier = Modifier.weight(1f)) + } + OutlinedTextField(value = strasse, onValueChange = { strasse = it }, label = { Text("Straße / Adresse") }, modifier = Modifier.fillMaxWidth()) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(value = email, onValueChange = { email = it }, label = { Text("E-Mail") }, modifier = Modifier.weight(1f)) + OutlinedTextField(value = tel, onValueChange = { tel = it }, label = { Text("Telefon") }, modifier = Modifier.weight(1f)) + } + } + } + ) + } } val events = StoreV2.eventsFor(veranstalterId) - if (events.isEmpty()) Text("Noch keine Veranstaltungen angelegt.", color = Color(0xFF6B7280)) + // Filter-/Suchmaske + var search by remember { mutableStateOf("") } + OutlinedTextField( + value = search, + onValueChange = { search = it }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + placeholder = { Text("Veranstaltungen suchen…") }, + modifier = Modifier.fillMaxWidth() + ) + + val filtered = remember(events, search) { + val q = search.trim() + if (q.isEmpty()) events else events.filter { + it.titel.contains(q, ignoreCase = true) || + it.status.contains(q, ignoreCase = true) || + it.datumVon.contains(q, ignoreCase = true) || + (it.datumBis?.contains(q, ignoreCase = true) == true) + } + } + if (filtered.isEmpty()) Text("Keine passenden Veranstaltungen gefunden.", color = Color(0xFF6B7280)) LazyColumn(Modifier.fillMaxSize()) { - items(events) { evt -> + items(filtered) { evt -> Card(Modifier.fillMaxWidth().padding(vertical = 6.dp)) { Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) { Column(Modifier.weight(1f)) { diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt index 56a293b9..36a8d1c2 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt @@ -417,7 +417,13 @@ fun VeranstaltungKonfigV2( Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { IconButton(onClick = { if (currentStep > 1) { - currentStep-- + // Wenn wir aus einem konkreten Veranstalter kommen (id > 0), + // gehen wir bei Zurück direkt ins Profil statt auf Schritt 1. + if (veranstalterId != 0L) { + onBack() + } else { + currentStep-- + } } else { onBack() } @@ -667,28 +673,60 @@ fun VeranstaltungKonfigV2( Spacer(Modifier.width(1.dp)) } + var showConfirm by remember { mutableStateOf(false) } + if (showConfirm) { + AlertDialog( + onDismissRequest = { showConfirm = false }, + confirmButton = { + TextButton(onClick = { + val id = System.currentTimeMillis() + val v = VeranstaltungV2( + id = id, + veranstalterId = selectedVereinId, + titel = titel.trim(), + datumVon = von.trim(), + datumBis = bis.trim().ifBlank { null }, + untertitel = untertitel.trim(), + ort = ort.trim().ifBlank { StoreV2.vereine.find { it.id == selectedVereinId }?.ort ?: "" }, + logoUrl = logoUrl.trim().ifBlank { null } + ) + sponsorenText.split(",").filter { it.isNotBlank() }.forEach { v.sponsoren.add(it.trim()) } + StoreV2.addEventFirst(selectedVereinId, v) + showConfirm = false + onSaved(id, selectedVereinId) + }) { Text("Anlegen") } + }, + dismissButton = { TextButton(onClick = { showConfirm = false }) { Text("Abbrechen") } }, + title = { Text("Veranstaltung final anlegen?") }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Bitte die Daten prüfen. Für diese Veranstaltung wird eine eigene Datenbank initialisiert.") + HorizontalDivider() + val titelText = titel.trim() + val untertitelText = untertitel.trim().ifBlank { "-" } + val vonText = von.trim() + val bisText = bis.trim() + val zeitraumText = if (bisText.isNotEmpty()) "$vonText – $bisText" else vonText + val vName = StoreV2.vereine.find { it.id == selectedVereinId }?.name ?: "#$selectedVereinId" + val spons = sponsorenText.split(',').map { it.trim() }.filter { it.isNotEmpty() } + + Text("Titel: $titelText") + Text("Untertitel: $untertitelText") + Text("Zeitraum: $zeitraumText") + Text("Veranstalter: $vName") + if (logoUrl.isNotBlank()) Text("Logo: ${logoUrl.trim()}") + if (spons.isNotEmpty()) Text("Sponsoren: ${spons.joinToString(", ")}") + } + } + ) + } + Button( onClick = { if (currentStep < 3) { currentStep++ } else { - val id = System.currentTimeMillis() - val v = VeranstaltungV2( - id = id, - veranstalterId = selectedVereinId, - titel = titel.trim(), - datumVon = von.trim(), - datumBis = bis.trim().ifBlank { null }, - untertitel = untertitel.trim(), - ort = ort.trim().ifBlank { StoreV2.vereine.find { it.id == selectedVereinId }?.ort ?: "" }, - logoUrl = logoUrl.trim().ifBlank { null } - ) - sponsorenText.split(",").filter { it.isNotBlank() }.forEach { - v.sponsoren.add(it.trim()) - } - - StoreV2.addEventFirst(selectedVereinId, v) - onSaved(id, selectedVereinId) + showConfirm = true } }, enabled = when (currentStep) {