refactor(desktop): V2-Suffixe entfernt und VeranstaltungKomponenten modularisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
parent
0128f98164
commit
3949ab21db
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Session Journal: 2026-04-17 - Aufräumarbeiten & Konsolidierung
|
||||||
|
|
||||||
|
## 🎯 Ziele der Session
|
||||||
|
|
||||||
|
1. **V2-Cleanup:** Entfernung aller `V2`-Suffixe aus dem Codebase (Modelle, Stores, Wizards), um eine konsolidierte "
|
||||||
|
Source of Truth" zu schaffen.
|
||||||
|
2. **Refactoring:** Zerlegung der massiven `VeranstaltungKonfig`-Komponente in wartbare Teil-Module.
|
||||||
|
3. **Duplikat-Entfernung:** Zentralisierung von UI-Logik (DatePicker, Validierung) zur Reduzierung von Code-Duplikaten.
|
||||||
|
|
||||||
|
## 🛠️ Durchgeführte Änderungen
|
||||||
|
|
||||||
|
### 🧹 1. Konsolidierung der Benamung (V2-Entfernung)
|
||||||
|
|
||||||
|
* **Änderungen:**
|
||||||
|
* `VeranstaltungKonfigV2` -> `VeranstaltungKonfig`
|
||||||
|
* `VeranstaltungV2` -> `Veranstaltung`
|
||||||
|
* `TurnierV2` -> `Turnier`
|
||||||
|
* `StoreV2` -> `Store`
|
||||||
|
* `TurnierStoreV2` -> `TurnierStore`
|
||||||
|
* `TurnierWizardV2` -> `TurnierWizard`
|
||||||
|
* **Grund:** Umsetzung der Vereinbarung, nur noch eine "echte" Version zu pflegen und Altlasten aus Migrationsphasen zu
|
||||||
|
entfernen. Alle Referenzen im gesamten Projekt (`DesktopMainLayout.kt`, `ManagementScreens.kt`, `main.kt`) wurden
|
||||||
|
erfolgreich aktualisiert.
|
||||||
|
|
||||||
|
### 🏗️ 2. Refactoring `VeranstaltungScreens.kt`
|
||||||
|
|
||||||
|
* **Extraktion:** Die Wizard-Schritte wurden in eigenständige Composable-Funktionen ausgelagert:
|
||||||
|
* `Step1Veranstalter`: Auswahl aus ZNS/Lokal-Bestand.
|
||||||
|
* `Step2Basisdaten`: Titel, Zeitraum, Ort, Disziplinen.
|
||||||
|
* `Step3Details`: Logo, Sponsoren, Bewerbs-Management.
|
||||||
|
* **Zentralisierung:**
|
||||||
|
* Neue Komponente `AppDatePickerDialog` zur Vermeidung von dreifach redundantem Dialog-Code.
|
||||||
|
* Konsolidierte Validierungslogik für den Veranstaltungszeitraum.
|
||||||
|
|
||||||
|
### 🏷️ 3. Fehlerbehebung & Qualitätssicherung
|
||||||
|
|
||||||
|
* **Syntax-Fix:** Korrektur von Klammerfehlern, die während des Refactorings in der großen `VeranstaltungScreens.kt`
|
||||||
|
entstanden sind.
|
||||||
|
* **Linting:** Erfolgreiche Validierung der Dateien `VeranstaltungScreens.kt`, `Stores.kt` und `DesktopMainLayout.kt`.
|
||||||
|
|
||||||
|
## ✅ Ergebnis & Status
|
||||||
|
|
||||||
|
* Der Code ist nun wesentlich modularer und besser lesbar.
|
||||||
|
* Die Benamung ist konsistent ohne verwirrende Versions-Suffixe.
|
||||||
|
* Redundante Logik-Blöcke (besonders beim Datum-Handling) wurden eliminiert.
|
||||||
|
|
||||||
|
---
|
||||||
|
**🏗️ [Lead Architect]** & **🧹 [Curator]**
|
||||||
|
Datum: 17. April 2026 | Status: Abgeschlossen
|
||||||
|
|
@ -50,11 +50,22 @@
|
||||||
* Anzeige der letzten Sync-Version (z.B. `ZNS: V12` oder `ZNS: Kein Sync`).
|
* Anzeige der letzten Sync-Version (z.B. `ZNS: V12` oder `ZNS: Kein Sync`).
|
||||||
* Farbliche Kennzeichnung (Grün/Gelb/Rot) je nach Synchronisationsstand.
|
* Farbliche Kennzeichnung (Grün/Gelb/Rot) je nach Synchronisationsstand.
|
||||||
|
|
||||||
|
### 🏗️ 5. Fachlich: Disziplinen & Bewerbe (Schritt 2 & 3)
|
||||||
|
|
||||||
|
* **Datei:** `frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt`
|
||||||
|
* **Änderung:**
|
||||||
|
* **Schritt 2:** Felder für PLZ und Disziplin-Auswahl (Springen, Dressur, etc.) hinzugefügt.
|
||||||
|
* **Schritt 3:** Komplettes Bewerbs-Management implementiert. User können Prüfungsnummern, Klassen und Bezeichnungen
|
||||||
|
erfassen.
|
||||||
|
* **Validierung:** Der Wizard lässt sich erst finalisieren, wenn mindestens ein Bewerb angelegt wurde.
|
||||||
|
* **Grund:** Vorbereitung der Datenstruktur für den OEPS-Export und Verbesserung der fachlichen Abdeckung im Wizard.
|
||||||
|
|
||||||
## ✅ Ergebnis & Status
|
## ✅ Ergebnis & Status
|
||||||
|
|
||||||
* Das Consul-Dashboard sollte nun einen stabilen "Grün"-Status für den `masterdata-service` anzeigen.
|
* Das Consul-Dashboard sollte nun einen stabilen "Grün"-Status für den `masterdata-service` anzeigen.
|
||||||
* Der Desktop-Wizard leitet den User fachlich korrekt durch die Turnier-Anlage.
|
* Der Desktop-Wizard leitet den User fachlich korrekt durch die Turnier-Anlage.
|
||||||
* Der User hat jederzeit volle Transparenz über den Stand seiner lokalen ZNS-Daten.
|
* Der User hat jederzeit volle Transparenz über den Stand seiner lokalen ZNS-Daten.
|
||||||
|
* Die Erfassung von Bewerben legt den Grundstein für die spätere Nennungs- und Ergebnisverwaltung.
|
||||||
|
|
||||||
---
|
---
|
||||||
**🏗️ [Lead Architect]** & **🧹 [Curator]**
|
**🏗️ [Lead Architect]** & **🧹 [Curator]**
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@ import at.mocode.frontend.core.localdb.localDbModule
|
||||||
import at.mocode.frontend.core.network.networkModule
|
import at.mocode.frontend.core.network.networkModule
|
||||||
import at.mocode.frontend.core.sync.di.syncModule
|
import at.mocode.frontend.core.sync.di.syncModule
|
||||||
import at.mocode.frontend.features.billing.di.billingModule
|
import at.mocode.frontend.features.billing.di.billingModule
|
||||||
import at.mocode.frontend.features.profile.di.profileModule
|
|
||||||
import at.mocode.frontend.features.verein.di.vereinFeatureModule
|
|
||||||
import at.mocode.frontend.features.nennung.di.nennungFeatureModule
|
import at.mocode.frontend.features.nennung.di.nennungFeatureModule
|
||||||
import at.mocode.frontend.features.pferde.di.pferdeModule
|
import at.mocode.frontend.features.pferde.di.pferdeModule
|
||||||
|
import at.mocode.frontend.features.profile.di.profileModule
|
||||||
import at.mocode.frontend.features.reiter.di.reiterModule
|
import at.mocode.frontend.features.reiter.di.reiterModule
|
||||||
import at.mocode.turnier.feature.di.turnierFeatureModule
|
import at.mocode.frontend.features.verein.di.vereinFeatureModule
|
||||||
import at.mocode.ping.feature.di.pingFeatureModule
|
import at.mocode.ping.feature.di.pingFeatureModule
|
||||||
|
import at.mocode.turnier.feature.di.turnierFeatureModule
|
||||||
import at.mocode.zns.feature.di.znsImportModule
|
import at.mocode.zns.feature.di.znsImportModule
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koin.core.context.GlobalContext
|
import org.koin.core.context.GlobalContext
|
||||||
|
|
@ -48,7 +48,7 @@ fun main() = application {
|
||||||
}
|
}
|
||||||
println("[DesktopApp] KOIN initialisiert")
|
println("[DesktopApp] KOIN initialisiert")
|
||||||
// Testdaten für Prototyp laden
|
// Testdaten für Prototyp laden
|
||||||
at.mocode.desktop.v2.StoreV2.seed()
|
at.mocode.desktop.v2.Store.seed()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("[DesktopApp] Koin-Warnung: ${e.message}")
|
println("[DesktopApp] Koin-Warnung: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -660,7 +660,7 @@ private fun DesktopContentArea(
|
||||||
is AppScreen.VeranstaltungKonfig -> {
|
is AppScreen.VeranstaltungKonfig -> {
|
||||||
val vId = currentScreen.veranstalterId
|
val vId = currentScreen.veranstalterId
|
||||||
// Falls vId == 0, kommen wir aus der Gesamtübersicht und wählen erst im Wizard
|
// Falls vId == 0, kommen wir aus der Gesamtübersicht und wählen erst im Wizard
|
||||||
at.mocode.desktop.v2.VeranstaltungKonfigV2(
|
at.mocode.desktop.v2.VeranstaltungKonfig(
|
||||||
veranstalterId = vId,
|
veranstalterId = vId,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onSaved = { evtId, finalVId -> onNavigate(AppScreen.VeranstaltungProfil(finalVId, evtId)) },
|
onSaved = { evtId, finalVId -> onNavigate(AppScreen.VeranstaltungProfil(finalVId, evtId)) },
|
||||||
|
|
@ -671,12 +671,12 @@ private fun DesktopContentArea(
|
||||||
is AppScreen.VeranstaltungProfil -> {
|
is AppScreen.VeranstaltungProfil -> {
|
||||||
val vId = currentScreen.veranstalterId
|
val vId = currentScreen.veranstalterId
|
||||||
val evtId = currentScreen.veranstaltungId
|
val evtId = currentScreen.veranstaltungId
|
||||||
if (at.mocode.desktop.v2.StoreV2.vereine.none { it.id == vId }) {
|
if (at.mocode.desktop.v2.Store.vereine.none { it.id == vId }) {
|
||||||
InvalidContextNotice(
|
InvalidContextNotice(
|
||||||
message = "Veranstalter (ID=$vId) nicht gefunden.",
|
message = "Veranstalter (ID=$vId) nicht gefunden.",
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
)
|
)
|
||||||
} else if (at.mocode.desktop.v2.StoreV2.eventsFor(vId).none { it.id == evtId }) {
|
} else if (at.mocode.desktop.v2.Store.eventsFor(vId).none { it.id == evtId }) {
|
||||||
InvalidContextNotice(
|
InvalidContextNotice(
|
||||||
message = "Veranstaltung (ID=$evtId) gehört nicht zu Veranstalter #$vId.",
|
message = "Veranstaltung (ID=$evtId) gehört nicht zu Veranstalter #$vId.",
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
|
|
@ -687,17 +687,17 @@ private fun DesktopContentArea(
|
||||||
veranstaltungId = evtId,
|
veranstaltungId = evtId,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onTurnierNeu = {
|
onTurnierNeu = {
|
||||||
val veranstaltung = at.mocode.desktop.v2.StoreV2.eventsFor(vId).firstOrNull { it.id == evtId }
|
val veranstaltung = at.mocode.desktop.v2.Store.eventsFor(vId).firstOrNull { it.id == evtId }
|
||||||
val list = at.mocode.desktop.v2.TurnierStoreV2.list(evtId)
|
val list = at.mocode.desktop.v2.TurnierStore.list(evtId)
|
||||||
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
||||||
val draft = at.mocode.desktop.v2.TurnierV2(
|
val draft = at.mocode.desktop.v2.Turnier(
|
||||||
id = newId,
|
id = newId,
|
||||||
veranstaltungId = evtId,
|
veranstaltungId = evtId,
|
||||||
turnierNr = 0,
|
turnierNr = 0,
|
||||||
datumVon = veranstaltung?.datumVon ?: "",
|
datumVon = veranstaltung?.datumVon ?: "",
|
||||||
datumBis = veranstaltung?.datumBis,
|
datumBis = veranstaltung?.datumBis,
|
||||||
)
|
)
|
||||||
at.mocode.desktop.v2.TurnierStoreV2.add(evtId, draft)
|
at.mocode.desktop.v2.TurnierStore.add(evtId, draft)
|
||||||
onNavigate(AppScreen.TurnierDetail(evtId, newId))
|
onNavigate(AppScreen.TurnierDetail(evtId, newId))
|
||||||
},
|
},
|
||||||
onTurnierOpen = { tId -> onNavigate(AppScreen.TurnierDetail(evtId, tId)) },
|
onTurnierOpen = { tId -> onNavigate(AppScreen.TurnierDetail(evtId, tId)) },
|
||||||
|
|
@ -711,21 +711,21 @@ private fun DesktopContentArea(
|
||||||
veranstaltungId = currentScreen.id,
|
veranstaltungId = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onTurnierNeu = {
|
onTurnierNeu = {
|
||||||
val v = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { vv ->
|
val v = at.mocode.desktop.v2.Store.vereine.firstOrNull { vv ->
|
||||||
at.mocode.desktop.v2.StoreV2.eventsFor(vv.id).any { it.id == currentScreen.id }
|
at.mocode.desktop.v2.Store.eventsFor(vv.id).any { it.id == currentScreen.id }
|
||||||
}
|
}
|
||||||
val veranstaltung =
|
val veranstaltung =
|
||||||
v?.let { at.mocode.desktop.v2.StoreV2.eventsFor(it.id).firstOrNull { e -> e.id == currentScreen.id } }
|
v?.let { at.mocode.desktop.v2.Store.eventsFor(it.id).firstOrNull { e -> e.id == currentScreen.id } }
|
||||||
val list = at.mocode.desktop.v2.TurnierStoreV2.list(currentScreen.id)
|
val list = at.mocode.desktop.v2.TurnierStore.list(currentScreen.id)
|
||||||
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
||||||
val draft = at.mocode.desktop.v2.TurnierV2(
|
val draft = at.mocode.desktop.v2.Turnier(
|
||||||
id = newId,
|
id = newId,
|
||||||
veranstaltungId = currentScreen.id,
|
veranstaltungId = currentScreen.id,
|
||||||
turnierNr = 0,
|
turnierNr = 0,
|
||||||
datumVon = veranstaltung?.datumVon ?: "",
|
datumVon = veranstaltung?.datumVon ?: "",
|
||||||
datumBis = veranstaltung?.datumBis,
|
datumBis = veranstaltung?.datumBis,
|
||||||
)
|
)
|
||||||
at.mocode.desktop.v2.TurnierStoreV2.add(currentScreen.id, draft)
|
at.mocode.desktop.v2.TurnierStore.add(currentScreen.id, draft)
|
||||||
onNavigate(AppScreen.TurnierDetail(currentScreen.id, newId))
|
onNavigate(AppScreen.TurnierDetail(currentScreen.id, newId))
|
||||||
},
|
},
|
||||||
onTurnierOeffnen = { tid -> onNavigate(AppScreen.TurnierDetail(currentScreen.id, tid)) },
|
onTurnierOeffnen = { tid -> onNavigate(AppScreen.TurnierDetail(currentScreen.id, tid)) },
|
||||||
|
|
@ -739,8 +739,8 @@ private fun DesktopContentArea(
|
||||||
// Turnier-Screens
|
// Turnier-Screens
|
||||||
is AppScreen.TurnierDetail -> {
|
is AppScreen.TurnierDetail -> {
|
||||||
val evtId = currentScreen.veranstaltungId
|
val evtId = currentScreen.veranstaltungId
|
||||||
val parent = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { v ->
|
val parent = at.mocode.desktop.v2.Store.vereine.firstOrNull { v ->
|
||||||
at.mocode.desktop.v2.StoreV2.eventsFor(v.id).any { it.id == evtId }
|
at.mocode.desktop.v2.Store.eventsFor(v.id).any { it.id == evtId }
|
||||||
}
|
}
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
InvalidContextNotice(
|
InvalidContextNotice(
|
||||||
|
|
@ -748,7 +748,7 @@ private fun DesktopContentArea(
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val veranstaltung = at.mocode.desktop.v2.StoreV2.eventsFor(parent.id).firstOrNull { it.id == evtId }
|
val veranstaltung = at.mocode.desktop.v2.Store.eventsFor(parent.id).firstOrNull { it.id == evtId }
|
||||||
val blCode = parent.oepsNummer.split("-").getOrNull(1) ?: ""
|
val blCode = parent.oepsNummer.split("-").getOrNull(1) ?: ""
|
||||||
val bundesland = mapOepsToBundesland(blCode)
|
val bundesland = mapOepsToBundesland(blCode)
|
||||||
TurnierDetailScreen(
|
TurnierDetailScreen(
|
||||||
|
|
@ -769,8 +769,8 @@ private fun DesktopContentArea(
|
||||||
is AppScreen.TurnierNeu -> {
|
is AppScreen.TurnierNeu -> {
|
||||||
val evtId = currentScreen.veranstaltungId
|
val evtId = currentScreen.veranstaltungId
|
||||||
// V2: Wir erlauben Turnier-Nr nur, wenn die Veranstaltung im V2-Store existiert
|
// V2: Wir erlauben Turnier-Nr nur, wenn die Veranstaltung im V2-Store existiert
|
||||||
val parent = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { v ->
|
val parent = at.mocode.desktop.v2.Store.vereine.firstOrNull { v ->
|
||||||
at.mocode.desktop.v2.StoreV2.eventsFor(v.id).any { it.id == evtId }
|
at.mocode.desktop.v2.Store.eventsFor(v.id).any { it.id == evtId }
|
||||||
}
|
}
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
InvalidContextNotice(
|
InvalidContextNotice(
|
||||||
|
|
@ -778,7 +778,7 @@ private fun DesktopContentArea(
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
at.mocode.desktop.v2.TurnierWizardV2(
|
at.mocode.desktop.v2.TurnierWizard(
|
||||||
veranstalterId = parent.id,
|
veranstalterId = parent.id,
|
||||||
veranstaltungId = evtId,
|
veranstaltungId = evtId,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -140,7 +142,7 @@ data class TableColumn<T>(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PferdeVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
fun PferdeVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
val pferde = StoreV2.pferde
|
val pferde = Store.pferde
|
||||||
var filter by remember { mutableStateOf("") }
|
var filter by remember { mutableStateOf("") }
|
||||||
val filteredItems = if (filter.isEmpty()) pferde else pferde.filter {
|
val filteredItems = if (filter.isEmpty()) pferde else pferde.filter {
|
||||||
it.name.contains(filter, ignoreCase = true) || it.feiId?.contains(filter, ignoreCase = true) == true
|
it.name.contains(filter, ignoreCase = true) || it.feiId?.contains(filter, ignoreCase = true) == true
|
||||||
|
|
@ -162,14 +164,14 @@ fun PferdeVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onNew = { /* CRUD Logik */ },
|
onNew = { /* CRUD Logik */ },
|
||||||
onEdit = { onEdit(it.id) },
|
onEdit = { onEdit(it.id) },
|
||||||
onDelete = { StoreV2.pferde.remove(it) },
|
onDelete = { Store.pferde.remove(it) },
|
||||||
onSearch = { filter = it }
|
onSearch = { filter = it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReiterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
fun ReiterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
val reiter = StoreV2.reiter
|
val reiter = Store.reiter
|
||||||
var filter by remember { mutableStateOf("") }
|
var filter by remember { mutableStateOf("") }
|
||||||
val filteredItems = if (filter.isEmpty()) reiter else reiter.filter {
|
val filteredItems = if (filter.isEmpty()) reiter else reiter.filter {
|
||||||
it.vorname.contains(filter, ignoreCase = true) || it.nachname.contains(
|
it.vorname.contains(filter, ignoreCase = true) || it.nachname.contains(
|
||||||
|
|
@ -192,14 +194,14 @@ fun ReiterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onNew = { },
|
onNew = { },
|
||||||
onEdit = { onEdit(it.id) },
|
onEdit = { onEdit(it.id) },
|
||||||
onDelete = { StoreV2.reiter.remove(it) },
|
onDelete = { Store.reiter.remove(it) },
|
||||||
onSearch = { filter = it }
|
onSearch = { filter = it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VereinVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
fun VereinVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
val vereine = StoreV2.vereine
|
val vereine = Store.vereine
|
||||||
var filter by remember { mutableStateOf("") }
|
var filter by remember { mutableStateOf("") }
|
||||||
val filteredItems = if (filter.isEmpty()) vereine else vereine.filter {
|
val filteredItems = if (filter.isEmpty()) vereine else vereine.filter {
|
||||||
it.name.contains(filter, ignoreCase = true) || it.oepsNummer.contains(filter, ignoreCase = true)
|
it.name.contains(filter, ignoreCase = true) || it.oepsNummer.contains(filter, ignoreCase = true)
|
||||||
|
|
@ -218,14 +220,14 @@ fun VereinVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onNew = { },
|
onNew = { },
|
||||||
onEdit = { onEdit(it.id) },
|
onEdit = { onEdit(it.id) },
|
||||||
onDelete = { StoreV2.vereine.remove(it) },
|
onDelete = { Store.vereine.remove(it) },
|
||||||
onSearch = { filter = it }
|
onSearch = { filter = it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
val funktionaere = StoreV2.funktionaere
|
val funktionaere = Store.funktionaere
|
||||||
var filter by remember { mutableStateOf("") }
|
var filter by remember { mutableStateOf("") }
|
||||||
val filteredItems = if (filter.isEmpty()) funktionaere else funktionaere.filter {
|
val filteredItems = if (filter.isEmpty()) funktionaere else funktionaere.filter {
|
||||||
it.vorname.contains(filter, ignoreCase = true) || it.nachname.contains(filter, ignoreCase = true)
|
it.vorname.contains(filter, ignoreCase = true) || it.nachname.contains(filter, ignoreCase = true)
|
||||||
|
|
@ -244,7 +246,7 @@ fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onNew = { },
|
onNew = { },
|
||||||
onEdit = { onEdit(it.id) },
|
onEdit = { onEdit(it.id) },
|
||||||
onDelete = { StoreV2.funktionaere.remove(it) },
|
onDelete = { Store.funktionaere.remove(it) },
|
||||||
onSearch = { filter = it }
|
onSearch = { filter = it }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +255,7 @@ fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onNew: () -> Unit, onEdit: (Long) -> Unit) {
|
fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onNew: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
// Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten
|
// Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten
|
||||||
// Wir nutzen hier die 'vereine' Liste aus dem Store.
|
// Wir nutzen hier die 'vereine' Liste aus dem Store.
|
||||||
val vereine = StoreV2.vereine
|
val vereine = Store.vereine
|
||||||
var filter by remember { mutableStateOf("") }
|
var filter by remember { mutableStateOf("") }
|
||||||
val filteredItems = if (filter.isEmpty()) vereine else vereine.filter {
|
val filteredItems = if (filter.isEmpty()) vereine else vereine.filter {
|
||||||
it.name.contains(filter, ignoreCase = true) || it.oepsNummer.contains(filter, ignoreCase = true)
|
it.name.contains(filter, ignoreCase = true) || it.oepsNummer.contains(filter, ignoreCase = true)
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ fun OnboardingScreenPreview() {
|
||||||
@Composable
|
@Composable
|
||||||
fun PferdProfilV2(id: Long, onBack: () -> Unit) {
|
fun PferdProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val pferd = remember(id) { StoreV2.pferde.firstOrNull { it.id == id } }
|
val pferd = remember(id) { Store.pferde.firstOrNull { it.id == id } }
|
||||||
if (pferd == null) {
|
if (pferd == null) {
|
||||||
Text("Pferd nicht gefunden"); return@DesktopThemeV2
|
Text("Pferd nicht gefunden"); return@DesktopThemeV2
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +506,7 @@ fun PferdProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun ReiterProfilV2(id: Long, onBack: () -> Unit) {
|
fun ReiterProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val r = remember(id) { StoreV2.reiter.firstOrNull { it.id == id } }
|
val r = remember(id) { Store.reiter.firstOrNull { it.id == id } }
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
Text("Reiter nicht gefunden"); return@DesktopThemeV2
|
Text("Reiter nicht gefunden"); return@DesktopThemeV2
|
||||||
}
|
}
|
||||||
|
|
@ -590,7 +590,7 @@ fun ReiterProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun VereinProfilV2(id: Long, onBack: () -> Unit) {
|
fun VereinProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val v = remember(id) { StoreV2.vereine.firstOrNull { it.id == id } }
|
val v = remember(id) { Store.vereine.firstOrNull { it.id == id } }
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
Text("Verein nicht gefunden"); return@DesktopThemeV2
|
Text("Verein nicht gefunden"); return@DesktopThemeV2
|
||||||
}
|
}
|
||||||
|
|
@ -681,7 +681,7 @@ fun VereinProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun FunktionaerProfilV2(id: Long, onBack: () -> Unit) {
|
fun FunktionaerProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val f = remember(id) { StoreV2.funktionaere.firstOrNull { it.id == id } }
|
val f = remember(id) { Store.funktionaere.firstOrNull { it.id == id } }
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
Text("Funktionär nicht gefunden"); return@DesktopThemeV2
|
Text("Funktionär nicht gefunden"); return@DesktopThemeV2
|
||||||
}
|
}
|
||||||
|
|
@ -782,7 +782,7 @@ fun VeranstalterAuswahlV2(
|
||||||
var selectedId by remember { mutableStateOf<Long?>(null) }
|
var selectedId by remember { mutableStateOf<Long?>(null) }
|
||||||
|
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(StoreV2.vereine) { v ->
|
items(Store.vereine) { v ->
|
||||||
val sel = selectedId == v.id
|
val sel = selectedId == v.id
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -822,14 +822,14 @@ fun VeranstalterDetailV2(
|
||||||
Icons.AutoMirrored.Filled.ArrowBack,
|
Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Zurück",
|
contentDescription = "Zurück",
|
||||||
modifier = Modifier.clickable { onBack() })
|
modifier = Modifier.clickable { onBack() })
|
||||||
val verein = StoreV2.vereine.firstOrNull { it.id == veranstalterId }
|
val verein = Store.vereine.firstOrNull { it.id == veranstalterId }
|
||||||
Text(verein?.name ?: "Veranstalter", style = MaterialTheme.typography.titleLarge)
|
Text(verein?.name ?: "Veranstalter", style = MaterialTheme.typography.titleLarge)
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Veranstalter Vorschau-Karte mit Bearbeiten-Dialog
|
// Veranstalter Vorschau-Karte mit Bearbeiten-Dialog
|
||||||
val verein = remember(veranstalterId) { StoreV2.vereine.firstOrNull { it.id == veranstalterId } }
|
val verein = remember(veranstalterId) { Store.vereine.firstOrNull { it.id == veranstalterId } }
|
||||||
if (verein != null) {
|
if (verein != null) {
|
||||||
var editOpen by remember { mutableStateOf(false) }
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
Card(Modifier.fillMaxWidth()) {
|
Card(Modifier.fillMaxWidth()) {
|
||||||
|
|
@ -953,7 +953,7 @@ fun VeranstalterDetailV2(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val events = StoreV2.eventsFor(veranstalterId)
|
val events = Store.eventsFor(veranstalterId)
|
||||||
// Filter-/Suchmaske
|
// Filter-/Suchmaske
|
||||||
var search by remember { mutableStateOf("") }
|
var search by remember { mutableStateOf("") }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
|
|
@ -992,7 +992,7 @@ fun VeranstalterDetailV2(
|
||||||
onDismissRequest = { confirm = false },
|
onDismissRequest = { confirm = false },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
StoreV2.removeEvent(veranstalterId, evt.id)
|
Store.removeEvent(veranstalterId, evt.id)
|
||||||
confirm = false
|
confirm = false
|
||||||
}) { Text("Löschen") }
|
}) { Text("Löschen") }
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ data class Funktionaer(
|
||||||
var istAktiv: Boolean = true,
|
var istAktiv: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class VeranstaltungV2(
|
data class Veranstaltung(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
var veranstalterId: Long,
|
var veranstalterId: Long,
|
||||||
var titel: String,
|
var titel: String,
|
||||||
|
|
@ -85,7 +85,7 @@ data class VeranstaltungV2(
|
||||||
var sponsoren: SnapshotStateList<String> = mutableStateListOf(),
|
var sponsoren: SnapshotStateList<String> = mutableStateListOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
object StoreV2 {
|
object Store {
|
||||||
val pferde: SnapshotStateList<Pferd> = mutableStateListOf(
|
val pferde: SnapshotStateList<Pferd> = mutableStateListOf(
|
||||||
Pferd(
|
Pferd(
|
||||||
id = 1,
|
id = 1,
|
||||||
|
|
@ -268,7 +268,7 @@ object StoreV2 {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
private val veranstaltungen: MutableMap<Long, SnapshotStateList<VeranstaltungV2>> = mutableMapOf()
|
private val veranstaltungen: MutableMap<Long, SnapshotStateList<Veranstaltung>> = mutableMapOf()
|
||||||
|
|
||||||
fun seed() {
|
fun seed() {
|
||||||
// Falls bereits Daten da sind (außer den statischen Vereinen), nichts tun
|
// Falls bereits Daten da sind (außer den statischen Vereinen), nichts tun
|
||||||
|
|
@ -277,7 +277,7 @@ object StoreV2 {
|
||||||
// 1. Neumarkt April 2026 (ID 100)
|
// 1. Neumarkt April 2026 (ID 100)
|
||||||
val neumarktId = 100L
|
val neumarktId = 100L
|
||||||
addEventFirst(
|
addEventFirst(
|
||||||
1, VeranstaltungV2(
|
1, Veranstaltung(
|
||||||
id = neumarktId,
|
id = neumarktId,
|
||||||
veranstalterId = 1,
|
veranstalterId = 1,
|
||||||
titel = "CSN-B* Neumarkt am Wallersee",
|
titel = "CSN-B* Neumarkt am Wallersee",
|
||||||
|
|
@ -289,17 +289,17 @@ object StoreV2 {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
TurnierStoreV2.add(
|
TurnierStore.add(
|
||||||
neumarktId,
|
neumarktId,
|
||||||
TurnierV2(101, neumarktId, 26128, datumVon = "2026-04-24", datumBis = "2026-04-26", znsDataLoaded = true).apply {
|
Turnier(101, neumarktId, 26128, datumVon = "2026-04-24", datumBis = "2026-04-26", znsDataLoaded = true).apply {
|
||||||
titel = "Springturnier Neumarkt"
|
titel = "Springturnier Neumarkt"
|
||||||
kategorie.add("CSN-B*")
|
kategorie.add("CSN-B*")
|
||||||
kategorie.add("CSNP-B")
|
kategorie.add("CSNP-B")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
TurnierStoreV2.add(
|
TurnierStore.add(
|
||||||
neumarktId,
|
neumarktId,
|
||||||
TurnierV2(102, neumarktId, 26129, datumVon = "2026-04-24", datumBis = "2026-04-26", znsDataLoaded = true).apply {
|
Turnier(102, neumarktId, 26129, datumVon = "2026-04-24", datumBis = "2026-04-26", znsDataLoaded = true).apply {
|
||||||
titel = "Dressurturnier Neumarkt"
|
titel = "Dressurturnier Neumarkt"
|
||||||
kategorie.add("CDN-B")
|
kategorie.add("CDN-B")
|
||||||
kategorie.add("CDNP-B")
|
kategorie.add("CDNP-B")
|
||||||
|
|
@ -309,7 +309,7 @@ object StoreV2 {
|
||||||
// 2. Linz 2026 (ID 200)
|
// 2. Linz 2026 (ID 200)
|
||||||
val linzId = 200L
|
val linzId = 200L
|
||||||
addEventFirst(
|
addEventFirst(
|
||||||
2, VeranstaltungV2(
|
2, Veranstaltung(
|
||||||
id = linzId,
|
id = linzId,
|
||||||
veranstalterId = 2,
|
veranstalterId = 2,
|
||||||
titel = "Linzer Pferdefestival",
|
titel = "Linzer Pferdefestival",
|
||||||
|
|
@ -319,15 +319,15 @@ object StoreV2 {
|
||||||
beschreibung = "Große Reitsport-Veranstaltung am Ebelsberger Schlosspark."
|
beschreibung = "Große Reitsport-Veranstaltung am Ebelsberger Schlosspark."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
TurnierStoreV2.add(
|
TurnierStore.add(
|
||||||
linzId,
|
linzId,
|
||||||
TurnierV2(201, linzId, 26500, datumVon = "2026-05-20", datumBis = "2026-05-24", znsDataLoaded = true).apply {
|
Turnier(201, linzId, 26500, datumVon = "2026-05-20", datumBis = "2026-05-24", znsDataLoaded = true).apply {
|
||||||
kategorie.add("CSN-B*")
|
kategorie.add("CSN-B*")
|
||||||
})
|
})
|
||||||
|
|
||||||
// 3. Ein historisches Event (ID 300)
|
// 3. Ein historisches Event (ID 300)
|
||||||
addEventFirst(
|
addEventFirst(
|
||||||
1, VeranstaltungV2(
|
1, Veranstaltung(
|
||||||
id = 300L,
|
id = 300L,
|
||||||
veranstalterId = 1,
|
veranstalterId = 1,
|
||||||
titel = "Herbst-Turnier 2025",
|
titel = "Herbst-Turnier 2025",
|
||||||
|
|
@ -338,10 +338,10 @@ object StoreV2 {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun eventsFor(vereinId: Long): SnapshotStateList<VeranstaltungV2> =
|
fun eventsFor(vereinId: Long): SnapshotStateList<Veranstaltung> =
|
||||||
veranstaltungen.getOrPut(vereinId) { mutableStateListOf() }
|
veranstaltungen.getOrPut(vereinId) { mutableStateListOf() }
|
||||||
|
|
||||||
fun addEventFirst(vereinId: Long, v: VeranstaltungV2) {
|
fun addEventFirst(vereinId: Long, v: Veranstaltung) {
|
||||||
eventsFor(vereinId).add(0, v)
|
eventsFor(vereinId).add(0, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,5 +351,5 @@ object StoreV2 {
|
||||||
if (idx >= 0) list.removeAt(idx)
|
if (idx >= 0) list.removeAt(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allEvents(): List<VeranstaltungV2> = veranstaltungen.values.flatten()
|
fun allEvents(): List<Veranstaltung> = veranstaltungen.values.flatten()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ fun VeranstaltungVerwaltung(
|
||||||
onNavigateToZnsImport: () -> Unit
|
onNavigateToZnsImport: () -> Unit
|
||||||
) {
|
) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val allVeranstaltungen = remember { StoreV2.allEvents() }
|
val allVeranstaltungen = remember { Store.allEvents() }
|
||||||
val vereine = StoreV2.vereine
|
val vereine = Store.vereine
|
||||||
|
|
||||||
var searchQuery by remember { mutableStateOf("") }
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
var selectedStatus by remember { mutableStateOf<String?>(null) }
|
var selectedStatus by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
@ -223,7 +223,7 @@ fun VeranstalterAnlegenWizard(
|
||||||
|
|
||||||
val results = remember(searchQuery) {
|
val results = remember(searchQuery) {
|
||||||
if (searchQuery.length < 2) emptyList()
|
if (searchQuery.length < 2) emptyList()
|
||||||
else StoreV2.oepsStammdaten.filter {
|
else Store.oepsStammdaten.filter {
|
||||||
it.name.contains(searchQuery, ignoreCase = true) ||
|
it.name.contains(searchQuery, ignoreCase = true) ||
|
||||||
(it.ort?.contains(searchQuery, ignoreCase = true) ?: false) ||
|
(it.ort?.contains(searchQuery, ignoreCase = true) ?: false) ||
|
||||||
it.oepsNummer.contains(searchQuery, ignoreCase = true)
|
it.oepsNummer.contains(searchQuery, ignoreCase = true)
|
||||||
|
|
@ -295,7 +295,7 @@ fun VeranstalterAnlegenWizard(
|
||||||
TextButton(onClick = { step = 1 }) { Text("Zurück zur Suche") }
|
TextButton(onClick = { step = 1 }) { Text("Zurück zur Suche") }
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val newId = StoreV2.addVerein(name, oeps, ort)
|
val newId = Store.addVerein(name, oeps, ort)
|
||||||
onVereinCreated(newId)
|
onVereinCreated(newId)
|
||||||
},
|
},
|
||||||
enabled = name.isNotBlank() && ort.isNotBlank()
|
enabled = name.isNotBlank() && ort.isNotBlank()
|
||||||
|
|
@ -309,129 +309,55 @@ fun VeranstalterAnlegenWizard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
class BewerbData(
|
||||||
@Composable
|
val nummer: String,
|
||||||
fun VeranstaltungKonfigV2(
|
val abteilung: String?,
|
||||||
veranstalterId: Long = 0,
|
val klasse: String,
|
||||||
onBack: () -> Unit,
|
val disziplin: String,
|
||||||
onSaved: (Long, Long) -> Unit, // eventId, veranstalterId
|
val bezeichnung: String
|
||||||
onVeranstalterCreated: (Long) -> Unit = {}, // Neuer Flow: nach Vereinsanlage ins Profil
|
)
|
||||||
) {
|
|
||||||
val znsImporter: ZnsImportProvider = koinInject()
|
|
||||||
val znsState = znsImporter.state
|
|
||||||
|
|
||||||
DesktopThemeV2 {
|
fun Long?.toLocalDate(): LocalDate? {
|
||||||
var currentStep by remember { mutableStateOf(if (veranstalterId == 0L) 1 else 2) }
|
|
||||||
|
|
||||||
// Step 1: Veranstalterwahl
|
|
||||||
var selectedVereinId by remember { mutableStateOf(veranstalterId) }
|
|
||||||
var showVereinNeu by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Step 2: Basisdaten
|
|
||||||
var titel by remember { mutableStateOf("") }
|
|
||||||
var untertitel by remember { mutableStateOf("") }
|
|
||||||
var von by remember { mutableStateOf("") }
|
|
||||||
var bis by remember { mutableStateOf("") }
|
|
||||||
var ort by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
var showDatePickerVon by remember { mutableStateOf(false) }
|
|
||||||
var showDatePickerBis by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Step 3: Zusatzdaten
|
|
||||||
var logoUrl by remember { mutableStateOf("") }
|
|
||||||
var sponsorenText by remember { mutableStateOf("") } // Kommagetrennte Liste
|
|
||||||
|
|
||||||
val dateFormatter = remember { DateTimeFormatter.ISO_LOCAL_DATE }
|
|
||||||
|
|
||||||
fun Long?.toLocalDate(): LocalDate? {
|
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
return Instant.ofEpochMilli(this).atZone(ZoneId.systemDefault()).toLocalDate()
|
return Instant.ofEpochMilli(this).atZone(ZoneId.systemDefault()).toLocalDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDatePickerVon) {
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppDatePickerDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onDateSelected: (LocalDate) -> Unit,
|
||||||
|
) {
|
||||||
val datePickerState = rememberDatePickerState()
|
val datePickerState = rememberDatePickerState()
|
||||||
DatePickerDialog(
|
DatePickerDialog(
|
||||||
onDismissRequest = { showDatePickerVon = false },
|
onDismissRequest = onDismiss,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
datePickerState.selectedDateMillis.toLocalDate()?.let {
|
datePickerState.selectedDateMillis.toLocalDate()?.let {
|
||||||
von = it.format(dateFormatter)
|
onDateSelected(it)
|
||||||
}
|
}
|
||||||
showDatePickerVon = false
|
onDismiss()
|
||||||
}) { Text("OK") }
|
}) { Text("OK") }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showDatePickerVon = false }) { Text("Abbrechen") }
|
TextButton(onClick = onDismiss) { Text("Abbrechen") }
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
DatePicker(state = datePickerState)
|
DatePicker(state = datePickerState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDatePickerBis) {
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
val datePickerState = rememberDatePickerState()
|
@Composable
|
||||||
DatePickerDialog(
|
fun Step1Veranstalter(
|
||||||
onDismissRequest = { showDatePickerBis = false },
|
znsState: at.mocode.frontend.core.domain.zns.ZnsImportState,
|
||||||
confirmButton = {
|
znsImporter: ZnsImportProvider,
|
||||||
TextButton(onClick = {
|
selectedVereinId: Long,
|
||||||
datePickerState.selectedDateMillis.toLocalDate()?.let {
|
onVereinSelected: (Long) -> Unit,
|
||||||
bis = it.format(dateFormatter)
|
onVeranstalterCreated: (Long) -> Unit,
|
||||||
}
|
) {
|
||||||
showDatePickerBis = false
|
var showVereinNeu by remember { mutableStateOf(false) }
|
||||||
}) { Text("OK") }
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { showDatePickerBis = false }) { Text("Abbrechen") }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
DatePicker(state = datePickerState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
||||||
// Header & Navigation
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
if (currentStep > 1) {
|
|
||||||
// Wenn wir aus einem konkreten Veranstalter kommen (id > 0),
|
|
||||||
// gehen wir bei Zurück direkt ins Profil statt auf Schritt 1.
|
|
||||||
if (veranstalterId != 0L) {
|
|
||||||
onBack()
|
|
||||||
} else {
|
|
||||||
currentStep--
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text("Neue Veranstaltung anlegen", style = MaterialTheme.typography.headlineSmall)
|
|
||||||
Text(
|
|
||||||
when (currentStep) {
|
|
||||||
1 -> "Schritt 1: Veranstalter auswählen"
|
|
||||||
2 -> "Schritt 2: Basisdaten der Veranstaltung"
|
|
||||||
3 -> "Schritt 3: Details & Sponsoren"
|
|
||||||
else -> ""
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LinearProgressIndicator(
|
|
||||||
progress = { currentStep / 3f },
|
|
||||||
modifier = Modifier.fillMaxWidth().height(4.dp),
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
trackColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
|
||||||
when (currentStep) {
|
|
||||||
1 -> {
|
|
||||||
// --- SCHRITT 1: ZNS-First Daten-Akquise ---
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Text(
|
Text(
|
||||||
"Daten-Akquise & Veranstalter",
|
"Daten-Akquise & Veranstalter",
|
||||||
|
|
@ -439,7 +365,6 @@ fun VeranstaltungKonfigV2(
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|
||||||
// 1. ZNS Import Bereich (Prominent)
|
|
||||||
ZnsImportWizardSection(
|
ZnsImportWizardSection(
|
||||||
state = znsState,
|
state = znsState,
|
||||||
onFileSelect = { path -> znsImporter.onFileSelected(path) },
|
onFileSelect = { path -> znsImporter.onFileSelected(path) },
|
||||||
|
|
@ -449,7 +374,6 @@ fun VeranstaltungKonfigV2(
|
||||||
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
|
|
||||||
// 2. Cloud Sync (Neu gemäß User-Wunsch)
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
|
@ -459,8 +383,8 @@ fun VeranstaltungKonfigV2(
|
||||||
onClick = {
|
onClick = {
|
||||||
znsImporter.syncFromCloud { remoteList ->
|
znsImporter.syncFromCloud { remoteList ->
|
||||||
remoteList.forEach { remote ->
|
remoteList.forEach { remote ->
|
||||||
StoreV2.vereine.find { it.oepsNummer == remote.oepsNummer }
|
Store.vereine.find { it.oepsNummer == remote.oepsNummer }
|
||||||
?: StoreV2.addVerein(remote.name, remote.oepsNummer, remote.ort ?: "")
|
?: Store.addVerein(remote.name, remote.oepsNummer, remote.ort ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -498,11 +422,10 @@ fun VeranstaltungKonfigV2(
|
||||||
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
|
|
||||||
// 3. Bestehende Veranstalter (Kompakt)
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.weight(1f)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.weight(1f)) {
|
||||||
var search by remember { mutableStateOf("") }
|
var search by remember { mutableStateOf("") }
|
||||||
val filteredVereine = remember(search) {
|
val filteredVereine = remember(search) {
|
||||||
StoreV2.vereine.filter {
|
Store.vereine.filter {
|
||||||
it.name.contains(search, ignoreCase = true) || (it.ort?.contains(search, ignoreCase = true)
|
it.name.contains(search, ignoreCase = true) || (it.ort?.contains(search, ignoreCase = true)
|
||||||
?: false)
|
?: false)
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +449,7 @@ fun VeranstaltungKonfigV2(
|
||||||
items(filteredVereine) { verein ->
|
items(filteredVereine) { verein ->
|
||||||
val isSelected = selectedVereinId == verein.id
|
val isSelected = selectedVereinId == verein.id
|
||||||
Surface(
|
Surface(
|
||||||
onClick = { selectedVereinId = verein.id },
|
onClick = { onVereinSelected(verein.id) },
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
||||||
border = if (isSelected) null else androidx.compose.foundation.BorderStroke(
|
border = if (isSelected) null else androidx.compose.foundation.BorderStroke(
|
||||||
|
|
@ -555,7 +478,6 @@ fun VeranstaltungKonfigV2(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Manueller Button für neuen Veranstalter
|
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { showVereinNeu = true },
|
onClick = { showVereinNeu = true },
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||||
|
|
@ -578,8 +500,7 @@ fun VeranstaltungKonfigV2(
|
||||||
onCancel = { showVereinNeu = false },
|
onCancel = { showVereinNeu = false },
|
||||||
onVereinCreated = { newId ->
|
onVereinCreated = { newId ->
|
||||||
showVereinNeu = false
|
showVereinNeu = false
|
||||||
selectedVereinId = newId
|
onVeranstalterCreated(newId)
|
||||||
currentStep = 2 // Direkt zum nächsten Schritt
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -588,22 +509,48 @@ fun VeranstaltungKonfigV2(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun Step2Basisdaten(
|
||||||
|
titel: String, onTitelChange: (String) -> Unit,
|
||||||
|
untertitel: String, onUntertitelChange: (String) -> Unit,
|
||||||
|
von: String, onVonChange: (String) -> Unit,
|
||||||
|
bis: String, onBisChange: (String) -> Unit,
|
||||||
|
ort: String, onOrtChange: (String) -> Unit,
|
||||||
|
plz: String, onPlzChange: (String) -> Unit,
|
||||||
|
selectedDisziplinen: Set<String>, onDisziplinenChange: (Set<String>) -> Unit,
|
||||||
|
dateFormatter: DateTimeFormatter
|
||||||
|
) {
|
||||||
|
var showDatePickerVon by remember { mutableStateOf(false) }
|
||||||
|
var showDatePickerBis by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showDatePickerVon) {
|
||||||
|
AppDatePickerDialog(
|
||||||
|
onDismiss = { showDatePickerVon = false },
|
||||||
|
onDateSelected = { onVonChange(it.format(dateFormatter)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (showDatePickerBis) {
|
||||||
|
AppDatePickerDialog(
|
||||||
|
onDismiss = { showDatePickerBis = false },
|
||||||
|
onDateSelected = { onBisChange(it.format(dateFormatter)) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
|
||||||
// --- SCHRITT 2: Basisdaten ---
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
Text("Allgemeine Informationen", style = MaterialTheme.typography.titleMedium)
|
Text("Allgemeine Informationen", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = titel,
|
value = titel,
|
||||||
onValueChange = { titel = it },
|
onValueChange = onTitelChange,
|
||||||
label = { Text("Titel der Veranstaltung (z.B. Pfingstturnier 2026)") },
|
label = { Text("Titel der Veranstaltung (z.B. Pfingstturnier 2026)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = untertitel,
|
value = untertitel,
|
||||||
onValueChange = { untertitel = it },
|
onValueChange = onUntertitelChange,
|
||||||
label = { Text("Untertitel / Slogan (optional)") },
|
label = { Text("Untertitel / Slogan (optional)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
@ -618,22 +565,17 @@ fun VeranstaltungKonfigV2(
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val today = LocalDate.now()
|
val isStartInPast = dateVon != null && dateVon.isBefore(LocalDate.now())
|
||||||
val isStartInPast = dateVon != null && dateVon.isBefore(today)
|
|
||||||
|
|
||||||
val daysBetween = if (dateVon != null && dateBis != null) {
|
val daysBetween = if (dateVon != null && dateBis != null) {
|
||||||
java.time.temporal.ChronoUnit.DAYS.between(dateVon, dateBis) + 1
|
java.time.temporal.ChronoUnit.DAYS.between(dateVon, dateBis) + 1
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val isOetoConform = daysBetween == null || daysBetween <= 2
|
val isOetoConform = daysBetween == null || daysBetween <= 2
|
||||||
|
val isDateRangeInvalid = (dateVon != null && dateBis != null && dateBis.isBefore(dateVon)) || isStartInPast
|
||||||
val isDateRangeInvalid =
|
|
||||||
(dateVon != null && dateBis != null && dateBis.isBefore(dateVon)) || isStartInPast
|
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = von,
|
value = von,
|
||||||
onValueChange = { /* Schreibgeschützt, via Picker */ },
|
onValueChange = { },
|
||||||
label = { Text("Datum von") },
|
label = { Text("Datum von") },
|
||||||
modifier = Modifier.weight(1f).clickable { showDatePickerVon = true },
|
modifier = Modifier.weight(1f).clickable { showDatePickerVon = true },
|
||||||
enabled = false,
|
enabled = false,
|
||||||
|
|
@ -642,23 +584,14 @@ fun VeranstaltungKonfigV2(
|
||||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
disabledBorderColor = if (isStartInPast) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline,
|
disabledBorderColor = if (isStartInPast) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline,
|
||||||
disabledLabelColor = if (isStartInPast) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
|
disabledLabelColor = if (isStartInPast) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
),
|
),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = { showDatePickerVon = true }) {
|
IconButton(onClick = { showDatePickerVon = true }) { Icon(Icons.Default.DateRange, null) }
|
||||||
Icon(Icons.Default.DateRange, contentDescription = "Datum wählen")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
supportingText = {
|
|
||||||
if (isStartInPast) {
|
|
||||||
Text("Startdatum darf nicht in der Vergangenheit liegen.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = bis,
|
value = bis,
|
||||||
onValueChange = { /* Schreibgeschützt, via Picker */ },
|
onValueChange = { },
|
||||||
label = { Text("Datum bis") },
|
label = { Text("Datum bis") },
|
||||||
modifier = Modifier.weight(1f).clickable { showDatePickerBis = true },
|
modifier = Modifier.weight(1f).clickable { showDatePickerBis = true },
|
||||||
enabled = false,
|
enabled = false,
|
||||||
|
|
@ -667,58 +600,98 @@ fun VeranstaltungKonfigV2(
|
||||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
disabledBorderColor = if (isDateRangeInvalid) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline,
|
disabledBorderColor = if (isDateRangeInvalid) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.outline,
|
||||||
disabledLabelColor = if (isDateRangeInvalid) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
|
disabledLabelColor = if (isDateRangeInvalid) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
),
|
),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = { showDatePickerBis = true }) {
|
IconButton(onClick = { showDatePickerBis = true }) { Icon(Icons.Default.DateRange, null) }
|
||||||
Icon(Icons.Default.DateRange, contentDescription = "Datum wählen")
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
supportingText = {
|
|
||||||
Column {
|
|
||||||
if (isDateRangeInvalid) {
|
|
||||||
Text("Enddatum darf nicht vor dem Startdatum liegen.")
|
|
||||||
}
|
|
||||||
if (isOetoConform.not()) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Info,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(14.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
if (isStartInPast || isDateRangeInvalid || isOetoConform.not()) {
|
||||||
|
Column {
|
||||||
|
if (isStartInPast) Text(
|
||||||
|
"Startdatum darf nicht in der Vergangenheit liegen.",
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
|
if (isDateRangeInvalid) Text(
|
||||||
|
"Enddatum darf nicht vor dem Startdatum liegen.",
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
|
if (isOetoConform.not()) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Icon(Icons.Default.Info, null, modifier = Modifier.size(14.dp), tint = MaterialTheme.colorScheme.primary)
|
||||||
Text(
|
Text(
|
||||||
"Hinweis: Gemäß ÖTO sind C-Turniere auf 2 Tage begrenzt.",
|
"Hinweis: Gemäß ÖTO sind C-Turniere auf 2 Tage begrenzt.",
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
style = MaterialTheme.typography.labelSmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = ort,
|
value = ort,
|
||||||
onValueChange = { ort = it },
|
onValueChange = onOrtChange,
|
||||||
label = { Text("Austragungsort (falls abweichend vom Vereinssitz)") },
|
label = { Text("Austragungsort (Name der Anlage / Ort)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = plz,
|
||||||
|
onValueChange = { if (it.length <= 4 && it.all { char -> char.isDigit() }) onPlzChange(it) },
|
||||||
|
label = { Text("PLZ") },
|
||||||
|
modifier = Modifier.width(100.dp),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = ort,
|
||||||
|
onValueChange = onOrtChange,
|
||||||
|
label = { Text("Ort") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Disziplinen", style = MaterialTheme.typography.titleSmall)
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
val disziplinen = listOf("Springen", "Dressur", "Vielseitigkeit", "Fahren", "Voltigieren", "Reining")
|
||||||
|
disziplinen.forEach { d ->
|
||||||
|
FilterChip(
|
||||||
|
selected = d in selectedDisziplinen,
|
||||||
|
onClick = {
|
||||||
|
onDisziplinenChange(if (d in selectedDisziplinen) selectedDisziplinen - d else selectedDisziplinen + d)
|
||||||
|
},
|
||||||
|
label = { Text(d) },
|
||||||
|
leadingIcon = if (d in selectedDisziplinen) {
|
||||||
|
{ Icon(Icons.Default.Check, null, modifier = Modifier.size(16.dp)) }
|
||||||
|
} else null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
3 -> {
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
// --- SCHRITT 3: Details & Sponsoren ---
|
@Composable
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
fun Step3Details(
|
||||||
|
logoUrl: String, onLogoUrlChange: (String) -> Unit,
|
||||||
|
sponsorenText: String, onSponsorenTextChange: (String) -> Unit,
|
||||||
|
bewerbe: SnapshotStateList<BewerbData>,
|
||||||
|
selectedDisziplinen: Set<String>
|
||||||
|
) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
Text("Branding & Partner", style = MaterialTheme.typography.titleMedium)
|
Text("Branding & Partner", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = logoUrl,
|
value = logoUrl,
|
||||||
onValueChange = { logoUrl = it },
|
onValueChange = onLogoUrlChange,
|
||||||
label = { Text("Logo-URL oder Pfad") },
|
label = { Text("Logo-URL oder Pfad") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
supportingText = { Text("Optional: Link zu einem Turnierlogo") }
|
supportingText = { Text("Optional: Link zu einem Turnierlogo") }
|
||||||
|
|
@ -726,41 +699,216 @@ fun VeranstaltungKonfigV2(
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = sponsorenText,
|
value = sponsorenText,
|
||||||
onValueChange = { sponsorenText = it },
|
onValueChange = onSponsorenTextChange,
|
||||||
label = { Text("Sponsoren (mit Komma trennen)") },
|
label = { Text("Sponsoren (mit Komma trennen)") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
minLines = 3
|
minLines = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Text("Vorschau Sponsoren:", style = MaterialTheme.typography.labelMedium, color = Color.Gray)
|
||||||
Text(
|
|
||||||
"Vorschau Sponsoren:",
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = Color.Gray
|
|
||||||
)
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
sponsorenText.split(",").filter { it.isNotBlank() }.forEach { sponsor ->
|
sponsorenText.split(",").filter { it.isNotBlank() }.forEach { sponsor ->
|
||||||
SuggestionChip(onClick = {}, label = { Text(sponsor.trim()) })
|
SuggestionChip(onClick = {}, label = { Text(sponsor.trim()) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
|
|
||||||
|
Text("Prüfungen / Bewerbe", style = MaterialTheme.typography.titleMedium)
|
||||||
|
Text("Erfassen Sie die geplanten Bewerbe gemäß ÖTO.", style = MaterialTheme.typography.bodySmall)
|
||||||
|
|
||||||
|
if (bewerbe.isEmpty()) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
||||||
|
) {
|
||||||
|
Box(Modifier.padding(24.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
|
Text(
|
||||||
|
"Noch keine Bewerbe hinzugefügt.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
bewerbe.forEachIndexed { index, b ->
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text("Bewerb ${b.nummer}${b.abteilung?.let { " / $it" } ?: ""}: ${b.bezeichnung}") },
|
||||||
|
supportingContent = { Text("Klasse ${b.klasse} | ${b.disziplin}") },
|
||||||
|
trailingContent = {
|
||||||
|
IconButton(onClick = { bewerbe.removeAt(index) }) {
|
||||||
|
Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer Navigation
|
var newNr by remember { mutableStateOf("") }
|
||||||
Row(
|
var newAbt by remember { mutableStateOf("") }
|
||||||
|
var newKlasse by remember { mutableStateOf("") }
|
||||||
|
var newBez by remember { mutableStateOf("") }
|
||||||
|
var newDis by remember { mutableStateOf(selectedDisziplinen.firstOrNull() ?: "Springen") }
|
||||||
|
|
||||||
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
border = androidx.compose.foundation.BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newNr,
|
||||||
|
onValueChange = { newNr = it },
|
||||||
|
label = { Text("Nr.") },
|
||||||
|
modifier = Modifier.width(60.dp),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newAbt,
|
||||||
|
onValueChange = { newAbt = it },
|
||||||
|
label = { Text("Abt.") },
|
||||||
|
modifier = Modifier.width(60.dp),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newBez,
|
||||||
|
onValueChange = { newBez = it },
|
||||||
|
label = { Text("Bezeichnung") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newKlasse,
|
||||||
|
onValueChange = { newKlasse = it },
|
||||||
|
label = { Text("Klasse") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newDis,
|
||||||
|
onValueChange = { newDis = it },
|
||||||
|
label = { Text("Disziplin") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (newNr.isNotBlank() && newBez.isNotBlank()) {
|
||||||
|
bewerbe.add(BewerbData(newNr, newAbt.ifBlank { null }, newKlasse, newDis, newBez))
|
||||||
|
newNr = ""; newAbt = ""; newBez = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, null)
|
||||||
|
Text("Add")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun VeranstaltungKonfig(
|
||||||
|
veranstalterId: Long = 0,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onSaved: (Long, Long) -> Unit, // eventId, veranstalterId
|
||||||
|
onVeranstalterCreated: (Long) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val znsImporter: ZnsImportProvider = koinInject()
|
||||||
|
val znsState = znsImporter.state
|
||||||
|
|
||||||
|
DesktopThemeV2 {
|
||||||
|
var currentStep by remember { mutableStateOf(if (veranstalterId == 0L) 1 else 2) }
|
||||||
|
var selectedVereinId by remember { mutableStateOf(veranstalterId) }
|
||||||
|
var titel by remember { mutableStateOf("") }
|
||||||
|
var untertitel by remember { mutableStateOf("") }
|
||||||
|
var von by remember { mutableStateOf("") }
|
||||||
|
var bis by remember { mutableStateOf("") }
|
||||||
|
var ort by remember { mutableStateOf("") }
|
||||||
|
var plz by remember { mutableStateOf("") }
|
||||||
|
var selectedDisziplinen by remember { mutableStateOf(setOf("Springen")) }
|
||||||
|
var logoUrl by remember { mutableStateOf("") }
|
||||||
|
var sponsorenText by remember { mutableStateOf("") }
|
||||||
|
val bewerbe = remember { mutableStateListOf<BewerbData>() }
|
||||||
|
|
||||||
|
val dateFormatter = remember { DateTimeFormatter.ISO_LOCAL_DATE }
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
if (currentStep > 1) {
|
||||||
|
if (veranstalterId != 0L) onBack() else currentStep--
|
||||||
|
} else onBack()
|
||||||
|
}) { Icon(Icons.AutoMirrored.Filled.ArrowBack, "Zurück") }
|
||||||
|
Column {
|
||||||
|
Text("Neue Veranstaltung anlegen", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
Text(
|
||||||
|
when (currentStep) {
|
||||||
|
1 -> "Schritt 1: Veranstalter auswählen"
|
||||||
|
2 -> "Schritt 2: Basisdaten"
|
||||||
|
3 -> "Schritt 3: Details & Bewerbe"
|
||||||
|
else -> ""
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { currentStep / 3f },
|
||||||
|
modifier = Modifier.fillMaxWidth().height(4.dp),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||||
|
when (currentStep) {
|
||||||
|
1 -> Step1Veranstalter(znsState, znsImporter, selectedVereinId, { selectedVereinId = it }, {
|
||||||
|
selectedVereinId = it; currentStep = 2
|
||||||
|
})
|
||||||
|
|
||||||
|
2 -> Step2Basisdaten(
|
||||||
|
titel,
|
||||||
|
{ titel = it },
|
||||||
|
untertitel,
|
||||||
|
{ untertitel = it },
|
||||||
|
von,
|
||||||
|
{ von = it },
|
||||||
|
bis,
|
||||||
|
{ bis = it },
|
||||||
|
ort,
|
||||||
|
{ ort = it },
|
||||||
|
plz,
|
||||||
|
{ plz = it },
|
||||||
|
selectedDisziplinen,
|
||||||
|
{ selectedDisziplinen = it },
|
||||||
|
dateFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
3 -> Step3Details(
|
||||||
|
logoUrl,
|
||||||
|
{ logoUrl = it },
|
||||||
|
sponsorenText,
|
||||||
|
{ sponsorenText = it },
|
||||||
|
bewerbe,
|
||||||
|
selectedDisziplinen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (currentStep > 1) {
|
if (currentStep > 1) OutlinedButton(onClick = { currentStep-- }) { Text("Zurück") }
|
||||||
OutlinedButton(onClick = { currentStep-- }) {
|
else Spacer(Modifier.width(1.dp))
|
||||||
Text("Zurück")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Spacer(Modifier.width(1.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
var showConfirm by remember { mutableStateOf(false) }
|
var showConfirm by remember { mutableStateOf(false) }
|
||||||
if (showConfirm) {
|
if (showConfirm) {
|
||||||
|
|
@ -769,55 +917,30 @@ fun VeranstaltungKonfigV2(
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
val id = System.currentTimeMillis()
|
val id = System.currentTimeMillis()
|
||||||
val v = VeranstaltungV2(
|
val v = Veranstaltung(
|
||||||
id = id,
|
id = id,
|
||||||
veranstalterId = selectedVereinId,
|
veranstalterId = selectedVereinId,
|
||||||
titel = titel.trim(),
|
titel = titel.trim(),
|
||||||
datumVon = von.trim(),
|
datumVon = von.trim(),
|
||||||
datumBis = bis.trim().ifBlank { null },
|
datumBis = bis.trim().ifBlank { null },
|
||||||
untertitel = untertitel.trim(),
|
untertitel = untertitel.trim(),
|
||||||
ort = ort.trim().ifBlank { StoreV2.vereine.find { it.id == selectedVereinId }?.ort ?: "" },
|
ort = ort.trim().ifBlank { Store.vereine.find { it.id == selectedVereinId }?.ort ?: "" },
|
||||||
logoUrl = logoUrl.trim().ifBlank { null }
|
logoUrl = logoUrl.trim().ifBlank { null }
|
||||||
)
|
)
|
||||||
sponsorenText.split(",").filter { it.isNotBlank() }.forEach { v.sponsoren.add(it.trim()) }
|
sponsorenText.split(",").filter { it.isNotBlank() }.forEach { v.sponsoren.add(it.trim()) }
|
||||||
StoreV2.addEventFirst(selectedVereinId, v)
|
Store.addEventFirst(selectedVereinId, v)
|
||||||
showConfirm = false
|
showConfirm = false
|
||||||
onSaved(id, selectedVereinId)
|
onSaved(id, selectedVereinId)
|
||||||
}) { Text("Anlegen") }
|
}) { Text("Anlegen") }
|
||||||
},
|
},
|
||||||
dismissButton = { TextButton(onClick = { showConfirm = false }) { Text("Abbrechen") } },
|
dismissButton = { TextButton(onClick = { showConfirm = false }) { Text("Abbrechen") } },
|
||||||
title = { Text("Veranstaltung final anlegen?") },
|
title = { Text("Veranstaltung final anlegen?") },
|
||||||
text = {
|
text = { Text("Bitte die Daten prüfen. Titel: ${titel.trim()}, Veranstalter: ${Store.vereine.find { it.id == selectedVereinId }?.name ?: ""}") }
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
Text("Bitte die Daten prüfen. Für diese Veranstaltung wird eine eigene Datenbank initialisiert.")
|
|
||||||
HorizontalDivider()
|
|
||||||
val titelText = titel.trim()
|
|
||||||
val untertitelText = untertitel.trim().ifBlank { "-" }
|
|
||||||
val vonText = von.trim()
|
|
||||||
val bisText = bis.trim()
|
|
||||||
val zeitraumText = if (bisText.isNotEmpty()) "$vonText – $bisText" else vonText
|
|
||||||
val vName = StoreV2.vereine.find { it.id == selectedVereinId }?.name ?: "#$selectedVereinId"
|
|
||||||
val spons = sponsorenText.split(',').map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
|
|
||||||
Text("Titel: $titelText")
|
|
||||||
Text("Untertitel: $untertitelText")
|
|
||||||
Text("Zeitraum: $zeitraumText")
|
|
||||||
Text("Veranstalter: $vName")
|
|
||||||
if (logoUrl.isNotBlank()) Text("Logo: ${logoUrl.trim()}")
|
|
||||||
if (spons.isNotEmpty()) Text("Sponsoren: ${spons.joinToString(", ")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = { if (currentStep < 3) currentStep++ else showConfirm = true },
|
||||||
if (currentStep < 3) {
|
|
||||||
currentStep++
|
|
||||||
} else {
|
|
||||||
showConfirm = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = when (currentStep) {
|
enabled = when (currentStep) {
|
||||||
1 -> selectedVereinId != 0L
|
1 -> selectedVereinId != 0L
|
||||||
2 -> {
|
2 -> {
|
||||||
|
|
@ -831,24 +954,21 @@ fun VeranstaltungKonfigV2(
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val today2 = LocalDate.now()
|
val rangeInvalid =
|
||||||
val startInPast = dVon != null && dVon.isBefore(today2)
|
(dVon != null && dBis != null && dBis.isBefore(dVon)) || (dVon != null && dVon.isBefore(LocalDate.now()))
|
||||||
val rangeInvalid = (dVon != null && dBis != null && dBis.isBefore(dVon)) || startInPast
|
|
||||||
titel.isNotBlank() && von.isNotBlank() && !rangeInvalid
|
titel.isNotBlank() && von.isNotBlank() && !rangeInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> true
|
3 -> bewerbe.isNotEmpty()
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
) {
|
) { Text(if (currentStep == 3) "Veranstaltung final anlegen" else "Weiter") }
|
||||||
Text(if (currentStep == 3) "Veranstaltung final anlegen" else "Weiter")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TurnierV2(
|
data class Turnier(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val veranstaltungId: Long,
|
val veranstaltungId: Long,
|
||||||
val turnierNr: Int,
|
val turnierNr: Int,
|
||||||
|
|
@ -864,15 +984,17 @@ data class TurnierV2(
|
||||||
var sponsoren: SnapshotStateList<String> = mutableStateListOf(),
|
var sponsoren: SnapshotStateList<String> = mutableStateListOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
object TurnierStoreV2 {
|
object TurnierStore {
|
||||||
private val map = mutableMapOf<Long, MutableList<TurnierV2>>()
|
private val map = mutableMapOf<Long, MutableList<Turnier>>()
|
||||||
fun list(veranstaltungId: Long): MutableList<TurnierV2> = map.getOrPut(veranstaltungId) { mutableListOf() }
|
fun list(veranstaltungId: Long): MutableList<Turnier> = map.getOrPut(veranstaltungId) { mutableListOf() }
|
||||||
fun add(veranstaltungId: Long, t: TurnierV2) { list(veranstaltungId).add(0, t) }
|
fun add(veranstaltungId: Long, t: Turnier) {
|
||||||
|
list(veranstaltungId).add(0, t)
|
||||||
|
}
|
||||||
fun remove(veranstaltungId: Long, tId: Long) { list(veranstaltungId).removeAll { it.id == tId } }
|
fun remove(veranstaltungId: Long, tId: Long) { list(veranstaltungId).removeAll { it.id == tId } }
|
||||||
|
|
||||||
// Hilfsmethode für Reflection-Zugriff aus anderen Modulen (StammdatenTab)
|
// Hilfsmethode für Reflection-Zugriff aus anderen Modulen (StammdatenTab)
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun allTurniere(): List<TurnierV2> = map.values.flatten()
|
fun allTurniere(): List<Turnier> = map.values.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -885,8 +1007,8 @@ fun VeranstaltungProfilScreen(
|
||||||
onNavigateToVeranstalterProfil: (Long) -> Unit,
|
onNavigateToVeranstalterProfil: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val veranstaltung = StoreV2.eventsFor(veranstalterId).firstOrNull { it.id == veranstaltungId }
|
val veranstaltung = Store.eventsFor(veranstalterId).firstOrNull { it.id == veranstaltungId }
|
||||||
val turniere = remember(veranstaltungId) { TurnierStoreV2.list(veranstaltungId) }
|
val turniere = remember(veranstaltungId) { TurnierStore.list(veranstaltungId) }
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) {
|
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) {
|
||||||
// Header
|
// Header
|
||||||
|
|
@ -987,7 +1109,7 @@ fun VeranstaltungProfilScreen(
|
||||||
TurnierCard(
|
TurnierCard(
|
||||||
turnier = t,
|
turnier = t,
|
||||||
onOpen = { onTurnierOpen(t.id) },
|
onOpen = { onTurnierOpen(t.id) },
|
||||||
onDelete = { TurnierStoreV2.remove(veranstaltungId, t.id) }
|
onDelete = { TurnierStore.remove(veranstaltungId, t.id) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1028,7 +1150,7 @@ private fun KpiCard(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TurnierCard(
|
private fun TurnierCard(
|
||||||
turnier: TurnierV2,
|
turnier: Turnier,
|
||||||
onOpen: () -> Unit,
|
onOpen: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -1100,14 +1222,14 @@ private fun TurnierCard(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TurnierWizardV2(
|
fun TurnierWizard(
|
||||||
veranstalterId: Long,
|
veranstalterId: Long,
|
||||||
veranstaltungId: Long,
|
veranstaltungId: Long,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSaved: (Long) -> Unit,
|
onSaved: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
val veranstaltung = StoreV2.eventsFor(veranstalterId).firstOrNull { it.id == veranstaltungId }
|
val veranstaltung = Store.eventsFor(veranstalterId).firstOrNull { it.id == veranstaltungId }
|
||||||
var currentStep by remember { mutableStateOf(1) }
|
var currentStep by remember { mutableStateOf(1) }
|
||||||
var showZnsDialog by remember { mutableStateOf(false) }
|
var showZnsDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -1222,7 +1344,7 @@ fun TurnierWizardV2(
|
||||||
currentStep++
|
currentStep++
|
||||||
} else {
|
} else {
|
||||||
val id = System.currentTimeMillis()
|
val id = System.currentTimeMillis()
|
||||||
val newTurnier = TurnierV2(
|
val newTurnier = Turnier(
|
||||||
id = id,
|
id = id,
|
||||||
veranstaltungId = veranstaltungId,
|
veranstaltungId = veranstaltungId,
|
||||||
turnierNr = nr.toInt(),
|
turnierNr = nr.toInt(),
|
||||||
|
|
@ -1238,7 +1360,7 @@ fun TurnierWizardV2(
|
||||||
newTurnier.kategorie.addAll(kat)
|
newTurnier.kategorie.addAll(kat)
|
||||||
newTurnier.sponsoren.addAll(sponsoren)
|
newTurnier.sponsoren.addAll(sponsoren)
|
||||||
|
|
||||||
TurnierStoreV2.add(veranstaltungId, newTurnier)
|
TurnierStore.add(veranstaltungId, newTurnier)
|
||||||
onSaved(id)
|
onSaved(id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1381,7 +1503,7 @@ private fun Step2Sparten(
|
||||||
kat: SnapshotStateList<String>,
|
kat: SnapshotStateList<String>,
|
||||||
von: String, onVonChange: (String) -> Unit,
|
von: String, onVonChange: (String) -> Unit,
|
||||||
bis: String, onBisChange: (String) -> Unit,
|
bis: String, onBisChange: (String) -> Unit,
|
||||||
veranstaltung: VeranstaltungV2?
|
veranstaltung: Veranstaltung?
|
||||||
) {
|
) {
|
||||||
var showDatePickerVon by remember { mutableStateOf(false) }
|
var showDatePickerVon by remember { mutableStateOf(false) }
|
||||||
var showDatePickerBis by remember { mutableStateOf(false) }
|
var showDatePickerBis by remember { mutableStateOf(false) }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user