Add exceptional hour modals
This commit is contained in:
@@ -178,43 +178,83 @@
|
||||
id?: number;
|
||||
name: string;
|
||||
description: string;
|
||||
week_starts: string[];
|
||||
rows: WorkingHourRow[];
|
||||
weekStarts: string[];
|
||||
hours: WorkingHourRow[];
|
||||
};
|
||||
|
||||
// DEMO DATA: Exception Groups
|
||||
let exceptionGroups = $state<ExceptionGroup[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Christmas Week',
|
||||
description: 'Closed from Mon-Wed, open reduced hours Thu/Fri',
|
||||
week_starts: ['2025-12-22'],
|
||||
rows: [
|
||||
{ weekday: 0, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 1, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 2, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 3, start_time: '10:00', end_time: '15:00', is_open: true },
|
||||
{ weekday: 4, start_time: '10:00', end_time: '15:00', is_open: true },
|
||||
{ weekday: 5, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 6, start_time: '09:00', end_time: '17:00', is_open: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Summer Holiday',
|
||||
description: 'Closed on Mondays/Tuesdays only',
|
||||
week_starts: ['2026-07-06', '2026-07-13', '2026-07-20'],
|
||||
rows: [
|
||||
{ weekday: 0, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 1, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 2, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 3, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 4, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 5, start_time: '09:00', end_time: '17:00', is_open: false },
|
||||
{ weekday: 6, start_time: '09:00', end_time: '17:00', is_open: false }
|
||||
]
|
||||
// Replace the demo data with empty array and add loading state
|
||||
let exceptionGroups = $state<ExceptionGroup[]>([]);
|
||||
let exceptionGroupsLoading = $state(true);
|
||||
|
||||
// Add state for the exception modal
|
||||
let exceptionDraft = $state<ExceptionGroup>({
|
||||
name: '',
|
||||
description: '',
|
||||
weekStarts: [],
|
||||
hours: [
|
||||
{ weekday: 0, start_time: '00:00', end_time: '00:00', is_open: false },
|
||||
{ weekday: 1, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 2, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 3, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 4, start_time: '12:00', end_time: '20:00', is_open: true },
|
||||
{ weekday: 5, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 6, start_time: '00:00', end_time: '00:00', is_open: false }
|
||||
]
|
||||
});
|
||||
|
||||
let weekRangeFrom = $state('');
|
||||
let weekRangeTo = $state('');
|
||||
|
||||
async function fetchExceptionGroups() {
|
||||
if (pageState !== 'authorized') return;
|
||||
|
||||
exceptionGroupsLoading = true;
|
||||
try {
|
||||
const response = await fetch('/api/scheduling/exceptional-groups', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data === null || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
exceptionGroups = data.map((group: any) => ({
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
description: group.description,
|
||||
weekStarts: group.weekStarts || [],
|
||||
hours:
|
||||
group.hours?.map((h: any) => ({
|
||||
id: h.id,
|
||||
weekday: h.weekday,
|
||||
start_time: formatTime(h.startTime),
|
||||
end_time: formatTime(h.endTime),
|
||||
is_open: h.isOpen
|
||||
})) || []
|
||||
}));
|
||||
} else {
|
||||
console.error('Failed to fetch exception groups:', response.status);
|
||||
toast.error('Failed to load exception groups');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching exception groups:', err);
|
||||
toast.error('Network error loading exception groups');
|
||||
} finally {
|
||||
exceptionGroupsLoading = false;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// Fetch on mount
|
||||
$effect(() => {
|
||||
if (pageState === 'authorized') {
|
||||
fetchExceptionGroups();
|
||||
}
|
||||
});
|
||||
|
||||
let loadingHours = $state(false);
|
||||
let savingHours = $state(false);
|
||||
@@ -283,24 +323,133 @@
|
||||
}
|
||||
|
||||
async function saveExceptionGroup() {
|
||||
// TODO
|
||||
}
|
||||
// Validate
|
||||
if (!exceptionDraft.name.trim()) {
|
||||
toast.error('Please enter a group name');
|
||||
return;
|
||||
}
|
||||
|
||||
if (exceptionDraft.weekStarts.length === 0) {
|
||||
toast.error('Please add at least one week');
|
||||
return;
|
||||
}
|
||||
|
||||
savingHours = true;
|
||||
const loadingToast = toast.loading('Creating exception group...');
|
||||
|
||||
async function confirmDeleteExceptionGroup() {
|
||||
try {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
exceptionGroups = exceptionGroups.filter((g) => g.id !== exceptionToDelete);
|
||||
showDeleteExceptionAlert = false;
|
||||
exceptionToDelete = undefined;
|
||||
// Map to API format
|
||||
const payload = {
|
||||
name: exceptionDraft.name,
|
||||
description: exceptionDraft.description,
|
||||
weekStarts: exceptionDraft.weekStarts,
|
||||
hours: exceptionDraft.hours.map((h) => ({
|
||||
weekday: h.weekday,
|
||||
startTime: h.start_time,
|
||||
endTime: h.end_time,
|
||||
isOpen: h.is_open
|
||||
}))
|
||||
};
|
||||
|
||||
const response = await fetch('/api/scheduling/exceptional-groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
toast.success('Exception group created successfully!', { id: loadingToast });
|
||||
showExceptionModal = false;
|
||||
resetExceptionForm();
|
||||
await fetchExceptionGroups();
|
||||
} else {
|
||||
const text = await response.text();
|
||||
toast.error('Failed to create: ' + text, { id: loadingToast });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error('Error creating exception group:', err);
|
||||
toast.error('Network error creating exception group', { id: loadingToast });
|
||||
} finally {
|
||||
savingHours = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDeleteExceptionGroup() {
|
||||
if (exceptionToDelete === undefined) return;
|
||||
|
||||
const loadingToast = toast.loading('Deleting exception group...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/scheduling/exceptional-groups?id=${exceptionToDelete}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
toast.success('Exception group deleted successfully!', { id: loadingToast });
|
||||
showDeleteExceptionAlert = false;
|
||||
exceptionToDelete = undefined;
|
||||
// Refresh the exception groups list
|
||||
await fetchExceptionGroups();
|
||||
} else {
|
||||
const text = await response.text();
|
||||
toast.error('Failed to delete: ' + text, { id: loadingToast });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting exception group:', err);
|
||||
toast.error('Network error deleting exception group', { id: loadingToast });
|
||||
}
|
||||
}
|
||||
|
||||
function openViewExceptionModal(exception: ExceptionGroup) {
|
||||
viewingException = exception;
|
||||
showViewExceptionModal = true;
|
||||
}
|
||||
|
||||
function resetExceptionForm() {
|
||||
exceptionDraft = {
|
||||
name: '',
|
||||
description: '',
|
||||
weekStarts: [],
|
||||
hours: [
|
||||
{ weekday: 0, start_time: '00:00', end_time: '00:00', is_open: false },
|
||||
{ weekday: 1, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 2, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 3, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 4, start_time: '12:00', end_time: '20:00', is_open: true },
|
||||
{ weekday: 5, start_time: '09:00', end_time: '17:00', is_open: true },
|
||||
{ weekday: 6, start_time: '00:00', end_time: '00:00', is_open: false }
|
||||
]
|
||||
};
|
||||
weekRangeFrom = '';
|
||||
weekRangeTo = '';
|
||||
}
|
||||
|
||||
function createNewException() {
|
||||
resetExceptionForm();
|
||||
showExceptionModal = true;
|
||||
}
|
||||
|
||||
function addWeekRange() {
|
||||
if (!weekRangeFrom || !weekRangeTo) {
|
||||
toast.error('Please select both start and end dates');
|
||||
return;
|
||||
}
|
||||
|
||||
addWeeksToException(weekRangeFrom, weekRangeTo, exceptionDraft.weekStarts);
|
||||
weekRangeFrom = '';
|
||||
weekRangeTo = '';
|
||||
}
|
||||
|
||||
function removeWeek(index: number) {
|
||||
exceptionDraft.weekStarts = exceptionDraft.weekStarts.filter((_, i) => i !== index);
|
||||
}
|
||||
|
||||
// =============== Users & Bookings ===============
|
||||
type User = {
|
||||
id: string;
|
||||
@@ -464,8 +613,12 @@
|
||||
const to = new Date(toISO + 'T00:00:00');
|
||||
const first = new Date(from);
|
||||
const day = first.getDay();
|
||||
const mondayOffset = (day + 6) % 7;
|
||||
first.setDate(first.getDate() - mondayOffset);
|
||||
const daysToMonday = day === 0 ? -6 : 1 - day;
|
||||
|
||||
// Set to the Monday of the current week
|
||||
first.setDate(first.getDate() + daysToMonday);
|
||||
|
||||
// Add all Mondays in the range
|
||||
for (let d = new Date(first); d <= to; d.setDate(d.getDate() + 7)) {
|
||||
dest.push(isoDateOf(new Date(d)));
|
||||
}
|
||||
@@ -819,6 +972,8 @@
|
||||
let showSaveDefaultHoursAlert = $state(false);
|
||||
let showDeleteExceptionAlert = $state(false);
|
||||
let exceptionToDelete = $state<number | undefined>(undefined);
|
||||
let showViewExceptionModal = $state(false);
|
||||
let viewingException = $state<ExceptionGroup | null>(null);
|
||||
</script>
|
||||
|
||||
{#if pageState === 'loading'}
|
||||
@@ -1130,52 +1285,52 @@
|
||||
<Button variant="default" onclick={createNewException}>New schedule</Button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 space-y-3 md:grid-cols-2">
|
||||
{#if exceptionGroups.length === 0}
|
||||
<p class="text-sm text-gray-500">No exception groups found.</p>
|
||||
{/if}
|
||||
{#if exceptionGroupsLoading}
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{#each Array(2) as _, i}
|
||||
<Skeleton class="h-32 w-full" />
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{#if exceptionGroups.length === 0}
|
||||
<p class="col-span-2 text-sm text-gray-500">No exception groups found.</p>
|
||||
{/if}
|
||||
|
||||
{#each exceptionGroups as g}
|
||||
<div class="relative h-full rounded border p-3 pb-12">
|
||||
<!-- add bottom padding to avoid overlap -->
|
||||
<div class="flex h-full flex-col gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">{g.name}</div>
|
||||
<div class="text-sm text-gray-600">{g.description}</div>
|
||||
<div class="mt-1 text-xs text-gray-500">
|
||||
Applies to weeks: {g.week_starts?.slice(0, 5).join(', ')}
|
||||
{#if (g.week_starts?.length ?? 0) > 5}
|
||||
(+{(g.week_starts?.length ?? 0) - 5} more)
|
||||
{/if}
|
||||
{#each exceptionGroups as g}
|
||||
<div class="relative h-full rounded border p-3 pb-12">
|
||||
<div class="flex h-full flex-col gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">{g.name}</div>
|
||||
<div class="text-sm text-gray-600">{g.description}</div>
|
||||
<div class="mt-1 text-xs text-gray-500">
|
||||
Applies to weeks: {g.weekStarts?.slice(0, 5).join(', ')}
|
||||
{#if (g.weekStarts?.length ?? 0) > 5}
|
||||
(+{(g.weekStarts?.length ?? 0) - 5} more)
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-3 right-3 flex gap-2">
|
||||
<Button variant="default" size="sm" onclick={() => openViewExceptionModal(g)}>
|
||||
View
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onclick={() => {
|
||||
exceptionToDelete = g.id;
|
||||
showDeleteExceptionAlert = true;
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Absolute positioned button -->
|
||||
<div class="absolute bottom-3 right-3">
|
||||
<Button
|
||||
variant="default"
|
||||
class="w-20"
|
||||
onclick={() => {
|
||||
alert('TODO - view group');
|
||||
}}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
class="w-20"
|
||||
onclick={() => {
|
||||
exceptionToDelete = g.id;
|
||||
showDeleteExceptionAlert = true;
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -1530,21 +1685,149 @@
|
||||
|
||||
<!-- Exception Group Modal -->
|
||||
<Modal.Root bind:open={showExceptionModal}>
|
||||
<Modal.Content class="max-h-[90vh] max-w-sm overflow-y-auto p-4 md:max-w-lg">
|
||||
<Modal.Header class="mb-4 p-0">
|
||||
<Modal.Title class="text-lg font-semibold">New Exception Group</Modal.Title>
|
||||
<Modal.Content class="max-h-[90vh] max-w-2xl overflow-y-auto">
|
||||
<Modal.Header>
|
||||
<Modal.Title class="text-lg font-semibold">Create Exception Schedule</Modal.Title>
|
||||
<Modal.Description>
|
||||
Define custom working hours for holidays, closures, or special events.
|
||||
</Modal.Description>
|
||||
</Modal.Header>
|
||||
<Modal.Footer class="flex items-center justify-end gap-2 p-0 pt-4">
|
||||
|
||||
<div class="space-y-6 px-4 pb-4">
|
||||
<!-- Basic Info -->
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label for="exception-name" class="text-sm font-medium">Schedule Name *</label>
|
||||
<Input
|
||||
id="exception-name"
|
||||
type="text"
|
||||
placeholder="e.g., Christmas Week, Summer Holiday"
|
||||
bind:value={exceptionDraft.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="exception-description" class="text-sm font-medium">Description</label>
|
||||
<Input
|
||||
id="exception-description"
|
||||
type="text"
|
||||
placeholder="Brief description of the service, will be shown to customers"
|
||||
bind:value={exceptionDraft.description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Week Selection -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-medium">Apply to Weeks *</h3>
|
||||
<p class="mb-3 text-xs text-gray-500">
|
||||
Select a date range to add all Mondays within that range
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div class="space-y-2">
|
||||
<label for="week-from" class="text-xs text-gray-600">From Date</label>
|
||||
<Input id="week-from" type="date" bind:value={weekRangeFrom} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label for="week-to" class="text-xs text-gray-600">To Date</label>
|
||||
<Input id="week-to" type="date" bind:value={weekRangeTo} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" size="sm" onclick={addWeekRange} class="mt-3">
|
||||
Add Week Range
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if exceptionDraft.weekStarts.length > 0}
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs text-gray-600">
|
||||
Selected weeks ({exceptionDraft.weekStarts.length}):
|
||||
</div>
|
||||
<div class="max-h-32 space-y-1 overflow-y-auto rounded border p-2">
|
||||
{#each exceptionDraft.weekStarts as week, index}
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span>Week starting: {week}</span>
|
||||
<button
|
||||
class="text-xs text-red-500 hover:text-red-700"
|
||||
onclick={() => removeWeek(index)}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Working Hours -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-medium">Working Hours for these Weeks *</h3>
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full table-auto text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-xs text-gray-500">
|
||||
<th class="py-2">Day</th>
|
||||
<th class="py-2">Open</th>
|
||||
<th class="py-2">Start</th>
|
||||
<th class="py-2">End</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each exceptionDraft.hours as row}
|
||||
<tr class="border-t">
|
||||
<td class="py-2">{weekdayLabel(row.weekday)}</td>
|
||||
<td class="py-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={row.is_open}
|
||||
class="h-4 w-4 rounded border-gray-300 bg-gray-100"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
bind:value={row.start_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
/>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<Input
|
||||
type="time"
|
||||
bind:value={row.end_time}
|
||||
disabled={!row.is_open}
|
||||
class="w-24 text-sm"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer class="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
showExceptionModal = false;
|
||||
resetExceptionForm();
|
||||
}}
|
||||
disabled={savingHours}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onclick={saveExceptionGroup} disabled={savingHours}>
|
||||
{savingHours ? 'Saving…' : 'Save'}
|
||||
{savingHours ? 'Creating…' : 'Create Schedule'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
@@ -1573,6 +1856,89 @@
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
|
||||
<!-- View Exception Modal -->
|
||||
{#if viewingException}
|
||||
<Modal.Root bind:open={showViewExceptionModal}>
|
||||
<Modal.Content class="max-h-[90vh] max-w-2xl overflow-y-auto">
|
||||
<Modal.Header>
|
||||
<Modal.Title class="text-lg font-semibold">{viewingException.name}</Modal.Title>
|
||||
<Modal.Description>
|
||||
{viewingException.description || 'Holiday schedule details'}
|
||||
</Modal.Description>
|
||||
</Modal.Header>
|
||||
|
||||
<div class="space-y-6 px-4 pb-4">
|
||||
<!-- Applied Weeks -->
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-medium">Applied to Weeks</h3>
|
||||
<div class="max-h-48 space-y-1 overflow-y-auto rounded border bg-gray-50 p-3">
|
||||
{#if viewingException.weekStarts && viewingException.weekStarts.length > 0}
|
||||
<div class="grid grid-cols-2 gap-2 md:grid-cols-3">
|
||||
{#each viewingException.weekStarts as week}
|
||||
<div class="text-sm">
|
||||
Week of {new Date(week).toLocaleDateString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-gray-500">No weeks specified</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Working Hours -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-medium">Working Hours</h3>
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full table-auto">
|
||||
<thead>
|
||||
<tr class="text-left text-xs text-gray-500">
|
||||
<th class="py-2">Day</th>
|
||||
<th class="py-2">Status</th>
|
||||
<th class="py-2">Start</th>
|
||||
<th class="py-2">End</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each viewingException.hours as row}
|
||||
<tr class="border-t">
|
||||
<td class="py-2 text-sm">{weekdayLabel(row.weekday)}</td>
|
||||
<td class="py-2">
|
||||
<span
|
||||
class="text-sm font-medium {row.is_open
|
||||
? 'text-emerald-600'
|
||||
: 'text-red-600'}"
|
||||
>
|
||||
{row.is_open ? 'Open' : 'Closed'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-2 text-sm">
|
||||
{row.is_open ? row.start_time : '—'}
|
||||
</td>
|
||||
<td class="py-2 text-sm">
|
||||
{row.is_open ? row.end_time : '—'}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer class="flex items-center justify-end gap-2">
|
||||
<Button onclick={() => (showViewExceptionModal = false)}>Close</Button>
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
{/if}
|
||||
|
||||
<!-- User Modal -->
|
||||
{#if selectedUser}
|
||||
<Modal.Root bind:open={showUserModal}>
|
||||
@@ -1669,7 +2035,7 @@
|
||||
<Input
|
||||
id="service-description"
|
||||
type="text"
|
||||
placeholder="Brief description of the service"
|
||||
placeholder="Brief description of the service, will be shown to customers"
|
||||
bind:value={newService.description}
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user