feat(frontend): integrate custom services across booking flow and routes

Update BookingFlow to support custom service selection, add custom_services to booking types, and link CustomServicesManagement into admin pages. Minor updates to schedules, prices, and today routes.

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-06-15 16:58:37 +01:00
co-authored by Sisyphus
parent 8f8bf83b64
commit 867f86978b
7 changed files with 36 additions and 7 deletions
@@ -1514,10 +1514,19 @@
{services} {services}
selected={selectedServices} selected={selectedServices}
loading={servicesLoading} loading={servicesLoading}
ontoggle={toggleService} ontoggle={toggleService}
/> />
{#if selectedServices.length > 0} <div class="mt-4 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-4 text-center">
<p class="text-sm text-gray-600">
Need something different? Select the closest service and add a note, or contact us for a bespoke treatment.
</p>
<a href="/contact" class="mt-1 inline-block text-sm font-medium text-blue-600 hover:underline">
Arrange a custom booking →
</a>
</div>
{#if selectedServices.length > 0}
<div class="rounded-lg bg-gray-50 p-4"> <div class="rounded-lg bg-gray-50 p-4">
<h4 class="mb-2 font-semibold">Selected Services</h4> <h4 class="mb-2 font-semibold">Selected Services</h4>
<div class="space-y-2"> <div class="space-y-2">
@@ -744,7 +744,7 @@
{item.data.user_name} {item.data.user_name}
</button> </button>
<div class="flex flex-wrap items-center gap-x-1 text-sm text-gray-600"> <div class="flex flex-wrap items-center gap-x-1 text-sm text-gray-600">
<span class="truncate">{item.data.services.join(', ')}</span> <span class="truncate">{(item.data.services ?? []).join(', ')}</span>
<span <span
class="shrink-0 rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium" class="shrink-0 rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium"
> >
+8 -1
View File
@@ -6,10 +6,17 @@ export interface Service {
duration_minutes: number; duration_minutes: number;
patch_test_duration_hours: number; patch_test_duration_hours: number;
minimum_age_required: number; minimum_age_required: number;
// Patch test status (present when user is authenticated, not admin)
patch_test_status?: 'ok' | 'required' | 'expired'; patch_test_status?: 'ok' | 'required' | 'expired';
} }
export interface CustomService extends Service {
usage_count: number;
last_used_at: string | null;
notes: string | null;
created_at: string;
created_by?: string;
}
export interface CustomerInfo { export interface CustomerInfo {
firstName: string; firstName: string;
lastName: string; lastName: string;
+2
View File
@@ -12,6 +12,7 @@
import TimeBlockers from '$lib/components/admin/TimeBlockers.svelte'; import TimeBlockers from '$lib/components/admin/TimeBlockers.svelte';
import WeeklySchedule from '$lib/components/admin/WeeklySchedule.svelte'; import WeeklySchedule from '$lib/components/admin/WeeklySchedule.svelte';
import ServicesManagement from '$lib/components/admin/ServicesManagement.svelte'; import ServicesManagement from '$lib/components/admin/ServicesManagement.svelte';
import CustomServicesManagement from '$lib/components/admin/CustomServicesManagement.svelte';
import PatchTestsManagement from '$lib/components/admin/PatchTestsManagement.svelte'; import PatchTestsManagement from '$lib/components/admin/PatchTestsManagement.svelte';
import DiscountsManagement from '$lib/components/admin/DiscountsManagement.svelte'; import DiscountsManagement from '$lib/components/admin/DiscountsManagement.svelte';
import GiftCardsManagement from '$lib/components/admin/GiftCardsManagement.svelte'; import GiftCardsManagement from '$lib/components/admin/GiftCardsManagement.svelte';
@@ -300,6 +301,7 @@
<HolidayHours /> <HolidayHours />
<WeeklySchedule /> <WeeklySchedule />
<ServicesManagement /> <ServicesManagement />
<CustomServicesManagement />
<PatchTestsManagement /> <PatchTestsManagement />
<DiscountsManagement /> <DiscountsManagement />
<GiftCardsManagement /> <GiftCardsManagement />
@@ -131,7 +131,7 @@
} }
function getServiceNames(b: ScheduleBooking): string { function getServiceNames(b: ScheduleBooking): string {
return b.services return (b.services ?? [])
.map((s) => s.service_name) .map((s) => s.service_name)
.filter(Boolean) .filter(Boolean)
.join(', '); .join(', ');
+11
View File
@@ -156,6 +156,17 @@
{/each} {/each}
</div> </div>
<Card.Root class="border-dashed border-gray-300 bg-gray-50">
<Card.Content class="p-6 text-center">
<p class="text-sm text-gray-600">
Need something different? Select the closest service and add a note, or contact us for a bespoke treatment.
</p>
<a href={resolve('/contact')} class="mt-1 inline-block text-sm font-medium text-blue-600 hover:underline">
Arrange a custom booking →
</a>
</Card.Content>
</Card.Root>
<Card.Root class="border-amber-200 bg-amber-50"> <Card.Root class="border-amber-200 bg-amber-50">
<Card.Header> <Card.Header>
<Card.Title class="flex items-center gap-2 text-amber-800"> <Card.Title class="flex items-center gap-2 text-amber-800">
+1 -1
View File
@@ -135,8 +135,8 @@
<!-- Quick Booking + Till Purchases Grid --> <!-- Quick Booking + Till Purchases Grid -->
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-6"> <div class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-6">
<div class="space-y-4"> <div class="space-y-4">
<WalkInBooking />
<CallInBooking /> <CallInBooking />
<WalkInBooking />
</div> </div>
<div> <div>
<TillPurchases /> <TillPurchases />