fixed date fields
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 48s

This commit is contained in:
Torsten Schulz (local)
2026-06-10 09:55:50 +02:00
parent 8d1bce2ff9
commit 8ef1f49118
9 changed files with 106 additions and 42 deletions

View File

@@ -1832,7 +1832,7 @@ private fun DiaryListScreen(
selectedNewDateGroupId = null
newDateGroupMenuExpanded = false
val tmpl = diaryState.dates.firstOrNull()
newDiaryDateStr = kotlin.runCatching { java.time.LocalDate.now().toString() }.getOrElse { "" }
newDiaryDateStr = todayIsoDate()
newDiaryStart = diaryTimeForFormField(tmpl?.trainingStart).ifBlank { "17:30" }
newDiaryEnd = diaryTimeForFormField(tmpl?.trainingEnd).ifBlank { "19:30" }
newDiaryExcludeFromBilling = false
@@ -1994,17 +1994,16 @@ private fun DiaryListScreen(
) { Text(tr("diary.applySuggestion", "Vorschlag übernehmen")) }
}
Divider(modifier = Modifier.padding(vertical = 12.dp))
OutlinedTextField(
LocalizedDatePickerField(
value = newDiaryDateStr,
onValueChange = { newDiaryDateStr = it },
label = { Text(tr("diary.date", "Datum (YYYY-MM-DD)")) },
singleLine = true,
label = tr("diary.date", "Datum"),
enabled = !diaryState.isLoading,
modifier = Modifier.fillMaxWidth(),
)
TextButton(
onClick = {
newDiaryDateStr = kotlin.runCatching { java.time.LocalDate.now().toString() }.getOrElse { newDiaryDateStr }
newDiaryDateStr = todayIsoDate()
},
enabled = !diaryState.isLoading,
) { Text(tr("diary.today", "Heute")) }
@@ -6465,11 +6464,11 @@ private fun MemberDetailRoute(
DetailLine(tr("members.street", "Straße"), member.street ?: "-")
DetailLine(tr("members.city", "Ort"), member.city ?: "-")
DetailLine(tr("members.postalCode", "PLZ"), member.postalCode ?: "-")
DetailLine(tr("members.birthDate", "Geburtsdatum"), member.birthDate ?: "-")
DetailLine(tr("members.birthDate", "Geburtsdatum"), formatIsoDateForDisplay(member.birthDate))
DetailLine(tr("members.gender", "Geschlecht"), member.gender ?: "-")
DetailLine("TTR", member.ttr?.toString() ?: "-")
DetailLine("QTTR", member.qttr?.toString() ?: "-")
DetailLine(tr("mobile.lastTraining", "Letztes Training"), member.lastTraining?.take(10) ?: "-")
DetailLine(tr("mobile.lastTraining", "Letztes Training"), formatIsoDateForDisplay(member.lastTraining))
DetailLine(tr("members.trainingParticipations", "Trainings-Teilnahmen"), member.trainingParticipations?.toString() ?: "-")
SectionTitle(tr("members.contact", "Kontakt"))
@@ -6735,7 +6734,7 @@ private fun MemberEditRoute(
OutlinedTextField(street, { street = it }, label = { Text(tr("members.street", "Straße")) }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(postalCode, { postalCode = it }, label = { Text(tr("members.postalCode", "PLZ")) }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(city, { city = it }, label = { Text(tr("members.city", "Ort")) }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(birthDate, { birthDate = it }, label = { Text(tr("members.birthDate", "Geburtsdatum")) }, modifier = Modifier.fillMaxWidth(), singleLine = true)
LocalizedDatePickerField(birthDate, { birthDate = it }, label = tr("members.birthDate", "Geburtsdatum"), modifier = Modifier.fillMaxWidth())
Box {
OutlinedButton(onClick = { genderMenu = true }, modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) {
Text("${tr("members.gender", "Geschlecht")}: $gender")
@@ -7308,7 +7307,7 @@ private fun DiaryEditForm(
Card(modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), elevation = 2.dp) {
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = date, onValueChange = { date = it }, label = { Text("Datum (YYYY-MM-DD)") }, singleLine = true)
LocalizedDatePickerField(value = date, onValueChange = { date = it }, label = "Datum")
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = start, onValueChange = { start = it }, label = { Text("Start") }, modifier = Modifier.weight(1f), singleLine = true)
OutlinedTextField(value = end, onValueChange = { end = it }, label = { Text("Ende") }, modifier = Modifier.weight(1f), singleLine = true)
@@ -7335,7 +7334,7 @@ private fun DiaryEditForm(
ErrorText(error)
Button(onClick = {
if (!date.matches(Regex("\\d{4}-\\d{2}-\\d{2}"))) {
error = "Datum im Format YYYY-MM-DD eingeben"
error = "Bitte Datum auswählen"
return@Button
}
if (start.isNotBlank() && end.isNotBlank() && start >= end) {
@@ -8070,7 +8069,7 @@ private fun DiaryQuickAddMemberDialog(
error?.let { Text(it, color = MaterialTheme.colors.error) }
OutlinedTextField(firstName, { firstName = it }, label = { Text(tr("members.firstName", "Vorname")) }, singleLine = true)
OutlinedTextField(lastName, { lastName = it }, label = { Text(tr("members.lastName", "Nachname")) }, singleLine = true)
OutlinedTextField(birthDate, { birthDate = it }, label = { Text(tr("members.birthDate", "Geburtsdatum")) }, singleLine = true)
LocalizedDatePickerField(birthDate, { birthDate = it }, label = tr("members.birthDate", "Geburtsdatum"))
DrawingChoiceRowForDiaryGender(gender) { gender = it }
}
},
@@ -8694,7 +8693,7 @@ private fun buildMembersCsvExport(rows: List<Member>): String {
private fun Member.fullName(): String = listOf(firstName, lastName).filter { it.isNotBlank() }.joinToString(" ").ifBlank { "Mitglied $id" }
private fun formatDate(value: String): String = value.take(10)
private fun formatDate(value: String): String = formatIsoDateForDisplay(value, fallback = value.take(10))
private fun formatTimeRange(start: String?, end: String?): String {
val parts = listOfNotNull(start?.takeIf { it.isNotBlank() }, end?.takeIf { it.isNotBlank() })

View File

@@ -845,13 +845,12 @@ private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit)
Switch(checked = omitLocation, onCheckedChange = { omitLocation = it }, enabled = canWrite)
Text(tr("billing.omitField", "Feld auslassen"), modifier = Modifier.padding(start = 8.dp))
}
OutlinedTextField(
LocalizedDatePickerField(
value = documentDate,
onValueChange = { documentDate = it },
label = { Text(tr("billing.documentDate", "Datum Dokument")) },
label = tr("billing.documentDate", "Datum Dokument"),
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
enabled = canWrite && !omitDocDate,
singleLine = true,
)
Row(verticalAlignment = Alignment.CenterVertically) {
Switch(checked = omitDocDate, onCheckedChange = { omitDocDate = it }, enabled = canWrite)

View File

@@ -368,19 +368,18 @@ fun CalendarScreen(
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
)
OutlinedTextField(
LocalizedDatePickerField(
value = cancelStart,
onValueChange = { cancelStart = it },
label = { Text(tr("mobile.calendarCancellationStart", "Datum (YYYY-MM-DD)")) },
label = tr("mobile.calendarCancellationStart", "Datum"),
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
singleLine = true,
)
OutlinedTextField(
LocalizedDatePickerField(
value = cancelEnd,
onValueChange = { cancelEnd = it },
label = { Text(tr("mobile.calendarCancellationEnd", "Bis optional")) },
label = tr("mobile.calendarCancellationEnd", "Bis"),
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
singleLine = true,
placeholder = tr("mobile.calendarCancellationEnd", "Bis optional"),
)
OutlinedTextField(
value = cancelReason,
@@ -428,7 +427,7 @@ fun CalendarScreen(
verticalAlignment = Alignment.CenterVertically,
) {
Column(Modifier.weight(1f)) {
Text("${c.date}${c.endDate}", style = MaterialTheme.typography.caption)
Text("${formatIsoDateForDisplay(c.dateIso)}${formatIsoDateForDisplay(c.endDateIso)}", style = MaterialTheme.typography.caption)
Text(c.title, style = MaterialTheme.typography.body2, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
TextButton(

View File

@@ -0,0 +1,67 @@
package de.tsschulz.tt_tagebuch.app.ui
import android.app.DatePickerDialog
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.util.Locale
private val UiIsoDateFormatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
private val UiGermanDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY)
internal fun parseIsoDateForUi(value: String?): LocalDate? {
val raw = value?.trim()?.take(10)?.takeIf { it.isNotBlank() } ?: return null
return try {
LocalDate.parse(raw, UiIsoDateFormatter)
} catch (_: DateTimeParseException) {
null
}
}
internal fun formatIsoDateForDisplay(value: String?, fallback: String = "-"): String {
if (value.isNullOrBlank()) return fallback
return parseIsoDateForUi(value)?.format(UiGermanDateFormatter) ?: value.trim().takeIf { it.isNotBlank() } ?: fallback
}
internal fun todayIsoDate(): String = LocalDate.now().format(UiIsoDateFormatter)
@Composable
internal fun LocalizedDatePickerField(
value: String,
onValueChange: (String) -> Unit,
label: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
placeholder: String = "Datum wählen",
) {
val context = LocalContext.current
val selected = remember(value) { parseIsoDateForUi(value) }
val initial = selected ?: LocalDate.now()
OutlinedButton(
onClick = {
DatePickerDialog(
context,
{ _, year, month, dayOfMonth ->
onValueChange(LocalDate.of(year, month + 1, dayOfMonth).format(UiIsoDateFormatter))
},
initial.year,
initial.monthValue - 1,
initial.dayOfMonth,
).show()
},
enabled = enabled,
modifier = modifier.fillMaxWidth().heightIn(min = 48.dp),
) {
val display = selected?.format(UiGermanDateFormatter) ?: value.ifBlank { placeholder }
Text("$label: $display", maxLines = 2)
}
}

View File

@@ -367,12 +367,14 @@ private fun TournamentEditorMetaTab(
modifier = Modifier.fillMaxWidth().onFocusChanged { fs: FocusState -> if (!fs.isFocused) saveIfChanged(name, date, winningSets, tables, doubles, lastSaved, onSave) },
singleLine = true,
)
OutlinedTextField(
LocalizedDatePickerField(
value = date,
onValueChange = { date = it },
modifier = Modifier.fillMaxWidth().onFocusChanged { fs: FocusState -> if (!fs.isFocused) saveIfChanged(name, date, winningSets, tables, doubles, lastSaved, onSave) },
label = { Text(tr("tournaments.date", "Datum")) },
singleLine = true,
onValueChange = {
date = it
saveIfChanged(name, it, winningSets, tables, doubles, lastSaved, onSave)
},
label = tr("tournaments.date", "Datum"),
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = winningSets,

View File

@@ -1026,7 +1026,7 @@ private fun HistoryTable(rows: List<ClubHistoryRow>, tr: (String, String) -> Str
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Text(r.date ?: "", Modifier.widthIn(72.dp), style = MaterialTheme.typography.body2)
Text(formatIsoDateForDisplay(r.date), Modifier.widthIn(72.dp), style = MaterialTheme.typography.body2)
Text(r.placement ?: "", Modifier.widthIn(52.dp), style = MaterialTheme.typography.body2)
}
Divider(color = OfficialPanelBorder.copy(alpha = 0.6f))
@@ -1324,9 +1324,9 @@ private fun CompetitionsTabContent(
}
if (exp) {
Column(Modifier.padding(start = 32.dp, bottom = 8.dp)) {
Text("${tr("officialTournaments.deadlineDate", "Meldeschluss")}: ${c.registrationDeadlineDate ?: c.meldeschlussDatum ?: ""}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.deadlineOnline", "Online bis")}: ${c.registrationDeadlineOnline ?: c.meldeschlussOnline ?: ""}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.cutoffDate", "Stichtag")}: ${c.cutoffDate ?: c.stichtag ?: ""}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.deadlineDate", "Meldeschluss")}: ${formatIsoDateForDisplay(c.registrationDeadlineDate ?: c.meldeschlussDatum)}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.deadlineOnline", "Online bis")}: ${formatIsoDateForDisplay(c.registrationDeadlineOnline ?: c.meldeschlussOnline)}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.cutoffDate", "Stichtag")}: ${formatIsoDateForDisplay(c.cutoffDate ?: c.stichtag)}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.openTo", "Offen für")}: ${c.openTo ?: c.offenFuer ?: ""}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.preliminaryRound", "Vorrunde")}: ${c.preliminaryRound ?: c.vorrunde ?: ""}", style = MaterialTheme.typography.caption)
Text("${tr("officialTournaments.finalRound", "Endrunde")}: ${c.finalRound ?: c.endrunde ?: ""}", style = MaterialTheme.typography.caption)
@@ -1571,7 +1571,7 @@ private fun MemberSelectionDialog(
)
Column {
Text(cr.name, style = MaterialTheme.typography.caption)
Text("${cr.date} ${cr.time} · ${cr.entryFee}", style = MaterialTheme.typography.caption)
Text("${formatIsoDateForDisplay(cr.date)} ${cr.time} · ${cr.entryFee}", style = MaterialTheme.typography.caption)
}
}
}

View File

@@ -638,7 +638,7 @@ private fun FriendlyMatchEditDialog(
onDelete: (() -> Unit)?,
) {
LaunchedEffect(Unit) { onLoadMembers() }
var date by remember(match?.id) { mutableStateOf(match?.date?.take(10) ?: java.time.LocalDate.now().toString()) }
var date by remember(match?.id) { mutableStateOf(match?.date?.take(10) ?: todayIsoDate()) }
var time by remember(match?.id) { mutableStateOf(match?.time?.take(5) ?: "") }
var homeTeam by remember(match?.id) { mutableStateOf(match?.homeTeam?.name ?: clubName) }
var guestTeam by remember(match?.id) { mutableStateOf(match?.guestTeam?.name ?: "") }
@@ -668,7 +668,7 @@ private fun FriendlyMatchEditDialog(
text = {
Column(Modifier.verticalScroll(rememberScrollState())) {
error?.let { Text(it, color = MaterialTheme.colors.error) }
OutlinedTextField(date, { date = it }, label = { Text("Datum") }, enabled = !readonly, modifier = Modifier.fillMaxWidth())
LocalizedDatePickerField(date, { date = it }, label = "Datum", enabled = !readonly, modifier = Modifier.fillMaxWidth())
OutlinedTextField(time, { time = it }, label = { Text("Uhrzeit") }, enabled = !readonly, modifier = Modifier.fillMaxWidth())
OutlinedTextField(homeTeam, { homeTeam = it }, label = { Text("Heimteam") }, enabled = !readonly, modifier = Modifier.fillMaxWidth())
OutlinedTextField(guestTeam, { guestTeam = it }, label = { Text("Gastteam") }, enabled = !readonly, modifier = Modifier.fillMaxWidth())

View File

@@ -436,7 +436,7 @@ private fun TournamentCreateStandardScreen(
onCreated: (Int) -> Unit,
) {
var name by remember { mutableStateOf("") }
var date by rememberSaveable { mutableStateOf("") }
var date by rememberSaveable { mutableStateOf(todayIsoDate()) }
var ws by remember { mutableStateOf("3") }
var error by remember { mutableStateOf<String?>(null) }
var busy by remember { mutableStateOf(false) }
@@ -462,12 +462,11 @@ private fun TournamentCreateStandardScreen(
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
OutlinedTextField(
LocalizedDatePickerField(
value = date,
onValueChange = { date = it },
label = { Text(tr("tournaments.date", "Datum (YYYY-MM-DD)")) },
label = tr("tournaments.date", "Datum"),
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
OutlinedTextField(
value = ws,
@@ -520,7 +519,7 @@ private fun TournamentCreateMiniScreen(
onCreated: (Int) -> Unit,
) {
var ort by remember { mutableStateOf("") }
var date by rememberSaveable { mutableStateOf("") }
var date by rememberSaveable { mutableStateOf(todayIsoDate()) }
var year by remember { mutableStateOf(Calendar.getInstance().get(Calendar.YEAR).toString()) }
var ws by remember { mutableStateOf("1") }
var error by remember { mutableStateOf<String?>(null) }
@@ -547,12 +546,11 @@ private fun TournamentCreateMiniScreen(
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
OutlinedTextField(
LocalizedDatePickerField(
value = date,
onValueChange = { date = it },
label = { Text(tr("tournaments.date", "Datum")) },
label = tr("tournaments.date", "Datum"),
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
OutlinedTextField(
value = year,