"use client"; /* Facility: details and time zone, slips and logo, finance and reports, and the ward notice. */ import { useMemo, useState } from "react"; import { useSnap } from "@/lib/client"; import { Field } from "@/components/ui"; import { fmtDate, type Settings, type Snapshot } from "@/lib/compute"; import { Msg, SectionHead, TextField, useSaver, useSettingsFields } from "./common"; // Only for a browser without Intl.supportedValuesOf, so the picker is never empty. const FALLBACK_ZONES = ["Australia/Brisbane", "Australia/Sydney", "Australia/Melbourne", "Australia/Hobart", "Australia/Adelaide", "Australia/Darwin", "Australia/Perth", "Australia/Broken_Hill", "Australia/Lord_Howe"]; type LiveNotice = { body: string; endsAt: string } | null; const dayMonth = (d: string) => { const t = new Date(d + "T00:00:00Z"); return isNaN(t.getTime()) ? d : t.toLocaleDateString("en-AU", { day: "numeric", month: "short", timeZone: "UTC" }); }; export default function FacilitySection() { const { s, isAdmin, mutate } = useSnap(); const saver = useSaver(); const { msg, say } = saver; const { val, setField } = useSettingsFields(saver); const [tzPick, setTzPick] = useState(null); const [tzErr, setTzErr] = useState(""); const [logoV, setLogoV] = useState(0); const [notice, setNotice] = useState({ body: "", endsAt: "" }); const [noticeBusy, setNoticeBusy] = useState(false); /* The live notice, once the snapshot carries it (spec S1). Undefined means this build's snapshot cannot see the board, which is different from an empty board, so nothing is claimed. */ const live = (s as Snapshot & { notice?: LiveNotice }).notice; const zones = useMemo(() => { const all = Intl.supportedValuesOf?.("timeZone") || FALLBACK_ZONES; return all.includes(s.settings.timezone) ? all : [s.settings.timezone, ...all]; }, [s.settings.timezone]); const F = (k: keyof Settings, label: string, opts: { ph?: string; hint?: string; numeric?: boolean; demoFixed?: boolean; style?: React.CSSProperties } = {}) => setField(k, opts.numeric ? v.replace(/[^0-9]/g, "") : v)} />; function uploadLogo(file: File) { if (file.size > 400 * 1024) { say("logo", "Logo must be under 400 KB."); return; } const r = new FileReader(); r.onload = async () => { const res = await mutate("settings.update", { logoData: String(r.result) }); setLogoV((v) => v + 1); say("logo", res.ok ? "Logo saved." : res.error); }; r.readAsDataURL(file); } async function postNotice() { const body = notice.body.trim(), endsAt = notice.endsAt.trim(); // The staff app only shows a notice whose end date is today or later. if (body && endsAt && endsAt < s.today) { say("notice", `${fmtDate(endsAt)} has already gone. Pick today or later, or leave it blank.`); return; } setNoticeBusy(true); const r = await mutate<{ cleared: boolean }>("notice.set", { body, endsAt }); setNoticeBusy(false); if (!r.ok) { say("notice", r.error); return; } if (!r.result.cleared) setNotice({ body: "", endsAt: "" }); say("notice", r.result.cleared ? "Notice taken down." : `Posted${endsAt ? ` until ${dayMonth(endsAt)}` : ""}.`); } return ( <> Facility
{F("facility", "Facility")} {F("location", "Stock location")} {F("coordinator", "Coordinator name")} {/* Fixed in the demo: everyone shares that facility, and these print on order forms. */} {F("coordinatorEmail", "Coordinator e-mail", { ph: "e.g. uniforms@yourhospital.org.au", demoFixed: true })} {F("coordinatorPhone", "Coordinator phone", { ph: "e.g. 07 3xxx xxxx", demoFixed: true })} {(c) => ( )}
Slips & logo
{F("slipOrg", "Organisation name on slips", { ph: "Printed when there is no logo" })} {/* A named group, not a Field: a preview, a picker and a remove button share one label. */}
{s.settings.hasLogo && The logo printed on slips} {!s.settings.hasLogo && !isAdmin && No logo} {isAdmin && } {isAdmin && s.settings.hasLogo && }
{F("slipCollectionFooter", "Collection slip footer", { style: { gridColumn: "1 / -1" } })} {F("slipDeliveryFooter", "Delivery slip footer", { style: { gridColumn: "1 / -1" } })}
Finance & reports
{F("glAccount", "GL account", { ph: "e.g. 631020" })} {F("journalDesc", "Journal description prefix", { ph: "e.g. Uniform issues" })} {F("exceptionHigh", "Exception threshold (items/month)", { numeric: true })}
Ward notice {live !== undefined && (
Currently posted {live ? <>
{live.body}
{live.endsAt ? <>until {dayMonth(live.endsAt)} : "no end date"} : Nothing posted}
)} {isAdmin && ( <> {(c) =>