This commit is contained in:
Binary file not shown.
@@ -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() })
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user