Usprawnij wybór urlopu przy konfliktach
This commit is contained in:
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId = "pl.firmatpp.kierowca"
|
applicationId = "pl.firmatpp.kierowca"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 101
|
versionCode = 102
|
||||||
versionName = "1.0.48"
|
versionName = "1.0.49"
|
||||||
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -1053,10 +1053,23 @@ private fun LeaveCalendarScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val months = remember(state.leaveCalendarLoadedUntil) { leaveCalendarMonths(state.leaveCalendarLoadedUntil) }
|
val months = remember(state.leaveCalendarLoadedUntil) { leaveCalendarMonths(state.leaveCalendarLoadedUntil) }
|
||||||
var detailsDate by remember { mutableStateOf<LocalDate?>(null) }
|
var showConflictDetails by remember { mutableStateOf(false) }
|
||||||
val detailsEntries = detailsDate
|
val selectedConflictEntries = remember(
|
||||||
?.let { DriverLeaveRequestUiRules.calendarEntriesForDate(state.leaveCalendarEntries, it) }
|
state.leaveCalendarEntries,
|
||||||
.orEmpty()
|
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)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
|
||||||
LaunchedEffect(months.size, state.leaveCalendarLoading) {
|
LaunchedEffect(months.size, state.leaveCalendarLoading) {
|
||||||
@@ -1074,6 +1087,8 @@ private fun LeaveCalendarScreen(
|
|||||||
bottomBar = {
|
bottomBar = {
|
||||||
LeaveCalendarContinueBar(
|
LeaveCalendarContinueBar(
|
||||||
state = state,
|
state = state,
|
||||||
|
conflictCount = selectedConflictEntries.size,
|
||||||
|
onShowConflicts = { showConflictDetails = true },
|
||||||
onContinue = onContinue,
|
onContinue = onContinue,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -1093,11 +1108,8 @@ private fun LeaveCalendarScreen(
|
|||||||
selectedFrom = state.leaveRequestDateFrom,
|
selectedFrom = state.leaveRequestDateFrom,
|
||||||
selectedTo = state.leaveRequestDateTo,
|
selectedTo = state.leaveRequestDateTo,
|
||||||
hasSelection = state.leaveCalendarHasSelection,
|
hasSelection = state.leaveCalendarHasSelection,
|
||||||
onDayClick = { date, entries ->
|
onDayClick = { date, _ ->
|
||||||
onDateSelected(date.toString())
|
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(
|
ModalBottomSheet(
|
||||||
onDismissRequest = { detailsDate = null },
|
onDismissRequest = { showConflictDetails = false },
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
containerColor = TppTheme.colors.card,
|
containerColor = TppTheme.colors.card,
|
||||||
) {
|
) {
|
||||||
LeaveCalendarDetailsSheet(
|
LeaveCalendarDetailsSheet(
|
||||||
date = detailsDate,
|
title = "Urlopy w wybranym terminie",
|
||||||
entries = detailsEntries,
|
entries = selectedConflictEntries,
|
||||||
onCall = { dialUri ->
|
onCall = { dialUri ->
|
||||||
context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse(dialUri)))
|
context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse(dialUri)))
|
||||||
},
|
},
|
||||||
@@ -1139,7 +1151,12 @@ private fun LeaveCalendarScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LeaveCalendarContinueBar(state: DriverUiState, onContinue: () -> Unit) {
|
private fun LeaveCalendarContinueBar(
|
||||||
|
state: DriverUiState,
|
||||||
|
conflictCount: Int,
|
||||||
|
onShowConflicts: () -> Unit,
|
||||||
|
onContinue: () -> Unit,
|
||||||
|
) {
|
||||||
val selectionText = when {
|
val selectionText = when {
|
||||||
!state.leaveCalendarHasSelection -> "Wybierz pierwszy dzień urlopu"
|
!state.leaveCalendarHasSelection -> "Wybierz pierwszy dzień urlopu"
|
||||||
else -> leaveDateRange(state.leaveRequestDateFrom, state.leaveRequestDateTo)
|
else -> leaveDateRange(state.leaveRequestDateFrom, state.leaveRequestDateTo)
|
||||||
@@ -1178,6 +1195,28 @@ private fun LeaveCalendarContinueBar(state: DriverUiState, onContinue: () -> Uni
|
|||||||
Text("Dalej", fontWeight = FontWeight.Bold)
|
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
|
@Composable
|
||||||
private fun LeaveCalendarDetailsSheet(
|
private fun LeaveCalendarDetailsSheet(
|
||||||
date: LocalDate?,
|
title: String,
|
||||||
entries: List<DriverLeaveCalendarEntryDto>,
|
entries: List<DriverLeaveCalendarEntryDto>,
|
||||||
onCall: (String) -> Unit,
|
onCall: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -1316,7 +1355,7 @@ private fun LeaveCalendarDetailsSheet(
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
date?.let { DateTimeFormatter.ofPattern("d MMMM yyyy", Locale("pl", "PL")).format(it) } ?: "Urlopy w tym dniu",
|
title,
|
||||||
color = TppTheme.colors.ink,
|
color = TppTheme.colors.ink,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
@@ -2926,6 +2965,9 @@ private fun photoCountLabel(count: Int): String =
|
|||||||
else -> "zdjęć"
|
else -> "zdjęć"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun leaveConflictCountLabel(count: Int): String =
|
||||||
|
if (count == 1) "1 urlopem" else "$count urlopami"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ManifestSection(route: DriverRouteDto, selectedDate: String, onNavigate: (NavigationPointDto) -> Unit) {
|
private fun ManifestSection(route: DriverRouteDto, selectedDate: String, onNavigate: (NavigationPointDto) -> Unit) {
|
||||||
val stripColor = routeStatusColor(route.status)
|
val stripColor = routeStatusColor(route.status)
|
||||||
|
|||||||
@@ -61,6 +61,19 @@ object DriverLeaveRequestUiRules {
|
|||||||
from != null && to != null && !date.isBefore(from) && !date.isAfter(to)
|
from != null && to != null && !date.isBefore(from) && !date.isAfter(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun calendarEntriesForRange(entries: List<DriverLeaveCalendarEntryDto>, dateFrom: String, dateTo: String): List<DriverLeaveCalendarEntryDto> {
|
||||||
|
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? {
|
fun phoneDialUri(phoneNumber: String?): String? {
|
||||||
val raw = phoneNumber?.trim().orEmpty()
|
val raw = phoneNumber?.trim().orEmpty()
|
||||||
if (raw.isBlank()) return null
|
if (raw.isBlank()) return null
|
||||||
|
|||||||
@@ -64,6 +64,25 @@ class DriverLeaveRequestUiRulesTest {
|
|||||||
assertEquals(listOf("week", "single-day"), matching.map { it.id })
|
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
|
@Test
|
||||||
fun normalizesPhoneNumberIntoDialerUri() {
|
fun normalizesPhoneNumberIntoDialerUri() {
|
||||||
assertEquals("tel:+48600700800", DriverLeaveRequestUiRules.phoneDialUri("+48 600 700 800"))
|
assertEquals("tel:+48600700800", DriverLeaveRequestUiRules.phoneDialUri("+48 600 700 800"))
|
||||||
|
|||||||
Reference in New Issue
Block a user