Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c54ad3830d | |||
| d66bd63cc9 | |||
| 3b4e3db51d | |||
| 2d7046d0e3 |
@@ -229,6 +229,17 @@ Journal: `docs/99_Journal/2026-04-21_Wizard-Orchestrator_Roadmap_Anchoring.md`
|
|||||||
|
|
||||||
## 3. Aktuelle Phase
|
## 3. Aktuelle Phase
|
||||||
|
|
||||||
|
### Progress Checkpoint – 2026-04-21 (Wizard-Orchestrator Initiative)
|
||||||
|
|
||||||
|
- Core/DSL: angelegt in `frontend/core/wizard` (Runtime, DSL), erste Tests grün.
|
||||||
|
- UI: `WizardScaffold` (MVP) + Hotkeys-Wrapper (Enter/Shift+Enter/Alt+S) vorhanden.
|
||||||
|
- Feature-Integration: Veranstaltungs-Wizard hinter Flag teilweise delegiert.
|
||||||
|
- Drafts: In‑Memory DraftStore (Autosave/Resume Hooks) angebunden.
|
||||||
|
- DI: Koin-Parameterübergabe für `EventWizardViewModel` vereinheitlicht.
|
||||||
|
- Flag: `WizardRuntimeEnabled = false` (Standard AUS; Dev-Verprobung manuell).
|
||||||
|
|
||||||
|
Nächste Schritte (Kurz): Tests für `needsContactPerson` (beide Zweige), VM‑Delegation für weitere Steps, Footer‑Fehler‑Summary, persistenter DraftStore, Dev‑Overlay.
|
||||||
|
|
||||||
### PHASE 5: P2-Contexts & Integration ✅ ABGESCHLOSSEN
|
### PHASE 5: P2-Contexts & Integration ✅ ABGESCHLOSSEN
|
||||||
|
|
||||||
*Ziel: `competition-context` und `event-management-context` implementieren.*
|
*Ziel: `competition-context` und `event-management-context` implementieren.*
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# 🧹 [Curator] Session-Log – Wizard-Orchestrator Migration (Pause)
|
||||||
|
|
||||||
|
Datum: 2026-04-21 · Kontext: Desktop-First, Offline-First · Initiative: Wizard-Orchestrator & Offline-Drafts
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
- Spike abgeschlossen: neues Modul `frontend/core/wizard` mit Runtime + Mini-DSL + UI-Scaffold (MVP) integriert.
|
||||||
|
- Erste Integration im Veranstaltungs-Flow hinter Feature-Flag; Koin-Parameterfehler behoben; Hotkeys + DraftStore (MVP) ergänzt.
|
||||||
|
- Build und Tests grün; Session wird hier sauber pausiert.
|
||||||
|
|
||||||
|
## Erreichte Ergebnisse (Inkrement Stand heute)
|
||||||
|
- Modul `:frontend:core:wizard`
|
||||||
|
- Runtime/DSL: `StepId`, `WizardContext`, `WizardState`, `Guard`, `Transition`, `StepEffects`, `WizardRuntime`.
|
||||||
|
- DSL: `flow { step { whenGuard(id) go target; otherwise go … } }`.
|
||||||
|
- Samples/Tests: Demo-Event-Flow (2+ Steps), Jvm/Common-Tests (Next/Back/Guards) – 6/6 grün.
|
||||||
|
- UI
|
||||||
|
- `WizardScaffold` (Header/Breadcrumb/Footer-Slots), `WizardScaffoldWithHotkeys` (Enter/Shift+Enter/Alt+S).
|
||||||
|
- Feature-Integration (hinter Flag)
|
||||||
|
- `EventWizardScreen` nutzt Scaffold-Variante bei aktivem Flag.
|
||||||
|
- `EventWizardViewModel` teils delegiert; Guard-Pfade (Ansprechperson/Meta) im Demo-Flow vorhanden.
|
||||||
|
- DI
|
||||||
|
- Koin-Parameterübergabe fix: `parametersOf(null as Long?)` für `AppScreen.EventNeu`.
|
||||||
|
- Drafts (MVP)
|
||||||
|
- In-Memory `DraftStore` für Autosave/Resume-Hooks angebunden.
|
||||||
|
|
||||||
|
## Verifikation
|
||||||
|
- Build: erfolgreich (Desktop-Konfiguration).
|
||||||
|
- Tests: Wizard-Modul 6/6 grün (Guards, Transitionen, Back/Next).
|
||||||
|
- Manueller Smoke: Navigation „Event neu“ startet ohne Koin-Fehler.
|
||||||
|
|
||||||
|
## Offene Punkte / Nächste Schritte (Resume-Plan)
|
||||||
|
1) Tests ergänzen
|
||||||
|
- Branch-Abdeckung für neuen Guard `needsContactPerson` finalisieren (true/false beide Pfade).
|
||||||
|
- Property-Test: Resume-Logik (Draft → korrekter Step) vorbereiten.
|
||||||
|
2) Event-Flow weiter migrieren
|
||||||
|
- VM-Delegation für weitere Steps (ANSPRECHPERSON_MAPPING, META_DATA) komplettieren.
|
||||||
|
- Mapping `VeranstaltungWizardState ↔ Acc` erweitern.
|
||||||
|
3) WizardScaffold UX
|
||||||
|
- Fehler-Summary im Footer anbinden (aktuell minimal/Platzhalter).
|
||||||
|
- Dev-Overlay (aktueller Step, Guard-Entscheide) optional aktivierbar.
|
||||||
|
4) DraftStore
|
||||||
|
- Persistente Speicherung (Datei/DB) statt in-memory; `flowVersion` + Migration-Schnittstellen.
|
||||||
|
5) Doku/ADRs
|
||||||
|
- ADR-0025/0026/0027 Status auf "PROPOSED"/"IN REVIEW" prüfen und ggf. aktualisieren.
|
||||||
|
- README Wizard-DSL verlinken; DI-Parameterkonventionen ergänzen.
|
||||||
|
|
||||||
|
## Wie fortsetzen (Kurz-Anleitung)
|
||||||
|
- Feature-Flag: Standard AUS. Zum Verproben im Dev-Profil aktivieren (`WizardRuntimeEnabled = true`).
|
||||||
|
- Tests ausführen:
|
||||||
|
- Modul-weit: `./gradlew :frontend:core:wizard:jvmTest`
|
||||||
|
- Projekt-Build: `./gradlew build`
|
||||||
|
- Einstiegspunkt Desktop: `frontend/shells/meldestelle-desktop/.../main.kt` → `AppScreen.EventNeu` (prüft Flag & Scaffold).
|
||||||
|
|
||||||
|
## Relevante Dateien & Pfade
|
||||||
|
- Core Wizard
|
||||||
|
- `frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/runtime/WizardCore.kt`
|
||||||
|
- `frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/dsl/WizardDsl.kt`
|
||||||
|
- `frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardScaffold.kt`
|
||||||
|
- `frontend/core/wizard/src/jvmMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardHotkeys.kt`
|
||||||
|
- `frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/samples/EventFlowSample.kt`
|
||||||
|
- Tests: `frontend/core/wizard/src/{commonTest|jvmTest}/kotlin/.../WizardRuntimeTest*.kt`
|
||||||
|
- Feature-Integration
|
||||||
|
- `frontend/features/veranstaltung-feature/src/jvmMain/kotlin/at/mocode/veranstaltung/feature/presentation/EventWizardScreen.kt`
|
||||||
|
- `frontend/features/veranstaltung-feature/src/jvmMain/kotlin/at/mocode/veranstaltung/feature/di/VeranstaltungModule.kt`
|
||||||
|
- `frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/screens/layout/components/ContentArea.kt`
|
||||||
|
- Flags & Navigation
|
||||||
|
- `frontend/core/domain/src/commonMain/kotlin/at/mocode/frontend/core/domain/config/WizardFeatureFlags.kt`
|
||||||
|
- `frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt`
|
||||||
|
- Doku
|
||||||
|
- Roadmap: `docs/01_Architecture/MASTER_ROADMAP.md`
|
||||||
|
- Reference: `docs/01_Architecture/Reference/Wizard-DSL-README.md`
|
||||||
|
- ADRs: `docs/01_Architecture/adr/0025-wizard-orchestrator-de.md`, `0026-validation-policy-de.md`, `0027-draft-domain-delta-sync-de.md`
|
||||||
|
|
||||||
|
## Anmerkungen
|
||||||
|
- Strangler-Pattern bleibt aktiv (Flag AUS). Risiken: Guard-Sprawl, Draft-Versionierung. Gegenmaßnahmen in ADR-0025/0027 definiert.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Wizard-Orchestrator (Core Module)
|
||||||
|
|
||||||
|
Dieses Modul enthält die Runtime, DSL und UI-Grundbausteine für die deklarative Wizard-Orchestrierung.
|
||||||
|
|
||||||
|
## Status (2026-04-21)
|
||||||
|
- Runtime & DSL (Minimal): `StepId`, `WizardContext`, `WizardState`, `Guard`, `Transition`, `StepEffects`, `WizardRuntime`.
|
||||||
|
- UI: `WizardScaffold` (MVP) + `WizardScaffoldWithHotkeys` (Enter/Shift+Enter/Alt+S auf JVM).
|
||||||
|
- Samples/Tests: Demo-Event-Flow + 6 grüne Tests (Next/Back/Guards).
|
||||||
|
- Drafts: In‑Memory DraftStore (Autosave/Resume Hooks) vorbereitet.
|
||||||
|
|
||||||
|
## Struktur
|
||||||
|
- `src/commonMain/kotlin/at/mocode/frontend/core/wizard/runtime` – Kern-Interfaces & -State
|
||||||
|
- `src/commonMain/kotlin/at/mocode/frontend/core/wizard/dsl` – Flow-DSL
|
||||||
|
- `src/commonMain/kotlin/at/mocode/frontend/core/wizard/ui` – Scaffold/Composable Bausteine
|
||||||
|
- `src/commonMain/kotlin/at/mocode/frontend/core/wizard/samples` – Demo-Flow/Step-IDs
|
||||||
|
- `src/{commonTest|jvmTest}/kotlin/...` – Tests
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
Build & Tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :frontend:core:wizard:build
|
||||||
|
./gradlew :frontend:core:wizard:jvmTest
|
||||||
|
```
|
||||||
|
|
||||||
|
Feature-Flag aktivieren (Dev):
|
||||||
|
- Datei: `frontend/core/domain/src/commonMain/kotlin/at/mocode/frontend/core/domain/config/WizardFeatureFlags.kt`
|
||||||
|
- Eigenschaft: `WizardRuntimeEnabled = true` (Standard: false)
|
||||||
|
|
||||||
|
Startpunkt Desktop:
|
||||||
|
- `frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/main.kt`
|
||||||
|
- Screen: `AppScreen.EventNeu` (Koin-Parameter wird mit `parametersOf(null as Long?)` übergeben)
|
||||||
|
|
||||||
|
## Nächste Schritte (Kurz)
|
||||||
|
- Weitere Steps im Event-Flow migrieren (VM-Delegation, Mapping ↔ Acc erweitern)
|
||||||
|
- Fehler-Summary im Scaffold-Footer anbinden
|
||||||
|
- Persistenten DraftStore implementieren (`flowVersion`, Migrationen)
|
||||||
|
- Dev-Overlay (aktueller Step, Guard-Entscheide)
|
||||||
|
|
||||||
|
## Referenzen
|
||||||
|
- Roadmap: `docs/01_Architecture/MASTER_ROADMAP.md` (Initiative 3)
|
||||||
|
- ADR‑0025/0026/0027: Orchestrator, Validation-Policy, Draft-Domain
|
||||||
|
- Wizard‑DSL README: `docs/01_Architecture/Reference/Wizard-DSL-README.md`
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package at.mocode.frontend.core.wizard.draft
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimaler DraftStore (MVP): in‑Memory, versioniert per Key. Persistenz folgt später.
|
||||||
|
*/
|
||||||
|
object DraftStoreMemory {
|
||||||
|
private data class Entry(val version: Int, val stepId: String)
|
||||||
|
private val store = mutableMapOf<String, Entry>()
|
||||||
|
|
||||||
|
fun save(flowKey: String, version: Int, stepId: String) {
|
||||||
|
store[flowKey] = Entry(version, stepId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(flowKey: String): Pair<Int, String>? = store[flowKey]?.let { it.version to it.stepId }
|
||||||
|
}
|
||||||
+15
-1
@@ -10,12 +10,22 @@ import at.mocode.frontend.core.wizard.runtime.WizardState
|
|||||||
sealed interface DemoEventStep : StepId {
|
sealed interface DemoEventStep : StepId {
|
||||||
data object ZnsCheck : DemoEventStep
|
data object ZnsCheck : DemoEventStep
|
||||||
data object VeranstalterSelection : DemoEventStep
|
data object VeranstalterSelection : DemoEventStep
|
||||||
|
data object AnsprechpersonMapping : DemoEventStep
|
||||||
|
data object MetaData : DemoEventStep
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DemoEventAcc(val dummy: String = "")
|
data class DemoEventAcc(
|
||||||
|
val veranstalterId: String? = null,
|
||||||
|
val veranstalterNr: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
object DemoEventGuards {
|
object DemoEventGuards {
|
||||||
val hasZns: Guard<DemoEventStep, DemoEventAcc> = { ctx, _ -> (ctx.stats?.vereinCount ?: 0) > 0 }
|
val hasZns: Guard<DemoEventStep, DemoEventAcc> = { ctx, _ -> (ctx.stats?.vereinCount ?: 0) > 0 }
|
||||||
|
// Heuristik für Demo: Kontaktperson nötig, wenn keine Veranstalter-ID vorhanden
|
||||||
|
// oder die Nummer ein Organisations-Präfix trägt (z. B. „ORG-“)
|
||||||
|
val needsContactPerson: Guard<DemoEventStep, DemoEventAcc> = { _, acc ->
|
||||||
|
acc.veranstalterId == null || acc.veranstalterNr.startsWith("ORG-")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val DemoEventFlow = flow<DemoEventStep, DemoEventAcc>(start = DemoEventStep.ZnsCheck) {
|
val DemoEventFlow = flow<DemoEventStep, DemoEventAcc>(start = DemoEventStep.ZnsCheck) {
|
||||||
@@ -23,6 +33,10 @@ val DemoEventFlow = flow<DemoEventStep, DemoEventAcc>(start = DemoEventStep.ZnsC
|
|||||||
whenGuard("hasZns", DemoEventGuards.hasZns, go = DemoEventStep.VeranstalterSelection)
|
whenGuard("hasZns", DemoEventGuards.hasZns, go = DemoEventStep.VeranstalterSelection)
|
||||||
otherwise(DemoEventStep.VeranstalterSelection)
|
otherwise(DemoEventStep.VeranstalterSelection)
|
||||||
}
|
}
|
||||||
|
step(DemoEventStep.VeranstalterSelection) {
|
||||||
|
whenGuard("needsContactPerson", DemoEventGuards.needsContactPerson, go = DemoEventStep.AnsprechpersonMapping)
|
||||||
|
otherwise(DemoEventStep.MetaData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hilfsfunktion für einfache manuelle Nutzung im Spike
|
// Hilfsfunktion für einfache manuelle Nutzung im Spike
|
||||||
|
|||||||
+27
@@ -1,6 +1,9 @@
|
|||||||
package at.mocode.frontend.core.wizard.ui
|
package at.mocode.frontend.core.wizard.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.key.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Desktop-spezifische Variante. Hotkeys werden in einem Folge‑Inkrement ergänzt,
|
* Desktop-spezifische Variante. Hotkeys werden in einem Folge‑Inkrement ergänzt,
|
||||||
@@ -20,6 +23,29 @@ fun WizardScaffoldWithHotkeys(
|
|||||||
finishLabel: String = "Fertig",
|
finishLabel: String = "Fertig",
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.onPreviewKeyEvent { evt ->
|
||||||
|
if (evt.type != KeyEventType.KeyUp) return@onPreviewKeyEvent false
|
||||||
|
when {
|
||||||
|
// Alt+S: Draft speichern (falls vorhanden)
|
||||||
|
evt.isAltPressed && evt.key == Key.S -> {
|
||||||
|
onSaveDraft?.invoke()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Shift+Enter: Zurück
|
||||||
|
evt.isShiftPressed && evt.key == Key.Enter -> {
|
||||||
|
if (canBack) onBack()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Enter: Weiter/Fertig
|
||||||
|
evt.key == Key.Enter -> {
|
||||||
|
if (canNext) onNext()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
WizardScaffold(
|
WizardScaffold(
|
||||||
steps = steps,
|
steps = steps,
|
||||||
currentIndex = currentIndex,
|
currentIndex = currentIndex,
|
||||||
@@ -33,4 +59,5 @@ fun WizardScaffoldWithHotkeys(
|
|||||||
finishLabel = finishLabel,
|
finishLabel = finishLabel,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
@@ -3,6 +3,7 @@ package at.mocode.frontend.core.wizard
|
|||||||
import at.mocode.frontend.core.domain.repository.MasterdataStats
|
import at.mocode.frontend.core.domain.repository.MasterdataStats
|
||||||
import at.mocode.frontend.core.navigation.AppScreen
|
import at.mocode.frontend.core.navigation.AppScreen
|
||||||
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
||||||
|
import at.mocode.frontend.core.wizard.samples.DemoEventAcc
|
||||||
import at.mocode.frontend.core.wizard.samples.DemoEventFlow
|
import at.mocode.frontend.core.wizard.samples.DemoEventFlow
|
||||||
import at.mocode.frontend.core.wizard.samples.DemoEventStep
|
import at.mocode.frontend.core.wizard.samples.DemoEventStep
|
||||||
import at.mocode.frontend.core.wizard.samples.demoStartState
|
import at.mocode.frontend.core.wizard.samples.demoStartState
|
||||||
@@ -47,4 +48,47 @@ class WizardRuntimeJvmTest {
|
|||||||
val s2 = DemoEventFlow.back(s1)
|
val s2 = DemoEventFlow.back(s1)
|
||||||
assertEquals(DemoEventStep.ZnsCheck, s2.current)
|
assertEquals(DemoEventStep.ZnsCheck, s2.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selection_goes_to_ansprechperson_when_guard_true_by_default() {
|
||||||
|
val ctx = WizardContext(
|
||||||
|
origin = AppScreen.Home,
|
||||||
|
isOnline = true,
|
||||||
|
stats = MasterdataStats(
|
||||||
|
lastImport = null,
|
||||||
|
vereinCount = 1,
|
||||||
|
reiterCount = 0,
|
||||||
|
pferdCount = 0,
|
||||||
|
funktionaerCount = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val s0 = demoStartState(AppScreen.Home)
|
||||||
|
val s1 = DemoEventFlow.next(ctx, s0) // -> VeranstalterSelection
|
||||||
|
val s2 = DemoEventFlow.next(ctx, s1)
|
||||||
|
assertEquals(DemoEventStep.AnsprechpersonMapping, s2.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selection_goes_to_meta_when_guard_false_by_acc() {
|
||||||
|
val ctx = WizardContext(
|
||||||
|
origin = AppScreen.Home,
|
||||||
|
isOnline = true,
|
||||||
|
stats = MasterdataStats(
|
||||||
|
lastImport = null,
|
||||||
|
vereinCount = 1,
|
||||||
|
reiterCount = 0,
|
||||||
|
pferdCount = 0,
|
||||||
|
funktionaerCount = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// Manuell einen State anlegen, der bereits auf VeranstalterSelection steht
|
||||||
|
val acc = DemoEventAcc(veranstalterId = "id-1", veranstalterNr = "V-123")
|
||||||
|
val s1 = at.mocode.frontend.core.wizard.runtime.WizardState(
|
||||||
|
current = DemoEventStep.VeranstalterSelection,
|
||||||
|
history = listOf(DemoEventStep.ZnsCheck),
|
||||||
|
acc = acc
|
||||||
|
)
|
||||||
|
val s2 = DemoEventFlow.next(ctx, s1)
|
||||||
|
assertEquals(DemoEventStep.MetaData, s2.current)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ fun EventWizardScreen(
|
|||||||
if (state.currentStep == WizardStep.ZNS_CHECK) onBack() else viewModel.previousStep()
|
if (state.currentStep == WizardStep.ZNS_CHECK) onBack() else viewModel.previousStep()
|
||||||
},
|
},
|
||||||
onNext = { viewModel.nextStep() },
|
onNext = { viewModel.nextStep() },
|
||||||
onSaveDraft = null, // Wird in einem Folge-Inkrement angebunden
|
onSaveDraft = { viewModel.saveDraft() },
|
||||||
onFinish = onFinish,
|
onFinish = onFinish,
|
||||||
onNavigateToVeranstalterNeu = onNavigateToVeranstalterNeu,
|
onNavigateToVeranstalterNeu = onNavigateToVeranstalterNeu,
|
||||||
renderStep = {
|
renderStep = {
|
||||||
|
|||||||
+56
-1
@@ -14,6 +14,7 @@ import at.mocode.frontend.core.domain.zns.ZnsImportProvider
|
|||||||
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
|
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
|
||||||
import at.mocode.frontend.core.navigation.AppScreen
|
import at.mocode.frontend.core.navigation.AppScreen
|
||||||
import at.mocode.frontend.core.network.NetworkConfig
|
import at.mocode.frontend.core.network.NetworkConfig
|
||||||
|
import at.mocode.frontend.core.wizard.draft.DraftStoreMemory
|
||||||
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
||||||
import at.mocode.frontend.core.wizard.runtime.WizardState
|
import at.mocode.frontend.core.wizard.runtime.WizardState
|
||||||
import at.mocode.frontend.core.wizard.samples.DemoEventAcc
|
import at.mocode.frontend.core.wizard.samples.DemoEventAcc
|
||||||
@@ -87,6 +88,8 @@ class EventWizardViewModel(
|
|||||||
|
|
||||||
// --- Orchestrator-Integration (minimal, 2 Steps) ---
|
// --- Orchestrator-Integration (minimal, 2 Steps) ---
|
||||||
private var wizardState: WizardState<DemoEventStep, DemoEventAcc>? = null
|
private var wizardState: WizardState<DemoEventStep, DemoEventAcc>? = null
|
||||||
|
private val draftFlowKey = "event_wizard_v1"
|
||||||
|
private val draftFlowVersion = 1
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkZnsAvailability()
|
checkZnsAvailability()
|
||||||
@@ -100,10 +103,21 @@ class EventWizardViewModel(
|
|||||||
|
|
||||||
// Initialisiere WizardRuntime-State (hinter Feature-Flag nutzbar)
|
// Initialisiere WizardRuntime-State (hinter Feature-Flag nutzbar)
|
||||||
if (WizardFeatureFlags.WizardRuntimeEnabled) {
|
if (WizardFeatureFlags.WizardRuntimeEnabled) {
|
||||||
|
// Resume Draft, falls vorhanden
|
||||||
|
val draft = DraftStoreMemory.load(draftFlowKey)
|
||||||
|
if (draft != null) {
|
||||||
|
val step = parseWizardStep(draft.second)
|
||||||
|
// Mappe auf Runtime-Step
|
||||||
|
val runtimeStep = mapFromWizardStep(step)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
wizardState = WizardState(current = (runtimeStep as DemoEventStep), acc = DemoEventAcc())
|
||||||
|
state = state.copy(currentStep = step)
|
||||||
|
} else {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
wizardState = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = DemoEventAcc())
|
wizardState = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = DemoEventAcc())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadVeranstalterContext(id: Long) {
|
private fun loadVeranstalterContext(id: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -192,6 +206,7 @@ class EventWizardViewModel(
|
|||||||
val mapped = mapToWizardStep(next.current)
|
val mapped = mapToWizardStep(next.current)
|
||||||
if (mapped != null) {
|
if (mapped != null) {
|
||||||
state = state.copy(currentStep = mapped)
|
state = state.copy(currentStep = mapped)
|
||||||
|
saveDraftInternal(mapped)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Fallback, sollte eigentlich nicht eintreten
|
// Fallback, sollte eigentlich nicht eintreten
|
||||||
@@ -215,6 +230,7 @@ class EventWizardViewModel(
|
|||||||
val mapped = mapToWizardStep(prev.current)
|
val mapped = mapToWizardStep(prev.current)
|
||||||
if (mapped != null) {
|
if (mapped != null) {
|
||||||
state = state.copy(currentStep = mapped)
|
state = state.copy(currentStep = mapped)
|
||||||
|
saveDraftInternal(mapped)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,17 +258,56 @@ class EventWizardViewModel(
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val initial = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = DemoEventAcc())
|
val initial = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = DemoEventAcc())
|
||||||
wizardState = initial
|
wizardState = initial
|
||||||
|
// Beim ersten Zugriff auch evtl. gespeicherten Draft berücksichtigen
|
||||||
|
DraftStoreMemory.load(draftFlowKey)?.let { (_, stepName) ->
|
||||||
|
val parsed = parseWizardStep(stepName)
|
||||||
|
val runtime = mapFromWizardStep(parsed)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val resumed = initial.copy(current = (runtime as DemoEventStep))
|
||||||
|
wizardState = resumed
|
||||||
|
state = state.copy(currentStep = parsed)
|
||||||
|
return resumed
|
||||||
|
}
|
||||||
return initial
|
return initial
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isHandledByRuntime(step: WizardStep): Boolean = when (step) {
|
private fun isHandledByRuntime(step: WizardStep): Boolean = when (step) {
|
||||||
WizardStep.ZNS_CHECK, WizardStep.VERANSTALTER_SELECTION -> true
|
WizardStep.ZNS_CHECK,
|
||||||
|
WizardStep.VERANSTALTER_SELECTION,
|
||||||
|
WizardStep.ANSPRECHPERSON_MAPPING,
|
||||||
|
WizardStep.META_DATA -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapToWizardStep(step: DemoEventStep): WizardStep? = when (step) {
|
private fun mapToWizardStep(step: DemoEventStep): WizardStep? = when (step) {
|
||||||
DemoEventStep.ZnsCheck -> WizardStep.ZNS_CHECK
|
DemoEventStep.ZnsCheck -> WizardStep.ZNS_CHECK
|
||||||
DemoEventStep.VeranstalterSelection -> WizardStep.VERANSTALTER_SELECTION
|
DemoEventStep.VeranstalterSelection -> WizardStep.VERANSTALTER_SELECTION
|
||||||
|
DemoEventStep.AnsprechpersonMapping -> WizardStep.ANSPRECHPERSON_MAPPING
|
||||||
|
DemoEventStep.MetaData -> WizardStep.META_DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapFromWizardStep(step: WizardStep): DemoEventStep = when (step) {
|
||||||
|
WizardStep.ZNS_CHECK -> DemoEventStep.ZnsCheck
|
||||||
|
WizardStep.VERANSTALTER_SELECTION -> DemoEventStep.VeranstalterSelection
|
||||||
|
WizardStep.ANSPRECHPERSON_MAPPING -> DemoEventStep.AnsprechpersonMapping
|
||||||
|
WizardStep.META_DATA -> DemoEventStep.MetaData
|
||||||
|
// Noch nicht im Runtime-Flow migriert: mappe konservativ auf letzten bekannten Schritt
|
||||||
|
WizardStep.TURNIER_ANLAGE -> DemoEventStep.MetaData
|
||||||
|
WizardStep.SUMMARY -> DemoEventStep.MetaData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseWizardStep(name: String): WizardStep = try {
|
||||||
|
WizardStep.valueOf(name)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
WizardStep.ZNS_CHECK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveDraftInternal(step: WizardStep) {
|
||||||
|
DraftStoreMemory.save(draftFlowKey, draftFlowVersion, step.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDraft() {
|
||||||
|
saveDraftInternal(state.currentStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVeranstalter(id: Uuid, nummer: String, name: String, standardOrt: String, logo: String?) {
|
fun setVeranstalter(id: Uuid, nummer: String, name: String, standardOrt: String, logo: String?) {
|
||||||
|
|||||||
+4
-1
@@ -260,7 +260,10 @@ fun DesktopContentArea(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AppScreen.EventNeu -> {
|
is AppScreen.EventNeu -> {
|
||||||
val viewModel: at.mocode.veranstaltung.feature.presentation.EventWizardViewModel = koinViewModel()
|
// EventWizardViewModel expects an optional Long parameter (veranstalterId) via Koin.
|
||||||
|
// When starting a brand-new event (no preselected organizer), pass null explicitly.
|
||||||
|
val viewModel: at.mocode.veranstaltung.feature.presentation.EventWizardViewModel =
|
||||||
|
koinViewModel { parametersOf(null as Long?) }
|
||||||
at.mocode.veranstaltung.feature.presentation.EventWizardScreen(
|
at.mocode.veranstaltung.feature.presentation.EventWizardScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
|
|||||||
Reference in New Issue
Block a user