Enhance Veranstalter and Veranstaltung flows: add confirm dialog for event creation, refine navigation logic, and improve onboarding with keyboard focus handling.
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
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
This commit is contained in:
@@ -21,10 +21,5 @@ kotlin {
|
|||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmTest by getting {
|
|
||||||
dependencies {
|
|
||||||
implementation(projects.platform.platformTesting)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ class AltersklasseRechnerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ermittleAltersklassen berücksichtigt SpartenFilter`() {
|
fun `ermittleAltersklassen beruecksichtigt SpartenFilter`() {
|
||||||
val reiter = DomReiter(
|
val reiter = DomReiter(
|
||||||
personId = Uuid.random(),
|
personId = Uuid.random(),
|
||||||
satznummer = "123456",
|
satznummer = "123456",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
+3
@@ -17,6 +17,9 @@ private class FakeNav : NavigationPort {
|
|||||||
override fun navigateToScreen(screen: AppScreen) {
|
override fun navigateToScreen(screen: AppScreen) {
|
||||||
last = screen.route
|
last = screen.route
|
||||||
}
|
}
|
||||||
|
override fun navigateBack() {
|
||||||
|
// no-op for tests
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeUserProvider(private val user: User?) : CurrentUserProvider {
|
private class FakeUserProvider(private val user: User?) : CurrentUserProvider {
|
||||||
|
|||||||
+1
-2
@@ -35,8 +35,7 @@ enum class Sparte(val label: String) {
|
|||||||
SPRINGEN("Springen"),
|
SPRINGEN("Springen"),
|
||||||
VIELSEITIGKEIT("Vielseitigkeit"),
|
VIELSEITIGKEIT("Vielseitigkeit"),
|
||||||
VOLTIGIEREN("Voltigieren"),
|
VOLTIGIEREN("Voltigieren"),
|
||||||
FAHREN("Fahren"),
|
FAHREN("Fahren")
|
||||||
REINING("Reining")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ReiterStatus(val label: String, val color: Color) {
|
enum class ReiterStatus(val label: String, val color: Color) {
|
||||||
|
|||||||
+3
-1
@@ -5,6 +5,8 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.window.singleWindowApplication
|
import androidx.compose.ui.window.singleWindowApplication
|
||||||
|
import at.mocode.frontend.features.reiter.presentation.ReiterScreen
|
||||||
|
import at.mocode.frontend.features.reiter.presentation.ReiterViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hot-Reload Preview Entry Point
|
* Hot-Reload Preview Entry Point
|
||||||
@@ -27,7 +29,7 @@ private fun PreviewContent() {
|
|||||||
Surface {
|
Surface {
|
||||||
|
|
||||||
// --- REITER ---
|
// --- REITER ---
|
||||||
// ReiterScreen(viewModel = ReiterViewModel())
|
ReiterScreen(viewModel = ReiterViewModel())
|
||||||
|
|
||||||
// --- PFERDE ---
|
// --- PFERDE ---
|
||||||
// PferdeScreen(viewModel = PferdeViewModel())
|
// PferdeScreen(viewModel = PferdeViewModel())
|
||||||
|
|||||||
+45
-32
@@ -11,8 +11,11 @@ import androidx.compose.material.icons.filled.Wifi
|
|||||||
import androidx.compose.material.icons.filled.WifiOff
|
import androidx.compose.material.icons.filled.WifiOff
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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
|
||||||
@@ -56,6 +59,10 @@ fun DesktopMainLayout(
|
|||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onLogout: () -> Unit,
|
onLogout: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
// Onboarding-Eingaben zwischen Navigationswechseln behalten → State hier (außerhalb des when) hosten
|
||||||
|
var obGeraet by rememberSaveable { mutableStateOf("") }
|
||||||
|
var obKey by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
DesktopTopBar(
|
DesktopTopBar(
|
||||||
currentScreen = currentScreen,
|
currentScreen = currentScreen,
|
||||||
@@ -69,6 +76,10 @@ fun DesktopMainLayout(
|
|||||||
currentScreen = currentScreen,
|
currentScreen = currentScreen,
|
||||||
onNavigate = onNavigate,
|
onNavigate = onNavigate,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
|
obGeraet = obGeraet,
|
||||||
|
obKey = obKey,
|
||||||
|
onObGeraetChange = { obGeraet = it },
|
||||||
|
onObKeyChange = { obKey = it },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DesktopFooterBar()
|
DesktopFooterBar()
|
||||||
@@ -102,8 +113,8 @@ private fun DesktopTopBar(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
// Zurück-Pfeil (nur wenn nicht Root)
|
// Zurück-Pfeil: für alle außer Onboarding anzeigen (damit man von "Verwaltung" zurück kommt)
|
||||||
if (currentScreen !is AppScreen.VeranstaltungVerwaltung) {
|
if (currentScreen !is AppScreen.Onboarding) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Zurück",
|
contentDescription = "Zurück",
|
||||||
@@ -138,10 +149,10 @@ private fun DesktopTopBar(
|
|||||||
is AppScreen.VeranstalterNeu -> {
|
is AppScreen.VeranstalterNeu -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstalter auswählen",
|
text = "Veranstalter-Verwaltung",
|
||||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
@@ -154,10 +165,10 @@ private fun DesktopTopBar(
|
|||||||
is AppScreen.VeranstalterDetail -> {
|
is AppScreen.VeranstalterDetail -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstalter auswählen",
|
text = "Veranstalter-Verwaltung",
|
||||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
@@ -170,10 +181,10 @@ private fun DesktopTopBar(
|
|||||||
is AppScreen.VeranstaltungProfil -> {
|
is AppScreen.VeranstaltungProfil -> {
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstalter auswählen",
|
text = "Veranstalter-Verwaltung",
|
||||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||||
)
|
)
|
||||||
BreadcrumbSeparator()
|
BreadcrumbSeparator()
|
||||||
Text(
|
Text(
|
||||||
@@ -335,12 +346,21 @@ private fun DesktopContentArea(
|
|||||||
currentScreen: AppScreen,
|
currentScreen: AppScreen,
|
||||||
onNavigate: (AppScreen) -> Unit,
|
onNavigate: (AppScreen) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
obGeraet: String,
|
||||||
|
obKey: String,
|
||||||
|
onObGeraetChange: (String) -> Unit,
|
||||||
|
onObKeyChange: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (currentScreen) {
|
when (currentScreen) {
|
||||||
// Onboarding ohne Login
|
// Onboarding ohne Login
|
||||||
is AppScreen.Onboarding -> {
|
is AppScreen.Onboarding -> {
|
||||||
val authTokenManager: at.mocode.frontend.core.auth.data.AuthTokenManager = koinInject()
|
val authTokenManager: at.mocode.frontend.core.auth.data.AuthTokenManager = koinInject()
|
||||||
at.mocode.desktop.v2.OnboardingScreen { _, _ ->
|
at.mocode.desktop.v2.OnboardingScreen(
|
||||||
|
geraetName = obGeraet,
|
||||||
|
secureKey = obKey,
|
||||||
|
onGeraetNameChange = onObGeraetChange,
|
||||||
|
onSecureKeyChange = onObKeyChange,
|
||||||
|
) { _, _ ->
|
||||||
authTokenManager.setToken("dummy.jwt.token")
|
authTokenManager.setToken("dummy.jwt.token")
|
||||||
onNavigate(AppScreen.VeranstaltungVerwaltung)
|
onNavigate(AppScreen.VeranstaltungVerwaltung)
|
||||||
}
|
}
|
||||||
@@ -373,11 +393,9 @@ private fun DesktopContentArea(
|
|||||||
onEdit = { onNavigate(AppScreen.PferdProfil(it)) }
|
onEdit = { onNavigate(AppScreen.PferdProfil(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.PferdProfil -> PlaceholderScreen(
|
is AppScreen.PferdProfil -> at.mocode.desktop.v2.PferdProfilV2(
|
||||||
"Pferde-Profil #${currentScreen.id}",
|
id = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
|
||||||
actionLabel = "Zurück zur Zentrale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Reiter-Verwaltung & Profil ---
|
// --- Reiter-Verwaltung & Profil ---
|
||||||
@@ -386,11 +404,9 @@ private fun DesktopContentArea(
|
|||||||
onEdit = { onNavigate(AppScreen.ReiterProfil(it)) }
|
onEdit = { onNavigate(AppScreen.ReiterProfil(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.ReiterProfil -> PlaceholderScreen(
|
is AppScreen.ReiterProfil -> at.mocode.desktop.v2.ReiterProfilV2(
|
||||||
"Reiter-Profil #${currentScreen.id}",
|
id = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
|
||||||
actionLabel = "Zurück zur Zentrale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Verein-Verwaltung & Profil ---
|
// --- Verein-Verwaltung & Profil ---
|
||||||
@@ -399,11 +415,9 @@ private fun DesktopContentArea(
|
|||||||
onEdit = { onNavigate(AppScreen.VereinProfil(it)) }
|
onEdit = { onNavigate(AppScreen.VereinProfil(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.VereinProfil -> PlaceholderScreen(
|
is AppScreen.VereinProfil -> at.mocode.desktop.v2.VereinProfilV2(
|
||||||
"Verein-Profil #${currentScreen.id}",
|
id = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAction = { onNavigate(AppScreen.VereinVerwaltung) },
|
|
||||||
actionLabel = "Zurück zur Zentrale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Funktionaer-Verwaltung & Profil ---
|
// --- Funktionaer-Verwaltung & Profil ---
|
||||||
@@ -412,24 +426,23 @@ private fun DesktopContentArea(
|
|||||||
onEdit = { onNavigate(AppScreen.FunktionaerProfil(it)) }
|
onEdit = { onNavigate(AppScreen.FunktionaerProfil(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.FunktionaerProfil -> PlaceholderScreen(
|
is AppScreen.FunktionaerProfil -> at.mocode.desktop.v2.FunktionaerProfilV2(
|
||||||
"Funktionär-Profil #${currentScreen.id}",
|
id = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAction = { onNavigate(AppScreen.FunktionaerVerwaltung) },
|
|
||||||
actionLabel = "Zurück zur Zentrale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Veranstalter-Verwaltung & Profil ---
|
// --- Veranstalter-Verwaltung & Profil ---
|
||||||
is AppScreen.VeranstalterVerwaltung -> at.mocode.desktop.v2.VeranstalterVerwaltungScreen(
|
is AppScreen.VeranstalterVerwaltung -> at.mocode.desktop.v2.VeranstalterVerwaltungScreen(
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
|
onNew = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||||
onEdit = { onNavigate(AppScreen.VeranstalterProfil(it)) }
|
onEdit = { onNavigate(AppScreen.VeranstalterProfil(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.VeranstalterProfil -> PlaceholderScreen(
|
is AppScreen.VeranstalterProfil -> at.mocode.desktop.v2.VeranstalterDetailV2(
|
||||||
"Veranstalter-Profil #${currentScreen.id}",
|
veranstalterId = currentScreen.id,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onAction = { onNavigate(AppScreen.PferdProfil(1L)) },
|
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
|
||||||
actionLabel = "Pferde-Profil öffnen"
|
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(currentScreen.id)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -445,9 +458,9 @@ private fun DesktopContentArea(
|
|||||||
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||||
)
|
)
|
||||||
|
|
||||||
is AppScreen.VeranstalterNeu -> VeranstalterNeuScreen(
|
is AppScreen.VeranstalterNeu -> at.mocode.desktop.v2.VeranstalterAnlegenWizard(
|
||||||
onAbbrechen = onBack,
|
onCancel = onBack,
|
||||||
onSpeichern = { _, _, _ -> onBack() },
|
onVereinCreated = { newId -> onNavigate(AppScreen.VeranstalterProfil(newId)) }
|
||||||
)
|
)
|
||||||
is AppScreen.VeranstalterDetail -> {
|
is AppScreen.VeranstalterDetail -> {
|
||||||
val vId = currentScreen.veranstalterId
|
val vId = currentScreen.veranstalterId
|
||||||
|
|||||||
+7
-5
@@ -5,8 +5,10 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
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.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -35,7 +37,7 @@ fun <T> ManagementTableScreen(
|
|||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = "Zurück")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||||
}
|
}
|
||||||
Text(title, style = MaterialTheme.typography.headlineMedium)
|
Text(title, style = MaterialTheme.typography.headlineMedium)
|
||||||
}
|
}
|
||||||
@@ -87,7 +89,7 @@ fun <T> ManagementTableScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||||
|
|
||||||
// Table Body
|
// Table Body
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
@@ -121,7 +123,7 @@ fun <T> ManagementTableScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider()
|
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +250,7 @@ fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onNew: () -> Unit, onEdit: (Long) -> Unit) {
|
||||||
// Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten
|
// Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten
|
||||||
// Wir nutzen hier die 'vereine' Liste aus dem Store.
|
// Wir nutzen hier die 'vereine' Liste aus dem Store.
|
||||||
val vereine = StoreV2.vereine
|
val vereine = StoreV2.vereine
|
||||||
@@ -268,7 +270,7 @@ fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
|||||||
TableColumn("Email", { it.email ?: "-" }, weight = 1f)
|
TableColumn("Email", { it.email ?: "-" }, weight = 1f)
|
||||||
),
|
),
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onNew = { },
|
onNew = onNew,
|
||||||
onEdit = { onEdit(it.id) },
|
onEdit = { onEdit(it.id) },
|
||||||
onDelete = { },
|
onDelete = { },
|
||||||
onSearch = { filter = it }
|
onSearch = { filter = it }
|
||||||
|
|||||||
+471
-56
@@ -1,12 +1,14 @@
|
|||||||
package at.mocode.desktop.v2
|
package at.mocode.desktop.v2
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
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.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
import androidx.compose.material.icons.filled.VisibilityOff
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -14,30 +16,66 @@ 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
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.FocusDirection
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OnboardingScreen(onContinue: (String, String) -> Unit) {
|
fun OnboardingScreen(
|
||||||
|
geraetName: String,
|
||||||
|
secureKey: String,
|
||||||
|
onGeraetNameChange: (String) -> Unit,
|
||||||
|
onSecureKeyChange: (String) -> Unit,
|
||||||
|
onContinue: (String, String) -> Unit,
|
||||||
|
) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Text("Onboarding", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold)
|
Text("Onboarding", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold)
|
||||||
var geraetName by remember { mutableStateOf("") }
|
|
||||||
var key by remember { mutableStateOf("") }
|
|
||||||
var showPw by remember { mutableStateOf(false) }
|
var showPw by remember { mutableStateOf(false) }
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
val frName = remember { FocusRequester() }
|
||||||
|
val frKey = remember { FocusRequester() }
|
||||||
|
val frBtn = remember { FocusRequester() }
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = geraetName,
|
value = geraetName,
|
||||||
onValueChange = { geraetName = it },
|
onValueChange = { onGeraetNameChange(it) },
|
||||||
label = { Text("Gerätename (Pflicht)") },
|
label = { Text("Gerätename (Pflicht)") },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(frName)
|
||||||
|
.onKeyEvent { e ->
|
||||||
|
if (e.type == KeyEventType.KeyUp) {
|
||||||
|
when (e.key) {
|
||||||
|
Key.Tab, Key.Enter -> {
|
||||||
|
focusManager.moveFocus(FocusDirection.Next)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
,
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
||||||
|
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = key,
|
value = secureKey,
|
||||||
onValueChange = { key = it },
|
onValueChange = { onSecureKeyChange(it) },
|
||||||
label = { Text("Sicherheitsschlüssel (Pflicht)") },
|
label = { Text("Sicherheitsschlüssel (Pflicht)") },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = { showPw = !showPw }) {
|
IconButton(onClick = { showPw = !showPw }) {
|
||||||
@@ -45,12 +83,53 @@ fun OnboardingScreen(onContinue: (String, String) -> Unit) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
visualTransformation = if (showPw) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (showPw) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(frKey)
|
||||||
|
.onKeyEvent { e ->
|
||||||
|
if (e.type == KeyEventType.KeyUp) {
|
||||||
|
when (e.key) {
|
||||||
|
Key.Tab -> {
|
||||||
|
focusManager.moveFocus(FocusDirection.Next)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Key.Enter -> {
|
||||||
|
if (geraetName.trim().length >= 3 && secureKey.trim().length >= 8) {
|
||||||
|
onContinue(geraetName, secureKey)
|
||||||
|
} else {
|
||||||
|
focusManager.moveFocus(FocusDirection.Next)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
,
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||||
|
keyboardActions = KeyboardActions(onDone = {
|
||||||
|
if (geraetName.trim().length >= 3 && secureKey.trim().length >= 8) {
|
||||||
|
onContinue(geraetName, secureKey)
|
||||||
|
} else {
|
||||||
|
focusManager.moveFocus(FocusDirection.Next)
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
val enabled = geraetName.trim().length >= 3 && key.trim().length >= 8
|
val enabled = geraetName.trim().length >= 3 && secureKey.trim().length >= 8
|
||||||
Button(onClick = { onContinue(geraetName, key) }, enabled = enabled) {
|
Button(
|
||||||
Text("Weiter zum Veranstalter‑Flow")
|
onClick = { onContinue(geraetName, secureKey) },
|
||||||
|
enabled = enabled,
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(frBtn)
|
||||||
|
.onKeyEvent { e ->
|
||||||
|
if (e.type == KeyEventType.KeyUp && (e.key == Key.Enter)) {
|
||||||
|
if (enabled) onContinue(geraetName, secureKey)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Zu den Veranstaltungen")
|
||||||
}
|
}
|
||||||
if (!enabled) Text("Mind. 3 Zeichen für Namen und 8 Zeichen für Schlüssel", color = Color(0xFFB00020))
|
if (!enabled) Text("Mind. 3 Zeichen für Namen und 8 Zeichen für Schlüssel", color = Color(0xFFB00020))
|
||||||
}
|
}
|
||||||
@@ -58,6 +137,298 @@ fun OnboardingScreen(onContinue: (String, String) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PferdProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
|
DesktopThemeV2 {
|
||||||
|
val pferd = remember(id) { StoreV2.pferde.firstOrNull { it.id == id } }
|
||||||
|
if (pferd == null) { Text("Pferd nicht gefunden"); return@DesktopThemeV2 }
|
||||||
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
||||||
|
Text("Pferde-Profil", style = MaterialTheme.typography.titleLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
|
Card(Modifier.fillMaxWidth()) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(modifier = Modifier.size(56.dp).background(Color(0xFF374151), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) {
|
||||||
|
Text(pferd.name.take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Text(pferd.name, style = MaterialTheme.typography.titleMedium)
|
||||||
|
val l2 = listOfNotNull(pferd.oepsNummer?.let { "OEPS: $it" }, pferd.feiId?.let { "FEI: $it" }).joinToString(" · ")
|
||||||
|
if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280))
|
||||||
|
val l3 = listOfNotNull(pferd.geburtsdatum?.let { "geb. $it" }, pferd.farbe).joinToString(" · ")
|
||||||
|
if (l3.isNotBlank()) Text(l3, color = Color(0xFF6B7280))
|
||||||
|
}
|
||||||
|
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editOpen) {
|
||||||
|
var name by remember { mutableStateOf(pferd.name) }
|
||||||
|
var oeps by remember { mutableStateOf(pferd.oepsNummer ?: "") }
|
||||||
|
var fei by remember { mutableStateOf(pferd.feiId ?: "") }
|
||||||
|
var geb by remember { mutableStateOf(pferd.geburtsdatum ?: "") }
|
||||||
|
var farbe by remember { mutableStateOf(pferd.farbe ?: "") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editOpen = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
pferd.name = name
|
||||||
|
pferd.oepsNummer = oeps.ifBlank { null }
|
||||||
|
pferd.feiId = fei.ifBlank { null }
|
||||||
|
pferd.geburtsdatum = geb.ifBlank { null }
|
||||||
|
pferd.farbe = farbe.ifBlank { null }
|
||||||
|
editOpen = false
|
||||||
|
}) { Text("Speichern") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Pferd bearbeiten") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(geb, { geb = it }, label = { Text("Geburtsdatum") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(farbe, { farbe = it }, label = { Text("Farbe") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReiterProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
|
DesktopThemeV2 {
|
||||||
|
val r = remember(id) { StoreV2.reiter.firstOrNull { it.id == id } }
|
||||||
|
if (r == null) { Text("Reiter nicht gefunden"); return@DesktopThemeV2 }
|
||||||
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
||||||
|
Text("Reiter-Profil", style = MaterialTheme.typography.titleLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
|
Card(Modifier.fillMaxWidth()) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(modifier = Modifier.size(56.dp).background(Color(0xFF4B5563), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) {
|
||||||
|
val initials = (r.vorname + " " + r.nachname).trim().split(" ").mapNotNull { it.firstOrNull()?.toString() }.take(2).joinToString("")
|
||||||
|
Text(initials.uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Text("${r.vorname} ${r.nachname}", style = MaterialTheme.typography.titleMedium)
|
||||||
|
val l2 = listOfNotNull(r.oepsNummer?.let { "OEPS: $it" }, r.feiId?.let { "FEI: $it" }, r.lizenzKlasse.takeIf { it.isNotBlank() } ).joinToString(" · ")
|
||||||
|
if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280))
|
||||||
|
r.verein?.let { Text(it, color = Color(0xFF6B7280)) }
|
||||||
|
}
|
||||||
|
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editOpen) {
|
||||||
|
var vor by remember { mutableStateOf(r.vorname) }
|
||||||
|
var nach by remember { mutableStateOf(r.nachname) }
|
||||||
|
var oeps by remember { mutableStateOf(r.oepsNummer ?: "") }
|
||||||
|
var fei by remember { mutableStateOf(r.feiId ?: "") }
|
||||||
|
var liz by remember { mutableStateOf(r.lizenzKlasse) }
|
||||||
|
var verein by remember { mutableStateOf(r.verein ?: "") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editOpen = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
r.vorname = vor
|
||||||
|
r.nachname = nach
|
||||||
|
r.oepsNummer = oeps.ifBlank { null }
|
||||||
|
r.feiId = fei.ifBlank { null }
|
||||||
|
r.lizenzKlasse = liz
|
||||||
|
r.verein = verein.ifBlank { null }
|
||||||
|
editOpen = false
|
||||||
|
}) { Text("Speichern") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Reiter bearbeiten") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(vor, { vor = it }, label = { Text("Vorname") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(nach, { nach = it }, label = { Text("Nachname") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(oeps, { oeps = it }, label = { Text("ÖPS-Nr.") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(fei, { fei = it }, label = { Text("FEI-ID") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(liz, { liz = it }, label = { Text("Lizenzklasse") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(verein, { verein = it }, label = { Text("Verein") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VereinProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
|
DesktopThemeV2 {
|
||||||
|
val v = remember(id) { StoreV2.vereine.firstOrNull { it.id == id } }
|
||||||
|
if (v == null) { Text("Verein nicht gefunden"); return@DesktopThemeV2 }
|
||||||
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
||||||
|
Text("Vereins-Profil", style = MaterialTheme.typography.titleLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
|
Card(Modifier.fillMaxWidth()) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(modifier = Modifier.size(56.dp).background(Color(0xFF1F2937), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) {
|
||||||
|
Text((v.kurzname ?: v.name).take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Text(v.name, style = MaterialTheme.typography.titleMedium)
|
||||||
|
val l2 = listOfNotNull("OEPS: ${v.oepsNummer}", v.ort, v.plz, v.strasse).filter { it.isNotBlank() }.joinToString(" · ")
|
||||||
|
if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280))
|
||||||
|
val l3 = listOfNotNull(v.email, v.telefon).filter { !it.isNullOrBlank() }.joinToString(" · ")
|
||||||
|
if (l3.isNotBlank()) Text(l3, color = Color(0xFF6B7280))
|
||||||
|
}
|
||||||
|
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editOpen) {
|
||||||
|
var name by remember { mutableStateOf(v.name) }
|
||||||
|
var oeps by remember { mutableStateOf(v.oepsNummer) }
|
||||||
|
var ort by remember { mutableStateOf(v.ort ?: "") }
|
||||||
|
var plz by remember { mutableStateOf(v.plz ?: "") }
|
||||||
|
var strasse by remember { mutableStateOf(v.strasse ?: "") }
|
||||||
|
var email by remember { mutableStateOf(v.email ?: "") }
|
||||||
|
var tel by remember { mutableStateOf(v.telefon ?: "") }
|
||||||
|
var logo by remember { mutableStateOf(v.logoUrl ?: "") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editOpen = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
v.name = name
|
||||||
|
v.oepsNummer = oeps
|
||||||
|
v.ort = ort.ifBlank { null }
|
||||||
|
v.plz = plz.ifBlank { null }
|
||||||
|
v.strasse = strasse.ifBlank { null }
|
||||||
|
v.email = email.ifBlank { null }
|
||||||
|
v.telefon = tel.ifBlank { null }
|
||||||
|
v.logoUrl = logo.ifBlank { null }
|
||||||
|
editOpen = false
|
||||||
|
}) { Text("Speichern") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Verein bearbeiten") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(oeps, { oeps = it }, label = { Text("OEPS-Nummer") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(logo, { logo = it }, label = { Text("Logo-URL") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(ort, { ort = it }, label = { Text("Ort") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(plz, { plz = it }, label = { Text("PLZ") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
OutlinedTextField(strasse, { strasse = it }, label = { Text("Straße / Adresse") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(email, { email = it }, label = { Text("E-Mail") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(tel, { tel = it }, label = { Text("Telefon") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FunktionaerProfilV2(id: Long, onBack: () -> Unit) {
|
||||||
|
DesktopThemeV2 {
|
||||||
|
val f = remember(id) { StoreV2.funktionaere.firstOrNull { it.id == id } }
|
||||||
|
if (f == null) { Text("Funktionär nicht gefunden"); return@DesktopThemeV2 }
|
||||||
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück") }
|
||||||
|
Text("Funktionärs-Profil", style = MaterialTheme.typography.titleLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
|
Card(Modifier.fillMaxWidth()) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(modifier = Modifier.size(56.dp).background(Color(0xFF111827), shape = MaterialTheme.shapes.small), contentAlignment = Alignment.Center) {
|
||||||
|
val initials = (f.vorname + " " + f.nachname).trim().split(" ").mapNotNull { it.firstOrNull()?.toString() }.take(2).joinToString("")
|
||||||
|
Text(initials.uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(12.dp))
|
||||||
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Text("${f.vorname} ${f.nachname}", style = MaterialTheme.typography.titleMedium)
|
||||||
|
val l2 = listOfNotNull(f.richterNummer?.let { "Nr. $it" }, f.richterQualifikation?.let { "Qual.: $it" }).joinToString(" · ")
|
||||||
|
if (l2.isNotBlank()) Text(l2, color = Color(0xFF6B7280))
|
||||||
|
f.email?.let { Text(it, color = Color(0xFF6B7280)) }
|
||||||
|
}
|
||||||
|
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editOpen) {
|
||||||
|
var vor by remember { mutableStateOf(f.vorname) }
|
||||||
|
var nach by remember { mutableStateOf(f.nachname) }
|
||||||
|
var num by remember { mutableStateOf(f.richterNummer ?: "") }
|
||||||
|
var qual by remember { mutableStateOf(f.richterQualifikation ?: "") }
|
||||||
|
var email by remember { mutableStateOf(f.email ?: "") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editOpen = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
f.vorname = vor
|
||||||
|
f.nachname = nach
|
||||||
|
f.richterNummer = num.ifBlank { null }
|
||||||
|
f.richterQualifikation = qual.ifBlank { null }
|
||||||
|
f.email = email.ifBlank { null }
|
||||||
|
editOpen = false
|
||||||
|
}) { Text("Speichern") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Funktionär bearbeiten") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(vor, { vor = it }, label = { Text("Vorname") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(nach, { nach = it }, label = { Text("Nachname") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(num, { num = it }, label = { Text("Nummer") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(qual, { qual = it }, label = { Text("Qualifikation") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
OutlinedTextField(email, { email = it }, label = { Text("E-Mail") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VeranstalterAuswahlV2(
|
fun VeranstalterAuswahlV2(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
@@ -125,62 +496,106 @@ fun VeranstalterDetailV2(
|
|||||||
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profil-Bereich (Logo URL, Ansprechpartner, Kontakt, Adresse)
|
// Veranstalter Vorschau-Karte mit Bearbeiten-Dialog
|
||||||
val verein = remember(veranstalterId) { StoreV2.vereine.firstOrNull { it.id == veranstalterId } }
|
val verein = remember(veranstalterId) { StoreV2.vereine.firstOrNull { it.id == veranstalterId } }
|
||||||
if (verein != null) {
|
if (verein != null) {
|
||||||
Card {
|
var editOpen by remember { mutableStateOf(false) }
|
||||||
Column(Modifier.fillMaxWidth().padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Card(Modifier.fillMaxWidth()) {
|
||||||
Text("Veranstalter‑Profil", style = MaterialTheme.typography.titleMedium)
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
OutlinedTextField(
|
// Logo/Avatar
|
||||||
value = verein.logoUrl ?: "",
|
Box(
|
||||||
onValueChange = { verein.logoUrl = it.ifBlank { null } },
|
modifier = Modifier.size(56.dp).background(Color(0xFF1F2937), shape = MaterialTheme.shapes.small),
|
||||||
label = { Text("Logo‑URL (optional)") },
|
contentAlignment = Alignment.Center
|
||||||
modifier = Modifier.fillMaxWidth()
|
) {
|
||||||
)
|
Text((verein.kurzname ?: verein.name).take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = verein.ort ?: "",
|
|
||||||
onValueChange = { verein.ort = it.ifBlank { null } },
|
|
||||||
label = { Text("Ansprechpartner / Ort (optional)") },
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
OutlinedTextField(
|
|
||||||
value = verein.telefon ?: "",
|
|
||||||
onValueChange = { verein.telefon = it.ifBlank { null } },
|
|
||||||
label = { Text("Telefon (optional)") },
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Spacer(Modifier.width(12.dp))
|
||||||
OutlinedTextField(
|
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
value = verein.email ?: "",
|
Text(verein.name, style = MaterialTheme.typography.titleMedium)
|
||||||
onValueChange = { verein.email = it.ifBlank { null } },
|
val line2 = listOfNotNull("OEPS: ${verein.oepsNummer}", verein.ort, verein.plz, verein.strasse).filter { it.isNotBlank() }.joinToString(" · ")
|
||||||
label = { Text("E‑Mail (optional)") },
|
if (line2.isNotBlank()) Text(line2, color = Color(0xFF6B7280))
|
||||||
modifier = Modifier.weight(1f)
|
val line3 = listOfNotNull(verein.email, verein.telefon).filter { !it.isNullOrBlank() }.joinToString(" · ")
|
||||||
)
|
if (line3.isNotBlank()) Text(line3, color = Color(0xFF6B7280))
|
||||||
OutlinedTextField(
|
|
||||||
value = verein.oepsNummer,
|
|
||||||
onValueChange = { verein.oepsNummer = it },
|
|
||||||
label = { Text("OEPS‑Nummer") },
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||||
value = verein.strasse ?: "",
|
|
||||||
onValueChange = { verein.strasse = it.ifBlank { null } },
|
|
||||||
label = { Text("Adresse / Straße (optional)") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
minLines = 2
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editOpen) {
|
||||||
|
// Lokale Edit-Felder
|
||||||
|
var name by remember { mutableStateOf(verein.name) }
|
||||||
|
var oeps by remember { mutableStateOf(verein.oepsNummer) }
|
||||||
|
var ort by remember { mutableStateOf(verein.ort ?: "") }
|
||||||
|
var plz by remember { mutableStateOf(verein.plz ?: "") }
|
||||||
|
var strasse by remember { mutableStateOf(verein.strasse ?: "") }
|
||||||
|
var email by remember { mutableStateOf(verein.email ?: "") }
|
||||||
|
var tel by remember { mutableStateOf(verein.telefon ?: "") }
|
||||||
|
var logo by remember { mutableStateOf(verein.logoUrl ?: "") }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editOpen = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
// Speichern in Store
|
||||||
|
verein.name = name
|
||||||
|
verein.oepsNummer = oeps
|
||||||
|
verein.ort = ort.ifBlank { null }
|
||||||
|
verein.plz = plz.ifBlank { null }
|
||||||
|
verein.strasse = strasse.ifBlank { null }
|
||||||
|
verein.email = email.ifBlank { null }
|
||||||
|
verein.telefon = tel.ifBlank { null }
|
||||||
|
verein.logoUrl = logo.ifBlank { null }
|
||||||
|
editOpen = false
|
||||||
|
}) { Text("Speichern") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { editOpen = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Veranstalter bearbeiten") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(value = oeps, onValueChange = { oeps = it }, label = { Text("OEPS-Nummer") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(value = logo, onValueChange = { logo = it }, label = { Text("Logo-URL") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(value = ort, onValueChange = { ort = it }, label = { Text("Ort") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(value = plz, onValueChange = { plz = it }, label = { Text("PLZ") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
OutlinedTextField(value = strasse, onValueChange = { strasse = it }, label = { Text("Straße / Adresse") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(value = email, onValueChange = { email = it }, label = { Text("E-Mail") }, modifier = Modifier.weight(1f))
|
||||||
|
OutlinedTextField(value = tel, onValueChange = { tel = it }, label = { Text("Telefon") }, modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val events = StoreV2.eventsFor(veranstalterId)
|
val events = StoreV2.eventsFor(veranstalterId)
|
||||||
if (events.isEmpty()) Text("Noch keine Veranstaltungen angelegt.", color = Color(0xFF6B7280))
|
// Filter-/Suchmaske
|
||||||
|
var search by remember { mutableStateOf("") }
|
||||||
|
OutlinedTextField(
|
||||||
|
value = search,
|
||||||
|
onValueChange = { search = it },
|
||||||
|
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
|
||||||
|
placeholder = { Text("Veranstaltungen suchen…") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
val filtered = remember(events, search) {
|
||||||
|
val q = search.trim()
|
||||||
|
if (q.isEmpty()) events else events.filter {
|
||||||
|
it.titel.contains(q, ignoreCase = true) ||
|
||||||
|
it.status.contains(q, ignoreCase = true) ||
|
||||||
|
it.datumVon.contains(q, ignoreCase = true) ||
|
||||||
|
(it.datumBis?.contains(q, ignoreCase = true) == true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filtered.isEmpty()) Text("Keine passenden Veranstaltungen gefunden.", color = Color(0xFF6B7280))
|
||||||
|
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(events) { evt ->
|
items(filtered) { evt ->
|
||||||
Card(Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
|
Card(Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
|
||||||
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column(Modifier.weight(1f)) {
|
Column(Modifier.weight(1f)) {
|
||||||
|
|||||||
+56
-18
@@ -417,7 +417,13 @@ fun VeranstaltungKonfigV2(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (currentStep > 1) {
|
if (currentStep > 1) {
|
||||||
currentStep--
|
// Wenn wir aus einem konkreten Veranstalter kommen (id > 0),
|
||||||
|
// gehen wir bei Zurück direkt ins Profil statt auf Schritt 1.
|
||||||
|
if (veranstalterId != 0L) {
|
||||||
|
onBack()
|
||||||
|
} else {
|
||||||
|
currentStep--
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onBack()
|
onBack()
|
||||||
}
|
}
|
||||||
@@ -667,28 +673,60 @@ fun VeranstaltungKonfigV2(
|
|||||||
Spacer(Modifier.width(1.dp))
|
Spacer(Modifier.width(1.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showConfirm by remember { mutableStateOf(false) }
|
||||||
|
if (showConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showConfirm = false },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
val id = System.currentTimeMillis()
|
||||||
|
val v = VeranstaltungV2(
|
||||||
|
id = id,
|
||||||
|
veranstalterId = selectedVereinId,
|
||||||
|
titel = titel.trim(),
|
||||||
|
datumVon = von.trim(),
|
||||||
|
datumBis = bis.trim().ifBlank { null },
|
||||||
|
untertitel = untertitel.trim(),
|
||||||
|
ort = ort.trim().ifBlank { StoreV2.vereine.find { it.id == selectedVereinId }?.ort ?: "" },
|
||||||
|
logoUrl = logoUrl.trim().ifBlank { null }
|
||||||
|
)
|
||||||
|
sponsorenText.split(",").filter { it.isNotBlank() }.forEach { v.sponsoren.add(it.trim()) }
|
||||||
|
StoreV2.addEventFirst(selectedVereinId, v)
|
||||||
|
showConfirm = false
|
||||||
|
onSaved(id, selectedVereinId)
|
||||||
|
}) { Text("Anlegen") }
|
||||||
|
},
|
||||||
|
dismissButton = { TextButton(onClick = { showConfirm = false }) { Text("Abbrechen") } },
|
||||||
|
title = { Text("Veranstaltung final anlegen?") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("Bitte die Daten prüfen. Für diese Veranstaltung wird eine eigene Datenbank initialisiert.")
|
||||||
|
HorizontalDivider()
|
||||||
|
val titelText = titel.trim()
|
||||||
|
val untertitelText = untertitel.trim().ifBlank { "-" }
|
||||||
|
val vonText = von.trim()
|
||||||
|
val bisText = bis.trim()
|
||||||
|
val zeitraumText = if (bisText.isNotEmpty()) "$vonText – $bisText" else vonText
|
||||||
|
val vName = StoreV2.vereine.find { it.id == selectedVereinId }?.name ?: "#$selectedVereinId"
|
||||||
|
val spons = sponsorenText.split(',').map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
Text("Titel: $titelText")
|
||||||
|
Text("Untertitel: $untertitelText")
|
||||||
|
Text("Zeitraum: $zeitraumText")
|
||||||
|
Text("Veranstalter: $vName")
|
||||||
|
if (logoUrl.isNotBlank()) Text("Logo: ${logoUrl.trim()}")
|
||||||
|
if (spons.isNotEmpty()) Text("Sponsoren: ${spons.joinToString(", ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (currentStep < 3) {
|
if (currentStep < 3) {
|
||||||
currentStep++
|
currentStep++
|
||||||
} else {
|
} else {
|
||||||
val id = System.currentTimeMillis()
|
showConfirm = true
|
||||||
val v = VeranstaltungV2(
|
|
||||||
id = id,
|
|
||||||
veranstalterId = selectedVereinId,
|
|
||||||
titel = titel.trim(),
|
|
||||||
datumVon = von.trim(),
|
|
||||||
datumBis = bis.trim().ifBlank { null },
|
|
||||||
untertitel = untertitel.trim(),
|
|
||||||
ort = ort.trim().ifBlank { StoreV2.vereine.find { it.id == selectedVereinId }?.ort ?: "" },
|
|
||||||
logoUrl = logoUrl.trim().ifBlank { null }
|
|
||||||
)
|
|
||||||
sponsorenText.split(",").filter { it.isNotBlank() }.forEach {
|
|
||||||
v.sponsoren.add(it.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
StoreV2.addEventFirst(selectedVereinId, v)
|
|
||||||
onSaved(id, selectedVereinId)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = when (currentStep) {
|
enabled = when (currentStep) {
|
||||||
|
|||||||
Reference in New Issue
Block a user