Enhance Veranstalter and Veranstaltung flows: add confirm dialog for event creation, refine navigation logic, and improve onboarding with keyboard focus handling.
Some checks failed
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
Some checks failed
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:
parent
b990f4dc05
commit
d3d80f6995
|
|
@ -21,10 +21,5 @@ kotlin {
|
|||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(projects.platform.platformTesting)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class AltersklasseRechnerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `ermittleAltersklassen berücksichtigt SpartenFilter`() {
|
||||
fun `ermittleAltersklassen beruecksichtigt SpartenFilter`() {
|
||||
val reiter = DomReiter(
|
||||
personId = Uuid.random(),
|
||||
satznummer = "123456",
|
||||
|
|
|
|||
BIN
docs/06_Frontend/Veranstalter-Card-v01.png
Normal file
BIN
docs/06_Frontend/Veranstalter-Card-v01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/06_Frontend/Veranstalter-Profil-Card-v01.png
Normal file
BIN
docs/06_Frontend/Veranstalter-Profil-Card-v01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -17,6 +17,9 @@ private class FakeNav : NavigationPort {
|
|||
override fun navigateToScreen(screen: AppScreen) {
|
||||
last = screen.route
|
||||
}
|
||||
override fun navigateBack() {
|
||||
// no-op for tests
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeUserProvider(private val user: User?) : CurrentUserProvider {
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ enum class Sparte(val label: String) {
|
|||
SPRINGEN("Springen"),
|
||||
VIELSEITIGKEIT("Vielseitigkeit"),
|
||||
VOLTIGIEREN("Voltigieren"),
|
||||
FAHREN("Fahren"),
|
||||
REINING("Reining")
|
||||
FAHREN("Fahren")
|
||||
}
|
||||
|
||||
enum class ReiterStatus(val label: String, val color: Color) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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
|
||||
|
|
@ -27,7 +29,7 @@ private fun PreviewContent() {
|
|||
Surface {
|
||||
|
||||
// --- REITER ---
|
||||
// ReiterScreen(viewModel = ReiterViewModel())
|
||||
ReiterScreen(viewModel = ReiterViewModel())
|
||||
|
||||
// --- PFERDE ---
|
||||
// PferdeScreen(viewModel = PferdeViewModel())
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ import androidx.compose.material.icons.filled.Wifi
|
|||
import androidx.compose.material.icons.filled.WifiOff
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -56,6 +59,10 @@ fun DesktopMainLayout(
|
|||
onBack: () -> 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()) {
|
||||
DesktopTopBar(
|
||||
currentScreen = currentScreen,
|
||||
|
|
@ -69,6 +76,10 @@ fun DesktopMainLayout(
|
|||
currentScreen = currentScreen,
|
||||
onNavigate = onNavigate,
|
||||
onBack = onBack,
|
||||
obGeraet = obGeraet,
|
||||
obKey = obKey,
|
||||
onObGeraetChange = { obGeraet = it },
|
||||
onObKeyChange = { obKey = it },
|
||||
)
|
||||
}
|
||||
DesktopFooterBar()
|
||||
|
|
@ -102,8 +113,8 @@ private fun DesktopTopBar(
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
// Zurück-Pfeil (nur wenn nicht Root)
|
||||
if (currentScreen !is AppScreen.VeranstaltungVerwaltung) {
|
||||
// Zurück-Pfeil: für alle außer Onboarding anzeigen (damit man von "Verwaltung" zurück kommt)
|
||||
if (currentScreen !is AppScreen.Onboarding) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Zurück",
|
||||
|
|
@ -138,10 +149,10 @@ private fun DesktopTopBar(
|
|||
is AppScreen.VeranstalterNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
|
|
@ -154,10 +165,10 @@ private fun DesktopTopBar(
|
|||
is AppScreen.VeranstalterDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
|
|
@ -170,10 +181,10 @@ private fun DesktopTopBar(
|
|||
is AppScreen.VeranstaltungProfil -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
text = "Veranstalter-Verwaltung",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterVerwaltung) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
|
|
@ -335,12 +346,21 @@ private fun DesktopContentArea(
|
|||
currentScreen: AppScreen,
|
||||
onNavigate: (AppScreen) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
obGeraet: String,
|
||||
obKey: String,
|
||||
onObGeraetChange: (String) -> Unit,
|
||||
onObKeyChange: (String) -> Unit,
|
||||
) {
|
||||
when (currentScreen) {
|
||||
// Onboarding ohne Login
|
||||
is AppScreen.Onboarding -> {
|
||||
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")
|
||||
onNavigate(AppScreen.VeranstaltungVerwaltung)
|
||||
}
|
||||
|
|
@ -373,11 +393,9 @@ private fun DesktopContentArea(
|
|||
onEdit = { onNavigate(AppScreen.PferdProfil(it)) }
|
||||
)
|
||||
|
||||
is AppScreen.PferdProfil -> PlaceholderScreen(
|
||||
"Pferde-Profil #${currentScreen.id}",
|
||||
is AppScreen.PferdProfil -> at.mocode.desktop.v2.PferdProfilV2(
|
||||
id = currentScreen.id,
|
||||
onBack = onBack,
|
||||
onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
||||
actionLabel = "Zurück zur Zentrale"
|
||||
)
|
||||
|
||||
// --- Reiter-Verwaltung & Profil ---
|
||||
|
|
@ -386,11 +404,9 @@ private fun DesktopContentArea(
|
|||
onEdit = { onNavigate(AppScreen.ReiterProfil(it)) }
|
||||
)
|
||||
|
||||
is AppScreen.ReiterProfil -> PlaceholderScreen(
|
||||
"Reiter-Profil #${currentScreen.id}",
|
||||
is AppScreen.ReiterProfil -> at.mocode.desktop.v2.ReiterProfilV2(
|
||||
id = currentScreen.id,
|
||||
onBack = onBack,
|
||||
onAction = { onNavigate(AppScreen.VeranstaltungVerwaltung) },
|
||||
actionLabel = "Zurück zur Zentrale"
|
||||
)
|
||||
|
||||
// --- Verein-Verwaltung & Profil ---
|
||||
|
|
@ -399,11 +415,9 @@ private fun DesktopContentArea(
|
|||
onEdit = { onNavigate(AppScreen.VereinProfil(it)) }
|
||||
)
|
||||
|
||||
is AppScreen.VereinProfil -> PlaceholderScreen(
|
||||
"Verein-Profil #${currentScreen.id}",
|
||||
is AppScreen.VereinProfil -> at.mocode.desktop.v2.VereinProfilV2(
|
||||
id = currentScreen.id,
|
||||
onBack = onBack,
|
||||
onAction = { onNavigate(AppScreen.VereinVerwaltung) },
|
||||
actionLabel = "Zurück zur Zentrale"
|
||||
)
|
||||
|
||||
// --- Funktionaer-Verwaltung & Profil ---
|
||||
|
|
@ -412,24 +426,23 @@ private fun DesktopContentArea(
|
|||
onEdit = { onNavigate(AppScreen.FunktionaerProfil(it)) }
|
||||
)
|
||||
|
||||
is AppScreen.FunktionaerProfil -> PlaceholderScreen(
|
||||
"Funktionär-Profil #${currentScreen.id}",
|
||||
is AppScreen.FunktionaerProfil -> at.mocode.desktop.v2.FunktionaerProfilV2(
|
||||
id = currentScreen.id,
|
||||
onBack = onBack,
|
||||
onAction = { onNavigate(AppScreen.FunktionaerVerwaltung) },
|
||||
actionLabel = "Zurück zur Zentrale"
|
||||
)
|
||||
|
||||
// --- Veranstalter-Verwaltung & Profil ---
|
||||
is AppScreen.VeranstalterVerwaltung -> at.mocode.desktop.v2.VeranstalterVerwaltungScreen(
|
||||
onBack = onBack,
|
||||
onNew = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||
onEdit = { onNavigate(AppScreen.VeranstalterProfil(it)) }
|
||||
)
|
||||
|
||||
is AppScreen.VeranstalterProfil -> PlaceholderScreen(
|
||||
"Veranstalter-Profil #${currentScreen.id}",
|
||||
is AppScreen.VeranstalterProfil -> at.mocode.desktop.v2.VeranstalterDetailV2(
|
||||
veranstalterId = currentScreen.id,
|
||||
onBack = onBack,
|
||||
onAction = { onNavigate(AppScreen.PferdProfil(1L)) },
|
||||
actionLabel = "Pferde-Profil öffnen"
|
||||
onZurVeranstaltung = { evtId -> onNavigate(AppScreen.VeranstaltungProfil(currentScreen.id, evtId)) },
|
||||
onNeuVeranstaltung = { onNavigate(AppScreen.VeranstaltungKonfig(currentScreen.id)) },
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -445,9 +458,9 @@ private fun DesktopContentArea(
|
|||
onNeu = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||
)
|
||||
|
||||
is AppScreen.VeranstalterNeu -> VeranstalterNeuScreen(
|
||||
onAbbrechen = onBack,
|
||||
onSpeichern = { _, _, _ -> onBack() },
|
||||
is AppScreen.VeranstalterNeu -> at.mocode.desktop.v2.VeranstalterAnlegenWizard(
|
||||
onCancel = onBack,
|
||||
onVereinCreated = { newId -> onNavigate(AppScreen.VeranstalterProfil(newId)) }
|
||||
)
|
||||
is AppScreen.VeranstalterDetail -> {
|
||||
val vId = currentScreen.veranstalterId
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -35,7 +37,7 @@ fun <T> ManagementTableScreen(
|
|||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Zurück")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||
}
|
||||
Text(title, style = MaterialTheme.typography.headlineMedium)
|
||||
}
|
||||
|
|
@ -87,7 +89,7 @@ fun <T> ManagementTableScreen(
|
|||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
|
||||
// Table Body
|
||||
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
|
||||
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
|
||||
// Wir nutzen hier die 'vereine' Liste aus dem Store.
|
||||
val vereine = StoreV2.vereine
|
||||
|
|
@ -268,7 +270,7 @@ fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
|
|||
TableColumn("Email", { it.email ?: "-" }, weight = 1f)
|
||||
),
|
||||
onBack = onBack,
|
||||
onNew = { },
|
||||
onNew = onNew,
|
||||
onEdit = { onEdit(it.id) },
|
||||
onDelete = { },
|
||||
onSearch = { filter = it }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package at.mocode.desktop.v2
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
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.VisibilityOff
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -14,30 +16,66 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.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.VisualTransformation
|
||||
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
|
||||
fun OnboardingScreen(onContinue: (String, String) -> Unit) {
|
||||
fun OnboardingScreen(
|
||||
geraetName: String,
|
||||
secureKey: String,
|
||||
onGeraetNameChange: (String) -> Unit,
|
||||
onSecureKeyChange: (String) -> Unit,
|
||||
onContinue: (String, String) -> Unit,
|
||||
) {
|
||||
DesktopThemeV2 {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
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) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val frName = remember { FocusRequester() }
|
||||
val frKey = remember { FocusRequester() }
|
||||
val frBtn = remember { FocusRequester() }
|
||||
|
||||
OutlinedTextField(
|
||||
value = geraetName,
|
||||
onValueChange = { geraetName = it },
|
||||
onValueChange = { onGeraetNameChange(it) },
|
||||
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(
|
||||
value = key,
|
||||
onValueChange = { key = it },
|
||||
value = secureKey,
|
||||
onValueChange = { onSecureKeyChange(it) },
|
||||
label = { Text("Sicherheitsschlüssel (Pflicht)") },
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showPw = !showPw }) {
|
||||
|
|
@ -45,12 +83,53 @@ fun OnboardingScreen(onContinue: (String, String) -> Unit) {
|
|||
}
|
||||
},
|
||||
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
|
||||
Button(onClick = { onContinue(geraetName, key) }, enabled = enabled) {
|
||||
Text("Weiter zum Veranstalter‑Flow")
|
||||
val enabled = geraetName.trim().length >= 3 && secureKey.trim().length >= 8
|
||||
Button(
|
||||
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))
|
||||
}
|
||||
|
|
@ -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
|
||||
fun VeranstalterAuswahlV2(
|
||||
onBack: () -> Unit,
|
||||
|
|
@ -125,62 +496,106 @@ fun VeranstalterDetailV2(
|
|||
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 } }
|
||||
if (verein != null) {
|
||||
Card {
|
||||
Column(Modifier.fillMaxWidth().padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Veranstalter‑Profil", style = MaterialTheme.typography.titleMedium)
|
||||
OutlinedTextField(
|
||||
value = verein.logoUrl ?: "",
|
||||
onValueChange = { verein.logoUrl = it.ifBlank { null } },
|
||||
label = { Text("Logo‑URL (optional)") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
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)
|
||||
)
|
||||
var editOpen by remember { mutableStateOf(false) }
|
||||
Card(Modifier.fillMaxWidth()) {
|
||||
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
// Logo/Avatar
|
||||
Box(
|
||||
modifier = Modifier.size(56.dp).background(Color(0xFF1F2937), shape = MaterialTheme.shapes.small),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text((verein.kurzname ?: verein.name).take(2).uppercase(), color = Color.White, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = verein.email ?: "",
|
||||
onValueChange = { verein.email = it.ifBlank { null } },
|
||||
label = { Text("E‑Mail (optional)") },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = verein.oepsNummer,
|
||||
onValueChange = { verein.oepsNummer = it },
|
||||
label = { Text("OEPS‑Nummer") },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(verein.name, style = MaterialTheme.typography.titleMedium)
|
||||
val line2 = listOfNotNull("OEPS: ${verein.oepsNummer}", verein.ort, verein.plz, verein.strasse).filter { it.isNotBlank() }.joinToString(" · ")
|
||||
if (line2.isNotBlank()) Text(line2, color = Color(0xFF6B7280))
|
||||
val line3 = listOfNotNull(verein.email, verein.telefon).filter { !it.isNullOrBlank() }.joinToString(" · ")
|
||||
if (line3.isNotBlank()) Text(line3, color = Color(0xFF6B7280))
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = verein.strasse ?: "",
|
||||
onValueChange = { verein.strasse = it.ifBlank { null } },
|
||||
label = { Text("Adresse / Straße (optional)") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
minLines = 2
|
||||
)
|
||||
Button(onClick = { editOpen = true }) { Text("bearbeiten") }
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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()) {
|
||||
items(events) { evt ->
|
||||
items(filtered) { evt ->
|
||||
Card(Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
|
||||
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
|
|
|
|||
|
|
@ -417,7 +417,13 @@ fun VeranstaltungKonfigV2(
|
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
IconButton(onClick = {
|
||||
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 {
|
||||
onBack()
|
||||
}
|
||||
|
|
@ -667,28 +673,60 @@ fun VeranstaltungKonfigV2(
|
|||
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(
|
||||
onClick = {
|
||||
if (currentStep < 3) {
|
||||
currentStep++
|
||||
} else {
|
||||
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)
|
||||
onSaved(id, selectedVereinId)
|
||||
showConfirm = true
|
||||
}
|
||||
},
|
||||
enabled = when (currentStep) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user