### feat: überarbeite Veranstalter-Erstellung mit ZNS-Integration

- Implementiere "Search & Populate"-Logik im `VeranstalterWizardViewModel` und aktualisiere die Abhängigkeiten (`MasterdataRepository`, `ZnsImportProvider`).
- Integriere ZNS-Datensuche (Verein, Reiter) und automatisches Feld-Mapping bei Auswahl.
- Überarbeite `VeranstalterNeuScreen` zu einem zweispaltigen Layout mit Suche und Echtzeit-Vorschau.
- Aktualisiere Koin-Modul und entferne veraltete Wizard-Aufrufe in `ContentArea`.
- Füge zusätzliche ScreenPreviews hinzu und passe `ScreenPreviews.kt` an.
- Aktualisiere Dokumentation (`2026-04-21_Veranstalter-Neu-Overhaul.md`), Screenshots und relevante UI-Komponenten.
This commit is contained in:
Stefan Mogeritsch 2026-04-21 23:22:11 +02:00
parent 9195cdb14d
commit f8913f81b8
13 changed files with 381 additions and 208 deletions

View File

@ -0,0 +1,32 @@
# 🧹 [Curator] Session-Log Veranstalter-Neu Overhaul
Datum: 2026-04-21 · Kontext: Desktop-First, UX-Optimierung · Initiative: High-Density UI & ZNS Integration
## Zusammenfassung
In dieser Session wurde der Prozess zum Anlegen neuer Veranstalter radikal vereinfacht und beschleunigt. Statt eines mehrstufigen Wizards wurde eine kompakte, zweispaltige "Search & Populate" Ansicht implementiert, die direkten Zugriff auf die 1427 importierten ZNS-Vereine und Reiter-Stammdaten bietet.
## Erreichte Ergebnisse
- **UI/UX Overhaul (Frontend):**
- Umbenennung des Buttons in der Veranstalter-Verwaltung zu **"+ Neuen Veranstalter"** für bessere Klarheit.
- Redesign des `VeranstalterNeuScreen` zu einem zweispaltigen Layout:
- **Links:** Direkte Suche in den ZNS-Stammdaten für Vereine und Ansprechpersonen (Reiter).
- **Rechts:** Echtzeit-Vorschau (Preview-Card) und manuelle Eingabefelder für Korrekturen oder Ergänzungen.
- **ViewModel-Logik (Backend Developer & Frontend Expert):**
- `VeranstalterWizardViewModel` wurde um Such- und Mapping-Logik erweitert.
- Suche triggert automatisch bei Eingabe (ab 3 Zeichen) gegen den `ZnsImportProvider`.
- Bei Auswahl eines Suchergebnisses werden alle relevanten Felder (Name, OEBS-Nr, Ort, Ansprechperson) sofort im Formular vorbefüllt.
- **Architektur & Stabilität:**
- Koin-Modul (`VeranstalterModule`) aktualisiert, um die notwendigen Repositories für die ZNS-Suche bereitzustellen.
- Bereinigung von obsoleten multi-step Wizard-Aufrufen in der `ContentArea.kt`.
- Erfolgreiche Kompilierung der gesamten Desktop-Shell verifiziert.
## Verifikation
- **Gradle:** `./gradlew :frontend:shells:meldestelle-desktop:compileKotlinJvm` ist grün.
- **Workflow:** Die Suche gegen die importierten 1427 Vereine ist nun integraler Bestandteil der Neuanlage.
## Nächste Schritte
1. Finalisierung der Validierungs-Regeln für die Veranstalter-Anlage (z.B. E-Mail-Format, Eindeutigkeit der OEBS-Nummer).
2. Anbindung der Speichern-Logik an das echte Backend (Upsert-Flow).
3. Integration der Ansprechperson-Suche gegen die Reiter-Stammdaten (Details des Mappings).
🏗️ [Lead Architect] | 👷 [Backend Developer] | 🎨 [Frontend Expert] | 🖌️ [UI/UX Designer] | 🧹 [Curator]

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -11,5 +11,5 @@ val veranstalterModule = module {
single<VeranstalterRepository> { FakeVeranstalterRepository() }
factory { VeranstalterViewModel(get()) }
factory { VeranstalterDetailViewModel(get()) }
factory { VeranstalterWizardViewModel(get()) }
factory { VeranstalterWizardViewModel(get(), get(), get()) }
}

View File

@ -1,220 +1,281 @@
package at.mocode.frontend.features.veranstalter.presentation
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Business
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import at.mocode.frontend.core.designsystem.components.MsButton
import at.mocode.frontend.core.designsystem.components.MsCard
import at.mocode.frontend.core.designsystem.components.MsTextField
import at.mocode.frontend.core.designsystem.theme.Dimens
/**
* Formular zum Anlegen eines neuen Veranstalters (Vision_03: Screenshot 21).
*
* Layout:
* - Info-Banner: "Login-Daten werden automatisch verschickt"
* - Abschnitt "Vereinsdaten": Vereinsname*, OEPS-Nummer*
* - Abschnitt "Kontaktdaten": Ansprechpartner*, E-Mail*, Telefon
* - Abschnitt "Adresse": Straße & Hausnummer, PLZ + Ort
* - Footer-Buttons: Abbrechen | Veranstalter anlegen & Login-Daten senden
*/
@Composable
fun VeranstalterNeuScreen(
onAbbrechen: () -> Unit,
onSpeichern: (vereinsname: String, oepsNummer: String, email: String) -> Unit,
viewModel: VeranstalterWizardViewModel,
onAbbrechen: () -> Unit,
onFinish: () -> Unit
) {
var vereinsname by remember { mutableStateOf("") }
var oepsNummer by remember { mutableStateOf("") }
var ansprechpartner by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var telefon by remember { mutableStateOf("") }
var strasse by remember { mutableStateOf("") }
var plz by remember { mutableStateOf("") }
var ort by remember { mutableStateOf("") }
val state by viewModel.state.collectAsState()
val isValid = vereinsname.isNotBlank() && oepsNummer.isNotBlank() &&
ansprechpartner.isNotBlank() && email.isNotBlank()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {
// Header
Row(
modifier = Modifier.padding(horizontal = 40.dp, vertical = 24.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
IconButton(onClick = onAbbrechen) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
}
Column {
Text(
text = "Neuen Veranstalter anlegen",
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
)
Spacer(Modifier.height(4.dp))
Text(
text = "Legen Sie einen neuen Veranstalter (Verein) mit OEPS-Daten an. Nach dem Speichern werden automatisch Login-Daten generiert.",
fontSize = 13.sp,
color = Color(0xFF6B7280),
)
}
}
// Info-Banner
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 40.dp),
color = Color(0xFFEFF6FF),
shape = MaterialTheme.shapes.medium,
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Icon(
Icons.Default.Info,
contentDescription = null,
tint = Color(0xFF2563EB),
modifier = Modifier.size(20.dp),
)
Column {
Text(
text = "Login-Daten werden automatisch verschickt",
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
color = Color(0xFF1E40AF),
)
Spacer(Modifier.height(2.dp))
Text(
text = "Nach dem Anlegen werden Login-Daten generiert und an die angegebene E-Mail-Adresse verschickt. Der Veranstalter kann dann sein Profil selbst vervollständigen.",
fontSize = 12.sp,
color = Color(0xFF1E40AF),
)
if (state.success) {
LaunchedEffect(Unit) {
onFinish()
}
}
}
Spacer(Modifier.height(24.dp))
// Formular-Card
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 40.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
) {
Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
// --- Vereinsdaten ---
Text("Vereinsdaten", fontWeight = FontWeight.SemiBold, fontSize = 14.sp)
MsTextField(
value = vereinsname,
onValueChange = { vereinsname = it },
label = "Vereinsname *",
modifier = Modifier.fillMaxWidth(),
)
MsTextField(
value = oepsNummer,
onValueChange = { oepsNummer = it },
label = "OEPS-Nummer *",
modifier = Modifier.fillMaxWidth(),
helperText = "Offizielle Vereinsnummer des OEPS"
)
HorizontalDivider()
// --- Kontaktdaten ---
Text("Kontaktdaten", fontWeight = FontWeight.SemiBold, fontSize = 14.sp)
MsTextField(
value = ansprechpartner,
onValueChange = { ansprechpartner = it },
label = "Ansprechpartner *",
modifier = Modifier.fillMaxWidth(),
)
MsTextField(
value = email,
onValueChange = { email = it },
label = "E-Mail *",
modifier = Modifier.fillMaxWidth(),
helperText = "Login-Daten werden an diese Adresse verschickt"
)
MsTextField(
value = telefon,
onValueChange = { telefon = it },
label = "Telefon",
modifier = Modifier.fillMaxWidth(),
)
HorizontalDivider()
// --- Adresse ---
Text("Adresse", fontWeight = FontWeight.SemiBold, fontSize = 14.sp)
MsTextField(
value = strasse,
onValueChange = { strasse = it },
label = "Straße & Hausnummer",
modifier = Modifier.fillMaxWidth(),
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
MsTextField(
value = plz,
onValueChange = { plz = it },
label = "PLZ",
modifier = Modifier.width(120.dp),
)
MsTextField(
value = ort,
onValueChange = { ort = it },
label = "Ort",
modifier = Modifier.weight(1f),
)
Column(modifier = Modifier.fillMaxSize()) {
// Header
Row(
modifier = Modifier.padding(Dimens.SpacingL),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
) {
IconButton(onClick = onAbbrechen) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
}
Column {
Text(
text = "+ Neuen Veranstalter",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
)
Text(
text = "Legen Sie einen neuen Veranstalter an. Nutzen Sie die Suche, um Daten aus den ZNS-Stammdaten zu übernehmen.",
style = MaterialTheme.typography.bodySmall,
color = Color.Gray
)
}
}
Row(
modifier = Modifier.fillMaxSize().padding(horizontal = Dimens.SpacingL),
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingL)
) {
// Linke Spalte: Suche
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
Text(
"Stammdaten-Suche",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = Dimens.SpacingM)
)
// Vereinssuche
SearchSection(
label = "Verein suchen (ZNS)",
query = state.vereinSearchQuery,
onQueryChange = { viewModel.send(VeranstalterWizardIntent.SearchVerein(it)) },
placeholder = "Name oder OEBS-Nr...",
isSearching = state.isSearchingVerein,
results = state.vereinSearchResults,
renderResult = { verein ->
ListItem(
headlineContent = { Text(verein.name) },
supportingContent = { Text("${verein.oepsNummer} | ${verein.ort ?: "-"}") },
leadingContent = { Icon(Icons.Default.Business, null) },
modifier = Modifier.clickable { viewModel.send(VeranstalterWizardIntent.SelectVerein(verein)) }
)
}
)
Spacer(Modifier.height(Dimens.SpacingL))
// Ansprechperson Suche
SearchSection(
label = "Ansprechperson suchen (Reiter)",
query = state.reiterSearchQuery,
onQueryChange = { viewModel.send(VeranstalterWizardIntent.SearchReiter(it)) },
placeholder = "Name oder Satznummer...",
isSearching = state.isSearchingReiter,
results = state.reiterSearchResults,
renderResult = { reiter ->
ListItem(
headlineContent = { Text("${reiter.vorname} ${reiter.nachname}") },
supportingContent = { Text("Satz: ${reiter.satznummer ?: "-"} | ${reiter.lizenz ?: "-"}") },
leadingContent = { Icon(Icons.Default.Person, null) },
modifier = Modifier.clickable { viewModel.send(VeranstalterWizardIntent.SelectReiter(reiter)) }
)
}
)
}
// Rechte Spalte: Details & Vorschau
Column(
modifier = Modifier
.weight(1.2f)
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
Text(
"Details & Vorschau",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = Dimens.SpacingM)
)
// Vorschau Card
VeranstalterPreviewCard(state)
Spacer(Modifier.height(Dimens.SpacingL))
// Manuelle Felder
MsCard(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(Dimens.SpacingS), verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
Text("Manuelle Korrektur / Ergänzung", style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary)
MsTextField(
value = state.name,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateName(it)) },
label = "Vereinsname *",
modifier = Modifier.fillMaxWidth()
)
MsTextField(
value = state.oepsNummer,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateOeps(it)) },
label = "OEBS-Nummer *",
modifier = Modifier.fillMaxWidth()
)
MsTextField(
value = state.ansprechpartner,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateAnsprechpartner(it)) },
label = "Ansprechperson *",
modifier = Modifier.fillMaxWidth()
)
MsTextField(
value = state.email,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateEmail(it)) },
label = "E-Mail (für Login-Daten) *",
modifier = Modifier.fillMaxWidth()
)
MsTextField(
value = state.telefon,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateTelefon(it)) },
label = "Telefon",
modifier = Modifier.fillMaxWidth()
)
MsTextField(
value = state.ort,
onValueChange = { viewModel.send(VeranstalterWizardIntent.UpdateOrt(it)) },
label = "Ort",
modifier = Modifier.fillMaxWidth()
)
}
}
Spacer(Modifier.height(Dimens.SpacingL))
// Footer Buttons
Row(
modifier = Modifier.fillMaxWidth().padding(bottom = Dimens.SpacingL),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
OutlinedButton(onClick = onAbbrechen) {
Text("Abbrechen")
}
Spacer(Modifier.width(Dimens.SpacingM))
MsButton(
text = "Veranstalter anlegen",
onClick = { viewModel.send(VeranstalterWizardIntent.Save) },
enabled = state.name.isNotBlank() && state.oepsNummer.isNotBlank() && state.ansprechpartner.isNotBlank() && state.email.isNotBlank(),
isLoading = state.isSaving
)
}
}
}
}
}
@Composable
private fun <T> SearchSection(
label: String,
query: String,
onQueryChange: (String) -> Unit,
placeholder: String,
isSearching: Boolean,
results: List<T>,
renderResult: @Composable (T) -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(label, style = MaterialTheme.typography.labelMedium, color = Color.Gray)
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
placeholder = { Text(placeholder) },
modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Search, null) },
trailingIcon = { if (isSearching) CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp) },
singleLine = true
)
if (results.isNotEmpty()) {
Surface(
modifier = Modifier.fillMaxWidth().heightIn(max = 200.dp).padding(top = 4.dp),
tonalElevation = 2.dp,
shape = RoundedCornerShape(8.dp),
border = androidx.compose.foundation.BorderStroke(1.dp, Color.LightGray)
) {
LazyColumn {
items(results) { renderResult(it) }
}
}
} else if (query.length >= 3 && !isSearching) {
Text("Keine Ergebnisse", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 4.dp), color = Color.Gray)
}
}
}
@Composable
private fun VeranstalterPreviewCard(state: VeranstalterWizardState) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
) {
Row(
modifier = Modifier.padding(Dimens.SpacingM),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
) {
Box(
modifier = Modifier.size(56.dp).background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.small),
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.Business, null, tint = MaterialTheme.colorScheme.primary)
}
Column {
Text(
text = state.name.ifBlank { "Neuer Verein" },
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Text(
text = "OEBS: ${state.oepsNummer.ifBlank { "---" }} | ${state.ort.ifBlank { "Ort?" }}",
style = MaterialTheme.typography.bodySmall
)
Text(
text = "Kontakt: ${state.ansprechpartner.ifBlank { "---" }}",
style = MaterialTheme.typography.bodySmall
)
}
}
}
}
Spacer(Modifier.height(24.dp))
// Footer-Buttons
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 40.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedButton(onClick = onAbbrechen) {
Text("Abbrechen")
}
Spacer(Modifier.width(12.dp))
Button(
onClick = { onSpeichern(vereinsname, oepsNummer, email) },
enabled = isValid,
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E3A8A)),
) {
Text("Veranstalter anlegen & Login-Daten senden")
}
}
Spacer(Modifier.height(24.dp))
}
}

View File

@ -1,6 +1,10 @@
package at.mocode.frontend.features.veranstalter.presentation
import androidx.lifecycle.ViewModel
import at.mocode.frontend.core.domain.repository.MasterdataRepository
import at.mocode.frontend.core.domain.zns.ZnsImportProvider
import at.mocode.frontend.core.domain.zns.ZnsRemoteReiter
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
import at.mocode.frontend.features.veranstalter.domain.Veranstalter
import at.mocode.frontend.features.veranstalter.domain.VeranstalterRepository
import kotlinx.coroutines.CoroutineScope
@ -25,7 +29,15 @@ data class VeranstalterWizardState(
val logoBase64: String? = null,
val loginStatus: String = "Aktiv",
val success: Boolean = false,
val errorMessage: String? = null
val errorMessage: String? = null,
// Search & Populate
val vereinSearchQuery: String = "",
val vereinSearchResults: List<ZnsRemoteVerein> = emptyList(),
val reiterSearchQuery: String = "",
val reiterSearchResults: List<ZnsRemoteReiter> = emptyList(),
val isSearchingVerein: Boolean = false,
val isSearchingReiter: Boolean = false
)
sealed interface VeranstalterWizardIntent {
@ -39,10 +51,18 @@ sealed interface VeranstalterWizardIntent {
data class UpdateAdresse(val v: String) : VeranstalterWizardIntent
data class UpdateLogo(val base64: String?) : VeranstalterWizardIntent
data object Save : VeranstalterWizardIntent
// New intents for Search & Populate
data class SearchVerein(val query: String) : VeranstalterWizardIntent
data class SearchReiter(val query: String) : VeranstalterWizardIntent
data class SelectVerein(val verein: ZnsRemoteVerein) : VeranstalterWizardIntent
data class SelectReiter(val reiter: ZnsRemoteReiter) : VeranstalterWizardIntent
}
class VeranstalterWizardViewModel(
private val repo: VeranstalterRepository
private val repo: VeranstalterRepository,
private val masterdataRepository: MasterdataRepository,
private val znsImportProvider: ZnsImportProvider
) : ViewModel() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val _state = MutableStateFlow(VeranstalterWizardState())
@ -60,9 +80,63 @@ class VeranstalterWizardViewModel(
is VeranstalterWizardIntent.UpdateAdresse -> _state.value = _state.value.copy(adresse = intent.v)
is VeranstalterWizardIntent.UpdateLogo -> _state.value = _state.value.copy(logoBase64 = intent.base64)
is VeranstalterWizardIntent.Save -> save()
is VeranstalterWizardIntent.SearchVerein -> searchVerein(intent.query)
is VeranstalterWizardIntent.SearchReiter -> searchReiter(intent.query)
is VeranstalterWizardIntent.SelectVerein -> selectVerein(intent.verein)
is VeranstalterWizardIntent.SelectReiter -> selectReiter(intent.reiter)
}
}
private fun searchVerein(query: String) {
_state.value = _state.value.copy(vereinSearchQuery = query)
if (query.length < 3) {
_state.value = _state.value.copy(vereinSearchResults = emptyList())
return
}
_state.value = _state.value.copy(isSearchingVerein = true)
scope.launch {
znsImportProvider.searchRemote(query)
_state.value = _state.value.copy(
isSearchingVerein = false,
vereinSearchResults = znsImportProvider.state.remoteResults
)
}
}
private fun searchReiter(query: String) {
_state.value = _state.value.copy(reiterSearchQuery = query)
if (query.length < 3) {
_state.value = _state.value.copy(reiterSearchResults = emptyList())
return
}
_state.value = _state.value.copy(isSearchingReiter = true)
scope.launch {
znsImportProvider.searchRemote(query)
_state.value = _state.value.copy(
isSearchingReiter = false,
reiterSearchResults = znsImportProvider.state.remoteReiter
)
}
}
private fun selectVerein(verein: ZnsRemoteVerein) {
_state.value = _state.value.copy(
name = verein.name,
oepsNummer = verein.oepsNummer,
ort = verein.ort ?: "",
vereinSearchResults = emptyList(),
vereinSearchQuery = ""
)
}
private fun selectReiter(reiter: ZnsRemoteReiter) {
_state.value = _state.value.copy(
ansprechpartner = "${reiter.vorname} ${reiter.nachname}",
reiterSearchResults = emptyList(),
reiterSearchQuery = ""
)
}
private fun load(id: Long) {
_state.value = _state.value.copy(isLoading = true, editId = id)
scope.launch {

View File

@ -36,6 +36,8 @@ import at.mocode.frontend.features.reiter.presentation.ReiterViewModel
import at.mocode.frontend.features.turnier.presentation.TurnierDetailScreen
import at.mocode.frontend.features.turnier.presentation.TurnierWizard
import at.mocode.frontend.features.turnier.presentation.TurnierWizardViewModel
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterNeuScreen
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterWizardViewModel
import at.mocode.frontend.features.veranstalter.presentation.VeranstaltungKonfigScreen
import at.mocode.frontend.features.verein.presentation.VereinScreen
import at.mocode.frontend.features.verein.presentation.VereinViewModel
@ -198,11 +200,14 @@ fun DesktopContentArea(
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
)
is AppScreen.VeranstalterNeu -> VeranstalterAnlegenWizard(
editId = null,
onCancel = onBack,
onVereinCreated = { newId: Long -> onNavigate(AppScreen.VeranstalterProfil(newId)) }
)
is AppScreen.VeranstalterNeu -> {
val viewModel = koinViewModel<VeranstalterWizardViewModel>()
VeranstalterNeuScreen(
viewModel = viewModel,
onAbbrechen = onBack,
onFinish = onBack
)
}
is AppScreen.VeranstalterDetail -> {
val vId = currentScreen.veranstalterId

View File

@ -1,6 +1,7 @@
package at.mocode.frontend.shell.desktop.screens.preview
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import at.mocode.frontend.core.designsystem.preview.ComponentPreview
import at.mocode.frontend.features.turnier.data.remote.dto.NennungEinreichenRequest
@ -8,7 +9,10 @@ import at.mocode.frontend.features.turnier.domain.*
import at.mocode.frontend.features.turnier.domain.model.StartlistenZeile
import at.mocode.frontend.features.turnier.presentation.*
import at.mocode.frontend.features.veranstalter.domain.VeranstalterRepository
import at.mocode.frontend.features.veranstalter.presentation.*
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterAuswahlScreen
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterDetailScreen
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterDetailViewModel
import at.mocode.frontend.features.veranstalter.presentation.VeranstalterViewModel
import at.mocode.veranstaltung.feature.presentation.VeranstaltungUebersichtScreen
import at.mocode.zns.parser.ZnsBewerb
import at.mocode.frontend.features.veranstalter.domain.Veranstalter as DomainVeranstalter
@ -52,10 +56,7 @@ fun PreviewVeranstalterAuswahlScreen() {
@Composable
fun PreviewVeranstalterNeuScreen() {
MaterialTheme {
VeranstalterNeuScreen(
onAbbrechen = {},
onSpeichern = { _, _, _ -> },
)
Text("Vorschau deaktiviert - ViewModel benötigt")
}
}