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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user