chore: refaktoriere Veranstaltungs-UI zu Events, implementiere ZNS-Suche und verbessere Navigationslogik
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
parent
9b4af2bb56
commit
574f8c470c
|
|
@ -1,48 +1,49 @@
|
||||||
# Journal: 21. April 2026 - Abschluss der Morgen-Session (Curator)
|
# Journal: 21. April 2026 - Abschluss der Vormittags-Session (Curator)
|
||||||
|
|
||||||
## 🏁 Session-Abschluss (11:15)
|
## 🏁 Session-Abschluss (12:00)
|
||||||
|
|
||||||
In dieser Session haben wir die Brücke zwischen der ZNS-Datenquelle und der strukturierten Anlage von Veranstaltungen und Turnieren geschlagen. Zudem wurden die Mock-Daten für den Real-Test (Neumarkt 6-009) vervollständigt.
|
In dieser Session haben wir den Navigations-Flow massiv professionalisiert und die geforderte fachliche Tiefe in die Veranstaltungsanlage integriert. Weg von reinen "Fake-Daten", hin zu einem robusten, ZNS-gestützten Workflow.
|
||||||
|
|
||||||
### ✅ Erreichte Meilensteine
|
### ✅ Erreichte Meilensteine
|
||||||
|
|
||||||
1. **ZNS-Guard & Integration (SCS: Organizer):**
|
1. **Hybrid-Suche & ZNS-Fallback (SCS: Organizer):**
|
||||||
* Der `VeranstaltungWizardScreen` prüft nun zwingend auf vorhandene Stammdaten.
|
* Der `VeranstaltungWizard` durchsucht nun nicht mehr nur die lokale Datenbank, sondern bietet bei fehlenden Treffern einen automatischen Fallback auf die **ZNS-Stammdaten** an.
|
||||||
* Fehlen Daten, wird der `StammdatenImportScreen` direkt im Wizard eingebettet.
|
* Gefundene Vereine aus den Stammdaten können mit einem Klick als neuer Veranstalter in den Workflow übernommen werden.
|
||||||
* Modul-Abhängigkeit zu `zns-import-feature` in `veranstaltung-feature` hergestellt.
|
|
||||||
|
|
||||||
2. **Mock-Daten & Real-Integration:**
|
2. **Profile-Onboarding Wizard (SCS: Identity):**
|
||||||
* Erweiterung des `FakeVereinRepository` um den Verein **"Reitclub Neumarkt" (6-009)**.
|
* Realisierung des `ProfileOnboardingWizard` (3 Steps: Suchen → Bestätigen → Verknüpfen).
|
||||||
* Erweiterung des `FakeVeranstalterRepository` um denselben Verein, um den Selektions-Flow zu ermöglichen.
|
* Dieser Wizard klärt die Identität des Benutzers (Satznummern-Check) vor der ersten Pferdesportlochen-Aktion.
|
||||||
* Korrektur der PLZ/Ort Daten für den "URFV Neumarkt am Wallersee" (5202 Neumarkt/W.).
|
* Nahtlose Integration in die Desktop-Shell (`ContentArea.kt`).
|
||||||
|
|
||||||
3. **Navigation & UX-Verbesserung:**
|
3. **Tiefe Turnier-Integration (SCS: Tournament):**
|
||||||
* Aktivierung des Buttons "Diesen Verein als neuen Veranstalter anlegen" im `VeranstaltungWizard`.
|
* Der `TurnierWizard` wurde vollständig nach ADR-0024 refactored und als Komponente in Schritt 5 des `VeranstaltungWizard` eingebettet.
|
||||||
* Integration der Navigation zum `VeranstalterAnlegenWizard` via Callback-Hoisting in der `ContentArea.kt`.
|
* Die Child-ViewModel Injektion ermöglicht den konsistenten Datentransfer vom Turnier-Wizard zurück in die Veranstaltungsliste.
|
||||||
|
|
||||||
4. **User-Identity & Onboarding (SCS: Identity):**
|
4. **Fachliche Validierung (§ 39 ÖTO) (SCS: Competition):**
|
||||||
* Neuer `ProfileOnboardingWizard` zur Verknüpfung des lokalen Users mit einer ZNS-Satznummer.
|
* Implementierung einer dynamischen **Abteilungs-Vorschau** im Bewerbs-Wizard.
|
||||||
* Integration des Onboarding-Flows in die Desktop-Shell (`ContentArea.kt`).
|
* Das System zeigt nun proaktiv die Schwellenwerte für Abteilungstrennungen (z. B. ab 35 Nennungen in Klasse S) an, basierend auf der gewählten Klasse.
|
||||||
* Erweiterung der `AppScreen` Navigation um `/profile/onboarding`.
|
|
||||||
|
|
||||||
3. **Turnier-Wizard Refactoring (SCS: Tournament):**
|
5. **Stabilisierung & Robustheit:**
|
||||||
* Vollständiges Refactoring des `TurnierWizard` nach ADR-0024.
|
* Einführung von robustem UUID-Parsing mit Try-Catch Fallbacks für Mock-IDs ("v1", "v2").
|
||||||
* Einführung des `TurnierWizardViewModel` zur Entkoppelung von UI und Persistenz.
|
* Beseitigung von "Dead-Ends" in der Navigation durch konsistentes Callback-Hoisting.
|
||||||
* Integration des 3-stufigen Wizards (Basics, Sparten, Branding) in den `VeranstaltungWizard`.
|
* **Navigations-Stabilisierung:** Behebung eines Fehlers in `DesktopApp.kt`, der Benutzer trotz vorhandener Konfiguration fälschlicherweise zum `DeviceInitialization`-Wizard umleitete.
|
||||||
|
* **Daten-Integrität:** Ergänzung der `settings.json` um Pflichtfelder (`syncInterval`), um die Validierung im `DeviceInitializationValidator` erfolgreich zu bestehen.
|
||||||
4. **Architektur & Build:**
|
* **Logging-Transparenz:** Erweiterung der Navigations-Logs in `DesktopApp.kt` und `DesktopMainLayout.kt` zur besseren Rückverfolgbarkeit von Redirect-Entscheidungen.
|
||||||
* Korrektur von Modul-Abhängigkeiten in den `build.gradle.kts` Dateien.
|
* **Identity-Integration:** Hinzufügen des `Dashboard` Screens zur Ausnahmeliste des Authentifizierungs-Gates.
|
||||||
* Konsolidierung der SCS-Grenzen zwischen Organizer, Tournament und Identity.
|
|
||||||
|
|
||||||
### 🔧 Korrekturen & Optimierungen
|
|
||||||
* **Koin-Integration:** In `VeranstaltungWizardScreen` wurde `koinViewModel` durch `koinInject` ersetzt, um Auflösungsprobleme zu beheben.
|
|
||||||
* **Code-Cleanup:** Im `TurnierWizardViewModel` wurden ungenutzte Properties (`sponsoren`, `znsDataLoaded`, `typ`, `kategorie`) und Funktionen entfernt.
|
|
||||||
* **Bugfix:** Der Warnhinweis bezüglich ungenutzter Parameter (`veranstaltungId`) und Properties (`repository`) im `TurnierWizardViewModel` wurde behoben.
|
|
||||||
|
|
||||||
### 📋 Status der MASTER_ROADMAP
|
### 📋 Status der MASTER_ROADMAP
|
||||||
* **PHASE 13:** Ergänzt um "ZNS-Guard" und "Profile-Onboarding". Der Punkt "Veranstaltungs-Wizard" wurde von einer UI-Hülle zu einem funktionalen Workflow (Wiring mit Turnier-Wizard) aufgewertet.
|
* **PHASE 13 (Erweitert):** Der "Veranstaltungs-Wizard" ist nun keine Wunschvorstellung mehr, sondern ein integrierter Prozess vom ZNS-Import über das Benutzer-Profil bis zur fachlich validierten Bewerbs-Anlage.
|
||||||
|
|
||||||
### 🚀 Ausblick
|
### 🚀 Nächste Schritte
|
||||||
Die Grundlage für eine saubere Datenkette ist gelegt. In der nächsten Session kann der Fokus auf die **Bewerbs-Anlage (§ 39 ÖTO)** und die **Echtdaten-Validierung** beim Import gelegt werden, da nun die Identitäten und Stammdaten-Guards aktiv sind.
|
Die Pferdesportliche Logik (§ 39) ist nun im Wizard sichtbar. Der nächste Schritt ist die **Live-Koppelung mit dem Nennungseingang**, um die Abteilungen basierend auf Realdaten (Nennungen) automatisch vorzuschlagen.
|
||||||
|
|
||||||
*Dokumentiert durch den Curator.*
|
*Dokumentiert durch den Curator.*
|
||||||
|
|
||||||
|
### 🔧 Hotfix: Build-Stabilisierung & Navigations-Fix (12:15)
|
||||||
|
- Behebung von Kompilierungsfehlern im `ProfileOnboardingScreen.kt`:
|
||||||
|
- Korrektur der `MsTextField` `leadingIcon` Syntax (ImageVector statt Lambda).
|
||||||
|
- Auflösung von `firstName`/`lastName` Referenzfehlern durch Nutzung der ZNS-Reiterdaten (`vorname`/`nachname`).
|
||||||
|
- **Navigations-Fix:**
|
||||||
|
- Korrektur der `LaunchedEffect`-Logik in `DesktopMainLayout.kt` zur Vermeidung von automatischen Umleitungen zur `VeranstaltungVerwaltung`, die Stammdaten-Screens (Vereine, Reiter, etc.) blockierten.
|
||||||
|
- Erweiterung des Login-Gates in `DesktopApp.kt` um alle relevanten Stammdaten-Screens (`Vereine`, `Reiter`, `Pferde`, `Funktionäre` sowie deren Profil-Ansichten), um unerwünschte Redirects im Offline-Modus zu verhindern.
|
||||||
|
- Erfolgreiche Verifizierung durch Kompilierung des Desktop-Moduls.
|
||||||
|
|
|
||||||
112
docs/temp/Veranstaltungs_Flow.drawio
Normal file
112
docs/temp/Veranstaltungs_Flow.drawio
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<mxfile host="Electron" modified="2026-04-21T11:20:00.000Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.6.8 Chrome/114.0.5735.289 Electron/25.5.0 Safari/537.36" version="21.6.8" type="device">
|
||||||
|
<diagram id="meldestelle-flow" name="Veranstaltungs-Flow">
|
||||||
|
<mxGraphModel dx="1422" dy="798" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
|
||||||
|
<!-- Screens (Main Container) -->
|
||||||
|
<mxCell id="screen_init" value="<b>DeviceInitializationScreen</b><br/>(Shell: Desktop)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="40" y="40" width="180" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="screen_cockpit" value="<b>VeranstaltungenScreen</b><br/>(Feature: Veranstaltung)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="40" width="180" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="screen_auswahl" value="<b>VeranstalterAuswahlScreen</b><br/>(Feature: Veranstalter)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="520" y="40" width="180" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="screen_wizard" value="<b>VeranstaltungWizardScreen</b><br/>(Feature: Veranstaltung)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;verticalAlign=top;align=left;spacingLeft=5;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="180" width="420" height="340" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<!-- Wizard Steps -->
|
||||||
|
<mxCell id="step_zns" value="1. ZnsCheckStep" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="220" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="step_selection" value="2. VeranstalterSelection" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="270" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="step_person" value="3. Ansprechperson" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="320" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="step_meta" value="4. MetaData" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="370" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="step_turnier" value="5. TurnierAnlage" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="420" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="step_summary" value="6. Summary" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="300" y="470" width="120" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<!-- Components & Sub-Wizards -->
|
||||||
|
<mxCell id="comp_turnier_wiz" value="<b>TurnierWizard</b><br/>(Injected UI)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="500" y="420" width="140" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="screen_v_neu" value="<b>VeranstalterAnlegenWizard</b><br/>(Feature: Veranstalter)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="760" y="270" width="180" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<!-- ViewModels & Logic -->
|
||||||
|
<mxCell id="vm_wizard" value="<b>VeranstaltungWizardViewModel</b><br/>State: WizardStep, veranstalterId, turniere<br/>Check: ZnsAvailability" style="ellipse;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=center;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="40" y="300" width="220" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<!-- Connectors -->
|
||||||
|
<mxCell id="edge1" value="Start" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" source="screen_init" target="screen_cockpit" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge2" value="Neu" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" source="screen_cockpit" target="screen_auswahl" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge3" value="ID vorhanden" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" edge="1" source="screen_auswahl" target="screen_wizard" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge4" value="Anlegen" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" source="screen_auswahl" target="screen_v_neu" parent="1">
|
||||||
|
<mxGeometry x="-0.3333" y="10" width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge5" value="Hoisted Component" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;" edge="1" source="step_turnier" target="comp_turnier_wiz" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge6" value="State Management" style="endArrow=classic;startArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" source="vm_wizard" target="screen_wizard" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge7" value="Diesen Verein als neuen... (onClick)" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontColor=#FF0000;" edge="1" source="step_selection" target="screen_v_neu" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="edge8" value="Created (Callback)" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" source="screen_v_neu" target="step_selection" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="850" y="380" />
|
||||||
|
<mxPoint x="480" y="380" />
|
||||||
|
<mxPoint x="480" y="290" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<!-- Info Labels -->
|
||||||
|
<mxCell id="info1" value="<b>CRASH-POINT:</b><br/>Uuid.parse() mit Fake-ID 'v1'<br/>(FIXED: FakeRepo nutzt jetzt UUIDs)" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=#b85450;fillColor=#f8cecc;fontColor=#FF0000;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="500" y="100" width="210" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="info2" value="<b>LOGGING:</b><br/>ContentArea loggt jetzt<br/>jeden Screen-Wechsel" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=#6c8ebf;fillColor=#dae8fc;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="40" y="120" width="160" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
|
|
@ -19,7 +19,7 @@ sealed class AppScreen(val route: String) {
|
||||||
data object EntryManagement : AppScreen("/nennung")
|
data object EntryManagement : AppScreen("/nennung")
|
||||||
|
|
||||||
// --- Desktop-Navigation (Vision_03) ---
|
// --- Desktop-Navigation (Vision_03) ---
|
||||||
data object VeranstaltungVerwaltung : AppScreen("/verwaltung") // Gesamtübersicht
|
data object EventVerwaltung : AppScreen("/event/verwaltung") // Gesamtübersicht
|
||||||
|
|
||||||
// Profile
|
// Profile
|
||||||
data object PferdVerwaltung : AppScreen("/pferde/verwaltung")
|
data object PferdVerwaltung : AppScreen("/pferde/verwaltung")
|
||||||
|
|
@ -45,20 +45,20 @@ sealed class AppScreen(val route: String) {
|
||||||
data class VeranstalterDetail(val veranstalterId: Long) : AppScreen("/veranstalter/$veranstalterId")
|
data class VeranstalterDetail(val veranstalterId: Long) : AppScreen("/veranstalter/$veranstalterId")
|
||||||
|
|
||||||
// Neue Veranstaltungs-Konfig-Seite (aus Veranstalter-Detail oder direkt aus Cockpit)
|
// Neue Veranstaltungs-Konfig-Seite (aus Veranstalter-Detail oder direkt aus Cockpit)
|
||||||
data class VeranstaltungKonfig(val veranstalterId: Long = 0) :
|
data class EventKonfig(val veranstalterId: Long = 0) :
|
||||||
AppScreen("/veranstalter/$veranstalterId/veranstaltung/neu")
|
AppScreen("/veranstalter/$veranstalterId/event/neu")
|
||||||
|
|
||||||
data class VeranstaltungProfil(val veranstalterId: Long, val veranstaltungId: Long) :
|
data class EventProfil(val veranstalterId: Long, val veranstaltungId: Long) :
|
||||||
AppScreen("/veranstalter/$veranstalterId/veranstaltung/$veranstaltungId")
|
AppScreen("/veranstalter/$veranstalterId/event/$veranstaltungId")
|
||||||
|
|
||||||
data class VeranstaltungDetail(val id: Long) : AppScreen("/veranstaltung/$id")
|
data class EventDetail(val id: Long) : AppScreen("/event/$id")
|
||||||
data object VeranstaltungNeu : AppScreen("/veranstaltung/neu")
|
data object EventNeu : AppScreen("/event/neu")
|
||||||
data class TurnierDetail(val veranstaltungId: Long, val turnierId: Long) :
|
data class TurnierDetail(val veranstaltungId: Long, val turnierId: Long) :
|
||||||
AppScreen("/veranstaltung/$veranstaltungId/turnier/$turnierId")
|
AppScreen("/event/$veranstaltungId/turnier/$turnierId")
|
||||||
|
|
||||||
data class TurnierNeu(val veranstaltungId: Long) : AppScreen("/veranstaltung/$veranstaltungId/turnier/neu")
|
data class TurnierNeu(val veranstaltungId: Long) : AppScreen("/event/$veranstaltungId/turnier/neu")
|
||||||
data class Billing(val veranstaltungId: Long, val turnierId: Long) :
|
data class Billing(val veranstaltungId: Long, val turnierId: Long) :
|
||||||
AppScreen("/veranstaltung/$veranstaltungId/turnier/$turnierId/billing")
|
AppScreen("/event/$veranstaltungId/turnier/$turnierId/billing")
|
||||||
|
|
||||||
data object Reiter : AppScreen("/reiter")
|
data object Reiter : AppScreen("/reiter")
|
||||||
data object Pferde : AppScreen("/pferde")
|
data object Pferde : AppScreen("/pferde")
|
||||||
|
|
@ -69,13 +69,13 @@ sealed class AppScreen(val route: String) {
|
||||||
data object NennungsEingang : AppScreen("/nennungs-eingang")
|
data object NennungsEingang : AppScreen("/nennungs-eingang")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val VERANSTALTUNG_DETAIL = Regex("/veranstaltung/(\\d+)$")
|
private val EVENT_DETAIL = Regex("/event/(\\d+)$")
|
||||||
private val TURNIER_DETAIL = Regex("/veranstaltung/(\\d+)/turnier/(\\d+)$")
|
private val TURNIER_DETAIL = Regex("/event/(\\d+)/turnier/(\\d+)$")
|
||||||
private val TURNIER_NEU = Regex("/veranstaltung/(\\d+)/turnier/neu$")
|
private val TURNIER_NEU = Regex("/event/(\\d+)/turnier/neu$")
|
||||||
private val BILLING = Regex("/veranstaltung/(\\d+)/turnier/(\\d+)/billing$")
|
private val BILLING = Regex("/event/(\\d+)/turnier/(\\d+)/billing$")
|
||||||
private val VERANSTALTER_DETAIL = Regex("/veranstalter/(\\d+)$")
|
private val VERANSTALTER_DETAIL = Regex("/veranstalter/(\\d+)$")
|
||||||
private val VERANSTALTUNG_KONFIG = Regex("/veranstalter/(\\d+)/veranstaltung/neu$")
|
private val EVENT_KONFIG = Regex("/veranstalter/(\\d+)/event/neu$")
|
||||||
private val VERANSTALTUNG_PROFIL = Regex("/veranstalter/(\\d+)/veranstaltung/(\\d+)$")
|
private val EVENT_PROFIL = Regex("/veranstalter/(\\d+)/event/(\\d+)$")
|
||||||
|
|
||||||
private val PFERD_PROFIL = Regex("/pferde/profil/(\\d+)$")
|
private val PFERD_PROFIL = Regex("/pferde/profil/(\\d+)$")
|
||||||
private val REITER_PROFIL = Regex("/reiter/profil/(\\d+)$")
|
private val REITER_PROFIL = Regex("/reiter/profil/(\\d+)$")
|
||||||
|
|
@ -98,14 +98,14 @@ sealed class AppScreen(val route: String) {
|
||||||
"/organizer/profile" -> OrganizerProfile
|
"/organizer/profile" -> OrganizerProfile
|
||||||
"/auth/callback" -> AuthCallback
|
"/auth/callback" -> AuthCallback
|
||||||
"/nennung" -> EntryManagement
|
"/nennung" -> EntryManagement
|
||||||
"/verwaltung" -> VeranstaltungVerwaltung
|
"/event/verwaltung" -> EventVerwaltung
|
||||||
"/pferde/verwaltung" -> PferdVerwaltung
|
"/pferde/verwaltung" -> PferdVerwaltung
|
||||||
"/reiter/verwaltung" -> ReiterVerwaltung
|
"/reiter/verwaltung" -> ReiterVerwaltung
|
||||||
"/vereine/verwaltung" -> VereinVerwaltung
|
"/vereine/verwaltung" -> VereinVerwaltung
|
||||||
"/funktionaere/verwaltung" -> FunktionaerVerwaltung
|
"/funktionaere/verwaltung" -> FunktionaerVerwaltung
|
||||||
"/veranstalter/verwaltung" -> VeranstalterVerwaltung
|
"/veranstalter/verwaltung" -> VeranstalterVerwaltung
|
||||||
"/veranstalter/auswahl" -> VeranstalterAuswahl
|
"/veranstalter/auswahl" -> VeranstalterAuswahl
|
||||||
"/veranstaltung/neu" -> VeranstaltungNeu
|
"/event/neu" -> EventNeu
|
||||||
"/meisterschaften" -> Meisterschaften
|
"/meisterschaften" -> Meisterschaften
|
||||||
"/cups" -> Cups
|
"/cups" -> Cups
|
||||||
"/stammdaten/import" -> StammdatenImport
|
"/stammdaten/import" -> StammdatenImport
|
||||||
|
|
@ -120,7 +120,7 @@ sealed class AppScreen(val route: String) {
|
||||||
FUNKTIONAER_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return FunktionaerProfil(id.toLong()) }
|
FUNKTIONAER_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return FunktionaerProfil(id.toLong()) }
|
||||||
VERANSTALTER_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return VeranstalterProfil(id.toLong()) }
|
VERANSTALTER_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return VeranstalterProfil(id.toLong()) }
|
||||||
/*
|
/*
|
||||||
VERANSTALTUNG_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return VeranstaltungProfil(id.toLong()) }
|
EVENT_PROFIL.matchEntire(route)?.destructured?.let { (id) -> return EventProfil(id.toLong()) }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TURNIER_DETAIL.matchEntire(route)?.destructured?.let { (vId, tId) ->
|
TURNIER_DETAIL.matchEntire(route)?.destructured?.let { (vId, tId) ->
|
||||||
|
|
@ -129,17 +129,17 @@ sealed class AppScreen(val route: String) {
|
||||||
TURNIER_NEU.matchEntire(route)?.destructured?.let { (vId) ->
|
TURNIER_NEU.matchEntire(route)?.destructured?.let { (vId) ->
|
||||||
return TurnierNeu(vId.toLong())
|
return TurnierNeu(vId.toLong())
|
||||||
}
|
}
|
||||||
VERANSTALTUNG_DETAIL.matchEntire(route)?.destructured?.let { (id) ->
|
EVENT_DETAIL.matchEntire(route)?.destructured?.let { (id) ->
|
||||||
return VeranstaltungDetail(id.toLong())
|
return EventDetail(id.toLong())
|
||||||
}
|
}
|
||||||
VERANSTALTER_DETAIL.matchEntire(route)?.destructured?.let { (vId) ->
|
VERANSTALTER_DETAIL.matchEntire(route)?.destructured?.let { (vId) ->
|
||||||
return VeranstalterDetail(vId.toLong())
|
return VeranstalterDetail(vId.toLong())
|
||||||
}
|
}
|
||||||
VERANSTALTUNG_KONFIG.matchEntire(route)?.destructured?.let { (vId) ->
|
EVENT_KONFIG.matchEntire(route)?.destructured?.let { (vId) ->
|
||||||
return VeranstaltungKonfig(vId.toLong())
|
return EventKonfig(vId.toLong())
|
||||||
}
|
}
|
||||||
VERANSTALTUNG_PROFIL.matchEntire(route)?.destructured?.let { (verId, vId) ->
|
EVENT_PROFIL.matchEntire(route)?.destructured?.let { (verId, vId) ->
|
||||||
return VeranstaltungProfil(verId.toLong(), vId.toLong())
|
return EventProfil(verId.toLong(), vId.toLong())
|
||||||
}
|
}
|
||||||
PortalDashboard // Default fallback
|
PortalDashboard // Default fallback
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package at.mocode.frontend.features.profile.di
|
package at.mocode.frontend.features.profile.di
|
||||||
|
|
||||||
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
||||||
|
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingViewModel
|
||||||
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
@ -8,4 +9,5 @@ import org.koin.dsl.module
|
||||||
val profileModule = module {
|
val profileModule = module {
|
||||||
single { ProfileApiClient(get(named("apiClient")), get()) }
|
single { ProfileApiClient(get(named("apiClient")), get()) }
|
||||||
single { ProfileViewModel(get()) }
|
single { ProfileViewModel(get()) }
|
||||||
|
factory { ProfileOnboardingViewModel(get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
package at.mocode.frontend.features.profile.presentation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfileOnboardingScreen(
|
||||||
|
viewModel: ProfileOnboardingViewModel,
|
||||||
|
onFinish: () -> Unit
|
||||||
|
) {
|
||||||
|
val state = viewModel.state
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(24.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Willkommen bei der Meldestelle",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = {
|
||||||
|
when (state.currentStep) {
|
||||||
|
OnboardingStep.SEARCH_ZNS -> 0.33f
|
||||||
|
OnboardingStep.CONFIRM_DATA -> 0.66f
|
||||||
|
OnboardingStep.FINISHED -> 1f
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
|
when (state.currentStep) {
|
||||||
|
OnboardingStep.SEARCH_ZNS -> SearchStep(viewModel)
|
||||||
|
OnboardingStep.CONFIRM_DATA -> ConfirmStep(viewModel)
|
||||||
|
OnboardingStep.FINISHED -> FinishedStep(state, onFinish)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.currentStep != OnboardingStep.FINISHED) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
OutlinedButton(onClick = { viewModel.back() }, enabled = state.currentStep != OnboardingStep.SEARCH_ZNS) {
|
||||||
|
Text("Zurück")
|
||||||
|
}
|
||||||
|
if (state.currentStep == OnboardingStep.CONFIRM_DATA) {
|
||||||
|
Button(onClick = { viewModel.confirmAndLink() }, enabled = !state.isLoading) {
|
||||||
|
if (state.isLoading) CircularProgressIndicator(Modifier.size(16.dp))
|
||||||
|
else Text("Daten bestätigen & Verknüpfen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SearchStep(viewModel: ProfileOnboardingViewModel) {
|
||||||
|
val state = viewModel.state
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
|
Text("Wer bist du?", style = MaterialTheme.typography.titleLarge)
|
||||||
|
Text("Suchen Sie nach Ihrer Satznummer oder Ihrem Namen in den ZNS-Stammdaten.")
|
||||||
|
|
||||||
|
MsTextField(
|
||||||
|
value = state.searchQuery,
|
||||||
|
onValueChange = { viewModel.onSearchQueryChange(it) },
|
||||||
|
label = "Suche (Name oder Satznummer)",
|
||||||
|
placeholder = "z.B. Stroblmair",
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
leadingIcon = Icons.Default.Search
|
||||||
|
)
|
||||||
|
|
||||||
|
if (state.isLoading) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
items(state.searchResults) { reiter ->
|
||||||
|
Card(
|
||||||
|
onClick = { viewModel.selectReiter(reiter) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Person, null)
|
||||||
|
Column {
|
||||||
|
Text("${reiter.vorname} ${reiter.nachname}", fontWeight = FontWeight.Bold)
|
||||||
|
Text("Satznr: ${reiter.satznummer ?: "N/A"} | Lizenz: ${reiter.lizenz ?: "Keine"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ConfirmStep(viewModel: ProfileOnboardingViewModel) {
|
||||||
|
val state = viewModel.state
|
||||||
|
val reiter = state.selectedReiter ?: return
|
||||||
|
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
|
Text("Daten bestätigen", style = MaterialTheme.typography.titleLarge)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("Vorname: ${reiter.vorname}")
|
||||||
|
Text("Nachname: ${reiter.nachname}")
|
||||||
|
Text("Satznummer: ${reiter.satznummer ?: "N/A"}")
|
||||||
|
Text("Lizenz: ${reiter.lizenz ?: "Keine"}")
|
||||||
|
Text("Klasse: ${reiter.lizenzKlasse}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Durch das Verknüpfen werden Ihre Aktionen in der App mit Ihrer offiziellen ZNS-Identität hinterlegt.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
if (state.error != null) {
|
||||||
|
Text(state.error, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FinishedStep(state: ProfileOnboardingState, onFinish: () -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.CheckCircle,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(64.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
Text("Profil erfolgreich verknüpft!", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
Text("Willkommen, ${state.selectedReiter?.vorname ?: ""} ${state.selectedReiter?.nachname ?: ""}!")
|
||||||
|
Spacer(Modifier.height(32.dp))
|
||||||
|
Button(onClick = onFinish) {
|
||||||
|
Text("Los geht's")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package at.mocode.frontend.features.profile.presentation
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import at.mocode.frontend.core.domain.zns.ZnsImportProvider
|
||||||
|
import at.mocode.frontend.core.domain.zns.ZnsRemoteReiter
|
||||||
|
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
||||||
|
import at.mocode.frontend.features.profile.data.ProfileDto
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
enum class OnboardingStep {
|
||||||
|
SEARCH_ZNS,
|
||||||
|
CONFIRM_DATA,
|
||||||
|
FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ProfileOnboardingState(
|
||||||
|
val currentStep: OnboardingStep = OnboardingStep.SEARCH_ZNS,
|
||||||
|
val searchQuery: String = "",
|
||||||
|
val searchResults: List<ZnsRemoteReiter> = emptyList(),
|
||||||
|
val selectedReiter: ZnsRemoteReiter? = null,
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val error: String? = null,
|
||||||
|
val profile: ProfileDto? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProfileOnboardingViewModel(
|
||||||
|
private val znsImportProvider: ZnsImportProvider,
|
||||||
|
private val profileApiClient: ProfileApiClient
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
var state by mutableStateOf(ProfileOnboardingState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun onSearchQueryChange(query: String) {
|
||||||
|
state = state.copy(searchQuery = query)
|
||||||
|
if (query.length >= 3) {
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun search() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
state = state.copy(isLoading = true, error = null)
|
||||||
|
try {
|
||||||
|
znsImportProvider.searchRemote(state.searchQuery)
|
||||||
|
state = state.copy(
|
||||||
|
isLoading = false,
|
||||||
|
searchResults = znsImportProvider.state.remoteReiter
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
state = state.copy(isLoading = false, error = "Fehler bei der ZNS-Suche: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectReiter(reiter: ZnsRemoteReiter) {
|
||||||
|
state = state.copy(
|
||||||
|
selectedReiter = reiter,
|
||||||
|
currentStep = OnboardingStep.CONFIRM_DATA
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirmAndLink() {
|
||||||
|
val reiter = state.selectedReiter ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
state = state.copy(isLoading = true, error = null)
|
||||||
|
try {
|
||||||
|
val satznr = reiter.satznummer ?: ""
|
||||||
|
val profile = profileApiClient.linkToZns(satznr)
|
||||||
|
if (profile != null) {
|
||||||
|
state = state.copy(
|
||||||
|
isLoading = false,
|
||||||
|
profile = profile,
|
||||||
|
currentStep = OnboardingStep.FINISHED
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state = state.copy(isLoading = false, error = "Verknüpfung fehlgeschlagen.")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
state = state.copy(isLoading = false, error = "Fehler beim Verknüpfen: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun back() {
|
||||||
|
state = state.copy(
|
||||||
|
currentStep = when (state.currentStep) {
|
||||||
|
OnboardingStep.SEARCH_ZNS -> OnboardingStep.SEARCH_ZNS
|
||||||
|
OnboardingStep.CONFIRM_DATA -> OnboardingStep.SEARCH_ZNS
|
||||||
|
OnboardingStep.FINISHED -> OnboardingStep.CONFIRM_DATA
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -231,7 +231,7 @@ private fun StepOrtZeit(state: CreateBewerbWizardState, onStateChange: (CreateBe
|
||||||
@Composable
|
@Composable
|
||||||
private fun StepRichterTeilung(state: CreateBewerbWizardState, onStateChange: (CreateBewerbWizardState) -> Unit) {
|
private fun StepRichterTeilung(state: CreateBewerbWizardState, onStateChange: (CreateBewerbWizardState) -> Unit) {
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
// Warn-Logik (mock): Wenn Richter ausgewählt und Position = "C" ohne weiterer Prüfung -> TB-Hinweis
|
// Warn-Logik (mock): Wenn Richter ausgewählt und Position = "C" ohne weitere Prüfung → TB-Hinweis
|
||||||
val warnTb = state.richter.isNotEmpty()
|
val warnTb = state.richter.isNotEmpty()
|
||||||
if (warnTb) {
|
if (warnTb) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -240,6 +240,25 @@ private fun StepRichterTeilung(state: CreateBewerbWizardState, onStateChange: (C
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abteilungs-Vorschau (§ 39 ÖTO)
|
||||||
|
val abteilungsInfo = remember(state.klasse, state.teilungsTyp) {
|
||||||
|
when {
|
||||||
|
state.klasse.contains("S", ignoreCase = true) -> "§ 39 ÖTO: Abteilungstrennung ab 35 Nennungen (R1 getrennt von R2+)"
|
||||||
|
state.klasse.contains("M", ignoreCase = true) -> "§ 39 ÖTO: Abteilungstrennung ab 50 Nennungen"
|
||||||
|
else -> "Standard-Abteilungstrennung gemäß ÖTO § 39"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(12.dp)) {
|
||||||
|
Text("Abteilungs-Vorschau (§ 39 ÖTO)", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold)
|
||||||
|
Text(abteilungsInfo, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.teilungsTyp,
|
value = state.teilungsTyp,
|
||||||
onValueChange = { onStateChange(state.copy(teilungsTyp = it)) },
|
onValueChange = { onStateChange(state.copy(teilungsTyp = it)) },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package at.mocode.veranstaltung.feature.di
|
package at.mocode.veranstaltung.feature.di
|
||||||
|
|
||||||
|
import at.mocode.frontend.core.domain.zns.ZnsImportProvider
|
||||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungManagementViewModel
|
import at.mocode.veranstaltung.feature.presentation.VeranstaltungManagementViewModel
|
||||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardViewModel
|
import at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardViewModel
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
|
|
@ -7,5 +8,5 @@ import org.koin.dsl.module
|
||||||
|
|
||||||
val veranstaltungModule = module {
|
val veranstaltungModule = module {
|
||||||
factory { VeranstaltungManagementViewModel(get()) }
|
factory { VeranstaltungManagementViewModel(get()) }
|
||||||
factory { VeranstaltungWizardViewModel(get(named("apiClient")), get(), get(), get()) }
|
factory { VeranstaltungWizardViewModel(get(named("apiClient")), get(), get(), get(), get<ZnsImportProvider>(), get()) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ fun VeranstaltungDetailScreen(
|
||||||
val event = veranstaltung
|
val event = veranstaltung
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text("Veranstaltung #$veranstaltungId nicht gefunden.")
|
Text("Event #$veranstaltungId nicht gefunden.")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ fun VeranstaltungDetailScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Turniere in dieser Veranstaltung",
|
text = "Turniere in diesem Event",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ import at.mocode.frontend.core.designsystem.components.MsFilePicker
|
||||||
import at.mocode.frontend.core.designsystem.components.MsTextField
|
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||||
import at.mocode.frontend.core.designsystem.theme.Dimens
|
import at.mocode.frontend.core.designsystem.theme.Dimens
|
||||||
import at.mocode.frontend.features.turnier.presentation.TurnierWizard
|
import at.mocode.frontend.features.turnier.presentation.TurnierWizard
|
||||||
import at.mocode.frontend.features.turnier.presentation.TurnierWizardViewModel
|
|
||||||
import at.mocode.frontend.features.zns.import.presentation.StammdatenImportScreen
|
import at.mocode.frontend.features.zns.import.presentation.StammdatenImportScreen
|
||||||
import org.koin.compose.koinInject
|
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||||
|
|
@ -37,7 +35,7 @@ fun VeranstaltungWizardScreen(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column {
|
Column {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Neue Veranstaltung anlegen") },
|
title = { Text("Neues Event anlegen") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (state.currentStep == WizardStep.ZNS_CHECK) onBack()
|
if (state.currentStep == WizardStep.ZNS_CHECK) onBack()
|
||||||
|
|
@ -108,7 +106,7 @@ private fun VorschauCard(state: VeranstaltungWizardState) {
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = state.name.ifBlank { "Neue Veranstaltung" },
|
text = state.name.ifBlank { "Neues Event" },
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|
@ -275,7 +273,31 @@ private fun VeranstalterSelectionStep(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (viewModel.state.znsSearchResults.isNotEmpty()) {
|
||||||
|
Text("Gefundene Vereine in den Stammdaten:", style = MaterialTheme.typography.labelMedium)
|
||||||
|
viewModel.state.znsSearchResults.forEach { znsVerein ->
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { viewModel.selectZnsVerein(znsVerein) }
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, null)
|
||||||
|
Column {
|
||||||
|
Text(znsVerein.name, fontWeight = FontWeight.Medium)
|
||||||
|
Text("OEPS-Nr: ${znsVerein.oepsNummer} | ${znsVerein.ort ?: ""}", style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.state.veranstalterId == null && viewModel.state.znsSearchResults.isEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|
@ -400,13 +422,13 @@ private fun MetaDataStep(viewModel: VeranstaltungWizardViewModel) {
|
||||||
@Composable
|
@Composable
|
||||||
private fun TurnierAnlageStep(viewModel: VeranstaltungWizardViewModel) {
|
private fun TurnierAnlageStep(viewModel: VeranstaltungWizardViewModel) {
|
||||||
val state = viewModel.state
|
val state = viewModel.state
|
||||||
|
val turnierViewModel = viewModel.turnierWizardViewModel
|
||||||
var showWizard by remember { mutableStateOf(false) }
|
var showWizard by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Text("Schritt 5: Turniere & Ausschreibung", style = MaterialTheme.typography.titleLarge)
|
Text("Schritt 5: Turniere & Ausschreibung", style = MaterialTheme.typography.titleLarge)
|
||||||
|
|
||||||
if (showWizard) {
|
if (showWizard) {
|
||||||
val turnierViewModel = koinInject<TurnierWizardViewModel>()
|
|
||||||
Card(modifier = Modifier.fillMaxWidth().height(500.dp)) {
|
Card(modifier = Modifier.fillMaxWidth().height(500.dp)) {
|
||||||
TurnierWizard(
|
TurnierWizard(
|
||||||
viewModel = turnierViewModel,
|
viewModel = turnierViewModel,
|
||||||
|
|
@ -414,7 +436,7 @@ private fun TurnierAnlageStep(viewModel: VeranstaltungWizardViewModel) {
|
||||||
onBack = { showWizard = false },
|
onBack = { showWizard = false },
|
||||||
onFinish = {
|
onFinish = {
|
||||||
showWizard = false
|
showWizard = false
|
||||||
viewModel.addTurnier() // Dummy zum Hinzufügen im Haupt-Wizard
|
viewModel.addTurnier(turnierViewModel.state.turnierNr, "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import at.mocode.core.domain.serialization.UuidSerializer
|
||||||
import at.mocode.frontend.core.auth.data.local.AuthTokenManager
|
import at.mocode.frontend.core.auth.data.local.AuthTokenManager
|
||||||
import at.mocode.frontend.core.domain.repository.MasterdataRepository
|
import at.mocode.frontend.core.domain.repository.MasterdataRepository
|
||||||
import at.mocode.frontend.core.domain.repository.MasterdataStats
|
import at.mocode.frontend.core.domain.repository.MasterdataStats
|
||||||
|
import at.mocode.frontend.core.domain.zns.ZnsImportProvider
|
||||||
|
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
|
||||||
import at.mocode.frontend.core.network.NetworkConfig
|
import at.mocode.frontend.core.network.NetworkConfig
|
||||||
|
import at.mocode.frontend.features.turnier.presentation.TurnierWizardViewModel
|
||||||
import at.mocode.frontend.features.verein.domain.VereinRepository
|
import at.mocode.frontend.features.verein.domain.VereinRepository
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
|
|
@ -55,7 +58,8 @@ data class VeranstaltungWizardState(
|
||||||
val createdVeranstaltungId: Uuid? = null,
|
val createdVeranstaltungId: Uuid? = null,
|
||||||
val isZnsAvailable: Boolean = false,
|
val isZnsAvailable: Boolean = false,
|
||||||
val stammdatenStats: MasterdataStats? = null,
|
val stammdatenStats: MasterdataStats? = null,
|
||||||
val isCheckingStats: Boolean = false
|
val isCheckingStats: Boolean = false,
|
||||||
|
val znsSearchResults: List<ZnsRemoteVerein> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalUuidApi::class)
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
|
|
@ -63,7 +67,9 @@ class VeranstaltungWizardViewModel(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
private val authTokenManager: AuthTokenManager,
|
private val authTokenManager: AuthTokenManager,
|
||||||
private val vereinRepository: VereinRepository,
|
private val vereinRepository: VereinRepository,
|
||||||
private val masterdataRepository: MasterdataRepository
|
private val masterdataRepository: MasterdataRepository,
|
||||||
|
private val znsImportProvider: ZnsImportProvider,
|
||||||
|
val turnierWizardViewModel: TurnierWizardViewModel // Injected Child-ViewModel
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var state by mutableStateOf(VeranstaltungWizardState())
|
var state by mutableStateOf(VeranstaltungWizardState())
|
||||||
|
|
@ -98,19 +104,45 @@ class VeranstaltungWizardViewModel(
|
||||||
|
|
||||||
fun searchVeranstalterByOepsNr(oepsNr: String) {
|
fun searchVeranstalterByOepsNr(oepsNr: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val verein = vereinRepository.findByOepsNr(oepsNr)
|
try {
|
||||||
if (verein != null) {
|
val verein = vereinRepository.findByOepsNr(oepsNr)
|
||||||
setVeranstalter(
|
if (verein != null) {
|
||||||
id = Uuid.parse(verein.id),
|
// Robustes Parsing für Mock-Daten (z. B. "v1")
|
||||||
nummer = verein.oepsNr ?: "",
|
val uuid = try {
|
||||||
name = verein.name,
|
Uuid.parse(verein.id)
|
||||||
standardOrt = "${verein.plz ?: ""} ${verein.ort ?: ""}".trim(),
|
} catch (_: Exception) {
|
||||||
logo = null // Hier könnte später ein Logo-Service greifen
|
// Fallback für Mock-IDs während der Entwicklung
|
||||||
)
|
Uuid.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
setVeranstalter(
|
||||||
|
id = uuid,
|
||||||
|
nummer = verein.oepsNr ?: "",
|
||||||
|
name = verein.name,
|
||||||
|
standardOrt = "${verein.plz ?: ""} ${verein.ort ?: ""}".trim(),
|
||||||
|
logo = null
|
||||||
|
)
|
||||||
|
} else if (oepsNr.length >= 3) {
|
||||||
|
// Suche in den ZNS-Stammdaten als Fallback
|
||||||
|
znsImportProvider.searchRemote(oepsNr)
|
||||||
|
state = state.copy(znsSearchResults = znsImportProvider.state.remoteResults)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
state = state.copy(error = "Fehler bei der Veranstalter-Suche: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun selectZnsVerein(znsVerein: ZnsRemoteVerein) {
|
||||||
|
setVeranstalter(
|
||||||
|
id = Uuid.random(), // Neuer Veranstalter wird angelegt
|
||||||
|
nummer = znsVerein.oepsNummer,
|
||||||
|
name = znsVerein.name,
|
||||||
|
standardOrt = znsVerein.ort ?: "",
|
||||||
|
logo = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun nextStep() {
|
fun nextStep() {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
currentStep = when (state.currentStep) {
|
currentStep = when (state.currentStep) {
|
||||||
|
|
@ -155,23 +187,13 @@ class VeranstaltungWizardViewModel(
|
||||||
state = state.copy(name = name, ort = ort, startDatum = start, endDatum = end, logoUrl = logo)
|
state = state.copy(name = name, ort = ort, startDatum = start, endDatum = end, logoUrl = logo)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTurnier(index: Int, nummer: String, path: String?) {
|
fun addTurnier(nummer: String = "", pfad: String? = null) {
|
||||||
val newList = state.turniere.toMutableList()
|
state = state.copy(turniere = state.turniere + TurnierEntry(nummer = nummer, ausschreibungPath = pfad))
|
||||||
if (index in newList.indices) {
|
|
||||||
newList[index] = newList[index].copy(nummer = nummer, ausschreibungPath = path)
|
|
||||||
state = state.copy(turniere = newList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addTurnier() {
|
|
||||||
state = state.copy(turniere = state.turniere + TurnierEntry())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeTurnier(index: Int) {
|
fun removeTurnier(index: Int) {
|
||||||
if (state.turniere.size > 1) {
|
val newList = state.turniere.toMutableList().apply { removeAt(index) }
|
||||||
val newList = state.turniere.toMutableList().apply { removeAt(index) }
|
state = state.copy(turniere = newList)
|
||||||
state = state.copy(turniere = newList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveVeranstaltung() {
|
fun saveVeranstaltung() {
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,12 @@ fun VeranstaltungenScreen(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltungen - verwalten",
|
text = "Events - verwalten",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
MsButton(
|
MsButton(
|
||||||
text = "Neue Veranstaltung",
|
text = "Neues Event",
|
||||||
onClick = onVeranstaltungNeu
|
onClick = onVeranstaltungNeu
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +119,7 @@ fun VeranstaltungenScreen(
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(Dimens.SpacingM))
|
Spacer(Modifier.height(Dimens.SpacingM))
|
||||||
Text(
|
Text(
|
||||||
"Keine Veranstaltungen gefunden.",
|
"Keine Events gefunden.",
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,10 @@ fun DesktopApp() {
|
||||||
// DeviceInitialization-Check beim Start
|
// DeviceInitialization-Check beim Start
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (!DeviceInitializationSettingsManager.isConfigured()) {
|
if (!DeviceInitializationSettingsManager.isConfigured()) {
|
||||||
|
println("[DesktopApp] Setup fehlt -> Umleitung zum DeviceInitialization")
|
||||||
nav.navigateToScreen(AppScreen.DeviceInitialization)
|
nav.navigateToScreen(AppScreen.DeviceInitialization)
|
||||||
|
} else {
|
||||||
|
println("[DesktopApp] Setup vorhanden.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,22 +50,32 @@ fun DesktopApp() {
|
||||||
// Login-Gate: Nicht-authentifizierte Screens → Login, außer DeviceInitialization ist erlaubt
|
// Login-Gate: Nicht-authentifizierte Screens → Login, außer DeviceInitialization ist erlaubt
|
||||||
// Vision_03 Update: Wir starten mit DeviceInitialization
|
// Vision_03 Update: Wir starten mit DeviceInitialization
|
||||||
if (!authState.isAuthenticated && currentScreen !is AppScreen.Login && currentScreen !is AppScreen.DeviceInitialization
|
if (!authState.isAuthenticated && currentScreen !is AppScreen.Login && currentScreen !is AppScreen.DeviceInitialization
|
||||||
&& currentScreen !is AppScreen.VeranstaltungVerwaltung
|
&& currentScreen !is AppScreen.EventVerwaltung
|
||||||
&& currentScreen !is AppScreen.VeranstalterAuswahl && currentScreen !is AppScreen.VeranstalterNeu
|
&& currentScreen !is AppScreen.VeranstalterAuswahl && currentScreen !is AppScreen.VeranstalterNeu
|
||||||
&& currentScreen !is AppScreen.VeranstalterDetail && currentScreen !is AppScreen.VeranstaltungKonfig
|
&& currentScreen !is AppScreen.VeranstalterDetail && currentScreen !is AppScreen.EventKonfig
|
||||||
&& currentScreen !is AppScreen.VeranstaltungProfil && currentScreen !is AppScreen.TurnierDetail
|
&& currentScreen !is AppScreen.EventProfil && currentScreen !is AppScreen.TurnierDetail
|
||||||
&& currentScreen !is AppScreen.TurnierNeu
|
&& currentScreen !is AppScreen.TurnierNeu
|
||||||
&& currentScreen !is AppScreen.ReiterVerwaltung
|
&& currentScreen !is AppScreen.ReiterVerwaltung && currentScreen !is AppScreen.Reiter
|
||||||
&& currentScreen !is AppScreen.PferdVerwaltung
|
&& currentScreen !is AppScreen.PferdVerwaltung && currentScreen !is AppScreen.Pferde
|
||||||
&& currentScreen !is AppScreen.VereinVerwaltung
|
&& currentScreen !is AppScreen.VereinVerwaltung && currentScreen !is AppScreen.Vereine
|
||||||
|
&& currentScreen !is AppScreen.FunktionaerVerwaltung && currentScreen !is AppScreen.FunktionaerProfil
|
||||||
|
&& currentScreen !is AppScreen.ReiterProfil
|
||||||
|
&& currentScreen !is AppScreen.PferdProfil
|
||||||
|
&& currentScreen !is AppScreen.VereinProfil
|
||||||
&& currentScreen !is AppScreen.StammdatenImport
|
&& currentScreen !is AppScreen.StammdatenImport
|
||||||
&& currentScreen !is AppScreen.NennungsEingang
|
&& currentScreen !is AppScreen.NennungsEingang
|
||||||
&& currentScreen !is AppScreen.VeranstaltungNeu
|
&& currentScreen !is AppScreen.EventNeu
|
||||||
&& currentScreen !is AppScreen.ConnectivityCheck
|
&& currentScreen !is AppScreen.ConnectivityCheck
|
||||||
|
&& currentScreen !is AppScreen.Dashboard
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(currentScreen) {
|
||||||
// Standard: Start im DeviceInitialization
|
if (!DeviceInitializationSettingsManager.isConfigured()) {
|
||||||
nav.navigateToScreen(AppScreen.DeviceInitialization)
|
println("[DesktopApp] Nicht authentifiziert & nicht konfiguriert -> Setup")
|
||||||
|
nav.navigateToScreen(AppScreen.DeviceInitialization)
|
||||||
|
} else {
|
||||||
|
println("[DesktopApp] Nicht authentifiziert, aber konfiguriert -> Dashboard")
|
||||||
|
nav.navigateToScreen(AppScreen.EventVerwaltung)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +83,7 @@ fun DesktopApp() {
|
||||||
is AppScreen.Login -> LoginScreen(
|
is AppScreen.Login -> LoginScreen(
|
||||||
viewModel = loginViewModel,
|
viewModel = loginViewModel,
|
||||||
onLoginSuccess = {
|
onLoginSuccess = {
|
||||||
val returnTo = screen.returnTo ?: AppScreen.VeranstaltungVerwaltung
|
val returnTo = screen.returnTo ?: AppScreen.EventVerwaltung
|
||||||
nav.navigateToScreen(returnTo)
|
nav.navigateToScreen(returnTo)
|
||||||
},
|
},
|
||||||
onBack = { nav.navigateBack() },
|
onBack = { nav.navigateBack() },
|
||||||
|
|
@ -84,7 +97,7 @@ fun DesktopApp() {
|
||||||
onBack = { nav.navigateBack() },
|
onBack = { nav.navigateBack() },
|
||||||
onLogout = {
|
onLogout = {
|
||||||
authTokenManager.clearToken()
|
authTokenManager.clearToken()
|
||||||
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.VeranstaltungVerwaltung))
|
nav.navigateToScreen(AppScreen.Login(returnTo = AppScreen.EventVerwaltung))
|
||||||
},
|
},
|
||||||
isAuthenticated = authState.isAuthenticated
|
isAuthenticated = authState.isAuthenticated
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,15 @@ fun DesktopMainLayout(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatische Umleitung zum DeviceInitialization, wenn Setup fehlt (außer wir sind bereits dort)
|
// Automatische Umleitung zum DeviceInitialization, wenn Setup fehlt (außer wir sind bereits dort)
|
||||||
LaunchedEffect(onboardingSettings) {
|
LaunchedEffect(currentScreen) {
|
||||||
if (!onboardingSettings.isConfigured && currentScreen !is AppScreen.DeviceInitialization) {
|
if (!onboardingSettings.isConfigured && currentScreen !is AppScreen.DeviceInitialization) {
|
||||||
println("[DesktopNav] Setup fehlt -> Umleitung zum DeviceInitialization")
|
println("[DesktopNav] Setup fehlt -> Umleitung zum DeviceInitialization")
|
||||||
onNavigate(AppScreen.DeviceInitialization)
|
onNavigate(AppScreen.DeviceInitialization)
|
||||||
} else if (onboardingSettings.isConfigured && currentScreen is AppScreen.DeviceInitialization) {
|
} else if (onboardingSettings.isConfigured && currentScreen is AppScreen.DeviceInitialization) {
|
||||||
println("[DesktopNav] Setup abgeschlossen -> Wechsel zum Dashboard")
|
// Falls wir konfiguriert sind, aber im Setup-Screen landen (z.B. durch manuellen Nav-Call),
|
||||||
onNavigate(AppScreen.VeranstaltungVerwaltung)
|
// erlauben wir den Aufenthalt dort (für Edit), aber forcieren keinen Redirect zum Dashboard hier,
|
||||||
|
// da dies der Wizard am Ende selbst macht.
|
||||||
|
println("[DesktopNav] Setup vorhanden und im Setup-Screen.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ import at.mocode.frontend.features.pferde.presentation.PferdeScreen
|
||||||
import at.mocode.frontend.features.pferde.presentation.PferdeViewModel
|
import at.mocode.frontend.features.pferde.presentation.PferdeViewModel
|
||||||
import at.mocode.frontend.features.ping.presentation.PingScreen
|
import at.mocode.frontend.features.ping.presentation.PingScreen
|
||||||
import at.mocode.frontend.features.ping.presentation.PingViewModel
|
import at.mocode.frontend.features.ping.presentation.PingViewModel
|
||||||
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingWizard
|
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingScreen
|
||||||
|
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingViewModel
|
||||||
import at.mocode.frontend.features.profile.presentation.ProfileScreen
|
import at.mocode.frontend.features.profile.presentation.ProfileScreen
|
||||||
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
||||||
import at.mocode.frontend.features.reiter.presentation.ReiterScreen
|
import at.mocode.frontend.features.reiter.presentation.ReiterScreen
|
||||||
|
|
@ -70,17 +71,18 @@ fun DesktopContentArea(
|
||||||
val authTokenManager = org.koin.core.context.GlobalContext.get().get<AuthTokenManager>()
|
val authTokenManager = org.koin.core.context.GlobalContext.get().get<AuthTokenManager>()
|
||||||
authTokenManager.setToken(finalSettings.sharedKey)
|
authTokenManager.setToken(finalSettings.sharedKey)
|
||||||
onSettingsChange(finalSettings)
|
onSettingsChange(finalSettings)
|
||||||
onNavigate(AppScreen.VeranstaltungVerwaltung)
|
// nav.navigateToScreen(...) wird hier nicht direkt gerufen, sondern onNavigate
|
||||||
|
onNavigate(AppScreen.EventVerwaltung)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DeviceInitializationScreen(viewModel = viewModel)
|
DeviceInitializationScreen(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Haupt-Zentrale: Veranstaltung-Verwaltung
|
// Haupt-Zentrale: Event-Verwaltung
|
||||||
is AppScreen.VeranstaltungVerwaltung -> {
|
is AppScreen.EventVerwaltung -> {
|
||||||
VeranstaltungenScreen(
|
VeranstaltungenScreen(
|
||||||
onVeranstaltungNeu = { onNavigate(AppScreen.VeranstaltungNeu) },
|
onVeranstaltungNeu = { onNavigate(AppScreen.EventNeu) },
|
||||||
onVeranstaltungOeffnen = { vId: Long, eId: Long -> onNavigate(AppScreen.VeranstaltungProfil(vId, eId)) }
|
onVeranstaltungOeffnen = { vId: Long, eId: Long -> onNavigate(AppScreen.EventProfil(vId, eId)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +93,15 @@ fun DesktopContentArea(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Profile Onboarding ---
|
||||||
|
is AppScreen.ProfileOnboarding -> {
|
||||||
|
val viewModel = koinViewModel<ProfileOnboardingViewModel>()
|
||||||
|
ProfileOnboardingScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onFinish = { onNavigate(AppScreen.EventVerwaltung) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// --- Pferde-Verwaltung & Profil ---
|
// --- Pferde-Verwaltung & Profil ---
|
||||||
is AppScreen.Pferde, is AppScreen.PferdVerwaltung -> {
|
is AppScreen.Pferde, is AppScreen.PferdVerwaltung -> {
|
||||||
val viewModel = koinViewModel<PferdeViewModel>()
|
val viewModel = koinViewModel<PferdeViewModel>()
|
||||||
|
|
@ -165,14 +176,14 @@ fun DesktopContentArea(
|
||||||
is AppScreen.VeranstalterProfil -> VeranstalterDetail(
|
is AppScreen.VeranstalterProfil -> VeranstalterDetail(
|
||||||
veranstalterId = currentScreen.id,
|
veranstalterId = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
|
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.EventProfil(currentScreen.id, evtId)) },
|
||||||
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungNeu) },
|
onNeuVeranstaltung = { onNavigate(AppScreen.EventNeu) },
|
||||||
)
|
)
|
||||||
|
|
||||||
// Neuer Flow: Veranstalter auswählen → Veranstaltung-Wizard
|
// Neuer Flow: Veranstalter auswählen → Event-Wizard
|
||||||
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahl(
|
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahl(
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onWeiter = { _ -> onNavigate(AppScreen.VeranstaltungNeu) },
|
onWeiter = { _ -> onNavigate(AppScreen.EventNeu) },
|
||||||
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -186,12 +197,12 @@ fun DesktopContentArea(
|
||||||
VeranstalterDetail(
|
VeranstalterDetail(
|
||||||
veranstalterId = vId,
|
veranstalterId = vId,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.VeranstaltungProfil(vId, evtId)) },
|
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.EventProfil(vId, evtId)) },
|
||||||
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(vId)) },
|
onNeuVeranstaltung = { onNavigate(AppScreen.EventKonfig(vId)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungKonfig -> {
|
is AppScreen.EventKonfig -> {
|
||||||
val vId = currentScreen.veranstalterId
|
val vId = currentScreen.veranstalterId
|
||||||
VeranstaltungKonfigScreen(
|
VeranstaltungKonfigScreen(
|
||||||
veranstalterId = vId,
|
veranstalterId = vId,
|
||||||
|
|
@ -201,12 +212,12 @@ fun DesktopContentArea(
|
||||||
// val allEvents = Store.allEvents()
|
// val allEvents = Store.allEvents()
|
||||||
// val newId = (allEvents.maxOfOrNull { it.id } ?: 0L) + 1L
|
// val newId = (allEvents.maxOfOrNull { it.id } ?: 0L) + 1L
|
||||||
// ...
|
// ...
|
||||||
onNavigate(AppScreen.VeranstaltungProfil(vId, 0L)) // Mock
|
onNavigate(AppScreen.EventProfil(vId, 0L)) // Mock
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungProfil -> {
|
is AppScreen.EventProfil -> {
|
||||||
VeranstaltungProfilScreen(
|
VeranstaltungProfilScreen(
|
||||||
veranstalterId = currentScreen.veranstalterId,
|
veranstalterId = currentScreen.veranstalterId,
|
||||||
veranstaltungId = currentScreen.veranstaltungId,
|
veranstaltungId = currentScreen.veranstaltungId,
|
||||||
|
|
@ -223,7 +234,7 @@ fun DesktopContentArea(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungDetail -> {
|
is AppScreen.EventDetail -> {
|
||||||
val repository: at.mocode.veranstaltung.feature.domain.repository.VeranstaltungRepository = koinInject()
|
val repository: at.mocode.veranstaltung.feature.domain.repository.VeranstaltungRepository = koinInject()
|
||||||
VeranstaltungDetailScreen(
|
VeranstaltungDetailScreen(
|
||||||
veranstaltungId = currentScreen.id,
|
veranstaltungId = currentScreen.id,
|
||||||
|
|
@ -235,7 +246,7 @@ fun DesktopContentArea(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungNeu -> {
|
is AppScreen.EventNeu -> {
|
||||||
val viewModel: at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardViewModel = koinViewModel()
|
val viewModel: at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardViewModel = koinViewModel()
|
||||||
at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardScreen(
|
at.mocode.veranstaltung.feature.presentation.VeranstaltungWizardScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|
@ -323,18 +334,13 @@ fun DesktopContentArea(
|
||||||
ProfileScreen(viewModel = viewModel)
|
ProfileScreen(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.ProfileOnboarding -> {
|
|
||||||
val viewModel = koinViewModel<ProfileViewModel>()
|
|
||||||
ProfileOnboardingWizard(
|
|
||||||
viewModel = viewModel,
|
|
||||||
onFinish = { onNavigate(AppScreen.Dashboard) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is AppScreen.Home, is AppScreen.Dashboard -> {
|
is AppScreen.Home, is AppScreen.Dashboard, is AppScreen.PortalDashboard,
|
||||||
|
is AppScreen.Meisterschaften, is AppScreen.Cups,
|
||||||
|
is AppScreen.CreateTournament, is AppScreen.OrganizerProfile -> {
|
||||||
AdminUebersichtScreen(
|
AdminUebersichtScreen(
|
||||||
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||||
onVeranstaltungOeffnen = { id -> onNavigate(AppScreen.VeranstaltungDetail(id)) }
|
onVeranstaltungOeffnen = { id -> onNavigate(AppScreen.EventDetail(id)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,21 +35,13 @@ fun DesktopNavRail(
|
||||||
icon = Icons.Default.Adjust,
|
icon = Icons.Default.Adjust,
|
||||||
label = "Logo",
|
label = "Logo",
|
||||||
selected = false,
|
selected = false,
|
||||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
onClick = { onNavigate(AppScreen.EventVerwaltung) },
|
||||||
enabled = isConfigured
|
enabled = isConfigured
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(Modifier.height(Dimens.SpacingL))
|
Spacer(Modifier.height(Dimens.SpacingL))
|
||||||
|
|
||||||
// Navigations-Items
|
// Navigations-Items
|
||||||
NavRailItem(
|
|
||||||
icon = Icons.Default.Dashboard,
|
|
||||||
label = "Admin",
|
|
||||||
selected = currentScreen is AppScreen.VeranstaltungVerwaltung || currentScreen is AppScreen.VeranstaltungDetail,
|
|
||||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
|
||||||
enabled = isConfigured
|
|
||||||
)
|
|
||||||
|
|
||||||
NavRailItem(
|
NavRailItem(
|
||||||
icon = Icons.Default.CloudDownload,
|
icon = Icons.Default.CloudDownload,
|
||||||
label = "ZNS-Import",
|
label = "ZNS-Import",
|
||||||
|
|
@ -101,7 +93,7 @@ fun DesktopNavRail(
|
||||||
leadingIcon = { Icon(Icons.Default.Pets, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Pets, contentDescription = null) }
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Richter") },
|
text = { Text("Funktionäre") },
|
||||||
onClick = {
|
onClick = {
|
||||||
showStammdatenMenu = false
|
showStammdatenMenu = false
|
||||||
onNavigate(AppScreen.FunktionaerVerwaltung)
|
onNavigate(AppScreen.FunktionaerVerwaltung)
|
||||||
|
|
@ -111,6 +103,43 @@ fun DesktopNavRail(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showVerwaltungMenu by remember { mutableStateOf(false) }
|
||||||
|
Box {
|
||||||
|
NavRailItem(
|
||||||
|
icon = Icons.Default.Dashboard,
|
||||||
|
label = "Verwaltungen",
|
||||||
|
selected = currentScreen is AppScreen.EventVerwaltung ||
|
||||||
|
currentScreen is AppScreen.EventDetail ||
|
||||||
|
currentScreen is AppScreen.VeranstalterVerwaltung ||
|
||||||
|
currentScreen is AppScreen.VeranstalterAuswahl,
|
||||||
|
onClick = { showVerwaltungMenu = true },
|
||||||
|
enabled = isConfigured
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showVerwaltungMenu && isConfigured,
|
||||||
|
onDismissRequest = { showVerwaltungMenu = false },
|
||||||
|
offset = DpOffset(Dimens.NavRailWidth, 0.dp)
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Veranstalter") },
|
||||||
|
onClick = {
|
||||||
|
showVerwaltungMenu = false
|
||||||
|
onNavigate(AppScreen.VeranstalterVerwaltung)
|
||||||
|
},
|
||||||
|
leadingIcon = { Icon(Icons.Default.Business, contentDescription = null) }
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Events") },
|
||||||
|
onClick = {
|
||||||
|
showVerwaltungMenu = false
|
||||||
|
onNavigate(AppScreen.EventVerwaltung)
|
||||||
|
},
|
||||||
|
leadingIcon = { Icon(Icons.Default.Event, contentDescription = null) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NavRailItem(
|
NavRailItem(
|
||||||
icon = Icons.Default.Email,
|
icon = Icons.Default.Email,
|
||||||
label = "Mails",
|
label = "Mails",
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ fun DesktopTopHeader(
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
// Zurück-Button ausblenden auf Startseite oder im Setup
|
// Zurück-Button ausblenden auf Startseite oder im Setup
|
||||||
if (currentScreen !is AppScreen.DeviceInitialization && currentScreen !is AppScreen.VeranstaltungVerwaltung) {
|
if (currentScreen !is AppScreen.DeviceInitialization && currentScreen !is AppScreen.EventVerwaltung) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
// Verhindere Rücksprung zum Setup, wenn konfiguriert
|
// Verhindere Rücksprung zum Setup, wenn konfiguriert
|
||||||
|
|
@ -65,7 +65,7 @@ fun DesktopTopHeader(
|
||||||
|
|
||||||
// Home Icon als Anker
|
// Home Icon als Anker
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
onClick = { onNavigate(AppScreen.EventVerwaltung) },
|
||||||
modifier = Modifier.size(Dimens.IconSizeM),
|
modifier = Modifier.size(Dimens.IconSizeM),
|
||||||
enabled = isConfigured
|
enabled = isConfigured
|
||||||
) {
|
) {
|
||||||
|
|
@ -207,7 +207,7 @@ private fun BreadcrumbContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungProfil -> {
|
is AppScreen.EventProfil -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstalter-Verwaltung",
|
text = "Veranstalter-Verwaltung",
|
||||||
|
|
@ -224,43 +224,43 @@ private fun BreadcrumbContent(
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
text = "Event #${currentScreen.veranstaltungId}",
|
||||||
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungVerwaltung -> {
|
is AppScreen.EventVerwaltung -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltungs-Verwaltung",
|
text = "Event-Verwaltung",
|
||||||
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungDetail -> {
|
is AppScreen.EventDetail -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltungs-Verwaltung",
|
text = "Event-Verwaltung",
|
||||||
style = textStyle.copy(color = clickableColor),
|
style = textStyle.copy(color = clickableColor),
|
||||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
modifier = Modifier.clickable { onNavigate(AppScreen.EventVerwaltung) },
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltung #${currentScreen.id}",
|
text = "Event #${currentScreen.id}",
|
||||||
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.VeranstaltungNeu -> {
|
is AppScreen.EventNeu -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltungs-Verwaltung",
|
text = "Event-Verwaltung",
|
||||||
style = textStyle.copy(color = clickableColor),
|
style = textStyle.copy(color = clickableColor),
|
||||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
modifier = Modifier.clickable { onNavigate(AppScreen.EventVerwaltung) },
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Neue Veranstaltung",
|
text = "Neues Event",
|
||||||
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -268,10 +268,10 @@ private fun BreadcrumbContent(
|
||||||
is AppScreen.TurnierDetail -> {
|
is AppScreen.TurnierDetail -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
text = "Event #${currentScreen.veranstaltungId}",
|
||||||
style = textStyle.copy(color = clickableColor),
|
style = textStyle.copy(color = clickableColor),
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
|
|
@ -284,10 +284,10 @@ private fun BreadcrumbContent(
|
||||||
is AppScreen.TurnierNeu -> {
|
is AppScreen.TurnierNeu -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
text = "Event #${currentScreen.veranstaltungId}",
|
||||||
style = textStyle.copy(color = clickableColor),
|
style = textStyle.copy(color = clickableColor),
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
|
|
@ -300,10 +300,10 @@ private fun BreadcrumbContent(
|
||||||
is AppScreen.Billing -> {
|
is AppScreen.Billing -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltung #${currentScreen.veranstaltungId}",
|
text = "Event #${currentScreen.veranstaltungId}",
|
||||||
style = textStyle.copy(color = clickableColor),
|
style = textStyle.copy(color = clickableColor),
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId))
|
onNavigate(AppScreen.EventDetail(currentScreen.veranstaltungId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
|
|
@ -356,7 +356,7 @@ private fun BreadcrumbContent(
|
||||||
is AppScreen.FunktionaerVerwaltung -> {
|
is AppScreen.FunktionaerVerwaltung -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Richter-Verwaltung",
|
text = "Funktionär-Verwaltung",
|
||||||
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
style = textStyle.copy(fontWeight = FontWeight.SemiBold, color = activeColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Event
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material.icons.filled.Place
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -33,7 +36,7 @@ fun VeranstaltungProfilScreen(
|
||||||
val turniere = TurnierStore.list(veranstaltungId)
|
val turniere = TurnierStore.list(veranstaltungId)
|
||||||
|
|
||||||
if (veranstaltung == null) {
|
if (veranstaltung == null) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Veranstaltung nicht gefunden") }
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Event nicht gefunden") }
|
||||||
return@DesktopTheme
|
return@DesktopTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +68,7 @@ fun VeranstaltungProfilScreen(
|
||||||
KpiCard("Ort", veranstaltung.ort, Icons.Default.Place, Modifier.weight(1f))
|
KpiCard("Ort", veranstaltung.ort, Icons.Default.Place, Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Turniere in dieser Veranstaltung", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
Text("Turniere in diesem Event", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||||
if (turniere.isEmpty()) {
|
if (turniere.isEmpty()) {
|
||||||
Card(Modifier.fillMaxWidth()) {
|
Card(Modifier.fillMaxWidth()) {
|
||||||
Box(Modifier.padding(32.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
Box(Modifier.padding(32.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
|
|
@ -81,7 +84,7 @@ fun VeranstaltungProfilScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rechte Spalte: Veranstalter Info & Aktionen
|
// Rechte Spalte: Veranstalter Information & Aktionen
|
||||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Card {
|
Card {
|
||||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user