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:
+73
@@ -0,0 +1,73 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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
|
||||
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
|
||||
|
||||
/**
|
||||
* 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+).",
|
||||
)
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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.filled.Print
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
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
|
||||
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val AccentBlue = Color(0xFF3B82F6)
|
||||
private val OffenePostenRot = Color(0xFFDC2626)
|
||||
|
||||
/**
|
||||
* ABRECHNUNG-Tab im TurnierDetailScreen.
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_06):
|
||||
* - Sub-Tabs: BUCHUNGEN | OFFENE POSTEN | RECHNUNG
|
||||
* - Rechte Sidebar: AUSWAHL | VERKAUF | BUCHUNGEN | ADRESSEN
|
||||
* - Buchungstabelle: Buchungstext, Soll, Haben, Saldo, Buchen-Checkbox, Rechnung-Checkbox
|
||||
* - Rechte Sidebar: Suche nach Reiter/Pferd, Zahlungsart, Buchen-Button
|
||||
*/
|
||||
@Composable
|
||||
fun AbrechnungTabContent() {
|
||||
var subTab by remember { mutableIntStateOf(0) }
|
||||
var sidebarTab by remember { mutableIntStateOf(2) } // BUCHUNGEN default
|
||||
val subTabs = listOf("BUCHUNGEN", "OFFENE POSTEN", "RECHNUNG")
|
||||
val sidebarTabs = listOf("AUSWAHL", "VERKAUF", "BUCHUNGEN", "ADRESSEN")
|
||||
|
||||
// Placeholder-Buchungen
|
||||
val buchungen = remember {
|
||||
listOf(
|
||||
BuchungspositionUiModel("Startgebühr Bewerb 12 - Dressur Kl. A", 25.00, 0.00),
|
||||
BuchungspositionUiModel("Startgebühr Bewerb 15 - Springen Kl. B", 30.00, 0.00),
|
||||
BuchungspositionUiModel("Nenngeld", 15.00, 0.00),
|
||||
BuchungspositionUiModel("Box 3 Tage", 45.00, 0.00),
|
||||
)
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Hauptbereich ─────────────────────────────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
// Sub-Tabs
|
||||
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,
|
||||
fontWeight = if (subTab == i) FontWeight.Bold else FontWeight.Normal
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (subTab) {
|
||||
0 -> BuchungenContent(buchungen)
|
||||
1 -> OffenePostenContent()
|
||||
2 -> RechnungContent()
|
||||
}
|
||||
}
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechte Sidebar ───────────────────────────────────────────────────
|
||||
Column(modifier = Modifier.width(320.dp).fillMaxHeight()) {
|
||||
TabRow(
|
||||
selectedTabIndex = sidebarTab,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = Color(0xFF1E3A8A),
|
||||
) {
|
||||
sidebarTabs.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
selected = sidebarTab == i,
|
||||
onClick = { sidebarTab = i },
|
||||
text = { Text(title, fontSize = 11.sp) },
|
||||
)
|
||||
}
|
||||
}
|
||||
when (sidebarTab) {
|
||||
2 -> BuchungenSidebar()
|
||||
else -> PlaceholderContent(title = sidebarTabs[sidebarTab], subtitle = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuchungenContent(buchungen: List<BuchungspositionUiModel>) {
|
||||
val gesamtSoll = buchungen.sumOf { it.soll }
|
||||
val gesamtHaben = buchungen.sumOf { it.haben }
|
||||
val gesamtSaldo = gesamtSoll - gesamtHaben
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.height(36.dp)) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = null, modifier = Modifier.size(14.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Aktualisieren", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.height(36.dp)) {
|
||||
Text("Übersicht", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.height(36.dp)) {
|
||||
Text("Tabelle Leeren", fontSize = 12.sp, color = Color(0xFFEA580C))
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.height(36.dp)) {
|
||||
Text("Pferd aus Liste entfernen", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
// Tabellen-Header
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(Color(0xFFF3F4F6)).padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Buchungstext", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(3f))
|
||||
Text("Soll", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Haben", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Saldo", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Buchen", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(60.dp))
|
||||
Text("Rechnung", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(70.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
LazyColumn(modifier = Modifier.weight(1f)) {
|
||||
items(buchungen) { b ->
|
||||
val saldo = b.soll - b.haben
|
||||
var buchen by remember { mutableStateOf(false) }
|
||||
var rechnung by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(b.buchungstext, fontSize = 13.sp, modifier = Modifier.weight(3f))
|
||||
Text("%.2f €".format(b.soll), fontSize = 13.sp, modifier = Modifier.weight(1f))
|
||||
Text("%.2f €".format(b.haben), fontSize = 13.sp, modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
"%.2f €".format(saldo),
|
||||
fontSize = 13.sp,
|
||||
color = if (saldo > 0) OffenePostenRot else Color.Unspecified,
|
||||
fontWeight = if (saldo > 0) FontWeight.SemiBold else FontWeight.Normal,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Checkbox(checked = buchen, onCheckedChange = { buchen = it }, modifier = Modifier.width(60.dp))
|
||||
Checkbox(checked = rechnung, onCheckedChange = { rechnung = it }, modifier = Modifier.width(70.dp))
|
||||
}
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
}
|
||||
|
||||
// Gesamt-Zeile
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("GESAMT", fontSize = 13.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(3f))
|
||||
Text("%.2f €".format(gesamtSoll), fontSize = 13.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Text("%.2f €".format(gesamtHaben), fontSize = 13.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
"%.2f €".format(gesamtSaldo),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (gesamtSaldo > 0) OffenePostenRot else Color.Unspecified,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuchungenSidebar() {
|
||||
var suchtext by remember { mutableStateOf("") }
|
||||
var zahlungsart by remember { mutableStateOf("BAR") }
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize().padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Nach Reiter oder Pferd", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
OutlinedTextField(
|
||||
value = suchtext,
|
||||
onValueChange = { suchtext = it },
|
||||
placeholder = { Text("Bitte auswählen...", fontSize = 12.sp) },
|
||||
modifier = Modifier.fillMaxWidth().height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Text("Buchen:", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("0.00 €", fontSize = 22.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Button(
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) { Text("Buchen") }
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Text("Direkt Drucken:", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.weight(1f)) {
|
||||
Icon(Icons.Default.Print, contentDescription = null, modifier = Modifier.size(14.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Saldo", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.weight(1f)) {
|
||||
Text("Rechnung", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Text("Zahlungsart:", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
listOf("BAR", "Scheck (+30 €)", "Bankomat", "Kreditkarte").forEach { art ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = zahlungsart == art, onClick = { zahlungsart = art })
|
||||
Text(art, fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) { Text("Gebühr buchen") }
|
||||
|
||||
// Hinweis
|
||||
Surface(
|
||||
color = Color(0xFFEFF6FF),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
border = androidx.compose.foundation.BorderStroke(1.dp, Color(0xFFBFDBFE)),
|
||||
) {
|
||||
Text(
|
||||
"💡 Hinweis: Bei Barzahlung werden die Buchungen sofort verarbeitet. Scheck-Zahlungen erfordern eine zusätzliche Gebühr von 30 €.",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF1E40AF),
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OffenePostenContent() {
|
||||
PlaceholderContent(title = "Offene Posten", subtitle = "Alle offenen Forderungen …")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RechnungContent() {
|
||||
PlaceholderContent(title = "Rechnung", subtitle = "Rechnungserstellung …")
|
||||
}
|
||||
|
||||
// --- UI-Modelle ---
|
||||
data class BuchungspositionUiModel(val buchungstext: String, val soll: Double, val haben: Double)
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
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)
|
||||
private val DeleteRed = Color(0xFFDC2626)
|
||||
|
||||
/**
|
||||
* ARTIKEL-Tab im TurnierDetailScreen.
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_07 / figma-entwurf_08):
|
||||
* - Nennungen & Gebühren: Nenngebühr, Startgebühr, Sporteuro, Nachnennungsgebühr, Nennungstausch
|
||||
* - Stallungen & Boxen: Box/Tag, Einstreu, Paddock
|
||||
* - Zusatzgebühren: dynamische Liste (Bezeichnung, Betrag, Pflicht)
|
||||
*/
|
||||
@Composable
|
||||
fun ArtikelTabContent() {
|
||||
var nenngebuehr by remember { mutableStateOf("0.00") }
|
||||
var startgebuehr by remember { mutableStateOf("15.00") }
|
||||
var sporteuro by remember { mutableStateOf("0.00") }
|
||||
var nachnennungsgebuehr by remember { mutableStateOf("0.00") }
|
||||
var nennungstauschGebuehr by remember { mutableStateOf("0.00") }
|
||||
var boxProTag by remember { mutableStateOf("0.00") }
|
||||
var einstreuErstEinstreu by remember { mutableStateOf("0.00") }
|
||||
var einstreuNachlegen by remember { mutableStateOf("0.00") }
|
||||
var paddockProTag by remember { mutableStateOf("0.00") }
|
||||
|
||||
var zusatzgebuehren by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
ZusatzgebuehrUiModel("Stromanschluss pro Tag", "5.00", false),
|
||||
ZusatzgebuehrUiModel("Camping pro Nacht", "10.00", false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
// ── Nennungen & Gebühren ─────────────────────────────────────────────
|
||||
ArtikelSectionCard(title = "Nennungen & Gebühren") {
|
||||
ArtikelSubSection("Nennungs- und Startgebühren") {
|
||||
ArtikelFormRow("Nenngebühr pro Pferd/Reiter:", "(Grundgebühr unabhängig von Anzahl Bewerben)") {
|
||||
EuroTextField(nenngebuehr) { nenngebuehr = it }
|
||||
}
|
||||
ArtikelFormRow("Startgebühr pro Bewerb:", "(Pro einzelner Prüfung)") {
|
||||
EuroTextField(startgebuehr) { startgebuehr = it }
|
||||
}
|
||||
ArtikelFormRow("Sporteuro (Beitrag OEPS):", null) {
|
||||
EuroTextField(sporteuro) { sporteuro = it }
|
||||
}
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
ArtikelFormRow("Nachnennungsgebühr:", "(Nach Nennschluss)") {
|
||||
EuroTextField(nachnennungsgebuehr) { nachnennungsgebuehr = it }
|
||||
}
|
||||
ArtikelFormRow("Nennungstausch-Gebühr:", "(Pferd- oder Reiter-Wechsel)") {
|
||||
EuroTextField(nennungstauschGebuehr) { nennungstauschGebuehr = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stallungen & Boxen ───────────────────────────────────────────────
|
||||
ArtikelSectionCard(title = "Stallungen & Boxen") {
|
||||
ArtikelFormRow("Box pro Tag:", null) {
|
||||
EuroTextField(boxProTag) { boxProTag = it }
|
||||
}
|
||||
ArtikelFormRow("Einstreu (Erst-Einstreu):", null) {
|
||||
EuroTextField(einstreuErstEinstreu) { einstreuErstEinstreu = it }
|
||||
}
|
||||
ArtikelFormRow("Einstreu (Nachlegen):", null) {
|
||||
EuroTextField(einstreuNachlegen) { einstreuNachlegen = it }
|
||||
}
|
||||
ArtikelFormRow("Paddock pro Tag:", null) {
|
||||
EuroTextField(paddockProTag) { paddockProTag = it }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Zusatzgebühren ───────────────────────────────────────────────────
|
||||
ArtikelSectionCard(
|
||||
title = "Zusatzgebühren",
|
||||
action = {
|
||||
TextButton(onClick = {
|
||||
zusatzgebuehren = zusatzgebuehren + ZusatzgebuehrUiModel("", "0.00", false)
|
||||
}) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp), tint = AccentBlue)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Tabellen-Header
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)) {
|
||||
Text("Bezeichnung", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(3f))
|
||||
Text("Betrag", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1.5f))
|
||||
Text("Pflicht", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Spacer(Modifier.width(44.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
zusatzgebuehren.forEachIndexed { idx, z ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = z.bezeichnung,
|
||||
onValueChange = { v ->
|
||||
zusatzgebuehren = zusatzgebuehren.toMutableList().also { it[idx] = z.copy(bezeichnung = v) }
|
||||
},
|
||||
modifier = Modifier.weight(3f).height(44.dp).padding(end = 8.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = z.betrag,
|
||||
onValueChange = { v ->
|
||||
zusatzgebuehren = zusatzgebuehren.toMutableList().also { it[idx] = z.copy(betrag = v) }
|
||||
},
|
||||
suffix = { Text("€") },
|
||||
modifier = Modifier.weight(1.5f).height(44.dp).padding(end = 8.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Checkbox(
|
||||
checked = z.pflicht,
|
||||
onCheckedChange = { v ->
|
||||
zusatzgebuehren = zusatzgebuehren.toMutableList().also { it[idx] = z.copy(pflicht = v) }
|
||||
},
|
||||
)
|
||||
Text("Pflicht", fontSize = 12.sp)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { zusatzgebuehren = zusatzgebuehren.toMutableList().also { it.removeAt(idx) } },
|
||||
modifier = Modifier.size(44.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Löschen",
|
||||
tint = DeleteRed,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hinweis ──────────────────────────────────────────────────────────
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = Color(0xFFFFFBEB),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
border = androidx.compose.foundation.BorderStroke(1.dp, Color(0xFFFDE68A)),
|
||||
) {
|
||||
Row(modifier = Modifier.padding(12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("ℹ️", fontSize = 14.sp)
|
||||
Column {
|
||||
Text("Hinweis zur Preisliste", fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text(
|
||||
"Die Gebührenstruktur wird in der offiziellen Ausschreibung veröffentlicht und ist für alle Teilnehmer verbindlich. " +
|
||||
"Bei nationalen Turnieren der Kategorie C-Neu sind oft reduzierte Gebühren oder Gebührenbefreiungen üblich (z.B. kein Nenngeld, kein Sporteuro).",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF92400E),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aktions-Buttons ──────────────────────────────────────────────────
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
OutlinedButton(onClick = {}) { Text("Zurücksetzen") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Button(onClick = {}, colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)) { Text("Speichern") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArtikelSectionCard(
|
||||
title: String,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(title, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
|
||||
action?.invoke()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArtikelSubSection(title: String, content: @Composable ColumnScope.() -> Unit) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFF9FAFB)),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(title, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ArtikelFormRow(label: String, hint: String?, content: @Composable RowScope.() -> Unit) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(label, fontSize = 13.sp, modifier = Modifier.width(220.dp), color = Color(0xFF374151))
|
||||
content()
|
||||
if (hint != null) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
hint,
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF9CA3AF),
|
||||
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EuroTextField(value: String, onValueChange: (String) -> Unit) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
suffix = { Text("€") },
|
||||
modifier = Modifier.width(120.dp).height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
// --- UI-Modelle ---
|
||||
data class ZusatzgebuehrUiModel(val bezeichnung: String, val betrag: String, val pflicht: Boolean)
|
||||
+644
@@ -0,0 +1,644 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
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 HeaderBg = Color(0xFFF1F5F9)
|
||||
private val SelectedRowBg = Color(0xFFEFF6FF)
|
||||
|
||||
/**
|
||||
* BEWERBE-Tab gemäß Vision_03 (Screenshots 09–12).
|
||||
*
|
||||
* Layout: 3-spaltig
|
||||
* - Links (140dp): Aktions-Buttons (Speichern, Rückgängig, Einfügen, Löschen, Teilen, Verschieben, Startliste, Ergebnisliste)
|
||||
* - Mitte (flex): Datentabelle (Tag | Platz | Bewerb | Beginn | Ende | Bewerbname | ZNS | Nennungen)
|
||||
* - Rechts (340dp): Detail-Panel mit Sub-Tabs (Bewerb | Bewertung | Geldpreise | Ort/Zeit)
|
||||
*/
|
||||
@Composable
|
||||
fun BewerbeTabContent() {
|
||||
var selectedIndex by remember { mutableIntStateOf(0) }
|
||||
val bewerbe = remember { sampleBewerbe() }
|
||||
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Linke Aktions-Spalte ──────────────────────────────────────────────
|
||||
BewerbeAktionsSpalte(modifier = Modifier.width(140.dp).fillMaxHeight())
|
||||
VerticalDivider()
|
||||
|
||||
// ── Mittlere Tabelle ──────────────────────────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
// Toolbar über der Tabelle
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = null, modifier = Modifier.size(14.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Aktualisieren", fontSize = 12.sp)
|
||||
}
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = PrimaryBlue,
|
||||
) {
|
||||
Text(
|
||||
text = "${bewerbe.size} Bewerbe",
|
||||
color = Color.White,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
|
||||
) {
|
||||
Text("Filtern", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
// Tabellen-Header
|
||||
BewerbeTableHeader()
|
||||
HorizontalDivider()
|
||||
|
||||
// Tabellen-Zeilen
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
itemsIndexed(bewerbe) { index, bewerb ->
|
||||
BewerbeTableRow(
|
||||
bewerb = bewerb,
|
||||
isSelected = index == selectedIndex,
|
||||
onClick = { selectedIndex = index },
|
||||
)
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechtes Detail-Panel ──────────────────────────────────────────────
|
||||
BewerbeDetailPanel(
|
||||
bewerb = bewerbe.getOrNull(selectedIndex),
|
||||
modifier = Modifier.width(340.dp).fillMaxHeight(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeTableHeader() {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(HeaderBg)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TableHeaderCell("Tag", 90.dp)
|
||||
TableHeaderCell("Platz", 50.dp)
|
||||
TableHeaderCell("Bewerb", 55.dp)
|
||||
TableHeaderCell("Beginn", 55.dp)
|
||||
TableHeaderCell("Ende", 55.dp)
|
||||
TableHeaderCell("Bewerbname", weight = 1f)
|
||||
TableHeaderCell("ZNS", 45.dp)
|
||||
TableHeaderCell("Nennungen", 75.dp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.TableHeaderCell(text: String, width: androidx.compose.ui.unit.Dp? = null, weight: Float? = null) {
|
||||
val mod = when {
|
||||
weight != null -> Modifier.weight(weight)
|
||||
width != null -> Modifier.width(width)
|
||||
else -> Modifier
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color(0xFF374151),
|
||||
modifier = mod,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeTableRow(bewerb: BewerbUiModel, isSelected: Boolean, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(if (isSelected) SelectedRowBg else Color.Transparent)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 8.dp, vertical = 5.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(bewerb.tag, fontSize = 12.sp, modifier = Modifier.width(90.dp))
|
||||
Text("${bewerb.platz}", fontSize = 12.sp, modifier = Modifier.width(50.dp))
|
||||
Text(
|
||||
"${bewerb.nummer}",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
|
||||
modifier = Modifier.width(55.dp),
|
||||
color = if (isSelected) PrimaryBlue else Color.Unspecified
|
||||
)
|
||||
Text(bewerb.beginn, fontSize = 12.sp, modifier = Modifier.width(55.dp))
|
||||
Text(bewerb.ende, fontSize = 12.sp, modifier = Modifier.width(55.dp))
|
||||
Text(bewerb.name, fontSize = 12.sp, modifier = Modifier.weight(1f), maxLines = 2)
|
||||
Text("${bewerb.zns}", fontSize = 12.sp, modifier = Modifier.width(45.dp))
|
||||
Text("${bewerb.nennungen}", fontSize = 12.sp, modifier = Modifier.width(75.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbeAktionsSpalte(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
AktionsBtn("Änderungen\nSpeichern")
|
||||
AktionsBtn("Änderungen\nRückgängig")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsBtn("Bewerb\nEinfügen")
|
||||
AktionsBtn("Bewerb\nLöschen")
|
||||
AktionsBtn("Bewerb Teilen")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsBtn("Bewerb nach\noben verschieben")
|
||||
AktionsBtn("Bewerb nach\nunten verschieben")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsBtn("Startliste\nBearbeiten")
|
||||
AktionsBtn("Startliste\nDrucken")
|
||||
AktionsBtn("Ergebnisliste\nBearbeiten")
|
||||
AktionsBtn("Ergebnisliste\nDrucken")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AktionsBtn(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(bewerb: BewerbUiModel?, modifier: Modifier = Modifier) {
|
||||
var subTab by remember { mutableIntStateOf(0) }
|
||||
val subTabs = listOf("Bewerb", "Bewertung", "Geldpreise", "Ort/Zeit")
|
||||
|
||||
Column(modifier = modifier) {
|
||||
PrimaryTabRow(
|
||||
selectedTabIndex = subTab,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = PrimaryBlue,
|
||||
) {
|
||||
subTabs.forEachIndexed { i, title ->
|
||||
Tab(
|
||||
selected = subTab == i,
|
||||
onClick = { subTab = i },
|
||||
text = { Text(title, fontSize = 12.sp) },
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp)) {
|
||||
when (subTab) {
|
||||
0 -> BewerbSubTab(bewerb)
|
||||
1 -> BewertungSubTab(bewerb)
|
||||
2 -> GeldpreiseSubTab()
|
||||
3 -> OrtZeitSubTab(bewerb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sub-Tab: Bewerb ───────────────────────────────────────────────────────────
|
||||
|
||||
@Composable
|
||||
private fun BewerbSubTab(bewerb: BewerbUiModel?) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
DetailField("Nummer:", bewerb?.nummer?.toString() ?: "")
|
||||
DetailField("Abteilung:", "")
|
||||
DetailField("Typ:", bewerb?.typ ?: "")
|
||||
DetailField("Name:", bewerb?.name ?: "")
|
||||
DetailField("Bezeichnung:", bewerb?.bezeichnung ?: "")
|
||||
DetailDropdown("Kategorie:")
|
||||
DetailDropdown("Klasse:")
|
||||
DetailDropdown("Lizenz:")
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Maximal:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = "3",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(60.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Pferde je Reiter", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
DetailDropdown("Pferdealter:")
|
||||
DetailField("Zeile 1:", bewerb?.zeile1 ?: "")
|
||||
DetailField("Zeile 2:", "")
|
||||
DetailField("Zeile 3:", "")
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Logo Bewerb:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(40.dp),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp)
|
||||
) {
|
||||
Text("…", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sub-Tab: Bewertung ────────────────────────────────────────────────────────
|
||||
|
||||
@Composable
|
||||
private fun BewertungSubTab(bewerb: BewerbUiModel?) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Text("Bewertungs-Konfiguration", fontWeight = FontWeight.SemiBold, fontSize = 13.sp, color = Color(0xFF374151))
|
||||
DetailField("Prüfung:", "Dressurreiterprüfung")
|
||||
DetailField("Richtverfahren:", "A")
|
||||
DetailField("Para-Grade:", "")
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Richteranzahl:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = "2",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(60.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
}
|
||||
DetailField("Aufgabe:", "Aufgabe R")
|
||||
DetailField("Aufgabennummer:", "")
|
||||
DetailField("Maximalpunkte:", "")
|
||||
HorizontalDivider()
|
||||
Text("Richter", fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
RichterRow("C:", "Schuster Alexandra")
|
||||
RichterRow("C:", "Vankova Kamila (CZ)")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RichterRow(position: String, name: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(position, fontSize = 13.sp, modifier = Modifier.width(30.dp))
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
Checkbox(checked = true, onCheckedChange = {})
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sub-Tab: Geldpreise ───────────────────────────────────────────────────────
|
||||
|
||||
@Composable
|
||||
private fun GeldpreiseSubTab() {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
// Geldpreis-Sektion
|
||||
Card(elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Geldpreis", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = false, onCheckedChange = {})
|
||||
Text("Geldpreis", fontSize = 13.sp)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Startgeld:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = "15,00",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(100.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Auszahlung:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
DetailDropdown("fortführend", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Geldpreis für Kadererreiter
|
||||
Card(elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Geldpreis für Kadererreiter", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = false, onCheckedChange = {})
|
||||
Text("Geldpreis für Kadererreiter", fontSize = 13.sp)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Startgeld für Kadererreiter:", fontSize = 13.sp, modifier = Modifier.width(180.dp))
|
||||
OutlinedTextField(
|
||||
value = "15,00",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(100.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Geldpreisvorlage
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Geldpreisvorlage wählen:", fontSize = 13.sp, modifier = Modifier.width(180.dp))
|
||||
DetailDropdown("", modifier = Modifier.weight(1f))
|
||||
}
|
||||
// Tabelle
|
||||
Text("0 Geldpreise", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(HeaderBg).padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Nummer", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Geldpreis", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
}
|
||||
HorizontalDivider()
|
||||
Text("•", fontSize = 12.sp, color = Color(0xFF9CA3AF), modifier = Modifier.padding(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sub-Tab: Ort/Zeit ─────────────────────────────────────────────────────────
|
||||
|
||||
@Composable
|
||||
private fun OrtZeitSubTab(bewerb: BewerbUiModel?) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Tag:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
DetailDropdown(bewerb?.tag ?: "28.05.2023", modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Beginnzeit:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
DetailDropdown("fix um", modifier = Modifier.width(100.dp))
|
||||
}
|
||||
LabeledTimeField("Beginnzeit:", bewerb?.beginn ?: "08:00", "(hh:mm)")
|
||||
LabeledTimeField("Reitdauer:", "02:00", "(mm:ss)")
|
||||
LabeledTimeField("Umbau:", "10", "(mm)")
|
||||
LabeledTimeField("Besichtigung:", "10", "(mm)")
|
||||
LabeledTimeField("Stechen:", "", "(mm)")
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Platz:", fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
DetailDropdown("Vorderer Turnierplatz", modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LabeledTimeField(label: String, value: String, unit: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(label, fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(80.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(unit, fontSize = 12.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hilfs-Composables ─────────────────────────────────────────────────────────
|
||||
|
||||
@Composable
|
||||
private fun DetailField(label: String, value: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(label, fontSize = 13.sp, modifier = Modifier.width(130.dp))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.weight(1f),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailDropdown(placeholder: String, modifier: Modifier = Modifier) {
|
||||
OutlinedTextField(
|
||||
value = placeholder,
|
||||
onValueChange = {},
|
||||
modifier = modifier,
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 13.sp),
|
||||
trailingIcon = {
|
||||
Text("▼", fontSize = 10.sp, color = Color(0xFF6B7280))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ── UI-Modell ─────────────────────────────────────────────────────────────────
|
||||
|
||||
data class BewerbUiModel(
|
||||
val tag: String,
|
||||
val platz: Int,
|
||||
val nummer: Int,
|
||||
val beginn: String,
|
||||
val ende: String,
|
||||
val name: String,
|
||||
val bezeichnung: String,
|
||||
val typ: String,
|
||||
val zeile1: String,
|
||||
val zns: Int,
|
||||
val nennungen: Int,
|
||||
)
|
||||
|
||||
private fun sampleBewerbe() = listOf(
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
1,
|
||||
"08:00",
|
||||
"08:00",
|
||||
"Dressurreiterprüfung Reiterpass\n(Aufgabe R 1)\nPony Einsteiger Cup OO",
|
||||
"Dressurreiterprüfung Reiterpass",
|
||||
"Dressur",
|
||||
"Pony Einsteiger Cup OO",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
2,
|
||||
"08:20",
|
||||
"08:20",
|
||||
"Dressurreiterprüfung Reitenadel\n(Aufgabe R 4)\nPony Einsteiger Cup OO",
|
||||
"Dressurreiterprüfung Reitenadel",
|
||||
"Dressur",
|
||||
"Pony Einsteiger Cup OO",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
3,
|
||||
"08:40",
|
||||
"08:40",
|
||||
"Dressurreiterprüfung lsf. (Istzfrei)\n(Aufgabe LF 1)",
|
||||
"Dressurreiterprüfung lsf.",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
4,
|
||||
"09:00",
|
||||
"09:00",
|
||||
"Dressurreiterprüfung lsf. (Lizenzfrei)\n(Aufgabe LF 3)",
|
||||
"Dressurreiterprüfung lsf.",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
5,
|
||||
"09:20",
|
||||
"09:20",
|
||||
"Führzügelklasse\nOO Kids Cup",
|
||||
"Führzügelklasse",
|
||||
"Dressur",
|
||||
"OO Kids Cup",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
6,
|
||||
"09:40",
|
||||
"09:40",
|
||||
"First Ridden\nOO Kids Cup",
|
||||
"First Ridden",
|
||||
"Dressur",
|
||||
"OO Kids Cup",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
7,
|
||||
"10:00",
|
||||
"10:00",
|
||||
"Pony Dressurprüfung Kl. A (Aufgabe P 1)",
|
||||
"Pony Dressurprüfung Kl. A",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
8,
|
||||
"10:20",
|
||||
"10:20",
|
||||
"Dressurreiterprüfung Kl. A (Aufgabe DRA 1)",
|
||||
"Dressurreiterprüfung Kl. A",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
9,
|
||||
"10:40",
|
||||
"10:40",
|
||||
"Dressurreiterprüfung Kl. A (Aufgabe A 5)",
|
||||
"Dressurreiterprüfung Kl. A",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
10,
|
||||
"11:00",
|
||||
"11:00",
|
||||
"Pony Dressurprüfung Kl. A (Aufgabe P 9)",
|
||||
"Pony Dressurprüfung Kl. A",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
11,
|
||||
"11:20",
|
||||
"11:20",
|
||||
"Dressurreiterprüfung Kl. L (Aufgabe DRL 1)",
|
||||
"Dressurreiterprüfung Kl. L",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
1,
|
||||
12,
|
||||
"11:40",
|
||||
"11:40",
|
||||
"Dressurprüfung Kl. L (Aufgabe L 3)",
|
||||
"Dressurprüfung Kl. L",
|
||||
"Dressur",
|
||||
"",
|
||||
0,
|
||||
0
|
||||
),
|
||||
)
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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
|
||||
*
|
||||
*/
|
||||
@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)
|
||||
PrimaryScrollableTabRow(
|
||||
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 werden in dedizierten Dateien implementiert:
|
||||
// TurnierBewerbeTab.kt → BewerbeTabContent()
|
||||
// TurnierNennungenTab.kt → NennungenTabContent()
|
||||
// TurnierStartlistenTab.kt → StartlistenTabContent()
|
||||
// TurnierErgebnislistenTab.kt → ErgebnislistenTabContent()
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
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 ElBlue = Color(0xFF1E3A8A)
|
||||
private val ElHeaderBg = Color(0xFFF1F5F9)
|
||||
|
||||
/**
|
||||
* ERGEBNISLISTEN-Tab gemäß Vision_03.
|
||||
*
|
||||
* Layout: 2-spaltig
|
||||
* - Links (flex): Bewerbs-Tabs + Ergebnis-Tabelle (Platz | Startnr | Pferd | Reiter | Fehler | Zeit | Punkte)
|
||||
* - Rechts (280dp): Platzierung & Geldpreis-Panel
|
||||
*/
|
||||
@Composable
|
||||
fun ErgebnislistenTabContent() {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Linke Spalte: Bewerbs-Tabs + Tabelle ─────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
ErgebnislistenBewerbsTabs()
|
||||
}
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechte Spalte: Platzierung & Geldpreis ───────────────────────────
|
||||
PlatzierungGeldpreisPanel(modifier = Modifier.width(280.dp).fillMaxHeight())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ErgebnislistenBewerbsTabs() {
|
||||
val bewerbe = remember {
|
||||
listOf("Bewerb 1", "Bewerb 2", "Bewerb 3", "Bewerb 4", "Bewerb 5")
|
||||
}
|
||||
var selectedBewerb by remember { mutableIntStateOf(0) }
|
||||
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedBewerb,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = ElBlue,
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
bewerbe.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedBewerb == index,
|
||||
onClick = { selectedBewerb = index },
|
||||
text = { Text(title, fontSize = 12.sp) },
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Bewerb ${selectedBewerb + 1} – Ergebnisliste",
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("Importieren", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("Exportieren", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("Drucken", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
// Tabellen-Header
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(ElHeaderBg).padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Platz", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(50.dp))
|
||||
Text("Startnr.", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(65.dp))
|
||||
Text("Pferd", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Reiter", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Fehler", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(60.dp))
|
||||
Text("Zeit", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(70.dp))
|
||||
Text("Punkte", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(70.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
// Leere Liste
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Keine Ergebnisse vorhanden", fontSize = 14.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Ergebnisse werden nach dem Turnier eingetragen.", fontSize = 12.sp, color = Color(0xFF9CA3AF))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = {}) {
|
||||
Text("Ergebnisse importieren", fontSize = 13.sp)
|
||||
}
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = ElBlue),
|
||||
) {
|
||||
Text("Ergebnisse eingeben", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlatzierungGeldpreisPanel(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Platzierung & Geldpreis", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
HorizontalDivider()
|
||||
|
||||
// Anzahl Platzierte
|
||||
Text("Platzierung", fontSize = 12.sp, color = Color(0xFF374151), fontWeight = FontWeight.Medium)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Anzahl Platzierte:", fontSize = 12.sp, modifier = Modifier.width(140.dp))
|
||||
OutlinedTextField(
|
||||
value = "3",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(60.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Stechen ab Platz:", fontSize = 12.sp, modifier = Modifier.width(140.dp))
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(60.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Geldpreise
|
||||
Text("Geldpreise", fontSize = 12.sp, color = Color(0xFF374151), fontWeight = FontWeight.Medium)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(ElHeaderBg).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text("Platz", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(50.dp))
|
||||
Text("Betrag (€)", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
}
|
||||
HorizontalDivider()
|
||||
listOf(1 to "–", 2 to "–", 3 to "–").forEach { (platz, betrag) ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"$platz.",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = ElBlue,
|
||||
modifier = Modifier.width(50.dp)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = betrag,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.weight(1f).height(36.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Aktions-Buttons
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = ElBlue),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("Platzierung berechnen", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Ergebnisliste drucken", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Geldpreise auszahlen", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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 NennBlue = Color(0xFF1E3A8A)
|
||||
private val NennHeaderBg = Color(0xFFF1F5F9)
|
||||
private val NennSelectedBg = Color(0xFFEFF6FF)
|
||||
|
||||
/**
|
||||
* NENNUNGEN-Tab gemäß Vision_03.
|
||||
*
|
||||
* Layout: 2-spaltig
|
||||
* - Links (flex): Pferd+Reiter-Suche + Nennungs-Tabelle
|
||||
* - Rechts (360dp): Verkauf/Buchungen + Bewerbsübersicht
|
||||
*/
|
||||
@Composable
|
||||
fun NennungenTabContent() {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Linke Spalte: Suche + Tabelle ─────────────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
NennungenSuchePanel()
|
||||
HorizontalDivider()
|
||||
NennungenTabelle()
|
||||
}
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechte Spalte: Verkauf + Bewerbsübersicht ─────────────────────────
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(360.dp)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
VerkaufBuchungenPanel()
|
||||
HorizontalDivider()
|
||||
BewerbsuebersichtPanel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NennungenSuchePanel() {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Pferd & Reiter suchen", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
placeholder = { Text("Pferd suchen (Name, OEPS-Nr.)…", fontSize = 12.sp) },
|
||||
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null, modifier = Modifier.size(16.dp)) },
|
||||
modifier = Modifier.weight(1f).height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
placeholder = { Text("Reiter suchen (Name, OEPS-Nr.)…", fontSize = 12.sp) },
|
||||
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null, modifier = Modifier.size(16.dp)) },
|
||||
modifier = Modifier.weight(1f).height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = NennBlue),
|
||||
modifier = Modifier.height(44.dp),
|
||||
) {
|
||||
Text("Suchen", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NennungenTabelle() {
|
||||
val nennungen = remember { sampleNennungen() }
|
||||
var selectedIndex by remember { mutableIntStateOf(-1) }
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Header
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(NennHeaderBg)
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Startnr.", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(60.dp))
|
||||
Text("Pferd", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Reiter", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Bewerb", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Status", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(80.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
if (nennungen.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Keine Nennungen vorhanden", fontSize = 14.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Suchen Sie nach Pferd und Reiter, um eine Nennung hinzuzufügen.",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF9CA3AF)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
itemsIndexed(nennungen) { index, nennung ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(if (index == selectedIndex) NennSelectedBg else Color.Transparent)
|
||||
.clickable { selectedIndex = index }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"${nennung.startnr}",
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.width(60.dp),
|
||||
color = NennBlue,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(nennung.pferd, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
Text(nennung.reiter, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
Text(nennung.bewerb, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
NennungStatusBadge(nennung.status)
|
||||
}
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NennungStatusBadge(status: String) {
|
||||
val (bg, fg) = when (status) {
|
||||
"Gemeldet" -> Color(0xFFDCFCE7) to Color(0xFF16A34A)
|
||||
"Bezahlt" -> Color(0xFFDBEAFE) to NennBlue
|
||||
"Abgemeldet" -> Color(0xFFFEE2E2) to Color(0xFFDC2626)
|
||||
else -> Color(0xFFF3F4F6) to Color(0xFF6B7280)
|
||||
}
|
||||
Surface(shape = MaterialTheme.shapes.small, color = bg) {
|
||||
Text(
|
||||
text = status,
|
||||
fontSize = 10.sp,
|
||||
color = fg,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VerkaufBuchungenPanel() {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Verkauf / Buchungen", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
|
||||
// Artikel-Buchungen
|
||||
Card(elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Text("Artikel-Buchungen", fontSize = 12.sp, fontWeight = FontWeight.Medium, color = Color(0xFF374151))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(NennHeaderBg).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text("Artikel", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Menge", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(50.dp))
|
||||
Text("Preis", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(60.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
Text(
|
||||
"Keine Buchungen",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF9CA3AF),
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("+ Artikel buchen", fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BewerbsuebersichtPanel() {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Bewerbsübersicht", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
Card(elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(NennHeaderBg).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text("Bewerb", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Nennungen", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(80.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
listOf(
|
||||
"Bewerb 1 – Dressur Kl. A" to 0,
|
||||
"Bewerb 2 – Dressur Kl. L" to 0,
|
||||
"Bewerb 3 – Springen Kl. A" to 0,
|
||||
).forEach { (name, count) ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(name, fontSize = 12.sp, modifier = Modifier.weight(1f))
|
||||
Text("$count", fontSize = 12.sp, modifier = Modifier.width(80.dp), color = Color(0xFF6B7280))
|
||||
}
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── UI-Modell ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private data class NennungUiModel(
|
||||
val startnr: Int,
|
||||
val pferd: String,
|
||||
val reiter: String,
|
||||
val bewerb: String,
|
||||
val status: String,
|
||||
)
|
||||
|
||||
private fun sampleNennungen(): List<NennungUiModel> = emptyList()
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
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
|
||||
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
|
||||
|
||||
/**
|
||||
* 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 …")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
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)
|
||||
private val DeleteRed = Color(0xFFDC2626)
|
||||
|
||||
/**
|
||||
* ORGANISATION-Tab im TurnierDetailScreen.
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_13 / figma-entwurf_14):
|
||||
* - Funktionäre & Offizielle (C-Satz): Turnierleiter, Turnierbeauftragter, Technischer Delegierter, Parcourschef
|
||||
* - Support-Team: Tierarzt, Schmied, Steward
|
||||
* - Richterkollegium: dynamische Liste (Name, Qualifikation, Funktion, Löschen)
|
||||
* - Austragungsplätze: dynamische Liste (Sparte, Größe, Bezeichnung, Löschen)
|
||||
*/
|
||||
@Composable
|
||||
fun OrganisationTabContent() {
|
||||
var turnierleiter by remember { mutableStateOf("") }
|
||||
var turnierbeauftragter by remember { mutableStateOf("") }
|
||||
var technischerDelegierter by remember { mutableStateOf("") }
|
||||
var parcourschef by remember { mutableStateOf("") }
|
||||
var tierarzt by remember { mutableStateOf("") }
|
||||
var schmied by remember { mutableStateOf("") }
|
||||
var steward by remember { mutableStateOf("") }
|
||||
|
||||
var richter by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
RichterUiModel("Alexandra Schuster", "D-GP", "Hauptrichter"),
|
||||
RichterUiModel("Ulrike Knasmüller-Prinz", "D-M", "Beisitzer"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
var plaetze by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
AustragungsplatzUiModel("Dressur", "20 x 60 m", "Hauptplatz"),
|
||||
AustragungsplatzUiModel("Dressur", "20 x 40 m", "Abreiteplatz 1"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
// ── Funktionäre & Offizielle ─────────────────────────────────────────
|
||||
OrgSectionCard(title = "Funktionäre & Offizielle (C-Satz)") {
|
||||
OrgSubSection("Turnier-Organisation") {
|
||||
OrgSearchField("Turnierleiter:", turnierleiter) { turnierleiter = it }
|
||||
OrgSearchField("Turnierbeauftragter:", turnierbeauftragter) { turnierbeauftragter = it }
|
||||
OrgSearchField("Technischer Delegierter:", technischerDelegierter) { technischerDelegierter = it }
|
||||
OrgSearchField("Parcourschef:", parcourschef) { parcourschef = it }
|
||||
}
|
||||
OrgSubSection("Support-Team") {
|
||||
OrgSearchField("Tierarzt:", tierarzt) { tierarzt = it }
|
||||
OrgSearchField("Schmied:", schmied) { schmied = it }
|
||||
OrgSearchField("Steward:", steward) { steward = it }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Richterkollegium ─────────────────────────────────────────────────
|
||||
OrgSectionCard(
|
||||
title = "Richterkollegium",
|
||||
action = {
|
||||
TextButton(onClick = {
|
||||
richter = richter + RichterUiModel("", "", "")
|
||||
}) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp), tint = AccentBlue)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Richter hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Tabellen-Header
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)) {
|
||||
Text("Name", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(3f))
|
||||
Text("Qualifikation", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1.5f))
|
||||
Text("Funktion", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1.5f))
|
||||
Text("Aktion", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(48.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
richter.forEachIndexed { idx, r ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = r.name,
|
||||
onValueChange = { v -> richter = richter.toMutableList().also { it[idx] = r.copy(name = v) } },
|
||||
modifier = Modifier.weight(3f).height(44.dp).padding(end = 8.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
// Qualifikation-Dropdown
|
||||
var qualExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = r.qualifikation,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.ArrowDropDown, contentDescription = null) },
|
||||
modifier = Modifier.fillMaxWidth().height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
DropdownMenu(expanded = qualExpanded, onDismissRequest = { qualExpanded = false }) {
|
||||
listOf("D-GP", "D-M", "D-L", "S-GP", "S-M").forEach { q ->
|
||||
DropdownMenuItem(text = { Text(q) }, onClick = {
|
||||
richter = richter.toMutableList().also { it[idx] = r.copy(qualifikation = q) }
|
||||
qualExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Funktion-Dropdown
|
||||
var funExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = r.funktion,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.ArrowDropDown, contentDescription = null) },
|
||||
modifier = Modifier.fillMaxWidth().height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
DropdownMenu(expanded = funExpanded, onDismissRequest = { funExpanded = false }) {
|
||||
listOf("Hauptrichter", "Beisitzer", "Schreiber").forEach { f ->
|
||||
DropdownMenuItem(text = { Text(f) }, onClick = {
|
||||
richter = richter.toMutableList().also { it[idx] = r.copy(funktion = f) }
|
||||
funExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = { richter = richter.toMutableList().also { it.removeAt(idx) } },
|
||||
modifier = Modifier.size(44.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Löschen",
|
||||
tint = DeleteRed,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Austragungsplätze ────────────────────────────────────────────────
|
||||
OrgSectionCard(
|
||||
title = "Austragungsplätze",
|
||||
) {
|
||||
OrgSubSection(
|
||||
title = "Plätze & Anlagen",
|
||||
action = {
|
||||
TextButton(onClick = {
|
||||
plaetze = plaetze + AustragungsplatzUiModel("", "", "")
|
||||
}) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp), tint = AccentBlue)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Platz hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)) {
|
||||
Text("Sparte", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1.5f))
|
||||
Text("Größe", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1.5f))
|
||||
Text("Bezeichnung", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(3f))
|
||||
Text("Aktion", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(48.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
plaetze.forEachIndexed { idx, p ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
var sparteExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = p.sparte,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.ArrowDropDown, contentDescription = null) },
|
||||
modifier = Modifier.fillMaxWidth().height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
DropdownMenu(expanded = sparteExpanded, onDismissRequest = { sparteExpanded = false }) {
|
||||
listOf("Dressur", "Springen", "Vielseitigkeit").forEach { s ->
|
||||
DropdownMenuItem(text = { Text(s) }, onClick = {
|
||||
plaetze = plaetze.toMutableList().also { it[idx] = p.copy(sparte = s) }
|
||||
sparteExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
var groesseExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.weight(1.5f).padding(end = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = p.groesse,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.ArrowDropDown, contentDescription = null) },
|
||||
modifier = Modifier.fillMaxWidth().height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
DropdownMenu(expanded = groesseExpanded, onDismissRequest = { groesseExpanded = false }) {
|
||||
listOf("20 x 60 m", "20 x 40 m", "60 x 80 m").forEach { g ->
|
||||
DropdownMenuItem(text = { Text(g) }, onClick = {
|
||||
plaetze = plaetze.toMutableList().also { it[idx] = p.copy(groesse = g) }
|
||||
groesseExpanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = p.bezeichnung,
|
||||
onValueChange = { v -> plaetze = plaetze.toMutableList().also { it[idx] = p.copy(bezeichnung = v) } },
|
||||
modifier = Modifier.weight(3f).height(44.dp).padding(end = 8.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
onClick = { plaetze = plaetze.toMutableList().also { it.removeAt(idx) } },
|
||||
modifier = Modifier.size(44.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Löschen",
|
||||
tint = DeleteRed,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Speichern ────────────────────────────────────────────────────────
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) { Text("Speichern") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrgSectionCard(
|
||||
title: String,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(title, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
|
||||
action?.invoke()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrgSubSection(
|
||||
title: String,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFFF9FAFB)),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(title, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
action?.invoke()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrgSearchField(label: String, value: String, onValueChange: (String) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(label, fontSize = 13.sp, modifier = Modifier.width(200.dp), color = Color(0xFF374151))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
placeholder = { Text("Name suchen...", fontSize = 12.sp) },
|
||||
modifier = Modifier.weight(1f).height(44.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI-Modelle ---
|
||||
|
||||
data class RichterUiModel(val name: String, val qualifikation: String, val funktion: String)
|
||||
data class AustragungsplatzUiModel(val sparte: String, val groesse: String, val bezeichnung: String)
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CloudDownload
|
||||
import androidx.compose.material.icons.filled.Usb
|
||||
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)
|
||||
|
||||
/**
|
||||
* STAMMDATEN-Tab im TurnierDetailScreen.
|
||||
* Gemäß Figma Vision_03 (figma-entwurf_16 / figma-entwurf_15):
|
||||
* - Turnier-Konfiguration: Nr., Typ (OTO/FEI), ZNS-Import, Sprache
|
||||
* - Sparten-Checkboxen, Klassen, Kategorien, Datum
|
||||
* - Turnier-Beschreibung: Titel, Sub-Titel
|
||||
* - Sponsoren
|
||||
*/
|
||||
@Composable
|
||||
fun StammdatenTabContent(turnierId: Long) {
|
||||
var turnierNr by remember { mutableStateOf("") }
|
||||
var typOto by remember { mutableStateOf(true) }
|
||||
var spracheDe by remember { mutableStateOf(true) }
|
||||
var sparteDressur by remember { mutableStateOf(false) }
|
||||
var sparteSpringen by remember { mutableStateOf(false) }
|
||||
var klasseC by remember { mutableStateOf(false) }
|
||||
var klasseB by remember { mutableStateOf(false) }
|
||||
var klasseA by remember { mutableStateOf(false) }
|
||||
var datumVon by remember { mutableStateOf("") }
|
||||
var datumBis by remember { mutableStateOf("") }
|
||||
var titel by remember { mutableStateOf("") }
|
||||
var subTitel by remember { mutableStateOf("") }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
// ── Turnier-Konfiguration ────────────────────────────────────────────
|
||||
SectionCard(title = "Turnier-Konfiguration") {
|
||||
FormRow("Turnier-Nr.:") {
|
||||
OutlinedTextField(
|
||||
value = turnierNr,
|
||||
onValueChange = { turnierNr = it },
|
||||
placeholder = { Text("z.B. 26128", fontSize = 13.sp) },
|
||||
modifier = Modifier.width(200.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
FormRow("Typ:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = typOto, onClick = { typOto = true })
|
||||
Text("OTO (National)", fontSize = 13.sp)
|
||||
RadioButton(selected = !typOto, onClick = { typOto = false })
|
||||
Text("FEI (International)", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
FormRow("ZNS-Daten:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue),
|
||||
) {
|
||||
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Import via Internet", fontSize = 13.sp)
|
||||
}
|
||||
OutlinedButton(onClick = {}) {
|
||||
Icon(Icons.Default.Usb, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Import via USB", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
"Reiter-, Pferde-, Funktionärs- und Vereinsdaten vom OEPS Backend",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF6B7280),
|
||||
)
|
||||
}
|
||||
FormRow("Sprache:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = spracheDe, onClick = { spracheDe = true })
|
||||
Text("Deutsch", fontSize = 13.sp)
|
||||
RadioButton(selected = !spracheDe, onClick = { spracheDe = false })
|
||||
Text("English", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
FormRow("Sparten:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = sparteDressur, onCheckedChange = { sparteDressur = it })
|
||||
Text("Dressur", fontSize = 13.sp)
|
||||
Checkbox(checked = sparteSpringen, onCheckedChange = { sparteSpringen = it })
|
||||
Text("Springen", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
FormRow("Klassen:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = klasseC, onCheckedChange = { klasseC = it })
|
||||
Text("C", fontSize = 13.sp)
|
||||
Checkbox(checked = klasseB, onCheckedChange = { klasseB = it })
|
||||
Text("B", fontSize = 13.sp)
|
||||
Checkbox(checked = klasseA, onCheckedChange = { klasseA = it })
|
||||
Text("A", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
FormRow("Kategorien:") {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(60.dp),
|
||||
color = Color(0xFFF3F4F6),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"Bitte Sparte(n) auswählen",
|
||||
fontSize = 13.sp,
|
||||
color = Color(0xFF9CA3AF),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FormRow("Datum:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
value = datumVon,
|
||||
onValueChange = { datumVon = it },
|
||||
placeholder = { Text("DD.MM.YYYY", fontSize = 12.sp) },
|
||||
modifier = Modifier.width(160.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
Text("bis", fontSize = 13.sp)
|
||||
OutlinedTextField(
|
||||
value = datumBis,
|
||||
onValueChange = { datumBis = it },
|
||||
placeholder = { Text("DD.MM.YYYY", fontSize = 12.sp) },
|
||||
modifier = Modifier.width(160.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Turnier-Beschreibung ─────────────────────────────────────────────
|
||||
SectionCard(title = "Turnier-Beschreibung") {
|
||||
OutlinedTextField(
|
||||
value = titel,
|
||||
onValueChange = { titel = it },
|
||||
placeholder = { Text("z.B. Frühjahrs-Turnier 2026", fontSize = 13.sp) },
|
||||
label = { Text("Titel") },
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = subTitel,
|
||||
onValueChange = { subTitel = it },
|
||||
placeholder = { Text("z.B. KIDS CUP • PONY EINSTEIGER CUP OÖ", fontSize = 13.sp) },
|
||||
label = { Text("Sub-Titel") },
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
// ── Sponsoren ────────────────────────────────────────────────────────
|
||||
SectionCard(
|
||||
title = "Sponsoren",
|
||||
action = {
|
||||
TextButton(onClick = {}) {
|
||||
Text("+ Sponsor hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(80.dp),
|
||||
color = Color(0xFFF9FAFB),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Noch keine Sponsoren hinzugefügt", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(4.dp))
|
||||
TextButton(onClick = {}) {
|
||||
Text("+ Ersten Sponsor hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aktions-Buttons ──────────────────────────────────────────────────
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(onClick = {}) { Text("Zurücksetzen") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) { Text("Speichern") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionCard(
|
||||
title: String,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(title, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
|
||||
action?.invoke()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FormRow(label: String, content: @Composable ColumnScope.() -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 13.sp,
|
||||
modifier = Modifier.width(140.dp).padding(top = 12.dp),
|
||||
color = Color(0xFF374151),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
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 SlBlue = Color(0xFF1E3A8A)
|
||||
private val SlHeaderBg = Color(0xFFF1F5F9)
|
||||
|
||||
/**
|
||||
* STARTLISTEN-Tab gemäß Vision_03.
|
||||
*
|
||||
* Layout: 2-spaltig
|
||||
* - Links (flex): Bewerbs-Tabs + Starter-Tabelle (Startnr | Pferd | Reiter | Abteilung | Beginn)
|
||||
* - Rechts (280dp): Sortierung & Zeit-Panel
|
||||
*/
|
||||
@Composable
|
||||
fun StartlistenTabContent() {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Linke Spalte: Bewerbs-Tabs + Tabelle ─────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
StartlistenBewerbsTabs()
|
||||
}
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechte Spalte: Sortierung & Zeit ─────────────────────────────────
|
||||
StartlistenSortierPanel(modifier = Modifier.width(280.dp).fillMaxHeight())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StartlistenBewerbsTabs() {
|
||||
val bewerbe = remember {
|
||||
listOf("Bewerb 1", "Bewerb 2", "Bewerb 3", "Bewerb 4", "Bewerb 5")
|
||||
}
|
||||
var selectedBewerb by remember { mutableIntStateOf(0) }
|
||||
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedBewerb,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = SlBlue,
|
||||
edgePadding = 0.dp,
|
||||
) {
|
||||
bewerbe.forEachIndexed { index, title ->
|
||||
Tab(
|
||||
selected = selectedBewerb == index,
|
||||
onClick = { selectedBewerb = index },
|
||||
text = { Text(title, fontSize = 12.sp) },
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
// Toolbar
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Bewerb ${selectedBewerb + 1} – Startliste",
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("Drucken", fontSize = 12.sp)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp)
|
||||
) {
|
||||
Text("Exportieren", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
// Tabellen-Header
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().background(SlHeaderBg).padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text("Startnr.", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(70.dp))
|
||||
Text("Pferd", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Reiter", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.weight(1f))
|
||||
Text("Abteilung", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(80.dp))
|
||||
Text("Beginn", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.width(70.dp))
|
||||
}
|
||||
HorizontalDivider()
|
||||
|
||||
// Leere Liste
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Keine Starter vorhanden", fontSize = 14.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Startliste wird nach Nennungsschluss generiert.", fontSize = 12.sp, color = Color(0xFF9CA3AF))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = SlBlue),
|
||||
) {
|
||||
Text("Startliste generieren", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StartlistenSortierPanel(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Sortierung & Zeit", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
|
||||
HorizontalDivider()
|
||||
|
||||
// Sortierung
|
||||
Text("Sortierung", fontSize = 12.sp, color = Color(0xFF374151), fontWeight = FontWeight.Medium)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
SortierOption("Aufsteigend (Startnummer)")
|
||||
SortierOption("Absteigend (Startnummer)")
|
||||
SortierOption("Auslosung (zufällig)")
|
||||
SortierOption("Alphabetisch (Pferd)")
|
||||
SortierOption("Alphabetisch (Reiter)")
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// Zeiten
|
||||
Text("Zeiten", fontSize = 12.sp, color = Color(0xFF374151), fontWeight = FontWeight.Medium)
|
||||
LabeledInput("Beginnzeit:", "08:00", "(hh:mm)")
|
||||
LabeledInput("Reitdauer:", "02:00", "(mm:ss)")
|
||||
LabeledInput("Umbau:", "10", "(mm)")
|
||||
LabeledInput("Besichtigung:", "10", "(mm)")
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = SlBlue),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("Zeiten neu berechnen", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SortierOption(label: String) {
|
||||
var selected by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
) {
|
||||
RadioButton(selected = selected, onClick = { selected = !selected })
|
||||
Text(label, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LabeledInput(label: String, value: String, unit: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(label, fontSize = 12.sp, modifier = Modifier.width(100.dp))
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.width(70.dp),
|
||||
singleLine = true,
|
||||
textStyle = LocalTextStyle.current.copy(fontSize = 12.sp),
|
||||
)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(unit, fontSize = 11.sp, color = Color(0xFF6B7280))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user