export type PageTitleError = 'required' | 'maxlength' | null; export function pageTitleError(value: string | null | undefined, maxLength = 120): PageTitleError { const v = value ?? ''; if (v.trim().length === 0) return 'required'; if (v.length > maxLength) return 'maxlength'; return null; } export function normalizePageTitle(value: string): string { return value.trim(); } export type EventDerivedState = 'running' | 'countdown' | 'stopped' | 'unconfigured'; export function deriveEventState(start: string, end: string): EventDerivedState { if (!start || !end) return 'unconfigured'; const s = Date.parse(start); const e = Date.parse(end); if (!Number.isFinite(s) || !Number.isFinite(e)) return 'unconfigured'; const now = Date.now(); if (now < s) return 'countdown'; if (now >= s && now < e) return 'running'; return 'stopped'; } export function endAfterStartValidator(group: any): { endBeforeStart: true } | null { const start = group.get?.('eventStartUtc')?.value; const end = group.get?.('eventEndUtc')?.value; if (!start || !end) return null; const s = Date.parse(start); const e = Date.parse(end); if (Number.isFinite(s) && Number.isFinite(e) && e <= s) { return { endBeforeStart: true }; } return null; } export function toDatetimeLocal(iso: string): string { if (!iso) return ''; const d = new Date(iso); if (Number.isNaN(d.getTime())) return ''; const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; } export function toIsoUtc(local: string): string { if (!local) return ''; const d = new Date(local); if (Number.isNaN(d.getTime())) return local; return d.toISOString(); }