diff --git a/web/src/lib/components/DayChip.svelte b/web/src/lib/components/DayChip.svelte new file mode 100644 index 0000000..0fe0c18 --- /dev/null +++ b/web/src/lib/components/DayChip.svelte @@ -0,0 +1,183 @@ + + + + + diff --git a/web/src/routes/week/+page.svelte b/web/src/routes/week/+page.svelte index 5e7f5bb..fe01c12 100644 --- a/web/src/routes/week/+page.svelte +++ b/web/src/routes/week/+page.svelte @@ -4,11 +4,12 @@ import { currentWeekKey, weekDayKeys, formatDurationShort, formatDelta, todayKey, isWorkday } from '$lib/utils'; + import DayChip from '$lib/components/DayChip.svelte'; let weekKey = $state(currentWeekKey()); let dayKeys = $derived(weekDayKeys(weekKey)); let closedDaysMap = $state>({}); - let daysWithEntries = $state>(new Set()); + let weekEntries = $state([]); let closedWeek = $state(null); let currentSettings = $state(null); let error = $state(''); @@ -27,10 +28,7 @@ closedDaysMap = Object.fromEntries((ds ?? []).map((d) => [d.day_key, d])); closedWeek = (ws ?? []).find((w) => w.week_key === weekKey) ?? null; currentSettings = s; - // Track which day_keys have at least one entry (for close-week guard). - const set = new Set(); - for (const e of (es ?? [])) set.add(e.day_key); - daysWithEntries = set; + weekEntries = es ?? []; } catch (e) { error = e instanceof ApiError ? e.message : String(e); } @@ -60,13 +58,36 @@ const DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + // Per-day worked ms: use closed_days value if closed, else sum open entries. + function dayWorkedMs(dk: string): number { + const cd = closedDaysMap[dk]; + if (cd) return cd.worked_ms; + return weekEntries + .filter((e) => e.day_key === dk && e.end_time != null) + .reduce((sum, e) => sum + (e.end_time! - e.start_time), 0); + } + + // Daily expected ms for a workday; 0 for weekends. + function dailyExpectedMs(dk: string): number { + if (!currentSettings) return 0; + const mask = currentSettings.workdays_mask; + if (!isWorkday(dk, mask)) return 0; + const workdayCount = [1, 2, 4, 8, 16, 32, 64].filter((b) => mask & b).length; + return (currentSettings.hours_per_week * 3_600_000) / workdayCount; + } + const totalWorkedMs = $derived( - dayKeys.reduce((sum, dk) => sum + (closedDaysMap[dk]?.worked_ms ?? 0), 0) + dayKeys.reduce((sum, dk) => sum + dayWorkedMs(dk), 0) ); const expectedMs = $derived( currentSettings ? currentSettings.hours_per_week * 3_600_000 : 0 ); + // Track which day_keys have at least one entry (for close-week guard). + const daysWithEntries = $derived( + new Set(weekEntries.map((e) => e.day_key)) + ); + // Week can be closed if it's not already closed, the week has started, // and every past workday that has entries is also closed. const canCloseWeek = $derived( @@ -102,25 +123,20 @@ {#if error}

{error}

{/if} -
+ +
{#each dayKeys as dk, i (dk)} - {@const cd = closedDaysMap[dk]} - {@const isToday = dk === todayKey()} - {@const workday = currentSettings ? isWorkday(dk, currentSettings.workdays_mask) : i < 5} -
-
- {DAY_NAMES[i]} - {dk.slice(5)} -
- {#if cd} -
{cd.kind}
-
{formatDurationShort(cd.worked_ms)}
- {:else if workday} -
open
- {:else} -
- {/if} -
+ {/each}
@@ -151,25 +167,20 @@