Update frontend to match new tested backend

This commit is contained in:
2026-03-02 22:49:20 +00:00
parent 1e13ccda05
commit ec2dfb6934
5 changed files with 308 additions and 201 deletions
@@ -5,6 +5,7 @@
import * as Modal from '$lib/components/ui/dialog';
import { Button } from '$lib/components/ui/button';
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
import type { Booking } from '$lib/types/booking';
interface Props {
open: boolean;
@@ -13,75 +14,6 @@
let { open = $bindable(), bookingId }: Props = $props();
type Booking = {
id: string;
start_time: string;
status:
| 'pending'
| 'confirmed'
| 'in_progress'
| 'completed'
| 'client_cancelled'
| 'we_cancelled'
| 're-schedule'
| 'no_show';
notes?: string;
created_at: string;
updated_at: string;
created_by?: string;
user?: {
id: string;
first_name: string;
last_name: string;
full_name: string;
email?: string;
phone?: string;
profile_pic_url?: string;
date_of_birth?: string;
account_role: string;
loyalty_stamps?: number;
referral_code?: string;
referral_code_uses?: number;
created_at: string;
notes?: string;
};
services: Array<{
booking_id: string;
service_id: string;
override_price?: number;
override_duration_minutes?: number;
service_name?: string;
service_description?: string;
price?: number;
duration_minutes?: number;
}>;
payments: Array<{
id: string;
booking_id: string;
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
vendor_code?: string;
invoice_number?: number;
status: 'pending' | 'completed' | 'failed' | 'refunded';
amount: number;
is_vat_applicable: boolean;
vat_rate?: number;
vat_amount?: number;
net_amount?: number;
created_at: string;
updated_at: string;
created_by?: string;
}>;
total_amount: number;
amount_paid: number;
amount_due: number;
duration_minutes: number;
};
let selectedBooking = $state<Booking | null>(null);
let showApprovalModal = $state(false);
@@ -111,6 +43,17 @@
start_time: data.start_time,
status: data.status,
notes: data.notes,
created_at: data.created_at,
updated_at: data.updated_at,
created_by: data.created_by,
// Deposit fields
deposit_required: data.deposit_required ?? false,
deposit_amount: data.deposit_amount,
deposit_paid: data.deposit_paid ?? false,
deposit_deadline: data.deposit_deadline,
// User
user: data.user
? {
id: data.user.id,
@@ -129,14 +72,20 @@
notes: data.user.notes
}
: undefined,
// Services
services: (data.services || []).map((s) => ({
booking_id: s.booking_id,
service_id: s.service_id,
service_name: s.service_name,
service_description: s.service_description,
price: s.price,
duration_minutes: s.duration_minutes
duration_minutes: s.duration_minutes,
override_price: s.override_price,
override_duration_minutes: s.override_duration_minutes
})),
// Payments
payments: (data.payments || []).map((p) => ({
id: p.id,
booking_id: p.booking_id,
@@ -154,13 +103,12 @@
updated_at: p.updated_at,
created_by: p.created_by
})),
// Financials
total_amount: data.total_amount || 0,
amount_paid: data.amount_paid || 0,
amount_due: data.amount_due || 0,
duration_minutes: data.duration_minutes || 0,
created_at: data.created_at,
updated_at: data.updated_at,
created_by: data.created_by
duration_minutes: data.duration_minutes || 0
};
} else {
const text = await response.text();
@@ -281,6 +229,48 @@
{/if}
</div>
<!-- Deposit Info -->
{#if selectedBooking.deposit_required}
<div class="rounded-lg border border-blue-200 bg-blue-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-blue-800 uppercase">
Deposit Information
</h3>
<div class="grid gap-3 md:grid-cols-2">
<div>
<div class="text-xs text-blue-600">Deposit Amount</div>
<div class="font-semibold text-blue-900">
£{selectedBooking.deposit_amount?.toFixed(2) || '0.00'}
</div>
</div>
<div>
<div class="text-xs text-blue-600">Deposit Status</div>
<div class="font-semibold">
<span
class="{selectedBooking.deposit_paid
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'} inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
>
{selectedBooking.deposit_paid ? 'Paid' : 'Outstanding'}
</span>
</div>
</div>
{#if selectedBooking.deposit_deadline && !selectedBooking.deposit_paid}
<div class="md:col-span-2">
<div class="text-xs text-blue-600">Deadline</div>
<div class="font-semibold text-blue-900">
{new SvelteDate(selectedBooking.deposit_deadline).toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</div>
</div>
{/if}
</div>
</div>
{/if}
<!-- Services -->
{#if selectedBooking.services && selectedBooking.services.length > 0}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
@@ -8,64 +8,11 @@
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { Skeleton } from '$lib/components/ui/skeleton';
import type { Booking } from '$lib/types/booking';
// Props
let { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
// Types
type Booking = {
id: string;
start_time: string;
status:
| 'pending'
| 'confirmed'
| 'in_progress'
| 'completed'
| 'client_cancelled'
| 'we_cancelled'
| 're-schedule'
| 'no_show';
notes?: string;
created_at: string;
updated_at: string;
created_by?: string;
user?: {
id: string;
full_name: string;
};
services: Array<{
booking_id: string;
service_id: string;
override_price?: number;
override_duration_minutes?: number;
service_name?: string;
service_description?: string;
price?: number;
duration_minutes?: number;
}>;
payments: Array<{
id: string;
booking_id: string;
payment_type: 'deposit' | 'full' | 'tip' | 'balance' | 'partial';
payment_method: 'online_square' | 'in_person_card' | 'cash' | 'giftcard' | 'discount';
vendor_code?: string;
invoice_number?: number;
status: 'pending' | 'completed' | 'failed' | 'refunded';
amount: number;
is_vat_applicable: boolean;
vat_rate?: number;
vat_amount?: number;
net_amount?: number;
created_at: string;
updated_at: string;
created_by?: string;
}>;
total_amount: number;
amount_paid: number;
amount_due: number;
duration_minutes: number;
};
// State
let bookings = $state<Booking[]>([]);
let totalBookings = $state(0);
@@ -99,7 +46,7 @@
});
if (response.ok) {
const data = await response.json();
if (data.bookings && data.bookings.length === 0) {
bookings = [];
totalBookings = 0;
@@ -110,28 +57,9 @@
return;
}
bookings = data.bookings.map((b: any) => ({
id: b.id,
start_time: b.start_time,
status: b.status,
notes: b.notes,
created_at: b.created_at,
updated_at: b.updated_at,
created_by: b.created_by,
user: b.user
? {
id: b.user.id,
full_name: b.user.full_name
}
: undefined,
services: b.services || [],
total_amount: b.total_amount || 0,
amount_paid: b.amount_paid || 0,
amount_due: b.amount_due || 0,
duration_minutes: b.duration_minutes || 0
}));
bookings = data.bookings as Booking[];
totalBookings = data.total || 0;
totalPages = data.totalPages || 1;
totalPages = data.total_pages || 1;
currentPage = data.page || 1;
} else {
const text = await response.text();
@@ -222,7 +150,7 @@
// Format services list
function formatServices(services: Booking['services']): string {
const serviceNames = services.map((s) => s.service_name || 'Unknown Service');
const serviceNames = services?.map((s) => s.service_name || 'Unknown Service') || [];
if (serviceNames.length === 0) return 'No services';
if (serviceNames.length === 1) return serviceNames[0];
if (serviceNames.length === 2) return serviceNames.join(' and ');
@@ -232,7 +160,7 @@
// Get status badge classes
function getStatusClasses(status: Booking['status']): string {
const baseClasses = 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium';
const statusMap = {
const statusMap: Record<string, string> = {
confirmed: 'bg-emerald-100 text-emerald-800',
pending: 'bg-amber-100 text-amber-800',
in_progress: 'bg-blue-100 text-blue-800',
@@ -240,13 +168,14 @@
client_cancelled: 'bg-red-100 text-red-800',
we_cancelled: 'bg-rose-100 text-rose-800',
're-schedule': 'bg-purple-100 text-purple-800',
no_show: 'bg-gray-100 text-gray-800'
no_show: 'bg-gray-100 text-gray-800',
no_deposit: 'bg-orange-100 text-orange-800'
};
return `${baseClasses} ${statusMap[status] || 'bg-gray-100 text-gray-800'}`;
}
function getStatusDotClasses(status: Booking['status']): string {
const statusMap = {
const statusMap: Record<string, string> = {
confirmed: 'bg-emerald-600',
pending: 'bg-amber-600',
in_progress: 'bg-blue-600',
@@ -254,7 +183,8 @@
client_cancelled: 'bg-red-600',
we_cancelled: 'bg-rose-600',
're-schedule': 'bg-purple-600',
no_show: 'bg-gray-600'
no_show: 'bg-gray-600',
no_deposit: 'bg-orange-600'
};
return `mr-1 h-1.5 w-1.5 rounded-full ${statusMap[status] || 'bg-gray-600'}`;
}