From e49e7c358ea2c3245d1c1329693dc194d6356513 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 10 Jul 2026 14:50:38 +0200 Subject: [PATCH] =?UTF-8?q?Obs=C5=82u=C5=BC=20wielodniowe=20kursy=20kierow?= =?UTF-8?q?cy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../kierowca/data/model/DriverModels.kt | 1 + .../data/sync/DriverSyncRepository.kt | 38 +++++++--- .../java/pl/firmatpp/kierowca/ui/DriverApp.kt | 43 ++++++++++- .../pl/firmatpp/kierowca/ui/DriverUiRules.kt | 73 ++++++++++++++++--- .../firmatpp/kierowca/ui/DriverViewModel.kt | 46 ++++++++++-- .../kierowca/ui/DriverRouteProjectionTest.kt | 1 + .../firmatpp/kierowca/ui/DriverUiRulesTest.kt | 36 ++++++++- .../firmatpp/kierowca/ui/DriverUiStateTest.kt | 18 ++++- 9 files changed, 224 insertions(+), 36 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5098f50..083b7cc 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 = 102 - versionName = "1.0.49" + versionCode = 103 + versionName = "1.0.50" setProperty("archivesBaseName", "pl.firmatpp.kierowca") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt b/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt index 74933df..fd616a0 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/model/DriverModels.kt @@ -244,6 +244,7 @@ data class RealtimeStatusBody( data class DriverRouteDto( val id: String, val routeDate: String? = null, + val unloadingDate: String? = null, val startsAt: String, val originName: String, val destinationName: String, diff --git a/app/src/main/java/pl/firmatpp/kierowca/data/sync/DriverSyncRepository.kt b/app/src/main/java/pl/firmatpp/kierowca/data/sync/DriverSyncRepository.kt index d917e41..430c21e 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/data/sync/DriverSyncRepository.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/data/sync/DriverSyncRepository.kt @@ -3,6 +3,7 @@ package pl.firmatpp.kierowca.data.sync import android.content.Context import com.google.gson.Gson import java.io.IOException +import java.time.LocalDate import pl.firmatpp.kierowca.data.DriverRepository import pl.firmatpp.kierowca.data.model.BootstrapResponse import pl.firmatpp.kierowca.data.model.DriverRouteDto @@ -72,18 +73,19 @@ class DriverSyncRepository( ), ) - val selectedDate = date ?: return - val cachedBootstrap = dao.bootstrap(selectedDate) ?: return - val response = gson.fromJson(cachedBootstrap.payloadJson, BootstrapResponse::class.java) - val updatedRoutes = response.routes.today.map { item -> if (item.id == route.id) route else item } - dao.upsertBootstrap( - cachedBootstrap.copy( - payloadJson = gson.toJson(response.copy(routes = response.routes.copy(today = updatedRoutes))), - checksum = null, - version = null, - syncedAtEpochMillis = syncedAt, - ), - ) + (routeVisibleDates(route) + listOfNotNull(date)).distinct().forEach { selectedDate -> + val cachedBootstrap = dao.bootstrap(selectedDate) ?: return@forEach + val response = gson.fromJson(cachedBootstrap.payloadJson, BootstrapResponse::class.java) + val updatedRoutes = response.routes.today.map { item -> if (item.id == route.id) route else item } + dao.upsertBootstrap( + cachedBootstrap.copy( + payloadJson = gson.toJson(response.copy(routes = response.routes.copy(today = updatedRoutes))), + checksum = null, + version = null, + syncedAtEpochMillis = syncedAt, + ), + ) + } } suspend fun fetchSyncState(date: String?, routeId: String?): SyncStateResponse = @@ -146,6 +148,18 @@ class DriverSyncRepository( private fun currentDateFallback(): String = java.time.LocalDate.now().toString() + private fun routeVisibleDates(route: DriverRouteDto): List { + val start = route.routeDate?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: return emptyList() + val end = route.unloadingDate + ?.let { runCatching { LocalDate.parse(it) }.getOrNull() } + ?.takeUnless { it.isBefore(start) } + ?: start + + return generateSequence(start) { date -> date.plusDays(1).takeIf { !it.isAfter(end) } } + .map(LocalDate::toString) + .toList() + } + companion object { const val SCOPE_ROUTES = "routes" const val SCOPE_ROUTE_DETAIL = "route_detail" 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 b5a0994..0995081 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt @@ -281,7 +281,7 @@ fun DriverApp( title = "Waga załadunku", stage = "loading", weightLabel = "Waga z wagi", - submitLabel = "Rozpocznij kurs", + submitLabel = "Załaduj", showWeightInput = true, showPhotoActions = false, showPhotoGrid = false, @@ -295,10 +295,10 @@ fun DriverApp( ) DriverScreen.FinishRoute -> RouteStageScreen( state = state, - title = "Zakończ kurs", + title = "Rozładunek", stage = "unloading", weightLabel = "Waga na rozładunku", - submitLabel = "Zakończ kurs", + submitLabel = "Rozładuj", onBack = viewModel::back, onWeightChange = viewModel::updateRouteStageWeight, onUpload = { uri, source, metadata -> viewModel.uploadPhoto(uri, source, metadata, "unloading") }, @@ -2030,6 +2030,8 @@ private fun StitchRouteCard(route: DriverRouteDto, onRoute: (String) -> Unit) { ) } } + Spacer(Modifier.height(12.dp)) + RouteScheduleLabel(route) Spacer(Modifier.height(if (compactWidth) 18.dp else 26.dp)) RouteTimeline(display.origin, display.destination) } @@ -2061,6 +2063,21 @@ private fun StatusChip(status: String, color: Color) { ) } +@Composable +private fun RouteScheduleLabel(route: DriverRouteDto) { + Text( + routeScheduleSummary(route), + modifier = Modifier + .fillMaxWidth() + .background(TppTheme.colors.panel, RoundedCornerShape(4.dp)) + .padding(horizontal = 10.dp, vertical = 8.dp), + color = TppTheme.colors.muted, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.labelMedium, + ) +} + @Composable private fun RouteTimeline(origin: String, destination: String) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(14.dp)) { @@ -2774,9 +2791,19 @@ private fun RouteLifecycleSection( val steps = routeFlowSteps(route, routeActions) val callout = routeSyncCallout(routeActions) val action = routeLifecyclePrimaryAction(route, selectedDate) + val instruction = routeTodayInstruction(route, selectedDate) Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) { RouteFlowStepper(steps) + if (instruction != null) { + RouteStateMessage( + text = instruction, + icon = Icons.Outlined.Info, + container = TppTheme.colors.panel, + outline = TppTheme.colors.outline, + color = TppTheme.colors.ink, + ) + } if (!feedback.isNullOrBlank()) { RouteStateMessage( text = feedback, @@ -2885,6 +2912,15 @@ private fun RouteFlowStepItem(step: RouteFlowStepUi, modifier: Modifier = Modifi maxLines = 2, overflow = TextOverflow.Ellipsis, ) + if (step.dateLabel != null) { + Text( + step.dateLabel, + color = TppTheme.colors.muted, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.labelSmall, + ) + } Text( step.stateLabel, color = color, @@ -2996,6 +3032,7 @@ private fun ManifestSection(route: DriverRouteDto, selectedDate: String, onNavig fontWeight = FontWeight.Bold, color = TppTheme.colors.ink, ) + RouteScheduleLabel(route) } } HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.65f)) diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt index afb41a7..5908c37 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverUiRules.kt @@ -34,6 +34,7 @@ data class RouteFlowStepUi( val title: String, val state: RouteFlowStepState, val stateLabel: String, + val dateLabel: String? = null, ) data class RouteLifecycleActionUi( @@ -49,12 +50,65 @@ data class RouteSyncCalloutUi( fun canManageRoutePhotos(selectedDate: String, today: LocalDate = LocalDate.now()): Boolean = runCatching { LocalDate.parse(selectedDate).isEqual(today) }.getOrDefault(false) +fun routeIsActiveToday( + route: DriverRouteDto, + selectedDate: String, + today: LocalDate = LocalDate.now(), +): Boolean { + val viewedDate = runCatching { LocalDate.parse(selectedDate) }.getOrNull() ?: return false + val loadingDate = route.routeDate?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: return false + val unloadingDate = route.unloadingDate + ?.let { runCatching { LocalDate.parse(it) }.getOrNull() } + ?: loadingDate + + return viewedDate == today && !today.isBefore(loadingDate) && !today.isAfter(unloadingDate) +} + +fun routeUnloadingDate(route: DriverRouteDto): String? = route.unloadingDate ?: route.routeDate + +fun routeScheduleDateLabel(date: String?): String = + date + ?.let { runCatching { LocalDate.parse(it).format(shortDateFormatter) }.getOrNull() } + ?: "—" + +fun routeScheduleSummary(route: DriverRouteDto): String { + val loading = routeScheduleDateLabel(route.routeDate) + val unloading = routeScheduleDateLabel(routeUnloadingDate(route)) + + return if (route.unloadingDate == null) { + "Załadunek: $loading · Rozładunek: ten sam dzień" + } else { + "Załadunek: $loading · Rozładunek: $unloading" + } +} + +fun routeTodayInstruction( + route: DriverRouteDto, + selectedDate: String, + today: LocalDate = LocalDate.now(), +): String? { + if (!routeIsActiveToday(route, selectedDate, today)) return null + + val loadingDate = route.routeDate?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: return null + val unloadingDate = routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: loadingDate + + return when { + route.status == "ZAKOŃCZONA" -> "Kurs zakończony." + loadingDate == unloadingDate -> "Dziś: załadunek i rozładunek." + today == loadingDate -> "Dziś: załadunek." + today == unloadingDate && route.status == "ZAPLANOWANA" -> "Dziś: rozładunek. Załadunek możesz potwierdzić dzisiaj." + today == unloadingDate -> "Dziś: rozładunek." + else -> "Kurs jest w trasie. Rozładunek: ${routeScheduleDateLabel(routeUnloadingDate(route))}." + } +} + fun canCompleteRouteFromDriverApp( route: DriverRouteDto, selectedDate: String, today: LocalDate = LocalDate.now(), ): Boolean = - canManageRoutePhotos(selectedDate, today) + runCatching { LocalDate.parse(selectedDate) }.getOrNull() == today + && routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } == today && route.status in setOf("ZAPLANOWANA", "W TRAKCIE") fun canStartRouteFromDriverApp( @@ -62,7 +116,7 @@ fun canStartRouteFromDriverApp( selectedDate: String, today: LocalDate = LocalDate.now(), ): Boolean = - canManageRoutePhotos(selectedDate, today) + routeIsActiveToday(route, selectedDate, today) && route.status == "ZAPLANOWANA" fun canFinishRouteFromDriverApp( @@ -70,8 +124,9 @@ fun canFinishRouteFromDriverApp( selectedDate: String, today: LocalDate = LocalDate.now(), ): Boolean = - canManageRoutePhotos(selectedDate, today) - && route.status == "W TRAKCIE" + route.status == "W TRAKCIE" + && runCatching { LocalDate.parse(selectedDate) }.getOrNull() == today + && routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } == today fun canSubmitRouteStageForm(weightText: String, serverPhotoCount: Int, localUploadCount: Int): Boolean { return routeStageSubmitBlocker(weightText, serverPhotoCount, localUploadCount) == null @@ -116,8 +171,8 @@ fun routeLifecyclePrimaryAction( today: LocalDate = LocalDate.now(), ): RouteLifecycleActionUi? = when { - canStartRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Rozpocznij kurs", RouteLifecycleAction.Start) - canFinishRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Zakończ kurs", RouteLifecycleAction.Finish) + canStartRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Załaduj", RouteLifecycleAction.Start) + canFinishRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Rozładuj", RouteLifecycleAction.Finish) else -> null } @@ -175,10 +230,10 @@ fun routeFlowSteps(route: DriverRouteDto, actions: List): Lis } return listOf( - RouteFlowStepUi("loading_photos", "Zdjęcia załadunku", loadingPhotosState, routeFlowStateLabel(loadingPhotosState)), - RouteFlowStepUi("loading_weight", "Waga załadunku", loadingWeightState, routeFlowStateLabel(loadingWeightState)), + RouteFlowStepUi("loading_photos", "Zdjęcia załadunku", loadingPhotosState, routeFlowStateLabel(loadingPhotosState), routeScheduleDateLabel(route.routeDate)), + RouteFlowStepUi("loading_weight", "Waga załadunku", loadingWeightState, routeFlowStateLabel(loadingWeightState), routeScheduleDateLabel(route.routeDate)), RouteFlowStepUi("transit", "W trasie", transitState, routeFlowStateLabel(transitState)), - RouteFlowStepUi("unloading", "Rozładunek", unloadingState, routeFlowStateLabel(unloadingState)), + RouteFlowStepUi("unloading", "Rozładunek", unloadingState, routeFlowStateLabel(unloadingState), routeScheduleDateLabel(routeUnloadingDate(route))), ) } diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt index 865351a..82e5931 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt @@ -96,6 +96,7 @@ data class DriverUiState( val displayRoutes: List get() = projectDriverRoutes(routes, projectionActions) + .sortedBy { route -> if (route.status == "ZAKOŃCZONA") 1 else 0 } val selectedRouteProjection: ProjectedDriverRoute? get() = selectedRoute?.let { projectDriverRoute(it, projectionActions) } @@ -634,7 +635,12 @@ class DriverViewModel(application: Application) : AndroidViewModel(application) } fun uploadPhoto(uri: Uri, source: String, metadata: PhotoUploadMetadata = PhotoUploadMetadata(), stage: String = "other") { - val route = _state.value.selectedRoute ?: return + val snapshot = _state.value + val route = snapshot.selectedRoute ?: return + if (!routeIsActiveToday(route, snapshot.selectedDate)) { + _state.update { it.copy(error = "Zdjęcia możesz dodać tylko w zaplanowanym zakresie kursu.") } + return + } viewModelScope.launch { _state.update { it.copy(error = null) } runCatching { photoUploadOutbox.enqueue(route.id, uri, source, metadata, stage) } @@ -645,7 +651,12 @@ class DriverViewModel(application: Application) : AndroidViewModel(application) } fun openStartRoute() { - val route = _state.value.selectedRoute ?: return + val snapshot = _state.value + val route = snapshot.selectedRoute ?: return + if (!canStartRouteFromDriverApp(route, snapshot.selectedDate)) { + _state.update { it.copy(error = "Załadunek nie jest dostępny poza zaplanowanym zakresem kursu.") } + return + } _state.update { it.copy( screen = DriverScreen.StartRoute, @@ -681,7 +692,12 @@ class DriverViewModel(application: Application) : AndroidViewModel(application) } fun openFinishRoute() { - val route = _state.value.selectedRoute ?: return + val snapshot = _state.value + val route = snapshot.selectedRoute ?: return + if (!canFinishRouteFromDriverApp(route, snapshot.selectedDate)) { + _state.update { it.copy(error = "Rozładunek jest dostępny w dniu zaplanowanego rozładunku.") } + return + } _state.update { it.copy( screen = DriverScreen.FinishRoute, @@ -708,6 +724,21 @@ class DriverViewModel(application: Application) : AndroidViewModel(application) private fun submitRouteStage(stage: String) { val snapshot = _state.value val route = snapshot.selectedRoute ?: return + val canSubmitStage = if (stage == "loading") { + canStartRouteFromDriverApp(route, snapshot.selectedDate) + } else { + canFinishRouteFromDriverApp(route, snapshot.selectedDate) + } + if (!canSubmitStage) { + _state.update { + it.copy(error = if (stage == "loading") { + "Załadunek nie jest dostępny poza zaplanowanym zakresem kursu." + } else { + "Rozładunek jest dostępny w dniu zaplanowanego rozładunku." + }) + } + return + } val driver = snapshot.driver val weight = snapshot.routeStageWeightText.trim().replace(',', '.').toDoubleOrNull() val stagePhotos = routePhotosForStage(route.photos, stage) @@ -880,8 +911,13 @@ class DriverViewModel(application: Application) : AndroidViewModel(application) } fun completeSelectedRoute() { - val route = _state.value.selectedRoute ?: return - if (_state.value.completingRoute || !_state.value.isOnline) return + val snapshot = _state.value + val route = snapshot.selectedRoute ?: return + if (snapshot.completingRoute || !snapshot.isOnline) return + if (!canCompleteRouteFromDriverApp(route, snapshot.selectedDate)) { + _state.update { it.copy(error = "Zakończenie kursu jest dostępne w dniu zaplanowanego rozładunku.") } + return + } viewModelScope.launch { _state.update { it.copy(completingRoute = true, feedback = null, error = null) } diff --git a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverRouteProjectionTest.kt b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverRouteProjectionTest.kt index b6bb35d..e0e5b90 100644 --- a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverRouteProjectionTest.kt +++ b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverRouteProjectionTest.kt @@ -154,6 +154,7 @@ class DriverRouteProjectionTest { ): DriverRouteDto = DriverRouteDto( id = "1", + routeDate = "2026-06-30", startsAt = "", originName = "Baza", destinationName = "Instalacja", diff --git a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiRulesTest.kt b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiRulesTest.kt index 578940f..73c10cd 100644 --- a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiRulesTest.kt +++ b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiRulesTest.kt @@ -39,6 +39,32 @@ class DriverUiRulesTest { assertFalse(canCompleteRouteFromDriverApp(route(status = "ZAPLANOWANA"), "2026-06-29", today)) } + @Test + fun multiDayRouteCanLoadTodayButCanUnloadOnlyOnItsUnloadingDate() { + val route = route( + status = "ZAPLANOWANA", + routeDate = "2026-06-29", + unloadingDate = "2026-06-30", + ) + + assertTrue(canStartRouteFromDriverApp(route, "2026-06-30", today)) + assertEquals("Załaduj", routeLifecyclePrimaryAction(route, "2026-06-30", today)?.label) + assertEquals("Dziś: rozładunek. Załadunek możesz potwierdzić dzisiaj.", routeTodayInstruction(route, "2026-06-30", today)) + + val activeRoute = route.copy(status = "W TRAKCIE") + assertTrue(canFinishRouteFromDriverApp(activeRoute, "2026-06-30", today)) + assertEquals("Rozładuj", routeLifecyclePrimaryAction(activeRoute, "2026-06-30", today)?.label) + assertFalse(canFinishRouteFromDriverApp(activeRoute, "2026-06-29", LocalDate.parse("2026-06-29"))) + } + + @Test + fun routeScheduleShowsBothDispatcherDates() { + val route = route(routeDate = "2026-06-30", unloadingDate = "2026-07-02") + + assertEquals("Załadunek: 30.06 · Rozładunek: 02.07", routeScheduleSummary(route)) + assertEquals("Załadunek: 30.06 · Rozładunek: ten sam dzień", routeScheduleSummary(route.copy(unloadingDate = null))) + } + @Test fun showsLiveRouteDayUpdateOnlyForViewedDate() { assertTrue(shouldShowRouteDayLiveUpdate(viewedDate = "2026-03-12", hintDate = "2026-03-12")) @@ -300,7 +326,7 @@ class DriverUiRulesTest { assertEquals(RouteFlowStepState.Todo, steps[1].state) assertEquals(RouteFlowStepState.Todo, steps[2].state) assertEquals(RouteFlowStepState.Todo, steps[3].state) - assertEquals("Rozpocznij kurs", action?.label) + assertEquals("Załaduj", action?.label) assertEquals(RouteLifecycleAction.Start, action?.action) } @@ -327,7 +353,7 @@ class DriverUiRulesTest { assertEquals(RouteFlowStepState.Confirmed, steps[1].state) assertEquals(RouteFlowStepState.LocalComplete, steps[2].state) assertEquals(RouteFlowStepState.Todo, steps[3].state) - assertEquals("Zakończ kurs", action?.label) + assertEquals("Rozładuj", action?.label) assertEquals(RouteLifecycleAction.Finish, action?.action) } @@ -400,13 +426,17 @@ class DriverUiRulesTest { } private fun route( - status: String, + status: String = "ZAPLANOWANA", loadingWeight: Double? = null, unloadingWeight: Double? = null, photos: List = emptyList(), + routeDate: String? = "2026-06-30", + unloadingDate: String? = null, ): DriverRouteDto = DriverRouteDto( id = "1", + routeDate = routeDate, + unloadingDate = unloadingDate, startsAt = "", originName = "Baza", destinationName = "Instalacja", diff --git a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiStateTest.kt b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiStateTest.kt index 85d7806..1f1db00 100644 --- a/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiStateTest.kt +++ b/app/src/test/java/pl/firmatpp/kierowca/ui/DriverUiStateTest.kt @@ -70,6 +70,19 @@ class DriverUiStateTest { assertEquals(12.5, state.displaySelectedRoute?.loadingWeight) } + @Test + fun displayRoutesMovesCompletedRoutesBelowActiveDispatcherOrder() { + val state = DriverUiState( + routes = listOf( + route(id = "completed", status = "ZAKOŃCZONA"), + route(id = "planned", status = "ZAPLANOWANA"), + route(id = "active", status = "W TRAKCIE"), + ), + ) + + assertEquals(listOf("planned", "active", "completed"), state.displayRoutes.map { it.id }) + } + private fun leaveRequest(id: String, status: String): DriverLeaveRequestDto = DriverLeaveRequestDto( id = id, @@ -79,9 +92,10 @@ class DriverUiStateTest { status = status, ) - private fun route(status: String): DriverRouteDto = + private fun route(id: String = "1", status: String): DriverRouteDto = DriverRouteDto( - id = "1", + id = id, + routeDate = "2026-06-30", startsAt = "", originName = "Baza", destinationName = "Instalacja",