chore: implementiere Suche nach Veranstalter via OEPS-Nummer, verbessere UI-Flow im Veranstaltungs-Wizard und erweitere VereinRepository um OEPS-Abfrage
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
+1
-1
@@ -245,7 +245,7 @@ actual fun DeviceInitializationConfig(
|
|||||||
Text("Client hinzufügen")
|
Text("Client hinzufügen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (settings.networkRole != NetworkRole.MASTER) {
|
||||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||||
Text("🔍 Verfügbare Master im Netzwerk", style = MaterialTheme.typography.titleSmall)
|
Text("🔍 Verfügbare Master im Netzwerk", style = MaterialTheme.typography.titleSmall)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -211,7 +211,7 @@ fun VeranstalterAuswahlScreen(
|
|||||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||||
shape = MaterialTheme.shapes.medium
|
shape = MaterialTheme.shapes.medium
|
||||||
) {
|
) {
|
||||||
Text("Weiter zur Turnier-Konfiguration")
|
Text("Veranstalter auswählen & Weiter")
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowForward, null, modifier = Modifier.size(16.dp))
|
Icon(Icons.AutoMirrored.Filled.ArrowForward, null, modifier = Modifier.size(16.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ kotlin {
|
|||||||
implementation(projects.frontend.core.domain)
|
implementation(projects.frontend.core.domain)
|
||||||
implementation(projects.core.coreDomain)
|
implementation(projects.core.coreDomain)
|
||||||
implementation(projects.frontend.core.auth)
|
implementation(projects.frontend.core.auth)
|
||||||
|
implementation(projects.frontend.features.vereinFeature)
|
||||||
|
implementation(projects.frontend.features.deviceInitialization)
|
||||||
|
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
|
|||||||
+50
-17
@@ -8,7 +8,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -187,30 +187,63 @@ private fun ZnsCheckStep(viewModel: VeranstaltungWizardViewModel) {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun VeranstalterSelectionStep(viewModel: VeranstaltungWizardViewModel) {
|
private fun VeranstalterSelectionStep(viewModel: VeranstaltungWizardViewModel) {
|
||||||
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Text("Schritt 2: Veranstalter auswählen", style = MaterialTheme.typography.titleLarge)
|
Text("Schritt 2: Veranstalter auswählen", style = MaterialTheme.typography.titleLarge)
|
||||||
Text("Suchen Sie nach dem Verein (Name oder OEPS-Nummer).")
|
Text("Suchen Sie nach dem Verein (Name oder OEPS-Nummer).")
|
||||||
|
|
||||||
// Mock Suche
|
MsTextField(
|
||||||
OutlinedTextField(
|
value = searchQuery,
|
||||||
value = "",
|
onValueChange = {
|
||||||
onValueChange = {},
|
searchQuery = it
|
||||||
label = { Text("Verein suchen...") },
|
if (it.length >= 3) {
|
||||||
|
viewModel.searchVeranstalterByOepsNr(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = "Verein suchen (z.B. 6-009)",
|
||||||
|
placeholder = "OEPS-Nummer eingeben...",
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(onClick = {
|
if (viewModel.state.veranstalterId != null) {
|
||||||
// Mock Selection
|
Card(
|
||||||
viewModel.setVeranstalter(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
id = kotlin.uuid.Uuid.random(),
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
|
||||||
nummer = "6-009",
|
) {
|
||||||
name = "Union Reit- u. Fahrverein Neumarkt/M.",
|
Row(
|
||||||
standardOrt = "4212 Neumarkt, Reitanlage Stroblmair",
|
modifier = Modifier.padding(16.dp),
|
||||||
logo = null
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.CheckCircle, null, tint = MaterialTheme.colorScheme.primary)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
viewModel.state.veranstalterName,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text("OEPS-Nr: ${viewModel.state.veranstalterVereinsNummer}")
|
||||||
|
}
|
||||||
|
Button(onClick = { viewModel.nextStep() }) {
|
||||||
|
Text("Auswählen & Weiter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Information für den User
|
||||||
|
Text(
|
||||||
|
"Geben Sie mindestens 3 Zeichen der OEPS-Nummer ein, um die Stammdaten zu durchsuchen.",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
viewModel.nextStep()
|
|
||||||
}) {
|
// Fallback/Demo Button beibehalten für 6-009
|
||||||
Text("Union Reit- u. Fahrverein Neumarkt/M. (6-009) wählen")
|
OutlinedButton(
|
||||||
|
onClick = { viewModel.searchVeranstalterByOepsNr("6-009") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Beispiel: Union Reit- u. Fahrverein Neumarkt/M. (6-009) suchen")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-4
@@ -7,8 +7,8 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.mocode.core.domain.serialization.UuidSerializer
|
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.zns.ZnsImportProvider
|
|
||||||
import at.mocode.frontend.core.network.NetworkConfig
|
import at.mocode.frontend.core.network.NetworkConfig
|
||||||
|
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.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
@@ -58,7 +58,7 @@ data class VeranstaltungWizardState(
|
|||||||
class VeranstaltungWizardViewModel(
|
class VeranstaltungWizardViewModel(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
private val authTokenManager: AuthTokenManager,
|
private val authTokenManager: AuthTokenManager,
|
||||||
val znsViewModel: ZnsImportProvider
|
private val vereinRepository: VereinRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var state by mutableStateOf(VeranstaltungWizardState())
|
var state by mutableStateOf(VeranstaltungWizardState())
|
||||||
@@ -71,13 +71,28 @@ class VeranstaltungWizardViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun checkZnsAvailability() {
|
fun checkZnsAvailability() {
|
||||||
// Hier prüfen wir, ob Stammdaten vorhanden sind (Simuliert)
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val hasData = true // Simulation: Stammdaten sind da
|
val vereineResult = vereinRepository.getVereine()
|
||||||
|
val hasData = vereineResult.getOrNull()?.isNotEmpty() ?: false
|
||||||
state = state.copy(isZnsAvailable = hasData)
|
state = state.copy(isZnsAvailable = hasData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun searchVeranstalterByOepsNr(oepsNr: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val verein = vereinRepository.findByOepsNr(oepsNr)
|
||||||
|
if (verein != null) {
|
||||||
|
setVeranstalter(
|
||||||
|
id = Uuid.parse(verein.id),
|
||||||
|
nummer = verein.oepsNr ?: "",
|
||||||
|
name = verein.name,
|
||||||
|
standardOrt = "${verein.plz ?: ""} ${verein.ort ?: ""}".trim(),
|
||||||
|
logo = null // Hier könnte später ein Logo-Service greifen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun nextStep() {
|
fun nextStep() {
|
||||||
state = state.copy(
|
state = state.copy(
|
||||||
currentStep = when (state.currentStep) {
|
currentStep = when (state.currentStep) {
|
||||||
@@ -149,6 +164,9 @@ class VeranstaltungWizardViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
state = state.copy(isSaving = true, error = null)
|
state = state.copy(isSaving = true, error = null)
|
||||||
try {
|
try {
|
||||||
|
// PDF-Kopiervorgang (lokal) entfernt wegen Import-Problemen in dieser Umgebung
|
||||||
|
// TODO: File-Copy Logik in ein Platform-Service auslagern
|
||||||
|
|
||||||
val token = authTokenManager.authState.value.token
|
val token = authTokenManager.authState.value.token
|
||||||
val response = httpClient.post("${NetworkConfig.baseUrl}/api/events") {
|
val response = httpClient.post("${NetworkConfig.baseUrl}/api/events") {
|
||||||
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
|
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
|
||||||
|
|||||||
+4
@@ -26,6 +26,10 @@ class FakeVereinRepository : VereinRepository {
|
|||||||
|
|
||||||
override suspend fun getVereine(): Result<List<Verein>> = Result.success(vereine.toList())
|
override suspend fun getVereine(): Result<List<Verein>> = Result.success(vereine.toList())
|
||||||
|
|
||||||
|
override suspend fun findByOepsNr(oepsNr: String): Verein? {
|
||||||
|
return vereine.find { it.oepsNr == oepsNr }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun saveVerein(verein: Verein): Result<Verein> {
|
override suspend fun saveVerein(verein: Verein): Result<Verein> {
|
||||||
val index = vereine.indexOfFirst { it.id == verein.id }
|
val index = vereine.indexOfFirst { it.id == verein.id }
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
|||||||
+11
@@ -41,6 +41,17 @@ class KtorVereinRepository(
|
|||||||
} else emptyList()
|
} else emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findByOepsNr(oepsNr: String): Verein? {
|
||||||
|
return runCatching {
|
||||||
|
val response = client.get("${ApiRoutes.Masterdata.VEREINE}/search") {
|
||||||
|
parameter("oepsNr", oepsNr)
|
||||||
|
}
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
response.body<VereinDto>().toDomain()
|
||||||
|
} else null
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun saveVerein(verein: Verein): Result<Verein> = runCatching {
|
override suspend fun saveVerein(verein: Verein): Result<Verein> = runCatching {
|
||||||
if (verein.id.isBlank() || verein.id.startsWith("new_")) {
|
if (verein.id.isBlank() || verein.id.startsWith("new_")) {
|
||||||
val request = VereinCreateRequest(
|
val request = VereinCreateRequest(
|
||||||
|
|||||||
+3
-3
@@ -164,13 +164,13 @@ fun DesktopContentArea(
|
|||||||
veranstalterId = currentScreen.id,
|
veranstalterId = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
|
onZurVeranstaltung = { evtId: Long -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
|
||||||
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(currentScreen.id)) },
|
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungNeu) },
|
||||||
)
|
)
|
||||||
|
|
||||||
// Neuer Flow: Veranstalter auswählen → Detail → Veranstaltung-Übersicht
|
// Neuer Flow: Veranstalter auswählen → Veranstaltung-Wizard
|
||||||
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahl(
|
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahl(
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onWeiter = { veranstalterId -> onNavigate(AppScreen.VeranstalterDetail(veranstalterId)) },
|
onWeiter = { _ -> onNavigate(AppScreen.VeranstaltungNeu) },
|
||||||
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -42,9 +42,15 @@ fun DesktopTopHeader(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (currentScreen !is AppScreen.DeviceInitialization) {
|
// Zurück-Button ausblenden auf Startseite oder im Setup
|
||||||
|
if (currentScreen !is AppScreen.DeviceInitialization && currentScreen !is AppScreen.VeranstaltungVerwaltung) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack,
|
onClick = {
|
||||||
|
// Verhindere Rücksprung zum Setup, wenn konfiguriert
|
||||||
|
if (currentScreen !is AppScreen.DeviceInitialization || !isConfigured) {
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.size(Dimens.IconSizeM)
|
modifier = Modifier.size(Dimens.IconSizeM)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
Reference in New Issue
Block a user