From 71aeb691df7277a3d94a1dded35b97fa3d5258d4 Mon Sep 17 00:00:00 2001 From: marijo Date: Tue, 9 Jun 2026 11:20:11 +0000 Subject: [PATCH] Feriefridage logic improved, mobile styling --- app.js | 35 ++++++++++++++++++++++++++++- style.css | 66 +++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index 124bb72..d837761 100644 --- a/app.js +++ b/app.js @@ -637,13 +637,46 @@ function calculateCurrentSnapshot(settings, entries, corrections = {}, feriefrid if (feriefridageBalance < -0.001) warnings.push({ level: 'bad', text: `Feriefridage balance is negative today: ${days(feriefridageBalance)} days.` }); if (flexBalance < -0.001) warnings.push({ level: 'bad', text: `FLEX balance is negative today: ${hours(flexBalance)}.` }); if (today.getMonth() + 1 <= Number(settings.feriefridage_expiry_month || 8) && feriefridageBalance > 0.001) { - warnings.push({ level: 'warn', text: `${days(feriefridageBalance)} feriefridage must be used before 31 August.` }); + const remainingAfterApprovedPlans = feriefridageRemainingAfterApprovedPlans(settings, entries, today, { + vacationBalance, + feriefridageBalance, + flexBalance, + }); + if (remainingAfterApprovedPlans > 0.001) { + warnings.push({ level: 'warn', text: `${days(remainingAfterApprovedPlans)} feriefridage need approved plan before 31 August.` }); + } } const totalDays = vacationBalance + feriefridageBalance + (flexBalance / avgDayHours); return { vacation: vacationBalance, feriefridage: feriefridageBalance, flex: flexBalance, totalDays, warnings: dedupeWarnings(warnings) }; } +function feriefridageRemainingAfterApprovedPlans(settings, entries, today, balances) { + const expiryMonth = Number(settings.feriefridage_expiry_month || 8); + const deadline = dateFromParts(today.getFullYear(), expiryMonth + 1, 0); + let { vacationBalance, feriefridageBalance, flexBalance } = balances; + + const futureApprovedWorkdays = []; + for (const entry of entries) { + if (entry.status !== 'approved') continue; + for (const date of workdaysBetween(entry.start_date, entry.end_date)) { + if (date > today && date <= deadline) { + futureApprovedWorkdays.push({ date, entry }); + } + } + } + + futureApprovedWorkdays.sort((a, b) => a.date - b.date || a.entry.id - b.entry.id); + for (const item of futureApprovedWorkdays) { + const allocation = allocateDay(item.entry, item.date, { vacationBalance, feriefridageBalance, flexBalance }, settings); + vacationBalance = allocation.balances.vacationBalance; + feriefridageBalance = allocation.balances.feriefridageBalance; + flexBalance = allocation.balances.flexBalance; + } + + return feriefridageBalance; +} + function allocateDay(entry, date, balances, settings) { const source = entry.source; const allowUnpaid = Boolean(Number(entry.allow_unpaid)); diff --git a/style.css b/style.css index 9af3f6f..bcfdee2 100644 --- a/style.css +++ b/style.css @@ -367,6 +367,7 @@ tbody tr:hover { background: rgba(255,255,255,.04); } @media (max-width: 1050px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .header-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); } .two-col { grid-template-columns: 1fr; } .form-grid.compact { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @@ -374,10 +375,11 @@ tbody tr:hover { background: rgba(255,255,255,.04); } @media (max-width: 680px) { body { font-size: 14px; } .floating-header { padding: .25rem .3rem; } - .cards { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .22rem; } + .cards, .header-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .18rem; } .card { padding: .28rem .32rem; min-width: 0; } - .card .label { font-size: .52rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } - .card .value { font-size: .82rem; line-height: 1.05; } + .header-cards .card { flex-direction: column; align-items: center; gap: .02rem; padding: .18rem .16rem; } + .card .label { font-size: .48rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } + .card .value { font-size: .76rem; line-height: 1.05; } .card .hint { display: none; } .header-cards .card { padding: .14rem .2rem; gap: .18rem; } .header-cards .card .label { font-size: .48rem; } @@ -422,20 +424,62 @@ tbody tr:hover { background: rgba(255,255,255,.04); } .header-warning, .header-action-button { min-height: 1.2rem; font-size: .62rem; padding: .14rem .3rem; } main { padding: 0 .3rem 3rem; } - .planner-column-header { margin-top: .1rem; padding-top: .08rem; } - .planner-header-table th { padding: .14rem .24rem; font-size: .54rem; letter-spacing: .01em; line-height: 1; } - .planner-body-table td { padding: .32rem .24rem; } - .month-table { min-width: 600px; } - .month-table th:nth-child(1), .month-table td:nth-child(1) { width: 6.8rem; } + .planner-column-header { display: none; } + .planner-panel .table-wrap { overflow: visible; } + .month-table, + .planner-body-table, + .planner-body-table tbody { display: block; width: 100%; min-width: 0; } + .planner-body-table tr { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: .18rem .28rem; + padding: .42rem 0; + border-bottom: 1px solid var(--line); + } + .planner-body-table td { + display: block; + border-bottom: 0; + padding: 0; + min-width: 0; + } + .planner-body-table td:nth-child(1) { + grid-column: 1 / 2; + font-weight: 900; + font-size: .78rem; + line-height: 1.1; + } + .planner-body-table td:nth-child(2) { + grid-column: 2 / -1; + min-width: 0; + } + .planner-body-table td:nth-child(n+3) { + text-align: left; + } + .planner-body-table td:nth-child(3)::before { content: 'VD'; } + .planner-body-table td:nth-child(4)::before { content: 'FF'; } + .planner-body-table td:nth-child(5)::before { content: 'FLEX'; } + .planner-body-table td:nth-child(6)::before { content: 'Total'; } + .planner-body-table td:nth-child(n+3)::before { + display: block; + color: var(--muted); + font-size: .52rem; + font-weight: 900; + text-transform: uppercase; + letter-spacing: .04em; + margin-bottom: .12rem; + } + .month-table th:nth-child(1), .month-table td:nth-child(1), + .month-table th:nth-child(2), .month-table td:nth-child(2), .month-table th:nth-child(3), .month-table td:nth-child(3), .month-table th:nth-child(4), .month-table td:nth-child(4), .month-table th:nth-child(5), .month-table td:nth-child(5), - .month-table th:nth-child(6), .month-table td:nth-child(6) { width: 5.2rem; } - .month-table td.entries { min-width: 10rem; } + .month-table th:nth-child(6), .month-table td:nth-child(6) { width: auto; } + .month-table td.entries { min-width: 0; } .month-entry { margin: .05rem 0 .18rem; } .planner-entry-meta { display: block; margin-left: 0; font-size: .64rem; } .planner-entry-dates { font-size: .6rem; } - .balance-override-input { width: 4.15rem; padding: .22rem .25rem; font-size: .75rem; } + .balance-override-input { width: 100%; max-width: 4.8rem; padding: .22rem .25rem; font-size: .75rem; } + .important-col { border-radius: 8px; padding: .2rem .25rem !important; } .modal { width: calc(100vw - .8rem); max-height: calc(100dvh - .8rem); padding: .75rem; overflow: auto; } .modal-heading { margin-bottom: .7rem; }