From c6e206ffe79933abfb7c2cc12eb3477d43f0a8ee Mon Sep 17 00:00:00 2001 From: marijo Date: Tue, 9 Jun 2026 12:52:47 +0000 Subject: [PATCH] Style update --- app.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------ index.html | 3 +++ style.css | 6 ++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 1852910..738b72f 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,7 @@ function bindEvents() { $('settingsDialog').addEventListener('click', closeDialogOnBackdrop); $('entryForm').addEventListener('submit', saveEntry); $('settingsForm').addEventListener('submit', saveSettings); + $('deleteHiddenEntriesBtn').addEventListener('click', deleteEntriesOutsideDisplayedMonths); ['startDate', 'endDate', 'nonWorkingDays'].forEach((id) => $(id).addEventListener('input', updateWorkdayPreview)); $('cancelEditBtn').addEventListener('click', resetEntryForm); } @@ -186,6 +187,43 @@ async function deleteEntry(id) { toast('Entry deleted'); } +async function deleteEntriesOutsideDisplayedMonths() { + const hiddenEntries = state.entries.filter((entry) => !entryOverlapsDisplayedRange(entry)); + if (!hiddenEntries.length) { + toast('No hidden entries to delete'); + return; + } + if (!confirm(`Delete ${hiddenEntries.length} entries outside the currently displayed months?`)) return; + + let latestEntries = state.entries; + for (const entry of hiddenEntries) { + const data = await post({ action: 'deleteEntry', id: entry.id }); + latestEntries = data.entries.map(normalizeEntry); + } + state.entries = latestEntries; + state.plan = calculatePlan(state.settings, state.entries, state.corrections, state.feriefridageOverrides, state.flexOverrides); + renderAll(); + toast('Hidden entries deleted'); +} + +function entriesInDisplayedMonths() { + return state.entries.filter(entryOverlapsDisplayedRange); +} + +function entryOverlapsDisplayedRange(entry) { + if (!state.plan?.range) return true; + return entry.start_date <= state.plan.range.end && entry.end_date >= state.plan.range.start; +} + +function isPastEntry(entry) { + return entry.end_date < toIsoDate(new Date()); +} + +function isPastMonth(month) { + const today = new Date(); + return month.year < today.getFullYear() || (month.year === today.getFullYear() && month.month < today.getMonth() + 1); +} + async function post(payload) { const res = await fetch('api.php', { method: 'POST', @@ -317,18 +355,19 @@ function renderWarnings() { } function renderEntries() { - $('entriesHeaderTitle').textContent = `VACATIONS (${state.entries.length})`; + const visibleEntries = entriesInDisplayedMonths(); + $('entriesHeaderTitle').textContent = `VACATIONS (${visibleEntries.length})`; const tbody = $('entriesTable').querySelector('tbody'); - if (!state.entries.length) { - tbody.innerHTML = 'No entries yet.'; + if (!visibleEntries.length) { + tbody.innerHTML = 'No entries in displayed months.'; return; } - tbody.innerHTML = state.entries.map((entry) => { + tbody.innerHTML = visibleEntries.map((entry) => { const summary = state.plan.entrySummaries[entry.id] || { workdays: 0, grossWorkdays: 0, nonWorkingDays: 0, usedVacation: 0, usedFeriefridage: 0, usedFlex: 0, usedFlexDays: 0 }; const source = sourceLabels[entry.source] || entry.source; return ` - + ${escapeHtml(entry.title)} ${escapeHtml(source)}
${escapeHtml(entry.start_date)} → ${escapeHtml(entry.end_date)}${Number(entry.allow_unpaid) ? ' unpaid' : ''} ${days(summary.workdays)} ${days(summary.usedVacation)} @@ -394,7 +433,7 @@ function renderMonthRow(month) { : ''; return ` - + ${month.year} - ${monthNames[month.month - 1]}${month.expiredFeriefridage > 0 ? `
Expired FF: ${days(month.expiredFeriefridage)}` : ''} ${entries} @@ -580,6 +619,7 @@ function calculatePlan(settings, entries, corrections = {}, feriefridageOverride const totalDays = vacationBalance + feriefridageBalance + (flexBalance / avgDayHours); return { months, + range: { start: toIsoDate(startDate), end: toIsoDate(endDate) }, warnings: dedupeWarnings(warnings), current: calculateCurrentSnapshot(settings, entries, corrections, feriefridageOverrides, flexOverrides), entrySummaries, @@ -817,6 +857,10 @@ function dateFromParts(year, month, day) { return new Date(year, month - 1, day, 12, 0, 0); } +function toIsoDate(date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +} + function parseDate(value) { const [year, month, day] = value.split('-').map(Number); return dateFromParts(year, month, day); diff --git a/index.html b/index.html index 27ff8bf..c4166cb 100644 --- a/index.html +++ b/index.html @@ -126,6 +126,9 @@

Monthly vacation, feriefridage, and FLEX balances are edited directly in the planner table. Clear a value to return to the calculated balance.

+
+ +
diff --git a/style.css b/style.css index 1d0d889..b5f2972 100644 --- a/style.css +++ b/style.css @@ -88,6 +88,8 @@ h2 { margin-bottom: 0; font-size: 1.1rem; } .compact-entries-table .pill { padding: .1rem .38rem; font-size: .68rem; } .compact-entries-table .entry-actions { gap: .25rem; } .compact-entries-table button.small { padding: .22rem .38rem; font-size: .68rem; } +.compact-entries-table tr.past-entry { opacity: .58; } +.compact-entries-table tr.past-entry td:first-child strong { text-decoration: line-through; } .header-warning { display: inline-flex; align-items: center; @@ -248,6 +250,7 @@ button.small { padding: .36rem .55rem; font-size: .78rem; } button.danger { background: #5a2320; color: var(--ink); border: 1px solid #5a2320; } .actions { display: flex; justify-content: flex-end; gap: .6rem; } .help-text { color: var(--muted); margin: 0; font-size: .9rem; } +.danger-zone { display: flex; justify-content: flex-end; } .hidden { display: none !important; } .quick-flex-form { @@ -290,6 +293,9 @@ table { width: 100%; border-collapse: collapse; font-size: .92rem; } th, td { padding: .75rem .65rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; } th { color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; } tbody tr:hover { background: rgba(255,255,255,.04); } +.planner-body-table tr.past-month { opacity: .55; } +.planner-body-table tr.past-month td:first-child { color: var(--muted); } +.planner-body-table tr.past-month .balance-override-input { opacity: .72; } .num { text-align: right; font-variant-numeric: tabular-nums; } .strong-num { font-weight: 900; color: var(--ink); } .muted { color: var(--muted); }