Compare commits

...
Author SHA1 Message Date
admin 802393962e Wydaj aplikację kierowcy 1.0.75 (128) 2026-07-30 02:04:03 +02:00
admin ac31308443 Ukrywaj wybrane salda w aplikacji kierowcy 2026-07-30 02:01:46 +02:00
6 changed files with 121 additions and 81 deletions
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId = "pl.firmatpp.kierowca"
minSdk = 26
targetSdk = 36
versionCode = 127
versionName = "1.0.74"
versionCode = 128
versionName = "1.0.75"
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -88,6 +88,13 @@ data class DriverAppSettingsDto(
data class LeaveRequestsConfigDto(
val enabled: Boolean = false,
val types: List<String> = listOf("URLOP"),
val usagePoolCodes: List<String> = listOf(
"ANNUAL",
"ANNUAL_DISABILITY",
"FORCE_MAJEURE",
"CHILD_CARE_14",
"CARE_LEAVE",
),
)
data class DispatchSheetReminderDto(
@@ -252,6 +259,13 @@ data class DriverLeaveSummaryDto(
val dailyNormMinutes: Int = 480,
val balanceConfigured: Boolean = false,
val overdueQuantity: Int = 0,
val visibleUsagePoolCodes: List<String> = listOf(
"ANNUAL",
"ANNUAL_DISABILITY",
"FORCE_MAJEURE",
"CHILD_CARE_14",
"CARE_LEAVE",
),
val availableTypes: List<LeaveRequestTypeDto> = emptyList(),
val reminders: List<DriverLeaveReminderDto> = emptyList(),
)
@@ -1547,6 +1547,12 @@ private fun LeaveBalanceOverview(state: DriverUiState) {
val summary = state.leaveSummary
val totals = DriverLeaveRequestUiRules.annualTotals(summary)
val norm = summary?.dailyNormMinutes ?: 480
val showAnnualUsage = DriverLeaveRequestUiRules.isUsageVisible(summary, "ANNUAL")
val otherPools = DriverLeaveRequestUiRules.otherEntitlementPools(summary)
if (summary != null && !showAnnualUsage && otherPools.isEmpty()) {
return
}
Card(
colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
@@ -1555,99 +1561,107 @@ private fun LeaveBalanceOverview(state: DriverUiState) {
modifier = Modifier.fillMaxWidth(),
) {
Column(Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
Text("Twój urlop wypoczynkowy", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 20.sp)
Text(
if (showAnnualUsage) "Twój urlop wypoczynkowy" else "Twoje limity nieobecności",
color = TppTheme.colors.ink,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
)
when {
summary == null -> {
Text("Pobieramy aktualne saldo…", color = TppTheme.colors.muted)
}
!summary.balanceConfigured -> {
Text(
"Kadry przygotowują Twoje saldo",
color = Color(0xFF7A5A00),
fontWeight = FontWeight.Bold,
)
Text(
"Możesz zapisać wniosek, ale jego godziny zostaną odjęte dopiero po potwierdzeniu puli urlopu.",
color = TppTheme.colors.muted,
)
}
else -> {
Text("Pozostało do wykorzystania", color = TppTheme.colors.muted, fontWeight = FontWeight.SemiBold)
Text(
DriverLeaveRequestUiRules.formatQuantity(totals.availableMinutes, "minutes", norm),
color = TppTheme.colors.forest,
fontWeight = FontWeight.Bold,
fontSize = 26.sp,
)
val denominator = (totals.grantedMinutes).coerceAtLeast(1)
LinearProgressIndicator(
progress = { (totals.usedMinutes.toFloat() / denominator).coerceIn(0f, 1f) },
modifier = Modifier.fillMaxWidth().height(7.dp),
color = TppTheme.colors.forest,
trackColor = TppTheme.colors.successContainer,
)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
LeaveBalanceStat(
title = "Wykorzystano",
value = DriverLeaveRequestUiRules.formatQuantity(totals.usedMinutes, "minutes", norm),
modifier = Modifier.weight(1f),
)
LeaveBalanceStat(
title = "W przyszłych urlopach",
value = DriverLeaveRequestUiRules.formatQuantity(totals.reservedMinutes, "minutes", norm),
modifier = Modifier.weight(1f),
)
if (showAnnualUsage) {
when {
summary == null -> {
Text("Pobieramy aktualne saldo…", color = TppTheme.colors.muted)
}
}
}
if (totals.overdueMinutes > 0) {
Row(
Modifier
.fillMaxWidth()
.background(TppTheme.colors.warningContainer, RoundedCornerShape(6.dp))
.border(1.dp, TppTheme.colors.warningOutline, RoundedCornerShape(6.dp))
.padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.Top,
) {
Icon(Icons.Outlined.Info, contentDescription = null, tint = Color(0xFF7A5A00))
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text("Masz zaległy urlop", color = Color(0xFF7A5A00), fontWeight = FontWeight.Bold)
!summary.balanceConfigured -> {
Text(
"${DriverLeaveRequestUiRules.formatQuantity(totals.overdueMinutes, "minutes", norm)} zaplanuj możliwie szybko, najpóźniej do 30 września. Saldo nie zniknie automatycznie.",
"Kadry przygotowują Twoje saldo",
color = Color(0xFF7A5A00),
fontSize = 13.sp,
fontWeight = FontWeight.Bold,
)
Text(
"Możesz zapisać wniosek, ale jego godziny zostaną odjęte dopiero po potwierdzeniu puli urlopu.",
color = TppTheme.colors.muted,
)
}
else -> {
Text("Pozostało do wykorzystania", color = TppTheme.colors.muted, fontWeight = FontWeight.SemiBold)
Text(
DriverLeaveRequestUiRules.formatQuantity(totals.availableMinutes, "minutes", norm),
color = TppTheme.colors.forest,
fontWeight = FontWeight.Bold,
fontSize = 26.sp,
)
val denominator = (totals.grantedMinutes).coerceAtLeast(1)
LinearProgressIndicator(
progress = { (totals.usedMinutes.toFloat() / denominator).coerceIn(0f, 1f) },
modifier = Modifier.fillMaxWidth().height(7.dp),
color = TppTheme.colors.forest,
trackColor = TppTheme.colors.successContainer,
)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
LeaveBalanceStat(
title = "Wykorzystano",
value = DriverLeaveRequestUiRules.formatQuantity(totals.usedMinutes, "minutes", norm),
modifier = Modifier.weight(1f),
)
LeaveBalanceStat(
title = "W przyszłych urlopach",
value = DriverLeaveRequestUiRules.formatQuantity(totals.reservedMinutes, "minutes", norm),
modifier = Modifier.weight(1f),
)
}
}
}
if (totals.overdueMinutes > 0) {
Row(
Modifier
.fillMaxWidth()
.background(TppTheme.colors.warningContainer, RoundedCornerShape(6.dp))
.border(1.dp, TppTheme.colors.warningOutline, RoundedCornerShape(6.dp))
.padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.Top,
) {
Icon(Icons.Outlined.Info, contentDescription = null, tint = Color(0xFF7A5A00))
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text("Masz zaległy urlop", color = Color(0xFF7A5A00), fontWeight = FontWeight.Bold)
Text(
"${DriverLeaveRequestUiRules.formatQuantity(totals.overdueMinutes, "minutes", norm)} zaplanuj możliwie szybko, najpóźniej do 30 września. Saldo nie zniknie automatycznie.",
color = Color(0xFF7A5A00),
fontSize = 13.sp,
)
}
}
}
DriverLeaveRequestUiRules.annualPools(summary).forEach { pool ->
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
if (pool.year < LocalDate.now().year) "Urlop zaległy z ${pool.year} r." else "Pula na ${pool.year} r.",
color = TppTheme.colors.muted,
)
Text(
DriverLeaveRequestUiRules.formatQuantity(pool.availableQuantity, pool.unit, norm),
color = TppTheme.colors.ink,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.End,
)
}
}
}
DriverLeaveRequestUiRules.annualPools(summary).forEach { pool ->
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
if (otherPools.isNotEmpty()) {
if (showAnnualUsage) {
HorizontalDivider(color = TppTheme.colors.outline)
Text(
if (pool.year < LocalDate.now().year) "Urlop zaległy z ${pool.year} r." else "Pula na ${pool.year} r.",
color = TppTheme.colors.muted,
)
Text(
DriverLeaveRequestUiRules.formatQuantity(pool.availableQuantity, pool.unit, norm),
"Pozostałe dostępne limity",
color = TppTheme.colors.ink,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.End,
fontSize = 17.sp,
)
}
}
val otherPools = DriverLeaveRequestUiRules.otherEntitlementPools(summary)
if (otherPools.isNotEmpty()) {
HorizontalDivider(color = TppTheme.colors.outline)
Text(
"Pozostałe dostępne limity",
color = TppTheme.colors.ink,
fontWeight = FontWeight.Bold,
fontSize = 17.sp,
)
Text(
"Te limity są rozliczane osobno i nie zmniejszają zwykłego urlopu wypoczynkowego.",
color = TppTheme.colors.muted,
@@ -106,6 +106,9 @@ object DriverLeaveRequestUiRules {
.filter { it.poolCode == "ANNUAL" && it.status == "active" }
.sortedByDescending { it.year }
fun isUsageVisible(summary: DriverLeaveSummaryDto?, poolCode: String): Boolean =
summary?.visibleUsagePoolCodes?.contains(poolCode) ?: true
fun otherEntitlementPools(summary: DriverLeaveSummaryDto?): List<DriverLeavePoolDto> =
summary?.pools.orEmpty()
.filter { it.poolCode != "ANNUAL" && it.status == "active" }
@@ -19,6 +19,7 @@ class DriverLeaveApiContractTest {
"dailyNormMinutes": 480,
"balanceConfigured": true,
"overdueQuantity": 480,
"visibleUsagePoolCodes": ["ANNUAL", "CHILD_CARE_14"],
"pools": [{
"id": 7,
"poolCode": "ANNUAL",
@@ -49,6 +50,7 @@ class DriverLeaveApiContractTest {
)
assertEquals(480, response.data.dailyNormMinutes)
assertEquals(listOf("ANNUAL", "CHILD_CARE_14"), response.data.visibleUsagePoolCodes)
assertEquals(7680, response.data.pools.single().availableQuantity)
assertEquals("URLOP", response.data.availableTypes.single().value)
assertEquals("ANNUAL", response.data.availableTypes.single().code)
@@ -124,6 +124,13 @@ class DriverLeaveRequestUiRulesTest {
assertEquals(listOf("CHILD_CARE_14", "CARE_LEAVE"), pools.map { it.poolCode })
assertEquals("Urlop opiekuńczy", DriverLeaveRequestUiRules.poolLabel("CARE_LEAVE"))
assertEquals("Rodzina i opieka", DriverLeaveRequestUiRules.categoryLabel("family"))
assertTrue(DriverLeaveRequestUiRules.isUsageVisible(summary, "ANNUAL"))
assertFalse(
DriverLeaveRequestUiRules.isUsageVisible(
summary.copy(visibleUsagePoolCodes = emptyList()),
"ANNUAL",
),
)
}
@Test