diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ccd6072..5098f50 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,8 +34,8 @@ android { applicationId = "pl.firmatpp.kierowca" minSdk = 26 targetSdk = 35 - versionCode = 101 - versionName = "1.0.48" + versionCode = 102 + versionName = "1.0.49" setProperty("archivesBaseName", "pl.firmatpp.kierowca") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt index b9935c5..b5a0994 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt @@ -1053,10 +1053,23 @@ private fun LeaveCalendarScreen( val context = LocalContext.current val listState = rememberLazyListState() val months = remember(state.leaveCalendarLoadedUntil) { leaveCalendarMonths(state.leaveCalendarLoadedUntil) } - var detailsDate by remember { mutableStateOf(null) } - val detailsEntries = detailsDate - ?.let { DriverLeaveRequestUiRules.calendarEntriesForDate(state.leaveCalendarEntries, it) } - .orEmpty() + var showConflictDetails by remember { mutableStateOf(false) } + val selectedConflictEntries = remember( + state.leaveCalendarEntries, + state.leaveCalendarHasSelection, + state.leaveRequestDateFrom, + state.leaveRequestDateTo, + ) { + if (state.leaveCalendarHasSelection) { + DriverLeaveRequestUiRules.calendarEntriesForRange( + entries = state.leaveCalendarEntries, + dateFrom = state.leaveRequestDateFrom, + dateTo = state.leaveRequestDateTo, + ) + } else { + emptyList() + } + } val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) LaunchedEffect(months.size, state.leaveCalendarLoading) { @@ -1074,6 +1087,8 @@ private fun LeaveCalendarScreen( bottomBar = { LeaveCalendarContinueBar( state = state, + conflictCount = selectedConflictEntries.size, + onShowConflicts = { showConflictDetails = true }, onContinue = onContinue, ) }, @@ -1093,11 +1108,8 @@ private fun LeaveCalendarScreen( selectedFrom = state.leaveRequestDateFrom, selectedTo = state.leaveRequestDateTo, hasSelection = state.leaveCalendarHasSelection, - onDayClick = { date, entries -> + onDayClick = { date, _ -> onDateSelected(date.toString()) - if (entries.isNotEmpty()) { - detailsDate = date - } }, ) } @@ -1121,15 +1133,15 @@ private fun LeaveCalendarScreen( } } - if (detailsDate != null && detailsEntries.isNotEmpty()) { + if (showConflictDetails && selectedConflictEntries.isNotEmpty()) { ModalBottomSheet( - onDismissRequest = { detailsDate = null }, + onDismissRequest = { showConflictDetails = false }, sheetState = sheetState, containerColor = TppTheme.colors.card, ) { LeaveCalendarDetailsSheet( - date = detailsDate, - entries = detailsEntries, + title = "Urlopy w wybranym terminie", + entries = selectedConflictEntries, onCall = { dialUri -> context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse(dialUri))) }, @@ -1139,7 +1151,12 @@ private fun LeaveCalendarScreen( } @Composable -private fun LeaveCalendarContinueBar(state: DriverUiState, onContinue: () -> Unit) { +private fun LeaveCalendarContinueBar( + state: DriverUiState, + conflictCount: Int, + onShowConflicts: () -> Unit, + onContinue: () -> Unit, +) { val selectionText = when { !state.leaveCalendarHasSelection -> "Wybierz pierwszy dzień urlopu" else -> leaveDateRange(state.leaveRequestDateFrom, state.leaveRequestDateTo) @@ -1178,6 +1195,28 @@ private fun LeaveCalendarContinueBar(state: DriverUiState, onContinue: () -> Uni Text("Dalej", fontWeight = FontWeight.Bold) } } + if (state.leaveCalendarHasSelection && conflictCount > 0) { + Row( + Modifier + .fillMaxWidth() + .background(TppTheme.colors.warningContainer, RoundedCornerShape(4.dp)) + .border(1.dp, TppTheme.colors.warningOutline, RoundedCornerShape(4.dp)) + .padding(horizontal = 12.dp, vertical = 10.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon(Icons.Outlined.Info, contentDescription = null, tint = Color(0xFF7A5A00), modifier = Modifier.size(20.dp)) + Text( + "Nakłada się z ${leaveConflictCountLabel(conflictCount)}", + color = Color(0xFF7A5A00), + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f), + ) + TextButton(onClick = onShowConflicts) { + Text("Zobacz", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold) + } + } + } } } @@ -1307,7 +1346,7 @@ private fun LeaveCalendarDayCell( @Composable private fun LeaveCalendarDetailsSheet( - date: LocalDate?, + title: String, entries: List, onCall: (String) -> Unit, ) { @@ -1316,7 +1355,7 @@ private fun LeaveCalendarDetailsSheet( verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - date?.let { DateTimeFormatter.ofPattern("d MMMM yyyy", Locale("pl", "PL")).format(it) } ?: "Urlopy w tym dniu", + title, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 22.sp, @@ -2926,6 +2965,9 @@ private fun photoCountLabel(count: Int): String = else -> "zdjęć" } +private fun leaveConflictCountLabel(count: Int): String = + if (count == 1) "1 urlopem" else "$count urlopami" + @Composable private fun ManifestSection(route: DriverRouteDto, selectedDate: String, onNavigate: (NavigationPointDto) -> Unit) { val stripColor = routeStatusColor(route.status) diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRules.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRules.kt index 24d1c4a..e8d47ca 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRules.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRules.kt @@ -61,6 +61,19 @@ object DriverLeaveRequestUiRules { from != null && to != null && !date.isBefore(from) && !date.isAfter(to) } + fun calendarEntriesForRange(entries: List, dateFrom: String, dateTo: String): List { + val selectedFrom = runCatching { LocalDate.parse(dateFrom) }.getOrNull() ?: return emptyList() + val selectedTo = runCatching { LocalDate.parse(dateTo) }.getOrNull() ?: return emptyList() + val from = minOf(selectedFrom, selectedTo) + val to = maxOf(selectedFrom, selectedTo) + + return entries.filter { entry -> + val entryFrom = entry.dateFrom?.let { runCatching { LocalDate.parse(it) }.getOrNull() } + val entryTo = entry.dateTo?.let { runCatching { LocalDate.parse(it) }.getOrNull() } + entryFrom != null && entryTo != null && !entryTo.isBefore(from) && !entryFrom.isAfter(to) + } + } + fun phoneDialUri(phoneNumber: String?): String? { val raw = phoneNumber?.trim().orEmpty() if (raw.isBlank()) return null diff --git a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRulesTest.kt b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRulesTest.kt index d1a656e..30d3be7 100644 --- a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRulesTest.kt +++ b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverLeaveRequestUiRulesTest.kt @@ -64,6 +64,25 @@ class DriverLeaveRequestUiRulesTest { assertEquals(listOf("week", "single-day"), matching.map { it.id }) } + @Test + fun findsCalendarEntriesThatOverlapSelectedRange() { + val entries = listOf( + calendarEntry(id = "starts-before", dateFrom = "2026-08-01", dateTo = "2026-08-04"), + calendarEntry(id = "inside", dateFrom = "2026-08-06", dateTo = "2026-08-07"), + calendarEntry(id = "ends-after", dateFrom = "2026-08-09", dateTo = "2026-08-12"), + calendarEntry(id = "before", dateFrom = "2026-07-20", dateTo = "2026-07-22"), + calendarEntry(id = "after", dateFrom = "2026-08-13", dateTo = "2026-08-14"), + ) + + val matching = DriverLeaveRequestUiRules.calendarEntriesForRange( + entries = entries, + dateFrom = "2026-08-03", + dateTo = "2026-08-10", + ) + + assertEquals(listOf("starts-before", "inside", "ends-after"), matching.map { it.id }) + } + @Test fun normalizesPhoneNumberIntoDialerUri() { assertEquals("tel:+48600700800", DriverLeaveRequestUiRules.phoneDialUri("+48 600 700 800"))