fix: admin booking modals and schedule components polish
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -5,17 +5,20 @@
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
|
||||
import RescheduleModal from '$lib/components/admin/RescheduleModal.svelte';
|
||||
import type { Booking } from '$lib/types/booking';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
bookingId: string;
|
||||
onReschedule?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), bookingId }: Props = $props();
|
||||
let { open = $bindable(), bookingId, onReschedule }: Props = $props();
|
||||
|
||||
let selectedBooking = $state<Booking | null>(null);
|
||||
let showApprovalModal = $state(false);
|
||||
let showRescheduleModal = $state(false);
|
||||
|
||||
// Calculate total duration from services
|
||||
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
||||
@@ -131,7 +134,7 @@
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-3xl">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
|
||||
<Modal.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -302,7 +305,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Email</div>
|
||||
<div class="font-medium break-all">{selectedBooking.user?.email || '—'}</div>
|
||||
<div class="font-medium break-words" title={selectedBooking.user?.email}>{selectedBooking.user?.email || '—'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Phone</div>
|
||||
@@ -478,6 +481,24 @@
|
||||
{/if}
|
||||
|
||||
<Modal.Footer class="flex items-center justify-end gap-2">
|
||||
{#if selectedBooking && !['completed', 'client_cancelled', 'we_cancelled', 'no_show', 'no_deposit'].includes(selectedBooking.status)}
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => (showRescheduleModal = true)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-4 w-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2" />
|
||||
</svg>
|
||||
Reschedule
|
||||
</Button>
|
||||
{/if}
|
||||
<Button onclick={() => (open = false)}>Close</Button>
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
@@ -494,3 +515,16 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Reschedule Sub-Modal -->
|
||||
{#if selectedBooking && showRescheduleModal}
|
||||
<RescheduleModal
|
||||
bind:open={showRescheduleModal}
|
||||
booking={selectedBooking}
|
||||
onSaved={() => {
|
||||
showRescheduleModal = false;
|
||||
fetchBookingDetails();
|
||||
onReschedule?.();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-3xl">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
|
||||
<Modal.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -418,7 +418,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Email</div>
|
||||
<div class="font-medium break-all">{booking.user?.email || '—'}</div>
|
||||
<div class="font-medium break-words" title={booking.user?.email}>{booking.user?.email || '—'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Phone</div>
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
|
||||
const TIME15 = ['00', '15', '30', '45'];
|
||||
|
||||
// =============== Types ===============
|
||||
type WorkingHourRow = {
|
||||
weekday: number;
|
||||
@@ -527,20 +529,30 @@
|
||||
<Checkbox bind:checked={row.is_open} />
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
bind:value={row.start_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
/>
|
||||
<select
|
||||
bind:value={row.start_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
>
|
||||
{#each Array(24) as _, hour}
|
||||
{#each TIME15 as min}
|
||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||
{/each}
|
||||
{/each}
|
||||
</select>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
bind:value={row.end_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
/>
|
||||
<select
|
||||
bind:value={row.end_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
>
|
||||
{#each Array(24) as _, hour}
|
||||
{#each TIME15 as min}
|
||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||
{/each}
|
||||
{/each}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
@@ -225,13 +225,12 @@
|
||||
});
|
||||
|
||||
function handleOpenBooking(bookingId: string) {
|
||||
open = false;
|
||||
openBookingModal(bookingId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-3xl">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl">
|
||||
<Modal.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -271,7 +270,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Email</div>
|
||||
<div class="font-medium break-all">{selectedUser.email || '—'}</div>
|
||||
<div class="font-medium break-words" title={selectedUser.email}>{selectedUser.email || '—'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Phone</div>
|
||||
@@ -280,13 +279,23 @@
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Date of Birth</div>
|
||||
<div class="font-medium">
|
||||
{selectedUser.dateOfBirth
|
||||
? new SvelteDate(selectedUser.dateOfBirth).toLocaleDateString('en-US', {
|
||||
{#if selectedUser.dateOfBirth}
|
||||
{(() => {
|
||||
const dob = new SvelteDate(selectedUser.dateOfBirth);
|
||||
const dateStr = dob.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
: '—'}
|
||||
});
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - dob.getFullYear();
|
||||
const m = today.getMonth() - dob.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
|
||||
return `${dateStr} (${age})`;
|
||||
})()}
|
||||
{:else}
|
||||
—
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -308,9 +317,10 @@
|
||||
<div class="font-medium">
|
||||
{#if customerRelationship?.lastVisitDate}
|
||||
{new SvelteDate(customerRelationship.lastVisitDate).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
year: 'numeric'
|
||||
})}
|
||||
{:else}
|
||||
—
|
||||
@@ -324,7 +334,7 @@
|
||||
<img
|
||||
src={selectedUser.profilePicUrl}
|
||||
alt="Profile"
|
||||
class="mt-2 h-24 w-24 rounded-lg object-cover"
|
||||
class="mt-2 h-20 w-20 md:h-24 md:w-24 rounded-lg object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -373,6 +373,7 @@
|
||||
if (res.ok) {
|
||||
toast.success('Booking created successfully!');
|
||||
open = false;
|
||||
window.dispatchEvent(new CustomEvent('bookingApproved'));
|
||||
onBookingCreated?.();
|
||||
} else {
|
||||
const errorText = await res.text();
|
||||
@@ -413,7 +414,7 @@
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-h-[90vh] max-w-4xl overflow-y-auto">
|
||||
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl md:max-w-4xl">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Walk-In Booking</Modal.Title>
|
||||
<Modal.Description>
|
||||
@@ -724,7 +725,7 @@
|
||||
{#if serviceOverrides[service.id]}
|
||||
<div class="rounded-lg border bg-white p-4">
|
||||
<div class="mb-3 font-medium">{service.name}</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<Label for="price-{service.id}" class="text-xs text-gray-600"
|
||||
>Price (£)</Label
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
// shadcn-svelte components
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
|
||||
const TIME15 = ['00', '15', '30', '45'];
|
||||
|
||||
// =============== Types ===============
|
||||
type WorkingHourRow = {
|
||||
weekday: number;
|
||||
@@ -479,20 +480,30 @@
|
||||
<Checkbox bind:checked={row.is_open} />
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
<select
|
||||
bind:value={row.start_time}
|
||||
disabled={!row.is_open}
|
||||
class="max-w-[70px] text-sm"
|
||||
/>
|
||||
>
|
||||
{#each Array(24) as _, hour}
|
||||
{#each TIME15 as min}
|
||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||
{/each}
|
||||
{/each}
|
||||
</select>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
<select
|
||||
bind:value={row.end_time}
|
||||
disabled={!row.is_open}
|
||||
class="max-w-[70px] text-sm"
|
||||
/>
|
||||
>
|
||||
{#each Array(24) as _, hour}
|
||||
{#each TIME15 as min}
|
||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||
{/each}
|
||||
{/each}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user