fix: hide close-week button when a tracked day is still open

Fetch entries for the week alongside days/weeks in the week view.
canCloseWeek now mirrors the server rule: every past workday that
has at least one entry must have a closed_days record. Days with
no entries are still skipped (they count as 0h implicitly).
This commit is contained in:
2026-04-30 18:10:44 +02:00
parent 563784d5fb
commit 3fd1455704

View File

@@ -1,14 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { days, weeks, settings, type ClosedDay, type ClosedWeek, type Settings, ApiError } from '$lib/api/client'; import { entries, days, weeks, settings, type Entry, type ClosedDay, type ClosedWeek, type Settings, ApiError } from '$lib/api/client';
import { import {
currentWeekKey, weekDayKeys, formatDurationShort, formatDelta, todayKey, isWorkday currentWeekKey, weekDayKeys, formatDurationShort, formatDelta, todayKey, isWorkday
} from '$lib/utils'; } from '$lib/utils';
let weekKey = $state(currentWeekKey()); let weekKey = $state(currentWeekKey());
let dayKeys = $derived(weekDayKeys(weekKey)); let dayKeys = $derived(weekDayKeys(weekKey));
let closedDaysMap: Record<string, ClosedDay> = $state({}); let closedDaysMap = $state<Record<string, ClosedDay>>({});
let closedWeek: ClosedWeek | null = $state(null); let daysWithEntries = $state<Set<string>>(new Set());
let closedWeek = $state<ClosedWeek | null>(null);
let currentSettings = $state<Settings | null>(null); let currentSettings = $state<Settings | null>(null);
let error = $state(''); let error = $state('');
@@ -17,14 +18,19 @@
const from = dayKeys[0]; const from = dayKeys[0];
const to = dayKeys[6]; const to = dayKeys[6];
try { try {
const [ds, ws, s] = await Promise.all([ const [ds, ws, s, es] = await Promise.all([
days.list(from, to), days.list(from, to),
weeks.list(weekKey, weekKey), weeks.list(weekKey, weekKey),
settings.current() settings.current(),
entries.list(from, to)
]); ]);
closedDaysMap = Object.fromEntries((ds ?? []).map((d) => [d.day_key, d])); closedDaysMap = Object.fromEntries((ds ?? []).map((d) => [d.day_key, d]));
closedWeek = (ws ?? []).find((w) => w.week_key === weekKey) ?? null; closedWeek = (ws ?? []).find((w) => w.week_key === weekKey) ?? null;
currentSettings = s; currentSettings = s;
// Track which day_keys have at least one entry (for close-week guard).
const set = new Set<string>();
for (const e of (es ?? [])) set.add(e.day_key);
daysWithEntries = set;
} catch (e) { } catch (e) {
error = e instanceof ApiError ? e.message : String(e); error = e instanceof ApiError ? e.message : String(e);
} }
@@ -61,10 +67,16 @@
currentSettings ? currentSettings.hours_per_week * 3_600_000 : 0 currentSettings ? currentSettings.hours_per_week * 3_600_000 : 0
); );
// Week can be closed if it's not already closed and the week has started // Week can be closed if it's not already closed, the week has started,
// (i.e. Monday ≤ today). The server enforces the detailed per-day rules. // and every past workday that has entries is also closed.
const canCloseWeek = $derived( const canCloseWeek = $derived(
!closedWeek && dayKeys[0] <= todayKey() !closedWeek &&
dayKeys[0] <= todayKey() &&
dayKeys.every((dk) => {
if (dk > todayKey()) return true; // future day — skip
if (!daysWithEntries.has(dk)) return true; // no entries — skip
return !!closedDaysMap[dk]; // has entries → must be closed
})
); );
function prevWeek() { weekKey = offsetWeek(weekKey, -1); } function prevWeek() { weekKey = offsetWeek(weekKey, -1); }