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.
This commit is contained in:
Stefan Mogeritsch 2026-04-14 15:27:04 +02:00
parent adfa97978e
commit 4de44623c2
7 changed files with 144 additions and 2 deletions

View File

@ -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())

View File

@ -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

View File

@ -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 = {})

View File

@ -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) },

View File

@ -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")
)

View File

@ -22,12 +22,12 @@ kotlin {
}
wasmJs {
binaries.library()
browser {
testTask {
enabled = false
}
}
binaries.executable()
}
sourceSets {

View File

@ -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