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:
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(', ');
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
Reference in New Issue
Block a user