chore: apply project-wide formatting and UI library updates

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-29 20:23:14 +01:00
co-authored by Sisyphus
parent ef650e013a
commit 6e901690fd
131 changed files with 2437 additions and 1571 deletions
@@ -9,7 +9,11 @@
import * as Label from '$lib/components/ui/label';
import DatePicker from '$lib/components/booking/DatePicker.svelte';
import type { Booking, Service, WorkingHoursDay, AvailableHoursDay } from '$lib/types/booking';
import { extractBookedSlots, getLunchProtectionForSlots, timeToMinutes } from '$lib/lunchProtection';
import {
extractBookedSlots,
getLunchProtectionForSlots,
timeToMinutes
} from '$lib/lunchProtection';
import ClockIcon from '@lucide/svelte/icons/clock';
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
@@ -94,32 +98,31 @@
);
let canSubmit = $derived(
submitting === false && (
(editMode === 'time' && !!newDate && newTime.length >= 4) ||
(editMode === 'services' && selectedServices.length > 0) ||
(editMode === 'both-time' && !!newDate && newTime.length >= 4)
)
submitting === false &&
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
(editMode === 'services' && selectedServices.length > 0) ||
(editMode === 'both-time' && !!newDate && newTime.length >= 4))
);
let notesChanged = $derived(notes.trim() !== originalNotes.trim());
let modalTitle = $derived(() => {
switch (editMode) {
case 'select': return 'Edit Request';
case 'time': return 'Change Time';
case 'services': return 'Change Services';
case 'both-services': return 'Change Services';
case 'both-time': return 'Choose Time';
case 'select':
return 'Edit Request';
case 'time':
return 'Change Time';
case 'services':
return 'Change Services';
case 'both-services':
return 'Change Services';
case 'both-time':
return 'Choose Time';
}
});
const lunchProtection = $derived(() => {
if (
!newDate ||
!workingHours ||
!availableHours ||
slotDuration === 0
) {
if (!newDate || !workingHours || !availableHours || slotDuration === 0) {
return new Map();
}
@@ -131,11 +134,7 @@
return new Map();
}
const existingBookings = extractBookedSlots(
dayWH.startTime,
dayWH.endTime,
dayAH.slots
);
const existingBookings = extractBookedSlots(dayWH.startTime, dayWH.endTime, dayAH.slots);
return getLunchProtectionForSlots(
dayWH.startTime,
@@ -316,7 +315,11 @@
const dayAH = availableHours?.[dateStr];
if (dayAH?.slots) {
const existingBookings = extractBookedSlots(dayHours.startTime, dayHours.endTime, dayAH.slots);
const existingBookings = extractBookedSlots(
dayHours.startTime,
dayHours.endTime,
dayAH.slots
);
const lunchProtection = getLunchProtectionForSlots(
dayHours.startTime,
dayHours.endTime,
@@ -390,7 +393,14 @@
// ─── Auto-select ────────────────────────────────────────
$effect(() => {
if (!workingHours || !availableHours || newDate || userNavigatedCalendar || editRequestAutoSelectDone) return;
if (
!workingHours ||
!availableHours ||
newDate ||
userNavigatedCalendar ||
editRequestAutoSelectDone
)
return;
editRequestAutoSelectDone = true;
const currentDate = new SvelteDate();
@@ -419,11 +429,7 @@
if (!isDateUnavailable(calDate)) {
newDate = calDate;
if (!userNavigatedCalendar) {
placeholderDate = new CalendarDate(
nextDate.getFullYear(),
nextDate.getMonth() + 1,
1
);
placeholderDate = new CalendarDate(nextDate.getFullYear(), nextDate.getMonth() + 1, 1);
}
return;
}
@@ -432,11 +438,7 @@
const tomorrow = new SvelteDate();
tomorrow.setDate(tomorrow.getDate() + 1);
newDate = new CalendarDate(
tomorrow.getFullYear(),
tomorrow.getMonth() + 1,
tomorrow.getDate()
);
newDate = new CalendarDate(tomorrow.getFullYear(), tomorrow.getMonth() + 1, tomorrow.getDate());
if (!userNavigatedCalendar) {
placeholderDate = new CalendarDate(tomorrow.getFullYear(), tomorrow.getMonth() + 1, 1);
}
@@ -716,7 +718,9 @@
</script>
<Modal.Root bind:open>
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
<Modal.Content
class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl"
>
<Modal.Header>
<div class="flex items-center justify-between">
<Modal.Title class="text-lg font-semibold">{modalTitle()}</Modal.Title>
@@ -734,7 +738,9 @@
onclick={() => selectMode('time')}
class="flex w-full items-center gap-4 rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:border-fuchsia-200 hover:bg-fuchsia-50"
>
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600">
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600"
>
<ClockIcon class="size-5" />
</div>
<div class="flex-1">
@@ -749,9 +755,13 @@
onclick={() => selectMode('services')}
disabled={hasOverrides}
class="flex w-full items-center gap-4 rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:border-fuchsia-200 hover:bg-fuchsia-50 disabled:cursor-not-allowed disabled:opacity-50"
title={hasOverrides ? 'This booking has custom pricing. To change services, please contact the salon.' : ''}
title={hasOverrides
? 'This booking has custom pricing. To change services, please contact the salon.'
: ''}
>
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600">
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600"
>
<RefreshCwIcon class="size-5" />
</div>
<div class="flex-1">
@@ -766,11 +776,15 @@
onclick={() => selectMode('both-services')}
disabled={hasOverrides}
class="flex w-full items-center gap-4 rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:border-fuchsia-200 hover:bg-fuchsia-50 disabled:cursor-not-allowed disabled:opacity-50"
title={hasOverrides ? 'This booking has custom pricing. To change services, please contact the salon.' : ''}
title={hasOverrides
? 'This booking has custom pricing. To change services, please contact the salon.'
: ''}
>
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600">
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-600"
>
<ClockIcon class="size-5" />
<RefreshCwIcon class="size-5 -ml-2" />
<RefreshCwIcon class="-ml-2 size-5" />
</div>
<div class="flex-1">
<div class="font-medium">Change Both</div>
@@ -781,11 +795,11 @@
{#if hasOverrides}
<div class="rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800">
This booking has custom pricing. To change services, please contact the salon. You can still request a time change.
This booking has custom pricing. To change services, please contact the salon. You can
still request a time change.
</div>
{/if}
</div>
{:else if editMode === 'time' || editMode === 'both-time'}
<!-- ─── Time Selection ────────────────────────── -->
{#if loadingHours && !workingHours}
@@ -799,7 +813,7 @@
placeholder={placeholderDate}
minValue={minDate}
maxValue={maxCalendarDate}
isDateUnavailable={isDateUnavailable}
{isDateUnavailable}
onchange={(d) => {
newDate = d;
newTime = '';
@@ -819,15 +833,15 @@
<p class="text-sm text-gray-500">Loading times...</p>
</div>
{:else}
<div class="no-scrollbar mt-2 flex min-h-[100px] max-h-40 w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t pt-4">
<div
class="no-scrollbar mt-2 flex max-h-40 min-h-[100px] w-full scroll-pb-6 flex-col gap-4 overflow-y-auto border-t pt-4"
>
<div class="grid justify-center gap-2 text-sm text-gray-600">
{newDate
.toDate(getLocalTimeZone())
.toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
month: 'short'
})}
{newDate.toDate(getLocalTimeZone()).toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
month: 'short'
})}
</div>
{#if workingHours && !workingHours[newDate.toString()]?.isOpen}
<p class="text-center text-sm text-gray-500">We're closed on this day</p>
@@ -844,7 +858,9 @@
<Button
variant="outline"
onclick={() => selectTime(slot.startTime)}
class="w-full hover:bg-fuchsia-50 {newTime === slot.startTime ? 'bg-fuchsia-100 border-fuchsia-200' : ''}"
class="w-full hover:bg-fuchsia-50 {newTime === slot.startTime
? 'border-fuchsia-200 bg-fuchsia-100'
: ''}"
>
{formatTime(slot.startTime)}
<span class="text-gray-500">- {formatTime(slot.endTime)}</span>
@@ -878,7 +894,6 @@
rows={2}
/>
</div>
{:else if editMode === 'services'}
<!-- ─── Services Only (with time restriction) ── -->
{#if hasOverrides}
@@ -897,9 +912,7 @@
<h4 class="mb-2 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Current Services
{#if selectedServices.length > 0}
<span class="ml-1 text-xs font-normal text-gray-400">
(tap to remove)
</span>
<span class="ml-1 text-xs font-normal text-gray-400"> (tap to remove) </span>
{/if}
</h4>
{#if selectedServices.length === 0}
@@ -912,16 +925,18 @@
onclick={() => {
selectedServices = selectedServices.filter((s) => s.id !== service.id);
}}
class="cursor-pointer rounded-lg border border-input bg-background p-4 text-left transition-colors hover:bg-fuchsia-50 bg-fuchsia-100"
class="cursor-pointer rounded-lg border border-input bg-background bg-fuchsia-100 p-4 text-left transition-colors hover:bg-fuchsia-50"
>
<div class="flex flex-col justify-between h-full min-h-[6rem]">
<div class="flex h-full min-h-[6rem] flex-col justify-between">
<div>
<h3 class="font-semibold">{service.name}</h3>
<p class="text-sm text-gray-600">{service.description || ''}</p>
</div>
<div class="flex items-center justify-between mt-2 text-sm text-gray-500">
<div class="mt-2 flex items-center justify-between text-sm text-gray-500">
<span>{service.duration_minutes} mins</span>
<span class="font-semibold text-foreground">£{service.price.toFixed(2)}</span>
<span class="font-semibold text-foreground"
>£{service.price.toFixed(2)}</span
>
</div>
</div>
</button>
@@ -942,24 +957,26 @@
</h4>
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
{#each fittingServices as service (service.id)}
<button
type="button"
onclick={() => {
selectedServices = [...selectedServices, service];
}}
class="cursor-pointer rounded-lg border border-input bg-background p-4 text-left transition-colors hover:bg-fuchsia-50"
>
<div class="flex flex-col justify-between h-full min-h-[6rem]">
<div>
<h3 class="font-semibold">{service.name}</h3>
<p class="text-sm text-gray-600">{service.description || ''}</p>
<button
type="button"
onclick={() => {
selectedServices = [...selectedServices, service];
}}
class="cursor-pointer rounded-lg border border-input bg-background p-4 text-left transition-colors hover:bg-fuchsia-50"
>
<div class="flex h-full min-h-[6rem] flex-col justify-between">
<div>
<h3 class="font-semibold">{service.name}</h3>
<p class="text-sm text-gray-600">{service.description || ''}</p>
</div>
<div class="mt-2 flex items-center justify-between text-sm text-gray-500">
<span>{service.duration_minutes} mins</span>
<span class="font-semibold text-foreground"
>£{service.price.toFixed(2)}</span
>
</div>
</div>
<div class="flex items-center justify-between mt-2 text-sm text-gray-500">
<span>{service.duration_minutes} mins</span>
<span class="font-semibold text-foreground">£{service.price.toFixed(2)}</span>
</div>
</div>
</button>
</button>
{/each}
</div>
</div>
@@ -969,15 +986,15 @@
</div>
{/if}
{:else}
<div class="rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800">
<div
class="rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800"
>
No remaining time available. Remove a service to free up time for additions.
</div>
{/if}
{#if selectedServices.length === 0}
<p class="text-xs text-amber-600">
Select at least one service to continue.
</p>
<p class="text-xs text-amber-600">Select at least one service to continue.</p>
{/if}
</div>
{/if}
@@ -991,7 +1008,8 @@
</div>
{#if originalNotes}
<div class="rounded-md bg-gray-50 p-2 text-xs text-gray-500">
<span class="font-medium">Original:</span> {originalNotes}
<span class="font-medium">Original:</span>
{originalNotes}
</div>
{/if}
<Textarea.Root
@@ -1001,7 +1019,6 @@
rows={2}
/>
</div>
{:else if editMode === 'both-services'}
<!-- ─── Both Step 1: Service Selection (no time restriction) ── -->
{#if hasOverrides}
@@ -1022,9 +1039,7 @@
<h4 class="mb-2 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Selected Services
{#if selectedServices.length > 0}
<span class="ml-1 text-xs font-normal text-gray-400">
(tap to remove)
</span>
<span class="ml-1 text-xs font-normal text-gray-400"> (tap to remove) </span>
{/if}
</h4>
{#if selectedServices.length === 0}
@@ -1037,16 +1052,18 @@
onclick={() => {
selectedServices = selectedServices.filter((s) => s.id !== service.id);
}}
class="cursor-pointer rounded-lg border border-input bg-background p-4 text-left transition-colors hover:bg-fuchsia-50 bg-fuchsia-100"
class="cursor-pointer rounded-lg border border-input bg-background bg-fuchsia-100 p-4 text-left transition-colors hover:bg-fuchsia-50"
>
<div class="flex flex-col justify-between h-full min-h-[6rem]">
<div class="flex h-full min-h-[6rem] flex-col justify-between">
<div>
<h3 class="font-semibold">{service.name}</h3>
<p class="text-sm text-gray-600">{service.description || ''}</p>
</div>
<div class="flex items-center justify-between mt-2 text-sm text-gray-500">
<div class="mt-2 flex items-center justify-between text-sm text-gray-500">
<span>{service.duration_minutes} mins</span>
<span class="font-semibold text-foreground">£{service.price.toFixed(2)}</span>
<span class="font-semibold text-foreground"
>£{service.price.toFixed(2)}</span
>
</div>
</div>
</button>
@@ -1069,14 +1086,16 @@
}}
class="cursor-pointer rounded-lg border border-input bg-background p-4 text-left transition-colors hover:bg-fuchsia-50"
>
<div class="flex flex-col justify-between h-full min-h-[6rem]">
<div class="flex h-full min-h-[6rem] flex-col justify-between">
<div>
<h3 class="font-semibold">{service.name}</h3>
<p class="text-sm text-gray-600">{service.description || ''}</p>
</div>
<div class="flex items-center justify-between mt-2 text-sm text-gray-500">
<div class="mt-2 flex items-center justify-between text-sm text-gray-500">
<span>{service.duration_minutes} mins</span>
<span class="font-semibold text-foreground">£{service.price.toFixed(2)}</span>
<span class="font-semibold text-foreground"
>£{service.price.toFixed(2)}</span
>
</div>
</div>
</button>
@@ -1086,9 +1105,7 @@
{/if}
{#if selectedServices.length === 0}
<p class="text-xs text-amber-600">
Select at least one service to continue.
</p>
<p class="text-xs text-amber-600">Select at least one service to continue.</p>
{/if}
</div>
@@ -1101,7 +1118,8 @@
</div>
{#if originalNotes}
<div class="rounded-md bg-gray-50 p-2 text-xs text-gray-500">
<span class="font-medium">Original:</span> {originalNotes}
<span class="font-medium">Original:</span>
{originalNotes}
</div>
{/if}
<Textarea.Root
@@ -1122,13 +1140,8 @@
<Button variant="ghost" size="sm" class="flex-1" onclick={() => (open = false)}>
Cancel
</Button>
{:else if editMode === 'time' || editMode === 'services'}
<Button
variant="outline"
size="sm"
onclick={goBack}
>
<Button variant="outline" size="sm" onclick={goBack}>
<ArrowLeftIcon class="size-4" />
Back
</Button>
@@ -1142,13 +1155,8 @@
>
{submitting ? 'Submitting...' : 'Submit Request'}
</Button>
{:else if editMode === 'both-services'}
<Button
variant="outline"
size="sm"
onclick={goBack}
>
<Button variant="outline" size="sm" onclick={goBack}>
<ArrowLeftIcon class="size-4" />
Back
</Button>
@@ -1165,13 +1173,8 @@
Next: Choose Time
<ArrowRightIcon class="size-4" />
</Button>
{:else if editMode === 'both-time'}
<Button
variant="outline"
size="sm"
onclick={goBack}
>
<Button variant="outline" size="sm" onclick={goBack}>
<ArrowLeftIcon class="size-4" />
Back
</Button>