feat(core+frontend+domain): add ZNS Bewerb parser and integrate start list feature
- **Parser Implementation:** - Introduced `ZnsBewerbParser` to parse n2-XXXXX.dat files and map B-Satz lines to the `ZnsBewerb` domain model. - Added test coverage for parsing B-Satz lines and edge cases in `ZnsParserTest`. - **Frontend Integration:** - Integrated ZNS import functionality into the `BewerbeTabContent` for uploading and previewing Bewerb data before import. - Enhanced `BewerbViewModel` with state and intents for managing ZNS import, preview dialogs, and import confirmation. - Supported start list generation and added modal for previewing generated start lists. - **Domain Services:** - Implemented `StartlistenService` to generate and calculate start times for start lists with respect to participant preferences. - Added extensive test coverage in `StartlistenServiceTest` to validate sorting, preferences, and time calculations. - **UI Enhancements:** - Updated `Bewerbe` tab layout with search, filtering, and action buttons for ZNS import and start list generation. - Introduced dialogs for ZNS import previews and start list previews.
This commit is contained in:
@@ -18,6 +18,7 @@ kotlin {
|
||||
implementation(projects.frontend.core.domain)
|
||||
implementation(projects.frontend.core.network)
|
||||
implementation(projects.frontend.core.navigation)
|
||||
implementation(project(":core:zns-parser"))
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.runtime)
|
||||
|
||||
+76
@@ -1,5 +1,7 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import at.mocode.zns.parser.ZnsBewerb
|
||||
import at.mocode.zns.parser.ZnsBewerbParser
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -17,6 +19,14 @@ data class BewerbListItem(
|
||||
val nennungen: Int,
|
||||
)
|
||||
|
||||
data class StartlistenZeile(
|
||||
val nr: Int,
|
||||
val zeit: String,
|
||||
val reiter: String,
|
||||
val pferd: String,
|
||||
val wunsch: String,
|
||||
)
|
||||
|
||||
data class BewerbState(
|
||||
val isLoading: Boolean = false,
|
||||
val searchQuery: String = "",
|
||||
@@ -24,6 +34,10 @@ data class BewerbState(
|
||||
val filtered: List<BewerbListItem> = emptyList(),
|
||||
val selectedId: Long? = null,
|
||||
val errorMessage: String? = null,
|
||||
val importPreview: List<ZnsBewerb> = emptyList(),
|
||||
val showImportDialog: Boolean = false,
|
||||
val showStartlistePreview: Boolean = false,
|
||||
val currentStartliste: List<StartlistenZeile> = emptyList(),
|
||||
// Verknüpfung zum Dialog-VM für Abteilungs-Logik (optional)
|
||||
val dialogState: BewerbAnlegenState = BewerbAnlegenState(),
|
||||
)
|
||||
@@ -40,10 +54,18 @@ sealed interface BewerbIntent {
|
||||
data object CloseDialog : BewerbIntent
|
||||
data class SetBewerbsTyp(val typ: String) : BewerbIntent
|
||||
data class SetAbteilungsTyp(val typ: AbteilungsTyp) : BewerbIntent
|
||||
|
||||
data object OpenImportDialog : BewerbIntent
|
||||
data object CloseImportDialog : BewerbIntent
|
||||
data class ProcessImportFile(val lines: List<String>) : BewerbIntent
|
||||
data class ConfirmImport(val turnierId: Long) : BewerbIntent
|
||||
data object GenerateStartliste : BewerbIntent
|
||||
data object CloseStartlistePreview : BewerbIntent
|
||||
}
|
||||
|
||||
interface BewerbRepository {
|
||||
suspend fun listByTurnier(turnierId: Long): List<BewerbListItem>
|
||||
suspend fun importBewerbe(turnierId: Long, bewerbe: List<ZnsBewerb>): Result<Unit>
|
||||
}
|
||||
|
||||
class BewerbViewModel(
|
||||
@@ -85,6 +107,60 @@ class BewerbViewModel(
|
||||
dialogVm.send(BewerbAnlegenIntent.SetAbteilungsTyp(intent.typ))
|
||||
syncDialogState()
|
||||
}
|
||||
|
||||
is BewerbIntent.OpenImportDialog -> _state.value = _state.value.copy(showImportDialog = true)
|
||||
is BewerbIntent.CloseImportDialog -> _state.value = _state.value.copy(showImportDialog = false, importPreview = emptyList())
|
||||
is BewerbIntent.ProcessImportFile -> {
|
||||
val bewerbe = intent.lines.mapNotNull { ZnsBewerbParser.parse(it) }
|
||||
_state.value = _state.value.copy(importPreview = bewerbe)
|
||||
}
|
||||
is BewerbIntent.ConfirmImport -> {
|
||||
confirmImport()
|
||||
}
|
||||
is BewerbIntent.GenerateStartliste -> generateStartliste()
|
||||
is BewerbIntent.CloseStartlistePreview -> reduce { it.copy(showStartlistePreview = false) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateStartliste() {
|
||||
val selectedId = _state.value.selectedId ?: return
|
||||
reduce { it.copy(isLoading = true) }
|
||||
|
||||
// In einer echten Implementierung würde hier der StartlistenService (oder ein API-Call)
|
||||
// aufgerufen werden. Für den MVP/Prototyp simulieren wir die Generierung.
|
||||
scope.launch {
|
||||
kotlinx.coroutines.delay(800) // Simulation
|
||||
val mockStartliste = listOf(
|
||||
StartlistenZeile(1, "08:00", "Max Mustermann", "Ares", "VORNE"),
|
||||
StartlistenZeile(2, "08:05", "Susi Sonnenschein", "Bibi", "KEIN_WUNSCH"),
|
||||
StartlistenZeile(3, "08:10", "Tom Turbo", "Flash", "HINTEN")
|
||||
)
|
||||
reduce {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
showStartlistePreview = true,
|
||||
currentStartliste = mockStartliste
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmImport() {
|
||||
val toImport = _state.value.importPreview
|
||||
if (toImport.isEmpty()) {
|
||||
_state.value = _state.value.copy(showImportDialog = false)
|
||||
return
|
||||
}
|
||||
|
||||
reduce { it.copy(isLoading = true) }
|
||||
scope.launch {
|
||||
val result = repo.importBewerbe(turnierId, toImport)
|
||||
if (result.isSuccess) {
|
||||
reduce { it.copy(showImportDialog = false, importPreview = emptyList()) }
|
||||
load()
|
||||
} else {
|
||||
reduce { it.copy(isLoading = false, errorMessage = "Import fehlgeschlagen: ${result.exceptionOrNull()?.message}") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -4,15 +4,17 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SecondaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.TabRowDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -68,14 +70,18 @@ fun CreateBewerbWizardScreen(
|
||||
Text("Neuen Bewerb anlegen", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
TabRow(selectedTabIndex = selectedTab) {
|
||||
SecondaryTabRow(
|
||||
selectedTabIndex = selectedTab,
|
||||
modifier = Modifier,
|
||||
divider = { HorizontalDivider() }
|
||||
) {
|
||||
Tab(selected = selectedTab == 0, onClick = { selectedTab = 0 }, text = { Text("Identifikation") })
|
||||
Tab(selected = selectedTab == 1, onClick = { selectedTab = 1 }, text = { Text("Details & Finanzen") })
|
||||
Tab(selected = selectedTab == 2, onClick = { selectedTab = 2 }, text = { Text("Ort & Zeitplan") })
|
||||
Tab(selected = selectedTab == 3, onClick = { selectedTab = 3 }, text = { Text("Richter & Teilung") })
|
||||
}
|
||||
|
||||
Divider(Modifier.padding(vertical = 8.dp))
|
||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||
|
||||
when (steps[selectedTab]) {
|
||||
WizardStep.IDENTIFIKATION -> StepIdentifikation(state, onStateChange)
|
||||
|
||||
+176
-17
@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -15,8 +16,14 @@ 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.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import java.io.File
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.filechooser.FileNameExtensionFilter
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val HeaderBg = Color(0xFFF1F5F9)
|
||||
@@ -31,9 +38,12 @@ private val SelectedRowBg = Color(0xFFEFF6FF)
|
||||
* - 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() }
|
||||
fun BewerbeTabContent(
|
||||
viewModel: BewerbViewModel,
|
||||
turnierId: Long,
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
// Dialog-ViewModel für "Bewerb anlegen"
|
||||
val bewerbDialogVm = remember { BewerbAnlegenViewModel() }
|
||||
val bewerbDialogState by bewerbDialogVm.state.collectAsState()
|
||||
@@ -43,6 +53,19 @@ fun BewerbeTabContent() {
|
||||
BewerbeAktionsSpalte(
|
||||
modifier = Modifier.width(140.dp).fillMaxHeight(),
|
||||
onBewerbEinfuegen = { bewerbDialogVm.send(BewerbAnlegenIntent.Open) },
|
||||
onZnsImport = {
|
||||
val fileChooser = JFileChooser().apply {
|
||||
fileFilter = FileNameExtensionFilter("ZNS Nennungs-Dateien (*.dat)", "dat")
|
||||
}
|
||||
val result = fileChooser.showOpenDialog(null)
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
val file = fileChooser.selectedFile
|
||||
val lines = file.readLines(Charsets.ISO_8859_1)
|
||||
viewModel.send(BewerbIntent.ProcessImportFile(lines))
|
||||
viewModel.send(BewerbIntent.OpenImportDialog)
|
||||
}
|
||||
},
|
||||
onGenerateStartliste = { viewModel.send(BewerbIntent.GenerateStartliste) }
|
||||
)
|
||||
VerticalDivider()
|
||||
|
||||
@@ -57,7 +80,7 @@ fun BewerbeTabContent() {
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {},
|
||||
onClick = { viewModel.send(BewerbIntent.Refresh) },
|
||||
modifier = Modifier.height(32.dp),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
|
||||
) {
|
||||
@@ -70,20 +93,22 @@ fun BewerbeTabContent() {
|
||||
color = PrimaryBlue,
|
||||
) {
|
||||
Text(
|
||||
text = "${bewerbe.size} Bewerbe",
|
||||
text = "${state.list.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)
|
||||
}
|
||||
// Suchfeld
|
||||
OutlinedTextField(
|
||||
value = state.searchQuery,
|
||||
onValueChange = { viewModel.send(BewerbIntent.SearchChanged(it)) },
|
||||
modifier = Modifier.weight(1f).height(48.dp),
|
||||
placeholder = { Text("Suche...", fontSize = 12.sp) },
|
||||
singleLine = true,
|
||||
textStyle = TextStyle(fontSize = 12.sp),
|
||||
)
|
||||
}
|
||||
|
||||
// Tabellen-Header
|
||||
@@ -92,11 +117,11 @@ fun BewerbeTabContent() {
|
||||
|
||||
// Tabellen-Zeilen
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
itemsIndexed(bewerbe) { index, bewerb ->
|
||||
itemsIndexed(state.filtered) { _, item ->
|
||||
BewerbeTableRow(
|
||||
bewerb = bewerb,
|
||||
isSelected = index == selectedIndex,
|
||||
onClick = { selectedIndex = index },
|
||||
bewerb = item.toUiModel(),
|
||||
isSelected = state.selectedId == item.id,
|
||||
onClick = { viewModel.send(BewerbIntent.Select(item.id)) },
|
||||
)
|
||||
HorizontalDivider(color = Color(0xFFE5E7EB))
|
||||
}
|
||||
@@ -106,8 +131,9 @@ fun BewerbeTabContent() {
|
||||
VerticalDivider()
|
||||
|
||||
// ── Rechtes Detail-Panel ──────────────────────────────────────────────
|
||||
val selectedItem = state.list.find { it.id == state.selectedId }
|
||||
BewerbeDetailPanel(
|
||||
bewerb = bewerbe.getOrNull(selectedIndex),
|
||||
bewerb = selectedItem?.toUiModel(),
|
||||
modifier = Modifier.width(340.dp).fillMaxHeight(),
|
||||
)
|
||||
}
|
||||
@@ -128,6 +154,67 @@ fun BewerbeTabContent() {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (state.showImportDialog) {
|
||||
ZnsImportPreviewDialog(
|
||||
bewerbe = state.importPreview,
|
||||
onDismiss = { viewModel.send(BewerbIntent.CloseImportDialog) },
|
||||
onConfirm = { viewModel.send(BewerbIntent.ConfirmImport(turnierId)) }
|
||||
)
|
||||
}
|
||||
|
||||
if (state.showStartlistePreview) {
|
||||
StartlistePreviewDialog(
|
||||
eintraege = state.currentStartliste,
|
||||
onDismiss = { viewModel.send(BewerbIntent.CloseStartlistePreview) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StartlistePreviewDialog(
|
||||
eintraege: List<StartlistenZeile>,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier.width(600.dp).heightIn(max = 500.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text("Startliste Vorschau", style = MaterialTheme.typography.titleLarge)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Box(modifier = Modifier.weight(1f).background(HeaderBg).padding(2.dp)) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item {
|
||||
Row(Modifier.fillMaxWidth().background(Color.LightGray).padding(4.dp)) {
|
||||
Text("Nr", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Zeit", modifier = Modifier.width(60.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Reiter", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Pferd", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
items(eintraege) { e: StartlistenZeile ->
|
||||
Row(Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 2.dp)) {
|
||||
Text(e.nr.toString(), modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
||||
Text(e.zeit, modifier = Modifier.width(60.dp), fontSize = 12.sp)
|
||||
Text(e.reiter, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
||||
Text(e.pferd, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
||||
}
|
||||
HorizontalDivider(color = Color.LightGray.copy(alpha = 0.5f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
Button(onClick = onDismiss) { Text("Schließen") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -197,6 +284,8 @@ private fun BewerbeTableRow(bewerb: BewerbUiModel, isSelected: Boolean, onClick:
|
||||
private fun BewerbeAktionsSpalte(
|
||||
modifier: Modifier = Modifier,
|
||||
onBewerbEinfuegen: () -> Unit = {},
|
||||
onZnsImport: () -> Unit = {},
|
||||
onGenerateStartliste: () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(8.dp),
|
||||
@@ -206,12 +295,14 @@ private fun BewerbeAktionsSpalte(
|
||||
AktionsBtn("Änderungen\nRückgängig")
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
AktionsBtn("Bewerb\nEinfügen", onClick = onBewerbEinfuegen)
|
||||
AktionsBtn("ZNS Import", onClick = onZnsImport)
|
||||
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\nGenerieren", onClick = onGenerateStartliste)
|
||||
AktionsBtn("Startliste\nBearbeiten")
|
||||
AktionsBtn("Startliste\nDrucken")
|
||||
AktionsBtn("Ergebnisliste\nBearbeiten")
|
||||
@@ -230,6 +321,74 @@ private fun AktionsBtn(label: String, onClick: () -> Unit = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ZnsImportPreviewDialog(
|
||||
bewerbe: List<at.mocode.zns.parser.ZnsBewerb>,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier.width(600.dp).heightIn(max = 500.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text("ZNS Bewerbe Import", style = MaterialTheme.typography.titleLarge)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Folgende Bewerbe wurden in der Datei gefunden:", fontSize = 14.sp)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Box(modifier = Modifier.weight(1f).background(HeaderBg).padding(2.dp)) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item {
|
||||
Row(Modifier.fillMaxWidth().background(Color.LightGray).padding(4.dp)) {
|
||||
Text("Nr", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Abt", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Name", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Kl", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
Text("Kat", modifier = Modifier.width(80.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
itemsIndexed(bewerbe) { _, b ->
|
||||
Row(Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 2.dp)) {
|
||||
Text(b.bewerbNummer.toString(), modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
||||
Text(b.abteilung.toString(), modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
||||
Text(b.name, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
||||
Text(b.klasse, modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
||||
Text(b.kategorie, modifier = Modifier.width(80.dp), fontSize = 12.sp)
|
||||
}
|
||||
HorizontalDivider(color = Color.LightGray.copy(alpha = 0.5f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
OutlinedButton(onClick = onDismiss) { Text("Abbrechen") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Button(onClick = onConfirm) { Text("${bewerbe.size} Bewerbe importieren") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfs-Extension
|
||||
private fun BewerbListItem.toUiModel() = BewerbUiModel(
|
||||
tag = tag,
|
||||
platz = platz,
|
||||
nummer = 0, // In der Liste oft 0, da über ID referenziert
|
||||
beginn = "",
|
||||
ende = "",
|
||||
name = name,
|
||||
bezeichnung = "$sparte $klasse",
|
||||
typ = "",
|
||||
zeile1 = "",
|
||||
zns = 1,
|
||||
nennungen = nennungen
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun BewerbAnlegenDialog(
|
||||
state: BewerbAnlegenState,
|
||||
|
||||
+4
-1
@@ -1,5 +1,6 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -98,7 +99,9 @@ fun TurnierDetailScreen(
|
||||
veranstalterLogoUrl = veranstalterLogoUrl,
|
||||
)
|
||||
1 -> OrganisationTabContent()
|
||||
2 -> BewerbeTabContent()
|
||||
2 -> Box(modifier = Modifier.fillMaxSize()) {
|
||||
Text("BEWERBE Tab (Anbindung in Arbeit)", modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
3 -> ArtikelTabContent()
|
||||
4 -> AbrechnungTabContent()
|
||||
5 -> NennungenTabContent()
|
||||
|
||||
Reference in New Issue
Block a user