import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { fetchOccupancy, fetchSiteConfig, saveSiteConfig, type Occupancy, type SiteConfig } from "./api.js"; // Live occupancy + capacity + park metadata. Occupancy is shown to everyone (it's a // fold over the signed ledger); capacity and the metadata fields are admin-editable. // The FULL gate (refuse transient entry at capacity) is enforced server-side in the // entry flow. Metadata (name, NIUS, address, contact) feeds the ticket header. // See wiki/concepts/capacity-occupancy.md and wiki/concepts/site-metadata.md. // The optional text fields, in display order. `labelKey`/`phKey` are i18n keys // (resolved at render); only `address` is multiline. const META_FIELDS: ReadonlyArray<{ key: keyof SiteConfig; labelKey: string; phKey?: string; multiline?: boolean }> = [ { key: "parkName", labelKey: "site.fieldParkName", phKey: "site.fieldParkNamePh" }, { key: "operatorName", labelKey: "site.fieldOperator", phKey: "site.fieldOperatorPh" }, { key: "nius", labelKey: "site.fieldNius", phKey: "site.fieldNiusPh" }, { key: "address", labelKey: "site.fieldAddress", multiline: true }, { key: "phone", labelKey: "site.fieldPhone" }, { key: "email", labelKey: "site.fieldEmail" }, ]; export function SiteSettings({ canEdit }: { canEdit: boolean }) { const { t } = useTranslation(); const [occ, setOcc] = useState(null); const [capInput, setCapInput] = useState(""); const [meta, setMeta] = useState>({}); const [exitVoucherDefault, setExitVoucherDefault] = useState(false); const [reserveSubs, setReserveSubs] = useState(false); const [anprEntry, setAnprEntry] = useState(true); const [msg, setMsg] = useState(null); function reload() { fetchOccupancy().then(setOcc).catch(() => {}); } useEffect(() => { reload(); fetchSiteConfig() .then((c) => { setCapInput(c.capacity == null ? "" : String(c.capacity)); setExitVoucherDefault(c.exitVoucherDefault); setReserveSubs(c.reserveSubscriberSpots); setAnprEntry(c.anprEntryEnabled); const m: Record = {}; for (const { key } of META_FIELDS) m[key] = c[key] == null ? "" : String(c[key]); setMeta(m); }) .catch(() => {}); }, []); async function save() { setMsg(null); const raw = capInput.trim(); const patch: Partial = { capacity: raw === "" ? null : Math.round(Number(raw)), exitVoucherDefault, reserveSubscriberSpots: reserveSubs, anprEntryEnabled: anprEntry, }; // Send each metadata field; "" → null is applied server-side. for (const { key } of META_FIELDS) (patch as Record)[key] = meta[key] ?? ""; try { await saveSiteConfig(patch); reload(); setMsg(t("site.saved")); } catch (e) { setMsg((e as Error).message); } } return (
{t("site.occupancy")} {occ == null ? ( … ) : ( <> {occ.count} {occ.capacity != null ? `/ ${occ.capacity}` : t("site.noCapacitySet")} {occ.capacity != null && ( · {occ.free} {t("site.free")} )} {occ.full && {t("site.full")}} )}
{canEdit && (
{t("site.capacityLabel")} setCapInput(e.target.value)} placeholder={t("site.capacityPlaceholder")} />
{t("site.parkDetails")}
{META_FIELDS.map(({ key, labelKey, phKey, multiline }) => (
{t(labelKey)} {multiline ? (