feat(masterdata): add ÖTO seed data, regulation validation tests, and profile module integration
- Introduced ÖTO 2026-compliant seed data (`V008__Seed_OETO_2026_Data.sql`) for tournament classes, license matrix, and age groups. - Added `RegulationSeedVerificationTest` to validate repository queries and domain eligibility logic. - Implemented a new `profile-feature` module covering user profile management and ZNS linking. - Integrated the `profile-feature` into the desktop shell and frontend with Koin DI configuration. - Extended CHANGELOG, ROADMAP, and architecture documentation to reflect related changes. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Dieses Modul kapselt die UI und Logik für die Profil-Verwaltung und den ZNS-Link.
|
||||
*/
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
group = "at.mocode.clients"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(projects.frontend.core.designSystem)
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(projects.frontend.core.sync)
|
||||
implementation(projects.frontend.core.localDb)
|
||||
implementation(projects.frontend.core.auth)
|
||||
implementation(projects.frontend.core.domain)
|
||||
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.material3)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.materialIconsExtended)
|
||||
|
||||
implementation(libs.bundles.kmp.common)
|
||||
implementation(libs.bundles.ktor.client.common)
|
||||
implementation(libs.bundles.compose.common)
|
||||
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.koin.compose)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.ktor.client.mock)
|
||||
}
|
||||
|
||||
jvmMain.dependencies {
|
||||
implementation(libs.ktor.client.cio)
|
||||
implementation(compose.uiTooling)
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package at.mocode.frontend.features.profile.data
|
||||
|
||||
import at.mocode.frontend.core.auth.data.AuthTokenManager
|
||||
import at.mocode.frontend.core.network.PlatformConfig
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Client für die Kommunikation mit dem Identity-Service (Profile-API).
|
||||
*/
|
||||
class ProfileApiClient(
|
||||
private val httpClient: HttpClient,
|
||||
private val authTokenManager: AuthTokenManager,
|
||||
private val baseUrl: String = PlatformConfig.resolveApiBaseUrl()
|
||||
) {
|
||||
|
||||
/**
|
||||
* Ruft das eigene Profil ab.
|
||||
*/
|
||||
suspend fun getMyProfile(): ProfileDto? {
|
||||
val token = authTokenManager.getToken() ?: return null
|
||||
return try {
|
||||
val response = httpClient.get("$baseUrl/api/v1/profiles/me") {
|
||||
header(HttpHeaders.Authorization, "Bearer $token")
|
||||
}
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> {
|
||||
response.body<ProfileDto>()
|
||||
}
|
||||
|
||||
HttpStatusCode.NoContent, HttpStatusCode.NotFound -> {
|
||||
null
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verknüpft den User mit einer ZNS-Satznummer.
|
||||
*/
|
||||
suspend fun linkToZns(satznummer: String): ProfileDto? {
|
||||
val token = authTokenManager.getToken() ?: return null
|
||||
return try {
|
||||
val response = httpClient.post("$baseUrl/api/v1/profiles/link/$satznummer") {
|
||||
header(HttpHeaders.Authorization, "Bearer $token")
|
||||
}
|
||||
if (response.status.isSuccess()) {
|
||||
response.body<ProfileDto>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert Profildaten.
|
||||
*/
|
||||
suspend fun updateProfile(request: ProfileUpdateRequest): ProfileDto? {
|
||||
val token = authTokenManager.getToken() ?: return null
|
||||
return try {
|
||||
val response = httpClient.put("$baseUrl/api/v1/profiles/me") {
|
||||
header(HttpHeaders.Authorization, "Bearer $token")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
if (response.status.isSuccess()) {
|
||||
response.body<ProfileDto>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ProfileUpdateRequest(
|
||||
val logoUrl: String? = null,
|
||||
val bio: String? = null,
|
||||
val contactEmail: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProfileDto(
|
||||
val satznummer: String? = null,
|
||||
val bio: String? = null,
|
||||
val contactEmail: String? = null,
|
||||
val logoUrl: String? = null
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package at.mocode.frontend.features.profile.di
|
||||
|
||||
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
||||
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val profileModule = module {
|
||||
singleOf(::ProfileApiClient)
|
||||
single { ProfileViewModel(get()) }
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package at.mocode.frontend.features.profile.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Badge
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Link
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.frontend.core.designsystem.components.LoadingIndicator
|
||||
import at.mocode.frontend.core.designsystem.components.MeldestelleButton
|
||||
import at.mocode.frontend.core.designsystem.components.MeldestelleTextField
|
||||
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
viewModel: ProfileViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val uiState = viewModel.uiState
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(
|
||||
text = "Mein Profil",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
)
|
||||
|
||||
if (uiState.isLoading) {
|
||||
LoadingIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||
} else {
|
||||
// Fehleranzeige
|
||||
uiState.errorMessage?.let { error ->
|
||||
ErrorMessage(
|
||||
message = error,
|
||||
onDismiss = { viewModel.clearError() }
|
||||
)
|
||||
}
|
||||
|
||||
// Erfolgsanzeige für Link
|
||||
if (uiState.linkSuccess) {
|
||||
SuccessMessage(
|
||||
message = "ZNS-Verknüpfung erfolgreich!",
|
||||
onDismiss = { viewModel.resetLinkStatus() }
|
||||
)
|
||||
}
|
||||
|
||||
val profile = uiState.profile
|
||||
|
||||
if (profile == null) {
|
||||
// ZNS Link Section wenn kein Profil existiert
|
||||
ZnsLinkSection(
|
||||
isLinking = uiState.isLinking,
|
||||
onLink = { satznummer -> viewModel.linkToZns(satznummer) }
|
||||
)
|
||||
} else {
|
||||
// Profil Details
|
||||
ProfileDetailsSection(
|
||||
profile = profile,
|
||||
onUpdate = { bio, email -> viewModel.updateProfile(bio, email) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ZnsLinkSection(
|
||||
isLinking: Boolean,
|
||||
onLink: (String) -> Unit
|
||||
) {
|
||||
var satznummer by remember { mutableStateOf("") }
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Default.Link, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("ZNS Identität verknüpfen", style = MaterialTheme.typography.titleLarge)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Verknüpfen Sie Ihren Account mit Ihrer offiziellen ZNS-Satznummer, um Nennungen abgeben zu können.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
MeldestelleTextField(
|
||||
value = satznummer,
|
||||
onValueChange = { satznummer = it },
|
||||
label = "ZNS Satznummer",
|
||||
placeholder = "z.B. 1234567",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !isLinking,
|
||||
leadingIcon = Icons.Default.Badge
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
MeldestelleButton(
|
||||
onClick = { onLink(satznummer) },
|
||||
text = "Jetzt verknüpfen",
|
||||
isLoading = isLinking,
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProfileDetailsSection(
|
||||
profile: at.mocode.frontend.features.profile.data.ProfileDto,
|
||||
onUpdate: (String?, String?) -> Unit
|
||||
) {
|
||||
var bio by remember(profile.bio) { mutableStateOf(profile.bio ?: "") }
|
||||
var contactEmail by remember(profile.contactEmail) { mutableStateOf(profile.contactEmail ?: "") }
|
||||
var isEditing by remember { mutableStateOf(false) }
|
||||
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Profildaten", style = MaterialTheme.typography.titleLarge)
|
||||
TextButton(onClick = {
|
||||
if (isEditing) onUpdate(bio, contactEmail)
|
||||
isEditing = !isEditing
|
||||
}) {
|
||||
Text(if (isEditing) "Speichern" else "Bearbeiten")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
DetailItem(label = "Satznummer", value = profile.satznummer ?: "Nicht verknüpft", icon = Icons.Default.Badge)
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
if (isEditing) {
|
||||
MeldestelleTextField(
|
||||
value = contactEmail,
|
||||
onValueChange = { contactEmail = it },
|
||||
label = "Kontakt E-Mail",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = Icons.Default.Email
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
MeldestelleTextField(
|
||||
value = bio,
|
||||
onValueChange = { bio = it },
|
||||
label = "Info / Bio",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = Icons.Default.Info,
|
||||
singleLine = false,
|
||||
maxLines = 5
|
||||
)
|
||||
} else {
|
||||
DetailItem(
|
||||
label = "Kontakt E-Mail",
|
||||
value = profile.contactEmail ?: "Nicht angegeben",
|
||||
icon = Icons.Default.Email
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
DetailItem(label = "Info", value = profile.bio ?: "Keine Information hinterlegt", icon = Icons.Default.Info)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DetailItem(label: String, value: String, icon: androidx.compose.ui.graphics.vector.ImageVector) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.secondary)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.secondary)
|
||||
Text(value, style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorMessage(message: String, onDismiss: () -> Unit) {
|
||||
Snackbar(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
action = { TextButton(onClick = onDismiss) { Text("OK", color = MaterialTheme.colorScheme.inverseOnSurface) } },
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
) {
|
||||
Text(message)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SuccessMessage(message: String, onDismiss: () -> Unit) {
|
||||
Snackbar(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
action = { TextButton(onClick = onDismiss) { Text("OK", color = MaterialTheme.colorScheme.inverseOnSurface) } },
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Text(message)
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
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.features.profile.data.ProfileApiClient
|
||||
import at.mocode.frontend.features.profile.data.ProfileDto
|
||||
import at.mocode.frontend.features.profile.data.ProfileUpdateRequest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class ProfileUiState(
|
||||
val isLoading: Boolean = false,
|
||||
val profile: ProfileDto? = null,
|
||||
val errorMessage: String? = null,
|
||||
val isLinking: Boolean = false,
|
||||
val linkSuccess: Boolean = false
|
||||
)
|
||||
|
||||
class ProfileViewModel(
|
||||
private val profileApiClient: ProfileApiClient
|
||||
) : ViewModel() {
|
||||
|
||||
var uiState by mutableStateOf(ProfileUiState())
|
||||
private set
|
||||
|
||||
init {
|
||||
loadProfile()
|
||||
}
|
||||
|
||||
fun loadProfile() {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, errorMessage = null)
|
||||
val profile = profileApiClient.getMyProfile()
|
||||
uiState = uiState.copy(isLoading = false, profile = profile)
|
||||
}
|
||||
}
|
||||
|
||||
fun linkToZns(satznummer: String) {
|
||||
if (satznummer.isBlank()) {
|
||||
uiState = uiState.copy(errorMessage = "Satznummer darf nicht leer sein.")
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLinking = true, errorMessage = null, linkSuccess = false)
|
||||
val profile = profileApiClient.linkToZns(satznummer)
|
||||
uiState = if (profile != null) {
|
||||
uiState.copy(
|
||||
isLinking = false,
|
||||
profile = profile,
|
||||
linkSuccess = true
|
||||
)
|
||||
} else {
|
||||
uiState.copy(
|
||||
isLinking = false,
|
||||
errorMessage = "Verknüpfung fehlgeschlagen. Bitte prüfen Sie die Satznummer."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateProfile(bio: String?, contactEmail: String?) {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, errorMessage = null)
|
||||
val profile = profileApiClient.updateProfile(
|
||||
ProfileUpdateRequest(
|
||||
bio = bio,
|
||||
contactEmail = contactEmail
|
||||
)
|
||||
)
|
||||
uiState = if (profile != null) {
|
||||
uiState.copy(isLoading = false, profile = profile)
|
||||
} else {
|
||||
uiState.copy(isLoading = false, errorMessage = "Profil-Update fehlgeschlagen.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
uiState = uiState.copy(errorMessage = null)
|
||||
}
|
||||
|
||||
fun resetLinkStatus() {
|
||||
uiState = uiState.copy(linkSuccess = false)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ kotlin {
|
||||
implementation(projects.frontend.features.veranstalterFeature)
|
||||
implementation(projects.frontend.features.veranstaltungFeature)
|
||||
implementation(projects.frontend.features.turnierFeature)
|
||||
implementation(project(":frontend:features:profile-feature"))
|
||||
|
||||
// Compose Desktop
|
||||
implementation(compose.desktop.currentOs)
|
||||
|
||||
@@ -11,6 +11,7 @@ import at.mocode.frontend.core.localdb.DatabaseProvider
|
||||
import at.mocode.frontend.core.localdb.localDbModule
|
||||
import at.mocode.frontend.core.network.networkModule
|
||||
import at.mocode.frontend.core.sync.di.syncModule
|
||||
import at.mocode.frontend.features.profile.di.profileModule
|
||||
import at.mocode.nennung.feature.di.nennungFeatureModule
|
||||
import at.mocode.ping.feature.di.pingFeatureModule
|
||||
import at.mocode.zns.feature.di.znsImportModule
|
||||
@@ -31,10 +32,11 @@ fun main() = application {
|
||||
pingFeatureModule,
|
||||
nennungFeatureModule,
|
||||
znsImportModule,
|
||||
profileModule,
|
||||
desktopModule,
|
||||
)
|
||||
}
|
||||
println("[DesktopApp] Koin initialisiert")
|
||||
println("[DesktopApp] KOIN initialisiert")
|
||||
} catch (e: Exception) {
|
||||
println("[DesktopApp] Koin-Warnung: ${e.message}")
|
||||
}
|
||||
|
||||
+14
-9
@@ -3,11 +3,10 @@ package at.mocode.desktop.screens.layout
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
@@ -19,19 +18,17 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import at.mocode.frontend.core.navigation.AppScreen
|
||||
import at.mocode.frontend.features.profile.presentation.ProfileScreen
|
||||
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
||||
import at.mocode.ping.feature.presentation.PingScreen
|
||||
import at.mocode.ping.feature.presentation.PingViewModel
|
||||
import at.mocode.turnier.feature.presentation.TurnierDetailScreen
|
||||
import at.mocode.turnier.feature.presentation.TurnierNeuScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterDetailScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterNeuScreen
|
||||
import at.mocode.veranstalter.feature.presentation.FakeVeranstalterStore
|
||||
import at.mocode.veranstalter.feature.presentation.FakeVeranstaltungStore
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterNeuScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.AdminUebersichtScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungDetailScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungNeuScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungUebersichtScreen
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
// Primärfarbe der TopBar (kann später ins Theme ausgelagert werden)
|
||||
@@ -300,7 +297,7 @@ private fun DesktopContentArea(
|
||||
}
|
||||
}
|
||||
|
||||
// Root-Screen: Leitet in V2-Fluss
|
||||
// Root-Screen: Leitet in V2-Fluss ab
|
||||
is AppScreen.Veranstaltungen -> {
|
||||
// Direkt zur Veranstalter-Auswahl V2
|
||||
at.mocode.desktop.v2.VeranstalterAuswahlV2(
|
||||
@@ -407,7 +404,7 @@ private fun DesktopContentArea(
|
||||
}
|
||||
is AppScreen.TurnierNeu -> {
|
||||
val evtId = currentScreen.veranstaltungId
|
||||
// V2: wir erlauben Turnier-Nr nur, wenn die Veranstaltung im V2-Store existiert
|
||||
// V2: Wir erlauben Turnier-Nr nur, wenn die Veranstaltung im V2-Store existiert
|
||||
val parent = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { v ->
|
||||
at.mocode.desktop.v2.StoreV2.eventsFor(v.id).any { it.id == evtId }
|
||||
}
|
||||
@@ -435,6 +432,14 @@ private fun DesktopContentArea(
|
||||
)
|
||||
}
|
||||
|
||||
// Profil-Screen
|
||||
is AppScreen.Profile -> {
|
||||
val profileViewModel: ProfileViewModel = koinInject()
|
||||
ProfileScreen(
|
||||
viewModel = profileViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
// Fallback → Root
|
||||
else -> AdminUebersichtScreen(
|
||||
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
|
||||
Reference in New Issue
Block a user