chore: refaktoriere Veranstaltungs-UI zu Events, implementiere ZNS-Suche und verbessere Navigationslogik

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-21 13:41:06 +02:00
parent 9b4af2bb56
commit 574f8c470c
18 changed files with 673 additions and 174 deletions
@@ -38,7 +38,10 @@ fun DesktopApp() {
// DeviceInitialization-Check beim Start
LaunchedEffect(Unit) {
if (!DeviceInitializationSettingsManager.isConfigured()) {
println("[DesktopApp] Setup fehlt -> Umleitung zum DeviceInitialization")
nav.navigateToScreen(AppScreen.DeviceInitialization)
} else {
println("[DesktopApp] Setup vorhanden.")
}
}
@@ -47,22 +50,32 @@ fun DesktopApp() {
// Login-Gate: Nicht-authentifizierte Screens → Login, außer DeviceInitialization ist erlaubt
// Vision_03 Update: Wir starten mit DeviceInitialization
if (!authState.isAuthenticated && currentScreen !is AppScreen.Login && currentScreen !is AppScreen.DeviceInitialization
&& currentScreen !is AppScreen.VeranstaltungVerwaltung
&& currentScreen !is AppScreen.EventVerwaltung
&& currentScreen !is AppScreen.VeranstalterAuswahl && currentScreen !is AppScreen.VeranstalterNeu
&& currentScreen !is AppScreen.VeranstalterDetail && currentScreen !is AppScreen.VeranstaltungKonfig
&& currentScreen !is AppScreen.VeranstaltungProfil && currentScreen !is AppScreen.TurnierDetail
&& currentScreen !is AppScreen.VeranstalterDetail && currentScreen !is AppScreen.EventKonfig
&& currentScreen !is AppScreen.EventProfil && currentScreen !is AppScreen.TurnierDetail
&& currentScreen !is AppScreen.TurnierNeu
&& currentScreen !is AppScreen.ReiterVerwaltung
&& currentScreen !is AppScreen.PferdVerwaltung
&& currentScreen !is AppScreen.VereinVerwaltung
&& currentScreen !is AppScreen.ReiterVerwaltung && currentScreen !is AppScreen.Reiter
&& currentScreen !is AppScreen.PferdVerwaltung && currentScreen !is AppScreen.Pferde
&& currentScreen !is AppScreen.VereinVerwaltung && currentScreen !is AppScreen.Vereine
&& currentScreen !is AppScreen.FunktionaerVerwaltung && currentScreen !is AppScreen.FunktionaerProfil
&& currentScreen !is AppScreen.ReiterProfil
&& currentScreen !is AppScreen.PferdProfil
&& currentScreen !is AppScreen.VereinProfil
&& currentScreen !is AppScreen.StammdatenImport
&& currentScreen !is AppScreen.NennungsEingang
&& currentScreen !is AppScreen.VeranstaltungNeu
&& currentScreen !is AppScreen.EventNeu
&& currentScreen !is AppScreen.ConnectivityCheck
&& currentScreen !is AppScreen.Dashboard
) {
LaunchedEffect(Unit) {
// Standard: Start im DeviceInitialization
nav.navigateToScreen(AppScreen.DeviceInitialization)
LaunchedEffect(currentScreen) {
if (!DeviceInitializationSettingsManager.isConfigured()) {
println("[DesktopApp] Nicht authentifiziert & nicht konfiguriert -> Setup")
nav.navigateToScreen(AppScreen.DeviceInitialization)
} else {
println("[DesktopApp] Nicht authentifiziert, aber konfiguriert -> Dashboard")
nav.navigateToScreen(AppScreen.EventVerwaltung)
}
}
}
@@ -70,7 +83,7 @@ fun DesktopApp() {
is AppScreen.Login -> LoginScreen(
viewModel = loginViewModel,
onLoginSuccess = {
val returnTo = screen.returnTo ?: AppScreen.VeranstaltungVerwaltung
val returnTo = screen.returnTo ?: AppScreen.EventVerwaltung
nav.navigateToScreen(returnTo)
},
onBack = { nav.navigateBack() },
@@ -84,7 +97,7 @@ fun DesktopApp() {
onBack = { nav.navigateBack() },
onLogout = {
authTokenManager.clearToken()
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.VeranstaltungVerwaltung))
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.EventVerwaltung))
},
isAuthenticated = authState.isAuthenticated
)
@@ -45,13 +45,15 @@ fun DesktopMainLayout(
}
// Automatische Umleitung zum DeviceInitialization, wenn Setup fehlt (außer wir sind bereits dort)
LaunchedEffect(onboardingSettings) {
LaunchedEffect(currentScreen) {
if (!onboardingSettings.isConfigured && currentScreen !is AppScreen.DeviceInitialization) {
println("[DesktopNav] Setup fehlt -> Umleitung zum DeviceInitialization")
onNavigate(AppScreen.DeviceInitialization)
} else if (onboardingSettings.isConfigured && currentScreen is AppScreen.DeviceInitialization) {
println("[DesktopNav] Setup abgeschlossen -> Wechsel zum Dashboard")
onNavigate(AppScreen.VeranstaltungVerwaltung)
// Falls wir konfiguriert sind, aber im Setup-Screen landen (z.B. durch manuellen Nav-Call),
// erlauben wir den Aufenthalt dort (für Edit), aber forcieren keinen Redirect zum Dashboard hier,
// da dies der Wizard am Ende selbst macht.
println("[DesktopNav] Setup vorhanden und im Setup-Screen.")
}
}
@@ -27,7 +27,8 @@ import at.mocode.frontend.features.pferde.presentation.PferdeScreen
import at.mocode.frontend.features.pferde.presentation.PferdeViewModel
import at.mocode.frontend.features.ping.presentation.PingScreen
import at.mocode.frontend.features.ping.presentation.PingViewModel
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingWizard
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingScreen
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingViewModel
import at.mocode.frontend.features.profile.presentation.ProfileScreen
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
import at.mocode.frontend.features.reiter.presentation.ReiterScreen
@@ -70,17 +71,18 @@ fun DesktopContentArea(
val authTokenManager = org.koin.core.context.GlobalContext.get().get<AuthTokenManager>()
authTokenManager.setToken(finalSettings.sharedKey)
onSettingsChange(finalSettings)
onNavigate(AppScreen.VeranstaltungVerwaltung)
// nav.navigateToScreen(...) wird hier nicht direkt gerufen, sondern onNavigate
onNavigate(AppScreen.EventVerwaltung)
})
}
DeviceInitializationScreen(viewModel = viewModel)
}
// Haupt-Zentrale: Veranstaltung-Verwaltung
is AppScreen.VeranstaltungVerwaltung -> {
// Haupt-Zentrale: Event-Verwaltung
is AppScreen.EventVerwaltung -> {
VeranstaltungenScreen(
onVeranstaltungNeu = { onNavigate(AppScreen.VeranstaltungNeu) },
onVeranstaltungOeffnen = { vId: Long, eId: Long -> onNavigate(AppScreen.VeranstaltungProfil(vId, eId)) }
onVeranstaltungNeu = { onNavigate(AppScreen.EventNeu) },
onVeranstaltungOeffnen = { vId: Long, eId: Long -> onNavigate(AppScreen.EventProfil(vId, eId)) }
)
}
@@ -91,6 +93,15 @@ fun DesktopContentArea(
)
}
// --- Profile Onboarding ---
is AppScreen.ProfileOnboarding -> {
val viewModel = koinViewModel<ProfileOnboardingViewModel>()
ProfileOnboardingScreen(
viewModel = viewModel,
onFinish = { onNavigate(AppScreen.EventVerwaltung) }
)
}
// --- Pferde-Verwaltung & Profil ---
is AppScreen.Pferde, is AppScreen.PferdVerwaltung -> {
val viewModel = koinViewModel<PferdeViewModel>()
@@ -165,14 +176,14 @@ fun DesktopContentArea(
is AppScreen.VeranstalterProfil -> VeranstalterDetail(
veranstalterId = currentScreen.id,
onBack = onBack,
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungNeu) },
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.EventProfil(currentScreen.id, evtId)) },
onNeuVeranstaltung = { onNavigate(AppScreen.EventNeu) },
)
// Neuer Flow: Veranstalter auswählen → Veranstaltung-Wizard
// Neuer Flow: Veranstalter auswählen → Event-Wizard
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahl(
onBack = onBack,
onWeiter = { _ -> onNavigate(AppScreen.VeranstaltungNeu) },
onWeiter = { _ -> onNavigate(AppScreen.EventNeu) },
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
)
@@ -186,12 +197,12 @@ fun DesktopContentArea(
VeranstalterDetail(
veranstalterId = vId,
onBack = onBack,
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.VeranstaltungProfil(vId, evtId)) },
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(vId)) },
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.EventProfil(vId, evtId)) },
onNeuVeranstaltung = { onNavigate(AppScreen.EventKonfig(vId)) },
)
}
is AppScreen.VeranstaltungKonfig -> {
is AppScreen.EventKonfig -> {
val vId = currentScreen.veranstalterId
VeranstaltungKonfigScreen(
veranstalterId = vId,
@@ -201,12 +212,12 @@ fun DesktopContentArea(
// val allEvents = Store.allEvents()
// val newId = (allEvents.maxOfOrNull { it.id } ?: 0L) + 1L
// ...
onNavigate(AppScreen.VeranstaltungProfil(vId, 0L)) // Mock
onNavigate(AppScreen.EventProfil(vId, 0L)) // Mock
}
)
}
is AppScreen.VeranstaltungProfil -> {
is AppScreen.EventProfil -> {
VeranstaltungProfilScreen(
veranstalterId = currentScreen.veranstalterId,
veranstaltungId = currentScreen.veranstaltungId,
@@ -223,7 +234,7 @@ fun DesktopContentArea(
)
}
is AppScreen.VeranstaltungDetail -> {
is AppScreen.EventDetail -> {
val repository: at.mocode.veranstaltung.feature.domain.repository.VeranstaltungRepository = koinInject()
VeranstaltungDetailScreen(
veranstaltungId = currentScreen.id,
@@ -235,7 +246,7 @@ fun DesktopContentArea(
)
}
is AppScreen.VeranstaltungNeu -> {
is AppScreen.EventNeu -> {
val viewModel: at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardViewModel = koinViewModel()
at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardScreen(
viewModel = viewModel,
@@ -323,18 +334,13 @@ fun DesktopContentArea(
ProfileScreen(viewModel = viewModel)
}
is AppScreen.ProfileOnboarding -> {
val viewModel = koinViewModel<ProfileViewModel>()
ProfileOnboardingWizard(
viewModel = viewModel,
onFinish = { onNavigate(AppScreen.Dashboard) }
)
}
is AppScreen.Home, is AppScreen.Dashboard -> {
is AppScreen.Home, is AppScreen.Dashboard, is AppScreen.PortalDashboard,
is AppScreen.Meisterschaften, is AppScreen.Cups,
is AppScreen.CreateTournament, is AppScreen.OrganizerProfile -> {
AdminUebersichtScreen(
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
onVeranstaltungOeffnen = { id -> onNavigate(AppScreen.VeranstaltungDetail(id)) }
onVeranstaltungOeffnen = { id -> onNavigate(AppScreen.EventDetail(id)) }
)
}
@@ -35,21 +35,13 @@ fun DesktopNavRail(
icon = Icons.Default.Adjust,
label = "Logo",
selected = false,
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
onClick = { onNavigate(AppScreen.EventVerwaltung) },
enabled = isConfigured
)
Spacer(Modifier.height(Dimens.SpacingL))
// Navigations-Items
NavRailItem(
icon = Icons.Default.Dashboard,
label = "Admin",
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
enabled = isConfigured
)
NavRailItem(
icon = Icons.Default.CloudDownload,
label = "ZNS-Import",
@@ -101,7 +93,7 @@ fun DesktopNavRail(
leadingIcon = { Icon(Icons.Default.Pets, contentDescription = null) }
)
DropdownMenuItem(
text = { Text("Richter") },
text = { Text("Funktionäre") },
onClick = {
showStammdatenMenu = false
onNavigate(AppScreen.FunktionaerVerwaltung)
@@ -111,6 +103,43 @@ fun DesktopNavRail(
}
}
var showVerwaltungMenu by remember { mutableStateOf(false) }
Box {
NavRailItem(
icon = Icons.Default.Dashboard,
label = "Verwaltungen",
selected = currentScreen is AppScreen.EventVerwaltung ||
currentScreen is AppScreen.EventDetail ||
currentScreen is AppScreen.VeranstalterVerwaltung ||
currentScreen is AppScreen.VeranstalterAuswahl,
onClick = { showVerwaltungMenu = true },
enabled = isConfigured
)
DropdownMenu(
expanded = showVerwaltungMenu && isConfigured,
onDismissRequest = { showVerwaltungMenu = false },
offset = DpOffset(Dimens.NavRailWidth, 0.dp)
) {
DropdownMenuItem(
text = { Text("Veranstalter") },
onClick = {
showVerwaltungMenu = false
onNavigate(AppScreen.VeranstalterVerwaltung)
},
leadingIcon = { Icon(Icons.Default.Business, contentDescription = null) }
)
DropdownMenuItem(
text = { Text("Events") },
onClick = {
showVerwaltungMenu = false
onNavigate(AppScreen.EventVerwaltung)
},
leadingIcon = { Icon(Icons.Default.Event, contentDescription = null) }
)
}
}
NavRailItem(
icon = Icons.Default.Email,
label = "Mails",
@@ -43,7 +43,7 @@ fun DesktopTopHeader(
) {
Row(verticalAlignment = Alignment.CenterVertically) {
// Zurück-Button ausblenden auf Startseite oder im Setup
if (currentScreen !is AppScreen.DeviceInitialization && currentScreen !is AppScreen.VeranstaltungVerwaltung) {
if (currentScreen !is AppScreen.DeviceInitialization && currentScreen !is AppScreen.EventVerwaltung) {
IconButton(
onClick = {
// Verhindere Rücksprung zum Setup, wenn konfiguriert
@@ -65,7 +65,7 @@ fun DesktopTopHeader(
// Home Icon als Anker
IconButton(
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
onClick = { onNavigate(AppScreen.EventVerwaltung) },
modifier = Modifier.size(Dimens.IconSizeM),
enabled = isConfigured
) {
@@ -207,7 +207,7 @@ private fun BreadcrumbContent(
)
}
is AppScreen.VeranstaltungProfil -> {
is AppScreen.EventProfil -> {
BreadcrumbSeparator()
Text(
text = "Veranstalter-Verwaltung",
@@ -224,43 +224,43 @@ private fun BreadcrumbContent(
)
BreadcrumbSeparator()
Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}",
text = "Event #${currentScreen.veranstaltungId}",
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
)
}
is AppScreen.VeranstaltungVerwaltung -> {
is AppScreen.EventVerwaltung -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
text = "Event-Verwaltung",
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
)
}
is AppScreen.VeranstaltungDetail -> {
is AppScreen.EventDetail -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
text = "Event-Verwaltung",
style = textStyle.copy(color = clickableColor),
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
modifier = Modifier.clickable { onNavigate(AppScreen.EventVerwaltung) },
)
BreadcrumbSeparator()
Text(
text = "Veranstaltung #${currentScreen.id}",
text = "Event #${currentScreen.id}",
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
)
}
is AppScreen.VeranstaltungNeu -> {
is AppScreen.EventNeu -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltungs-Verwaltung",
text = "Event-Verwaltung",
style = textStyle.copy(color = clickableColor),
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
modifier = Modifier.clickable { onNavigate(AppScreen.EventVerwaltung) },
)
BreadcrumbSeparator()
Text(
text = "Neue Veranstaltung",
text = "Neues Event",
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
)
}
@@ -268,10 +268,10 @@ private fun BreadcrumbContent(
is AppScreen.TurnierDetail -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}",
text = "Event #${currentScreen.veranstaltungId}",
style = textStyle.copy(color = clickableColor),
modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
},
)
BreadcrumbSeparator()
@@ -284,10 +284,10 @@ private fun BreadcrumbContent(
is AppScreen.TurnierNeu -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}",
text = "Event #${currentScreen.veranstaltungId}",
style = textStyle.copy(color = clickableColor),
modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
},
)
BreadcrumbSeparator()
@@ -300,10 +300,10 @@ private fun BreadcrumbContent(
is AppScreen.Billing -> {
BreadcrumbSeparator()
Text(
text = "Veranstaltung #${currentScreen.veranstaltungId}",
text = "Event #${currentScreen.veranstaltungId}",
style = textStyle.copy(color = clickableColor),
modifier = Modifier.clickable {
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
},
)
BreadcrumbSeparator()
@@ -356,7 +356,7 @@ private fun BreadcrumbContent(
is AppScreen.FunktionaerVerwaltung -> {
BreadcrumbSeparator()
Text(
text = "Richter-Verwaltung",
text = "Funktionär-Verwaltung",
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
)
}
@@ -4,7 +4,10 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Event
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Place
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -33,7 +36,7 @@ fun VeranstaltungProfilScreen(
val turniere = TurnierStore.list(veranstaltungId)
if (veranstaltung == null) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Veranstaltung nicht gefunden") }
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Event nicht gefunden") }
return@DesktopTheme
}
@@ -65,7 +68,7 @@ fun VeranstaltungProfilScreen(
KpiCard("Ort", veranstaltung.ort, Icons.Default.Place, Modifier.weight(1f))
}
Text("Turniere in dieser Veranstaltung", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Text("Turniere in diesem Event", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
if (turniere.isEmpty()) {
Card(Modifier.fillMaxWidth()) {
Box(Modifier.padding(32.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
@@ -81,7 +84,7 @@ fun VeranstaltungProfilScreen(
}
}
// Rechte Spalte: Veranstalter Info & Aktionen
// Rechte Spalte: Veranstalter Information & Aktionen
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Card {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {