Added booking approvals

This commit is contained in:
2026-01-19 22:32:25 +00:00
parent 73610e5d8e
commit 1dd37a8d22
7 changed files with 110 additions and 50 deletions
+1 -1
View File
@@ -42,7 +42,7 @@
"svelte-sonner": "^1.0.5",
"sveltekit-superforms": "^2.27.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^3.1.1",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.0.0",
"tw-animate-css": "^1.3.8",
"typescript": "^5.0.0",
@@ -4,6 +4,7 @@
import { toast } from 'svelte-sonner';
import * as Modal from '$lib/components/ui/dialog';
import { Button } from '$lib/components/ui/button';
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
interface Props {
open: boolean;
@@ -31,6 +32,8 @@
user?: {
id: string;
first_name: string;
last_name: string;
full_name: string;
email?: string;
phone?: string;
@@ -80,6 +83,13 @@
};
let selectedBooking = $state<Booking | null>(null);
let showApprovalModal = $state(false);
// Calculate total duration from services
let totalDuration = $derived(
selectedBooking?.services?.reduce((sum, service) => sum + (service.duration_minutes || 0), 0) ||
0
);
async function fetchBookingDetails() {
if (!bookingId) return;
@@ -104,6 +114,8 @@
user: data.user
? {
id: data.user.id,
first_name: data.user.first_name,
last_name: data.user.last_name,
full_name: data.user.full_name,
email: data.user.email,
phone: data.user.phone,
@@ -178,28 +190,35 @@
{/if}
</div>
{#if selectedBooking}
<span
class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium
{selectedBooking.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: selectedBooking.status === 'confirmed'
? 'bg-emerald-100 text-emerald-800'
: selectedBooking.status === 'in_progress'
? 'bg-blue-100 text-blue-800'
: selectedBooking.status === 'completed'
? 'bg-green-100 text-green-800'
: selectedBooking.status === 'client_cancelled'
? 'bg-red-100 text-red-800'
: selectedBooking.status === 'we_cancelled'
? 'bg-rose-100 text-rose-800'
: selectedBooking.status === 're-schedule'
? 'bg-purple-100 text-purple-800'
: selectedBooking.status === 'no_show'
? 'bg-gray-100 text-gray-800'
: 'bg-gray-100 text-gray-800'}"
>
{selectedBooking.status.charAt(0).toUpperCase() + selectedBooking.status.slice(1)}
</span>
{#if selectedBooking.status === 'pending'}
<div class="flex items-center gap-2">
<Button
onclick={() => (showApprovalModal = true)}
class="bg-emerald-600 hover:bg-emerald-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
Approve Booking
</Button>
<span
class="inline-flex items-center rounded-full bg-yellow-100 px-3 py-1 text-sm
font-medium text-yellow-800"
>
Pending
</span>
</div>
{/if}
{/if}
</div>
</Modal.Header>
@@ -233,7 +252,7 @@
</div>
<div>
<div class="text-xs text-gray-500">Duration</div>
<div class="font-medium">{selectedBooking.duration_minutes} minutes</div>
<div class="font-medium">{totalDuration} minutes</div>
</div>
<div>
<div class="text-xs text-gray-500">Created</div>
@@ -262,6 +281,29 @@
{/if}
</div>
<!-- Services -->
{#if selectedBooking.services && selectedBooking.services.length > 0}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Services
</h3>
<div class="space-y-3">
{#each selectedBooking.services as service, index (index)}
<div class="rounded-md border border-gray-300 bg-white p-3">
<div class="font-medium">{service.service_name || '—'}</div>
{#if service.service_description}
<div class="mt-1 text-sm text-gray-600">{service.service_description}</div>
{/if}
<div class="mt-2 flex items-center justify-between text-sm">
<span class="text-gray-600">{service.duration_minutes} min</span>
<span class="font-semibold">£{service.price?.toFixed(2) || '0.00'}</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
<!-- Customer Information -->
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
@@ -311,29 +353,6 @@
{/if}
</div>
<!-- Services -->
{#if selectedBooking.services && selectedBooking.services.length > 0}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Services
</h3>
<div class="space-y-3">
{#each selectedBooking.services as service, index (index)}
<div class="rounded-md border border-gray-300 bg-white p-3">
<div class="font-medium">{service.service_name || '—'}</div>
{#if service.service_description}
<div class="mt-1 text-sm text-gray-600">{service.service_description}</div>
{/if}
<div class="mt-2 flex items-center justify-between text-sm">
<span class="text-gray-600">{service.duration_minutes} min</span>
<span class="font-semibold">£{service.price?.toFixed(2) || '0.00'}</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
<!-- Financial Summary -->
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
@@ -435,3 +454,15 @@
</Modal.Footer>
</Modal.Content>
</Modal.Root>
<!-- Approval Sub-Modal -->
{#if selectedBooking && showApprovalModal}
<ApprovalModal
bind:open={showApprovalModal}
booking={selectedBooking}
onApproved={() => {
showApprovalModal = false;
fetchBookingDetails();
}}
/>
{/if}
+1 -1
View File
@@ -711,7 +711,7 @@
<li> Share your referral code with friends</li>
<li> They get 10% off their first booking</li>
<li> You get 10% off your next booking after they claim</li>
<li> You earn 1 loyalty stamp for each use to keep the savings going</li>
<li> You earn 3 loyalty stamp for each use to keep the savings going</li>
</ul>
</div>
</Card.Content>