Mobile optimise

This commit is contained in:
2025-10-13 23:30:45 +01:00
parent b474b8a41c
commit 8b3d08ce84
+104 -140
View File
@@ -44,6 +44,7 @@
fd.append('file', file); fd.append('file', file);
try { try {
// Note: API call is mocked here, replace with your actual endpoint
// Mock success/fail // Mock success/fail
await new Promise((r) => setTimeout(r, 500)); // Simulate network delay await new Promise((r) => setTimeout(r, 500)); // Simulate network delay
if (file.name.toLowerCase().includes('fail')) { if (file.name.toLowerCase().includes('fail')) {
@@ -142,7 +143,6 @@
let editingException = $state<ExceptionGroup | null>(null); let editingException = $state<ExceptionGroup | null>(null);
let editingExceptionIndex = $state<number | null>(null); let editingExceptionIndex = $state<number | null>(null);
let showExceptionModal = $state(false); let showExceptionModal = $state(false);
let showUploadModal = $state(false); // Kept in case you want to toggle visibility
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
@@ -463,34 +463,34 @@
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
<!-- Default Hours Card -->
<Card.Root> <Card.Root>
<Card.Header> <Card.Header>
<Card.Title>Working Hours</Card.Title> <Card.Title>Working Hours</Card.Title>
<Card.Description> <Card.Description>View and manage your standard weekly schedule.</Card.Description>
View default hours for each weekday, and manage exceptions.
</Card.Description>
</Card.Header> </Card.Header>
<Card.Content class="space-y-4"> <Card.Content class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 class="font-semibold">Default Hours</h3> <h3 class="font-semibold">Weekly Schedule</h3>
<Button onclick={prepareDefaultHoursEdit} disabled={loadingHours || savingHours}> <Button onclick={prepareDefaultHoursEdit} disabled={loadingHours || savingHours}>
Edit Defaults Edit Defaults
</Button> </Button>
</div> </div>
<div class="overflow-x-auto"> <div class="w-full overflow-x-auto">
<table class="w-full table-auto"> <table class="w-full table-auto">
<thead> <thead>
<tr class="text-left text-xs text-gray-500"> <tr class="text-left text-xs text-gray-500">
<th class="py-2">Day</th> <th class="py-2">Day</th>
<th class="py-2">Status</th> <th class="py-2">Start</th> <th class="py-2">Status</th>
<th class="py-2">Start</th>
<th class="py-2">End</th> <th class="py-2">End</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each defaultHours as row, i} {#each defaultHours as row}
<tr class="border-t"> <tr class="border-t">
<td class="py-2">{weekdayLabel(row.weekday)}</td> <td class="py-2 text-sm">{weekdayLabel(row.weekday)}</td>
<td class="py-2"> <td class="py-2">
<span <span
class="text-sm font-medium {row.is_open ? 'text-green-600' : 'text-red-600'}" class="text-sm font-medium {row.is_open ? 'text-green-600' : 'text-red-600'}"
@@ -498,34 +498,31 @@
{row.is_open ? 'Open' : 'Closed'} {row.is_open ? 'Open' : 'Closed'}
</span> </span>
</td> </td>
<td class="py-2"> <td class="py-2 text-sm">
<input {row.is_open ? row.start_time : '—'}
class="rounded border bg-gray-100 px-2 py-1"
type="time"
value={row.start_time}
disabled
/>
</td> </td>
<td class="py-2"> <td class="py-2 text-sm">
<input {row.is_open ? row.end_time : '—'}
class="rounded border bg-gray-100 px-2 py-1"
type="time"
value={row.end_time}
disabled
/>
</td> </td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
</div> </div>
<Separator /> </Card.Content>
</Card.Root>
<!-- Exception Groups Card -->
<Card.Root>
<Card.Header>
<Card.Title>Exception Groups</Card.Title>
<Card.Description>
Manage temporary schedules for holidays, closures, and special events.
</Card.Description>
</Card.Header>
<Card.Content class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div>
<h3 class="font-semibold">Exception Groups</h3>
<p class="text-sm text-gray-500">Temporary schedules such as 'Christmas' or 'Holiday'</p>
</div>
<Button variant="outline" onclick={createNewException}>New Exception Group</Button> <Button variant="outline" onclick={createNewException}>New Exception Group</Button>
</div> </div>
@@ -572,66 +569,67 @@
<Card.Root> <Card.Root>
<Card.Header> <Card.Header>
<Card.Title>Users & Bookings</Card.Title> <Card.Title>Users</Card.Title>
<Card.Description> <Card.Description>Search and manage user details.</Card.Description>
Search users or bookings. Click an item to view details and history.
</Card.Description>
</Card.Header> </Card.Header>
<Card.Content class="space-y-4"> <Card.Content class="space-y-4">
<div class="grid gap-4 md:grid-cols-2"> <div>
<div> <div class="flex gap-2">
<Label>Search Users</Label> <Input
<div class="flex gap-2"> placeholder="Name, email or phone"
<Input bind:value={userQuery}
placeholder="Name, email or phone" onkeyup={(e) => {
bind:value={userQuery} if ((e as KeyboardEvent).key === 'Enter') searchUsers();
onkeyup={(e) => { }}
if ((e as KeyboardEvent).key === 'Enter') searchUsers(); />
}} <Button onclick={searchUsers} disabled={loadingSearch}>Search</Button>
/>
<Button onclick={searchUsers} disabled={loadingSearch}>Search</Button>
</div>
<div class="mt-3 max-h-60 space-y-2 overflow-y-auto">
{#each users as u}
<div class="flex items-center justify-between rounded bg-gray-50 p-2">
<div>
<div class="font-medium">{u.fn || `${u.n_first_name} ${u.n_last_name}`}</div>
<div class="text-xs text-gray-500">{u.email} • {u.phone}</div>
</div>
<Button variant="outline" onclick={() => openUserModal(u.id)}>View</Button>
</div>
{/each}
</div>
</div> </div>
<div> <div class="mt-3 max-h-60 space-y-2 overflow-y-auto">
<Label>Search Bookings</Label> {#each users as u}
<div class="flex gap-2"> <div class="flex items-center justify-between rounded bg-gray-50 p-2">
<Input <div>
placeholder="Booking ID or user" <div class="font-medium">{u.fn || `${u.n_first_name} ${u.n_last_name}`}</div>
bind:value={bookingQuery} <div class="text-xs text-gray-500">{u.email} • {u.phone}</div>
onkeyup={(e) => {
if ((e as KeyboardEvent).key === 'Enter') searchBookings();
}}
/>
<Button onclick={searchBookings} disabled={loadingSearch}>Search</Button>
</div>
<div class="mt-3 max-h-60 space-y-2 overflow-y-auto">
{#each bookings as b}
<div class="flex items-center justify-between rounded bg-gray-50 p-2">
<div>
<div class="font-medium">{new Date(b.start_time).toLocaleString()}</div>
<div class="text-xs text-gray-500">
{b.status} • {b.services?.map((s) => s.name).join(', ')}
</div>
</div>
<Button variant="outline" onclick={() => openBookingModal(b.id)}>View</Button>
</div> </div>
{/each} <Button variant="outline" onclick={() => openUserModal(u.id)}>View</Button>
</div> </div>
{/each}
</div>
</div>
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<Card.Title>Bookings</Card.Title>
<Card.Description>Search and manage booking history.</Card.Description>
</Card.Header>
<Card.Content class="space-y-4">
<div>
<div class="flex gap-2">
<Input
placeholder="Booking ID or user"
bind:value={bookingQuery}
onkeyup={(e) => {
if ((e as KeyboardEvent).key === 'Enter') searchBookings();
}}
/>
<Button onclick={searchBookings} disabled={loadingSearch}>Search</Button>
</div>
<div class="mt-3 max-h-60 space-y-2 overflow-y-auto">
{#each bookings as b}
<div class="flex items-center justify-between rounded bg-gray-50 p-2">
<div>
<div class="font-medium">{new Date(b.start_time).toLocaleString()}</div>
<div class="text-xs text-gray-500">
{b.status} • {b.services?.map((s) => s.name).join(', ')}
</div>
</div>
<Button variant="outline" onclick={() => openBookingModal(b.id)}>View</Button>
</div>
{/each}
</div> </div>
</div> </div>
</Card.Content> </Card.Content>
@@ -639,7 +637,7 @@
</div> </div>
<Modal.Root bind:open={showDefaultHoursModal}> <Modal.Root bind:open={showDefaultHoursModal}>
<Modal.Content> <Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-lg">
<Modal.Header> <Modal.Header>
<Modal.Title class="text-lg font-semibold">Edit Default Working Hours</Modal.Title> <Modal.Title class="text-lg font-semibold">Edit Default Working Hours</Modal.Title>
<Modal.Description> <Modal.Description>
@@ -648,7 +646,7 @@
</Modal.Header> </Modal.Header>
<div class="px-4 pb-4"> <div class="px-4 pb-4">
<div class="overflow-x-auto"> <div class="w-full overflow-x-auto">
<table class="w-full table-auto"> <table class="w-full table-auto">
<thead> <thead>
<tr class="text-left text-xs text-gray-500"> <tr class="text-left text-xs text-gray-500">
@@ -661,7 +659,7 @@
<tbody> <tbody>
{#each defaultHoursDraft as row} {#each defaultHoursDraft as row}
<tr class="border-t"> <tr class="border-t">
<td class="py-2">{weekdayLabel(row.weekday)}</td> <td class="py-2 text-sm">{weekdayLabel(row.weekday)}</td>
<td class="py-2"> <td class="py-2">
<input <input
type="checkbox" type="checkbox"
@@ -674,7 +672,7 @@
type="time" type="time"
bind:value={row.start_time} bind:value={row.start_time}
disabled={!row.is_open} disabled={!row.is_open}
class="max-w-24" class="max-w-[70px] text-sm"
/> />
</td> </td>
<td class="py-2"> <td class="py-2">
@@ -682,7 +680,7 @@
type="time" type="time"
bind:value={row.end_time} bind:value={row.end_time}
disabled={!row.is_open} disabled={!row.is_open}
class="max-w-24" class="max-w-[70px] text-sm"
/> />
</td> </td>
</tr> </tr>
@@ -709,15 +707,15 @@
</Modal.Root> </Modal.Root>
<Modal.Root bind:open={showExceptionModal}> <Modal.Root bind:open={showExceptionModal}>
<Modal.Content> <Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto p-4 md:max-w-lg">
<Modal.Header> <Modal.Header class="mb-4 p-0">
<Modal.Title class="text-lg font-semibold"> <Modal.Title class="text-lg font-semibold">
{editingExceptionIndex === null ? 'New Exception Group' : 'Edit Exception Group'} {editingExceptionIndex === null ? 'New Exception Group' : 'Edit Exception Group'}
</Modal.Title> </Modal.Title>
</Modal.Header> </Modal.Header>
{#if editingException} {#if editingException}
<div class="space-y-4 px-4 pb-4"> <div class="space-y-4 pb-4">
<div> <div>
<Label>Name</Label> <Label>Name</Label>
<Input bind:value={editingException.name} placeholder="e.g. Christmas" /> <Input bind:value={editingException.name} placeholder="e.g. Christmas" />
@@ -727,16 +725,17 @@
<Textarea <Textarea
rows={3} rows={3}
bind:value={editingException.description} bind:value={editingException.description}
placeholder="A short description of the exception" placeholder="e.g. Extended weekend hours and shortened weekday hours for the holiday period"
/> />
</div> </div>
<div> <div>
<Label>Week range to apply</Label> <Label>Week range to apply</Label>
<div class="flex gap-2"> <div class="flex flex-wrap gap-2">
<Input type="date" id="exception_from" /> <Input type="date" id="exception_from" class="min-w-[120px] flex-1" />
<Input type="date" id="exception_to" /> <Input type="date" id="exception_to" class="min-w-[120px] flex-1" />
<Button <Button
class="w-full sm:w-auto"
onclick={() => { onclick={() => {
const fromEl = document.getElementById('exception_from') as HTMLInputElement; const fromEl = document.getElementById('exception_from') as HTMLInputElement;
const toEl = document.getElementById('exception_to') as HTMLInputElement; const toEl = document.getElementById('exception_to') as HTMLInputElement;
@@ -792,9 +791,14 @@
type="time" type="time"
bind:value={r.start_time} bind:value={r.start_time}
disabled={!r.is_open} disabled={!r.is_open}
class="max-w-24" class="max-w-[70px] text-sm"
/>
<Input
type="time"
bind:value={r.end_time}
disabled={!r.is_open}
class="max-w-[70px] text-sm"
/> />
<Input type="time" bind:value={r.end_time} disabled={!r.is_open} class="max-w-24" />
</div> </div>
{/each} {/each}
</div> </div>
@@ -802,7 +806,7 @@
</div> </div>
{/if} {/if}
<Modal.Footer class="flex items-center justify-end gap-2"> <Modal.Footer class="flex items-center justify-end gap-2 p-0 pt-4">
<Button <Button
variant="outline" variant="outline"
onclick={() => { onclick={() => {
@@ -819,49 +823,9 @@
</Modal.Content> </Modal.Content>
</Modal.Root> </Modal.Root>
<Modal.Root bind:open={showUploadModal}>
<Modal.Content>
<Modal.Header>
<Modal.Title class="text-lg font-semibold">Upload Images</Modal.Title>
</Modal.Header>
<div class="px-4 pb-4">
<FileDropZone onfiles={handleFilesDropped} accept="image/*" multiple>
<div class="p-6 text-center">
<p class="text-sm text-gray-500">Drop files here, or click to open the file picker</p>
</div>
</FileDropZone>
<div class="mt-4">
<div class="text-sm text-gray-600">Selected files</div>
<div class="mt-2 space-y-2">
{#each uploadFiles as f}
<div class="flex items-center justify-between rounded bg-gray-50 p-2">
<div>{f.name} • {Math.round(f.size / 1024)}KB</div>
<button
class="text-red-500"
onclick={() => (uploadFiles = uploadFiles.filter((x) => x !== f))}
>
Remove
</button>
</div>
{/each}
</div>
</div>
</div>
<Modal.Footer class="flex items-center justify-end gap-2">
<Button variant="outline" onclick={() => (showUploadModal = false)}>Close</Button>
<Button onclick={uploadOneOrMany} disabled={!uploadFiles.length || uploading}>
{uploading ? `Uploading (${uploadProgress}%)` : 'Upload'}
</Button>
</Modal.Footer>
</Modal.Content>
</Modal.Root>
{#if selectedUser} {#if selectedUser}
<Modal.Root bind:open={showUserModal}> <Modal.Root bind:open={showUserModal}>
<Modal.Content> <Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-lg">
<Modal.Header> <Modal.Header>
<Modal.Title class="text-lg font-semibold"> <Modal.Title class="text-lg font-semibold">
User: {selectedUser.fn || `${selectedUser.n_first_name} ${selectedUser.n_last_name}`} User: {selectedUser.fn || `${selectedUser.n_first_name} ${selectedUser.n_last_name}`}
@@ -936,7 +900,7 @@
{#if selectedBooking} {#if selectedBooking}
<Modal.Root bind:open={showBookingModal}> <Modal.Root bind:open={showBookingModal}>
<Modal.Content> <Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto md:max-w-lg">
<Modal.Header> <Modal.Header>
<Modal.Title class="text-lg font-semibold">Booking {selectedBooking.id}</Modal.Title> <Modal.Title class="text-lg font-semibold">Booking {selectedBooking.id}</Modal.Title>
</Modal.Header> </Modal.Header>