Portal: per-system daily/weekly schedule editor

System picker lists the account's License Keys labelled by System Name
(fallback: key). Schedule type selector (Daily|Weekly) with a confirmation
before switching a populated schedule (daily->weekly prefills all seven
days; otherwise clears). Weekly shows a per-day editor. Blocks use
TimeInput, allow adjacent/midnight-crossing ranges (labelled 'ends next
day'), block empty blocks client-side, default dynamic audio on, and show
title/category/group only. Adds gap helper text and an informational
kiosk-timezone note (browser fallback; no external/paid dependency).
This commit is contained in:
NeilClack
2026-09-29 10:28:05 -05:00
parent 3bb8e0189b
commit 9a2888af36
5 changed files with 371 additions and 111 deletions
+17 -6
View File
@@ -8,7 +8,7 @@
hmToMinutes,
isOvernight,
toHm,
validateBlock,
validateBlockAgainstSchedule,
type BlockSibling
} from '$lib/schedule-utils';
@@ -25,10 +25,13 @@
open = false,
mode = 'create',
blockId,
day = null,
licenseKeyId = '',
initialStartMinutes = 9 * 60,
initialEndMinutes = 10 * 60,
initialProclamationIds = [],
initialDynamicAudio = true,
scheduleType = 'daily' as 'daily' | 'weekly' | null,
categories = [],
groups = [],
proclamations = [],
@@ -42,10 +45,13 @@
open?: boolean;
mode?: 'create' | 'edit';
blockId?: string;
day?: string | null;
licenseKeyId?: string;
initialStartMinutes?: number;
initialEndMinutes?: number;
initialProclamationIds?: string[];
initialDynamicAudio?: boolean;
scheduleType?: 'daily' | 'weekly' | null;
categories?: BrowserCategory[];
groups?: BrowserGroup[];
proclamations?: BrowserProclamation[];
@@ -105,10 +111,11 @@
function validate(): string | null {
if (!startValid || !endValid) return 'Enter a valid start and end time.';
const result = validateBlock(
{ startMinutes, endMinutes, proclamationIds: selectedIds },
siblings,
{ isCreate: mode === 'create' }
if (scheduleType === null) return 'Set a schedule type first.';
const result = validateBlockAgainstSchedule(
scheduleType,
{ day, startMinutes, endMinutes, proclamationIds: selectedIds },
siblings
);
return result.ok ? null : result.error;
}
@@ -193,6 +200,10 @@
{#if mode === 'edit' && blockId}
<input type="hidden" name="id" value={blockId} />
{/if}
<input type="hidden" name="licenseKeyId" value={licenseKeyId} />
{#if day}
<input type="hidden" name="day" value={day} />
{/if}
<input type="hidden" name="startMinutes" value={startMinutes} />
<input type="hidden" name="endMinutes" value={endMinutes} />
<input type="hidden" name="proclamationIds" value={JSON.stringify(selectedIds)} />
@@ -280,7 +291,7 @@
<h3 class="text-sm font-semibold text-slate-800 dark:text-slate-200">Review</h3>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
{formatTime(startMinutes)} – {formatTime(endMinutes)}{overnight
? ' (next day)'
? ' (ends next day)'
: ''}
</p>
{#if selectedTitles.length === 0}