### feat: verbessere Validierungs- und Draft-Funktionalität im Wizard
- Entferne `onNavigateToVeranstalterNeu` aus `EventWizardScreen` und zugehörigen Komponenten. - Füge persistente Speicherung für Drafts über `DraftStore` hinzu (JSON für JVM, No-op für Wasm). - Ergänze WizardScaffold um `errorSummary` zur Anzeige von Validierungsfehlern. - Bereinige und optimiere Schritt-Logik in `EventWizardViewModel`.
This commit is contained in:
+6
-9
@@ -29,8 +29,7 @@ import kotlin.uuid.ExperimentalUuidApi
|
||||
fun EventWizardScreen(
|
||||
viewModel: EventWizardViewModel,
|
||||
onBack: () -> Unit,
|
||||
onFinish: () -> Unit,
|
||||
onNavigateToVeranstalterNeu: () -> Unit = {}
|
||||
onFinish: () -> Unit
|
||||
) {
|
||||
val state = viewModel.state
|
||||
|
||||
@@ -44,11 +43,10 @@ fun EventWizardScreen(
|
||||
onNext = { viewModel.nextStep() },
|
||||
onSaveDraft = { viewModel.saveDraft() },
|
||||
onFinish = onFinish,
|
||||
onNavigateToVeranstalterNeu = onNavigateToVeranstalterNeu,
|
||||
renderStep = {
|
||||
when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> ZnsCheckStep(viewModel)
|
||||
WizardStep.VERANSTALTER_SELECTION -> VeranstalterSelectionStep(viewModel, onNavigateToVeranstalterNeu)
|
||||
WizardStep.VERANSTALTER_SELECTION -> VeranstalterSelectionStep(viewModel)
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> AnsprechpersonMappingStep(viewModel)
|
||||
WizardStep.META_DATA -> MetaDataStep(viewModel)
|
||||
WizardStep.TURNIER_ANLAGE -> TurnierAnlageStep(viewModel)
|
||||
@@ -96,7 +94,7 @@ fun EventWizardScreen(
|
||||
) {
|
||||
when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> ZnsCheckStep(viewModel)
|
||||
WizardStep.VERANSTALTER_SELECTION -> VeranstalterSelectionStep(viewModel, onNavigateToVeranstalterNeu)
|
||||
WizardStep.VERANSTALTER_SELECTION -> VeranstalterSelectionStep(viewModel)
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> AnsprechpersonMappingStep(viewModel)
|
||||
WizardStep.META_DATA -> MetaDataStep(viewModel)
|
||||
WizardStep.TURNIER_ANLAGE -> TurnierAnlageStep(viewModel)
|
||||
@@ -114,7 +112,6 @@ private fun EventWizardScreenScaffolded(
|
||||
onNext: () -> Unit,
|
||||
onSaveDraft: (() -> Unit)?,
|
||||
onFinish: () -> Unit,
|
||||
onNavigateToVeranstalterNeu: () -> Unit,
|
||||
renderStep: @Composable () -> Unit
|
||||
) {
|
||||
val steps = remember {
|
||||
@@ -144,6 +141,7 @@ private fun EventWizardScreenScaffolded(
|
||||
onBack = onBack,
|
||||
onNext = if (state.currentStep == WizardStep.SUMMARY) onFinish else onNext,
|
||||
onSaveDraft = onSaveDraft,
|
||||
errorSummary = state.error,
|
||||
nextLabel = if (state.currentStep == WizardStep.SUMMARY) "Fertig" else "Weiter",
|
||||
backLabel = "Zurück",
|
||||
finishLabel = "Fertig"
|
||||
@@ -312,8 +310,7 @@ private fun ZnsCheckStep(viewModel: EventWizardViewModel) {
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||
@Composable
|
||||
private fun VeranstalterSelectionStep(
|
||||
viewModel: EventWizardViewModel,
|
||||
onNavigateToVeranstalterNeu: () -> Unit
|
||||
viewModel: EventWizardViewModel
|
||||
) {
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
@@ -398,7 +395,7 @@ private fun VeranstalterSelectionStep(
|
||||
Text("Verein nicht gefunden?", style = MaterialTheme.typography.labelLarge)
|
||||
|
||||
Button(
|
||||
onClick = onNavigateToVeranstalterNeu,
|
||||
onClick = { /* Fallback Logic */ },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondary)
|
||||
) {
|
||||
Icon(Icons.Default.Add, null)
|
||||
|
||||
+37
-27
@@ -14,7 +14,7 @@ import at.mocode.frontend.core.domain.zns.ZnsImportProvider
|
||||
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
|
||||
import at.mocode.frontend.core.navigation.AppScreen
|
||||
import at.mocode.frontend.core.network.NetworkConfig
|
||||
import at.mocode.frontend.core.wizard.draft.DraftStoreMemory
|
||||
import at.mocode.frontend.core.wizard.draft.DraftStore
|
||||
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
||||
import at.mocode.frontend.core.wizard.runtime.WizardState
|
||||
import at.mocode.frontend.core.wizard.samples.DemoEventAcc
|
||||
@@ -104,13 +104,13 @@ class EventWizardViewModel(
|
||||
// Initialisiere WizardRuntime-State (hinter Feature-Flag nutzbar)
|
||||
if (WizardFeatureFlags.WizardRuntimeEnabled) {
|
||||
// Resume Draft, falls vorhanden
|
||||
val draft = DraftStoreMemory.load(draftFlowKey)
|
||||
val draft = DraftStore.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())
|
||||
wizardState = WizardState(current = runtimeStep, acc = DemoEventAcc())
|
||||
state = state.copy(currentStep = step)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -209,17 +209,19 @@ class EventWizardViewModel(
|
||||
saveDraftInternal(mapped)
|
||||
return
|
||||
}
|
||||
// Fallback, sollte eigentlich nicht eintreten
|
||||
// Fallback sollte eigentlich nicht eintreten
|
||||
}
|
||||
|
||||
state = state.copy(currentStep = when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> WizardStep.VERANSTALTER_SELECTION
|
||||
WizardStep.VERANSTALTER_SELECTION -> WizardStep.ANSPRECHPERSON_MAPPING
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> WizardStep.META_DATA
|
||||
WizardStep.META_DATA -> WizardStep.TURNIER_ANLAGE
|
||||
WizardStep.TURNIER_ANLAGE -> WizardStep.SUMMARY
|
||||
WizardStep.SUMMARY -> WizardStep.SUMMARY
|
||||
})
|
||||
state = state.copy(
|
||||
currentStep = when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> WizardStep.VERANSTALTER_SELECTION
|
||||
WizardStep.VERANSTALTER_SELECTION -> WizardStep.ANSPRECHPERSON_MAPPING
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> WizardStep.META_DATA
|
||||
WizardStep.META_DATA -> WizardStep.TURNIER_ANLAGE
|
||||
WizardStep.TURNIER_ANLAGE -> WizardStep.SUMMARY
|
||||
WizardStep.SUMMARY -> WizardStep.SUMMARY
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun previousStep() {
|
||||
@@ -235,14 +237,16 @@ class EventWizardViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
state = state.copy(currentStep = when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> WizardStep.ZNS_CHECK
|
||||
WizardStep.VERANSTALTER_SELECTION -> WizardStep.ZNS_CHECK
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> WizardStep.VERANSTALTER_SELECTION
|
||||
WizardStep.META_DATA -> WizardStep.ANSPRECHPERSON_MAPPING
|
||||
WizardStep.TURNIER_ANLAGE -> WizardStep.META_DATA
|
||||
WizardStep.SUMMARY -> WizardStep.TURNIER_ANLAGE
|
||||
})
|
||||
state = state.copy(
|
||||
currentStep = when (state.currentStep) {
|
||||
WizardStep.ZNS_CHECK -> WizardStep.ZNS_CHECK
|
||||
WizardStep.VERANSTALTER_SELECTION -> WizardStep.ZNS_CHECK
|
||||
WizardStep.ANSPRECHPERSON_MAPPING -> WizardStep.VERANSTALTER_SELECTION
|
||||
WizardStep.META_DATA -> WizardStep.ANSPRECHPERSON_MAPPING
|
||||
WizardStep.TURNIER_ANLAGE -> WizardStep.META_DATA
|
||||
WizardStep.SUMMARY -> WizardStep.TURNIER_ANLAGE
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildWizardContext(): WizardContext = WizardContext(
|
||||
@@ -252,18 +256,23 @@ class EventWizardViewModel(
|
||||
stats = state.stammdatenStats
|
||||
)
|
||||
|
||||
private fun buildAccFromState(): DemoEventAcc = DemoEventAcc(
|
||||
veranstalterId = state.veranstalterId?.toString(),
|
||||
veranstalterNr = state.veranstalterVereinsNummer
|
||||
)
|
||||
|
||||
private fun ensureWizardStateInitialized(): WizardState<DemoEventStep, DemoEventAcc> {
|
||||
val existing = wizardState
|
||||
if (existing != null) return existing
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val initial = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = DemoEventAcc())
|
||||
if (existing != null) return existing.copy(acc = buildAccFromState())
|
||||
val initial = WizardState(current = (DemoEventStep.ZnsCheck as DemoEventStep), acc = buildAccFromState())
|
||||
wizardState = initial
|
||||
// Beim ersten Zugriff auch evtl. gespeicherten Draft berücksichtigen
|
||||
DraftStoreMemory.load(draftFlowKey)?.let { (_, stepName) ->
|
||||
DraftStore.load(draftFlowKey)?.let { (_, stepName) ->
|
||||
val parsed = parseWizardStep(stepName)
|
||||
val runtime = mapFromWizardStep(parsed)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val resumed = initial.copy(current = (runtime as DemoEventStep))
|
||||
val resumed = initial.copy(current = runtime)
|
||||
wizardState = resumed
|
||||
state = state.copy(currentStep = parsed)
|
||||
return resumed
|
||||
@@ -276,6 +285,7 @@ class EventWizardViewModel(
|
||||
WizardStep.VERANSTALTER_SELECTION,
|
||||
WizardStep.ANSPRECHPERSON_MAPPING,
|
||||
WizardStep.META_DATA -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -291,7 +301,7 @@ class EventWizardViewModel(
|
||||
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
|
||||
// Noch nicht im Runtime-Flow migriert: Mappe konservativ auf letzten bekannten Schritt
|
||||
WizardStep.TURNIER_ANLAGE -> DemoEventStep.MetaData
|
||||
WizardStep.SUMMARY -> DemoEventStep.MetaData
|
||||
}
|
||||
@@ -303,7 +313,7 @@ class EventWizardViewModel(
|
||||
}
|
||||
|
||||
private fun saveDraftInternal(step: WizardStep) {
|
||||
DraftStoreMemory.save(draftFlowKey, draftFlowVersion, step.name)
|
||||
DraftStore.save(draftFlowKey, draftFlowVersion, step.name)
|
||||
}
|
||||
|
||||
fun saveDraft() {
|
||||
|
||||
Reference in New Issue
Block a user