Added booking approvals
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user