Mobile compatibility, and removed unnecessary settings

This commit is contained in:
marijo 2026-06-09 11:04:13 +00:00
parent 098844ef7b
commit 57463e3865
4 changed files with 88 additions and 29 deletions

View file

@ -18,9 +18,8 @@ The SQLite database is created automatically in `data/go.sqlite`.
- Feriefridage grant yearly, default `5` days/year.
- Feriefridage must be used before `31 August` and expire before September planning.
- FLEX days use `7.5h` Monday-Thursday and `7h` Friday.
- Start balances are configured separately from current FLEX hours.
- The planner shows configurable numbers of previous and future months around the current month.
- Monthly vacation-day corrections can be entered directly in the planner table, e.g. `+1.2` or `-2.6` days.
- Monthly vacation, feriefridage, and FLEX balances can be corrected directly in the planner table.
- Each date-range entry can individually allow unpaid fallback so only that range is converted to unpaid days if balances are insufficient.
## Data

22
app.js
View file

@ -10,8 +10,7 @@ const state = {
const settingKeys = [
'previous_months', 'future_months',
'vacation_days_per_month', 'feriefridage_per_year', 'feriefridage_expiry_month',
'normal_day_hours', 'friday_hours', 'opening_vacation_days',
'opening_feriefridage_days', 'opening_flex_hours'
'normal_day_hours', 'friday_hours'
];
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
@ -315,6 +314,25 @@ function renderPlanner() {
document.querySelectorAll('.balance-override-input').forEach((input) => {
input.addEventListener('change', (event) => saveBalanceOverride(event).catch((error) => toast(error.message || String(error))));
});
syncPlannerScroll();
}
function syncPlannerScroll() {
const headerWrap = document.querySelector('.planner-column-header.table-wrap');
const bodyWrap = document.querySelector('.planner-panel .table-wrap');
if (!headerWrap || !bodyWrap) return;
let syncing = false;
const sync = (from, to) => {
if (syncing) return;
syncing = true;
to.scrollLeft = from.scrollLeft;
requestAnimationFrame(() => { syncing = false; });
};
headerWrap.addEventListener('scroll', () => sync(headerWrap, bodyWrap), { passive: true });
bodyWrap.addEventListener('scroll', () => sync(bodyWrap, headerWrap), { passive: true });
headerWrap.scrollLeft = bodyWrap.scrollLeft;
}
function renderMonthRow(month) {

View file

@ -120,10 +120,7 @@
<label>FF deadline month<input id="feriefridage_expiry_month" type="number" step="1" min="1" max="12"></label>
<label>MonThu hours<input id="normal_day_hours" type="number" step="0.25"></label>
<label>Friday hours<input id="friday_hours" type="number" step="0.25"></label>
<label>Start vacation balance<input id="opening_vacation_days" type="number" step="0.01"></label>
<label>Start feriefridage balance<input id="opening_feriefridage_days" type="number" step="0.01"></label>
<label>Start FLEX hours<input id="opening_flex_hours" type="number" step="0.25"></label>
<p class="full help-text">Start balances are the balances before the first shown month. Monthly vacation, feriefridage, and FLEX balances are edited directly in the planner table.</p>
<p class="full help-text">Monthly vacation, feriefridage, and FLEX balances are edited directly in the planner table. Clear a value to return to the calculated balance.</p>
<div class="actions full">
<button type="submit">Save settings</button>
</div>

View file

@ -355,27 +355,72 @@ tbody tr:hover { background: rgba(255,255,255,.04); }
}
@media (max-width: 680px) {
.floating-header { padding: .3rem .4rem; }
.cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .28rem; }
.card { padding: .34rem .4rem; }
.card .label { font-size: .6rem; }
.card .value { font-size: .95rem; }
body { font-size: 14px; }
.floating-header { padding: .25rem .3rem; }
.cards { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .22rem; }
.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; }
.card .hint { display: none; }
.quick-flex-form { margin-top: .06rem; }
.quick-flex-form input { padding: .26rem .32rem; font-size: .9rem; }
.quick-flex-form button { padding: .26rem .38rem; }
.header-entries { padding: .18rem .25rem; margin: .18rem 0; }
.header-entries .table-wrap { overflow-x: auto; overflow-y: visible; }
.compact-entries-table { font-size: .68rem; }
.compact-entries-table th, .compact-entries-table td { padding: .18rem .25rem; }
.header-warning-row { gap: .3rem; }
.header-warning, .header-action-button { min-height: 1.32rem; font-size: .68rem; padding: .18rem .36rem; }
main { padding: 0 .4rem 3rem; }
.planner-column-header { margin-top: .18rem; padding-top: .16rem; }
.planner-header-table th,
.planner-body-table td { padding: .42rem .32rem; }
.planner-header-table th { font-size: .68rem; }
.month-table { min-width: 680px; }
.form-grid, .form-grid.compact { grid-template-columns: 1fr; }
th, td { padding: .55rem .45rem; }
.header-entries { padding: .16rem .22rem; margin: .16rem 0; }
.header-entries .table-wrap { overflow-x: visible; overflow-y: visible; }
.compact-entries-table,
.compact-entries-table tbody { display: block; width: 100%; }
.compact-entries-table thead { display: none; }
.compact-entries-table tr {
display: grid;
grid-template-columns: minmax(0, 1fr) repeat(4, auto);
gap: .12rem .32rem;
align-items: center;
padding: .18rem 0;
border-bottom: 1px solid rgba(163,138,99,.22);
}
.compact-entries-table tr:last-child { border-bottom: 0; }
.compact-entries-table td {
display: block;
border-bottom: 0;
padding: 0;
white-space: normal;
line-height: 1.05;
}
.compact-entries-table td:nth-child(1) { grid-column: 1 / -1; min-width: 0; }
.compact-entries-table td:nth-child(2)::before { content: 'wd '; color: var(--muted); font-weight: 700; }
.compact-entries-table td:nth-child(3)::before { content: 'vd '; color: var(--muted); font-weight: 700; }
.compact-entries-table td:nth-child(4)::before { content: 'ff '; color: var(--muted); font-weight: 700; }
.compact-entries-table td:nth-child(5)::before { content: 'fl '; color: var(--muted); font-weight: 700; }
.compact-entries-table td:nth-child(6) { grid-column: 1 / 2; }
.compact-entries-table td:nth-child(7) { grid-column: 2 / -1; }
.compact-entries-table .entry-actions { justify-content: flex-end; gap: .18rem; }
.compact-entries-table .pill { font-size: .62rem; padding: .08rem .28rem; }
.compact-entries-table button.small { padding: .2rem .34rem; font-size: .62rem; }
.entry-date { font-size: .6rem; }
.header-warning-row { gap: .25rem; align-items: center; }
.header-warning-list { flex: 1 1 auto; }
.header-action-list { flex: none; }
.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: .14rem; padding-top: .12rem; }
.planner-header-table th,
.planner-body-table td { padding: .32rem .24rem; }
.planner-header-table th { font-size: .58rem; letter-spacing: .02em; }
.month-table { min-width: 600px; }
.month-table th:nth-child(1), .month-table td:nth-child(1) { width: 6.8rem; }
.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-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; }
.modal { width: calc(100vw - .8rem); max-height: calc(100dvh - .8rem); padding: .75rem; overflow: auto; }
.modal-heading { margin-bottom: .7rem; }
.form-grid, .form-grid.compact { grid-template-columns: 1fr; gap: .55rem; }
input, select, textarea { padding: .62rem .68rem; }
th, td { padding: .45rem .35rem; }
}