From d0c1f41c133a3def56889ee4b4e4202e70a4d809 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 30 Apr 2026 19:06:55 +0200 Subject: [PATCH] refactor: extract DayDetail component; render today inside week view - New DayDetail.svelte: self-contained day panel with full capability gating (canStartStop, canAddInterval, canEditEntries, canMarkKind, canCloseDay, canReopenDay) - Closed-day state: banner + read-only entry list with 'Reopen day to edit' hint; mark-kind buttons still available - Close Day button disabled (with tooltip) when running entry exists - Timer only ticks when dayKey === todayKey() and entry is running; properly cleaned up on dayKey change and component destroy - oninvalidate() callback: called after every mutation so parent (week view) can refetch and update the chip strip - /today route: refactored to thin wrapper using DayDetail - Week page: renders DayDetail below summary when today is in the displayed week; oninvalidate triggers full week reload --- web/src/lib/components/DayDetail.svelte | 488 ++++++++++++++++++++++++ web/src/routes/today/+page.svelte | 401 +------------------ web/src/routes/week/+page.svelte | 20 + 3 files changed, 517 insertions(+), 392 deletions(-) create mode 100644 web/src/lib/components/DayDetail.svelte diff --git a/web/src/lib/components/DayDetail.svelte b/web/src/lib/components/DayDetail.svelte new file mode 100644 index 0000000..5459b42 --- /dev/null +++ b/web/src/lib/components/DayDetail.svelte @@ -0,0 +1,488 @@ + + +
+ {#if error} +

{error}

+ {/if} + + {#if closedDay} + +
+ Day closed ({closedDay.kind}) — {formatDurationShort(closedDay.worked_ms)} worked + {#if capabilities.canReopenDay} + + {/if} +
+ + + {#if entryList.length > 0} +
+
+

Entries

+ Reopen day to edit +
+ {#each entryList as entry (entry.id)} +
+ {formatTime(entry.start_time)} + + {entry.end_time ? formatTime(entry.end_time) : '…'} + {#if entry.end_time} + {formatDuration(entry.end_time - entry.start_time)} + {/if} + {#if entry.note} + {entry.note} + {/if} +
+ {/each} +
Total: {formatDurationShort(totalWorkedMs)}
+
+ {/if} + + + {#if capabilities.canMarkKind} +
+ Remark as: + + + +
+ {/if} + + {:else} + + + {#if capabilities.canStartStop} + +
+ {#if running} +
{formatDuration(elapsed)}
+ + {:else} +
{formatDuration(totalWorkedMs)}
+
+ e.key === 'Enter' && handleStart()} /> + +
+ {/if} +
+ {/if} + + {#if capabilities.canMarkKind} +
+ Mark day as: + + + +
+ {/if} + + {#if capabilities.canCloseDay && entryList.length > 0 && !running} + + {/if} + + + {#if !capabilities.canEditEntries && entryList.length === 0} + + {:else} +
+
+

Entries

+ {#if capabilities.canAddInterval} + + {/if} +
+ + {#if showAddForm && capabilities.canAddInterval} +
+

Add interval

+ {#if addError}

{addError}

{/if} +
+ + + +
+
+ + +
+
+ {/if} + + {#if entryList.length === 0} +

No entries.

+ {:else} + {#each entryList as entry (entry.id)} + {#if editingId === entry.id && capabilities.canEditEntries} +
+ {#if editError}

{editError}

{/if} +
+ + + + +
+
+ + +
+
+ {:else} +
+ {formatTime(entry.start_time)} + + {entry.end_time ? formatTime(entry.end_time) : '…'} + {#if entry.end_time} + {formatDuration(entry.end_time - entry.start_time)} + {/if} + {#if entry.note} + {entry.note} + {/if} + {#if entry.auto_stopped} + auto-stopped + {/if} + {#if capabilities.canEditEntries} + + + {/if} +
+ {/if} + {/each} +
Total: {formatDurationShort(totalWorkedMs)}
+ {/if} +
+ {/if} + {/if} +
+ + diff --git a/web/src/routes/today/+page.svelte b/web/src/routes/today/+page.svelte index 32c7d65..80e77e0 100644 --- a/web/src/routes/today/+page.svelte +++ b/web/src/routes/today/+page.svelte @@ -1,402 +1,19 @@ -
+

Today — {today}

- - {#if error} -

{error}

- {/if} - - {#if closedDay} -
- Day closed ({closedDay.kind}) — {formatDurationShort(closedDay.worked_ms)} worked - -
- {:else} - -
- {#if running} -
{formatDuration(elapsed)}
- - {:else} -
{formatDuration(totalWorkedMs)}
-
- e.key === 'Enter' && handleStart()} /> - -
- {/if} -
- - -
- Mark day as: - - - -
- - - {#if entryList.length > 0 && !running} - - {/if} - {/if} - - -
-
-

Entries

- {#if !closedDay} - - {/if} -
- - - {#if showAddForm} -
-

Add interval

- {#if addError}

{addError}

{/if} -
- - - -
-
- - -
-
- {/if} - - {#if entryList.length === 0} -

No entries yet today.

- {:else} - {#each entryList as entry (entry.id)} - {#if editingId === entry.id} - -
- {#if editError}

{editError}

{/if} -
- - - - -
-
- - -
-
- {:else} -
- {formatTime(entry.start_time)} - - {entry.end_time ? formatTime(entry.end_time) : '…'} - {#if entry.end_time} - {formatDuration(entry.end_time - entry.start_time)} - {/if} - {#if entry.note} - {entry.note} - {/if} - {#if entry.auto_stopped} - auto-stopped - {/if} - {#if !closedDay} - - - {/if} -
- {/if} - {/each} -
Total: {formatDurationShort(totalWorkedMs)}
- {/if} -
+
diff --git a/web/src/routes/week/+page.svelte b/web/src/routes/week/+page.svelte index fe01c12..ac0d157 100644 --- a/web/src/routes/week/+page.svelte +++ b/web/src/routes/week/+page.svelte @@ -5,6 +5,8 @@ currentWeekKey, weekDayKeys, formatDurationShort, formatDelta, todayKey, isWorkday } from '$lib/utils'; import DayChip from '$lib/components/DayChip.svelte'; + import DayDetail from '$lib/components/DayDetail.svelte'; + import { dayCapabilities } from '$lib/utils'; let weekKey = $state(currentWeekKey()); let dayKeys = $derived(weekDayKeys(weekKey)); @@ -102,6 +104,15 @@ function prevWeek() { weekKey = offsetWeek(weekKey, -1); } function nextWeek() { weekKey = offsetWeek(weekKey, 1); } + + // Today in this week? + const todayInWeek = $derived(dayKeys.includes(todayKey())); + const detailDayKey = $derived(todayInWeek ? todayKey() : null); + const detailCaps = $derived( + detailDayKey + ? dayCapabilities(detailDayKey, todayKey(), !!closedDaysMap[detailDayKey]) + : null + ); function offsetWeek(wk: string, offset: number): string { const [y, w] = wk.split('-W').map(Number); const date = new Date(y, 0, 4); @@ -163,6 +174,13 @@ {:else if canCloseWeek} {/if} + + {#if detailDayKey && detailCaps} +
+

{detailDayKey}

+ +
+ {/if}