/* ================================================================== Academics → Holidays ------------------------------------------------------------------ Announcing a holiday tells the attendance machinery the school is shut: no check-in nudge, no absence digest to admins, no automatic student-register marking, and the staff heat-map stops counting the day as a working day. That last one is what makes a holiday not a leave. Check-in is NOT blocked: staff who work through a break still have their day recorded. Announce waits for a second press; everything else here is a list. ================================================================== */ const HolidaysScreen = ({ setScreen }) => { const CX = window.CX; const Api = window.AppApi; const [list, setList] = useState(null); const [err, setErr] = useState(null); const [busy, setBusy] = useState(false); const [done, setDone] = useState(null); const todayIso = (() => { // Local parts, not toISOString(): before 05:30 IST that returns yesterday. const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; })(); const [form, setForm] = useState({ name: "", start_date: todayIso, end_date: todayIso, notes: "", // Outpass generation belongs to the (removed) hostel module; always off. generate_outpasses: false, pause_staff_attendance: true, pause_student_automark: true, }); const [confirming, setConfirming] = useState(false); const load = async () => { try { const h = await Api.get("/admin/holidays?limit=24"); setList(h.holidays); setErr(null); } catch (e) { setErr(e.message || "Couldn't load holidays"); } }; useEffect(() => { load(); }, []); const set = (k) => (e) => { const v = e.target.type === "checkbox" ? e.target.checked : e.target.value; setForm((f) => { const next = { ...f, [k]: v }; // A range that ends before it starts is a typo, not a choice. if (k === "start_date" && next.end_date < v) next.end_date = v; return next; }); setConfirming(false); }; const announce = async () => { setBusy(true); setErr(null); try { const r = await Api.post("/admin/holidays", form); setDone(r); setConfirming(false); setForm((f) => ({ ...f, name: "", notes: "" })); await load(); } catch (e) { setErr(e.message || "Couldn't announce the holiday"); } finally { setBusy(false); } }; const cancel = async (h) => { if (!window.confirm(`Call off ${h.name}?`)) return; setBusy(true); setErr(null); try { await Api.post(`/admin/holidays/${h.id}/cancel`, {}); setDone(null); await load(); } catch (e) { setErr(e.message || "Couldn't cancel the holiday"); } finally { setBusy(false); } }; const days = (() => { const a = new Date(`${form.start_date}T00:00:00Z`), b = new Date(`${form.end_date}T00:00:00Z`); const n = Math.round((b - a) / 86400000) + 1; return Number.isFinite(n) && n > 0 ? n : 0; })(); const ready = form.name.trim() && form.start_date && form.end_date && days > 0 && !busy; return (
{err &&
{err}
} {done && (
{done.holiday.name} announced
)} {/* ── Announce ─────────────────────────────────────────────── */}
Announce a holiday

{days > 0 && <>{days} day{days === 1 ? "" : "s"}.}

{!confirming ? ( ) : (
Announce {form.name} for {days} day{days === 1 ? "" : "s"} ?
)}
{/* ── What has been announced ──────────────────────────────── */}
Announced
{!list && } {list && list.length === 0 && } {list && list.length > 0 && (
{list.map((h) => ( ))}
HolidayDatesPausesAnnounced
{h.name} {h.running_now && <> on now} {h.cancelled_at && <> called off} {h.notes &&
{h.notes}
}
{h.start_date === h.end_date ? CX.date(h.start_date) : `${CX.date(h.start_date)} – ${CX.date(h.end_date)}`} {h.pause_staff_attendance ? "staff" : "—"} {h.pause_student_automark ? " · register" : ""} {CX.date(h.announced_at)}{h.announced_by_name ? ` · ${h.announced_by_name}` : ""} {h.cancelled_at &&
called off {CX.date(h.cancelled_at)}{h.cancelled_by_name ? ` · ${h.cancelled_by_name}` : ""}
}
{!h.cancelled_at && ( )}
)}
); }; window.CockpitHolidays = HolidaysScreen;