From 4de44623c210c4289c48a31f25f4b9d99fdacc64 Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Tue, 14 Apr 2026 15:27:04 +0200 Subject: [PATCH] feat(desktop): add `NennungsEingang` screen and integrate into navigation - Introduced `NennungsEingangScreen` for managing online nomination entries. - Added `NennungsEingang` to `AppScreen` with corresponding route configuration. - Updated `DesktopMainLayout` to include navigation and UI components for `NennungsEingang`. - Adjusted `PreviewMain` for screen integration and testing. --- .../frontend/core/navigation/AppScreen.kt | 2 + .../kotlin/at/mocode/desktop/DesktopApp.kt | 1 + .../kotlin/at/mocode/desktop/PreviewMain.kt | 4 + .../screens/layout/DesktopMainLayout.kt | 13 ++ .../desktop/v2/NennungsEingangScreen.kt | 123 ++++++++++++++++++ .../shells/meldestelle-web/build.gradle.kts | 2 +- .../kotlin/at/mocode/web/WebMainScreen.kt | 1 - 7 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/NennungsEingangScreen.kt diff --git a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt index a4674e37..d12ef38f 100644 --- a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt +++ b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt @@ -65,6 +65,7 @@ sealed class AppScreen(val route: String) { data object Meisterschaften : AppScreen("/meisterschaften") data object Cups : AppScreen("/cups") data object StammdatenImport : AppScreen("/stammdaten/import") + data object NennungsEingang : AppScreen("/nennungs-eingang") companion object { private val VERANSTALTUNG_DETAIL = Regex("/veranstaltung/(\\d+)$") @@ -106,6 +107,7 @@ sealed class AppScreen(val route: String) { "/meisterschaften" -> Meisterschaften "/cups" -> Cups "/stammdaten/import" -> StammdatenImport + "/nennungs-eingang" -> NennungsEingang else -> { BILLING.matchEntire(route)?.destructured?.let { (vId, tId) -> return Billing(vId.toLong(), tId.toLong()) diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/DesktopApp.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/DesktopApp.kt index 85166dbd..874cb933 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/DesktopApp.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/DesktopApp.kt @@ -48,6 +48,7 @@ fun DesktopApp() { && currentScreen !is AppScreen.PferdVerwaltung && currentScreen !is AppScreen.VereinVerwaltung && currentScreen !is AppScreen.StammdatenImport + && currentScreen !is AppScreen.NennungsEingang ) { LaunchedEffect(Unit) { // Standard: Start im Onboarding 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 40f69e1f..c03eff29 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 androidx.lifecycle.viewmodel.compose.viewModel +import at.mocode.desktop.v2.NennungsEingangScreen /** * Hot-Reload Preview Entry Point @@ -35,6 +37,8 @@ private fun PreviewContent() { // --- VEREIN --- + + // ── Hier den gewünschten Screen eintragen ────────────────────── // VeranstalterAuswahlScreen(onVeranstalterSelected = {}, onNeuerVeranstalter = {}) // VeranstalterNeuScreen(onBack = {}, onSave = {}) 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 07332e34..c3af2dad 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 @@ -143,6 +143,13 @@ private fun DesktopNavRail( onClick = { onNavigate(AppScreen.VereinVerwaltung) } ) + NavRailItem( + icon = Icons.Default.Email, + label = "Mails", + selected = currentScreen is AppScreen.NennungsEingang, + onClick = { onNavigate(AppScreen.NennungsEingang) } + ) + NavRailItem( icon = Icons.Default.Settings, label = "Tools", @@ -795,6 +802,12 @@ private fun DesktopContentArea( ) } + is AppScreen.NennungsEingang -> { + at.mocode.desktop.v2.NennungsEingangScreen( + onBack = onBack + ) + } + // Fallback → Root else -> AdminUebersichtScreen( onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) }, diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/NennungsEingangScreen.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/NennungsEingangScreen.kt new file mode 100644 index 00000000..e1b52700 --- /dev/null +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/NennungsEingangScreen.kt @@ -0,0 +1,123 @@ +package at.mocode.desktop.v2 + +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.Check +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +data class OnlineNennungMail( + val id: String, + val sender: String, + val empfaenger: String, + val datum: String, + val turnierNr: String, + val reiter: String, + val pferd: String, + val bewerbe: String, + val status: String = "Neu" +) + +@Composable +fun NennungsEingangScreen(onBack: () -> Unit) { + DesktopThemeV2 { + var mails by remember { mutableStateOf(getMockMails()) } + var isRefreshing by remember { mutableStateOf(false) } + + Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + // Header + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { + IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } + Icon(Icons.Default.Email, null, modifier = Modifier.size(32.dp), tint = MaterialTheme.colorScheme.primary) + Text("Nennungs-Eingang (Online-Nennen)", style = MaterialTheme.typography.headlineMedium) + Spacer(Modifier.weight(1f)) + Button( + onClick = { /* Refresh Logik */ }, + enabled = !isRefreshing + ) { + Icon(Icons.Default.Refresh, null) + Spacer(Modifier.width(8.dp)) + Text("Aktualisieren") + } + } + + Text( + "Hier werden alle eingegangenen Online-Nennungen angezeigt, die über das Web-Formular an meldestelle-[Nr]@mo-code.at gesendet wurden.", + style = MaterialTheme.typography.bodyMedium, + color = Color.Gray + ) + + // Tabelle + Card(modifier = Modifier.fillMaxWidth().weight(1f)) { + Column { + // Header Zeile + Row( + Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant).padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("Datum", Modifier.width(150.dp), fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text("Turnier", Modifier.width(100.dp), fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text("Reiter", Modifier.width(200.dp), fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text("Pferd", Modifier.width(200.dp), fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text("Bewerbe", Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 13.sp) + Text("Aktion", Modifier.width(150.dp), fontWeight = FontWeight.Bold, fontSize = 13.sp) + } + HorizontalDivider() + + if (mails.isEmpty()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Keine neuen Nennungen vorhanden.", color = Color.Gray) + } + } else { + LazyColumn(Modifier.fillMaxSize()) { + items(mails) { mail -> + Row( + Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(mail.datum, Modifier.width(150.dp), fontSize = 13.sp) + Text(mail.turnierNr, Modifier.width(100.dp), fontSize = 13.sp, fontWeight = FontWeight.SemiBold) + Text(mail.reiter, Modifier.width(200.dp), fontSize = 13.sp) + Text(mail.pferd, Modifier.width(200.dp), fontSize = 13.sp) + Text(mail.bewerbe, Modifier.weight(1f), fontSize = 13.sp) + + Row(Modifier.width(150.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + onClick = { /* Übernahme Logik */ }, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp), + modifier = Modifier.height(32.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary) + ) { + Icon(Icons.Default.Check, null, modifier = Modifier.size(14.dp)) + Spacer(Modifier.width(4.dp)) + Text("Übernehmen", fontSize = 11.sp) + } + } + } + HorizontalDivider(Modifier.padding(horizontal = 8.dp), thickness = 0.5.dp) + } + } + } + } + } + } + } +} + +private fun getMockMails() = listOf( + OnlineNennungMail("1", "max.mustermann@web.de", "meldestelle-26128@mo-code.at", "14.04.2026 14:30", "26128", "Max Mustermann", "Spirit", "1, 2, 5"), + OnlineNennungMail("2", "susi.sorglos@gmx.at", "meldestelle-26128@mo-code.at", "14.04.2026 15:12", "26128", "Susi Sorglos", "Flocke", "10, 11"), + OnlineNennungMail("3", "info@reitstall-hofer.at", "meldestelle-26129@mo-code.at", "14.04.2026 16:05", "26129", "Georg Hofer", "Black Beauty", "3, 4, 8") +) diff --git a/frontend/shells/meldestelle-web/build.gradle.kts b/frontend/shells/meldestelle-web/build.gradle.kts index f774d9e5..beb2bed3 100644 --- a/frontend/shells/meldestelle-web/build.gradle.kts +++ b/frontend/shells/meldestelle-web/build.gradle.kts @@ -22,12 +22,12 @@ kotlin { } wasmJs { - binaries.library() browser { testTask { enabled = false } } + binaries.executable() } sourceSets { diff --git a/frontend/shells/meldestelle-web/src/wasmJsMain/kotlin/at/mocode/web/WebMainScreen.kt b/frontend/shells/meldestelle-web/src/wasmJsMain/kotlin/at/mocode/web/WebMainScreen.kt index e59faf16..97b9a618 100644 --- a/frontend/shells/meldestelle-web/src/wasmJsMain/kotlin/at/mocode/web/WebMainScreen.kt +++ b/frontend/shells/meldestelle-web/src/wasmJsMain/kotlin/at/mocode/web/WebMainScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import at.mocode.frontend.core.designsystem.theme.AppColors import at.mocode.frontend.features.billing.presentation.BillingViewModel -import at.mocode.frontend.features.nennung.presentation.web.NennungPayload import at.mocode.frontend.features.nennung.presentation.web.OnlineNennungFormular import org.koin.compose.viewmodel.koinViewModel