refactor: today page components with calendar grid and stats
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { authStore } from '$lib/stores/auth.svelte';
|
import { authStore } from '$lib/stores/auth.svelte';
|
||||||
import { SvelteDate } from 'svelte/reactivity';
|
import { SvelteDate } from 'svelte/reactivity';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { formatDuration } from '$lib/utils/format';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { Badge } from '$lib/components/ui/badge';
|
import { Badge } from '$lib/components/ui/badge';
|
||||||
@@ -184,8 +185,8 @@
|
|||||||
|
|
||||||
<Card.Root class="border-2 border-blue-200 bg-blue-50">
|
<Card.Root class="border-2 border-blue-200 bg-blue-50">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<Card.Title class="text-xl md:text-2xl">
|
<Card.Title class="text-xl md:text-2xl">
|
||||||
{isInProgress ? 'Current Appointment' : 'Next Appointment'}
|
{isInProgress ? 'Current Appointment' : 'Next Appointment'}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
@@ -211,8 +212,8 @@
|
|||||||
|
|
||||||
{#if activeAppointment}
|
{#if activeAppointment}
|
||||||
{#if isInProgress}
|
{#if isInProgress}
|
||||||
<div class="flex flex-col gap-2 items-end">
|
<div class="flex flex-row flex-wrap gap-2 sm:flex-col sm:items-end">
|
||||||
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
|
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800 whitespace-nowrap">
|
||||||
<span class="relative mr-2 flex h-2 w-2">
|
<span class="relative mr-2 flex h-2 w-2">
|
||||||
<span
|
<span
|
||||||
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75"
|
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75"
|
||||||
@@ -223,18 +224,18 @@
|
|||||||
</Badge>
|
</Badge>
|
||||||
{#if timeRemaining > 29}
|
{#if timeRemaining > 29}
|
||||||
{#if freeTimeCapped && freeTimeAfter === 0}
|
{#if freeTimeCapped && freeTimeAfter === 0}
|
||||||
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
|
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800 whitespace-nowrap">
|
||||||
closing after
|
closing after
|
||||||
</Badge>
|
</Badge>
|
||||||
{:else if freeTimeAfter > 0}
|
{:else if freeTimeAfter > 0}
|
||||||
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
|
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800 whitespace-nowrap">
|
||||||
{freeTimeAfter} min free afterwards{#if freeTimeCapped} (closing after){/if}
|
{freeTimeAfter} min free afterwards{#if freeTimeCapped} (closing after){/if}
|
||||||
</Badge>
|
</Badge>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Badge class="bg-amber-100 px-3 py-1 text-sm text-amber-800">
|
<Badge class="bg-amber-100 px-3 py-1 text-sm text-amber-800 whitespace-nowrap">
|
||||||
Starts in {timeRemaining} min
|
Starts in {timeRemaining} min
|
||||||
</Badge>
|
</Badge>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -279,24 +280,24 @@
|
|||||||
<Card.Content>
|
<Card.Content>
|
||||||
<div class="grid gap-6 md:grid-cols-3">
|
<div class="grid gap-6 md:grid-cols-3">
|
||||||
<!-- Customer Info -->
|
<!-- Customer Info -->
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-3 sm:gap-4 min-w-0">
|
||||||
{#if activeAppointment.user?.profile_pic_url}
|
{#if activeAppointment.user?.profile_pic_url}
|
||||||
<img
|
<img
|
||||||
src={activeAppointment.user.profile_pic_url}
|
src={activeAppointment.user.profile_pic_url}
|
||||||
alt={activeAppointment.user.full_name}
|
alt={activeAppointment.user.full_name}
|
||||||
class="h-16 w-16 md:h-20 md:w-20 rounded-full object-cover ring-4 ring-blue-200"
|
class="h-14 w-14 sm:h-16 sm:w-16 md:h-20 md:w-20 shrink-0 rounded-full object-cover ring-2 sm:ring-4 ring-blue-200"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
{@const initials = activeAppointment.user?.full_name?.split(' ').map(n => n[0]).join('') || '?'}
|
{@const initials = activeAppointment.user?.full_name?.split(' ').map(n => n[0]).join('') || '?'}
|
||||||
<div
|
<div
|
||||||
class="flex h-16 w-16 md:h-20 md:w-20 items-center justify-center rounded-full bg-gray-200 text-2xl font-bold text-gray-600 ring-4 ring-blue-200"
|
class="flex h-14 w-14 sm:h-16 sm:w-16 md:h-20 md:w-20 shrink-0 items-center justify-center rounded-full bg-gray-200 text-xl sm:text-2xl font-bold text-gray-600 ring-2 sm:ring-4 ring-blue-200"
|
||||||
>
|
>
|
||||||
{initials}
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<div class="text-lg font-semibold">{activeAppointment.user?.full_name || 'Guest'}</div>
|
<div class="truncate text-lg font-semibold">{activeAppointment.user?.full_name || 'Guest'}</div>
|
||||||
<div class="text-sm text-gray-600">{activeAppointment.user?.phone || '—'}</div>
|
<div class="truncate text-sm text-gray-600">{activeAppointment.user?.phone || '—'}</div>
|
||||||
{#if activeAppointment.user}
|
{#if activeAppointment.user}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -320,7 +321,7 @@
|
|||||||
<div class="text-xs text-gray-600">{service.service_description}</div>
|
<div class="text-xs text-gray-600">{service.service_description}</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="mt-1 flex items-center justify-between text-xs text-gray-500">
|
<div class="mt-1 flex items-center justify-between text-xs text-gray-500">
|
||||||
<span>{service.override_duration_minutes ?? service.duration_minutes} mins</span>
|
<span>{formatDuration(service.override_duration_minutes ?? service.duration_minutes ?? 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -112,8 +112,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatRelativeTime(iso: string): string {
|
function formatRelativeTime(iso: string): string {
|
||||||
const d = new Date(iso);
|
const d = new SvelteDate(iso);
|
||||||
const now = new Date();
|
const now = new SvelteDate();
|
||||||
const diffMs = now.getTime() - d.getTime();
|
const diffMs = now.getTime() - d.getTime();
|
||||||
const diffMin = Math.floor(diffMs / 60000);
|
const diffMin = Math.floor(diffMs / 60000);
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
const servicesChanged = areEditServicesChanged(er);
|
const servicesChanged = areEditServicesChanged(er);
|
||||||
|
|
||||||
if (timeChanged) {
|
if (timeChanged) {
|
||||||
const d = new Date(er.proposed.start_time!);
|
const d = new SvelteDate(er.proposed.start_time!);
|
||||||
const dateStr = d.toLocaleDateString('en-US', {
|
const dateStr = d.toLocaleDateString('en-US', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const newApprovals = (data.approvals || []).sort(
|
const newApprovals = (data.approvals || []).sort(
|
||||||
(a: PendingApproval, b: PendingApproval) =>
|
(a: PendingApproval, b: PendingApproval) =>
|
||||||
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
new SvelteDate(a.created_at).getTime() - new SvelteDate(b.created_at).getTime()
|
||||||
);
|
);
|
||||||
const newJson = JSON.stringify(newApprovals);
|
const newJson = JSON.stringify(newApprovals);
|
||||||
if (newJson !== prevApprovalsJson) {
|
if (newJson !== prevApprovalsJson) {
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const newEditRequests = (data.edit_requests || []).sort(
|
const newEditRequests = (data.edit_requests || []).sort(
|
||||||
(a: EditRequest, b: EditRequest) =>
|
(a: EditRequest, b: EditRequest) =>
|
||||||
new Date(a.requested_at).getTime() - new Date(b.requested_at).getTime()
|
new SvelteDate(a.requested_at).getTime() - new SvelteDate(b.requested_at).getTime()
|
||||||
);
|
);
|
||||||
const newJson = JSON.stringify(newEditRequests);
|
const newJson = JSON.stringify(newEditRequests);
|
||||||
if (newJson !== prevEditRequestsJson) {
|
if (newJson !== prevEditRequestsJson) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import * as Modal from '$lib/components/ui/dialog';
|
import * as Modal from '$lib/components/ui/dialog';
|
||||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||||
import { calculateMiddleWindow, extractBookedSlots, findAllLunchGaps } from '$lib/lunchProtection';
|
import { calculateMiddleWindow, extractBookedSlots, findAllLunchGaps } from '$lib/lunchProtection';
|
||||||
|
import { formatDuration } from '$lib/utils/format';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
openBookingModal: (bookingId: string) => void;
|
openBookingModal: (bookingId: string) => void;
|
||||||
@@ -62,8 +63,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
function isPastAppointment(startTime: string, durationMinutes: number): boolean {
|
function isPastAppointment(startTime: string, durationMinutes: number): boolean {
|
||||||
const start = new Date(startTime);
|
const start = new SvelteDate(startTime);
|
||||||
const end = new Date(start.getTime() + durationMinutes * 60_000);
|
const end = new SvelteDate(start.getTime() + durationMinutes * 60_000);
|
||||||
return end.getTime() < Date.now();
|
return end.getTime() < Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
let endSelectValue = $derived(`${endHour}:${endMinute}:${endPeriod}`);
|
let endSelectValue = $derived(`${endHour}:${endMinute}:${endPeriod}`);
|
||||||
|
|
||||||
const today = $derived.by(() => {
|
const today = $derived.by(() => {
|
||||||
const d = new Date();
|
const d = new SvelteDate();
|
||||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,12 +125,7 @@
|
|||||||
return { hour, minute, period };
|
return { hour, minute, period };
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(minutes: number): string {
|
|
||||||
if (minutes < 60) return `${minutes}m`;
|
|
||||||
const h = Math.floor(minutes / 60);
|
|
||||||
const m = minutes % 60;
|
|
||||||
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAutoGenerated(blocker: TimeBlocker): boolean {
|
function isAutoGenerated(blocker: TimeBlocker): boolean {
|
||||||
return blocker.description?.startsWith('RESERVATION:') ?? false;
|
return blocker.description?.startsWith('RESERVATION:') ?? false;
|
||||||
@@ -232,7 +228,7 @@
|
|||||||
const items: TimelineItem[] = [];
|
const items: TimelineItem[] = [];
|
||||||
|
|
||||||
for (const apt of appointments) {
|
for (const apt of appointments) {
|
||||||
const start = new Date(apt.start_time);
|
const start = new SvelteDate(apt.start_time);
|
||||||
const startM = start.getHours() * 60 + start.getMinutes();
|
const startM = start.getHours() * 60 + start.getMinutes();
|
||||||
items.push({
|
items.push({
|
||||||
id: `apt-${apt.id}`,
|
id: `apt-${apt.id}`,
|
||||||
@@ -244,7 +240,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const b of blockers) {
|
for (const b of blockers) {
|
||||||
const start = new Date(b.start_time);
|
const start = new SvelteDate(b.start_time);
|
||||||
const startM = start.getHours() * 60 + start.getMinutes();
|
const startM = start.getHours() * 60 + start.getMinutes();
|
||||||
items.push({
|
items.push({
|
||||||
id: `blk-${b.id}`,
|
id: `blk-${b.id}`,
|
||||||
@@ -362,8 +358,8 @@
|
|||||||
const startIso = buildDateTime(today, startHour, startMinute, startPeriod);
|
const startIso = buildDateTime(today, startHour, startMinute, startPeriod);
|
||||||
const endIso = buildDateTime(today, endHour, endMinute, endPeriod);
|
const endIso = buildDateTime(today, endHour, endMinute, endPeriod);
|
||||||
if (!startIso || !endIso) { overlappingBookings = []; hasOverlap = false; return; }
|
if (!startIso || !endIso) { overlappingBookings = []; hasOverlap = false; return; }
|
||||||
const blockerStart = new Date(startIso);
|
const blockerStart = new SvelteDate(startIso);
|
||||||
const blockerEnd = new Date(endIso);
|
const blockerEnd = new SvelteDate(endIso);
|
||||||
if (blockerEnd.getTime() <= blockerStart.getTime()) { overlappingBookings = []; hasOverlap = false; return; }
|
if (blockerEnd.getTime() <= blockerStart.getTime()) { overlappingBookings = []; hasOverlap = false; return; }
|
||||||
checkingOverlap = true;
|
checkingOverlap = true;
|
||||||
try {
|
try {
|
||||||
@@ -374,8 +370,8 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const allBookings: OverlappingBooking[] = data.bookings || [];
|
const allBookings: OverlappingBooking[] = data.bookings || [];
|
||||||
const filtered = allBookings.filter((b) => {
|
const filtered = allBookings.filter((b) => {
|
||||||
const bStart = new Date(b.start_time);
|
const bStart = new SvelteDate(b.start_time);
|
||||||
const bEnd = new Date(bStart.getTime() + b.duration_minutes * 60000);
|
const bEnd = new SvelteDate(bStart.getTime() + b.duration_minutes * 60000);
|
||||||
return bStart < blockerEnd && bEnd > blockerStart;
|
return bStart < blockerEnd && bEnd > blockerStart;
|
||||||
});
|
});
|
||||||
overlappingBookings = filtered;
|
overlappingBookings = filtered;
|
||||||
@@ -567,15 +563,15 @@
|
|||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#each Array(5) as _, i (i)}
|
{#each Array(5) as _, i (i)}
|
||||||
<div class="flex items-center gap-4 rounded-lg border p-3">
|
<div class="flex items-center gap-3 sm:gap-4 rounded-lg border p-3">
|
||||||
<Skeleton class="h-4 w-20" />
|
<Skeleton class="h-4 w-[45px] sm:w-[60px] lg:w-[80px]" />
|
||||||
<Skeleton class="h-10 w-1" />
|
<Skeleton class="h-10 w-1 shrink-0" />
|
||||||
<div class="flex-1 space-y-2">
|
<div class="flex-1 space-y-2 min-w-0">
|
||||||
<Skeleton class="h-4 w-32" />
|
<Skeleton class="h-4 w-32" />
|
||||||
<Skeleton class="h-3 w-48" />
|
<Skeleton class="h-3 w-48" />
|
||||||
</div>
|
</div>
|
||||||
<Skeleton class="h-6 w-20" />
|
<Skeleton class="h-6 w-20 hidden sm:block shrink-0" />
|
||||||
<Skeleton class="h-8 w-16" />
|
<Skeleton class="h-8 w-9 sm:w-16 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -603,33 +599,42 @@
|
|||||||
{#each timeline as item (item.id)}
|
{#each timeline as item (item.id)}
|
||||||
{#if item.type === 'appointment'}
|
{#if item.type === 'appointment'}
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-4 rounded-lg border p-3 transition-all hover:shadow-md
|
class="flex items-center gap-3 sm:gap-4 rounded-lg border p-3 transition-all hover:shadow-md
|
||||||
{isPastAppointment(item.data.start_time, item.data.duration_minutes) ? 'line-through opacity-50' : ''}"
|
{isPastAppointment(item.data.start_time, item.data.duration_minutes) ? 'line-through opacity-50' : ''}"
|
||||||
>
|
>
|
||||||
<div class="min-w-[60px] sm:min-w-[80px] text-sm font-semibold text-gray-700">
|
<div class="min-w-[45px] sm:min-w-[60px] lg:min-w-[80px] text-xs sm:text-sm font-semibold text-gray-700 shrink-0">
|
||||||
{formatTime(item.data.start_time)}
|
{formatTime(item.data.start_time)}
|
||||||
</div>
|
</div>
|
||||||
<div class="h-10 w-1 rounded {getStatusBarColor(item.data.status)}"></div>
|
<div class="h-10 w-1 shrink-0 rounded {getStatusBarColor(item.data.status)}"></div>
|
||||||
<div class="flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="font-medium hover:text-blue-600 hover:underline"
|
class="truncate font-medium hover:text-blue-600 hover:underline max-w-full block"
|
||||||
onclick={() => openUserModal(item.data.user_id)}
|
onclick={() => openUserModal(item.data.user_id)}
|
||||||
>
|
>
|
||||||
{item.data.user_name}
|
{item.data.user_name}
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-wrap items-center gap-x-1 text-sm text-gray-600">
|
<div class="flex flex-wrap items-center gap-x-1 text-sm text-gray-600">
|
||||||
<span>{item.data.services.join(', ')}</span>
|
<span class="truncate">{item.data.services.join(', ')}</span>
|
||||||
<span class="rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium">
|
<span class="shrink-0 rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium">
|
||||||
{formatDuration(item.data.duration_minutes)}
|
{formatDuration(item.data.duration_minutes)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge class={getStatusColor(item.data.status)}>
|
<Badge class="{getStatusColor(item.data.status)} hidden sm:inline-flex shrink-0">
|
||||||
{formatStatus(item.data.status)}
|
{formatStatus(item.data.status)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button size="sm" variant="outline" onclick={() => openBookingModal(item.data.id)}>
|
<Button
|
||||||
View
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
class="shrink-0 sm:px-3 h-9 w-9 sm:w-auto"
|
||||||
|
onclick={() => openBookingModal(item.data.id)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 sm:mr-1.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
</svg>
|
||||||
|
<span class="hidden sm:inline">View</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else if item.type === 'blocker'}
|
{:else if item.type === 'blocker'}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { SvelteDate } from 'svelte/reactivity';
|
||||||
import { authStore } from '$lib/stores/auth.svelte';
|
import { authStore } from '$lib/stores/auth.svelte';
|
||||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
let initialized = $state(false);
|
let initialized = $state(false);
|
||||||
|
|
||||||
const today = $derived.by(() => {
|
const today = $derived.by(() => {
|
||||||
const d = new Date();
|
const d = new SvelteDate();
|
||||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,11 +49,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function findLastWorkingDayClose(): Promise<{ cutoff: string; spansClosed: boolean }> {
|
async function findLastWorkingDayClose(): Promise<{ cutoff: string; spansClosed: boolean }> {
|
||||||
const now = new Date();
|
const now = new SvelteDate();
|
||||||
let spansClosed = false;
|
let spansClosed = false;
|
||||||
|
|
||||||
for (let i = 1; i <= 14; i++) {
|
for (let i = 1; i <= 14; i++) {
|
||||||
const d = new Date(now);
|
const d = new SvelteDate(now);
|
||||||
d.setDate(d.getDate() - i);
|
d.setDate(d.getDate() - i);
|
||||||
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
if (day?.isOpen) {
|
if (day?.isOpen) {
|
||||||
const closeTime = day.endTime;
|
const closeTime = day.endTime;
|
||||||
const [h, m] = closeTime.split(':').map(Number);
|
const [h, m] = closeTime.split(':').map(Number);
|
||||||
const closeDate = new Date(d);
|
const closeDate = new SvelteDate(d);
|
||||||
closeDate.setHours(h, m, 0, 0);
|
closeDate.setHours(h, m, 0, 0);
|
||||||
return { cutoff: closeDate.toISOString(), spansClosed };
|
return { cutoff: closeDate.toISOString(), spansClosed };
|
||||||
} else {
|
} else {
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback = new Date(now);
|
const fallback = new SvelteDate(now);
|
||||||
fallback.setDate(fallback.getDate() - 1);
|
fallback.setDate(fallback.getDate() - 1);
|
||||||
fallback.setHours(17, 0, 0, 0);
|
fallback.setHours(17, 0, 0, 0);
|
||||||
return { cutoff: fallback.toISOString(), spansClosed };
|
return { cutoff: fallback.toISOString(), spansClosed };
|
||||||
@@ -124,7 +125,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { cutoff, spansClosed } = await findLastWorkingDayClose();
|
const { cutoff, spansClosed } = await findLastWorkingDayClose();
|
||||||
const nowISO = new Date().toISOString();
|
const nowISO = new SvelteDate().toISOString();
|
||||||
let bookingsMade = 0;
|
let bookingsMade = 0;
|
||||||
try {
|
try {
|
||||||
const bmRes = await fetch(`/api/admin/bookings/by-created-range?start=${encodeURIComponent(cutoff)}&end=${encodeURIComponent(nowISO)}`, {
|
const bmRes = await fetch(`/api/admin/bookings/by-created-range?start=${encodeURIComponent(cutoff)}&end=${encodeURIComponent(nowISO)}`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user