diff --git a/docs/99_Journal/2026-03-18_Session_Log_Domain_und_Frontend_Kickoff.md b/docs/99_Journal/2026-03-18_Session_Log_Domain_und_Frontend_Kickoff.md new file mode 100644 index 00000000..8cc9b73f --- /dev/null +++ b/docs/99_Journal/2026-03-18_Session_Log_Domain_und_Frontend_Kickoff.md @@ -0,0 +1,55 @@ +# Session Log: 18.03.2026 - Domain Workshop (Fortsetzung) & Frontend Kick-off + +**Teilnehmer:** Owner, 🏗️ [Lead Architect], 📜 [ÖTO/FEI Rulebook Expert], 👷 [Backend Developer], 🎨 [Frontend Expert], +🖌️ [UI/UX Designer], 🧹 [Curator] + +## Ziel der Session + +Zusammenfassung der Domain-Erkenntnisse vom Vortag, Festlegung des Scopes für das MVP (Phase 1) und Start der +Frontend-Implementierung auf Basis des "UI-Driven Development" Ansatzes. + +## Ergebnisse & Beschlüsse + +### 1. Scope für MVP (Phase 1) + +* **Fokus-Turniere:** Die Turniere in Neumarkt (26128 und 26129) Ende April dienen als erstes großes Etappenziel (MVP + Feldtest). +* **Turnier-Kategorien:** Fokus ausschließlich auf **C-NEU** und **C**. +* **Sparten:** Fokus ausschließlich auf **Dressur (D)** und **Springen (S)**. +* **Begründung:** Diese Eingrenzung reduziert die Komplexität des Regelwerks massiv und ermöglicht eine schnelle + Auslieferung eines produktionsreifen Kernsystems für den Großteil der nationalen Turniere. + +### 2. Frontend-Entwicklung (UI-Driven Development) + +* Die Entwicklung startet beim Frontend (`frontend/shells/meldestelle-portal`), um den perfekten, praxisnahen + Workflow ("Enter & Tab") zu garantieren, bevor das Backend die Daten liefert. +* **Landing Page (`mo-code.at`):** Wurde neu designt. Kern-Säulen des Systems (Regelwerks-Intelligenz, Offline-First, + Speed-Workflow, Smarte Kassenführung) wurden als USPs platziert. +* **Aktuelle Turniere:** Eine neue Sektion auf der Landing Page zeigt eine Liste der kommenden Turniere (mit + Platzhaltern für Neumarkt 26128/26129). +* **Meldestelle Dashboard:** Nach dem Login gelangt der User nicht mehr auf eine generische "Welcome"-Seite, sondern in + ein dediziertes Dashboard. + * Links: Verwaltung der eigenen Turniere (inkl. Absprung ins Meldestellen-Cockpit). + * Rechts: System-Tools (Ping-Service, ZNS-Import). +* **Turnieranlage Wizard:** Der Button "+ Neues Turnier anlegen" führt nun in einen mehrstufigen Wizard: + 1. Stammdaten & ZNS Import + 2. Konfiguration (Austragungsplätze) + 3. Funktionäre + 4. Bewerbe anlegen (Master-Detail Ansicht analog zum Legacy-System) +* **Korrektur Compose-Updates:** Veraltete Compose-Komponenten (`Divider`, `TabRow`) wurden durch ihre modernen + Pendants (`HorizontalDivider`, `PrimaryTabRow`) ersetzt, um Kompilierfehler zu beheben. + +### 3. Nächste Schritte (Backlog) + +* **Backend:** Implementierung des ZNS-Importers (Entpacken der `zns.zip`, Einlesen der `.dat` Dateien mit Codepage 850, + Generierung erster Events). +* **Frontend:** Detaillierung der "Nennungs-Maske" (Cockpit), die für die schnelle telefonische Nennung optimiert werden + muss. + +## Aktualisierte Dokumente + +* `docs/03_Domain/01_Core_Model/Entities/Event_Structure_Diagram.md` (Scope-Eingrenzung auf C-NEU/C & D/S dokumentiert) +* `docs/03_Domain/03_Analysis/Domain_Workshop_Results_2026-03-17.md` (MVP-Fokus hinzugefügt) +* `frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt` (Landing Page, Dashboard & Wizard implementiert) +* `frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt` (Routen für + Dashboard & CreateTournament hinzugefügt) diff --git a/docs/Neumarkt2026/26128.md b/docs/Neumarkt2026/26128.md new file mode 100644 index 00000000..f46f0e94 --- /dev/null +++ b/docs/Neumarkt2026/26128.md @@ -0,0 +1,71 @@ +# CSN-C NEU / CSNP-C NEU NEUMARKT/M. + +**Turnier-Nr.: 26128** | [cite_start]**Datum: 25. April 2026** [cite: 1, 2] + +## Allgemeine Informationen + +* **Veranstalter:** Union Reit- u. Fahrverein Neumarkt/M. (6-009) [cite_start][cite: 3] +* [cite_start]**Ort:** Reitanlage Stroblmair, 4212 Neumarkt [cite: 3] +* [cite_start]**Kontakt:** Ursula Stroblmair, Brandstetterweg 2, 4212 Neumarkt [cite: 4] + * **Tel.:** 0664 1832381 + * [cite_start]**E-Mail:** reit-stall@gmx.at [cite: 4] +* [cite_start]**Nennungsschluss:** 24.04.2026, 19:00 Uhr [cite: 4] +* [cite_start]**Online-Nennung:** Ab Mittwoch, 22.04. + auf [www.ihremeldestelle.at](http://www.ihremeldestelle.at) [cite: 5] +* [cite_start]**Meldestelle:** Geöffnet ab 24.04., 17:00 Uhr (Tel: +43 681 10769120) [cite: 8] + +## Technische Details + +* [cite_start]**Austragungsplatz:** 45 x 65 m (Sand/Vlies) [cite: 6] +* [cite_start]**Vorbereitungsplatz:** 20 x 40 m Halle (Sand/Vlies) [cite: 6] +* [cite_start]**Warmreiten:** Draußen (20 x 60 m Sand/Vlies) möglich [cite: 16] +* [cite_start]**Boxen:** Keine Einstallung möglich [cite: 9] + +## Funktionäre + +* [cite_start]**Turnierleiter:** Ursula Stroblmair [cite: 6] +* [cite_start]**Turnierbeauftragter:** Rudi Kreupl [cite: 7] +* [cite_start]**Richter:** Rudi Kreupl, Helmut Riedler [cite: 7] +* [cite_start]**Parcoursbauchef:** Kurt Reitetschlägerr [cite: 8] +* [cite_start]**Tierarzt:** Dr. Sabine Ötschmaier [cite: 8] + +--- + +## Besondere Bestimmungen + +* **Kosten:** Startgeld € 15,- pro Bewerb. [cite_start]Kein Nenngeld, kein Sporteuro. [cite: 11] +* **Teilnahmebedingungen:** + * [cite_start]Für Springprüfungen bis 95 cm: Mitgliedschaft OEPS-Verein und Reiterpass erforderlich. [cite: 12] + * [cite_start]Pferde bis 90 cm müssen **nicht** beim OEPS registriert sein. [cite: 14] + * [cite_start]Pferdepass mit gültigem Impfschutz (§ 11 OTO) ist vorzulegen. [cite: 15] + * [cite_start]Haftpflichtversicherung für jedes Pferd ist Pflicht. [cite: 21] +* **Startregelung:** + * [cite_start]Ein Pferd darf maximal 3x pro Tag starten. [cite: 14] + * [cite_start]In Bewerben bis 95 cm darf ein Pferd mit zwei verschiedenen Reitern starten. [cite: 13] +* [cite_start]**Hunde:** Am gesamten Gelände herrscht Leinenpflicht. [cite: 18] + +--- + +## Bewerbe (Samstag, 25. April 2026 - Beginn 08:00 Uhr) + +| Nr. | Bewerb | Höhe | Richtverfahren / Abteilungen | +|:-------|:--------------------------------|:-------|:------------------------------------------------------------------------------------| +| **1** | Pony Stilspringprüfung | 60 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **2** | Einlaufspringprüfung | 60 cm | [cite_start]RV: § 204/4 (1. Abt: lizenzfrei / 2. Abt: mit Lizenz) [cite: 27] | +| **3** | Pony Stilspringprüfung | 70 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **4** | Einlaufspringprüfung | 70 cm | [cite_start]RV: § 218 (1. Abt: lizenzfrei / 2. Abt: mit Lizenz) [cite: 27] | +| **5** | Pony Stilspringprüfung | 80 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **6** | Stilspringprüfung | 80 cm | [cite_start]RV: § 204/4 (1. Abt: lizenzfrei / 2. Abt: R1 & 5-6j. Pferde) [cite: 27] | +| **7** | Pony Stilspringprüfung | 95 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **8** | Springreiterbewerb (lizenzfrei) | 95 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **9** | Standardspringprüfung | 95 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2 und höher) [cite: 27] | +| **10** | Springpferdeprüfung | 105 cm | [cite_start]RV: § 203/3 (1. Abt: 4-jährig / 2. Abt: 5-6-jährig) [cite: 27] | +| **11** | Stilspringprüfung | 105 cm | [cite_start]RV: § 204/4 (1. Abt: R1) [cite: 27] | +| **12** | Standardspringprüfung | 105 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2/RS2 und höher) [cite: 27] | +| **13** | Stilspringprüfung | 115 cm | [cite_start]RV: § 204/4 (1. Abt: R1) [cite: 28, 30, 31] | +| **14** | Standardspringprüfung | 115 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2/RS2 und höher) [cite: 32, 34, 36] | + +--- +**Haftung:** Der Veranstalter übernimmt keine Haftung. [cite_start]Teilnehmer haften persönlich für Schäden gegenüber +Dritten. [cite: 19, 20] + diff --git a/docs/Neumarkt2026/26128.pdf b/docs/Neumarkt2026/26128.pdf new file mode 100644 index 00000000..624dbe06 Binary files /dev/null and b/docs/Neumarkt2026/26128.pdf differ diff --git a/docs/Neumarkt2026/26129.md b/docs/Neumarkt2026/26129.md new file mode 100644 index 00000000..7f9b58d6 --- /dev/null +++ b/docs/Neumarkt2026/26129.md @@ -0,0 +1,70 @@ +# CDN-C NEU / CDNP-C NEU NEUMARKT/M., OÖ + +**Turnier-Nr.: 26129** | [cite_start]**Datum: 26. April 2026** [cite: 37] + +## Allgemeine Informationen + +* **Veranstalter**: Union Reit- u. Fahrverein Neumarkt/M. (6-009) [cite_start][cite: 38]. +* [cite_start]**Ort**: Reitanlage Stroblmair, 4212 Neumarkt[cite: 38]. +* [cite_start]**Kontaktadresse**: Ursula Stroblmair, Brandstetterweg 2, 4212 Neumarkt[cite: 39]. + * [cite_start]**Telefon**: 0664 1832381[cite: 39]. + * [cite_start]**E-Mail**: reit-stall@gmx.at[cite: 39]. +* [cite_start]**Nennungsschluss**: 25.04.2026, 19:00 Uhr[cite: 39, 53]. +* [cite_start]**Online-Nennung**: Ab Mittwoch, 22.04. auf www.ihremeldestelle.at möglich[cite: 40]. +* [cite_start]**Meldestelle**: Geöffnet ab 25.04., 17:00 Uhr (Tel: +43 681 10769120)[cite: 43]. +* [cite_start]**Start- und Ergebnislisten**: Ab 20:30 Uhr auf www.ihremeldestelle.at verfügbar[cite: 44]. + +## Technische Details und Gebühren + +* [cite_start]**Austragungsplatz**: 20 x 60 m Sand/Vlies[cite: 41]. +* [cite_start]**Vorbereitungsplatz**: 20 x 40 m Halle (Sand/Vlies) und 20 x 60 m (Sand/Vlies)[cite: 41]. +* [cite_start]**Boxen**: Keine Einstallung möglich[cite: 44]. +* [cite_start]**Kosten**: Startgeld € 15,- pro Bewerb; kein Nenngeld und kein Sporteuro[cite: 40, 47]. + +## Funktionäre + +* [cite_start]**Turnierleiter**: Ursula Stroblmair[cite: 41]. +* [cite_start]**Turnierbeauftragte**: Alexandra Schuster[cite: 42]. +* [cite_start]**Richter**: Alexandra Schuster, Ulrike Knasmüller-Prinz, Karin Wallner[cite: 42]. +* [cite_start]**Steward**: Barbara Hruschka[cite: 42]. +* [cite_start]**Tierarzt**: Dr. Sabine Ötschmaier[cite: 42]. + +--- + +## Besondere Bestimmungen + +* **Teilnahmevoraussetzungen**: + * [cite_start]Für Reiterpass-/Reiternadel-Aufgaben ist die Mitgliedschaft bei einem OEPS-Verein und der Besitz des + Reiterpasses erforderlich[cite: 48]. + * [cite_start]Pferde für Reiterpass-/Reiternadel-Aufgaben müssen nicht beim OEPS registriert sein[cite: 50]. +* **Pferde**: + * [cite_start]Ein Pferd darf pro Tag maximal 3x starten[cite: 49]. + * [cite_start]Ein Pferd darf mit zwei verschiedenen Reitern an den Start gehen[cite: 49]. + * [cite_start]Vorlage des Pferdepasses mit gültigem Impfschutz gemäß § 11 OTO ist Pflicht[cite: 51]. + * [cite_start]Jedes teilnehmende Pferd muss haftpflichtversichert sein[cite: 57]. +* [cite_start]**Haftung**: Der Veranstalter übernimmt keine Haftung jeder Art und Ursache[cite: 55]. [cite_start] + Teilnehmer und Besitzer haften persönlich für Schäden gegenüber Dritten[cite: 56]. +* [cite_start]**Sonstiges**: Es gilt Leinenpflicht für Hunde auf dem gesamten Gelände[cite: 54]. [cite_start] + Ausländische Equiden unterliegen der TRACES-Pflicht[cite: 58]. + +--- + +## Bewerbe (Sonntag, 26. April 2026 - Beginn 08:00 Uhr) + +| Nr. | Bewerb | Aufg. | Details / Abteilungen | +|:-------|:---------------------------------|:---------------|:------------------------------------------------------------------------| +| **1** | Dressurreiterprüfung Reiterpass | R1 | [cite_start]RV: A § 103/5 [cite: 63] | +| **2** | Dressurreiterprüfung Reiternadel | R4 | [cite_start]RV: A § 103/5 [cite: 64] | +| **3** | Dressurreiterprüfung lizenzfrei | LF1 | [cite_start]RV: A § 103/5 [cite: 68] | +| **4** | Dressurreiterprüfung lizenzfrei | LF3 | [cite_start]RV: A § 103/5 [cite: 69] | +| **5** | First Ridden | - [cite_start] | [cite: 71] | +| **6** | Führzügelklasse | - [cite_start] | [cite: 73] | +| **7** | Pony Dressurprüfung Kl. A | P1 | [cite_start]RV: A, § 901 [cite: 75, 76] | +| **8** | Dressurreiterprüfung Kl. A | DRA1 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 78, 79, 81] | +| **9** | Dressurprüfung Kl. A | A5 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 82, 83, 98] | +| **13** | Dressurpferdeprüfung Kl. A | DPA1 | 1. Abt: 4-jähr. Pferde; 2. Abt: 5-6-jähr. [cite_start]Pferde [cite: 85] | +| **14** | Dressurpferdprüfung Kl. L | DPL1 | Für 5-6-jähr. [cite_start]Pferde [cite: 87] | +| **10** | Pony Dressurprüfung Kl. L | P6 | [cite_start]RV: A, § 901 [cite: 89, 90] | +| **11** | Dressurreiterprüfung Kl. L | DRL1 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 89, 92, 97] | +| **12** | Dressurprüfung Kl. L | L3 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 94, 96] | + diff --git a/docs/Neumarkt2026/26129.pdf b/docs/Neumarkt2026/26129.pdf new file mode 100644 index 00000000..2c348ee2 Binary files /dev/null and b/docs/Neumarkt2026/26129.pdf differ diff --git a/docs/Neumarkt2026/Logo+URFV+Neumarkt_uni_300-640w.webp b/docs/Neumarkt2026/Logo+URFV+Neumarkt_uni_300-640w.webp new file mode 100644 index 00000000..e2acf68b Binary files /dev/null and b/docs/Neumarkt2026/Logo+URFV+Neumarkt_uni_300-640w.webp differ diff --git a/docs/Neumarkt2026/Meldestelle-Dashboard_2026-03-18_13.png b/docs/Neumarkt2026/Meldestelle-Dashboard_2026-03-18_13.png new file mode 100644 index 00000000..232314f9 Binary files /dev/null and b/docs/Neumarkt2026/Meldestelle-Dashboard_2026-03-18_13.png differ diff --git a/docs/Neumarkt2026/Turnier-Card-Vorstellung_2026-03-18_13-18.png b/docs/Neumarkt2026/Turnier-Card-Vorstellung_2026-03-18_13-18.png new file mode 100644 index 00000000..b44b18dd Binary files /dev/null and b/docs/Neumarkt2026/Turnier-Card-Vorstellung_2026-03-18_13-18.png differ diff --git a/docs/OePS/ZNS.zip b/docs/OePS/ZNS.zip new file mode 100644 index 00000000..eab575b0 Binary files /dev/null and b/docs/OePS/ZNS.zip differ diff --git a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt index c59e018b..264ba957 100644 --- a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt +++ b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt @@ -3,6 +3,8 @@ package at.mocode.frontend.core.navigation sealed class AppScreen(val route: String) { data object Landing : AppScreen(Routes.HOME) data object Home : AppScreen("/home") + data object Dashboard : AppScreen("/dashboard") + data object CreateTournament : AppScreen("/tournament/create") // Neuer Screen data object Login : AppScreen(Routes.LOGIN) data object Ping : AppScreen("/ping") data object Profile : AppScreen("/profile") @@ -13,6 +15,8 @@ sealed class AppScreen(val route: String) { return when (route) { Routes.HOME -> Landing "/home" -> Home + "/dashboard" -> Dashboard + "/tournament/create" -> CreateTournament Routes.LOGIN, Routes.Auth.LOGIN -> Login "/ping" -> Ping "/profile" -> Profile diff --git a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt index e668e92a..062cdd80 100644 --- a/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt +++ b/frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt @@ -1,19 +1,20 @@ -import androidx.compose.material3.* -import androidx.compose.runtime.* 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.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.runtime.collectAsState -import at.mocode.frontend.core.navigation.AppScreen import at.mocode.frontend.core.auth.data.AuthTokenManager import at.mocode.frontend.core.auth.presentation.LoginScreen import at.mocode.frontend.core.auth.presentation.LoginViewModel -import at.mocode.ping.feature.presentation.PingScreen -import at.mocode.ping.feature.presentation.PingViewModel import at.mocode.frontend.core.designsystem.components.AppFooter import at.mocode.frontend.core.designsystem.theme.AppTheme +import at.mocode.frontend.core.navigation.AppScreen +import at.mocode.ping.feature.presentation.PingScreen +import at.mocode.ping.feature.presentation.PingViewModel import navigation.StateNavigationPort import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel @@ -39,31 +40,48 @@ fun MainApp() { when (currentScreen) { is AppScreen.Landing -> LandingScreen( - onPrimaryCta = { navigationPort.navigateToScreen(AppScreen.Login) }, - onSecondary = { navigationPort.navigateToScreen(AppScreen.Home) } + onPrimaryCta = { navigationPort.navigateToScreen(AppScreen.Login) } ) - is AppScreen.Home -> WelcomeScreen( + is AppScreen.Dashboard -> DashboardScreen( authTokenManager = authTokenManager, onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) }, - onOpenLogin = { navigationPort.navigateToScreen(AppScreen.Login) }, - onOpenProfile = { navigationPort.navigateToScreen(AppScreen.Profile) } + onCreateTournament = { navigationPort.navigateToScreen(AppScreen.CreateTournament) }, + onLogout = { + authTokenManager.clearToken() + navigationPort.navigateToScreen(AppScreen.Landing) + } + ) + + is AppScreen.Home -> DashboardScreen( // Route /home to Dashboard for now + authTokenManager = authTokenManager, + onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) }, + onCreateTournament = { navigationPort.navigateToScreen(AppScreen.CreateTournament) }, + onLogout = { + authTokenManager.clearToken() + navigationPort.navigateToScreen(AppScreen.Landing) + } + ) + + is AppScreen.CreateTournament -> CreateTournamentScreen( + onBack = { navigationPort.navigateToScreen(AppScreen.Dashboard) }, + onSave = { navigationPort.navigateToScreen(AppScreen.Dashboard) } // Later we go to tournament detail ) is AppScreen.Login -> LoginScreen( viewModel = loginViewModel, - onLoginSuccess = { navigationPort.navigateToScreen(AppScreen.Profile) }, - onBack = { navigationPort.navigateToScreen(AppScreen.Home) } + onLoginSuccess = { navigationPort.navigateToScreen(AppScreen.Dashboard) }, + onBack = { navigationPort.navigateToScreen(AppScreen.Landing) } ) is AppScreen.Ping -> PingScreen( viewModel = pingViewModel, - onBack = { navigationPort.navigateToScreen(AppScreen.Home) } // Navigate back to Home + onBack = { navigationPort.navigateToScreen(AppScreen.Dashboard) } ) is AppScreen.Profile -> AuthStatusScreen( authTokenManager = authTokenManager, - onBackToHome = { navigationPort.navigateToScreen(AppScreen.Home) } + onBackToHome = { navigationPort.navigateToScreen(AppScreen.Dashboard) } ) else -> {} @@ -74,8 +92,7 @@ fun MainApp() { @Composable private fun LandingScreen( - onPrimaryCta: () -> Unit, - onSecondary: () -> Unit + onPrimaryCta: () -> Unit ) { val scrollState = rememberScrollState() @@ -84,56 +101,103 @@ private fun LandingScreen( .fillMaxSize() .verticalScroll(scrollState) ) { + // Top Bar area (simple for landing) + 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 = "mo-code.at", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + Button(onClick = onPrimaryCta) { + Text("Login Meldestelle") + } + } + } + // Hero Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 40.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + .padding(horizontal = 24.dp, vertical = 60.dp), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "Equest‑Events", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary + text = "Die moderne Turniermeldestelle", + style = MaterialTheme.typography.displayMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = "Von Praktikern für Praktiker. Schneller Nennen, fehlerfrei Richten und stressfrei Auswerten – konform nach ÖTO & FEI.", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // --- AKTUELLE TURNIERE SECTION --- + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 40.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Text("Aktuelle Turniere", style = MaterialTheme.typography.headlineMedium) + + // Dummy Daten basierend auf Neumarkt 2026 + val turniere = listOf( + TournamentData( + id = "26128", + date = "25. APRIL 2026", + title = "CSN-C NEU CSNP-C NEU", + location = "NEUMARKT/M., OÖ" + ), + TournamentData( + id = "26129", + date = "26. APRIL 2026", + title = "CDN-C NEU CDNP-C NEU", + location = "NEUMARKT/M., OÖ" + ) ) - Text( - text = "Die kompetente Turnier‑Meldestelle.", - style = MaterialTheme.typography.headlineLarge - ) - Text( - text = "Equest‑Events entwickelt die digitale Infrastruktur des österreichischen Pferdesports – aus der Praxis. Für die Praxis.", - style = MaterialTheme.typography.bodyLarge - ) - Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - Button(onClick = onPrimaryCta) { Text("Anmelden (Pilot‑Partner)") } - TextButton(onClick = onSecondary) { Text("Mehr erfahren") } + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth() + ) { + turniere.forEach { turnier -> + TournamentCard(turnier) + } } } - // Manifest + // Manifest / Intro Surface(color = MaterialTheme.colorScheme.surfaceVariant) { Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 40.dp), + .padding(horizontal = 24.dp, vertical = 60.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text("Unser Anspruch: Durchdachtes System.", style = MaterialTheme.typography.headlineMedium) + Text("Unser Anspruch: Ein durchdachtes System.", style = MaterialTheme.typography.headlineMedium) Text( "Die Meldestelle ist das Herzstück jedes Turniers. Wenn sie stolpert, stockt der Sport. Wir verstehen den Balanceakt zwischen Veranstaltern, Reitern und den Verbänden.", style = MaterialTheme.typography.bodyLarge ) Text( - "Deshalb entwickeln wir Equest‑Events nicht am Reißbrett, sondern direkt am Turnier – aus der Sicht der Meldestelle, der Richter, der Zeitnehmer und aller Funktionäre.", + "Deshalb entwickeln wir diese Plattform nicht am Reißbrett, sondern direkt am Turnierplatz – aus der Sicht der Meldestelle, der Richter, der Zeitnehmer und aller Funktionäre.", style = MaterialTheme.typography.bodyLarge ) Text( - "Aktuell befindet sich unser System in einer Pilotphase für C‑ und C‑NEU‑Turniere. Wir wachsen organisch – Seite an Seite mit unseren Pilot‑Partnern.", - style = MaterialTheme.typography.bodyLarge - ) - Text( - "Jedes Feedback fließt direkt in die Entwicklung ein, um eine Lösung zu schaffen, die den realen Bedürfnissen vor Ort entspricht.", + "Mit Fokus auf die Praxis: Tastaturbedienung für höchste Geschwindigkeit, Offline-Fähigkeit für das 'Plumpsklo' am Rand des Abreiteplatzes und eine integrierte Kassenführung.", style = MaterialTheme.typography.bodyLarge ) } @@ -143,25 +207,30 @@ private fun LandingScreen( Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 40.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) + .padding(horizontal = 24.dp, vertical = 60.dp), + verticalArrangement = Arrangement.spacedBy(32.dp) ) { - Text("Die drei Säulen", style = MaterialTheme.typography.headlineMedium) - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Text("Die Kern-Säulen", style = MaterialTheme.typography.headlineMedium) + Column(verticalArrangement = Arrangement.spacedBy(24.dp)) { FeatureCard( number = "01", - title = "Regelwerks‑Intelligenz (FEI & ÖTO)", - body = "Wir verbinden ÖTO und FEI – und nehmen Ihnen die Validierungs‑Komplexität ab. Von der Lizenzprüfung bis zur korrekten Anwendung der Bestimmungen." + title = "Regelwerks-Intelligenz (ÖTO)", + body = "Wir nehmen Ihnen die Validierungs-Komplexität ab. Von der Lizenzprüfung der Reiter bis zur Kontrolle der Richterqualifikationen beim Anlegen der Bewerbe." ) FeatureCard( number = "02", - title = "Plattformunabhängig & Offline‑fähig", - body = "Stabil auf Laptop und mobil. Dank Offline‑Unterstützung arbeiten Sie nahtlos weiter – selbst wenn die Internetverbindung am Platz abreißt." + title = "Offline-First & Resilient", + body = "Stabil auf dem Laptop. Dank Offline-Unterstützung und lokaler Datenbank arbeiten Sie nahtlos weiter, selbst wenn die Internetverbindung am Platz wieder einmal abreißt." ) FeatureCard( number = "03", - title = "Fokus auf den Sport", - body = "Wir reduzieren Administration dort, wo es sinnvoll ist – damit sich alle auf das Wesentliche konzentrieren können: den Reitsport." + title = "Speed-Workflow", + body = "Die Nennungsmaske und die Ergebniserfassung sind kompromisslos auf Geschwindigkeit und Tastaturbedienung (Enter & Tab) optimiert. Weil am Turniertag jede Sekunde zählt." + ) + FeatureCard( + number = "04", + title = "Smarte Kassenführung", + body = "Kontobasierte Abrechnung für Reiter und Besitzer. Nenngelder, Startgelder und Nachnenngebühren sauber getrennt – selbst ein Nennungstausch wird als einfacher Transfer verbucht." ) } } @@ -171,65 +240,513 @@ private fun LandingScreen( } } +// Data class for dummy tournament +private data class TournamentData( + val id: String, + val date: String, + val title: String, + val location: String +) + @Composable -private fun FeatureCard(number: String, title: String, body: String) { - Surface(tonalElevation = 0.dp) { - Row(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.width(56.dp).padding(top = 6.dp)) { - Text(text = number, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) +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 + ) + } } - Column(modifier = Modifier.weight(1f)) { - Text(title, style = MaterialTheme.typography.titleLarge) - Spacer(Modifier.height(4.dp)) - Text(body, style = MaterialTheme.typography.bodyLarge) + + 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 WelcomeScreen( +private fun FeatureCard(number: String, title: String, body: String) { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + verticalAlignment = Alignment.Top + ) { + Text( + text = number, + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Black, + modifier = Modifier.width(64.dp) + ) + Column(modifier = Modifier.weight(1f)) { + Text(title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Spacer(Modifier.height(8.dp)) + Text(body, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } + } +} + +@Composable +private fun DashboardScreen( authTokenManager: AuthTokenManager, onOpenPing: () -> Unit, - onOpenLogin: () -> Unit, - onOpenProfile: () -> Unit + onCreateTournament: () -> Unit, + onLogout: () -> Unit ) { val authState by authTokenManager.authState.collectAsState() - val scrollState = rememberScrollState() Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .padding(24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + modifier = Modifier.fillMaxSize() ) { - Text( - text = "Willkommen zur Meldestelle", - style = MaterialTheme.typography.headlineMedium - ) + // 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 = "Meldestelle 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") + } + } + } + } - // Auth info - Card(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(16.dp)) { - if (authState.isAuthenticated) { - Text("Du bist als ${authState.username ?: authState.userId ?: "unbekannt"} angemeldet.") - Spacer(Modifier.height(8.dp)) - Button(onClick = onOpenProfile) { Text("Profil anzeigen") } - } else { - Text("Du bist nicht angemeldet.") + // Main Content Area + Row( + modifier = Modifier.fillMaxSize().padding(24.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + // Left Column (Turniere) + Column( + modifier = Modifier.weight(2f), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text("Meine Turniere", style = MaterialTheme.typography.headlineSmall) + + // Dummy Turniere für die Meldestelle + val turniere = listOf( + TournamentData( + id = "26128", + date = "25. APRIL 2026", + title = "CSN-C NEU CSNP-C NEU", + location = "NEUMARKT/M., OÖ" + ), + TournamentData( + id = "26129", + date = "26. APRIL 2026", + title = "CDN-C NEU CDNP-C NEU", + location = "NEUMARKT/M., OÖ" + ) + ) + + turniere.forEach { turnier -> + 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(turnier.title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text("Nr: ${turnier.id} | ${turnier.date}", style = MaterialTheme.typography.bodyMedium) + } + Button(onClick = { /* TODO: Open Meldestellen Cockpit for this tournament */ }) { + Text("Meldestelle öffnen") + } + } + } + } + + OutlinedButton( + onClick = onCreateTournament, + modifier = Modifier.fillMaxWidth().padding(top = 16.dp) + ) { + Text("+ Neues Turnier anlegen") + } + } + + // Right Column (System / Tools) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text("System & Tools", style = MaterialTheme.typography.headlineSmall) + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + OutlinedButton( + onClick = onOpenPing, + modifier = Modifier.fillMaxWidth() + ) { + Text("Ping-Service (System Status)") + } + + OutlinedButton( + onClick = { /* TODO: ZNS Import */ }, + modifier = Modifier.fillMaxWidth() + ) { + Text("ZNS-Daten Importieren") + } + } + } + } + } + } +} + +@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", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) } } } - // Actions - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { - Button(onClick = onOpenPing, modifier = Modifier.weight(1f)) { Text("Ping-Service") } - if (!authState.isAuthenticated) { - Button( - onClick = onOpenLogin, - modifier = Modifier.weight(1f) - ) { Text("Login") } + // 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 = "Stammdaten", isActive = currentStep == 1, isCompleted = currentStep > 1) + StepIndicator(step = 2, title = "Konfiguration", isActive = currentStep == 2, isCompleted = currentStep > 2) + StepIndicator(step = 3, title = "Funktionäre", isActive = currentStep == 3, isCompleted = currentStep > 3) + StepIndicator(step = 4, title = "Bewerbe", isActive = currentStep == 4, isCompleted = currentStep > 4) + } + } + + // Wizard Content Area + Box(modifier = Modifier.weight(1f).padding(24.dp)) { + when (currentStep) { + 1 -> TournamentStepStammdaten() + 2 -> TournamentStepKonfiguration() + 3 -> TournamentStepFunktionaere() + 4 -> 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 < 4) { + 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 TournamentStepStammdaten() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 1: Turnier-Stammdaten", style = MaterialTheme.typography.headlineSmall) + + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Turniernummer OEPS (z.B. 26128)") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Turniername (z.B. CSN-C NEU Neumarkt)") }, + modifier = Modifier.fillMaxWidth() + ) + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) { + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Datum von") }, + modifier = Modifier.weight(1f) + ) + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Datum bis") }, + modifier = Modifier.weight(1f) + ) + } + + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("ZNS Import", fontWeight = FontWeight.Bold) + Text( + "Hier laden wir später das ZNS.zip für dieses Turnier hoch, um Starter und Lizenzen zu importieren.", + style = MaterialTheme.typography.bodyMedium + ) + Button(onClick = { /*TODO*/ }) { Text("ZNS.zip auswählen...") } + } + } + } +} + +@Composable +fun TournamentStepKonfiguration() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 2: 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)") }) + FilterChip(selected = false, onClick = {}, label = { Text("Halle") }) + FilterChip(selected = false, onClick = {}, label = { Text("+ Hinzufügen") }) + } + } + } + } +} + +@Composable +fun TournamentStepFunktionaere() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) { + Text("Schritt 3: Team & Funktionäre", style = MaterialTheme.typography.headlineSmall) + Text("Zuweisung von Richtern und Parcoursbauern (aus ZNS)") + + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Turnierbeauftragter (Suche nach Name oder ID)") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = "", + onValueChange = {}, + label = { Text("Richter (Suche nach Name oder ID)") }, + modifier = Modifier.fillMaxWidth() + ) + } +} + +@Composable +fun TournamentStepBewerbe() { + Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize()) { + Text("Schritt 4: 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("...", 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") + } + } + } } } } @@ -259,11 +776,11 @@ private fun AuthStatusScreen( }) { Text("Abmelden") } Spacer(Modifier.height(8.dp)) - OutlinedButton(onClick = onBackToHome) { Text("Zurück zur Startseite") } + OutlinedButton(onClick = onBackToHome) { Text("Zurück zum Dashboard") } } else { Text("Nicht angemeldet.") Spacer(Modifier.height(8.dp)) - Button(onClick = onBackToHome) { Text("Zurück zur Startseite") } + Button(onClick = onBackToHome) { Text("Zurück") } } } }