From 18d8822c47faabd85cca74c7cac8c50899924de6 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Fri, 29 May 2026 16:08:25 +0100 Subject: [PATCH] refactor: admin user modal, image upload, and new schedule page Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../lib/components/admin/ImageUpload.svelte | 2 +- .../src/lib/components/admin/UserModal.svelte | 2 +- .../src/routes/admin/schedule/+page.svelte | 648 ++++++++++++++++++ 3 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 frontend/src/routes/admin/schedule/+page.svelte diff --git a/frontend/src/lib/components/admin/ImageUpload.svelte b/frontend/src/lib/components/admin/ImageUpload.svelte index 457a150..37639bb 100644 --- a/frontend/src/lib/components/admin/ImageUpload.svelte +++ b/frontend/src/lib/components/admin/ImageUpload.svelte @@ -825,7 +825,7 @@ {tags.length === 1 ? 'tag' : 'tags'}. {#if tags.length > 0}
- {#each tags as tag} + {#each tags as tag (tag)} diff --git a/frontend/src/lib/components/admin/UserModal.svelte b/frontend/src/lib/components/admin/UserModal.svelte index 0995aa9..0056958 100644 --- a/frontend/src/lib/components/admin/UserModal.svelte +++ b/frontend/src/lib/components/admin/UserModal.svelte @@ -287,7 +287,7 @@ month: 'long', day: 'numeric' }); - const today = new Date(); + const today = new SvelteDate(); let age = today.getFullYear() - dob.getFullYear(); const m = today.getMonth() - dob.getMonth(); if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--; diff --git a/frontend/src/routes/admin/schedule/+page.svelte b/frontend/src/routes/admin/schedule/+page.svelte new file mode 100644 index 0000000..4842ecd --- /dev/null +++ b/frontend/src/routes/admin/schedule/+page.svelte @@ -0,0 +1,648 @@ + + +{#if pageState === 'loading'} +
+ + +
+{:else if pageState === 'authorized' && weekStart} +
+ +
+

Schedule

+
+ + + + {formatWeekLabel(weekStart)} +
+
+ + + {#if loading} +
+ +
+ {:else} +
+
+ +
+ +
+ + {#each getWeekDays(weekStart) as day, dayIdx (formatDate(day))} + {@const isToday = formatDate(day) === todayStr} +
+
{dayHeaders[dayIdx]}
+
+ {day.getDate()} +
+
+ {/each} +
+ + +
+ +
+ {#each timeRange.hours as h (h)} +
+ {formatHour(h)} +
+ {/each} +
+ + +
+ +
+ {#each timeRange.hours as h (h)} +
+ {#each getWeekDays(weekStart) as day (formatDate(day))} + {@const dateStr = formatDate(day)} + {@const isToday = dateStr === todayStr} + {@const dayBookings = bookingsByDate.get(dateStr) || []} + +
+ +
+ + + {#each dayBookings.filter((b) => new SvelteDate(b.start_time).getHours() === h) as b (b.id)} + {@const startMinutes = new SvelteDate(b.start_time).getMinutes()} + {@const topOffset = (startMinutes / 60) * HOUR_HEIGHT} + {@const heightPx = Math.max((b.duration_minutes / 60) * HOUR_HEIGHT, 20)} + {@const isCancelled = + b.status === 'client_cancelled' || + b.status === 'we_cancelled' || + b.status === 'no_show'} + {@const hasOverlap = + isCancelled && + dayBookings.some( + (other) => + other.id !== b.id && + !( + other.status === 'client_cancelled' || + other.status === 'we_cancelled' || + other.status === 'no_show' + ) && + bookingsOverlap(b, other) + )} + + {#if !hasOverlap} + + {/if} + {/each} +
+ {/each} +
+ {/each} +
+ + + +
+ {#each getWeekDays(weekStart) as day, dayIdx (formatDate(day))} + {@const dateStr = formatDate(day)} + {@const wh = workingHours.find((w) => w.date === dateStr)} + {@const colLeft = `calc(${dayIdx} * (100% / 7))`} + {@const colWidth = `calc(100% / 7)`} + + {#if !wh?.isOpen} + +
+ Closed +
+ {:else} + {@const openPx = gridPxFromTime(wh.startTime, timeRange.minStart)} + {@const closePx = gridPxFromTime(wh.endTime, timeRange.minStart)} + + + {#if openPx > 0} +
+ {/if} + + + {#if closePx < timeRange.hours.length * HOUR_HEIGHT} +
+ {/if} + + +
+
+ {/if} + {/each} + + + {#if isCurrentWeek} + {@const nowTime = `${String(Math.floor(nowMinutes / 60)).padStart(2, '0')}:${String(nowMinutes % 60).padStart(2, '0')}`} + {@const nowPx = gridPxFromTime(nowTime, timeRange.minStart)} + {@const todayIdx = getWeekDays(weekStart!).findIndex( + (d) => formatDate(d) === todayStr + )} + {@const colLeft = `calc(${todayIdx} * (100% / 7))`} + {@const colWidth = `calc(100% / 7)`} + + {#if nowPx >= 0 && nowPx <= timeRange.hours.length * HOUR_HEIGHT && todayIdx !== -1} + +
+ + + +
+ {/if} + {/if} +
+
+
+
+
+ {/if} +
+ + +{/if} + +