From e89c58bd28e1dbd9caa5f12a279509eed142ca5d Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Sat, 21 Mar 2026 18:37:23 +0100 Subject: [PATCH] feat(ui): add new screens for AuthStatus, OrganizerProfile, and Dashboard - Introduced `AuthStatusScreen` to display user authentication status and navigation options. - Added `OrganizerProfileScreen` with detailed profile editing features (contacts, address, and social links). - Created `DashboardScreen` for admins and organizers with responsive layouts. - Developed reusable `TournamentCard` component for displaying tournament details. Signed-off-by: Stefan Mogeritsch --- .../src/commonMain/kotlin/MainApp.kt | 1350 +---------------- .../kotlin/components/TournamentCard.kt | 145 ++ .../kotlin/screens/AuthStatusScreen.kt | 68 + .../kotlin/screens/CreateTournamentScreen.kt | 390 +++++ .../kotlin/screens/DashboardScreen.kt | 382 +++++ .../kotlin/screens/LandingScreen.kt | 2 + .../kotlin/screens/OrganizerProfileScreen.kt | 345 +++++ 7 files changed, 1365 insertions(+), 1317 deletions(-) create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/components/TournamentCard.kt create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/AuthStatusScreen.kt create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/CreateTournamentScreen.kt create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/DashboardScreen.kt create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/OrganizerProfileScreen.kt diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt index 0710c141..3957f343 100644 --- a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt @@ -1,16 +1,11 @@ -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import at.mocode.frontend.core.auth.data.AuthTokenManager import at.mocode.frontend.core.auth.presentation.LoginScreen import at.mocode.frontend.core.auth.presentation.LoginViewModel @@ -23,31 +18,24 @@ import at.mocode.ping.feature.presentation.PingViewModel import navigation.StateNavigationPort import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel +import screens.* @Composable fun MainApp() { - // Wrap the entire app in our centralized AppTheme AppTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - // Resolve NavigationPort to observe state changes val navigationPort = koinInject() val currentScreen by navigationPort.currentScreen.collectAsState() - - // Resolve AuthTokenManager from Koin val authTokenManager = koinInject() - - // Delta-Sync blueprint: resolve the Ping feature view model via Koin. val pingViewModel: PingViewModel = koinViewModel() val loginViewModel: LoginViewModel = koinViewModel() when (val screen = currentScreen) { is AppScreen.Landing -> { if (currentPlatform() == PlatformType.DESKTOP) { - // Master Desktop: MUSS eingeloggt sein, um irgendwas zu sehen. - // Wir leiten für den POC erst mal hart auf den Login, wenn nicht eingeloggt val authState = authTokenManager.authState.collectAsState().value if (authState.isAuthenticated) { DashboardScreen( @@ -65,8 +53,8 @@ fun MainApp() { } } else { LandingScreen( - onPrimaryCta = { navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.Dashboard)) }, // Takes you to Meldestelle login - onOpenPing = { navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.OrganizerProfile)) } // Nach Login zum OrganizerProfile + onPrimaryCta = { navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.Dashboard)) }, + onOpenPing = { navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.OrganizerProfile)) } ) } } @@ -85,7 +73,7 @@ fun MainApp() { onCreateTournament = { navigationPort.navigateToScreen(AppScreen.CreateTournament) } ) - is AppScreen.Home -> DashboardScreen( // Route /home to Dashboard for now + is AppScreen.Home -> DashboardScreen( authTokenManager = authTokenManager, onLogout = { authTokenManager.clearToken() @@ -100,23 +88,23 @@ fun MainApp() { is AppScreen.CreateTournament -> CreateTournamentScreen( onBack = { navigationPort.navigateToScreen(AppScreen.Dashboard) }, - onSave = { navigationPort.navigateToScreen(AppScreen.Dashboard) } // Later we go to tournament detail + onSave = { navigationPort.navigateToScreen(AppScreen.Dashboard) } ) is AppScreen.Login -> { - // wir prüfen hier, woher wir kamen (falls wir das im State hätten) - // Im Moment: nach erfolgreichem Login zum returnTo-Screen navigieren, Default ist Dashboard LoginScreen( viewModel = loginViewModel, onLoginSuccess = { - val targetScreen = screen.returnTo ?: AppScreen.Dashboard - navigationPort.navigateToScreen(targetScreen) + val returnTo = screen.returnTo + if (returnTo != null) { + navigationPort.navigateToScreen(returnTo) + } else { + navigationPort.navigateToScreen(AppScreen.Dashboard) + } }, onBack = { if (currentPlatform() == PlatformType.DESKTOP) { - // No real back from login in desktop if forced, but let's go to Dashboard empty state - // This is a bit tricky, but for PoC we just clear - navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.Dashboard)) + // Desktop hat keine Landing Page — bleibt auf Login } else { navigationPort.navigateToScreen(AppScreen.Landing) } @@ -126,9 +114,21 @@ fun MainApp() { is AppScreen.Ping -> PingScreen( viewModel = pingViewModel, - onBack = { navigationPort.navigateToScreen(AppScreen.Profile) } // Zurück zum Profil/Übersicht + onBack = { + if (currentPlatform() == PlatformType.DESKTOP) { + navigationPort.navigateToScreen(AppScreen.Dashboard) + } else { + navigationPort.navigateToScreen(AppScreen.Landing) + } + } ) + is AppScreen.Nennung -> { + // NennungsMaske wird über das nennung-feature eingebunden (jvmMain only) + // Placeholder, bis das Feature vollständig integriert ist + NennungScreenContent() + } + is AppScreen.OrganizerProfile -> OrganizerProfileScreen( authTokenManager = authTokenManager, onLogout = { @@ -138,19 +138,12 @@ fun MainApp() { onNavigateToDashboard = { navigationPort.navigateToScreen(AppScreen.Dashboard) } ) - is AppScreen.AuthCallback -> { - // OIDC-Callback: Nach erfolgreichem OAuth-Redirect zum Dashboard navigieren - LaunchedEffect(Unit) { - navigationPort.navigateToScreen(AppScreen.Dashboard) - } + is AppScreen.AuthCallback -> { /* OIDC Callback wird vom Auth-Modul verarbeitet */ } - is AppScreen.Nennung -> NennungScreenContent() is AppScreen.Profile -> AuthStatusScreen( authTokenManager = authTokenManager, onNavigateToLogin = { - // Hier nutzen wir den dynamischen Return-Pfad: Nach dem Login soll der User - // exakt auf dieses AuthStatusScreen/Profile zurückkehren. navigationPort.navigateToScreen(AppScreen.Login(returnTo = AppScreen.Profile)) }, onNavigateToPing = { navigationPort.navigateToScreen(AppScreen.Ping) }, @@ -166,1280 +159,3 @@ fun MainApp() { } } } - -// Data class for dummy tournament -private data class TournamentData( - val id: String, - val date: String, - val title: String, - val location: String -) - -@Composable -private fun TournamentCard(data: TournamentData) { - OutlinedCard( - modifier = Modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // Left: Logo Placeholder - Surface( - modifier = Modifier.size(100.dp), - color = MaterialTheme.colorScheme.surfaceVariant, - shape = MaterialTheme.shapes.medium - ) { - Box(contentAlignment = Alignment.Center) { - Text( - "URFV\nLogo", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - - Spacer(modifier = Modifier.width(24.dp)) - - // Middle: Info - Column( - modifier = Modifier.weight(1f), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = data.title, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "${data.location} ${data.date}", - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "Turnier-Nr.:${data.id}", - style = MaterialTheme.typography.bodyMedium - ) - } - - Spacer(modifier = Modifier.width(24.dp)) - - // Right: Actions - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.width(200.dp) - ) { - OutlinedButton( - onClick = { /* TODO */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Ausschreibung") - } - OutlinedButton( - onClick = { /* TODO */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Nennen") - } - OutlinedButton( - onClick = { /* TODO */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Start- Ergebnislisten") - } - } - } - } -} - - -@Composable -private fun DashboardScreen( - authTokenManager: AuthTokenManager, - onLogout: () -> Unit, - onCreateTournament: () -> Unit, - onNennungOeffnen: () -> Unit = {}, -) { - val authState by authTokenManager.authState.collectAsState() - val scrollState = rememberScrollState() - val isDesktop = currentPlatform() == PlatformType.DESKTOP - - // Security Check für das Dashboard - if (!authState.isAuthenticated) { - // Wenn nicht eingeloggt, zeige nur eine leere Seite oder einen Hinweis an - // (Die Umleitung zum Login passiert in MainApp bzw. LaunchedEffect) - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - return - } - - val isAdmin = authTokenManager.isAdmin() - - Column( - modifier = Modifier.fillMaxSize() - ) { - // App Header (Meldestelle Toolbar) - Surface( - color = MaterialTheme.colorScheme.surface, - shadowElevation = 2.dp, - modifier = Modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = if (isDesktop) "Master-Meldestelle Steuerungszentrale" else if (isAdmin) "Admin-Dashboard (Web)" else "Veranstalter-Dashboard", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "Angemeldet als: ${authState.username ?: "Admin"}", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - TextButton(onClick = onLogout) { - Text("Abmelden") - } - } - } - } - - // Main Content Area - Column( - modifier = Modifier.fillMaxSize().padding(24.dp).verticalScroll(scrollState), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - - if (isDesktop && isAdmin) { - // DESKTOP VIEW - STEUERUNGSZENTRALE FÜR DEN ADMIN (DICH) - // Neues Turnier anlegen Button - Button( - onClick = onNennungOeffnen, - modifier = Modifier.fillMaxWidth().height(64.dp) - ) { - Text( - text = "📋 Nennungs-Maske öffnen", - style = MaterialTheme.typography.titleMedium - ) - } - OutlinedButton( - onClick = onCreateTournament, - modifier = Modifier.fillMaxWidth().height(64.dp) - ) { - Text( - text = "+ neues Turnier anlegen", - style = MaterialTheme.typography.titleMedium - ) - } - - // Meine Turniere Section - Text( - text = "Alle verwalteten Turniere", - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold - ) - - // Filters (Mockup) - Surface( - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), - shape = MaterialTheme.shapes.small, - modifier = Modifier.fillMaxWidth() - ) { - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Text("Zeitraum:", style = MaterialTheme.typography.bodyMedium) - OutlinedTextField( - value = "März", - onValueChange = {}, - modifier = Modifier.width(100.dp).height(48.dp), - singleLine = true - ) - Text("bis", style = MaterialTheme.typography.bodyMedium) - OutlinedTextField( - value = "Dezember", - onValueChange = {}, - modifier = Modifier.width(120.dp).height(48.dp), - singleLine = true - ) - OutlinedTextField( - value = "2026", - onValueChange = {}, - modifier = Modifier.width(80.dp).height(48.dp), - singleLine = true - ) - OutlinedTextField( - value = "Bundesland", - onValueChange = {}, - modifier = Modifier.width(150.dp).height(48.dp), - singleLine = true - ) - Button( - onClick = {}, - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE67E22)) - ) { // Orange color - Text("Anzeigen") - } - } - Text("Zusätzliche Filter auf Suchergebnisse:", style = MaterialTheme.typography.bodyMedium) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField( - value = "Veranstalter (Verein)", - onValueChange = {}, - modifier = Modifier.width(180.dp).height(48.dp), - singleLine = true - ) - OutlinedTextField( - value = "Ort", - onValueChange = {}, - modifier = Modifier.width(120.dp).height(48.dp), - singleLine = true - ) - OutlinedTextField( - value = "Sparte", - onValueChange = {}, - modifier = Modifier.width(120.dp).height(48.dp), - singleLine = true - ) - OutlinedTextField( - value = "Turnierart", - onValueChange = {}, - modifier = Modifier.width(120.dp).height(48.dp), - singleLine = true - ) - } - } - } - - // Desktop Tournament Card (Steuerungszentrale Ansicht) - Card( - modifier = Modifier.fillMaxWidth(), - border = androidx.compose.foundation.BorderStroke(2.dp, MaterialTheme.colorScheme.outlineVariant), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) - ) { - Row( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - // Left Side: Logo and Text - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.weight(1f) - ) { - // Logo Placeholder - Surface( - modifier = Modifier.size(120.dp), - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), - shape = MaterialTheme.shapes.small - ) { - Box(contentAlignment = Alignment.Center) { - Text("NEUMARKT\nLOGO", textAlign = TextAlign.Center, style = MaterialTheme.typography.bodySmall) - } - } - - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text("CDN-C NEU CDNP-C", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Text( - "Veranstalter: URFV Neumarkt", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary - ) - Text("NEUMARKT/M., OÖ 26. APRIL 2026", style = MaterialTheme.typography.bodyMedium) - Text("Turnier-Nr.: 26129", style = MaterialTheme.typography.bodyMedium) - } - } - - // Right Side: Toggles (Statusanzeigen für den Admin) - Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.width(300.dp)) { - var meldestelleOnline by remember { mutableStateOf(true) } - var nennsystemOnline by remember { mutableStateOf(true) } - var startlisteOnline by remember { mutableStateOf(true) } - ToggleRow("Meldestelle-Desktop online", isOnline = meldestelleOnline, isInteractive = false) - ToggleRow("Nennsystem online", isOnline = nennsystemOnline, isInteractive = true) - ToggleRow("Start- Ergebnislisten online", isOnline = startlisteOnline, isInteractive = true) - - Spacer(modifier = Modifier.height(4.dp)) - OutlinedButton( - onClick = { /* Link kopieren oder Email Dialog öffnen */ }, - modifier = Modifier.fillMaxWidth() - ) { - Text("Veranstalter-Link senden") - } - } - } - } - - } else if (isDesktop && !isAdmin) { - // DESKTOP VIEW - VERANSTALTER (Meldestelle am Platz) - Text( - "Willkommen in der Meldestellen-Software für Turnier 26129", - style = MaterialTheme.typography.headlineMedium - ) - Text("Bitte initialisieren Sie die lokale Datenbank oder importieren Sie einen Stand vom USB-Stick.") - Spacer(Modifier.height(16.dp)) - Button(onClick = onCreateTournament) { // This acts as the "Setup Wizard" - Text("Turnier initialisieren / Importieren") - } - } else if (!isDesktop && isAdmin) { - // WEB VIEW - ADMIN PORTAL (Deine Steuerungszentrale im Web) - Text( - text = "Alle verwalteten Turniere", - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold - ) - - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) - ) { - Row( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text( - "CDN-C NEU CDNP-C Neumarkt", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Text("Veranstalter: URFV Neumarkt", style = MaterialTheme.typography.bodyMedium) - Text("Nr: 26129 | 26. APRIL 2026", style = MaterialTheme.typography.bodyMedium) - } - Button(onClick = { /* TODO: Download Trigger for Master App */ }) { - Text("Master-Desktop-App herunterladen") - } - } - } - } else { - // WEB VIEW - VERANSTALTER PORTAL - // Top: Aktuelles Turnier & Download - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) - ) { - Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - "Aktuelles Turnier: CDN-C NEU CDNP-C Neumarkt", - style = MaterialTheme.typography.headlineSmall, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - Text( - "Turnier-Nr.: 26129 | 26. APRIL 2026", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - - Spacer(Modifier.height(8.dp)) - - Text( - "Bitte laden Sie die Desktop-Anwendung herunter, um die Meldestelle lokal an Ihrem Turnierplatz zu betreiben.", - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.padding(top = 8.dp) - ) { - Button(onClick = { /* TODO: Download Trigger for generic app */ }) { - Text("Meldestelle-App herunterladen (.exe)") - } - // Lizenz-Key Anzeige - Surface( - shape = MaterialTheme.shapes.small, - color = MaterialTheme.colorScheme.surface, - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outline) - ) { - Text( - "Ihr Aktivierungs-Code: X7F9-K2M4", - modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), - style = MaterialTheme.typography.labelLarge - ) - } - } - } - } - - // Middle: Historie (Meine Turniere) - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth() - ) { - Text("Turnier-Historie (Archiv)", style = MaterialTheme.typography.headlineSmall) - - // Dummy Turniere aus der Vergangenheit - val turniere = listOf( - TournamentData( - id = "25044", - date = "24. APRIL 2025", - title = "CSN-C CSNP-C Neumarkt", - location = "NEUMARKT/M., OÖ" - ), - TournamentData( - id = "24012", - date = "28. APRIL 2024", - title = "CDN-C CDNP-C Neumarkt", - location = "NEUMARKT/M., OÖ" - ) - ) - - turniere.forEach { turnier -> - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant) - ) { - Row( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(turnier.title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Text("Nr: ${turnier.id} | ${turnier.date}", style = MaterialTheme.typography.bodyMedium) - } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedButton(onClick = { /* TODO: Open PDF Archive */ }) { - Text("Ergebnislisten (PDF)") - } - OutlinedButton(onClick = { /* TODO: Open Stats */ }) { - Text("Statistiken") - } - } - } - } - } - } - } - } - } -} - -@Composable -private fun ToggleRow(label: String, isOnline: Boolean, isInteractive: Boolean = false) { - Surface( - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), - shape = MaterialTheme.shapes.small, - modifier = Modifier.fillMaxWidth().height(40.dp), - color = if (isInteractive) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant - ) { - Row( - modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text(label, style = MaterialTheme.typography.bodyMedium) - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - // Fake status circle - val statusColor = if (isOnline) Color(0xFF4CAF50) else Color(0xFF9E9E9E) - Surface( - modifier = Modifier.size(16.dp), - shape = androidx.compose.foundation.shape.CircleShape, - color = statusColor - ) {} - - // Fake switch / Status text - if (isInteractive) { - Surface( - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outline), - modifier = Modifier.width(40.dp).height(24.dp) - ) { - Box(contentAlignment = Alignment.Center) { - Text(if (isOnline) "on" else "off", style = MaterialTheme.typography.labelSmall) - } - } - } else { - Text( - if (isOnline) "Online" else "Offline", - style = MaterialTheme.typography.labelSmall, - modifier = Modifier.width(40.dp), - textAlign = TextAlign.Center - ) - } - } - } - } -} - -@Composable -fun CreateTournamentScreen( - onBack: () -> Unit, - onSave: () -> Unit -) { - // Simple state to track the current step in the wizard - var currentStep by remember { mutableStateOf(1) } - - Column(modifier = Modifier.fillMaxSize()) { - // App Header - Surface( - color = MaterialTheme.colorScheme.surface, - shadowElevation = 2.dp, - modifier = Modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) { - TextButton(onClick = onBack) { - Text("← Zurück") - } - Text( - text = "Neues Turnier anlegen (Desktop Client)", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - } - } - } - - // Stepper / Progress Bar - Surface(color = MaterialTheme.colorScheme.surfaceVariant, modifier = Modifier.fillMaxWidth()) { - Row( - modifier = Modifier.padding(16.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - StepIndicator(step = 1, title = "Transfer", isActive = currentStep == 1, isCompleted = currentStep > 1) - StepIndicator(step = 2, title = "Stammdaten", isActive = currentStep == 2, isCompleted = currentStep > 2) - StepIndicator(step = 3, title = "Konfiguration", isActive = currentStep == 3, isCompleted = currentStep > 3) - StepIndicator(step = 4, title = "Funktionäre", isActive = currentStep == 4, isCompleted = currentStep > 4) - StepIndicator(step = 5, title = "Bewerbe", isActive = currentStep == 5, isCompleted = currentStep > 5) - } - } - - // Wizard Content Area - Box(modifier = Modifier.weight(1f).padding(24.dp)) { - when (currentStep) { - 1 -> TournamentStepTransfer() - 2 -> TournamentStepStammdaten() - 3 -> TournamentStepKonfiguration() - 4 -> TournamentStepFunktionaere() - 5 -> TournamentStepBewerbe() - } - } - - // Bottom Navigation Bar - Surface(shadowElevation = 8.dp, modifier = Modifier.fillMaxWidth()) { - Row( - modifier = Modifier.padding(24.dp).fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - if (currentStep > 1) { - OutlinedButton(onClick = { currentStep-- }) { Text("Zurück") } - } else { - Spacer(modifier = Modifier.width(1.dp)) // Empty space to keep "Weiter" on the right - } - - if (currentStep < 5) { - Button(onClick = { currentStep++ }) { Text("Weiter") } - } else { - Button(onClick = onSave) { Text("Turnier speichern") } - } - } - } - } -} - -@Composable -fun StepIndicator(step: Int, title: String, isActive: Boolean, isCompleted: Boolean) { - val color = when { - isActive -> MaterialTheme.colorScheme.primary - isCompleted -> MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) - else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) - } - val fontWeight = if (isActive) FontWeight.Bold else FontWeight.Normal - - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Surface( - shape = MaterialTheme.shapes.small, - color = color, - modifier = Modifier.size(24.dp) - ) { - Box(contentAlignment = Alignment.Center) { - Text(step.toString(), color = MaterialTheme.colorScheme.onPrimary, style = MaterialTheme.typography.labelSmall) - } - } - Text(title, color = color, fontWeight = fontWeight) - } -} - -@Composable -fun TournamentStepTransfer() { - Column(verticalArrangement = Arrangement.spacedBy(24.dp), modifier = Modifier.fillMaxWidth(0.8f)) { - Text("Schritt 1: Transfer & Initialisierung", style = MaterialTheme.typography.headlineSmall) - Text( - "In diesem Schritt erschaffen wir eine separate Datenbank für dieses spezifische Turnier. " + - "Diese Datenbank kann für den komplett isolierten Offline-Betrieb (z.B. am USB-Stick) auf andere Laptops übertragen werden.", - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Turniernummer OEPS (z.B. 26128)") }, - modifier = Modifier.weight(1f) - ) - Button(onClick = { /*TODO*/ }, modifier = Modifier.align(Alignment.CenterVertically)) { - Text("Turnierdatenbank Initialisieren") - } - } - - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - - Text("Datenaustausch (OEPS / Externe Systeme)", style = MaterialTheme.typography.titleMedium) - - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - Card(modifier = Modifier.weight(1f)) { - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("ZNS / Stammdaten Import", fontWeight = FontWeight.Bold) - Text( - "Aktualisieren Sie die Reiter, Pferde und Funktionäre aus dem zentralen System.", - style = MaterialTheme.typography.bodyMedium - ) - - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Pfad zur ZNS.zip") }, - modifier = Modifier.weight(1f), - readOnly = true - ) - Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } - } - - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Pfad zur AWÖ/Zucht-Datei") }, - modifier = Modifier.weight(1f), - readOnly = true - ) - Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } - } - } - } - - Card(modifier = Modifier.weight(1f)) { - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { - Text("OEPS Export", fontWeight = FontWeight.Bold) - Text( - "Erzeugt die xxxxx.erg Datei für die offizielle Ergebnismeldung nach dem Turnier.", - style = MaterialTheme.typography.bodyMedium - ) - - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Ziel-Ordner für .erg Export") }, - modifier = Modifier.weight(1f), - readOnly = true - ) - Button(onClick = { /* Öffnet Directory Picker */ }) { Text("...") } - } - Button( - onClick = { /*TODO*/ }, - modifier = Modifier.fillMaxWidth(), - enabled = false - ) { Text("Ergebnis-Export (.erg)") } - } - } - } - - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - - Text("Offline-Sync (USB-Stick / Lokales Netzwerk)", style = MaterialTheme.typography.titleMedium) - Text( - "Übertragen Sie den kompletten Turnierstand zwischen Master-Meldestelle und Richterturm-Laptops ohne Internet.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - // Export (Speichern auf Stick) - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Ziel-Pfad (z.B. D:/Export)") }, - modifier = Modifier.weight(1f), - readOnly = true - ) - Button(onClick = { /* Öffnet Directory Picker */ }) { Text("...") } - } - OutlinedButton(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) { Text("↑ Turnier Exportieren") } - } - - // Import (Lesen vom Stick) - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = "", - onValueChange = {}, - label = { Text("Quell-Datei (z.B. D:/turnier.db)") }, - modifier = Modifier.weight(1f), - readOnly = true - ) - Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } - } - OutlinedButton(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) { Text("↓ Turnier Importieren") } - } - } - } -} - -@Composable -fun TournamentStepStammdaten() { - Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { - Text("Schritt 2: Turnier-Stammdaten", style = MaterialTheme.typography.headlineSmall) - - OutlinedTextField( - value = "CSN-C NEU Neumarkt", // Dummy pre-fill - onValueChange = {}, - label = { Text("Turniername") }, - modifier = Modifier.fillMaxWidth() - ) - - Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) { - OutlinedTextField( - value = "25.04.2026", - onValueChange = {}, - label = { Text("Datum von") }, - modifier = Modifier.weight(1f) - ) - OutlinedTextField( - value = "25.04.2026", - onValueChange = {}, - label = { Text("Datum bis") }, - modifier = Modifier.weight(1f) - ) - } - } -} - -@Composable -fun TournamentStepKonfiguration() { - Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { - Text("Schritt 3: Konfiguration", style = MaterialTheme.typography.headlineSmall) - Text("Austragungsplätze und Preisliste") - - // Placeholder for Austragungsplätze - OutlinedCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - Text("Austragungsplätze", fontWeight = FontWeight.Bold) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = 8.dp)) { - FilterChip(selected = true, onClick = {}, label = { Text("Platz 1 (Sand/Vlies 45x65m)") }) - FilterChip(selected = false, onClick = {}, label = { Text("Halle (Sand/Vlies 20x40m)") }) - FilterChip(selected = false, onClick = {}, label = { Text("+ Hinzufügen") }) - } - } - } - } -} - -@Composable -fun TournamentStepFunktionaere() { - Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { - Text("Schritt 4: Team & Funktionäre", style = MaterialTheme.typography.headlineSmall) - Text("Zuweisung von Richtern und Parcoursbauern (aus ZNS)") - - OutlinedTextField( - value = "Rudi Kreupl", - onValueChange = {}, - label = { Text("Turnierbeauftragter (Suche nach Name oder ID)") }, - modifier = Modifier.fillMaxWidth() - ) - - OutlinedTextField( - value = "Helmut Riedler", - onValueChange = {}, - label = { Text("Richter (Suche nach Name oder ID)") }, - modifier = Modifier.fillMaxWidth() - ) - - OutlinedTextField( - value = "Kurt Reitetschlägerr", - onValueChange = {}, - label = { Text("Parcoursbauchef") }, - modifier = Modifier.fillMaxWidth() - ) - } -} - -@Composable -fun TournamentStepBewerbe() { - Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize()) { - Text("Schritt 5: Bewerbe anlegen", style = MaterialTheme.typography.headlineSmall) - - Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { - // Left: List of Bewerbe - Card(modifier = Modifier.weight(1f).fillMaxHeight()) { - Column(modifier = Modifier.padding(16.dp)) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Text("Bewerbe", fontWeight = FontWeight.Bold) - TextButton(onClick = {}) { Text("+ Neu") } - } - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - Text("1: Pony Stilspringprüfung (60cm)", modifier = Modifier.padding(vertical = 4.dp)) - Text("2: Einlaufspringprüfung (60cm)", modifier = Modifier.padding(vertical = 4.dp)) - Text("3: Pony Stilspringprüfung (70cm)", modifier = Modifier.padding(vertical = 4.dp)) - Text("4: Einlaufspringprüfung (70cm)", modifier = Modifier.padding(vertical = 4.dp)) - Text("...", modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant) - } - } - - // Right: Detail Tabs for selected Bewerb - Card(modifier = Modifier.weight(2f).fillMaxHeight()) { - Column { - // Tabs - PrimaryTabRow(selectedTabIndex = 0) { - Tab(selected = true, onClick = {}, text = { Text("Bewertung") }) - Tab(selected = false, onClick = {}, text = { Text("Geldpreis") }) - Tab(selected = false, onClick = {}, text = { Text("Ort/Zeit") }) - } - - // Tab Content (Bewertung) - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - OutlinedTextField( - value = "2", - onValueChange = {}, - label = { Text("Bewerb Nr.") }, - modifier = Modifier.width(100.dp) - ) - OutlinedTextField( - value = "Einlaufspringprüfung", - onValueChange = {}, - label = { Text("Bezeichnung") }, - modifier = Modifier.weight(1f) - ) - } - - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - OutlinedTextField( - value = "60cm", - onValueChange = {}, - label = { Text("Klasse / Höhe") }, - modifier = Modifier.weight(1f) - ) - OutlinedTextField( - value = "§ 218", - onValueChange = {}, - label = { Text("Richtverfahren") }, - modifier = Modifier.weight(1f) - ) - } - - Text("Abteilungen", fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 8.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox(checked = true, onCheckedChange = {}) - Text("1. Abt: lizenzfrei") - } - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox(checked = true, onCheckedChange = {}) - Text("2. Abt: mit Lizenz") - } - } - } - } - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun OrganizerProfileScreen( - authTokenManager: AuthTokenManager, - onLogout: () -> Unit, - onNavigateToDashboard: () -> Unit -) { - val authState by authTokenManager.authState.collectAsState() - val scrollState = rememberScrollState() - - // Formular-Felder - var vereinsname by remember { mutableStateOf("URFV Neumarkt") } - var vereinskuerzel by remember { mutableStateOf("URFV") } - var adresse by remember { mutableStateOf("") } - var plz by remember { mutableStateOf("") } - var ort by remember { mutableStateOf("") } - var land by remember { mutableStateOf("Österreich") } - var mapsLink by remember { mutableStateOf("") } - - // Ansprechpersonen - var kontakt1Name by remember { mutableStateOf("") } - var kontakt1Email by remember { mutableStateOf("") } - var kontakt1Telefon by remember { mutableStateOf("") } - var kontakt2Name by remember { mutableStateOf("") } - var kontakt2Email by remember { mutableStateOf("") } - var kontakt2Telefon by remember { mutableStateOf("") } - - // Social / Links - var webseite by remember { mutableStateOf("") } - var facebook by remember { mutableStateOf("") } - var instagram by remember { mutableStateOf("") } - var youtube by remember { mutableStateOf("") } - - // Weitere Infos - var vereinsbeschreibung by remember { mutableStateOf("") } - var bankverbindung by remember { mutableStateOf("") } - var uid by remember { mutableStateOf("") } - - var saveSuccess by remember { mutableStateOf(false) } - - Scaffold( - topBar = { - TopAppBar( - title = { Text("Veranstalter Profil") }, - navigationIcon = { - IconButton(onClick = onNavigateToDashboard) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") - } - }, - actions = { - Text( - text = authState.username ?: "", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(end = 8.dp) - ) - TextButton(onClick = onLogout) { Text("Abmelden") } - } - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .verticalScroll(scrollState) - .padding(24.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - - // --- Logo & Vereinsname --- - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text("Verein / Veranstalter", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - - // Logo Placeholder - Surface( - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.medium, - color = MaterialTheme.colorScheme.surfaceVariant, - border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant) - ) { - Column( - modifier = Modifier.padding(24.dp).fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text("🏆", style = MaterialTheme.typography.displayMedium) - Text("Vereins-/Veranstaltungslogo", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) - OutlinedButton(onClick = { /* TODO: File Picker */ }) { - Text("Logo hochladen") - } - } - } - - OutlinedTextField( - value = vereinsname, - onValueChange = { vereinsname = it }, - label = { Text("Vereinsname / Veranstalter") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = vereinskuerzel, - onValueChange = { vereinskuerzel = it }, - label = { Text("Kürzel") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = vereinsbeschreibung, - onValueChange = { vereinsbeschreibung = it }, - label = { Text("Kurzbeschreibung / Über uns") }, - minLines = 3, - maxLines = 6, - modifier = Modifier.fillMaxWidth() - ) - } - } - - // --- Adresse --- - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text("Adresse", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - - OutlinedTextField( - value = adresse, - onValueChange = { adresse = it }, - label = { Text("Straße & Hausnummer") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - OutlinedTextField( - value = plz, - onValueChange = { plz = it }, - label = { Text("PLZ") }, - singleLine = true, - modifier = Modifier.width(100.dp) - ) - OutlinedTextField( - value = ort, - onValueChange = { ort = it }, - label = { Text("Ort") }, - singleLine = true, - modifier = Modifier.weight(1f) - ) - } - OutlinedTextField( - value = land, - onValueChange = { land = it }, - label = { Text("Land") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = mapsLink, - onValueChange = { mapsLink = it }, - label = { Text("Google Maps / OpenStreetMap Link") }, - placeholder = { Text("https://maps.google.com/...") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - } - } - - // --- Ansprechpersonen --- - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text("Ansprechpersonen", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - - Text("Hauptkontakt", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) - OutlinedTextField( - value = kontakt1Name, - onValueChange = { kontakt1Name = it }, - label = { Text("Name") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = kontakt1Email, - onValueChange = { kontakt1Email = it }, - label = { Text("E-Mail") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = kontakt1Telefon, - onValueChange = { kontakt1Telefon = it }, - label = { Text("Telefon / Mobil") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - - HorizontalDivider() - - Text("Weiterer Kontakt (optional)", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.secondary) - OutlinedTextField( - value = kontakt2Name, - onValueChange = { kontakt2Name = it }, - label = { Text("Name") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = kontakt2Email, - onValueChange = { kontakt2Email = it }, - label = { Text("E-Mail") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = kontakt2Telefon, - onValueChange = { kontakt2Telefon = it }, - label = { Text("Telefon / Mobil") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - } - } - - // --- Social Media & Links --- - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text("Links & Social Media", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - - OutlinedTextField( - value = webseite, - onValueChange = { webseite = it }, - label = { Text("Webseite") }, - placeholder = { Text("https://www.meinverein.at") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = facebook, - onValueChange = { facebook = it }, - label = { Text("Facebook") }, - placeholder = { Text("https://facebook.com/...") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = instagram, - onValueChange = { instagram = it }, - label = { Text("Instagram") }, - placeholder = { Text("https://instagram.com/...") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = youtube, - onValueChange = { youtube = it }, - label = { Text("YouTube") }, - placeholder = { Text("https://youtube.com/...") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - } - } - - // --- Weitere Vereinsdaten --- - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text("Weitere Informationen", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - - OutlinedTextField( - value = bankverbindung, - onValueChange = { bankverbindung = it }, - label = { Text("IBAN / Bankverbindung") }, - placeholder = { Text("AT12 3456 7890 1234 5678") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = uid, - onValueChange = { uid = it }, - label = { Text("UID-Nummer / ZVR-Zahl") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) - } - } - - // --- Speichern --- - if (saveSuccess) { - Card( - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer), - modifier = Modifier.fillMaxWidth() - ) { - Text( - "✓ Profil erfolgreich gespeichert!", - modifier = Modifier.padding(16.dp), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge - ) - } - } - - Button( - onClick = { - // TODO: Backend-Anbindung (PUT /api/organizer/profile) - saveSuccess = true - }, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) { - Text("Profil speichern", style = MaterialTheme.typography.titleMedium) - } - - OutlinedButton( - onClick = onNavigateToDashboard, - modifier = Modifier.fillMaxWidth() - ) { - Text("Zum Dashboard") - } - - Spacer(modifier = Modifier.height(24.dp)) - } - } -} - -@Composable -private fun AuthStatusScreen( - authTokenManager: AuthTokenManager, - onNavigateToLogin: () -> Unit, - onNavigateToPing: () -> Unit, - onBackToHome: () -> Unit -) { - val authState by authTokenManager.authState.collectAsState() - Column( - modifier = Modifier - .fillMaxSize() - .padding(24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text("Ping-Service / System Status", style = MaterialTheme.typography.headlineMedium) - - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - if (authState.isAuthenticated) { - Text( - "Du bist angemeldet als: ${authState.username ?: authState.userId ?: "unbekannt"}", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary - ) - Spacer(Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - Button(onClick = onNavigateToPing) { - Text("Ping-Service Tests durchführen") - } - OutlinedButton(onClick = { - authTokenManager.clearToken() - }) { Text("Abmelden") } - } - - } else { - Text( - "Du bist abgemeldet.", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.error - ) - Spacer(Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - Button(onClick = onNavigateToLogin) { - Text("Login") - } - OutlinedButton(onClick = onNavigateToPing) { - Text("Ping-Service (eingeschränkt testen)") - } - } - } - - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - OutlinedButton(onClick = onBackToHome) { Text("Zurück zur Startseite") } - } - } - } -} diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/components/TournamentCard.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/components/TournamentCard.kt new file mode 100644 index 00000000..59f8d345 --- /dev/null +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/components/TournamentCard.kt @@ -0,0 +1,145 @@ +package components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +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.text.style.TextAlign +import androidx.compose.ui.unit.dp + +// Dummy-Datenklasse für Turnier-Einträge (wird später durch echtes Domain-Model ersetzt) +data class TournamentData( + val id: String, + val date: String, + val title: String, + val location: String +) + +@Composable +fun TournamentCard(data: TournamentData) { + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Left: Logo Placeholder + Surface( + modifier = Modifier.size(100.dp), + color = MaterialTheme.colorScheme.surfaceVariant, + shape = MaterialTheme.shapes.medium + ) { + Box(contentAlignment = Alignment.Center) { + Text( + "URFV\nLogo", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + Spacer(modifier = Modifier.width(24.dp)) + + // Middle: Info + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = data.title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "${data.location} ${data.date}", + style = MaterialTheme.typography.bodyMedium + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Turnier-Nr.:${data.id}", + style = MaterialTheme.typography.bodyMedium + ) + } + + Spacer(modifier = Modifier.width(24.dp)) + + // Right: Actions + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.width(200.dp) + ) { + OutlinedButton( + onClick = { /* TODO */ }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Ausschreibung") + } + OutlinedButton( + onClick = { /* TODO */ }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Nennen") + } + OutlinedButton( + onClick = { /* TODO */ }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Start- Ergebnislisten") + } + } + } + } +} + +@Composable +fun ToggleRow(label: String, isOnline: Boolean, isInteractive: Boolean = false) { + Surface( + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth().height(40.dp), + color = if (isInteractive) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant + ) { + Row( + modifier = Modifier.fillMaxSize().padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(label, style = MaterialTheme.typography.bodyMedium) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + val statusColor = if (isOnline) Color(0xFF4CAF50) else Color(0xFF9E9E9E) + Surface( + modifier = Modifier.size(16.dp), + shape = CircleShape, + color = statusColor + ) {} + + if (isInteractive) { + Surface( + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), + modifier = Modifier.width(40.dp).height(24.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text(if (isOnline) "on" else "off", style = MaterialTheme.typography.labelSmall) + } + } + } else { + Text( + if (isOnline) "Online" else "Offline", + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.width(40.dp), + textAlign = TextAlign.Center + ) + } + } + } + } +} diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/AuthStatusScreen.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/AuthStatusScreen.kt new file mode 100644 index 00000000..7ff8604c --- /dev/null +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/AuthStatusScreen.kt @@ -0,0 +1,68 @@ +package screens + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import at.mocode.frontend.core.auth.data.AuthTokenManager + +@Composable +fun AuthStatusScreen( + authTokenManager: AuthTokenManager, + onNavigateToLogin: () -> Unit, + onNavigateToPing: () -> Unit, + onBackToHome: () -> Unit +) { + val authState by authTokenManager.authState.collectAsState() + Column( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text("Ping-Service / System Status", style = MaterialTheme.typography.headlineMedium) + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + if (authState.isAuthenticated) { + Text( + "Du bist angemeldet als: ${authState.username ?: authState.userId ?: "unbekannt"}", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button(onClick = onNavigateToPing) { + Text("Ping-Service Tests durchführen") + } + OutlinedButton(onClick = { + authTokenManager.clearToken() + }) { Text("Abmelden") } + } + + } else { + Text( + "Du bist abgemeldet.", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.error + ) + Spacer(Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Button(onClick = onNavigateToLogin) { + Text("Login") + } + OutlinedButton(onClick = onNavigateToPing) { + Text("Ping-Service (eingeschränkt testen)") + } + } + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + OutlinedButton(onClick = onBackToHome) { Text("Zurück zur Startseite") } + } + } + } +} diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/CreateTournamentScreen.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/CreateTournamentScreen.kt new file mode 100644 index 00000000..6c5f7efc --- /dev/null +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/CreateTournamentScreen.kt @@ -0,0 +1,390 @@ +package screens + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@Composable +fun CreateTournamentScreen( + onBack: () -> Unit, + onSave: () -> Unit +) { + var currentStep by remember { mutableStateOf(1) } + + Column(modifier = Modifier.fillMaxSize()) { + // App Header + Surface( + color = MaterialTheme.colorScheme.surface, + shadowElevation = 2.dp, + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp)) { + TextButton(onClick = onBack) { + Text("← Zurück") + } + Text( + text = "Neues Turnier anlegen (Desktop Client)", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + } + } + } + + // Stepper / Progress Bar + Surface(color = MaterialTheme.colorScheme.surfaceVariant, modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + StepIndicator(step = 1, title = "Transfer", isActive = currentStep == 1, isCompleted = currentStep > 1) + StepIndicator(step = 2, title = "Stammdaten", isActive = currentStep == 2, isCompleted = currentStep > 2) + StepIndicator(step = 3, title = "Konfiguration", isActive = currentStep == 3, isCompleted = currentStep > 3) + StepIndicator(step = 4, title = "Funktionäre", isActive = currentStep == 4, isCompleted = currentStep > 4) + StepIndicator(step = 5, title = "Bewerbe", isActive = currentStep == 5, isCompleted = currentStep > 5) + } + } + + // Wizard Content Area + Box(modifier = Modifier.weight(1f).padding(24.dp)) { + when (currentStep) { + 1 -> TournamentStepTransfer() + 2 -> TournamentStepStammdaten() + 3 -> TournamentStepKonfiguration() + 4 -> TournamentStepFunktionaere() + 5 -> TournamentStepBewerbe() + } + } + + // Bottom Navigation Bar + Surface(shadowElevation = 8.dp, modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier.padding(24.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (currentStep > 1) { + OutlinedButton(onClick = { currentStep-- }) { Text("Zurück") } + } else { + Spacer(modifier = Modifier.width(1.dp)) + } + + if (currentStep < 5) { + Button(onClick = { currentStep++ }) { Text("Weiter") } + } else { + Button(onClick = onSave) { Text("Turnier speichern") } + } + } + } + } +} + +@Composable +fun StepIndicator(step: Int, title: String, isActive: Boolean, isCompleted: Boolean) { + val color = when { + isActive -> MaterialTheme.colorScheme.primary + isCompleted -> MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) + else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) + } + val fontWeight = if (isActive) FontWeight.Bold else FontWeight.Normal + + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Surface( + shape = MaterialTheme.shapes.small, + color = color, + modifier = Modifier.size(24.dp) + ) { + Box(contentAlignment = Alignment.Center) { + Text(step.toString(), color = MaterialTheme.colorScheme.onPrimary, style = MaterialTheme.typography.labelSmall) + } + } + Text(title, color = color, fontWeight = fontWeight) + } +} + +@Composable +fun TournamentStepTransfer() { + Column(verticalArrangement = Arrangement.spacedBy(24.dp), modifier = Modifier.fillMaxWidth(0.8f)) { + Text("Schritt 1: Transfer & Initialisierung", style = MaterialTheme.typography.headlineSmall) + Text( + "In diesem Schritt erschaffen wir eine separate Datenbank für dieses spezifische Turnier. " + + "Diese Datenbank kann für den komplett isolierten Offline-Betrieb (z.B. am USB-Stick) auf andere Laptops übertragen werden.", + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Turniernummer OEPS (z.B. 26128)") }, + modifier = Modifier.weight(1f) + ) + Button(onClick = { /*TODO*/ }, modifier = Modifier.align(Alignment.CenterVertically)) { + Text("Turnierdatenbank Initialisieren") + } + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + Text("Datenaustausch (OEPS / Externe Systeme)", style = MaterialTheme.typography.titleMedium) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Card(modifier = Modifier.weight(1f)) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("ZNS / Stammdaten Import", fontWeight = FontWeight.Bold) + Text( + "Aktualisieren Sie die Reiter, Pferde und Funktionäre aus dem zentralen System.", + style = MaterialTheme.typography.bodyMedium + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Pfad zur ZNS.zip") }, + modifier = Modifier.weight(1f), + readOnly = true + ) + Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Pfad zur AWÖ/Zucht-Datei") }, + modifier = Modifier.weight(1f), + readOnly = true + ) + Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } + } + } + } + + Card(modifier = Modifier.weight(1f)) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("OEPS Export", fontWeight = FontWeight.Bold) + Text( + "Erzeugt die xxxxx.erg Datei für die offizielle Ergebnismeldung nach dem Turnier.", + style = MaterialTheme.typography.bodyMedium + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Ziel-Ordner für .erg Export") }, + modifier = Modifier.weight(1f), + readOnly = true + ) + Button(onClick = { /* Öffnet Directory Picker */ }) { Text("...") } + } + Button( + onClick = { /*TODO*/ }, + modifier = Modifier.fillMaxWidth(), + enabled = false + ) { Text("Ergebnis-Export (.erg)") } + } + } + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + Text("Offline-Sync (USB-Stick / Lokales Netzwerk)", style = MaterialTheme.typography.titleMedium) + Text( + "Übertragen Sie den kompletten Turnierstand zwischen Master-Meldestelle und Richterturm-Laptops ohne Internet.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Ziel-Pfad (z.B. D:/Export)") }, + modifier = Modifier.weight(1f), + readOnly = true + ) + Button(onClick = { /* Öffnet Directory Picker */ }) { Text("...") } + } + OutlinedButton(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) { Text("↑ Turnier Exportieren") } + } + + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Quell-Datei (z.B. D:/turnier.db)") }, + modifier = Modifier.weight(1f), + readOnly = true + ) + Button(onClick = { /* Öffnet File Picker */ }) { Text("...") } + } + OutlinedButton(onClick = { /*TODO*/ }, modifier = Modifier.fillMaxWidth()) { Text("↓ Turnier Importieren") } + } + } + } +} + +@Composable +fun TournamentStepStammdaten() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 2: Turnier-Stammdaten", style = MaterialTheme.typography.headlineSmall) + + OutlinedTextField( + value = "CSN-C NEU Neumarkt", + onValueChange = {}, + label = { Text("Turniername") }, + modifier = Modifier.fillMaxWidth() + ) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) { + OutlinedTextField( + value = "25.04.2026", + onValueChange = {}, + label = { Text("Datum von") }, + modifier = Modifier.weight(1f) + ) + OutlinedTextField( + value = "25.04.2026", + onValueChange = {}, + label = { Text("Datum bis") }, + modifier = Modifier.weight(1f) + ) + } + } +} + +@Composable +fun TournamentStepKonfiguration() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 3: Konfiguration", style = MaterialTheme.typography.headlineSmall) + Text("Austragungsplätze und Preisliste") + + OutlinedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Austragungsplätze", fontWeight = FontWeight.Bold) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = 8.dp)) { + FilterChip(selected = true, onClick = {}, label = { Text("Platz 1 (Sand/Vlies 45x65m)") }) + FilterChip(selected = false, onClick = {}, label = { Text("Halle (Sand/Vlies 20x40m)") }) + FilterChip(selected = false, onClick = {}, label = { Text("+ Hinzufügen") }) + } + } + } + } +} + +@Composable +fun TournamentStepFunktionaere() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 4: Team & Funktionäre", style = MaterialTheme.typography.headlineSmall) + Text("Zuweisung von Richtern und Parcoursbauern (aus ZNS)") + + OutlinedTextField( + value = "Rudi Kreupl", + onValueChange = {}, + label = { Text("Turnierbeauftragter (Suche nach Name oder ID)") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = "Helmut Riedler", + onValueChange = {}, + label = { Text("Richter (Suche nach Name oder ID)") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = "Kurt Reitetschlägerr", + onValueChange = {}, + label = { Text("Parcoursbauchef") }, + modifier = Modifier.fillMaxWidth() + ) + } +} + +@Composable +fun TournamentStepBewerbe() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize()) { + Text("Schritt 5: Bewerbe anlegen", style = MaterialTheme.typography.headlineSmall) + + Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { + // Left: List of Bewerbe + Card(modifier = Modifier.weight(1f).fillMaxHeight()) { + Column(modifier = Modifier.padding(16.dp)) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text("Bewerbe", fontWeight = FontWeight.Bold) + TextButton(onClick = {}) { Text("+ Neu") } + } + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text("1: Pony Stilspringprüfung (60cm)", modifier = Modifier.padding(vertical = 4.dp)) + Text("2: Einlaufspringprüfung (60cm)", modifier = Modifier.padding(vertical = 4.dp)) + Text("3: Pony Stilspringprüfung (70cm)", modifier = Modifier.padding(vertical = 4.dp)) + Text("4: Einlaufspringprüfung (70cm)", modifier = Modifier.padding(vertical = 4.dp)) + Text("...", modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + + // Right: Detail Tabs for selected Bewerb + Card(modifier = Modifier.weight(2f).fillMaxHeight()) { + Column { + PrimaryTabRow(selectedTabIndex = 0) { + Tab(selected = true, onClick = {}, text = { Text("Bewertung") }) + Tab(selected = false, onClick = {}, text = { Text("Geldpreis") }) + Tab(selected = false, onClick = {}, text = { Text("Ort/Zeit") }) + } + + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + OutlinedTextField( + value = "2", + onValueChange = {}, + label = { Text("Bewerb Nr.") }, + modifier = Modifier.width(100.dp) + ) + OutlinedTextField( + value = "Einlaufspringprüfung", + onValueChange = {}, + label = { Text("Bezeichnung") }, + modifier = Modifier.weight(1f) + ) + } + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + OutlinedTextField( + value = "60cm", + onValueChange = {}, + label = { Text("Klasse / Höhe") }, + modifier = Modifier.weight(1f) + ) + OutlinedTextField( + value = "§ 218", + onValueChange = {}, + label = { Text("Richtverfahren") }, + modifier = Modifier.weight(1f) + ) + } + + Text("Abteilungen", fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = true, onCheckedChange = {}) + Text("1. Abt: lizenzfrei") + } + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = true, onCheckedChange = {}) + Text("2. Abt: mit Lizenz") + } + } + } + } + } + } +} diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/DashboardScreen.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/DashboardScreen.kt new file mode 100644 index 00000000..9de57026 --- /dev/null +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/DashboardScreen.kt @@ -0,0 +1,382 @@ +package screens + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import at.mocode.frontend.core.auth.data.AuthTokenManager +import at.mocode.frontend.core.domain.PlatformType +import at.mocode.frontend.core.domain.currentPlatform +import components.ToggleRow +import components.TournamentData + +@Composable +fun DashboardScreen( + authTokenManager: AuthTokenManager, + onLogout: () -> Unit, + onCreateTournament: () -> Unit, + onNennungOeffnen: () -> Unit = {}, +) { + val authState by authTokenManager.authState.collectAsState() + val scrollState = rememberScrollState() + val isDesktop = currentPlatform() == PlatformType.DESKTOP + + // Security Check für das Dashboard + if (!authState.isAuthenticated) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + return + } + + val isAdmin = authTokenManager.isAdmin() + + Column( + modifier = Modifier.fillMaxSize() + ) { + // App Header (Meldestelle Toolbar) + Surface( + color = MaterialTheme.colorScheme.surface, + shadowElevation = 2.dp, + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = if (isDesktop) "Master-Meldestelle Steuerungszentrale" else if (isAdmin) "Admin-Dashboard (Web)" else "Veranstalter-Dashboard", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Angemeldet als: ${authState.username ?: "Admin"}", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + TextButton(onClick = onLogout) { + Text("Abmelden") + } + } + } + } + + // Main Content Area + Column( + modifier = Modifier.fillMaxSize().padding(24.dp).verticalScroll(scrollState), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + + if (isDesktop && isAdmin) { + // DESKTOP VIEW - STEUERUNGSZENTRALE FÜR DEN ADMIN + Button( + onClick = onNennungOeffnen, + modifier = Modifier.fillMaxWidth().height(64.dp) + ) { + Text( + text = "📋 Nennungs-Maske öffnen", + style = MaterialTheme.typography.titleMedium + ) + } + OutlinedButton( + onClick = onCreateTournament, + modifier = Modifier.fillMaxWidth().height(64.dp) + ) { + Text( + text = "+ neues Turnier anlegen", + style = MaterialTheme.typography.titleMedium + ) + } + + Text( + text = "Alle verwalteten Turniere", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) + + // Filters (Mockup) + Surface( + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Zeitraum:", style = MaterialTheme.typography.bodyMedium) + OutlinedTextField( + value = "März", + onValueChange = {}, + modifier = Modifier.width(100.dp).height(48.dp), + singleLine = true + ) + Text("bis", style = MaterialTheme.typography.bodyMedium) + OutlinedTextField( + value = "Dezember", + onValueChange = {}, + modifier = Modifier.width(120.dp).height(48.dp), + singleLine = true + ) + OutlinedTextField( + value = "2026", + onValueChange = {}, + modifier = Modifier.width(80.dp).height(48.dp), + singleLine = true + ) + OutlinedTextField( + value = "Bundesland", + onValueChange = {}, + modifier = Modifier.width(150.dp).height(48.dp), + singleLine = true + ) + Button( + onClick = {}, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE67E22)) + ) { + Text("Anzeigen") + } + } + Text("Zusätzliche Filter auf Suchergebnisse:", style = MaterialTheme.typography.bodyMedium) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField( + value = "Veranstalter (Verein)", + onValueChange = {}, + modifier = Modifier.width(180.dp).height(48.dp), + singleLine = true + ) + OutlinedTextField( + value = "Ort", + onValueChange = {}, + modifier = Modifier.width(120.dp).height(48.dp), + singleLine = true + ) + OutlinedTextField( + value = "Sparte", + onValueChange = {}, + modifier = Modifier.width(120.dp).height(48.dp), + singleLine = true + ) + OutlinedTextField( + value = "Turnierart", + onValueChange = {}, + modifier = Modifier.width(120.dp).height(48.dp), + singleLine = true + ) + } + } + } + + // Desktop Tournament Card (Steuerungszentrale Ansicht) + Card( + modifier = Modifier.fillMaxWidth(), + border = BorderStroke(2.dp, MaterialTheme.colorScheme.outlineVariant), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Row( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.weight(1f) + ) { + Surface( + modifier = Modifier.size(120.dp), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant), + shape = MaterialTheme.shapes.small + ) { + Box(contentAlignment = Alignment.Center) { + Text("NEUMARKT\nLOGO", textAlign = TextAlign.Center, style = MaterialTheme.typography.bodySmall) + } + } + + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text("CDN-C NEU CDNP-C", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text( + "Veranstalter: URFV Neumarkt", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary + ) + Text("NEUMARKT/M., OÖ 26. APRIL 2026", style = MaterialTheme.typography.bodyMedium) + Text("Turnier-Nr.: 26129", style = MaterialTheme.typography.bodyMedium) + } + } + + Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.width(300.dp)) { + var meldestelleOnline by remember { mutableStateOf(true) } + var nennsystemOnline by remember { mutableStateOf(true) } + var startlisteOnline by remember { mutableStateOf(true) } + ToggleRow("Meldestelle-Desktop online", isOnline = meldestelleOnline, isInteractive = false) + ToggleRow("Nennsystem online", isOnline = nennsystemOnline, isInteractive = true) + ToggleRow("Start- Ergebnislisten online", isOnline = startlisteOnline, isInteractive = true) + + Spacer(modifier = Modifier.height(4.dp)) + OutlinedButton( + onClick = { /* Link kopieren oder Email Dialog öffnen */ }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Veranstalter-Link senden") + } + } + } + } + + } else if (isDesktop && !isAdmin) { + // DESKTOP VIEW - VERANSTALTER (Meldestelle am Platz) + Text( + "Willkommen in der Meldestellen-Software für Turnier 26129", + style = MaterialTheme.typography.headlineMedium + ) + Text("Bitte initialisieren Sie die lokale Datenbank oder importieren Sie einen Stand vom USB-Stick.") + Spacer(Modifier.height(16.dp)) + Button(onClick = onCreateTournament) { + Text("Turnier initialisieren / Importieren") + } + } else if (!isDesktop && isAdmin) { + // WEB VIEW - ADMIN PORTAL + Text( + text = "Alle verwalteten Turniere", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) + ) { + Row( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + "CDN-C NEU CDNP-C Neumarkt", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text("Veranstalter: URFV Neumarkt", style = MaterialTheme.typography.bodyMedium) + Text("Nr: 26129 | 26. APRIL 2026", style = MaterialTheme.typography.bodyMedium) + } + Button(onClick = { /* TODO: Download Trigger for Master App */ }) { + Text("Master-Desktop-App herunterladen") + } + } + } + } else { + // WEB VIEW - VERANSTALTER PORTAL + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) + ) { + Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + "Aktuelles Turnier: CDN-C NEU CDNP-C Neumarkt", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + Text( + "Turnier-Nr.: 26129 | 26. APRIL 2026", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + + Spacer(Modifier.height(8.dp)) + + Text( + "Bitte laden Sie die Desktop-Anwendung herunter, um die Meldestelle lokal an Ihrem Turnierplatz zu betreiben.", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.padding(top = 8.dp) + ) { + Button(onClick = { /* TODO: Download Trigger for generic app */ }) { + Text("Meldestelle-App herunterladen (.exe)") + } + Surface( + shape = MaterialTheme.shapes.small, + color = MaterialTheme.colorScheme.surface, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline) + ) { + Text( + "Ihr Aktivierungs-Code: X7F9-K2M4", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + style = MaterialTheme.typography.labelLarge + ) + } + } + } + } + + // Turnier-Historie + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth() + ) { + Text("Turnier-Historie (Archiv)", style = MaterialTheme.typography.headlineSmall) + + val turniere = listOf( + TournamentData( + id = "25044", + date = "24. APRIL 2025", + title = "CSN-C CSNP-C Neumarkt", + location = "NEUMARKT/M., OÖ" + ), + TournamentData( + id = "24012", + date = "28. APRIL 2024", + title = "CDN-C CDNP-C Neumarkt", + location = "NEUMARKT/M., OÖ" + ) + ) + + turniere.forEach { turnier -> + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant) + ) { + Row( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text(turnier.title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text("Nr: ${turnier.id} | ${turnier.date}", style = MaterialTheme.typography.bodyMedium) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedButton(onClick = { /* TODO: Open PDF Archive */ }) { + Text("Ergebnislisten (PDF)") + } + OutlinedButton(onClick = { /* TODO: Open Stats */ }) { + Text("Statistiken") + } + } + } + } + } + } + } + } + } +} diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/LandingScreen.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/LandingScreen.kt index 4353938a..73b24a2b 100644 --- a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/LandingScreen.kt +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/LandingScreen.kt @@ -1,3 +1,5 @@ +package screens + import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/OrganizerProfileScreen.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/OrganizerProfileScreen.kt new file mode 100644 index 00000000..c3d2aa48 --- /dev/null +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/screens/OrganizerProfileScreen.kt @@ -0,0 +1,345 @@ +package screens + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import at.mocode.frontend.core.auth.data.AuthTokenManager + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OrganizerProfileScreen( + authTokenManager: AuthTokenManager, + onLogout: () -> Unit, + onNavigateToDashboard: () -> Unit +) { + val authState by authTokenManager.authState.collectAsState() + val scrollState = rememberScrollState() + + // Formular-Felder + var vereinsname by remember { mutableStateOf("URFV Neumarkt") } + var vereinskuerzel by remember { mutableStateOf("URFV") } + var adresse by remember { mutableStateOf("") } + var plz by remember { mutableStateOf("") } + var ort by remember { mutableStateOf("") } + var land by remember { mutableStateOf("Österreich") } + var mapsLink by remember { mutableStateOf("") } + + // Ansprechpersonen + var kontakt1Name by remember { mutableStateOf("") } + var kontakt1Email by remember { mutableStateOf("") } + var kontakt1Telefon by remember { mutableStateOf("") } + var kontakt2Name by remember { mutableStateOf("") } + var kontakt2Email by remember { mutableStateOf("") } + var kontakt2Telefon by remember { mutableStateOf("") } + + // Social / Links + var webseite by remember { mutableStateOf("") } + var facebook by remember { mutableStateOf("") } + var instagram by remember { mutableStateOf("") } + var youtube by remember { mutableStateOf("") } + + // Weitere Infos + var vereinsbeschreibung by remember { mutableStateOf("") } + var bankverbindung by remember { mutableStateOf("") } + var uid by remember { mutableStateOf("") } + + var saveSuccess by remember { mutableStateOf(false) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Veranstalter Profil") }, + navigationIcon = { + IconButton(onClick = onNavigateToDashboard) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") + } + }, + actions = { + Text( + text = authState.username ?: "", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(end = 8.dp) + ) + TextButton(onClick = onLogout) { Text("Abmelden") } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(scrollState) + .padding(24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + + // --- Logo & Vereinsname --- + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Verein / Veranstalter", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + + // Logo Placeholder + Surface( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.surfaceVariant, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant) + ) { + Column( + modifier = Modifier.padding(24.dp).fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text("🏆", style = MaterialTheme.typography.displayMedium) + Text( + "Vereins-/Veranstaltungslogo", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + OutlinedButton(onClick = { /* TODO: File Picker */ }) { + Text("Logo hochladen") + } + } + } + + OutlinedTextField( + value = vereinsname, + onValueChange = { vereinsname = it }, + label = { Text("Vereinsname / Veranstalter") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = vereinskuerzel, + onValueChange = { vereinskuerzel = it }, + label = { Text("Kürzel") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = vereinsbeschreibung, + onValueChange = { vereinsbeschreibung = it }, + label = { Text("Kurzbeschreibung / Über uns") }, + minLines = 3, + maxLines = 6, + modifier = Modifier.fillMaxWidth() + ) + } + } + + // --- Adresse --- + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Adresse", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + + OutlinedTextField( + value = adresse, + onValueChange = { adresse = it }, + label = { Text("Straße & Hausnummer") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + OutlinedTextField( + value = plz, + onValueChange = { plz = it }, + label = { Text("PLZ") }, + singleLine = true, + modifier = Modifier.width(100.dp) + ) + OutlinedTextField( + value = ort, + onValueChange = { ort = it }, + label = { Text("Ort") }, + singleLine = true, + modifier = Modifier.weight(1f) + ) + } + OutlinedTextField( + value = land, + onValueChange = { land = it }, + label = { Text("Land") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = mapsLink, + onValueChange = { mapsLink = it }, + label = { Text("Google Maps / OpenStreetMap Link") }, + placeholder = { Text("https://maps.google.com/...") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + } + } + + // --- Ansprechpersonen --- + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Ansprechpersonen", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + + Text("Hauptkontakt", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + OutlinedTextField( + value = kontakt1Name, + onValueChange = { kontakt1Name = it }, + label = { Text("Name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = kontakt1Email, + onValueChange = { kontakt1Email = it }, + label = { Text("E-Mail") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = kontakt1Telefon, + onValueChange = { kontakt1Telefon = it }, + label = { Text("Telefon / Mobil") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + HorizontalDivider() + + Text( + "Weiterer Kontakt (optional)", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.secondary + ) + OutlinedTextField( + value = kontakt2Name, + onValueChange = { kontakt2Name = it }, + label = { Text("Name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = kontakt2Email, + onValueChange = { kontakt2Email = it }, + label = { Text("E-Mail") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = kontakt2Telefon, + onValueChange = { kontakt2Telefon = it }, + label = { Text("Telefon / Mobil") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + } + } + + // --- Social Media & Links --- + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Links & Social Media", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + + OutlinedTextField( + value = webseite, + onValueChange = { webseite = it }, + label = { Text("Webseite") }, + placeholder = { Text("https://www.meinverein.at") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = facebook, + onValueChange = { facebook = it }, + label = { Text("Facebook") }, + placeholder = { Text("https://facebook.com/...") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = instagram, + onValueChange = { instagram = it }, + label = { Text("Instagram") }, + placeholder = { Text("https://instagram.com/...") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = youtube, + onValueChange = { youtube = it }, + label = { Text("YouTube") }, + placeholder = { Text("https://youtube.com/...") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + } + } + + // --- Weitere Vereinsdaten --- + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Weitere Informationen", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + + OutlinedTextField( + value = bankverbindung, + onValueChange = { bankverbindung = it }, + label = { Text("IBAN / Bankverbindung") }, + placeholder = { Text("AT12 3456 7890 1234 5678") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = uid, + onValueChange = { uid = it }, + label = { Text("UID-Nummer / ZVR-Zahl") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + } + } + + // --- Speichern --- + if (saveSuccess) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer), + modifier = Modifier.fillMaxWidth() + ) { + Text( + "✓ Profil erfolgreich gespeichert!", + modifier = Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.onPrimaryContainer, + style = MaterialTheme.typography.bodyLarge + ) + } + } + + Button( + onClick = { + // TODO: Backend-Anbindung (PUT /api/organizer/profile) + saveSuccess = true + }, + modifier = Modifier.fillMaxWidth().height(52.dp) + ) { + Text("Profil speichern", style = MaterialTheme.typography.titleMedium) + } + + OutlinedButton( + onClick = onNavigateToDashboard, + modifier = Modifier.fillMaxWidth() + ) { + Text("Zum Dashboard") + } + + Spacer(modifier = Modifier.height(24.dp)) + } + } +} +