refactor(ui): update app title, footer text, and layout spacing
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 7m39s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 7m21s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m15s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m45s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 7m39s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 7m21s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m15s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m45s
- Changed the app title to "Equest-Events Master Desktop" in `main.kt`. - Updated footer text with new phrasing: "© Mocode-Software · Developed with love for equestrian sports". - Improved code formatting for consistent alignment and indentation across files. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
# Session Log: 19.03.2026 - Frontend "Turnier anlegen" & Offline-Architektur
|
||||||
|
|
||||||
|
**Teilnehmer:** Owner, 🏗️ [Lead Architect], 📜 [ÖTO/FEI Rulebook Expert], 👷 [Backend Developer], 🎨 [Frontend Expert],
|
||||||
|
🧹 [Curator]
|
||||||
|
|
||||||
|
## Ziel der Session
|
||||||
|
|
||||||
|
Erweiterung des Frontend-Konzepts für den Turnieranlage-Wizard (Setup) mit besonderem Fokus auf harte Offline-Szenarien,
|
||||||
|
Datenbank-Trennung pro Turnier, Synchronisation via USB-Stick und der architektonischen Trennung zwischen Web-App und
|
||||||
|
Master-Desktop-App.
|
||||||
|
|
||||||
|
## Ergebnisse & Beschlüsse
|
||||||
|
|
||||||
|
### 1. Trennung: Web-App vs. "Equest-Events Master Desktop"
|
||||||
|
|
||||||
|
Es wurde eine klare architektonische Trennung der Clients beschlossen, die auf den Fähigkeiten von Compose
|
||||||
|
Multiplatform (KMP) basiert:
|
||||||
|
|
||||||
|
* **Web-App (mo-code.at):** Dient als zentrale Anlaufstelle. Für die Öffentlichkeit (Ausschreibungen, Nennungen durch
|
||||||
|
Reiter) und als leichtgewichtiges "Meldestellen Dashboard" zur Übersicht. Sie benötigt keine komplexen lokalen
|
||||||
|
Datenbanken (Service Worker) für die Startseite.
|
||||||
|
* **Desktop-App ("Equest-Events Master Desktop"):** Eine herunterladbare, native Anwendung (.exe / .app). Dies ist das "
|
||||||
|
Profi-Werkzeug" für die Meldestelle vor Ort.
|
||||||
|
* *Nur* die Desktop-App hat volle Rechte auf das Dateisystem (OS-Ebene), um z.B. direkt auf USB-Sticks zuzugreifen.
|
||||||
|
* Sie beinhaltet exklusiv die "Admin-Features": Turnieranlage, ZNS-Import, OEPS-Export und die Initialisierung der
|
||||||
|
lokalen Offline-Datenbank (SQLite).
|
||||||
|
* Der Download-Link für diese Master-App ist direkt im Web-Dashboard für authentifizierte Meldestellen-Admins
|
||||||
|
integriert.
|
||||||
|
|
||||||
|
### 2. Hardcore Offline-First & Turnier-Datenbanken
|
||||||
|
|
||||||
|
* **Isolierte Datenbanken:** Um extreme Offline-Szenarien (kein Internet, kein lokales Netzwerk, "Plumpsklo"-Setup)
|
||||||
|
abzusichern, wird bei der Turnieranlage pro Turnier eine eigenständige, isolierte Datenbank (Turnier-Instanz)
|
||||||
|
initialisiert.
|
||||||
|
* **USB-Stick Synchronisation:** Die Master-Desktop-App muss in der Lage sein, diese Turnier-Datenbank komplett über das
|
||||||
|
Dateisystem zu exportieren und auf einem anderen Gerät (z.B. Desktop-App am Richterturm) wieder zu importieren. Es
|
||||||
|
gibt einen manuellen Sync-Prozess (Im-/Export) von Start- und Ergebnislisten via USB-Stick, falls WLAN/LAN ausfallen.
|
||||||
|
* **Spezifizierung Dateipfade im UI:** Der "Transfer"-Schritt im UI wurde verfeinert. Statt abstrakter Buttons gibt es
|
||||||
|
nun klare Eingabefelder für Datei- und Ordnerpfade inkl. File-Picker-Buttons (`...`), um den Desktop-Charakter der
|
||||||
|
Anwendung zu unterstreichen.
|
||||||
|
|
||||||
|
### 3. Fremddaten, Y- & Z-Nummern
|
||||||
|
|
||||||
|
* **Regelwerks-Bestätigung:** Der Rulebook Expert bestätigte, dass die Vergabe von **Y-Nummern** (junge nationale Pferde
|
||||||
|
ohne Registrierung) und **Z-Nummern** (ausländische Pferde) für *alle* nationalen Turniere gilt, nicht nur für C-NEU.
|
||||||
|
* **Manuelle Anlage:** Das System unterstützt zwingend die nahtlose manuelle Anlage dieser Pferde und Reiter, die nicht
|
||||||
|
im ZNS existieren (z.B. Gäste aus CZ). Diese manuellen Anlagen erfolgen auf Basis der isolierten Turnier-Datenbank.
|
||||||
|
|
||||||
|
### 4. UI-Erweiterung: Der neue "Transfer"-Schritt im Wizard
|
||||||
|
|
||||||
|
Der geführte Wizard zum Anlegen eines Turniers in der Desktop-App (`MainApp.kt`) wurde um den essenziellen ersten
|
||||||
|
Schritt erweitert: **"Transfer & Initialisierung"**.
|
||||||
|
Dieser Schritt fungiert als technische Schaltzentrale und beinhaltet:
|
||||||
|
|
||||||
|
* Initialisierung der Turnier-Datenbank (OEPS Nummer).
|
||||||
|
* ZNS-Daten Import (OEPS) & Import von externen Zucht-Daten (z.B. AWÖ) via Dateiauswahl.
|
||||||
|
* OEPS-Ergebnis-Export (Generierung der `.erg` Datei nach Turnierende) via Dateiauswahl.
|
||||||
|
* Manuelle Up-/Downloads für den USB-Stick Sync (Turnier Export / Import) via Verzeichnisauswahl.
|
||||||
|
|
||||||
|
## Aktualisierte Dokumente
|
||||||
|
|
||||||
|
* `frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt` (Fünfstufiger Wizard mit detailliertem "
|
||||||
|
Transfer"-Schritt und File-Picker Mockups implementiert. Dashboard-Hinweis auf Desktop-App Download hinzugefügt).
|
||||||
|
* `frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt` (Titel der Desktop-Anwendung auf "Equest-Events Master
|
||||||
|
Desktop" geändert).
|
||||||
+4
-2
@@ -1,7 +1,9 @@
|
|||||||
package at.mocode.frontend.core.designsystem.components
|
package at.mocode.frontend.core.designsystem.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -20,7 +22,7 @@ fun AppFooter() {
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "© Equest-Events · Entwickelt in Österreich",
|
text = "© Mocode-Software · Developed with love for equestrian sports",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ fun MainApp() {
|
|||||||
is AppScreen.Dashboard -> DashboardScreen(
|
is AppScreen.Dashboard -> DashboardScreen(
|
||||||
authTokenManager = authTokenManager,
|
authTokenManager = authTokenManager,
|
||||||
onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) },
|
onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) },
|
||||||
onCreateTournament = { navigationPort.navigateToScreen(AppScreen.CreateTournament) },
|
|
||||||
onLogout = {
|
onLogout = {
|
||||||
authTokenManager.clearToken()
|
authTokenManager.clearToken()
|
||||||
navigationPort.navigateToScreen(AppScreen.Landing)
|
navigationPort.navigateToScreen(AppScreen.Landing)
|
||||||
@@ -56,7 +55,6 @@ fun MainApp() {
|
|||||||
is AppScreen.Home -> DashboardScreen( // Route /home to Dashboard for now
|
is AppScreen.Home -> DashboardScreen( // Route /home to Dashboard for now
|
||||||
authTokenManager = authTokenManager,
|
authTokenManager = authTokenManager,
|
||||||
onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) },
|
onOpenPing = { navigationPort.navigateToScreen(AppScreen.Ping) },
|
||||||
onCreateTournament = { navigationPort.navigateToScreen(AppScreen.CreateTournament) },
|
|
||||||
onLogout = {
|
onLogout = {
|
||||||
authTokenManager.clearToken()
|
authTokenManager.clearToken()
|
||||||
navigationPort.navigateToScreen(AppScreen.Landing)
|
navigationPort.navigateToScreen(AppScreen.Landing)
|
||||||
@@ -358,7 +356,6 @@ private fun FeatureCard(number: String, title: String, body: String) {
|
|||||||
private fun DashboardScreen(
|
private fun DashboardScreen(
|
||||||
authTokenManager: AuthTokenManager,
|
authTokenManager: AuthTokenManager,
|
||||||
onOpenPing: () -> Unit,
|
onOpenPing: () -> Unit,
|
||||||
onCreateTournament: () -> Unit,
|
|
||||||
onLogout: () -> Unit
|
onLogout: () -> Unit
|
||||||
) {
|
) {
|
||||||
val authState by authTokenManager.authState.collectAsState()
|
val authState by authTokenManager.authState.collectAsState()
|
||||||
@@ -447,11 +444,42 @@ private fun DashboardScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedButton(
|
// DEIN NEUES KONZEPT: Download Desktop App statt "Neues Turnier anlegen" im Web
|
||||||
onClick = onCreateTournament,
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 16.dp)
|
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||||
) {
|
) {
|
||||||
Text("+ Neues Turnier anlegen")
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text(
|
||||||
|
"Master-Meldestelle Desktop App",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Für die Turnieranlage, den ZNS-Import und vollen Offline-Betrieb (USB-Sync) laden Sie bitte die Desktop-Anwendung herunter.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
|
Button(onClick = { /* TODO: Download Trigger */ }) {
|
||||||
|
Text("Download für Windows (.exe)")
|
||||||
|
}
|
||||||
|
// Status Anzeige
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
) {}
|
||||||
|
Text("Desktop App derzeit Offline", style = MaterialTheme.typography.labelMedium)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,13 +498,6 @@ private fun DashboardScreen(
|
|||||||
) {
|
) {
|
||||||
Text("Ping-Service (System Status)")
|
Text("Ping-Service (System Status)")
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedButton(
|
|
||||||
onClick = { /* TODO: ZNS Import */ },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text("ZNS-Daten Importieren")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,7 +530,7 @@ fun CreateTournamentScreen(
|
|||||||
Text("← Zurück")
|
Text("← Zurück")
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "Neues Turnier anlegen",
|
text = "Neues Turnier anlegen (Desktop Client)",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -523,20 +544,22 @@ fun CreateTournamentScreen(
|
|||||||
modifier = Modifier.padding(16.dp).fillMaxWidth(),
|
modifier = Modifier.padding(16.dp).fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
StepIndicator(step = 1, title = "Stammdaten", isActive = currentStep == 1, isCompleted = currentStep > 1)
|
StepIndicator(step = 1, title = "Transfer", isActive = currentStep == 1, isCompleted = currentStep > 1)
|
||||||
StepIndicator(step = 2, title = "Konfiguration", isActive = currentStep == 2, isCompleted = currentStep > 2)
|
StepIndicator(step = 2, title = "Stammdaten", isActive = currentStep == 2, isCompleted = currentStep > 2)
|
||||||
StepIndicator(step = 3, title = "Funktionäre", isActive = currentStep == 3, isCompleted = currentStep > 3)
|
StepIndicator(step = 3, title = "Konfiguration", isActive = currentStep == 3, isCompleted = currentStep > 3)
|
||||||
StepIndicator(step = 4, title = "Bewerbe", isActive = currentStep == 4, isCompleted = currentStep > 4)
|
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
|
// Wizard Content Area
|
||||||
Box(modifier = Modifier.weight(1f).padding(24.dp)) {
|
Box(modifier = Modifier.weight(1f).padding(24.dp)) {
|
||||||
when (currentStep) {
|
when (currentStep) {
|
||||||
1 -> TournamentStepStammdaten()
|
1 -> TournamentStepTransfer()
|
||||||
2 -> TournamentStepKonfiguration()
|
2 -> TournamentStepStammdaten()
|
||||||
3 -> TournamentStepFunktionaere()
|
3 -> TournamentStepKonfiguration()
|
||||||
4 -> TournamentStepBewerbe()
|
4 -> TournamentStepFunktionaere()
|
||||||
|
5 -> TournamentStepBewerbe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +575,7 @@ fun CreateTournamentScreen(
|
|||||||
Spacer(modifier = Modifier.width(1.dp)) // Empty space to keep "Weiter" on the right
|
Spacer(modifier = Modifier.width(1.dp)) // Empty space to keep "Weiter" on the right
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStep < 4) {
|
if (currentStep < 5) {
|
||||||
Button(onClick = { currentStep++ }) { Text("Weiter") }
|
Button(onClick = { currentStep++ }) { Text("Weiter") }
|
||||||
} else {
|
} else {
|
||||||
Button(onClick = onSave) { Text("Turnier speichern") }
|
Button(onClick = onSave) { Text("Turnier speichern") }
|
||||||
@@ -586,56 +609,167 @@ fun StepIndicator(step: Int, title: String, isActive: Boolean, isCompleted: Bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TournamentStepStammdaten() {
|
fun TournamentStepTransfer() {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) {
|
Column(verticalArrangement = Arrangement.spacedBy(24.dp), modifier = Modifier.fillMaxWidth(0.8f)) {
|
||||||
Text("Schritt 1: Turnier-Stammdaten", style = MaterialTheme.typography.headlineSmall)
|
Text("Schritt 1: Transfer & Initialisierung", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
Text(
|
||||||
OutlinedTextField(
|
"In diesem Schritt erschaffen wir eine separate Datenbank für dieses spezifische Turnier. " +
|
||||||
value = "",
|
"Diese Datenbank kann für den komplett isolierten Offline-Betrieb (z.B. am USB-Stick) auf andere Laptops übertragen werden.",
|
||||||
onValueChange = {},
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
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()) {
|
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth()) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = "",
|
||||||
onValueChange = {},
|
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") },
|
label = { Text("Datum von") },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = "25.04.2026",
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = { Text("Datum bis") },
|
label = { Text("Datum bis") },
|
||||||
modifier = Modifier.weight(1f)
|
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
|
@Composable
|
||||||
fun TournamentStepKonfiguration() {
|
fun TournamentStepKonfiguration() {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) {
|
||||||
Text("Schritt 2: Konfiguration", style = MaterialTheme.typography.headlineSmall)
|
Text("Schritt 3: Konfiguration", style = MaterialTheme.typography.headlineSmall)
|
||||||
Text("Austragungsplätze und Preisliste")
|
Text("Austragungsplätze und Preisliste")
|
||||||
|
|
||||||
// Placeholder for Austragungsplätze
|
// Placeholder for Austragungsplätze
|
||||||
@@ -643,8 +777,8 @@ fun TournamentStepKonfiguration() {
|
|||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("Austragungsplätze", fontWeight = FontWeight.Bold)
|
Text("Austragungsplätze", fontWeight = FontWeight.Bold)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = 8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = 8.dp)) {
|
||||||
FilterChip(selected = true, onClick = {}, label = { Text("Platz 1 (Sand)") })
|
FilterChip(selected = true, onClick = {}, label = { Text("Platz 1 (Sand/Vlies 45x65m)") })
|
||||||
FilterChip(selected = false, onClick = {}, label = { Text("Halle") })
|
FilterChip(selected = false, onClick = {}, label = { Text("Halle (Sand/Vlies 20x40m)") })
|
||||||
FilterChip(selected = false, onClick = {}, label = { Text("+ Hinzufügen") })
|
FilterChip(selected = false, onClick = {}, label = { Text("+ Hinzufügen") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,29 +789,36 @@ fun TournamentStepKonfiguration() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun TournamentStepFunktionaere() {
|
fun TournamentStepFunktionaere() {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth(0.6f)) {
|
||||||
Text("Schritt 3: Team & Funktionäre", style = MaterialTheme.typography.headlineSmall)
|
Text("Schritt 4: Team & Funktionäre", style = MaterialTheme.typography.headlineSmall)
|
||||||
Text("Zuweisung von Richtern und Parcoursbauern (aus ZNS)")
|
Text("Zuweisung von Richtern und Parcoursbauern (aus ZNS)")
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = "Rudi Kreupl",
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = { Text("Turnierbeauftragter (Suche nach Name oder ID)") },
|
label = { Text("Turnierbeauftragter (Suche nach Name oder ID)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = "",
|
value = "Helmut Riedler",
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = { Text("Richter (Suche nach Name oder ID)") },
|
label = { Text("Richter (Suche nach Name oder ID)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = "Kurt Reitetschlägerr",
|
||||||
|
onValueChange = {},
|
||||||
|
label = { Text("Parcoursbauchef") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TournamentStepBewerbe() {
|
fun TournamentStepBewerbe() {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize()) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize()) {
|
||||||
Text("Schritt 4: Bewerbe anlegen", style = MaterialTheme.typography.headlineSmall)
|
Text("Schritt 5: Bewerbe anlegen", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
// Left: List of Bewerbe
|
// Left: List of Bewerbe
|
||||||
@@ -690,6 +831,8 @@ fun TournamentStepBewerbe() {
|
|||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||||
Text("1: Pony Stilspringprüfung (60cm)", modifier = Modifier.padding(vertical = 4.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("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)
|
Text("...", modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import androidx.compose.ui.window.Window
|
|
||||||
import androidx.compose.ui.window.application
|
|
||||||
import androidx.compose.ui.window.WindowState
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import at.mocode.frontend.core.network.networkModule
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.WindowState
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
import at.mocode.frontend.core.auth.di.authModule
|
import at.mocode.frontend.core.auth.di.authModule
|
||||||
import at.mocode.frontend.core.sync.di.syncModule
|
|
||||||
import at.mocode.ping.feature.di.pingFeatureModule
|
|
||||||
import at.mocode.frontend.core.localdb.AppDatabase
|
import at.mocode.frontend.core.localdb.AppDatabase
|
||||||
import at.mocode.frontend.core.localdb.DatabaseProvider
|
import at.mocode.frontend.core.localdb.DatabaseProvider
|
||||||
import at.mocode.frontend.core.localdb.localDbModule
|
import at.mocode.frontend.core.localdb.localDbModule
|
||||||
import navigation.navigationModule
|
import at.mocode.frontend.core.network.networkModule
|
||||||
|
import at.mocode.frontend.core.sync.di.syncModule
|
||||||
|
import at.mocode.ping.feature.di.pingFeatureModule
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koin.core.context.startKoin
|
import navigation.navigationModule
|
||||||
import org.koin.core.context.loadKoinModules
|
import org.koin.core.context.loadKoinModules
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
@@ -40,7 +40,7 @@ fun main() = application {
|
|||||||
}
|
}
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = ::exitApplication,
|
||||||
title = "Meldestelle - Desktop Development",
|
title = "Equest-Events Master Desktop",
|
||||||
state = WindowState(width = 1200.dp, height = 800.dp)
|
state = WindowState(width = 1200.dp, height = 800.dp)
|
||||||
) {
|
) {
|
||||||
MainApp()
|
MainApp()
|
||||||
|
|||||||
Reference in New Issue
Block a user