From b23a8b89c433e6cf08d0d265fd34643bf6852a66 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Thu, 18 Jun 2026 16:26:58 +0100 Subject: [PATCH] feat(frontend): update admin components Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../lib/components/admin/ApprovalModal.svelte | 118 ++++++++---- .../admin/BookingCreateModal.svelte | 2 +- .../lib/components/admin/BookingModal.svelte | 169 +++++++++++++++++- .../lib/components/admin/BookingsCard.svelte | 8 +- .../admin/CustomServicesManagement.svelte | 9 +- .../admin/DiscountsManagement.svelte | 27 +-- .../components/admin/EditBookingModal.svelte | 6 +- .../admin/GiftCardsManagement.svelte | 6 +- .../lib/components/admin/HolidayHours.svelte | 45 ++--- .../admin/PatchTestsManagement.svelte | 13 +- .../components/admin/RescheduleModal.svelte | 67 ++++++- .../lib/components/admin/TillPurchases.svelte | 3 +- .../lib/components/admin/TimeBlockers.svelte | 5 +- .../src/lib/components/admin/UserModal.svelte | 6 +- .../components/admin/WalkInCreateModal.svelte | 5 +- .../components/admin/WeeklySchedule.svelte | 2 +- 16 files changed, 370 insertions(+), 121 deletions(-) diff --git a/frontend/src/lib/components/admin/ApprovalModal.svelte b/frontend/src/lib/components/admin/ApprovalModal.svelte index 7c58fa1..e0c01b8 100644 --- a/frontend/src/lib/components/admin/ApprovalModal.svelte +++ b/frontend/src/lib/components/admin/ApprovalModal.svelte @@ -2,12 +2,15 @@ import { SvelteDate } from 'svelte/reactivity'; import { authStore } from '$lib/stores/auth.svelte'; import { toast } from 'svelte-sonner'; + import { sanitizeText } from '$lib/utils/toast-safe'; + import { formatDateTime, formatDuration, calculateAge } from '$lib/utils/format'; import * as Modal from '$lib/components/ui/dialog'; import { Button } from '$lib/components/ui/button'; import { Input } from '$lib/components/ui/input'; import { Textarea } from '$lib/components/ui/textarea'; import * as AlertDialog from '$lib/components/ui/alert-dialog'; import CharCounter from '$lib/components/ui/CharCounter.svelte'; + import type { BookingDiscount } from '$lib/types/booking'; interface Props { open: boolean; @@ -28,6 +31,7 @@ price?: number; duration_minutes?: number; }>; + discounts?: BookingDiscount[]; }; onApproved: () => void; } @@ -328,7 +332,7 @@ onApproved(); } else { const text = await response.text(); - toast.error('Failed to confirm: ' + text, { id: loadingToast }); + toast.error('Failed to confirm: ' + sanitizeText(text), { id: loadingToast }); } } catch (err) { console.error('Error confirming booking:', err); @@ -358,7 +362,7 @@ onApproved(); } else { const text = await response.text(); - toast.error('Failed to decline: ' + text, { id: loadingToast }); + toast.error('Failed to decline: ' + sanitizeText(text), { id: loadingToast }); } } catch (err) { console.error('Error declining booking:', err); @@ -371,7 +375,7 @@ - + Approve Booking @@ -452,35 +456,72 @@ {/if} - +

- Customer Contact + Appointment Details

-
+
-
Name
-
{booking.user?.full_name || '—'}
+
Scheduled Date & Time
+
{getBookingDateTime()}
-
+ {#if getTotalDuration() > 0}
-
Phone
-
{booking.user?.phone || '—'}
-
-
-
Email
-
{booking.user?.email || '—'}
+
Duration
+
{formatDuration(getTotalDuration())}
+ {/if} +
+
Created
+
{formatDateTime(booking.created_at)}
+ {#if booking.notes} +
+
Booking Notes
+
{booking.notes}
+
+ {/if}
- +
-

- Booking Date & Time +

+ Customer Information

-
{getBookingDateTime()}
+ +
+
+
{booking.user?.full_name || '—'}
+
+
+ +
+
+
Email
+ {#if booking.user?.email} + {booking.user.email} + {:else} +
+ {/if} +
+
+
Phone
+ {#if booking.user?.phone} + {booking.user.phone} + {:else} +
+ {/if} +
+
@@ -503,8 +544,8 @@ {/if}
- -
+ +

Services & Pricing

@@ -530,7 +571,7 @@ type="text" inputmode="decimal" value={serviceOverrides[service.service_id].price} - oninput={(e) => handlePriceInput(service.service_id, e.target.value)} + oninput={(e) => handlePriceInput(service.service_id, e.currentTarget.value)} onblur={() => { // Format to 2 decimal places on blur if needed const val = serviceOverrides[service.service_id].price; @@ -582,7 +623,7 @@ min="1" step="1" value={serviceOverrides[service.service_id].duration} - oninput={(e) => handleDurationInput(service.service_id, e.target.value)} + oninput={(e) => handleDurationInput(service.service_id, e.currentTarget.value)} class="no-spin w-full" placeholder={service.duration_minutes?.toString() || '60'} /> @@ -602,13 +643,25 @@ {/each}
+ + {#if booking.discounts && booking.discounts.length > 0} +
+

Applied Discounts

+ {#each booking.discounts as d} +

+ - {d.discount_source === 'loyalty' ? 'Loyalty Stamp Card' : d.campaign_name || 'Promo Campaign'} + ({d.discount_percent}% off): -£{d.discount_amount.toFixed(2)} +

+ {/each} +
+ {/if}
- Total: £{getTotalCost().toFixed(2)} - | - {getTotalDuration()} min + £{getTotalCost().toFixed(2)} + · + {getTotalDuration()} min