refactor: admin management components (discounts, holidays, schedule, time blockers)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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 { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
|
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
|
||||||
if (form.campaign_type === 'time_based') {
|
if (form.campaign_type === 'time_based') {
|
||||||
if (!form.start_date || !form.end_date) return false;
|
if (!form.start_date || !form.end_date) return false;
|
||||||
if (new Date(form.end_date) <= new Date(form.start_date)) return false;
|
if (new SvelteDate(form.end_date) <= new SvelteDate(form.start_date)) return false;
|
||||||
}
|
}
|
||||||
if (form.campaign_type === 'milestone') {
|
if (form.campaign_type === 'milestone') {
|
||||||
if (form.milestone_value <= 0) return false;
|
if (form.milestone_value <= 0) return false;
|
||||||
|
|||||||
@@ -535,7 +535,7 @@
|
|||||||
class="w-24 text-sm"
|
class="w-24 text-sm"
|
||||||
>
|
>
|
||||||
{#each Array(24) as _, hour}
|
{#each Array(24) as _, hour}
|
||||||
{#each TIME15 as min}
|
{#each TIME15 as min (min)}
|
||||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -548,7 +548,7 @@
|
|||||||
class="w-24 text-sm"
|
class="w-24 text-sm"
|
||||||
>
|
>
|
||||||
{#each Array(24) as _, hour}
|
{#each Array(24) as _, hour}
|
||||||
{#each TIME15 as min}
|
{#each TIME15 as min (min)}
|
||||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -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 { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
|
|
||||||
function getWorkingHoursForDate(dateStr: string): WorkingHourRow | null {
|
function getWorkingHoursForDate(dateStr: string): WorkingHourRow | null {
|
||||||
if (!dateStr || defaultHours.length === 0) return null;
|
if (!dateStr || defaultHours.length === 0) return null;
|
||||||
const d = new Date(dateStr + 'T00:00:00');
|
const d = new SvelteDate(dateStr + 'T00:00:00');
|
||||||
const jsDay = d.getDay();
|
const jsDay = d.getDay();
|
||||||
const weekday = jsDay === 0 ? 6 : jsDay - 1;
|
const weekday = jsDay === 0 ? 6 : jsDay - 1;
|
||||||
return defaultHours.find((h) => h.weekday === weekday) ?? null;
|
return defaultHours.find((h) => h.weekday === weekday) ?? null;
|
||||||
@@ -152,16 +153,11 @@
|
|||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
|
|
||||||
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 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 = d.getTime() - now.getTime();
|
const diffMs = d.getTime() - now.getTime();
|
||||||
if (diffMs < 0) {
|
if (diffMs < 0) {
|
||||||
const absMin = Math.floor(Math.abs(diffMs) / 60000);
|
const absMin = Math.floor(Math.abs(diffMs) / 60000);
|
||||||
@@ -178,7 +174,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sortedBlockers = $derived.by(() => {
|
let sortedBlockers = $derived.by(() => {
|
||||||
return [...blockers].sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime());
|
return [...blockers].sort((a, b) => new SvelteDate(a.start_time).getTime() - new SvelteDate(b.start_time).getTime());
|
||||||
});
|
});
|
||||||
|
|
||||||
let totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
let totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
||||||
@@ -261,8 +257,8 @@
|
|||||||
return;
|
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()) {
|
if (blockerEnd.getTime() <= blockerStart.getTime()) {
|
||||||
overlappingBookings = [];
|
overlappingBookings = [];
|
||||||
hasOverlap = false;
|
hasOverlap = false;
|
||||||
@@ -288,8 +284,8 @@
|
|||||||
const allBookings: OverlappingBooking[] = data.bookings || [];
|
const allBookings: OverlappingBooking[] = data.bookings || [];
|
||||||
// Client-side filter: only bookings that overlap with the proposed blocker timespan
|
// Client-side filter: only bookings that overlap with the proposed blocker timespan
|
||||||
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;
|
||||||
@@ -701,7 +697,7 @@
|
|||||||
onStartTimeChange();
|
onStartTimeChange();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#each availableStartOptions as opt}
|
{#each availableStartOptions as opt (opt.totalMin)}
|
||||||
<option value={`${opt.hour}:${opt.minute}:${opt.period}`}>
|
<option value={`${opt.hour}:${opt.minute}:${opt.period}`}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</option>
|
</option>
|
||||||
@@ -736,7 +732,7 @@
|
|||||||
onEndTimeChange();
|
onEndTimeChange();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#each availableEndOptions as opt}
|
{#each availableEndOptions as opt (opt.totalMin)}
|
||||||
<option value={`${opt.hour}:${opt.minute}:${opt.period}`}>
|
<option value={`${opt.hour}:${opt.minute}:${opt.period}`}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -486,7 +486,7 @@
|
|||||||
class="max-w-[70px] text-sm"
|
class="max-w-[70px] text-sm"
|
||||||
>
|
>
|
||||||
{#each Array(24) as _, hour}
|
{#each Array(24) as _, hour}
|
||||||
{#each TIME15 as min}
|
{#each TIME15 as min (min)}
|
||||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
class="max-w-[70px] text-sm"
|
class="max-w-[70px] text-sm"
|
||||||
>
|
>
|
||||||
{#each Array(24) as _, hour}
|
{#each Array(24) as _, hour}
|
||||||
{#each TIME15 as min}
|
{#each TIME15 as min (min)}
|
||||||
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
<option value="{String(hour).padStart(2,'0')}:{min}">{String(hour).padStart(2,'0')}:{min}</option>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user