chore: remove obsolete screens from meldestelle-desktop module
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Failing after 2m56s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Failing after 3m3s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 2m49s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m13s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Failing after 2m56s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Failing after 3m3s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 2m49s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m13s
- Deleted unused screens including `AdminUebersichtScreen`, `AktorScreens`, `StammdatenImportScreen`, `TurnierDetailScreen`, and supporting components such as `PlaceholderContent`. - Cleaned up references and placeholders to streamline module structure. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -10,6 +10,7 @@ plugins {
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
id("org.jetbrains.compose.hot-reload")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -30,6 +31,9 @@ kotlin {
|
||||
implementation(projects.frontend.features.nennungFeature)
|
||||
implementation(projects.frontend.features.pingFeature)
|
||||
implementation(projects.frontend.features.znsImportFeature)
|
||||
implementation(projects.frontend.features.veranstalterFeature)
|
||||
implementation(projects.frontend.features.veranstaltungFeature)
|
||||
implementation(projects.frontend.features.turnierFeature)
|
||||
|
||||
// Compose Desktop
|
||||
implementation(compose.desktop.currentOs)
|
||||
@@ -39,6 +43,7 @@ kotlin {
|
||||
implementation(compose.ui)
|
||||
implementation(compose.materialIconsExtended)
|
||||
implementation(compose.uiTooling)
|
||||
implementation(libs.composeHotReloadApi)
|
||||
|
||||
// DI (Koin)
|
||||
implementation(libs.koin.core)
|
||||
@@ -59,6 +64,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "at.mocode.desktop.MainKt"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import at.mocode.desktop.navigation.DesktopNavigationPort
|
||||
import at.mocode.desktop.screens.DesktopMainLayout
|
||||
import at.mocode.desktop.screens.layout.DesktopMainLayout
|
||||
import at.mocode.frontend.core.auth.data.AuthTokenManager
|
||||
import at.mocode.frontend.core.auth.presentation.LoginScreen
|
||||
import at.mocode.frontend.core.auth.presentation.LoginViewModel
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package at.mocode.desktop
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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.turnier.feature.presentation.TurnierDetailScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterDetailScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterNeuScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.AdminUebersichtScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungUebersichtScreen
|
||||
|
||||
/**
|
||||
* Hot-Reload Preview Entry Point
|
||||
*
|
||||
* Starten mit:
|
||||
* ./gradlew :frontend:shells:meldestelle-desktop:hotRunJvm
|
||||
*
|
||||
* Einfach den gewünschten Screen in `PreviewContent()` eintragen,
|
||||
* Gradle-Task starten – Änderungen werden live ohne Neustart übernommen.
|
||||
* Bei singleWindowApplication ist kein DevelopmentEntryPoint-Wrapper nötig.
|
||||
*/
|
||||
fun main() = singleWindowApplication(title = "🔥 Hot-Reload Preview") {
|
||||
PreviewContent()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewContent() {
|
||||
MaterialTheme {
|
||||
Surface {
|
||||
// ── Hier den gewünschten Screen eintragen ──────────────────────
|
||||
// VeranstalterAuswahlScreen(onVeranstalterSelected = {}, onNeuerVeranstalter = {})
|
||||
// VeranstalterNeuScreen(onBack = {}, onSave = {})
|
||||
// VeranstalterDetailScreen(veranstalterId = 1L, onBack = {}, onVeranstaltungSelected = {})
|
||||
// VeranstaltungUebersichtScreen(veranstalterId = 1L, onBack = {}, onTurnierSelected = {})
|
||||
// TurnierDetailScreen(turnierId = 1L, onBack = {})
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
// Standard: AdminUebersichtScreen (Startseite nach Login)
|
||||
AdminUebersichtScreen(
|
||||
onVeranstalterAuswahl = {},
|
||||
onVeranstaltungOeffnen = {},
|
||||
onPingService = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
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.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Status-Farben gemäß Vision_03
|
||||
private val StatusVorbereitung = Color(0xFFEA580C) // Orange
|
||||
private val StatusLive = Color(0xFF16A34A) // Grün
|
||||
private val StatusAbgeschlossen = Color(0xFF6B7280) // Grau
|
||||
|
||||
/**
|
||||
* Root-Screen der Desktop-App gemäß Vision_03 (Screenshot 23/24).
|
||||
*
|
||||
* Layout:
|
||||
* - KPI-Kacheln oben (Live/Aktiv, In Vorbereitung, Gesamt, Archiv)
|
||||
* - Toolbar: "+ Neue Veranstaltung" + Suche + Status-Filter
|
||||
* - Veranstaltungs-Cards (expandiert mit Turnier-Liste)
|
||||
*
|
||||
* TODO: Echte Daten aus event-management-context laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun AdminUebersichtScreen(
|
||||
onVeranstalterAuswahl: () -> Unit,
|
||||
onVeranstaltungOeffnen: (Long) -> Unit,
|
||||
) {
|
||||
// Placeholder-Daten für die UI-Struktur
|
||||
val veranstaltungen = listOf<VeranstaltungUiModel>() // leer bis Backend angebunden
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// KPI-Kacheln
|
||||
KpiKachelRow(
|
||||
liveAktiv = 0,
|
||||
inVorbereitung = 0,
|
||||
gesamt = 0,
|
||||
archiv = 0,
|
||||
)
|
||||
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Button(
|
||||
onClick = onVeranstalterAuswahl,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E3A8A)),
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Neue Veranstaltung")
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
placeholder = { Text("Suche nach Name, Ort oder Turnier-Nr.", fontSize = 13.sp) },
|
||||
modifier = Modifier.weight(1f).height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
// Status-Filter Chips
|
||||
StatusFilterChip("Alle", selected = true)
|
||||
StatusFilterChip("Vorbereitung", selected = false)
|
||||
StatusFilterChip("Live", selected = false)
|
||||
StatusFilterChip("Abgeschlossen", selected = false)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Veranstaltungs-Liste
|
||||
if (veranstaltungen.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = "Noch keine Veranstaltungen",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Lege eine neue Veranstaltung an, um zu beginnen.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Button(
|
||||
onClick = onVeranstalterAuswahl,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E3A8A)),
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Neue Veranstaltung anlegen")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
) {
|
||||
items(veranstaltungen) { veranstaltung ->
|
||||
VeranstaltungCard(
|
||||
veranstaltung = veranstaltung,
|
||||
onOeffnen = { onVeranstaltungOeffnen(veranstaltung.id) },
|
||||
onLoeschen = { /* TODO */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KpiKachelRow(
|
||||
liveAktiv: Int,
|
||||
inVorbereitung: Int,
|
||||
gesamt: Int,
|
||||
archiv: Int,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
KpiKachel(
|
||||
label = "LIVE / AKTIV",
|
||||
wert = liveAktiv.toString(),
|
||||
akzentFarbe = StatusLive,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
KpiKachel(
|
||||
label = "IN VORBEREITUNG",
|
||||
wert = inVorbereitung.toString(),
|
||||
akzentFarbe = Color(0xFF3B82F6),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
KpiKachel(
|
||||
label = "GESAMT",
|
||||
wert = gesamt.toString(),
|
||||
akzentFarbe = Color(0xFF6B7280),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
KpiKachel(
|
||||
label = "ARCHIV",
|
||||
wert = archiv.toString(),
|
||||
akzentFarbe = Color(0xFF9CA3AF),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KpiKachel(
|
||||
label: String,
|
||||
wert: String,
|
||||
akzentFarbe: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
border = BorderStroke(2.dp, akzentFarbe),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 10.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Text(
|
||||
text = wert,
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = akzentFarbe,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusFilterChip(label: String, selected: Boolean) {
|
||||
FilterChip(
|
||||
selected = selected,
|
||||
onClick = {},
|
||||
label = { Text(label, fontSize = 12.sp) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VeranstaltungCard(
|
||||
veranstaltung: VeranstaltungUiModel,
|
||||
onOeffnen: () -> Unit,
|
||||
onLoeschen: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = if (veranstaltung.status == VeranstaltungStatus.VORBEREITUNG)
|
||||
BorderStroke(1.dp, Color(0xFF3B82F6)) else null,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
// Header
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = veranstaltung.name,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
) {
|
||||
Text("📍 ${veranstaltung.ort}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("📅 ${veranstaltung.datum}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("🏆 ${veranstaltung.turnierAnzahl} Turniere", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
StatusBadge(veranstaltung.status)
|
||||
}
|
||||
|
||||
// Turnier-Liste
|
||||
if (veranstaltung.turniere.isNotEmpty()) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Turniere (${veranstaltung.turniere.size}):", fontSize = 12.sp, fontWeight = FontWeight.Medium)
|
||||
veranstaltung.turniere.forEach { turnier ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = Color(0xFF1E3A8A),
|
||||
) {
|
||||
Text(
|
||||
text = turnier.nummer.toString(),
|
||||
color = Color.White,
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
Text("${turnier.name} (${turnier.bewerbAnzahl} Bewerbe)", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = onOeffnen, modifier = Modifier.height(28.dp)) {
|
||||
Text("Öffnen", fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Nennungen: ${veranstaltung.nennungen}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("Letzte Aktivität: ${veranstaltung.letzteAktivitaet}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
onClick = onOeffnen,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E3A8A)),
|
||||
modifier = Modifier.height(32.dp),
|
||||
) {
|
||||
Text("Öffnen", fontSize = 12.sp)
|
||||
}
|
||||
IconButton(onClick = onLoeschen, modifier = Modifier.size(32.dp)) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Löschen", tint = Color(0xFFDC2626))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusBadge(status: VeranstaltungStatus) {
|
||||
val (text, color) = when (status) {
|
||||
VeranstaltungStatus.VORBEREITUNG -> "Vorbereitung" to StatusVorbereitung
|
||||
VeranstaltungStatus.LIVE -> "Live" to StatusLive
|
||||
VeranstaltungStatus.ABGESCHLOSSEN -> "Abgeschlossen" to StatusAbgeschlossen
|
||||
}
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = color.copy(alpha = 0.15f),
|
||||
border = BorderStroke(1.dp, color),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI-Modelle (Placeholder bis echte Domain-Modelle angebunden sind) ---
|
||||
|
||||
data class VeranstaltungUiModel(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val ort: String,
|
||||
val datum: String,
|
||||
val turnierAnzahl: Int,
|
||||
val nennungen: Int,
|
||||
val letzteAktivitaet: String,
|
||||
val status: VeranstaltungStatus,
|
||||
val turniere: List<TurnierUiModel> = emptyList(),
|
||||
)
|
||||
|
||||
data class TurnierUiModel(
|
||||
val id: Long,
|
||||
val nummer: Long,
|
||||
val name: String,
|
||||
val bewerbAnzahl: Int,
|
||||
)
|
||||
|
||||
enum class VeranstaltungStatus { VORBEREITUNG, LIVE, ABGESCHLOSSEN }
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Placeholder-Screens für Akteur-Verwaltung (actor-context).
|
||||
* Werden in Phase 4/5 mit echten Daten aus dem actor-context befüllt.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun ReiterScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Text("Reiter", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PlaceholderContent(
|
||||
title = "Reiter-Verwaltung",
|
||||
subtitle = "Satznummer, Lizenzklasse, Sparten-Lizenz – actor-context (Phase 4).",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PferdeScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Text("Pferde", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PlaceholderContent(
|
||||
title = "Pferde-Verwaltung",
|
||||
subtitle = "Lebensnummer, ZNS-Daten, Passbesitzer – actor-context (Phase 4).",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FunktionaereScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Text("Funktionäre", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PlaceholderContent(
|
||||
title = "Funktionäre-Verwaltung",
|
||||
subtitle = "Richter, Parcourschef, Tierarzt – actor-context (Phase 4).",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MeisterschaftenScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Text("Meisterschaften", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PlaceholderContent(
|
||||
title = "Meisterschaften",
|
||||
subtitle = "Konfigurierbare Reglements, Punktesysteme – series-context (Phase 2+).",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CupsScreen() {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Text("Cups", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
PlaceholderContent(
|
||||
title = "Cups & Serien",
|
||||
subtitle = "Pluggable Berechnungsmodell, Paar-Bindung – series-context (Phase 2+).",
|
||||
)
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Construction
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Wiederverwendbarer Platzhalter für Screens, die noch nicht implementiert sind.
|
||||
*/
|
||||
@Composable
|
||||
fun PlaceholderContent(
|
||||
title: String,
|
||||
subtitle: String = "Wird in einer späteren Phase implementiert.",
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Construction,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = MaterialTheme.colorScheme.outlineVariant,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-257
@@ -1,257 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
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.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.zns.feature.ZnsImportViewModel
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.filechooser.FileNameExtensionFilter
|
||||
|
||||
@Composable
|
||||
fun StammdatenImportScreen(
|
||||
viewModel: ZnsImportViewModel = koinViewModel(),
|
||||
) {
|
||||
val state = viewModel.state
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
// Titel
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Default.CloudUpload, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
||||
Text("Stammdaten-Import (ZNS)", style = MaterialTheme.typography.headlineSmall)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Datei-Auswahl
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Datei auswählen", style = MaterialTheme.typography.titleMedium)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = state.selectedFilePath ?: "",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
placeholder = { Text("Keine Datei ausgewählt…") },
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
val path = pickZipFile()
|
||||
if (path != null) viewModel.onFileSelected(path)
|
||||
},
|
||||
enabled = !state.isUploading && !(!state.isFinished && state.jobId != null),
|
||||
) {
|
||||
Icon(Icons.Default.FolderOpen, contentDescription = null)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Durchsuchen")
|
||||
}
|
||||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
onClick = { viewModel.startImport() },
|
||||
enabled = state.selectedFilePath != null && !state.isUploading && !(state.jobId != null && !state.isFinished),
|
||||
) {
|
||||
if (state.isUploading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
} else {
|
||||
Icon(Icons.Default.Upload, contentDescription = null)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
Text("Import starten")
|
||||
}
|
||||
if (state.isFinished || state.errorMessage != null) {
|
||||
OutlinedButton(onClick = { viewModel.reset() }) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = null)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Zurücksetzen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fehler-Banner
|
||||
if (state.errorMessage != null) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(Icons.Default.Error, contentDescription = null, tint = MaterialTheme.colorScheme.error)
|
||||
Text(state.errorMessage!!, color = MaterialTheme.colorScheme.onErrorContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fortschritt
|
||||
if (state.jobId != null) {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("Status", style = MaterialTheme.typography.titleMedium)
|
||||
StatusChip(state.jobStatus)
|
||||
}
|
||||
|
||||
LinearProgressIndicator(
|
||||
progress = { state.progress / 100f },
|
||||
modifier = Modifier.fillMaxWidth().height(8.dp),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
state.progressDetail.ifBlank { "Warte auf Server…" },
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
"${state.progress}%",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
if (state.isFinished && state.jobStatus == "ABGESCHLOSSEN") {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
"Import erfolgreich abgeschlossen.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fehler-Liste
|
||||
if (state.errors.isNotEmpty()) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Text(
|
||||
"Import-Fehler (${state.errors.size})",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(state.errors) { error ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface, RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text("•", color = MaterialTheme.colorScheme.error)
|
||||
Text(
|
||||
error,
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusChip(status: String?) {
|
||||
val (label, color) = when (status) {
|
||||
"AUSSTEHEND" -> "Ausstehend" to MaterialTheme.colorScheme.outline
|
||||
"ENTPACKEN" -> "Entpacken…" to MaterialTheme.colorScheme.tertiary
|
||||
"LADE_VEREINE" -> "Lade Vereine…" to MaterialTheme.colorScheme.secondary
|
||||
"LADE_REITER" -> "Lade Reiter…" to MaterialTheme.colorScheme.secondary
|
||||
"LADE_PFERDE" -> "Lade Pferde…" to MaterialTheme.colorScheme.secondary
|
||||
"LADE_RICHTER" -> "Lade Richter…" to MaterialTheme.colorScheme.secondary
|
||||
"ABGESCHLOSSEN" -> "Abgeschlossen ✓" to MaterialTheme.colorScheme.primary
|
||||
"FEHLER" -> "Fehler ✗" to MaterialTheme.colorScheme.error
|
||||
else -> (status ?: "–") to MaterialTheme.colorScheme.outline
|
||||
}
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = color.copy(alpha = 0.15f),
|
||||
) {
|
||||
Text(
|
||||
label,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Öffnet einen nativen JFileChooser (JVM-only) und gibt den Pfad der gewählten ZIP zurück. */
|
||||
private fun pickZipFile(): String? {
|
||||
val chooser = JFileChooser()
|
||||
chooser.dialogTitle = "ZNS.zip auswählen"
|
||||
chooser.fileFilter = FileNameExtensionFilter("ZIP-Archiv (*.zip)", "zip")
|
||||
chooser.isAcceptAllFileFilterUsed = false
|
||||
val result = chooser.showOpenDialog(null)
|
||||
return if (result == JFileChooser.APPROVE_OPTION) chooser.selectedFile.absolutePath else null
|
||||
}
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* Detailansicht eines Turniers gemäß Vision_03.
|
||||
*
|
||||
* Layout: Horizontale Tab-Bar mit 8 Tabs (kein eigener Toolbar-Zurück-Button –
|
||||
* Navigation erfolgt über den Breadcrumb in der TopBar).
|
||||
*
|
||||
* Tabs:
|
||||
* 1. STAMMDATEN – Turnier-Konfiguration, ZNS-Import, Sparten, Datum
|
||||
* 2. ORGANISATION – Funktionäre, Richterkollegium, Austragungsplätze
|
||||
* 3. BEWERBE – 3-spaltiges Layout (Aktionen | Tabelle | Detail-Panel)
|
||||
* 4. ARTIKEL – Gebühren, Stallungen & Boxen, Zusatzgebühren
|
||||
* 5. ABRECHNUNG – Buchungen, Offene Posten, Rechnung
|
||||
* 6. NENNUNGEN – Pferd+Reiter-Suche, Verkauf/Buchungen, Bewerbsübersicht
|
||||
* 7. STARTLISTEN – Bewerbs-Tabs, Sortierung, Zeit/Dauer
|
||||
* 8. ERGEBNISLISTEN – Bewerbs-Tabs, Platzierung & Geldpreise
|
||||
*
|
||||
* TODO: Echte Inhalte pro Tab implementieren (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun TurnierDetailScreen(
|
||||
veranstaltungId: Long,
|
||||
turnierId: Long,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
|
||||
val tabs = listOf(
|
||||
"STAMMDATEN",
|
||||
"ORGANISATION",
|
||||
"BEWERBE",
|
||||
"ARTIKEL",
|
||||
"ABRECHNUNG",
|
||||
"NENNUNGEN",
|
||||
"STARTLISTEN",
|
||||
"ERGEBNISLISTEN",
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Horizontale Tab-Bar (direkt unter der TopBar)
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = selectedTab,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = Color(0xFF1E3A8A),
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
tabs.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedTab == index,
|
||||
onClick = { selectedTab = index },
|
||||
text = {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = if (selectedTab == index) FontWeight.Bold else FontWeight.Normal,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Tab-Inhalte
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
when (selectedTab) {
|
||||
0 -> StammdatenTabContent(turnierId = turnierId)
|
||||
1 -> OrganisationTabContent()
|
||||
2 -> BewerbeTabContent()
|
||||
3 -> ArtikelTabContent()
|
||||
4 -> AbrechnungTabContent()
|
||||
5 -> NennungenTabContent()
|
||||
6 -> StartlistenTabContent()
|
||||
7 -> ErgebnislistenTabContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab-Inhalte (Placeholder – werden in späteren Phasen befüllt)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@Composable
|
||||
private fun StammdatenTabContent(turnierId: Long) {
|
||||
PlaceholderContent(
|
||||
title = "Stammdaten – Turnier $turnierId",
|
||||
subtitle = "Turnier-Konfiguration, ZNS-Import, Sparten, Klassen, Datum …",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrganisationTabContent() {
|
||||
PlaceholderContent(
|
||||
title = "Organisation",
|
||||
subtitle = "Funktionäre & Offizielle (C-Satz), Richterkollegium, Austragungsplätze …",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeTabContent() {
|
||||
// Typ C: 3-spaltiges Layout (Aktionen | Tabelle | Detail-Panel)
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// Linke Aktions-Spalte
|
||||
BewerbeAktionsSpalte(modifier = Modifier.width(140.dp).fillMaxHeight())
|
||||
VerticalDivider()
|
||||
// Mittlere Tabelle
|
||||
Box(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Bewerbe",
|
||||
subtitle = "Liste aller Bewerbe dieses Turniers …",
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
// Rechtes Detail-Panel
|
||||
BewerbeDetailPanel(modifier = Modifier.width(320.dp).fillMaxHeight())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeAktionsSpalte(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
AktionsButton("Änderungen\nSpeichern")
|
||||
AktionsButton("Änderungen\nRückgängig")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsButton("Bewerb\nEinfügen")
|
||||
AktionsButton("Bewerb\nLöschen")
|
||||
AktionsButton("Bewerb Teilen")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsButton("Bewerb nach\noben verschieben")
|
||||
AktionsButton("Bewerb nach\nunten verschieben")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsButton("Startliste\nBearbeiten")
|
||||
AktionsButton("Startliste\nDrucken")
|
||||
AktionsButton("Ergebnisliste\nBearbeiten")
|
||||
AktionsButton("Ergebnisliste\nDrucken")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AktionsButton(label: String) {
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||
contentPadding = PaddingValues(horizontal = 4.dp, vertical = 2.dp),
|
||||
) {
|
||||
Text(label, fontSize = 11.sp, lineHeight = 13.sp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeDetailPanel(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(12.dp)) {
|
||||
// Sub-Tabs: Bewerb | Bewertung | Geldpreise | Ort/Zeit
|
||||
var subTab by remember { mutableIntStateOf(0) }
|
||||
val subTabs = listOf("Bewerb", "Bewertung", "Geldpreise", "Ort/Zeit")
|
||||
TabRow(
|
||||
selectedTabIndex = subTab,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = Color(0xFF1E3A8A),
|
||||
) {
|
||||
subTabs.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
selected = subTab == i,
|
||||
onClick = { subTab = i },
|
||||
text = { Text(title, fontSize = 12.sp) },
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
PlaceholderContent(
|
||||
title = subTabs[subTab],
|
||||
subtitle = "Bewerb-Details …",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArtikelTabContent() {
|
||||
PlaceholderContent(
|
||||
title = "Artikel – Nennungen & Gebühren",
|
||||
subtitle = "Nenngebühr, Startgebühr, Sporteuro, Stallungen & Boxen, Zusatzgebühren …",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AbrechnungTabContent() {
|
||||
PlaceholderContent(
|
||||
title = "Abrechnung",
|
||||
subtitle = "Buchungen, Offene Posten, Rechnung …",
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NennungenTabContent() {
|
||||
// Typ B: 2-spaltig (Pferd+Reiter-Suche | Verkauf/Buchungen)
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Nennungen",
|
||||
subtitle = "Pferd- und Reiter-Suche, Nennungs-Tabelle …",
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
Box(modifier = Modifier.width(340.dp).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Verkauf / Buchungen",
|
||||
subtitle = "Artikel-Buchungen, Bewerbsübersicht …",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StartlistenTabContent() {
|
||||
// Typ B: Tabelle + rechtes Sortier/Zeit-Panel
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Startlisten",
|
||||
subtitle = "Bewerbs-Tabs, Starter-Liste …",
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
Box(modifier = Modifier.width(280.dp).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Sortierung & Zeit",
|
||||
subtitle = "Aufsteigend/Absteigend, Auslosung, Beginnzeit, Reitdauer …",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ErgebnislistenTabContent() {
|
||||
// Typ B: Tabelle + rechtes Platzierungs-Panel
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Ergebnislisten",
|
||||
subtitle = "Bewerbs-Tabs, Ergebnis-Eingabe (Fehler, Zeit) …",
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
Box(modifier = Modifier.width(280.dp).fillMaxHeight()) {
|
||||
PlaceholderContent(
|
||||
title = "Platzierung & Geldpreis",
|
||||
subtitle = "Anzahl Platzierte, Geldpreis, Import/Export/Drucken …",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Formular zum Anlegen eines neuen Turniers (Vision_03: /veranstaltung/{id}/turnier/neu).
|
||||
* Tabs: Übersicht | Stammdaten (A-Satz) | Organisation | Bewerbe ⭐ | Preisliste
|
||||
* TODO: Echte Formular-Felder und Persistenz (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun TurnierNeuScreen(
|
||||
veranstaltungId: Long,
|
||||
onBack: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
var selectedTab by remember { mutableIntStateOf(3) } // Bewerbe ist Standard-Tab (⭐)
|
||||
val tabs = listOf("Übersicht", "Stammdaten (A-Satz)", "Organisation", "Bewerbe ⭐", "Preisliste")
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Neues Turnier (Veranstaltung #$veranstaltungId)",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
)
|
||||
}
|
||||
Button(onClick = onSave) { Text("Speichern") }
|
||||
}
|
||||
|
||||
PrimaryTabRow(selectedTabIndex = selectedTab) {
|
||||
tabs.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedTab == index,
|
||||
onClick = { selectedTab = index },
|
||||
text = { Text(title) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
when (selectedTab) {
|
||||
0 -> PlaceholderContent("Übersicht", "Wird nach dem Speichern befüllt.")
|
||||
1 -> PlaceholderContent("Stammdaten (A-Satz)", "OEPS-Turniernummer, Kategorie, Sparte …")
|
||||
2 -> PlaceholderContent("Organisation", "Richter, Parcourschef, Tierarzt …")
|
||||
3 -> PlaceholderContent("Bewerbe", "Bewerbe anlegen und Abteilungen konfigurieren …")
|
||||
4 -> PlaceholderContent("Preisliste", "Nenngebühren pro Bewerb/Sparte …")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val AccentBlue = Color(0xFF3B82F6)
|
||||
|
||||
/**
|
||||
* Screen: "Admin - Verwaltung / Veranstalter auswählen"
|
||||
*
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_22 / figma-entwurf_20):
|
||||
* - Tabelle aller registrierten Veranstalter/Kunden
|
||||
* - Klick auf Zeile → Veranstalter markiert (selektiert)
|
||||
* - "Weiter zum Veranstalter"-Button wird aktiv sobald ein Veranstalter ausgewählt ist
|
||||
*
|
||||
* TODO: Echte Daten aus customer-context laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstalterAuswahlScreen(
|
||||
onZurueck: () -> Unit,
|
||||
onWeiter: (Long) -> Unit,
|
||||
) {
|
||||
var selectedId by remember { mutableStateOf<Long?>(null) }
|
||||
var suchtext by remember { mutableStateOf("") }
|
||||
|
||||
// Placeholder-Daten
|
||||
val veranstalter = remember {
|
||||
listOf(
|
||||
VeranstalterUiModel(1L, "Reit- und Fahrverein Wels", "Wels", "OÖ", 12),
|
||||
VeranstalterUiModel(2L, "Pferdesportverein Linz", "Linz", "OÖ", 8),
|
||||
VeranstalterUiModel(3L, "Reiterverein Salzburg", "Salzburg", "S", 5),
|
||||
VeranstalterUiModel(4L, "Reitclub Wien Nord", "Wien", "W", 3),
|
||||
VeranstalterUiModel(5L, "Fahrverein Graz", "Graz", "ST", 7),
|
||||
)
|
||||
}
|
||||
|
||||
val gefiltert = veranstalter.filter {
|
||||
suchtext.isBlank() || it.name.contains(suchtext, ignoreCase = true) ||
|
||||
it.ort.contains(suchtext, ignoreCase = true)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Seiten-Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
text = "Wähle einen registrierten Veranstalter aus, um eine neue Veranstaltung anzulegen.",
|
||||
fontSize = 13.sp,
|
||||
color = Color(0xFF6B7280),
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = onZurueck) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
Button(
|
||||
onClick = { selectedId?.let { onWeiter(it) } },
|
||||
enabled = selectedId != null,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) {
|
||||
Text("Weiter zum Veranstalter")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Suchfeld
|
||||
OutlinedTextField(
|
||||
value = suchtext,
|
||||
onValueChange = { suchtext = it },
|
||||
placeholder = { Text("Suche nach Name oder Ort...", fontSize = 13.sp) },
|
||||
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
// Tabellen-Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color(0xFFF3F4F6))
|
||||
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Name", fontWeight = FontWeight.SemiBold, fontSize = 12.sp, modifier = Modifier.weight(3f))
|
||||
Text("Ort", fontWeight = FontWeight.SemiBold, fontSize = 12.sp, modifier = Modifier.weight(1.5f))
|
||||
Text("Bundesland", fontWeight = FontWeight.SemiBold, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
Text("Veranstaltungen", fontWeight = FontWeight.SemiBold, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Tabellen-Inhalt
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(gefiltert) { v ->
|
||||
val isSelected = v.id == selectedId
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
if (isSelected) AccentBlue.copy(alpha = 0.1f)
|
||||
else Color.Transparent
|
||||
)
|
||||
.clickable { selectedId = v.id }
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Auswahl-Indikator
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = { selectedId = v.id },
|
||||
colors = RadioButtonDefaults.colors(selectedColor = AccentBlue),
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = v.name,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
modifier = Modifier.weight(3f),
|
||||
)
|
||||
Text(v.ort, fontSize = 13.sp, modifier = Modifier.weight(1.5f))
|
||||
Text(v.bundesland, fontSize = 13.sp, modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = "${v.veranstaltungsAnzahl}",
|
||||
fontSize = 13.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI-Modell ---
|
||||
|
||||
data class VeranstalterUiModel(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val ort: String,
|
||||
val bundesland: String,
|
||||
val veranstaltungsAnzahl: Int,
|
||||
)
|
||||
-213
@@ -1,213 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
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.filled.Add
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val StatusVorbereitungColor = Color(0xFFEA580C)
|
||||
private val StatusLiveColor = Color(0xFF16A34A)
|
||||
private val StatusAbgeschlossenColor = Color(0xFF6B7280)
|
||||
|
||||
/**
|
||||
* Screen: "Admin - Verwaltung / Veranstalter auswählen / <Vereinsname>"
|
||||
*
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_19):
|
||||
* - Veranstalter-Profil (Name, Ort, Kontakt)
|
||||
* - Liste aller Veranstaltungen dieses Veranstalters
|
||||
* - Klick auf Veranstaltung → VeranstaltungUebersicht
|
||||
*
|
||||
* TODO: Echte Daten aus customer-context / event-management-context laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstalterDetailScreen(
|
||||
veranstalterId: Long,
|
||||
onZurueck: () -> Unit,
|
||||
onVeranstaltungOeffnen: (Long) -> Unit,
|
||||
onVeranstaltungNeu: () -> Unit,
|
||||
) {
|
||||
// Placeholder-Daten
|
||||
val veranstalter = remember(veranstalterId) {
|
||||
VeranstalterUiModel(
|
||||
id = veranstalterId,
|
||||
name = "Reit- und Fahrverein Wels",
|
||||
ort = "Wels",
|
||||
bundesland = "OÖ",
|
||||
veranstaltungsAnzahl = 12,
|
||||
)
|
||||
}
|
||||
|
||||
val veranstaltungen = remember(veranstalterId) {
|
||||
listOf(
|
||||
VeranstaltungUiModel(
|
||||
id = 1L, name = "Frühjahrsturnier Wels 2026", ort = "Wels", datum = "15.04.2026",
|
||||
turnierAnzahl = 3, nennungen = 47, letzteAktivitaet = "heute",
|
||||
status = VeranstaltungStatus.VORBEREITUNG,
|
||||
),
|
||||
VeranstaltungUiModel(
|
||||
id = 2L, name = "Sommerturnier Wels 2025", ort = "Wels", datum = "20.07.2025",
|
||||
turnierAnzahl = 5, nennungen = 112, letzteAktivitaet = "vor 8 Monaten",
|
||||
status = VeranstaltungStatus.ABGESCHLOSSEN,
|
||||
),
|
||||
VeranstaltungUiModel(
|
||||
id = 3L, name = "Herbstturnier Wels 2025", ort = "Wels", datum = "12.10.2025",
|
||||
turnierAnzahl = 4, nennungen = 89, letzteAktivitaet = "vor 5 Monaten",
|
||||
status = VeranstaltungStatus.ABGESCHLOSSEN,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Veranstalter-Profil-Header
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFF8FAFC),
|
||||
border = BorderStroke(1.dp, Color(0xFFE2E8F0)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = veranstalter.name,
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("📍 ${veranstalter.ort}, ${veranstalter.bundesland}", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Text("🏆 ${veranstalter.veranstaltungsAnzahl} Veranstaltungen gesamt", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = onZurueck) {
|
||||
Text("← Zurück zur Auswahl")
|
||||
}
|
||||
Button(
|
||||
onClick = onVeranstaltungNeu,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Neue Veranstaltung")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Veranstaltungs-Liste
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Veranstaltungen (${veranstaltungen.size})",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
) {
|
||||
items(veranstaltungen) { veranstaltung ->
|
||||
VeranstaltungListCard(
|
||||
veranstaltung = veranstaltung,
|
||||
onOeffnen = { onVeranstaltungOeffnen(veranstaltung.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VeranstaltungListCard(
|
||||
veranstaltung: VeranstaltungUiModel,
|
||||
onOeffnen: () -> Unit,
|
||||
) {
|
||||
val statusColor = when (veranstaltung.status) {
|
||||
VeranstaltungStatus.VORBEREITUNG -> StatusVorbereitungColor
|
||||
VeranstaltungStatus.LIVE -> StatusLiveColor
|
||||
VeranstaltungStatus.ABGESCHLOSSEN -> StatusAbgeschlossenColor
|
||||
}
|
||||
val statusText = when (veranstaltung.status) {
|
||||
VeranstaltungStatus.VORBEREITUNG -> "Vorbereitung"
|
||||
VeranstaltungStatus.LIVE -> "Live"
|
||||
VeranstaltungStatus.ABGESCHLOSSEN -> "Abgeschlossen"
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = if (veranstaltung.status == VeranstaltungStatus.VORBEREITUNG)
|
||||
BorderStroke(1.dp, Color(0xFF3B82F6)) else null,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = veranstaltung.name,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = statusColor.copy(alpha = 0.15f),
|
||||
border = BorderStroke(1.dp, statusColor),
|
||||
) {
|
||||
Text(
|
||||
text = statusText,
|
||||
color = statusColor,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("📍 ${veranstaltung.ort}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("📅 ${veranstaltung.datum}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("🏆 ${veranstaltung.turnierAnzahl} Turniere", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("📋 ${veranstaltung.nennungen} Nennungen", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = onOeffnen,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) {
|
||||
Text("Veranstaltung öffnen →")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Detailansicht einer bestehenden Veranstaltung (Vision_03: /veranstaltung/{id}).
|
||||
* Zeigt Übersicht-Tab mit Turniere-Section.
|
||||
* TODO: Echte Daten laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstaltungDetailScreen(
|
||||
veranstaltungId: Long,
|
||||
onBack: () -> Unit,
|
||||
onTurnierNeu: () -> Unit,
|
||||
onTurnierOeffnen: (Long) -> Unit,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Veranstaltung #$veranstaltungId",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryTabRow(selectedTabIndex = 0) {
|
||||
Tab(selected = true, onClick = {}, text = { Text("Veranstaltung – Übersicht") })
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
// Turniere-Section
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("Turniere", style = MaterialTheme.typography.titleMedium)
|
||||
OutlinedButton(onClick = onTurnierNeu) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Neues Turnier")
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
PlaceholderContent(
|
||||
title = "Noch keine Turniere",
|
||||
subtitle = "Lege ein neues Turnier für diese Veranstaltung an.",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Formular zum Anlegen einer neuen Veranstaltung (Vision_03: /veranstaltung/neu).
|
||||
* Tabs: Veranstaltung-Übersicht | Stammdaten (A-Satz) | Organisation | Preisliste
|
||||
* TODO: Echte Formular-Felder und Persistenz (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstaltungNeuScreen(
|
||||
onBack: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
var selectedTab by remember { mutableIntStateOf(1) } // Stammdaten ist Standard-Tab
|
||||
val tabs = listOf("Übersicht", "Stammdaten (A-Satz)", "Organisation", "Preisliste")
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Neue Veranstaltung",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
)
|
||||
}
|
||||
Button(onClick = onSave) { Text("Speichern") }
|
||||
}
|
||||
|
||||
PrimaryTabRow(selectedTabIndex = selectedTab) {
|
||||
tabs.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedTab == index,
|
||||
onClick = { selectedTab = index },
|
||||
text = { Text(title) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
when (selectedTab) {
|
||||
0 -> PlaceholderContent("Veranstaltung – Übersicht", "Wird nach dem Speichern befüllt.")
|
||||
1 -> PlaceholderContent("Stammdaten (A-Satz)", "Felder: Bezeichnung, Datum, Ort, Veranstalter …")
|
||||
2 -> PlaceholderContent("Organisation", "Felder: Richter, Parcourschef, Tierarzt …")
|
||||
3 -> PlaceholderContent("Preisliste", "Nenngebühren pro Bewerb/Sparte …")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-349
@@ -1,349 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
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.filled.Add
|
||||
import androidx.compose.material.icons.filled.FileDownload
|
||||
import androidx.compose.material.icons.filled.FileUpload
|
||||
import androidx.compose.material.icons.filled.FolderOpen
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val ZnsGreen = Color(0xFF16A34A)
|
||||
private val ImportOrange = Color(0xFFEA580C)
|
||||
private val ExportBlue = Color(0xFF2563EB)
|
||||
|
||||
/**
|
||||
* Screen: "Veranstaltung - Übersicht"
|
||||
*
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_17):
|
||||
* - Veranstaltungs-Header (Name, Datum, Ort, Status)
|
||||
* - Liste aller Turniere dieser Veranstaltung als Cards
|
||||
* - Jede Turnier-Card hat Buttons:
|
||||
* - "Öffnen" → Meldestelle des Turniers öffnen (TurnierDetail)
|
||||
* - "Import" → Datenbank-Sicherung importieren
|
||||
* - "Export" → Datenbank-Sicherung exportieren
|
||||
* - "ZNS" → ZNS-Import für dieses Turnier starten
|
||||
*
|
||||
* Warum ZNS hier? Jedes Turnier hat seine eigene Datenbank/Kassa.
|
||||
* Der ZNS-Import muss daher turnierspezifisch sein.
|
||||
*
|
||||
* TODO: Echte Daten aus event-management-context laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstaltungUebersichtScreen(
|
||||
veranstalterId: Long,
|
||||
veranstaltungId: Long,
|
||||
onZurueck: () -> Unit,
|
||||
onTurnierOeffnen: (turnierId: Long) -> Unit,
|
||||
onTurnierNeu: () -> Unit,
|
||||
onZnsImport: (turnierId: Long) -> Unit,
|
||||
onDbImport: (turnierId: Long) -> Unit,
|
||||
onDbExport: (turnierId: Long) -> Unit,
|
||||
) {
|
||||
// Placeholder-Daten
|
||||
val veranstaltung = remember(veranstaltungId) {
|
||||
VeranstaltungUiModel(
|
||||
id = veranstaltungId,
|
||||
name = "Frühjahrsturnier Wels 2026",
|
||||
ort = "Wels",
|
||||
datum = "15.04.2026",
|
||||
turnierAnzahl = 3,
|
||||
nennungen = 47,
|
||||
letzteAktivitaet = "heute",
|
||||
status = VeranstaltungStatus.VORBEREITUNG,
|
||||
)
|
||||
}
|
||||
|
||||
val turniere = remember(veranstaltungId) {
|
||||
listOf(
|
||||
TurnierKarteUiModel(
|
||||
id = 1L,
|
||||
nummer = 1L,
|
||||
name = "Dressurturnier",
|
||||
sparte = "Dressur",
|
||||
bewerbAnzahl = 8,
|
||||
nennungen = 24,
|
||||
status = TurnierKarteStatus.VORBEREITUNG,
|
||||
datum = "15.04.2026",
|
||||
),
|
||||
TurnierKarteUiModel(
|
||||
id = 2L,
|
||||
nummer = 2L,
|
||||
name = "Springturnier",
|
||||
sparte = "Springen",
|
||||
bewerbAnzahl = 6,
|
||||
nennungen = 18,
|
||||
status = TurnierKarteStatus.VORBEREITUNG,
|
||||
datum = "15.04.2026",
|
||||
),
|
||||
TurnierKarteUiModel(
|
||||
id = 3L,
|
||||
nummer = 3L,
|
||||
name = "Vielseitigkeitsturnier",
|
||||
sparte = "Vielseitigkeit",
|
||||
bewerbAnzahl = 4,
|
||||
nennungen = 5,
|
||||
status = TurnierKarteStatus.VORBEREITUNG,
|
||||
datum = "16.04.2026",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Veranstaltungs-Header
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFF8FAFC),
|
||||
border = BorderStroke(1.dp, Color(0xFFE2E8F0)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = veranstaltung.name,
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("📍 ${veranstaltung.ort}", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Text("📅 ${veranstaltung.datum}", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Text("🏆 ${veranstaltung.turnierAnzahl} Turniere", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Text("📋 ${veranstaltung.nennungen} Nennungen gesamt", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = onZurueck) {
|
||||
Text("← Zurück")
|
||||
}
|
||||
Button(
|
||||
onClick = onTurnierNeu,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Neues Turnier")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Turnier-Liste
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Turniere (${turniere.size})",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Jedes Turnier hat eine eigene Datenbank und Kassa.",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF6B7280),
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
) {
|
||||
items(turniere) { turnier ->
|
||||
TurnierKarte(
|
||||
turnier = turnier,
|
||||
onOeffnen = { onTurnierOeffnen(turnier.id) },
|
||||
onZns = { onZnsImport(turnier.id) },
|
||||
onImport = { onDbImport(turnier.id) },
|
||||
onExport = { onDbExport(turnier.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TurnierKarte(
|
||||
turnier: TurnierKarteUiModel,
|
||||
onOeffnen: () -> Unit,
|
||||
onZns: () -> Unit,
|
||||
onImport: () -> Unit,
|
||||
onExport: () -> Unit,
|
||||
) {
|
||||
val statusColor = when (turnier.status) {
|
||||
TurnierKarteStatus.VORBEREITUNG -> Color(0xFFEA580C)
|
||||
TurnierKarteStatus.LIVE -> Color(0xFF16A34A)
|
||||
TurnierKarteStatus.ABGESCHLOSSEN -> Color(0xFF6B7280)
|
||||
}
|
||||
val statusText = when (turnier.status) {
|
||||
TurnierKarteStatus.VORBEREITUNG -> "Vorbereitung"
|
||||
TurnierKarteStatus.LIVE -> "Live"
|
||||
TurnierKarteStatus.ABGESCHLOSSEN -> "Abgeschlossen"
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
// Turnier-Header
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
// Turnier-Nummer Badge
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = PrimaryBlue,
|
||||
) {
|
||||
Text(
|
||||
text = "T${turnier.nummer}",
|
||||
color = Color.White,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
)
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
text = turnier.name,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("🏇 ${turnier.sparte}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("📅 ${turnier.datum}", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("${turnier.bewerbAnzahl} Bewerbe", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
Text("${turnier.nennungen} Nennungen", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Status-Badge
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = statusColor.copy(alpha = 0.15f),
|
||||
border = BorderStroke(1.dp, statusColor),
|
||||
) {
|
||||
Text(
|
||||
text = statusText,
|
||||
color = statusColor,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
// Aktions-Buttons
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Öffnen – Hauptaktion
|
||||
Button(
|
||||
onClick = onOeffnen,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
modifier = Modifier.height(36.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.FolderOpen,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Öffnen", fontSize = 13.sp)
|
||||
}
|
||||
|
||||
// ZNS-Import – turnierspezifisch (eigene DB!)
|
||||
OutlinedButton(
|
||||
onClick = onZns,
|
||||
border = BorderStroke(1.dp, ZnsGreen),
|
||||
modifier = Modifier.height(36.dp),
|
||||
) {
|
||||
Text("ZNS", fontSize = 13.sp, color = ZnsGreen, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
// Import / Export – DB-Sicherung
|
||||
OutlinedButton(
|
||||
onClick = onImport,
|
||||
border = BorderStroke(1.dp, ImportOrange),
|
||||
modifier = Modifier.height(36.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.FileUpload,
|
||||
contentDescription = null,
|
||||
tint = ImportOrange,
|
||||
modifier = Modifier.size(15.dp),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Import", fontSize = 13.sp, color = ImportOrange)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = onExport,
|
||||
border = BorderStroke(1.dp, ExportBlue),
|
||||
modifier = Modifier.height(36.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.FileDownload,
|
||||
contentDescription = null,
|
||||
tint = ExportBlue,
|
||||
modifier = Modifier.size(15.dp),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Export", fontSize = 13.sp, color = ExportBlue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI-Modelle ---
|
||||
|
||||
data class TurnierKarteUiModel(
|
||||
val id: Long,
|
||||
val nummer: Long,
|
||||
val name: String,
|
||||
val sparte: String,
|
||||
val bewerbAnzahl: Int,
|
||||
val nennungen: Int,
|
||||
val status: TurnierKarteStatus,
|
||||
val datum: String,
|
||||
)
|
||||
|
||||
enum class TurnierKarteStatus { VORBEREITUNG, LIVE, ABGESCHLOSSEN }
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package at.mocode.desktop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Veranstaltungs-Übersicht (Drawer-Einstieg gemäß Vision_03).
|
||||
* Zeigt Liste aller Veranstaltungen + Button "Neue Veranstaltung".
|
||||
* TODO: Echte Daten aus dem event-management-context laden (Phase 4/5).
|
||||
*/
|
||||
@Composable
|
||||
fun VeranstaltungenScreen(
|
||||
onVeranstaltungNeu: () -> Unit,
|
||||
onVeranstaltungOeffnen: (Long) -> Unit,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Veranstaltungen",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
Button(onClick = onVeranstaltungNeu) {
|
||||
Icon(Icons.Default.Add, contentDescription = null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Neue Veranstaltung")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Platzhalter – wird durch echte Daten ersetzt
|
||||
PlaceholderContent(
|
||||
title = "Noch keine Veranstaltungen",
|
||||
subtitle = "Lege eine neue Veranstaltung an, um zu beginnen.",
|
||||
)
|
||||
}
|
||||
}
|
||||
+58
-3
@@ -1,4 +1,4 @@
|
||||
package at.mocode.desktop.screens
|
||||
package at.mocode.desktop.screens.layout
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -15,6 +15,19 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import at.mocode.frontend.core.navigation.AppScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.AdminUebersichtScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterNeuScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterDetailScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungUebersichtScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungDetailScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungNeuScreen
|
||||
import at.mocode.turnier.feature.presentation.TurnierDetailScreen
|
||||
import at.mocode.turnier.feature.presentation.TurnierNeuScreen
|
||||
import at.mocode.zns.feature.presentation.StammdatenImportScreen
|
||||
import at.mocode.ping.feature.presentation.PingScreen
|
||||
import org.koin.compose.koinInject
|
||||
import at.mocode.ping.feature.presentation.PingViewModel
|
||||
|
||||
// Primärfarbe der TopBar (kann später ins Theme ausgelagert werden)
|
||||
private val TopBarColor = Color(0xFF1E3A8A)
|
||||
@@ -55,9 +68,9 @@ fun DesktopMainLayout(
|
||||
* TopBar: dunkelblauer Balken mit Breadcrumb-Navigation und Logout-Button.
|
||||
*
|
||||
* Breadcrumb-Logik:
|
||||
* - Root: "🏠 Admin - Verwaltung"
|
||||
* - Root: "🏠 Admin - Verwaltung"
|
||||
* - Veranstaltung: "🏠 Admin - Verwaltung / Veranstaltung #<id>"
|
||||
* - Turnier: "🏠 Admin - Verwaltung / Veranstaltung #<id> / Turnier <tid>"
|
||||
* - Turnier: "🏠 Admin - Verwaltung / Veranstaltung #<id> / Turnier <tid>"
|
||||
*/
|
||||
@Composable
|
||||
private fun DesktopTopBar(
|
||||
@@ -107,6 +120,23 @@ private fun DesktopTopBar(
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.VeranstalterNeu -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Veranstalter auswählen",
|
||||
color = TopBarTextColor.copy(alpha = 0.75f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.clickable { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
)
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Neuer Veranstalter",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
is AppScreen.VeranstalterDetail -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
@@ -199,6 +229,15 @@ private fun DesktopTopBar(
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
|
||||
is AppScreen.Ping -> {
|
||||
BreadcrumbSeparator()
|
||||
Text(
|
||||
text = "Ping Service",
|
||||
color = TopBarTextColor,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -236,12 +275,19 @@ private fun DesktopContentArea(
|
||||
is AppScreen.Veranstaltungen -> AdminUebersichtScreen(
|
||||
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
onVeranstaltungOeffnen = { id -> onNavigate(AppScreen.VeranstaltungDetail(id)) },
|
||||
onPingService = { onNavigate(AppScreen.Ping) },
|
||||
)
|
||||
|
||||
// Neuer Flow: Veranstalter auswählen → Detail → Veranstaltung-Übersicht
|
||||
is AppScreen.VeranstalterAuswahl -> VeranstalterAuswahlScreen(
|
||||
onZurueck = { onNavigate(AppScreen.Veranstaltungen) },
|
||||
onWeiter = { veranstalterId -> onNavigate(AppScreen.VeranstalterDetail(veranstalterId)) },
|
||||
onNeuerVeranstalter = { onNavigate(AppScreen.VeranstalterNeu) },
|
||||
)
|
||||
|
||||
is AppScreen.VeranstalterNeu -> VeranstalterNeuScreen(
|
||||
onAbbrechen = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
onSpeichern = { _, _, _ -> onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
)
|
||||
is AppScreen.VeranstalterDetail -> VeranstalterDetailScreen(
|
||||
veranstalterId = currentScreen.veranstalterId,
|
||||
@@ -288,6 +334,15 @@ private fun DesktopContentArea(
|
||||
onSave = { onNavigate(AppScreen.VeranstaltungDetail(currentScreen.veranstaltungId)) },
|
||||
)
|
||||
|
||||
// Ping-Screen
|
||||
is AppScreen.Ping -> {
|
||||
val pingViewModel: PingViewModel = koinInject()
|
||||
PingScreen(
|
||||
viewModel = pingViewModel,
|
||||
onBack = { onNavigate(AppScreen.Veranstaltungen) },
|
||||
)
|
||||
}
|
||||
|
||||
// Fallback → Root
|
||||
else -> AdminUebersichtScreen(
|
||||
onVeranstalterAuswahl = { onNavigate(AppScreen.VeranstalterAuswahl) },
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package at.mocode.desktop.screens.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterNeuScreen
|
||||
import at.mocode.veranstalter.feature.presentation.VeranstalterDetailScreen
|
||||
import at.mocode.veranstaltung.feature.presentation.VeranstaltungUebersichtScreen
|
||||
import at.mocode.turnier.feature.presentation.TurnierDetailScreen
|
||||
import at.mocode.turnier.feature.presentation.StammdatenTabContent
|
||||
import at.mocode.turnier.feature.presentation.OrganisationTabContent
|
||||
import at.mocode.turnier.feature.presentation.BewerbeTabContent
|
||||
import at.mocode.turnier.feature.presentation.ArtikelTabContent
|
||||
import at.mocode.turnier.feature.presentation.AbrechnungTabContent
|
||||
import at.mocode.turnier.feature.presentation.NennungenTabContent
|
||||
import at.mocode.turnier.feature.presentation.StartlistenTabContent
|
||||
import at.mocode.turnier.feature.presentation.ErgebnislistenTabContent
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Compose Desktop Previews – alle wichtigen Screens auf einen Blick
|
||||
//
|
||||
// Verwendung: In IntelliJ IDEA / Android Studio die @Preview-Funktion öffnen
|
||||
// und auf das Preview-Icon in der Gutter klicken.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Veranstalter-Auswahl ─────────────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewVeranstalterAuswahlScreen() {
|
||||
MaterialTheme {
|
||||
VeranstalterAuswahlScreen(
|
||||
onZurueck = {},
|
||||
onWeiter = {},
|
||||
onNeuerVeranstalter = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Neuer Veranstalter ───────────────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewVeranstalterNeuScreen() {
|
||||
MaterialTheme {
|
||||
VeranstalterNeuScreen(
|
||||
onAbbrechen = {},
|
||||
onSpeichern = { _, _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Veranstalter-Detail ──────────────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewVeranstalterDetailScreen() {
|
||||
MaterialTheme {
|
||||
VeranstalterDetailScreen(
|
||||
veranstalterId = 1L,
|
||||
onZurueck = {},
|
||||
onVeranstaltungOeffnen = {},
|
||||
onVeranstaltungNeu = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Veranstaltung-Übersicht ──────────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewVeranstaltungUebersichtScreen() {
|
||||
MaterialTheme {
|
||||
VeranstaltungUebersichtScreen(
|
||||
veranstalterId = 1L,
|
||||
veranstaltungId = 1L,
|
||||
onZurueck = {},
|
||||
onTurnierOeffnen = {},
|
||||
onTurnierNeu = {},
|
||||
onZnsImport = {},
|
||||
onDbImport = {},
|
||||
onDbExport = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Turnier-Detail (alle Tabs) ───────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierDetailScreen() {
|
||||
MaterialTheme {
|
||||
TurnierDetailScreen(
|
||||
veranstaltungId = 1L,
|
||||
turnierId = 1L,
|
||||
onBack = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Turnier-Tabs einzeln ─────────────────────────────────────────────────────
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierStammdatenTab() {
|
||||
MaterialTheme {
|
||||
StammdatenTabContent(turnierId = 1L)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierOrganisationTab() {
|
||||
MaterialTheme {
|
||||
OrganisationTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierBewerbeTab() {
|
||||
MaterialTheme {
|
||||
BewerbeTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierArtikelTab() {
|
||||
MaterialTheme {
|
||||
ArtikelTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierAbrechnungTab() {
|
||||
MaterialTheme {
|
||||
AbrechnungTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierNennungenTab() {
|
||||
MaterialTheme {
|
||||
NennungenTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierStartlistenTab() {
|
||||
MaterialTheme {
|
||||
StartlistenTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTurnierErgebnislistenTab() {
|
||||
MaterialTheme {
|
||||
ErgebnislistenTabContent()
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stammdaten-Import ────────────────────────────────────────────────────────
|
||||
|
||||
// StammdatenImportScreen benötigt einen ZnsImportViewModel (Koin) – Preview nur als Hinweis.
|
||||
// @Preview
|
||||
// @Composable
|
||||
// fun PreviewStammdatenImportScreen() {
|
||||
// MaterialTheme { StammdatenImportScreen() }
|
||||
// }
|
||||
Reference in New Issue
Block a user