// Screens: Access Requests, My Requests, Admin Approval Queue, Admin Users / Metadata / Audit / ROE / Routing

const DRow = ({ k, v }) => (
  <tr>
    <td style={{ color: "var(--ink-mute)", fontSize: 12, paddingRight: 16, paddingBottom: 8, whiteSpace: "nowrap", verticalAlign: "top" }}>{k}</td>
    <td style={{ fontSize: 13, paddingBottom: 8 }}>{v}</td>
  </tr>
);

function AccessRequests({ user, perms, onApprovalAction }) {
  const [isApprover,       setIsApprover]       = useState(false);
  const [isManager,        setIsManager]        = useState(false);
  const [isRoutingMember,  setIsRoutingMember]  = useState(false);
  const [tab, setTab]                           = useState(null);
  const [showForm,     setShowForm]     = useState(false);
  const [showBehalf,   setShowBehalf]   = useState(false);
  const [openReq,      setOpenReq]      = useState(null);
  const [queueReload,  setQueueReload]  = useState(0);
  const [myReload,     setMyReload]     = useState(0);

  React.useEffect(() => {
    apiFetch("/api/access-requests/is-approver")
      .then(res => {
        const approver = !!res.is_approver;
        setIsApprover(approver);
        setIsManager(!!res.is_manager);
        setIsRoutingMember(!!res.is_routing_member);
        setTab(approver ? "queue" : "my");
      })
      .catch(() => setTab("my"));
  }, []);

  const tabs = [];
  if (isApprover) tabs.push({ id: "queue", label: "Approval Queue" });
  tabs.push({ id: "my", label: "My Requests" });
  if (isManager || perms.admin) tabs.push({ id: "routing", label: "Routing Table" });

  if (tab === null) return <div className="muted tiny" style={{ padding: 40 }}>Loading…</div>;

  return (
    <div>
      <PageHeader
        title="Access Requests"
        sub={isApprover ? "Manage incoming access requests · SLA 3 business days" : "Submit and track your access requests"}
        right={
          <div className="row gap-6">
            {isRoutingMember && (
              <button className="btn" onClick={() => setShowBehalf(true)}>
                <Icon name="user" size={14} /> Apply on behalf of
              </button>
            )}
            <button className="btn primary" onClick={() => setShowForm(true)}>
              <Icon name="plus" size={14} /> New request
            </button>
          </div>
        }
      />
      <Tabs tabs={tabs} current={tab} onChange={setTab} />
      {tab === "queue" && <ApprovalQueue onOpen={setOpenReq} reload={queueReload} onApprovalAction={onApprovalAction} />}
      {tab === "my"    && <MyRequests user={user} reload={myReload} />}
      {tab === "routing" && <RoutingTable />}
      {showForm   && <AccessRequestForm user={user} onClose={() => setShowForm(false)}
                       onSubmitted={() => { setTab("my"); setMyReload(n => n + 1); }} />}
      {showBehalf && <AccessRequestForm user={user} onBehalf onClose={() => setShowBehalf(false)}
                       onSubmitted={() => { setTab("queue"); setQueueReload(n => n + 1); }} />}
      {openReq    && <RequestDetail req={openReq} isApprover={isApprover}
                       onClose={() => setOpenReq(null)}
                       onAction={() => { setOpenReq(null); setQueueReload(n => n + 1); onApprovalAction?.(); }} />}
    </div>
  );
}

function _slaDisplay(sla_deadline, status) {
  if (status !== "Pending") return <span className="tiny muted">—</span>;
  if (!sla_deadline) return null;
  const days = Math.ceil((new Date(sla_deadline) - new Date()) / 86400000);
  if (days < 0) return <span className="tiny mono" style={{ color: "var(--red)", fontWeight: 600 }}>⚠ Overdue</span>;
  if (days <= 1) return <span className="tiny mono" style={{ color: "var(--amber)", fontWeight: 600 }}>⚠ {days}d</span>;
  return <span className="tiny mono" style={{ color: "var(--green)" }}>{days}d</span>;
}

function ApprovalQueue({ onOpen, reload, onApprovalAction }) {
  const [status,  setStatus]  = useState("Pending");
  const [rows,    setRows]    = useState([]);
  const [counts,  setCounts]  = useState({});
  const [loading, setLoading] = useState(true);
  const [acting,  setActing]  = useState(null);   // req_id being actioned inline

  const load = () => {
    setLoading(true);
    Promise.all([
      apiFetch("/api/admin/access-requests"),
      apiFetch("/api/admin/access-requests?status=Pending"),
    ]).then(([all, pending]) => {
      const c = {};
      ["Pending","Approved","Rejected"].forEach(s => {
        c[s] = (Array.isArray(all) ? all : []).filter(r => r.status === s).length;
      });
      setCounts(c);
      setRows(Array.isArray(all) ? all.filter(r => r.status === status) : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  };

  React.useEffect(() => { load(); }, [reload]);

  React.useEffect(() => {
    apiFetch(`/api/admin/access-requests${status ? `?status=${status}` : ""}`)
      .then(r => setRows(Array.isArray(r) ? r : []))
      .catch(() => {});
  }, [status]);

  const quickAction = async (e, req_id, action) => {
    e.stopPropagation();
    setActing(req_id);
    try {
      await apiFetch(`/api/admin/access-requests/${req_id}/${action}`, { method: "PUT", body: JSON.stringify({ comment: null }) });
      load();
      onApprovalAction?.();
    } catch (err) { alert(err.message); }
    setActing(null);
  };

  const parseRegions = str => { try { return JSON.parse(str).join(", "); } catch { return str || "—"; } };

  return (
    <div>
      <div className="filterbar">
        <div className="row gap-4" style={{ background: "var(--paper)", padding: 4, borderRadius: 4 }}>
          {["Pending","Approved","Rejected"].map(s => (
            <button key={s} onClick={() => setStatus(s)} className="tiny"
              style={{ padding: "4px 10px", border: 0, borderRadius: 3,
                background: status === s ? "#fff" : "transparent",
                fontWeight: status === s ? 600 : 400,
                color: status === s ? "var(--ink)" : "var(--ink-mute)", cursor: "pointer" }}>
              {s} · {counts[s] ?? "…"}
            </button>
          ))}
        </div>
        <div className="grow" />
        <div className="row gap-8">
          <div className="row gap-6 tiny muted"><span style={{ width: 8, height: 8, borderRadius: 50, background: "var(--amber)" }} /> SLA breach</div>
          <div className="row gap-6 tiny muted"><span style={{ width: 8, height: 8, borderRadius: 50, background: "var(--green)" }} /> Within SLA</div>
        </div>
      </div>
      <div className="card">
        <div className="table-wrap">
          {loading ? <div className="muted tiny" style={{ padding: 24 }}>Loading…</div> : (
            <table className="table">
              <thead>
                <tr>
                  <th>Request #</th>
                  <th>Requester</th>
                  <th>Requested Role</th>
                  <th>Region(s)</th>
                  <th>Submitted</th>
                  <th>SLA</th>
                  <th>Routed to</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {rows.map(r => (
                  <tr key={r.id} onClick={() => onOpen(r)} style={{ cursor: "pointer" }}>
                    <td className="mono tiny bold">AR-{String(r.id).padStart(4, "0")}</td>
                    <td>
                      <div className="bold tiny">{r.requester_name}</div>
                      <div className="tiny muted mono">{r.requester_email}</div>
                    </td>
                    <td><Pill className="plain">{r.requested_role}</Pill></td>
                    <td className="tiny">{parseRegions(r.requested_regions)}</td>
                    <td className="mono tiny muted">{r.submitted_at?.slice(5, 16)}</td>
                    <td>{_slaDisplay(r.sla_deadline, r.status)}</td>
                    <td className="tiny muted">{r.routed_to_email || "—"}</td>
                    <td onClick={e => e.stopPropagation()}>
                      {r.status === "Pending" && (
                        <div className="row gap-4">
                          <button className="btn tiny" disabled={acting === r.id}
                            style={{ height: 26, fontSize: 11, color: "var(--green)" }}
                            onClick={e => quickAction(e, r.id, "approve")}>
                            <Icon name="check" size={11} /> {acting === r.id ? "…" : "Approve"}
                          </button>
                          <button className="btn tiny" disabled={acting === r.id}
                            style={{ height: 26, fontSize: 11, color: "var(--red)" }}
                            onClick={e => quickAction(e, r.id, "reject")}>
                            {acting === r.id ? "…" : "Reject"}
                          </button>
                        </div>
                      )}
                    </td>
                  </tr>
                ))}
                {rows.length === 0 && (
                  <tr><td colSpan={8} className="tiny muted" style={{ padding: 32, textAlign: "center" }}>No {status.toLowerCase()} requests</td></tr>
                )}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

function MyRequests({ user, reload }) {
  const [rows, setRows]       = useState([]);
  const [loading, setLoading] = useState(true);

  React.useEffect(() => {
    setLoading(true);
    apiFetch("/api/access-requests/my")
      .then(r => { setRows(Array.isArray(r) ? r : []); setLoading(false); })
      .catch(() => setLoading(false));
  }, [reload]);

  const parseRegions = str => { try { return JSON.parse(str).join(", "); } catch { return str || "—"; } };

  return (
    <div className="card">
      <div className="table-wrap">
        {loading ? <div className="muted tiny" style={{ padding: 24 }}>Loading…</div> : (
          <table className="table">
            <thead>
              <tr>
                <th>Request #</th>
                <th>Role</th>
                <th>Region(s)</th>
                <th>Submitted</th>
                <th>Routed to</th>
                <th>Decided</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              {rows.map(r => (
                <tr key={r.id}>
                  <td className="mono tiny bold">AR-{String(r.id).padStart(4, "0")}</td>
                  <td><Pill className="plain">{r.requested_role}</Pill></td>
                  <td className="tiny">{parseRegions(r.requested_regions)}</td>
                  <td className="mono tiny muted">{r.submitted_at?.slice(0, 16)}</td>
                  <td className="tiny muted">{r.routed_to_email || "—"}</td>
                  <td className="mono tiny muted">{r.reviewed_at?.slice(0, 16) || "—"}</td>
                  <td><Pill status={r.status} /></td>
                </tr>
              ))}
              {rows.length === 0 && (
                <tr><td colSpan={7} className="tiny muted" style={{ padding: 32, textAlign: "center" }}>No requests submitted yet</td></tr>
              )}
            </tbody>
          </table>
        )}
      </div>
    </div>
  );
}

function RoutingModal({ row, regions, onClose, onSaved }) {
  const isEdit = !!(row && row.id);
  const empty = { region_id: "", manager_email: "", delegate_email: "", delegate_valid_until: "" };
  const [form, setForm] = React.useState(isEdit ? {
    region_id: row.region_id ?? "",
    manager_email: row.manager_email ?? "",
    delegate_email: row.delegate_email ?? "",
    delegate_valid_until: row.delegate_valid_until ? row.delegate_valid_until.slice(0, 10) : "",
  } : empty);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const inputStyle = { padding: "6px 8px", borderRadius: 6, border: "1px solid var(--border)", fontSize: 13, width: "100%", boxSizing: "border-box" };

  const submit = async () => {
    if (!form.manager_email) { setError("Manager email is required."); return; }
    setSaving(true); setError(null);
    const body = {
      region_id: form.region_id ? parseInt(form.region_id) : null,
      manager_email: form.manager_email,
      delegate_email: form.delegate_email || null,
      delegate_valid_until: form.delegate_valid_until || null,
    };
    try {
      if (isEdit) await apiFetch(`/api/admin/routing/${row.id}`, { method: "PUT", body: JSON.stringify(body) });
      else await apiFetch("/api/admin/routing", { method: "POST", body: JSON.stringify(body) });
      onSaved();
    } catch (e) { setError(e.message); setSaving(false); }
  };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.4)", zIndex: 900, display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="card" style={{ width: 520, padding: 24 }}>
        <div className="row gap-8" style={{ marginBottom: 18, alignItems: "center" }}>
          <span style={{ fontWeight: 700, fontSize: 15 }}>{isEdit ? "Edit" : "Add"} Routing Rule</span>
          <div className="grow" />
          <button className="btn ghost tiny" onClick={onClose}><Icon name="x" size={14} /></button>
        </div>
        <div style={{ display: "grid", gap: 12 }}>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            Region
            <select value={form.region_id} onChange={e => set("region_id", e.target.value)} style={inputStyle}>
              <option value="">— Any / All regions —</option>
              {regions.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
            </select>
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            Manager Email <span style={{ color: "var(--red)" }}>*</span>
            <input type="email" value={form.manager_email} onChange={e => set("manager_email", e.target.value)}
              placeholder="contract.manager@maersk.com" style={inputStyle} />
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            Delegate Email
            <input type="email" value={form.delegate_email} onChange={e => set("delegate_email", e.target.value)}
              placeholder="delegate@maersk.com (optional)" style={inputStyle} />
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            Delegate Valid Until
            <input type="date" value={form.delegate_valid_until} onChange={e => set("delegate_valid_until", e.target.value)} style={inputStyle} />
            <span className="tiny muted">Leave blank if delegate is permanent</span>
          </label>
        </div>
        {error && <div className="tiny" style={{ color: "var(--red)", marginTop: 10 }}>{error}</div>}
        <div className="row gap-8" style={{ marginTop: 18, justifyContent: "flex-end" }}>
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={saving}>{saving ? "Saving…" : (isEdit ? "Save changes" : "Add rule")}</button>
        </div>
      </div>
    </div>
  );
}

function RoutingTable() {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [regions, setRegions] = React.useState([]);
  const [modal, setModal] = React.useState(null); // null | "new" | row object

  const load = () => {
    setLoading(true);
    apiFetch("/api/metadata/regions")
      .then(r => setRegions(Array.isArray(r) ? r : []))
      .catch(() => {});
    apiFetch("/api/admin/routing")
      .then(r => { setRows(Array.isArray(r) ? r : []); setLoading(false); })
      .catch(() => setLoading(false));
  };
  React.useEffect(() => { load(); }, []);

  const handleDelete = async (id, email) => {
    if (!confirm(`Delete routing rule for "${email}"?`)) return;
    try { await apiFetch(`/api/admin/routing/${id}`, { method: "DELETE" }); load(); }
    catch (e) { alert(e.message); }
  };

  const today = new Date().toISOString().slice(0, 10);
  const regionName = id => regions.find(r => r.id === Number(id))?.name || "—";

  const delegateStatus = row => {
    if (!row.delegate_email) return null;
    if (!row.delegate_valid_until) return <Pill className="plain">Permanent</Pill>;
    const expired = row.delegate_valid_until.slice(0, 10) < today;
    return <Pill status={expired ? "Inactive" : "Active"}>{expired ? "Expired" : `Until ${row.delegate_valid_until.slice(0, 10)}`}</Pill>;
  };

  const onSaved = () => { setModal(null); load(); };

  return (
    <div>
      <Card
        title="Region → Contract Manager Routing"
        sub="Drives approval routing for access requests"
        right={<button className="btn primary tiny" onClick={() => setModal("new")}><Icon name="plus" size={12} /> Add rule</button>}
      >
        {loading ? (
          <div className="muted tiny" style={{ padding: 24 }}>Loading…</div>
        ) : (
          <div className="table-wrap">
            <table className="table">
              <thead>
                <tr>
                  <th>Region</th>
                  <th>Manager Email</th>
                  <th>Delegate Email</th>
                  <th>Delegate Status</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {rows.length === 0 && (
                  <tr><td colSpan={5} className="tiny muted" style={{ padding: 20, textAlign: "center" }}>No routing rules configured.</td></tr>
                )}
                {rows.map(r => (
                  <tr key={r.id}>
                    <td className="bold">{regionName(r.region_id)}</td>
                    <td className="tiny mono">{r.manager_email}</td>
                    <td className="tiny mono muted">{r.delegate_email || "—"}</td>
                    <td>{delegateStatus(r)}</td>
                    <td>
                      <div className="row gap-4">
                        <button className="btn ghost tiny" onClick={() => setModal(r)}><Icon name="edit" size={12} /></button>
                        <button className="btn ghost tiny" onClick={() => handleDelete(r.id, r.manager_email)}
                          style={{ color: "var(--red)" }}><Icon name="trash" size={12} /></button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </Card>
      {modal && (
        <RoutingModal
          row={modal === "new" ? null : modal}
          regions={regions}
          onClose={() => setModal(null)}
          onSaved={onSaved}
        />
      )}
    </div>
  );
}

function AccessRequestForm({ user, onClose, onSubmitted, onBehalf = false }) {
  const ROLE_DESCS = {
    rate_calculator: "Cost estimation tool only",
    read: "View rates, run calculator",
    edit: "Create / update rates",
    fbp: "Analytical, AI Q&A, export",
    procurement: "Contract upload, AI Q&A, export",
  };
  const [form, setForm]             = useState({ role: "read", regions: [], areas: [], justification: "" });
  const [behalfEmail, setBehalfEmail] = useState("");
  const [regionObjs, setRegionObjs] = useState([]);   // { id, name }
  const [allAreas, setAllAreas]     = useState([]);   // { id, name, region_id }
  const [approvers, setApprovers]   = useState([]);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError]           = useState(null);
  const [areaOpen, setAreaOpen]     = useState(false);
  const [areaSearch, setAreaSearch] = useState("");
  const areaRef = React.useRef(null);

  React.useEffect(() => {
    Promise.all([
      apiFetch("/api/metadata/regions"),
      apiFetch("/api/metadata/areas"),
    ]).then(([r, a]) => {
      setRegionObjs(Array.isArray(r) ? r : []);
      setAllAreas(Array.isArray(a) ? a : []);
    }).catch(() => {});
  }, []);

  React.useEffect(() => {
    const h = e => { if (areaRef.current && !areaRef.current.contains(e.target)) setAreaOpen(false); };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  React.useEffect(() => {
    if (form.regions.length === 0) { setApprovers([]); return; }
    apiFetch(`/api/access-requests/routing-preview?regions=${encodeURIComponent(form.regions.join(","))}`)
      .then(r => setApprovers(Array.isArray(r.approvers) ? r.approvers : []))
      .catch(() => {});
  }, [form.regions]);

  // Areas available for current selected regions
  const selectedRegionIds = regionObjs.filter(r => form.regions.includes(r.name)).map(r => r.id);
  const availableAreas = selectedRegionIds.length > 0
    ? allAreas.filter(a => selectedRegionIds.includes(a.region_id))
    : allAreas;
  const filteredAreas = areaSearch.trim()
    ? availableAreas.filter(a => a.name.toLowerCase().includes(areaSearch.trim().toLowerCase()))
    : availableAreas;

  const toggleRegion = r => {
    const has = form.regions.includes(r);
    const newRegions = has ? form.regions.filter(x => x !== r) : [...form.regions, r];
    // When deselecting a region, remove areas that no longer belong to any selected region
    if (has) {
      const remaining = regionObjs.filter(ro => newRegions.includes(ro.name)).map(ro => ro.id);
      const validAreaNames = allAreas.filter(a => remaining.includes(a.region_id)).map(a => a.name);
      setForm(f => ({ ...f, regions: newRegions, areas: f.areas.filter(a => validAreaNames.includes(a)) }));
    } else {
      setForm(f => ({ ...f, regions: newRegions }));
    }
  };

  const toggleArea = name => {
    setForm(f => ({
      ...f,
      areas: f.areas.includes(name) ? f.areas.filter(x => x !== name) : [...f.areas, name],
    }));
  };

  const submit = async () => {
    if (onBehalf && !behalfEmail.trim()) { setError("Target user email is required."); return; }
    if (onBehalf && !behalfEmail.includes("@")) { setError("Please enter a valid email address."); return; }
    if (!form.justification.trim()) { setError("Business justification is required."); return; }
    if (form.regions.length === 0) { setError("Please select at least one region."); return; }
    setSubmitting(true); setError(null);
    try {
      await apiFetch("/api/access-requests", {
        method: "POST",
        body: JSON.stringify({
          requested_role: form.role,
          requested_regions: JSON.stringify(form.regions),
          requested_areas: JSON.stringify(form.areas),
          justification: form.justification,
          ...(onBehalf ? { on_behalf_of: behalfEmail.trim().toLowerCase() } : {}),
        }),
      });
      onSubmitted?.();
      onClose();
    } catch (e) { setError(e.message); setSubmitting(false); }
  };

  return (
    <SlideOver
      open={true}
      onClose={onClose}
      title={onBehalf ? "Apply access on behalf of" : "New access request"}
      sub="SLA 3 business days · approval by Contract Manager(s)"
      foot={
        <>
          <button className="btn ghost" onClick={onClose} disabled={submitting}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={submitting}>
            <Icon name="bolt" size={14} /> {submitting ? "Submitting…" : "Submit request"}
          </button>
        </>
      }
    >
      <div style={{ display: "grid", gap: 14 }}>
        <Field label="Email (Maersk SSO)" required>
          {onBehalf ? (
            <input className="input mono" type="email" value={behalfEmail}
              onChange={e => setBehalfEmail(e.target.value)}
              placeholder="target.user@maersk.com" autoFocus />
          ) : (
            <input className="input mono" value={user?.email || "—"} readOnly style={{ background: "var(--paper)" }} />
          )}
        </Field>
        <Field label="Requested Role" required>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 6 }}>
            {Object.keys(ROLE_DESCS).map(r => (
              <label key={r} className="card" style={{ padding: 10, cursor: "pointer", display: "flex", gap: 6, alignItems: "flex-start", borderColor: form.role === r ? "var(--teal)" : "var(--line)" }}>
                <input type="radio" checked={form.role === r} onChange={() => setForm(f => ({ ...f, role: r }))} style={{ marginTop: 2 }} />
                <div>
                  <div className="bold mono tiny">{r}</div>
                  <div className="tiny muted">{ROLE_DESCS[r]}</div>
                </div>
              </label>
            ))}
          </div>
        </Field>
        <Field label="Region(s)" required>
          <div className="row gap-6" style={{ flexWrap: "wrap" }}>
            {regionObjs.map(r => (
              <label key={r.id} className="check card" style={{ padding: "6px 10px", borderColor: form.regions.includes(r.name) ? "var(--teal)" : "var(--line)" }}>
                <input type="checkbox" checked={form.regions.includes(r.name)} onChange={() => toggleRegion(r.name)} />
                {r.name}
              </label>
            ))}
          </div>
        </Field>
        <Field label="Area(s)" hint="Optional · narrows access within selected regions">
          <div ref={areaRef} style={{ position: "relative" }}>
            {/* Trigger button */}
            <button type="button" onClick={() => { setAreaOpen(o => !o); setAreaSearch(""); }}
              style={{ width: "100%", padding: "7px 10px", textAlign: "left", fontSize: 13,
                border: `1px solid ${form.areas.length > 0 ? "var(--teal)" : "var(--border)"}`,
                borderRadius: 6, background: form.areas.length > 0 ? "rgba(0,158,135,.06)" : "var(--white)",
                cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}>
              {form.areas.length === 0
                ? <span style={{ color: "var(--ink-mute)" }}>Select area(s)…</span>
                : <span style={{ color: "var(--teal)", fontWeight: 500 }}>{form.areas.length} area{form.areas.length > 1 ? "s" : ""} selected</span>}
              <span style={{ marginLeft: "auto", color: "var(--ink-mute)", fontSize: 11 }}>{areaOpen ? "▴" : "▾"}</span>
            </button>
            {/* Selected tags */}
            {form.areas.length > 0 && (
              <div className="row gap-4" style={{ flexWrap: "wrap", marginTop: 6 }}>
                {form.areas.map(a => (
                  <span key={a} style={{ display: "flex", alignItems: "center", gap: 4, padding: "2px 8px",
                    background: "rgba(0,158,135,.12)", borderRadius: 12, fontSize: 11, color: "var(--teal)" }}>
                    {a}
                    <button type="button" onClick={() => toggleArea(a)}
                      style={{ background: "none", border: "none", cursor: "pointer", padding: 0, lineHeight: 1, color: "var(--teal)", fontSize: 14 }}>×</button>
                  </span>
                ))}
              </div>
            )}
            {/* Dropdown panel */}
            {areaOpen && (
              <div style={{ position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0, zIndex: 600,
                background: "var(--white)", border: "1px solid var(--border)", borderRadius: 6,
                boxShadow: "0 6px 20px rgba(0,0,0,.15)", maxHeight: 260, display: "flex", flexDirection: "column" }}>
                <div style={{ padding: "8px 10px", borderBottom: "1px solid var(--border)" }}>
                  <input autoFocus type="text" value={areaSearch} onChange={e => setAreaSearch(e.target.value)}
                    placeholder="Search areas…" style={{ width: "100%", padding: "5px 8px", fontSize: 12,
                      border: "1px solid var(--border)", borderRadius: 4, outline: "none", boxSizing: "border-box" }} />
                </div>
                <div style={{ overflowY: "auto", flex: 1 }}>
                  {availableAreas.length === 0 && (
                    <div className="tiny muted" style={{ padding: "12px 12px" }}>
                      {form.regions.length === 0 ? "Select a region first" : "No areas for selected region(s)"}
                    </div>
                  )}
                  {filteredAreas.length === 0 && availableAreas.length > 0 && (
                    <div className="tiny muted" style={{ padding: "12px 12px" }}>No matches</div>
                  )}
                  {filteredAreas.map(a => (
                    <label key={a.id} style={{ display: "flex", alignItems: "center", gap: 8,
                      padding: "7px 12px", cursor: "pointer", fontSize: 13,
                      background: form.areas.includes(a.name) ? "rgba(0,158,135,.06)" : "transparent" }}>
                      <input type="checkbox" style={{ margin: 0, cursor: "pointer", accentColor: "var(--teal)" }}
                        checked={form.areas.includes(a.name)} onChange={() => toggleArea(a.name)} />
                      <span>{a.name}</span>
                    </label>
                  ))}
                </div>
                <div style={{ padding: "6px 10px", borderTop: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <button type="button" className="btn ghost tiny" style={{ fontSize: 11 }}
                    disabled={filteredAreas.length === 0}
                    onClick={() => {
                      const next = new Set(form.areas);
                      filteredAreas.forEach(a => next.add(a.name));
                      setForm(f => ({ ...f, areas: [...next] }));
                    }}>Select all{areaSearch.trim() ? " filtered" : ""}</button>
                  <button type="button" className="btn ghost tiny" style={{ fontSize: 11, color: "var(--ink-mute)" }}
                    disabled={form.areas.length === 0}
                    onClick={() => setForm(f => ({ ...f, areas: [] }))}>Clear all</button>
                </div>
              </div>
            )}
          </div>
        </Field>
        <Field label="Business Justification" required hint="Will be shown to the approving Contract Manager">
          <textarea className="input" rows={4} value={form.justification}
            onChange={e => setForm(f => ({ ...f, justification: e.target.value }))}
            placeholder="Explain why you need this access and how you will use the rate data…" />
        </Field>
        {error && <div className="tiny" style={{ color: "var(--red)" }}>{error}</div>}
        <div style={{ background: "var(--teal-soft)", border: "1px solid #c8e3ea", borderRadius: 6, padding: 12, display: "flex", gap: 10 }}>
          <Icon name="bolt" size={16} stroke="var(--teal)" />
          <div className="tiny">
            <b style={{ color: "var(--teal)" }}>Routing preview.</b>{" "}
            {approvers.length === 0
              ? "Select region(s) above to see who will receive this request."
              : <>This request will be sent for approval to: <span className="mono">{approvers.join(", ")}</span></>}
          </div>
        </div>
      </div>
    </SlideOver>
  );
}

function RequestDetail({ req, isApprover, onClose, onAction }) {
  const [comment, setComment]   = useState("");
  const [acting,  setActing]    = useState(null);   // "approve" | "reject"

  const parseRegions = str => { try { return JSON.parse(str).join(", "); } catch { return str || "—"; } };
  const parseAreas   = str => { try { const a = JSON.parse(str); return a.length ? a.join(", ") : "—"; } catch { return str || "—"; } };

  const doAction = async action => {
    setActing(action);
    try {
      await apiFetch(`/api/admin/access-requests/${req.id}/${action}`, {
        method: "PUT",
        body: JSON.stringify({ comment: comment || null }),
      });
      onAction?.();
    } catch (e) { alert(e.message); setActing(null); }
  };

  const isPending = req.status === "Pending";
  const reqLabel = `AR-${String(req.id).padStart(4, "0")}`;

  return (
    <SlideOver
      open={true}
      onClose={onClose}
      title={`${reqLabel} · ${req.requester_name}`}
      sub={`Submitted ${req.submitted_at?.slice(0, 16)}`}
      foot={isPending && isApprover ? (
        <>
          <button className="btn ghost" onClick={onClose} disabled={!!acting}>Close</button>
          <div className="grow" />
          <button className="btn danger" onClick={() => doAction("reject")} disabled={!!acting}>
            {acting === "reject" ? "Rejecting…" : "Reject"}
          </button>
          <button className="btn primary" onClick={() => doAction("approve")} disabled={!!acting}>
            <Icon name="check" size={14} /> {acting === "approve" ? "Approving…" : "Approve"}
          </button>
        </>
      ) : <button className="btn" onClick={onClose}>Close</button>}
    >
      <div style={{ display: "grid", gap: 14 }}>
        <div className="row gap-8">
          <Pill status={req.status} />
          {_slaDisplay(req.sla_deadline, req.status)}
        </div>
        <Card>
          <div style={{ padding: 16 }}>
            <table style={{ width: "100%", fontSize: 13 }}>
              <tbody>
                <DRow k="Requester" v={<><b>{req.requester_name}</b> <span className="muted mono tiny">{req.requester_email}</span></>} />
                <DRow k="Requested role" v={<Pill className="plain">{req.requested_role}</Pill>} />
                <DRow k="Region(s)" v={parseRegions(req.requested_regions)} />
                <DRow k="Area(s)" v={parseAreas(req.requested_areas)} />
                <DRow k="Routed to" v={<span className="mono">{req.routed_to_email || "—"}</span>} />
                <DRow k="Submitted" v={<span className="mono">{req.submitted_at?.slice(0, 16)}</span>} />
                {req.reviewed_by && <DRow k="Reviewed by" v={<span className="mono">{req.reviewed_by} · {req.reviewed_at?.slice(0, 16)}</span>} />}
                {req.review_comment && <DRow k="Comment" v={req.review_comment} />}
              </tbody>
            </table>
          </div>
        </Card>
        <Card title="Business Justification">
          <div style={{ padding: 16, fontSize: 13, lineHeight: 1.6 }}>{req.justification}</div>
        </Card>
        {isPending && isApprover && (
          <Field label="Review Comment" hint="Optional · visible to requester">
            <textarea className="input" rows={3} value={comment} onChange={e => setComment(e.target.value)}
              placeholder="Add a note (optional)…" />
          </Field>
        )}
        <Card title="Workflow trace">
          <div style={{ padding: "4px 0" }}>
            {[
              { ok: true,  l: "Request submitted",          ts: req.submitted_at?.slice(0,16),  by: req.requester_name },
              { ok: !!req.routed_to_email, l: "Routed to approver(s)", ts: req.submitted_at?.slice(0,16), by: req.routed_to_email || "No routing configured" },
              { ok: req.status !== "Pending",
                l: req.status === "Approved" ? "Approved" : req.status === "Rejected" ? "Rejected" : "Awaiting review",
                ts: req.reviewed_at?.slice(0, 16) || "—",
                by: req.reviewed_by || "—" },
            ].map((s, i) => (
              <div key={i} style={{ padding: "10px 16px", borderTop: i === 0 ? 0 : "1px solid var(--line)", display: "flex", gap: 12 }}>
                <div style={{ width: 16, height: 16, borderRadius: 50, background: s.ok ? "var(--green)" : "var(--paper-2)", color: s.ok ? "#fff" : "var(--ink-mute)", display: "grid", placeItems: "center", flexShrink: 0, marginTop: 1 }}>
                  {s.ok ? <Icon name="check" size={9} /> : "·"}
                </div>
                <div>
                  <div className="tiny">{s.l}</div>
                  <div className="tiny muted mono">{s.ts} · {s.by}</div>
                </div>
              </div>
            ))}
          </div>
        </Card>
      </div>
    </SlideOver>
  );
}

// ============== ADMIN ==============
function Admin({ user }) {
  const [tab, setTab] = useState("audit");
  const [activeUserCount, setActiveUserCount] = useState(null);

  React.useEffect(() => {
    apiFetch("/api/admin/users?status=Active")
      .then(r => setActiveUserCount(Array.isArray(r) ? r.length : null))
      .catch(() => {});
  }, []);

  const tabs = [
    { id: "audit",       label: "Audit Log" },
    { id: "users",       label: "Users", count: activeUserCount },
    { id: "permissions", label: "Permissions" },
    { id: "metadata",    label: "Metadata" },
    { id: "routing",     label: "Approval Routing" },
  ];
  return (
    <div>
      <PageHeader title="Admin" sub="System configuration · audit · access control" />
      <Tabs tabs={tabs} current={tab} onChange={setTab} />
      {tab === "audit"       && <AuditLog />}
      {tab === "users"       && <UserManagement />}
      {tab === "permissions" && <PermissionsMatrix currentUser={user} />}
      {tab === "metadata"    && <Metadata />}
      {tab === "routing"     && <RoutingTable />}
    </div>
  );
}

function AuditLog() {
  const [filters, setFilters] = useState({ from: "2025-05-01", to: "2025-05-13", event: "", user: "" });
  return (
    <div>
      <div className="filterbar">
        <FilterChipMenu keyLabel="Event" value={filters.event} options={["Rate Created","Rate Updated","Rate Sheet Exported","Contract Uploaded (DIFY)","Access Approved","Calculator Used","User Login","ROE Updated","Metadata Updated"]} onChange={v => setFilters({ ...filters, event: v })} />
        <FilterChipMenu keyLabel="User" value={filters.user} options={["P. Wijaya","S. Lindqvist","R. Singh","D. Park","L. Tanaka","M. Chen"]} onChange={v => setFilters({ ...filters, user: v })} />
        <FilterChip keyLabel="Date range" value={`${filters.from} → ${filters.to}`} set onClick={()=>{}} />
        <div className="grow" />
        <div className="search">
          <Icon name="search" size={14} />
          <input placeholder="Search by target, IP, content…" />
        </div>
        <button className="btn"><Icon name="download" size={14} /> Export log</button>
      </div>
      <div className="grid-4" style={{ marginBottom: 16 }}>
        <KPI label="Events (7d)" value="2,184" sub="↑ 12% vs prior week" deltaDir="up" />
        <KPI label="Rate Changes" value="148" sub="83 created · 65 updated" />
        <KPI label="Exports" value="34" sub="6 users" />
        <KPI label="Retention" value="5.0 yr" sub="Archival after Y3" />
      </div>
      <Card title="Event stream" sub="Immutable · 5-year retention">
        <div className="table-wrap">
          <table className="table">
            <thead>
              <tr>
                <th>Timestamp</th>
                <th>User</th>
                <th>Event</th>
                <th>Target</th>
                <th>Detail</th>
                <th>IP</th>
              </tr>
            </thead>
            <tbody>
              {AUDIT.map((a, i) => (
                <tr key={i}>
                  <td className="mono tiny muted">{a.ts}</td>
                  <td className="tiny">{a.user}</td>
                  <td><Pill className="plain">{a.event}</Pill></td>
                  <td className="tiny mono">{a.target}</td>
                  <td className="tiny muted">{a.detail}</td>
                  <td className="tiny mono muted">{a.ip}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </Card>
    </div>
  );
}

const USER_IMPORT_CHUNK = 25;

function downloadUserTemplate() {
  const header = "email,name,status,role,regions,areas";
  const example = "john.doe@maersk.com,John Doe,Active,Viewer,APA;NALA,CNSHA;SGSIN";
  const csv = [header, example].join("\r\n");
  const a = Object.assign(document.createElement("a"), {
    href: URL.createObjectURL(new Blob([csv], { type: "text/csv" })),
    download: "ocms_users_template.csv",
  });
  a.click();
}

function UserManagement() {
  const [users,          setUsers]          = useState([]);
  const [loading,        setLoading]        = useState(true);
  const [statusFilter,   setStatusFilter]   = useState("Active");
  const [search,         setSearch]         = useState("");
  const [acting,         setActing]         = useState(null);
  const [reload,         setReload]         = useState(0);
  const [importProgress, setImportProgress] = useState(null); // null | { total, done }
  const fileRef = React.useRef(null);

  React.useEffect(() => {
    setLoading(true);
    apiFetch("/api/admin/users")
      .then(r => { setUsers(Array.isArray(r) ? r : []); setLoading(false); })
      .catch(() => setLoading(false));
  }, [reload]);

  const toggleStatus = async (u) => {
    const next = u.status === "Active" ? "Suspended" : "Active";
    if (!confirm(`${next === "Suspended" ? "Suspend" : "Reactivate"} ${u.name || u.email}?`)) return;
    setActing(u.id);
    try {
      await apiFetch(`/api/admin/users/${u.id}/status`, { method: "PUT", body: JSON.stringify({ status: next }) });
      setReload(n => n + 1);
    } catch (e) { alert(e.message); }
    finally { setActing(null); }
  };

  const handleExport = () => {
    const rows = users.map(u => ({
      email:   u.email,
      name:    u.name || "",
      status:  u.status,
      role:    u.role || "",
      regions: (u.regions || []).join(";"),
      areas:   (u.areas   || []).join(";"),
    }));
    exportCSV(rows, `ocms_users_${new Date().toISOString().slice(0,10)}.csv`);
  };

  const handleImport = e => {
    const file = e.target.files?.[0];
    if (!file) return;
    e.target.value = "";
    const reader = new FileReader();
    reader.onload = async ev => {
      const rawRows = parseCSV(ev.target.result);
      if (!rawRows.length) { alert("No valid rows found in CSV."); return; }
      setImportProgress({ total: rawRows.length, done: 0 });
      let ok = 0, fail = 0;
      for (let i = 0; i < rawRows.length; i += USER_IMPORT_CHUNK) {
        const chunk = rawRows.slice(i, i + USER_IMPORT_CHUNK);
        try {
          const res = await apiFetch("/api/admin/users/import", {
            method: "POST",
            body: JSON.stringify({ rows: chunk }),
          });
          ok   += (res.inserted || 0) + (res.updated || 0);
          fail += res.skipped || 0;
        } catch { fail += chunk.length; }
        setImportProgress({ total: rawRows.length, done: Math.min(i + USER_IMPORT_CHUNK, rawRows.length) });
      }
      setImportProgress(null);
      setReload(n => n + 1);
      alert(`Import complete: ${ok} added/updated, ${fail} skipped.`);
    };
    reader.readAsText(file);
  };

  const filtered = users.filter(u => {
    if (statusFilter && u.status !== statusFilter) return false;
    if (search) {
      const q = search.toLowerCase();
      return (u.name || "").toLowerCase().includes(q) || (u.email || "").toLowerCase().includes(q);
    }
    return true;
  });

  const statusCounts = {};
  ["Active","Suspended"].forEach(s => { statusCounts[s] = users.filter(u => u.status === s).length; });

  return (
    <div>
      <input ref={fileRef} type="file" accept=".csv" style={{ display: "none" }} onChange={handleImport} />
      <div className="filterbar">
        {["Active","Suspended",""].map(s => (
          <FilterChip key={s || "all"} keyLabel="Status" value={s || "All"} set={statusFilter === s} onClick={() => setStatusFilter(s)} />
        ))}
        <div className="search"><Icon name="search" size={14} /><input placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} /></div>
        <div className="grow" />
        <button className="btn ghost tiny" onClick={() => setReload(n => n + 1)}><Icon name="refresh" size={14} /></button>
        <button className="btn ghost tiny" onClick={downloadUserTemplate}><Icon name="download" size={13} /> Template</button>
        <button className="btn ghost tiny" onClick={handleExport} disabled={!users.length}><Icon name="download" size={13} /> Export</button>
        {importProgress ? (
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <div style={{ width: 120, height: 6, background: "var(--border)", borderRadius: 3, overflow: "hidden" }}>
              <div style={{ height: "100%", borderRadius: 3, background: "var(--teal)", width: `${Math.round(importProgress.done / importProgress.total * 100)}%`, transition: "width .2s" }} />
            </div>
            <span className="tiny muted">{importProgress.done} / {importProgress.total}</span>
          </div>
        ) : (
          <button className="btn primary tiny" onClick={() => fileRef.current?.click()}><Icon name="upload" size={13} /> Import</button>
        )}
      </div>
      <Card>
        {loading ? (
          <div className="muted tiny" style={{ padding: 24 }}>Loading…</div>
        ) : filtered.length === 0 ? (
          <div className="muted tiny" style={{ padding: 24 }}>No users found.</div>
        ) : (
          <div className="table-wrap">
            <table className="table">
              <thead>
                <tr>
                  <th>User</th>
                  <th>Role</th>
                  <th>Region / Area</th>
                  <th>Status</th>
                  <th>Last login</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {filtered.map(u => (
                  <tr key={u.email}>
                    <td>
                      <div className="bold tiny">{u.name || "—"}</div>
                      <div className="tiny muted mono">{u.email}</div>
                    </td>
                    <td>
                      {u.role
                        ? <span className="pill plain" style={{ fontSize: 10 }}>{u.role}</span>
                        : <span className="tiny muted">—</span>}
                    </td>
                    <td>
                      <div className="row gap-4" style={{ flexWrap: "wrap" }}>
                        {(u.regions || []).map((r, i) => (
                          <span key={i} className="pill plain" style={{ fontSize: 10 }}>{r}</span>
                        ))}
                        {(u.areas || []).map((a, i) => (
                          <span key={i} className="pill" style={{ fontSize: 10, background: "rgba(0,158,135,.08)", color: "var(--teal)" }}>{a}</span>
                        ))}
                        {(!u.regions?.length && !u.areas?.length) && <span className="tiny muted">—</span>}
                      </div>
                    </td>
                    <td>
                      <Pill status={u.status === "Active" ? "Active" : "Rejected"}>{u.status}</Pill>
                    </td>
                    <td className="tiny mono muted">
                      {u.last_login ? u.last_login.replace("T", " ").slice(0, 16) : "—"}
                    </td>
                    <td>
                      <button
                        className="btn tiny"
                        disabled={acting === u.id}
                        onClick={() => toggleStatus(u)}
                        title={u.status === "Active" ? "Suspend user" : "Reactivate user"}
                        style={{
                          color: u.status === "Active" ? "var(--red)" : "var(--green)",
                          borderColor: u.status === "Active" ? "var(--red)" : "var(--green)",
                          opacity: acting === u.id ? 0.5 : 1,
                        }}
                      >
                        {u.status === "Active"
                          ? <><Icon name="eyeOff" size={12} /> Suspend</>
                          : <><Icon name="check" size={12} /> Activate</>}
                      </button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
        <div className="tiny muted" style={{ padding: "6px 12px" }}>
          {filtered.length} of {users.length} users · Active: {statusCounts.Active || 0} · Suspended: {statusCounts.Suspended || 0}
        </div>
      </Card>
    </div>
  );
}

function useMetadata(apiSlug) {
  const [data, setData] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const load = React.useCallback(() => {
    setLoading(true);
    apiFetch(`/api/metadata/${apiSlug}`)
      .then(r => { setData(Array.isArray(r) ? r : []); setLoading(false); })
      .catch(() => setLoading(false));
  }, [apiSlug]);
  React.useEffect(() => { load(); }, [load]);
  return { data, loading, refresh: load };
}

function MetadataTable({ loading, children }) {
  if (loading) return <div className="muted tiny" style={{ padding: 24 }}>Loading…</div>;
  return children;
}

function exportCSV(rows, filename) {
  if (!rows.length) return;
  const keys = Object.keys(rows[0]);
  const escape = v => { const s = v == null ? "" : String(v); return (s.includes(",") || s.includes('"') || s.includes("\n")) ? `"${s.replace(/"/g, '""')}"` : s; };
  const csv = [keys.join(","), ...rows.map(r => keys.map(k => escape(r[k])).join(","))].join("\r\n");
  const a = Object.assign(document.createElement("a"), { href: URL.createObjectURL(new Blob([csv], { type: "text/csv" })), download: filename });
  a.click();
}

function parseCSV(text) {
  const parseRow = line => {
    const cells = []; let cur = "", inQ = false;
    for (let i = 0; i < line.length; i++) {
      const c = line[i];
      if (inQ) {
        if (c === '"' && line[i + 1] === '"') { cur += '"'; i++; }
        else if (c === '"') { inQ = false; }
        else cur += c;
      } else {
        if (c === '"') inQ = true;
        else if (c === ',') { cells.push(cur.trim()); cur = ""; }
        else cur += c;
      }
    }
    cells.push(cur.trim());
    return cells;
  };
  const lines = text.trim().split(/\r?\n/);
  if (lines.length < 2) return [];
  const headers = parseRow(lines[0]);
  return lines.slice(1).filter(l => l.trim()).map(line => {
    const vals = parseRow(line);
    const obj = {};
    headers.forEach((h, i) => { obj[h] = vals[i] ?? ""; });
    return obj;
  });
}

function MetadataModal({ entity, fields, row, dropdowns, onClose, onSaved }) {
  const isEdit = !!(row && row.id);
  const init = {};
  fields.forEach(f => { init[f.key] = isEdit ? (row[f.key] ?? "") : (f.default ?? ""); });
  const [form, setForm] = React.useState(init);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);

  const set = (k, v) => setForm(prev => ({ ...prev, [k]: v }));

  const submit = async () => {
    setSaving(true); setError(null);
    const body = { ...form };
    fields.forEach(f => {
      if (f.type === "number") body[f.key] = body[f.key] !== "" && body[f.key] != null ? parseFloat(body[f.key]) : null;
      if (f.type === "select" && f.numeric) body[f.key] = body[f.key] !== "" && body[f.key] != null ? parseInt(body[f.key]) : null;
      if (!f.required && body[f.key] === "") body[f.key] = null;
    });
    try {
      if (isEdit) await apiFetch(`/api/admin/metadata/${entity}/${row.id}`, { method: "PUT", body: JSON.stringify({ data: body }) });
      else await apiFetch(`/api/admin/metadata/${entity}`, { method: "POST", body: JSON.stringify({ data: body }) });
      onSaved();
    } catch (e) { setError(e.message); setSaving(false); }
  };

  const toggleActive = async () => {
    if (!isEdit) return;
    if (!confirm(`${row.is_active ? "Deactivate" : "Reactivate"} "${row.name || row.code || row.iso_code}"?`)) return;
    try {
      await apiFetch(`/api/admin/metadata/${entity}/${row.id}`, { method: "PUT", body: JSON.stringify({ data: { is_active: !row.is_active } }) });
      onSaved();
    } catch (e) { alert(e.message); }
  };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.4)", zIndex: 900, display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="card" style={{ width: 580, maxHeight: "82vh", overflowY: "auto", padding: 24 }}>
        <div className="row gap-8" style={{ marginBottom: 18, alignItems: "center" }}>
          <span style={{ fontWeight: 700, fontSize: 15 }}>{isEdit ? "Edit" : "Add"} {entity.replace(/-/g, " ")}</span>
          <div className="grow" />
          {isEdit && (
            <button className="btn ghost tiny" onClick={toggleActive} style={{ color: row.is_active ? "var(--red)" : "var(--green)" }}>
              {row.is_active ? "Deactivate" : "Reactivate"}
            </button>
          )}
          <button className="btn ghost tiny" onClick={onClose}><Icon name="x" size={14} /></button>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 16px" }}>
          {fields.map(f => {
            const opts = f.optionsKey ? (dropdowns[f.optionsKey] || []) : [];
            const inputStyle = { padding: "6px 8px", borderRadius: 6, border: "1px solid var(--border)", fontSize: 13, width: "100%", boxSizing: "border-box" };
            return (
              <label key={f.key} style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12, gridColumn: f.wide ? "1/-1" : undefined }}>
                <span>{f.label}{f.required && <span style={{ color: "var(--red)" }}> *</span>}</span>
                {f.type === "select" ? (
                  <select value={form[f.key] ?? ""} onChange={e => set(f.key, e.target.value)} style={inputStyle}>
                    <option value="">—</option>
                    {opts.map(o => <option key={o[f.optionValue]} value={o[f.optionValue]}>{o[f.optionLabel]}</option>)}
                  </select>
                ) : f.type === "textarea" ? (
                  <textarea value={form[f.key] ?? ""} onChange={e => set(f.key, e.target.value)} rows={3}
                    style={{ ...inputStyle, resize: "vertical" }} />
                ) : (
                  <input type={f.type || "text"} value={form[f.key] ?? ""} onChange={e => set(f.key, e.target.value)}
                    placeholder={f.placeholder} step={f.step} style={inputStyle} />
                )}
              </label>
            );
          })}
        </div>
        {error && <div className="tiny" style={{ color: "var(--red)", marginTop: 10 }}>{error}</div>}
        <div className="row gap-8" style={{ marginTop: 18, justifyContent: "flex-end" }}>
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={saving}>{saving ? "Saving…" : (isEdit ? "Save changes" : "Add")}</button>
        </div>
      </div>
    </div>
  );
}

function Paginator({ total, page, pageSize, onPage, onPageSize }) {
  const totalPages = Math.ceil(total / pageSize);
  if (total === 0 || (totalPages <= 1 && pageSize <= 20 && total <= 20)) return null;

  const show = new Set([1, totalPages, page, page - 1, page + 1].filter(p => p >= 1 && p <= totalPages));
  const sorted = [...show].sort((a, b) => a - b);
  const items = [];
  let prev = 0;
  for (const p of sorted) {
    if (prev && p - prev > 1) items.push("…");
    items.push(p);
    prev = p;
  }

  return (
    <div className="row gap-8" style={{ alignItems: "center", justifyContent: "space-between", padding: "10px 0 2px", borderTop: "1px solid var(--border)", marginTop: 6 }}>
      <div className="row gap-6 tiny muted" style={{ alignItems: "center" }}>
        <span>Showing {Math.min((page - 1) * pageSize + 1, total)}–{Math.min(page * pageSize, total)} of {total}</span>
        <select value={pageSize} onChange={e => { onPageSize(Number(e.target.value)); onPage(1); }}
          style={{ fontSize: 11, border: "1px solid var(--border)", borderRadius: 3, padding: "1px 4px", cursor: "pointer", background: "var(--paper)" }}>
          {[20, 50, 100].map(n => <option key={n} value={n}>{n} / page</option>)}
        </select>
      </div>
      <div className="row gap-2">
        <button className="btn ghost tiny" onClick={() => onPage(page - 1)} disabled={page === 1}
          style={{ padding: "2px 8px", minWidth: 28 }}>‹</button>
        {items.map((item, i) =>
          item === "…"
            ? <span key={"e" + i} className="tiny muted" style={{ padding: "0 4px", lineHeight: "24px" }}>…</span>
            : <button key={item} onClick={() => onPage(item)}
                className={"btn tiny" + (item === page ? " primary" : " ghost")}
                style={{ padding: "2px 8px", minWidth: 28 }}>{item}</button>
        )}
        <button className="btn ghost tiny" onClick={() => onPage(page + 1)} disabled={page === totalPages}
          style={{ padding: "2px 8px", minWidth: 28 }}>›</button>
      </div>
    </div>
  );
}

function Metadata() {
  const [entity, setEntity] = useState("terminals");
  const [modal, setModal] = useState(null);
  const [selectedIds, setSelectedIds] = useState(new Set());
  const [deleting, setDeleting] = useState(false);
  const [importProgress, setImportProgress] = useState(null); // null | { total, done }
  const [search, setSearch] = useState("");
  const [colFilters, setColFilters] = useState({});  // { [key]: string[] }
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const [openDropdown, setOpenDropdown] = React.useState(null);
  const [dropdownSearch, setDropdownSearch] = React.useState("");

  // Close dropdown when clicking anywhere outside a filter cell
  React.useEffect(() => {
    const handler = () => setOpenDropdown(null);
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  const { data: terminals, loading: tLoad, refresh: tRefresh } = useMetadata("terminals");
  const { data: costTypes, loading: ctLoad, refresh: ctRefresh } = useMetadata("cost-types");
  const { data: chargeUnits, loading: cuLoad, refresh: cuRefresh } = useMetadata("charge-units");
  const { data: regions, loading: rLoad, refresh: rRefresh } = useMetadata("regions");
  const { data: currencies, loading: cLoad, refresh: cRefresh } = useMetadata("currencies");
  const { data: areas, loading: aLoad, refresh: aRefresh } = useMetadata("areas");
  const { data: cities, loading: cityLoad, refresh: cityRefresh } = useMetadata("cities");
  const { data: paymentTerms, loading: ptLoad, refresh: ptRefresh } = useMetadata("payment-terms");
  const { data: vendors, loading: vLoad, refresh: vRefresh } = useMetadata("vendors");
  const { data: brandCompanies, loading: bcLoad, refresh: bcRefresh } = useMetadata("brand-companies");
  const { data: materials, loading: matLoad, refresh: matRefresh } = useMetadata("materials");
  const { data: equipmentTypes, loading: etLoad, refresh: etRefresh } = useMetadata("equipment-types");
  const { data: moveTypes, loading: mtLoad, refresh: mtRefresh } = useMetadata("move-types");
  const { data: slabUnits, loading: suLoad, refresh: suRefresh } = useMetadata("slab-units");
  const { data: slabTypes, loading: stLoad, refresh: stRefresh } = useMetadata("slab-types");
  const { data: rateStatuses, loading: rsLoad, refresh: rsRefresh } = useMetadata("rate-statuses");
  const [allRegions, setAllRegions] = React.useState([]);
  React.useEffect(() => {
    apiFetch("/api/metadata/regions/all")
      .then(r => setAllRegions(Array.isArray(r) ? r : []))
      .catch(() => {});
  }, []);

  const refreshMap = { "terminals": tRefresh, "cost-types": ctRefresh, "charge-units": cuRefresh, "regions": rRefresh, "currencies": cRefresh, "areas": aRefresh, "cities": cityRefresh, "payment-terms": ptRefresh, "vendors": vRefresh, "brand-companies": bcRefresh, "materials": matRefresh, "equipment-types": etRefresh, "move-types": mtRefresh, "slab-units": suRefresh, "slab-types": stRefresh, "rate-statuses": rsRefresh };
  const dataMap    = { "terminals": terminals, "cost-types": costTypes, "charge-units": chargeUnits, "regions": regions, "currencies": currencies, "areas": areas, "cities": cities, "payment-terms": paymentTerms, "vendors": vendors, "brand-companies": brandCompanies, "materials": materials, "equipment-types": equipmentTypes, "move-types": moveTypes, "slab-units": slabUnits, "slab-types": slabTypes, "rate-statuses": rateStatuses };

  const entities = [
    { id: "terminals",     label: "Terminals",      count: terminals.length,    loading: tLoad },
    { id: "cities",        label: "Cities",         count: cities.length,       loading: cityLoad },
    { id: "cost-types",    label: "Cost Types",     count: costTypes.length,    loading: ctLoad },
    { id: "charge-units",  label: "Charge Units",   count: chargeUnits.length,  loading: cuLoad },
    { id: "regions",       label: "Regions",        count: regions.length,      loading: rLoad },
    { id: "areas",         label: "Areas",          count: areas.length,        loading: aLoad },
    { id: "currencies",    label: "Currencies",     count: currencies.length,   loading: cLoad },
    { id: "payment-terms", label: "Payment Terms",  count: paymentTerms.length, loading: ptLoad },
    { id: "vendors",          label: "Vendor List",      count: vendors.length,        loading: vLoad },
    { id: "brand-companies",  label: "Brand Companies",  count: brandCompanies.length, loading: bcLoad },
    { id: "materials",        label: "Material List",    count: materials.length,      loading: matLoad },
    { id: "equipment-types", label: "Equipment Types", count: equipmentTypes.length, loading: etLoad },
    { id: "move-types",      label: "Move Types",      count: moveTypes.length,      loading: mtLoad },
    { id: "slab-units",      label: "Slab Units",      count: slabUnits.length,      loading: suLoad },
    { id: "slab-types",      label: "Slab Types",      count: slabTypes.length,      loading: stLoad },
    { id: "rate-statuses",   label: "Rate Status",   count: rateStatuses.length,   loading: rsLoad },
  ];

  const ENTITY_FIELDS = {
    "terminals": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
    "cities": [
      { key: "code",    label: "City Code", required: true },
      { key: "name",    label: "City Name", required: true },
      { key: "area_id", label: "Area",      type: "select", optionsKey: "areas", optionValue: "id", optionLabel: "code", numeric: true },
    ],
    "cost-types": [
      { key: "code",        label: "Code",        required: true },
      { key: "name",        label: "Name",        required: true },
      { key: "category",    label: "Category" },
      { key: "description", label: "Description", type: "textarea", wide: true },
    ],
    "charge-units": [
      { key: "code",   label: "Code",   required: true },
      { key: "name",   label: "Name",   required: true },
      { key: "factor", label: "Factor", type: "number" },
    ],
    "regions": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
    "areas": [
      { key: "code",      label: "Area Code",  required: true },
      { key: "name",      label: "Area Name",  required: true },
      { key: "region_id", label: "Region",     type: "select", optionsKey: "regions", optionValue: "id", optionLabel: "name", numeric: true, required: true },
    ],
    "currencies": [
      { key: "iso_code", label: "ISO Code", required: true },
      { key: "name",     label: "Name",     required: true },
    ],
    "payment-terms": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
    "vendors": [
      { key: "code", label: "Vendor Code", required: true },
      { key: "name", label: "Vendor Name", required: true },
    ],
    "brand-companies": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
    "materials": [
      { key: "code",               label: "Material Code",      required: true },
      { key: "name",               label: "Material Name",      required: true },
      { key: "cost_type_name",     label: "Cost Type Name" },
      { key: "containertype_code", label: "Container Type" },
      { key: "container_size",     label: "Container Size" },
      { key: "container_fullness", label: "Container Fullness" },
    ],
    "equipment-types": [
      { key: "code", label: "Equipment Code", required: true },
      { key: "name", label: "Equipment Name", required: true },
    ],
    "move-types": [
      { key: "code", label: "Move Code", required: true },
      { key: "name", label: "Move Name", required: true },
    ],
    "slab-units": [
      { key: "code",   label: "Code",   required: true },
      { key: "name",   label: "Name",   required: true },
      { key: "factor", label: "Factor", type: "number" },
    ],
    "slab-types": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
    "rate-statuses": [
      { key: "code", label: "Code", required: true },
      { key: "name", label: "Name", required: true },
    ],
  };

  const dropdowns = { regions, areas, currencies, _cats: [{ v: "CY" }, { v: "Behavior" }] };

  // --- selection helpers ---
  const toggleSelect = id => setSelectedIds(prev => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });

  const toggleAll = rows => {
    const ids = rows.map(r => r.id);
    const allChk = ids.length > 0 && ids.every(id => selectedIds.has(id));
    setSelectedIds(allChk ? new Set() : new Set(ids));
  };

  const chkTh = rows => {
    const ids = rows.map(r => r.id);
    const all  = ids.length > 0 && ids.every(id => selectedIds.has(id));
    const some = ids.some(id => selectedIds.has(id));
    return (
      <th style={{ width: 36, paddingLeft: 12 }}>
        <input type="checkbox" checked={all}
          ref={el => { if (el) el.indeterminate = some && !all; }}
          onChange={() => toggleAll(rows)} />
      </th>
    );
  };
  const chkTd = id => (
    <td style={{ width: 36, paddingLeft: 12 }} onClick={e => e.stopPropagation()}>
      <input type="checkbox" checked={selectedIds.has(id)} onChange={() => toggleSelect(id)} />
    </td>
  );

  // --- bulk delete (single SQL UPDATE via batch endpoint) ---
  const handleBulkDelete = async () => {
    const currentData = dataMap[entity];
    const names = currentData
      .filter(r => selectedIds.has(r.id))
      .map(r => r.name || r.code || r.iso_code)
      .join(", ");
    if (!confirm(`Soft-delete ${selectedIds.size} item(s)? This sets them Inactive.\n\n${names}`)) return;
    setDeleting(true);
    try {
      const ids = [...selectedIds];
      const res = await apiFetch(`/api/admin/metadata/${entity}/delete-batch`, {
        method: "POST",
        body: JSON.stringify({ ids }),
      });
      setSelectedIds(new Set());
      refreshMap[entity]();
      if (res.deleted < ids.length) alert(`Done: ${res.deleted} deleted, ${ids.length - res.deleted} not found.`);
    } catch (e) {
      alert(`Delete failed: ${e.message}`);
    }
    setDeleting(false);
  };

  // --- import ---
  // For export: replace foreign-key IDs with human-readable codes/names.
  // Uses allRegions (includes inactive) so old seed IDs can still be resolved.
  const getExportData = () => {
    const rows = dataMap[entity];
    const n = id => Number(id);
    const regionLookup = id => allRegions.find(r => r.id === n(id))?.code ?? regions.find(r => r.id === n(id))?.code ?? String(id || "");
    if (entity === "areas") {
      return rows.map(a => {
        const { region_id, ...rest } = a;
        return { ...rest, region_code: regionLookup(region_id) };
      });
    }
    if (entity === "cities") {
      return rows.map(c => {
        const { area_id, ...rest } = c;
        return { ...rest, area_code: areas.find(a => a.id === n(area_id))?.code ?? String(area_id || "") };
      });
    }
    return rows;
  };

  const CHUNK_SIZE = 25;
  const fileRef = React.useRef(null);

  const handleImport = e => {
    const file = e.target.files[0];
    if (!file) return;
    e.target.value = "";
    const reader = new FileReader();
    reader.onload = async ev => {
      const rawRows = parseCSV(ev.target.result);
      if (!rawRows.length) { alert("No data rows found in CSV."); return; }

      // Resolve human-readable codes → IDs
      const rows = rawRows.map(raw => {
        const row = { ...raw };
        if ((entity === "areas" || entity === "cities") && row.region_code) {
          const r = regions.find(x => x.code === row.region_code);
          if (r) row.region_id = r.id;
          delete row.region_code;
        }
        if ((entity === "areas" || entity === "cities") && row.area_code) {
          const a = areas.find(x => x.code === row.area_code);
          if (a) row.area_id = a.id;
          delete row.area_code;
        }
        return row;
      });

      setImportProgress({ total: rows.length, done: 0 });
      let totalOk = 0, totalFail = 0;

      for (let i = 0; i < rows.length; i += CHUNK_SIZE) {
        const chunk = rows.slice(i, i + CHUNK_SIZE);
        try {
          const res = await apiFetch(`/api/admin/metadata/${entity}/import`, {
            method: "POST",
            body: JSON.stringify({ rows: chunk }),
          });
          totalOk  += res.ok     || 0;
          totalFail += res.failed || 0;
        } catch {
          totalFail += chunk.length;
        }
        setImportProgress({ total: rows.length, done: Math.min(i + CHUNK_SIZE, rows.length) });
      }

      setImportProgress(null);
      refreshMap[entity]();
      alert(`Import complete: ${totalOk} added/updated, ${totalFail} failed.`);
    };
    reader.readAsText(file);
  };

  const current = entities.find(e => e.id === entity);
  const onSaved = () => { setModal(null); refreshMap[entity](); };

  // --- filter helpers ---
  const clearFilters = () => { setSearch(""); setColFilters({}); setPage(1); };
  const activeFilterCount = [search.trim(), ...Object.values(colFilters).map(v => (v || []).length > 0 ? "x" : "")].filter(Boolean).length;

  const filterRows = (rows, keys) => {
    const q = search.trim().toLowerCase();
    return rows.filter(r => {
      if (q && !Object.values(r).some(v => v != null && String(v).toLowerCase().includes(q))) return false;
      for (const k of keys) {
        const selected = colFilters[k] || [];
        if (selected.length > 0 && !selected.includes(String(r[k] ?? ""))) return false;
      }
      return true;
    });
  };

  const paginate = rows => rows.slice((page - 1) * pageSize, page * pageSize);

  // Multi-select dropdown filter header cell
  const fDrop = (key, allRows) => {
    if (!key) return <th key={"__" + Math.random()} style={{ padding: "2px 4px" }} />;
    const options = [...new Set(allRows.map(r => String(r[key] ?? "")).filter(Boolean))].sort();
    const selected = colFilters[key] || [];
    const isActive = selected.length > 0;
    return (
      <th key={key} style={{ verticalAlign: "top", padding: "2px 4px" }}>
        <div style={{ position: "relative" }} onMouseDown={e => e.stopPropagation()}>
          <button
            onClick={() => { const next = openDropdown === key ? null : key; setOpenDropdown(next); setDropdownSearch(""); }}
            style={{
              width: "100%", padding: "2px 6px", fontSize: 11, cursor: "pointer", textAlign: "left",
              border: `1px solid ${isActive ? "var(--teal)" : "var(--border)"}`,
              borderRadius: 3, whiteSpace: "nowrap", overflow: "hidden",
              background: isActive ? "rgba(0,158,135,.12)" : "var(--paper)",
              color: isActive ? "var(--teal)" : "var(--ink-mute)",
            }}>
            {isActive ? `✓ ${selected.length}/${options.length}` : "▾ All"}
          </button>
          {openDropdown === key && (
            <div style={{
              position: "absolute", top: "calc(100% + 2px)", left: 0, zIndex: 400,
              background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 4,
              boxShadow: "0 4px 16px rgba(0,0,0,.18)", minWidth: 200,
            }}>
              {/* search input */}
              <div style={{ padding: "6px 8px", borderBottom: "1px solid var(--border)" }}>
                <input
                  type="text"
                  autoFocus
                  value={dropdownSearch}
                  onChange={e => setDropdownSearch(e.target.value)}
                  placeholder="Search…"
                  style={{
                    width: "100%", padding: "3px 7px", fontSize: 12,
                    border: "1px solid var(--border)", borderRadius: 3,
                    outline: "none", boxSizing: "border-box", background: "var(--paper)",
                  }}
                />
              </div>
              {/* select all / clear row */}
              {(() => {
                const visible = options.filter(v => !dropdownSearch || v.toLowerCase().includes(dropdownSearch.toLowerCase()));
                const allVisibleSelected = visible.length > 0 && visible.every(v => selected.includes(v));
                return (
                  <>
                    <div style={{ padding: "4px 8px", borderBottom: "1px solid var(--border)", display: "flex", gap: 6 }}>
                      <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--teal)" }}
                        onClick={() => {
                          setColFilters(f => {
                            const cur = f[key] || [];
                            const next = allVisibleSelected
                              ? cur.filter(v => !visible.includes(v))   // deselect visible
                              : [...new Set([...cur, ...visible])];      // add visible
                            return { ...f, [key]: next };
                          });
                          setPage(1);
                        }}>
                        {allVisibleSelected ? "Deselect all" : "Select all"}
                      </button>
                      {isActive && (
                        <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--ink-mute)" }}
                          onClick={() => { setColFilters(f => ({ ...f, [key]: [] })); setPage(1); }}>
                          Clear
                        </button>
                      )}
                    </div>
                    {/* options list */}
                    <div style={{ maxHeight: 220, overflowY: "auto" }}>
                      {visible.map(v => (
                        <label key={v} style={{ display: "flex", alignItems: "center", padding: "5px 10px", cursor: "pointer", gap: 8, fontSize: 12 }}>
                          <input type="checkbox" style={{ margin: 0, cursor: "pointer" }}
                            checked={selected.includes(v)}
                            onChange={() => {
                              setColFilters(f => {
                                const cur = f[key] || [];
                                return { ...f, [key]: cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v] };
                              });
                              setPage(1);
                            }} />
                          <span>{v}</span>
                        </label>
                      ))}
                      {visible.length === 0 && <div className="tiny muted" style={{ padding: "8px 10px" }}>No matches</div>}
                    </div>
                  </>
                );
              })()}
            </div>
          )}
        </div>
      </th>
    );
  };

  const editBtn = row => (
    <button className="btn ghost tiny" onClick={e => { e.stopPropagation(); setModal(row); }}>
      <Icon name="edit" size={12} />
    </button>
  );

  const toolbar = (
    <div className="row gap-8" style={{ alignItems: "center" }}>
      {selectedIds.size > 0 && (
        <>
          <span className="tiny muted">{selectedIds.size} selected</span>
          <button className="btn ghost tiny" onClick={handleBulkDelete} disabled={deleting}
            style={{ color: "var(--red)", borderColor: "var(--red)" }}>
            <Icon name="trash" size={12} /> {deleting ? "Deleting…" : "Delete selected"}
          </button>
          <div style={{ width: 1, background: "var(--border)", height: 18, alignSelf: "center" }} />
        </>
      )}
      <div style={{ position: "relative", display: "flex", alignItems: "center" }}>
        <svg style={{ position: "absolute", left: 7, color: "var(--ink-mute)", pointerEvents: "none" }} width={13} height={13} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
        <input type="text" value={search} onChange={e => { setSearch(e.target.value); setPage(1); }}
          placeholder="Search…" style={{
            padding: "4px 24px 4px 26px", fontSize: 12,
            border: `1px solid ${search.trim() ? "var(--teal)" : "var(--border)"}`,
            borderRadius: 4, outline: "none", width: 160,
            background: search.trim() ? "rgba(0,158,135,.08)" : "var(--paper)",
          }} />
        {search && (
          <button onClick={() => { setSearch(""); setPage(1); }} style={{ position: "absolute", right: 5, background: "none", border: "none", cursor: "pointer", padding: 0, lineHeight: 1, color: "var(--ink-mute)", fontSize: 16 }}>×</button>
        )}
      </div>
      {activeFilterCount > 0 && (
        <button className="btn ghost tiny" onClick={clearFilters} style={{ color: "var(--teal)", borderColor: "var(--teal)" }}>
          ✕ Clear ({activeFilterCount})
        </button>
      )}
      <button className="btn ghost tiny" onClick={() => exportCSV(getExportData(), `ocms_${entity}_${new Date().toISOString().slice(0,10)}.csv`)}>
        <Icon name="download" size={12} /> Export
      </button>
      {importProgress ? (
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <div style={{ width: 120, height: 6, background: "var(--border)", borderRadius: 3, overflow: "hidden" }}>
            <div style={{
              height: "100%", borderRadius: 3, background: "var(--teal)",
              width: `${Math.round(importProgress.done / importProgress.total * 100)}%`,
              transition: "width 0.2s ease",
            }} />
          </div>
          <span className="tiny muted">{importProgress.done} / {importProgress.total}</span>
        </div>
      ) : (
        <button className="btn ghost tiny" onClick={() => fileRef.current?.click()}>
          <Icon name="upload" size={12} /> Import
        </button>
      )}
      <input ref={fileRef} type="file" accept=".csv" style={{ display: "none" }} onChange={handleImport} />
      <button className="btn primary tiny" onClick={() => setModal("new")}><Icon name="plus" size={12} /> Add</button>
    </div>
  );

  return (
    <div style={{ display: "grid", gridTemplateColumns: "200px 1fr", gap: 16 }}>
      <div className="card" style={{ padding: 8, height: "fit-content" }}>
        {entities.map(e => (
          <button key={e.id} onClick={() => { setEntity(e.id); setModal(null); setSelectedIds(new Set()); setSearch(""); setColFilters({}); setPage(1); }} className="nav-item"
            style={{ color: entity === e.id ? "var(--ink)" : "var(--ink-mute)", background: entity === e.id ? "var(--paper)" : "transparent" }}>
            {e.label}
            <span className="nav-count" style={{ background: "var(--paper-2)", color: "var(--ink-mute)" }}>{e.loading ? "…" : e.count}</span>
          </button>
        ))}
      </div>

      <Card title={current.label} sub="Soft-delete only · all changes audit-logged" right={toolbar}>
        {entity === "terminals" && (() => {
          const filtered = filterRows(terminals, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={tLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", terminals)}{fDrop("name", terminals)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>
                  {paged.map(t => (
                    <tr key={t.id} style={{ background: selectedIds.has(t.id) ? "var(--paper-2)" : undefined }}>
                      {chkTd(t.id)}
                      <td className="mono tiny bold">{t.code}</td>
                      <td>{t.name}</td>
                      <td><Pill status={t.is_active ? "Active" : "Inactive"} /></td>
                      <td>{editBtn(t)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "cost-types" && (() => {
          const filtered = filterRows(costTypes, ["code", "name", "category", "description"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={ctLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Category</th><th>Description</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", costTypes)}{fDrop("name", costTypes)}{fDrop("category", costTypes)}{fDrop(null)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(c => (
                  <tr key={c.id} style={{ background: selectedIds.has(c.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(c.id)}
                    <td className="mono tiny">{c.code}</td>
                    <td>{c.name}</td>
                    <td><Pill className="plain">{c.category}</Pill></td>
                    <td className="tiny muted">{c.description}</td>
                    <td><Pill status={c.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(c)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "charge-units" && (() => {
          const filtered = filterRows(chargeUnits, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={cuLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Factor</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", chargeUnits)}{fDrop("name", chargeUnits)}{fDrop(null)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(u => (
                  <tr key={u.id} style={{ background: selectedIds.has(u.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(u.id)}
                    <td className="mono tiny">{u.code}</td>
                    <td>{u.name}</td>
                    <td className="mono tiny">{u.factor ?? "—"}</td>
                    <td><Pill status={u.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(u)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "regions" && (() => {
          const filtered = filterRows(regions, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={rLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Areas</th><th>Terminals</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", regions)}{fDrop("name", regions)}{fDrop(null)}{fDrop(null)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td className="mono tiny muted">{areas.filter(a => a.region_id === r.id).length}</td>
                    <td className="mono tiny muted">{terminals.filter(t => t.region_id === r.id).length}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "areas" && (() => {
          const filtered = filterRows(areas, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={aLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Area Code</th><th>Area Name</th><th>Region</th><th>Terminals</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", areas)}{fDrop("name", areas)}{fDrop(null)}{fDrop(null)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(a => (
                  <tr key={a.id} style={{ background: selectedIds.has(a.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(a.id)}
                    <td className="mono tiny bold">{a.code}</td>
                    <td>{a.name}</td>
                    <td className="tiny muted">{regions.find(r => r.id === a.region_id)?.name || "—"}</td>
                    <td className="mono tiny muted">{terminals.filter(t => t.area_id === a.id).length}</td>
                    <td><Pill status={a.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(a)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "currencies" && (() => {
          const filtered = filterRows(currencies, ["iso_code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={cLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>ISO</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("iso_code", currencies)}{fDrop("name", currencies)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(c => (
                  <tr key={c.id} style={{ background: selectedIds.has(c.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(c.id)}
                    <td className="mono bold">{c.iso_code}</td>
                    <td>{c.name}</td>
                    <td><Pill status={c.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(c)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "cities" && (() => {
          const cEnriched = cities.map(c => ({ ...c, _area_code: areas.find(a => a.id === c.area_id)?.code || "" }));
          const filtered = filterRows(cEnriched, ["code", "name", "_area_code"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={cityLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>City Code</th><th>City Name</th><th>Area</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", cEnriched)}{fDrop("name", cEnriched)}{fDrop("_area_code", cEnriched)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(c => (
                  <tr key={c.id} style={{ background: selectedIds.has(c.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(c.id)}
                    <td className="mono tiny bold">{c.code}</td>
                    <td>{c.name}</td>
                    <td className="mono tiny">{c._area_code || "—"}</td>
                    <td><Pill status={c.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(c)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "payment-terms" && (() => {
          const filtered = filterRows(paymentTerms, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={ptLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", paymentTerms)}{fDrop("name", paymentTerms)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "vendors" && (() => {
          const filtered = filterRows(vendors, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={vLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Vendor Code</th><th>Vendor Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", vendors)}{fDrop("name", vendors)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "brand-companies" && (() => {
          const filtered = filterRows(brandCompanies, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={bcLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", brandCompanies)}{fDrop("name", brandCompanies)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "materials" && (() => {
          const filtered = filterRows(materials, ["code", "name", "cost_type_name", "containertype_code"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={matLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Material Code</th><th>Material Name</th><th>Cost Type Name</th><th>Ctype</th><th>Size</th><th>F/E</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", materials)}{fDrop("name", materials)}{fDrop("cost_type_name", materials)}{fDrop("containertype_code", materials)}{fDrop("container_size", materials)}{fDrop("container_fullness", materials)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td className="muted tiny">{r.cost_type_name || "—"}</td>
                    <td className="muted tiny">{r.containertype_code || "—"}</td>
                    <td className="muted tiny">{r.container_size || "—"}</td>
                    <td className="muted tiny">{r.container_fullness || "—"}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "equipment-types" && (() => {
          const filtered = filterRows(equipmentTypes, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={etLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Equipment Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", equipmentTypes)}{fDrop("name", equipmentTypes)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "move-types" && (() => {
          const filtered = filterRows(moveTypes, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={mtLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Move Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", moveTypes)}{fDrop("name", moveTypes)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "slab-units" && (() => {
          const filtered = filterRows(slabUnits, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={suLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Factor</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", slabUnits)}{fDrop("name", slabUnits)}{fDrop(null)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td className="mono tiny">{r.factor ?? "—"}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "slab-types" && (() => {
          const filtered = filterRows(slabTypes, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={stLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", slabTypes)}{fDrop("name", slabTypes)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
        {entity === "rate-statuses" && (() => {
          const filtered = filterRows(rateStatuses, ["code", "name"]);
          const paged = paginate(filtered);
          return (
          <MetadataTable loading={rsLoad}>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>{chkTh(filtered)}<th>Code</th><th>Name</th><th>Status</th><th></th></tr>
                  <tr>{fDrop(null)}{fDrop("code", rateStatuses)}{fDrop("name", rateStatuses)}{fDrop(null)}{fDrop(null)}</tr>
                </thead>
                <tbody>{paged.map(r => (
                  <tr key={r.id} style={{ background: selectedIds.has(r.id) ? "var(--paper-2)" : undefined }}>
                    {chkTd(r.id)}
                    <td className="mono tiny bold">{r.code}</td>
                    <td>{r.name}</td>
                    <td><Pill status={r.is_active ? "Active" : "Inactive"} /></td>
                    <td>{editBtn(r)}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </MetadataTable>
          );
        })()}
      </Card>

      {modal && (
        <MetadataModal
          entity={entity}
          fields={ENTITY_FIELDS[entity]}
          row={modal === "new" ? null : modal}
          dropdowns={dropdowns}
          onClose={() => setModal(null)}
          onSaved={onSaved}
        />
      )}
    </div>
  );
}

function ROEModal({ row, currencies, onClose, onSaved }) {
  const isEdit = !!(row && row.id);
  const [form, setForm] = React.useState({
    currency_id:    isEdit ? String(row.currency_id) : "",
    rate:           isEdit ? String(row.rate)         : "",
    effective_date: isEdit ? (row.effective_date?.slice(0, 10) || "") : "",
    expiry_date:    isEdit ? (row.expiry_date?.slice(0, 10)   || "") : "",
    source:         isEdit ? (row.source || "Manual")          : "Manual",
  });
  const [saving, setSaving] = React.useState(false);
  const [error,  setError]  = React.useState(null);
  const set = (k, v) => setForm(prev => ({ ...prev, [k]: v }));
  const iStyle = { padding: "6px 8px", borderRadius: 6, border: "1px solid var(--border)", fontSize: 13, width: "100%", boxSizing: "border-box" };

  const submit = async () => {
    if (!form.currency_id || !form.rate || !form.effective_date) {
      setError("Currency, Rate and Effective Date are required."); return;
    }
    setSaving(true); setError(null);
    try {
      if (isEdit) {
        await apiFetch(`/api/admin/roe/${row.id}`, { method: "PUT", body: JSON.stringify({
          rate: parseFloat(form.rate),
          effective_date: form.effective_date,
          expiry_date: form.expiry_date || null,
          source: form.source,
        })});
      } else {
        await apiFetch("/api/admin/roe", { method: "POST", body: JSON.stringify({
          currency_id: parseInt(form.currency_id),
          rate: parseFloat(form.rate),
          effective_date: form.effective_date,
          expiry_date: form.expiry_date || null,
          source: form.source,
        })});
      }
      onSaved();
    } catch (e) { setError(e.message); setSaving(false); }
  };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.4)", zIndex: 900, display: "flex", alignItems: "center", justifyContent: "center" }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="card" style={{ width: 500, padding: 24 }}>
        <div className="row gap-8" style={{ marginBottom: 18, alignItems: "center" }}>
          <span style={{ fontWeight: 700, fontSize: 15 }}>{isEdit ? "Edit ROE" : "Add ROE"}</span>
          <div className="grow" />
          <button className="btn ghost tiny" onClick={onClose}><Icon name="close" size={14} /></button>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 16px" }}>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            <span>Currency <span style={{ color: "var(--red)" }}>*</span></span>
            <select value={form.currency_id} onChange={e => set("currency_id", e.target.value)} style={iStyle} disabled={isEdit}>
              <option value="">Select…</option>
              {currencies.map(c => <option key={c.id} value={c.id}>{c.iso_code} — {c.name}</option>)}
            </select>
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            <span>Rate (local per 1 USD) <span style={{ color: "var(--red)" }}>*</span></span>
            <input type="number" step="0.000001" value={form.rate} onChange={e => set("rate", e.target.value)} placeholder="e.g. 7.254000" style={iStyle} />
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            <span>Effective Date <span style={{ color: "var(--red)" }}>*</span></span>
            <input type="date" value={form.effective_date} onChange={e => set("effective_date", e.target.value)} style={iStyle} />
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12 }}>
            <span>Expiry Date <span className="muted">(optional)</span></span>
            <input type="date" value={form.expiry_date} onChange={e => set("expiry_date", e.target.value)} style={iStyle} />
          </label>
          <label style={{ display: "flex", flexDirection: "column", gap: 4, fontSize: 12, gridColumn: "1/-1" }}>
            Source
            <select value={form.source} onChange={e => set("source", e.target.value)} style={iStyle}>
              <option>Manual</option><option>System</option><option>Bloomberg</option>
            </select>
          </label>
        </div>
        {error && <div className="tiny" style={{ color: "var(--red)", marginTop: 10 }}>{error}</div>}
        <div className="row gap-8" style={{ marginTop: 18, justifyContent: "flex-end" }}>
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={saving}>{saving ? "Saving…" : (isEdit ? "Save changes" : "Add")}</button>
        </div>
      </div>
    </div>
  );
}

function RoeTrend({ points }) {
  // points: [{rate, effective_date}, ...] sorted oldest→newest (at least 2)
  if (!points || points.length < 2) {
    return <span style={{ color: "var(--ink-mute)", fontSize: 11 }}>—</span>;
  }
  const W = 84, H = 28, PAD = 3;
  const rates = points.map(p => Number(p.rate));
  const minR = Math.min(...rates), maxR = Math.max(...rates);
  const range = maxR - minR || (minR * 0.001) || 1;
  const xs = points.map((_, i) => PAD + (i / (points.length - 1)) * (W - PAD * 2));
  const ys = rates.map(r => PAD + (1 - (r - minR) / range) * (H - PAD * 2));
  const polyline = xs.map((x, i) => `${x.toFixed(1)},${ys[i].toFixed(1)}`).join(" ");
  const isUp = rates[rates.length - 1] >= rates[0];
  const color = isUp ? "#009e87" : "#e8443a";
  const last = { x: xs[xs.length - 1], y: ys[ys.length - 1] };
  const first = { x: xs[0], y: ys[0] };
  // Fill area under line
  const fillPts = `${first.x.toFixed(1)},${H - PAD} ${polyline} ${last.x.toFixed(1)},${H - PAD}`;
  return (
    <svg width={W} height={H} style={{ display: "block", overflow: "visible" }}>
      <polygon points={fillPts} fill={color} fillOpacity={0.12} />
      <polyline points={polyline} fill="none" stroke={color} strokeWidth={1.5}
        strokeLinejoin="round" strokeLinecap="round" />
      <circle cx={first.x} cy={first.y} r={2} fill={color} fillOpacity={0.5} />
      <circle cx={last.x} cy={last.y} r={2.5} fill={color} />
    </svg>
  );
}

function ROETable() {
  const [rows,           setRows]           = React.useState([]);
  const [currencies,     setCurrencies]     = React.useState([]);
  const [loading,        setLoading]        = React.useState(true);
  const [modal,          setModal]          = React.useState(null);   // null | "add" | row
  const [selectedIds,    setSelectedIds]    = React.useState(new Set());
  const [deleting,       setDeleting]       = React.useState(false);
  const [expiryModal,    setExpiryModal]    = React.useState(false);
  const [expiryDate,     setExpiryDate]     = React.useState("");
  const [settingExpiry,  setSettingExpiry]  = React.useState(false);
  const [importProgress, setImportProgress] = React.useState(null);
  const [search,         setSearch]         = React.useState("");
  const [colFilters,     setColFilters]     = React.useState({});
  const [page,           setPage]           = React.useState(1);
  const [pageSize,       setPageSize]       = React.useState(20);
  const [openDropdown,   setOpenDropdown]   = React.useState(null);
  const [dropSearch,     setDropSearch]     = React.useState("");
  const fileRef = React.useRef(null);

  React.useEffect(() => {
    const h = () => setOpenDropdown(null);
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  const load = () => {
    setLoading(true);
    Promise.all([
      apiFetch("/api/roe"),
      apiFetch("/api/metadata/currencies"),
    ]).then(([roe, cur]) => {
      setRows(Array.isArray(roe) ? roe : []);
      setCurrencies(Array.isArray(cur) ? cur : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  };
  React.useEffect(() => { load(); }, []);

  // Enriched rows: _age only for records without expiry_date; _expiry_label for filter/display
  const enriched = rows.map(r => ({
    ...r,
    _age:          r.expiry_date ? null : Math.floor((new Date() - new Date(r.effective_date)) / 86400000),
    _expiry_label: r.expiry_date ? r.expiry_date.slice(0, 10) : "Latest",
  }));

  // Group by iso_code, sort each group effective_date DESC (latest first)
  const grouped = React.useMemo(() => {
    const g = {};
    enriched.forEach(r => { if (!g[r.iso_code]) g[r.iso_code] = []; g[r.iso_code].push(r); });
    Object.values(g).forEach(arr => arr.sort((a, b) => new Date(b.effective_date) - new Date(a.effective_date)));
    return g;
  }, [enriched]);

  // Latest record per currency (used for filter/display/KPI)
  const latestRows = Object.values(grouped).map(g => g[0]);

  // Stale: latest record is > 90d old AND has no expiry_date set
  const stale = latestRows.filter(r => r._age > 90 && !r.expiry_date);

  // Filter operates on latestRows (one row per currency)
  const FILTER_KEYS = ["iso_code", "_expiry_label", "source"];
  const filterRows = allRows => {
    const q = search.trim().toLowerCase();
    return allRows.filter(r => {
      if (q && !["iso_code", "source", "created_by"].some(k => String(r[k] ?? "").toLowerCase().includes(q))) return false;
      for (const k of FILTER_KEYS) {
        const sel = colFilters[k] || [];
        if (sel.length > 0 && !sel.includes(String(r[k] ?? ""))) return false;
      }
      return true;
    });
  };
  const filtered = filterRows(latestRows);
  const paged    = filtered.slice((page - 1) * pageSize, page * pageSize);

  // Expand state
  const [expandedIsos, setExpandedIsos] = React.useState(new Set());
  const toggleExpand = iso => setExpandedIsos(prev => {
    const s = new Set(prev); s.has(iso) ? s.delete(iso) : s.add(iso); return s;
  });

  // Selection
  const toggleSelect = id => setSelectedIds(prev => { const s = new Set(prev); s.has(id) ? s.delete(id) : s.add(id); return s; });
  const toggleAll    = () => {
    const ids = filtered.map(r => r.id);
    const all = ids.length > 0 && ids.every(id => selectedIds.has(id));
    setSelectedIds(all ? new Set() : new Set(ids));
  };

  // Bulk delete
  const handleBulkDelete = async () => {
    if (!confirm(`Delete ${selectedIds.size} record(s)? This cannot be undone.`)) return;
    setDeleting(true);
    try {
      await apiFetch("/api/admin/roe/delete-batch", { method: "POST", body: JSON.stringify({ ids: [...selectedIds] }) });
      setSelectedIds(new Set());
      load();
    } catch (e) { alert(`Delete failed: ${e.message}`); }
    setDeleting(false);
  };

  // Batch set expiry
  const handleBatchSetExpiry = async () => {
    if (!expiryDate) { alert("Please select an expiry date."); return; }
    setSettingExpiry(true);
    try {
      const res = await apiFetch("/api/admin/roe/set-expiry-batch", {
        method: "POST",
        body: JSON.stringify({ ids: [...selectedIds], expiry_date: expiryDate }),
      });
      setExpiryModal(false);
      setExpiryDate("");
      setSelectedIds(new Set());
      load();
      alert(`Expiry date set for ${res.updated} record(s).`);
    } catch (e) { alert(`Failed: ${e.message}`); }
    setSettingExpiry(false);
  };

  // Export
  const handleExport = () => {
    exportCSV(filtered.map(r => ({
      iso_code:       r.iso_code,
      rate:           r.rate,
      effective_date: r.effective_date?.slice(0, 10),
      expiry_date:    r.expiry_date?.slice(0, 10) || "",
      source:         r.source || "Manual",
    })), `ocms_roe_${new Date().toISOString().slice(0, 10)}.csv`);
  };

  // Import
  const CHUNK_SIZE = 25;
  const handleImport = e => {
    const file = e.target.files[0]; if (!file) return; e.target.value = "";
    const reader = new FileReader();
    reader.onload = async ev => {
      const rawRows = parseCSV(ev.target.result);
      if (!rawRows.length) { alert("No data rows found in CSV."); return; }
      setImportProgress({ total: rawRows.length, done: 0 });
      let ok = 0, fail = 0;
      for (let i = 0; i < rawRows.length; i += CHUNK_SIZE) {
        const chunk = rawRows.slice(i, i + CHUNK_SIZE);
        try {
          const res = await apiFetch("/api/admin/roe/import", { method: "POST", body: JSON.stringify({ rows: chunk }) });
          ok += res.ok || 0; fail += res.failed || 0;
        } catch { fail += chunk.length; }
        setImportProgress({ total: rawRows.length, done: Math.min(i + CHUNK_SIZE, rawRows.length) });
      }
      setImportProgress(null); load();
      alert(`Import done: ${ok} OK, ${fail} failed.`);
    };
    reader.readAsText(file);
  };

  // Column filter dropdown
  const fDrop = key => {
    const options  = [...new Set(enriched.map(r => String(r[key] ?? "")).filter(Boolean))].sort();
    const selected = colFilters[key] || [];
    const isActive = selected.length > 0;
    return (
      <th key={key} style={{ verticalAlign: "top", padding: "2px 4px" }}>
        <div style={{ position: "relative" }} onMouseDown={e => e.stopPropagation()}>
          <button onClick={() => { setOpenDropdown(openDropdown === key ? null : key); setDropSearch(""); }}
            style={{ width: "100%", padding: "2px 6px", fontSize: 11, cursor: "pointer", textAlign: "left",
              border: `1px solid ${isActive ? "var(--teal)" : "var(--border)"}`,
              borderRadius: 3, background: isActive ? "rgba(0,158,135,.12)" : "var(--paper)",
              color: isActive ? "var(--teal)" : "var(--ink-mute)" }}>
            {isActive ? `✓ ${selected.length}/${options.length}` : "▾ All"}
          </button>
          {openDropdown === key && (
            <div style={{ position: "absolute", top: "calc(100% + 2px)", left: 0, zIndex: 400,
              background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 4,
              boxShadow: "0 4px 16px rgba(0,0,0,.18)", minWidth: 200 }}>
              <div style={{ padding: "6px 8px", borderBottom: "1px solid var(--border)" }}>
                <input type="text" autoFocus value={dropSearch} onChange={e => setDropSearch(e.target.value)}
                  placeholder="Search…" style={{ width: "100%", padding: "4px 6px", fontSize: 12, border: "1px solid var(--border)", borderRadius: 4 }} />
              </div>
              {(() => {
                const visible = options.filter(v => !dropSearch || v.toLowerCase().includes(dropSearch.toLowerCase()));
                const allSel  = visible.length > 0 && visible.every(v => selected.includes(v));
                return (
                  <>
                    <div style={{ padding: "4px 8px", borderBottom: "1px solid var(--border)", display: "flex", gap: 6 }}>
                      <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--teal)" }}
                        onClick={() => { setColFilters(f => { const cur = f[key]||[]; const next = allSel ? cur.filter(v=>!visible.includes(v)) : [...new Set([...cur,...visible])]; return {...f,[key]:next}; }); setPage(1); }}>
                        {allSel ? "Deselect all" : "Select all"}
                      </button>
                      {isActive && (
                        <button className="btn ghost" style={{ fontSize: 10, padding: "1px 8px", color: "var(--ink-mute)" }}
                          onClick={() => { setColFilters(f => ({...f,[key]:[]})); setPage(1); }}>Clear</button>
                      )}
                    </div>
                    <div style={{ maxHeight: 220, overflowY: "auto" }}>
                      {visible.map(v => (
                        <label key={v} style={{ display: "flex", alignItems: "center", padding: "5px 10px", cursor: "pointer", gap: 8, fontSize: 12 }}>
                          <input type="checkbox" style={{ margin: 0, cursor: "pointer" }} checked={selected.includes(v)}
                            onChange={() => { setColFilters(f => { const cur=f[key]||[]; return {...f,[key]: cur.includes(v)?cur.filter(x=>x!==v):[...cur,v]}; }); setPage(1); }} />
                          <span>{v}</span>
                        </label>
                      ))}
                      {visible.length === 0 && <div className="tiny muted" style={{ padding: "8px 10px" }}>No matches</div>}
                    </div>
                  </>
                );
              })()}
            </div>
          )}
        </div>
      </th>
    );
  };
  const fEmpty = () => <th style={{ padding: "2px 4px" }} />;

  const allPaged  = filtered.length > 0 && filtered.every(r => selectedIds.has(r.id));
  const somePaged = filtered.some(r => selectedIds.has(r.id));

  return (
    <div>
      <PageHeader title="ROE Table" sub="Exchange rates · local per 1 USD · maintained by Rate Focal" />

      {/* KPIs */}
      <div className="row gap-12" style={{ marginBottom: 14 }}>
        <KPI label="Currencies" value={loading ? "…" : latestRows.length} sub={`${rows.length} total records · ${filtered.length} currencies matching filters`} />
        <KPI label="Stale (> 90d, no expiry)" value={loading ? "…" : stale.length}
          sub={stale.length ? stale.map(r => r.iso_code).join(", ") : "none"}
          deltaDir={stale.length > 0 ? "up" : undefined} />
        <div className="grow" />
      </div>

      {/* Toolbar */}
      <div className="row gap-8" style={{ marginBottom: 10, alignItems: "center" }}>
        <div style={{ position: "relative", display: "flex", alignItems: "center" }}>
          <svg style={{ position: "absolute", left: 7, color: "var(--ink-mute)", pointerEvents: "none" }} width={13} height={13} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
          <input type="text" value={search} onChange={e => { setSearch(e.target.value); setPage(1); }}
            placeholder="Search currency, source, created by…" style={{
              padding: "4px 24px 4px 26px", fontSize: 12, width: 260,
              border: `1px solid ${search.trim() ? "var(--teal)" : "var(--border)"}`,
              borderRadius: 4, outline: "none",
              background: search.trim() ? "rgba(0,158,135,.08)" : "var(--paper)",
            }} />
          {search && (
            <button onClick={() => { setSearch(""); setPage(1); }} style={{ position: "absolute", right: 5, background: "none", border: "none", cursor: "pointer", padding: 0, lineHeight: 1, color: "var(--ink-mute)", fontSize: 16 }}>×</button>
          )}
        </div>
        <div className="grow" />
        {selectedIds.size > 0 && (<>
          <button className="btn ghost tiny" style={{ color: "var(--teal)" }}
            onClick={() => { setExpiryDate(new Date().toISOString().slice(0, 10)); setExpiryModal(true); }}>
            <Icon name="calendar" size={13} /> Set Expiry ({selectedIds.size})
          </button>
          <button className="btn ghost tiny" style={{ color: "var(--red)" }} onClick={handleBulkDelete} disabled={deleting}>
            <Icon name="trash" size={13} /> {deleting ? "Deleting…" : `Delete ${selectedIds.size}`}
          </button>
        </>)}
        <button className="btn ghost tiny" onClick={handleExport}><Icon name="download" size={13} /> Export</button>
        <button className="btn ghost tiny" onClick={() => fileRef.current?.click()}><Icon name="upload" size={13} /> Import</button>
        <button className="btn primary" onClick={() => setModal("add")}><Icon name="plus" size={14} /> Add ROE</button>
        <input ref={fileRef} type="file" accept=".csv" style={{ display: "none" }} onChange={handleImport} />
      </div>

      {importProgress && (
        <div className="card" style={{ padding: "10px 14px", marginBottom: 10, display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{ flex: 1, background: "var(--paper-2)", borderRadius: 4, height: 6 }}>
            <div style={{ width: `${(importProgress.done / importProgress.total) * 100}%`, height: "100%", background: "var(--teal)", borderRadius: 4, transition: "width .2s" }} />
          </div>
          <span className="tiny muted">{importProgress.done} / {importProgress.total}</span>
        </div>
      )}

      {/* Table */}
      <Card title="Exchange Rate Table" sub={`Local per 1 USD · ${rows.length} records`}>
        {loading ? (
          <div className="muted tiny" style={{ padding: 24 }}>Loading…</div>
        ) : (
          <>
            <div className="table-wrap" style={{ minHeight: 280 }}>
              <table className="table">
                <thead>
                  <tr>
                    <th style={{ width: 36, paddingLeft: 12 }}>
                      <input type="checkbox" checked={allPaged && paged.length > 0}
                        ref={el => { if (el) el.indeterminate = somePaged && !allPaged; }}
                        onChange={toggleAll} />
                    </th>
                    <th style={{ width: 32 }} />
                    <th>Currency</th>
                    <th style={{ width: 96 }}>Trend</th>
                    <th className="num">Rate (local/USD)</th>
                    <th className="num">USD per 1 unit</th>
                    <th>Effective</th>
                    <th>Expiry</th>
                    <th>Source</th>
                    <th>Age</th>
                    <th>Created by</th>
                    <th></th>
                  </tr>
                  <tr>
                    {fEmpty()}{fEmpty()}{fDrop("iso_code")}{fEmpty()}{fEmpty()}{fEmpty()}{fEmpty()}{fDrop("_expiry_label")}{fDrop("source")}{fEmpty()}{fEmpty()}{fEmpty()}
                  </tr>
                </thead>
                <tbody>
                  {paged.map(r => {
                    const history = grouped[r.iso_code] || [];
                    const isExpanded = expandedIsos.has(r.iso_code);
                    const hasHistory = history.length > 1;
                    const rowStyle = { cursor: "pointer" };
                    const histStyle = { background: "var(--paper)", cursor: "pointer" };
                    // trendPoints: history sorted oldest→newest, for sparkline
                    const trendPoints = [...history].reverse();
                    const renderCells = (rec, isHistory) => (<>
                      <td style={{ width: 36, paddingLeft: 12 }} onClick={e => e.stopPropagation()}>
                        <input type="checkbox" checked={selectedIds.has(rec.id)} onChange={() => toggleSelect(rec.id)} />
                      </td>
                      <td style={{ width: 32, paddingLeft: 8 }} onClick={e => { e.stopPropagation(); if (!isHistory && hasHistory) toggleExpand(rec.iso_code); }}>
                        {!isHistory && hasHistory && (
                          <button className="btn ghost tiny" style={{ padding: "0 5px", fontSize: 13, lineHeight: 1, fontWeight: 700 }}>
                            {isExpanded ? "−" : "+"}
                          </button>
                        )}
                      </td>
                      <td className={`mono ${isHistory ? "muted" : "bold"}`} style={isHistory ? { paddingLeft: 24 } : {}}>
                        {rec.iso_code}/USD
                        {!isHistory && hasHistory && <span className="tiny muted" style={{ marginLeft: 6 }}>{history.length} records</span>}
                      </td>
                      <td style={{ width: 96, padding: "4px 8px", verticalAlign: "middle" }}>
                        {!isHistory && <RoeTrend points={trendPoints} />}
                      </td>
                      <td className="num mono">{Number(rec.rate).toFixed(6)}</td>
                      <td className="num mono muted">{(1 / rec.rate).toFixed(6)}</td>
                      <td className="mono tiny">{rec.effective_date?.slice(0, 10)}</td>
                      <td className="mono tiny">
                        {rec.expiry_date
                          ? <span className="muted">{rec.expiry_date.slice(0, 10)}</span>
                          : <span style={{ color: "var(--teal)", fontWeight: 600, fontSize: 11 }}>Latest</span>}
                      </td>
                      <td><Pill className="plain">{rec.source}</Pill></td>
                      <td className="tiny" style={{ color: rec._age > 90 ? "var(--red)" : "var(--ink-mute)" }}>
                        {rec._age !== null ? `${rec._age}d` : "—"}
                      </td>
                      <td className="tiny muted">{rec.created_by || "—"}</td>
                      <td onClick={e => e.stopPropagation()}>
                        <button className="btn ghost tiny" onClick={() => setModal(rec)} title="Edit"><Icon name="edit" size={12} /></button>
                      </td>
                    </>);
                    return (
                      <React.Fragment key={r.iso_code}>
                        <tr style={rowStyle} onClick={() => setModal(r)}>{renderCells(r, false)}</tr>
                        {isExpanded && history.slice(1).map(h => (
                          <tr key={h.id} style={histStyle} onClick={() => setModal(h)}>{renderCells(h, true)}</tr>
                        ))}
                      </React.Fragment>
                    );
                  })}
                  {paged.length === 0 && (
                    <tr><td colSpan={12} style={{ textAlign: "center", color: "var(--ink-mute)", padding: 32 }}>No records match filters</td></tr>
                  )}
                </tbody>
              </table>
            </div>
            <Paginator total={filtered.length} page={page} pageSize={pageSize} onPage={setPage} onPageSize={setPageSize} />
          </>
        )}
      </Card>

      {expiryModal && (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div className="card" style={{ width: 380, padding: 28, boxShadow: "0 8px 32px rgba(0,0,0,.22)" }}>
            <div style={{ fontWeight: 700, fontSize: 16, marginBottom: 6 }}>Set Expiry Date</div>
            <div className="tiny muted" style={{ marginBottom: 18 }}>
              Apply expiry date to <strong>{selectedIds.size}</strong> selected record(s).
              Typically used to expire old rates before importing new ones.
            </div>
            <label className="label" style={{ display: "block", marginBottom: 4 }}>Expiry Date</label>
            <input type="date" className="input" value={expiryDate}
              onChange={e => setExpiryDate(e.target.value)}
              style={{ width: "100%", padding: "7px 10px", fontSize: 14, border: "1px solid var(--border)", borderRadius: 6, marginBottom: 20 }} />
            <div className="row gap-8" style={{ justifyContent: "flex-end" }}>
              <button className="btn ghost" onClick={() => { setExpiryModal(false); setExpiryDate(""); }} disabled={settingExpiry}>Cancel</button>
              <button className="btn primary" onClick={handleBatchSetExpiry} disabled={settingExpiry || !expiryDate}>
                {settingExpiry ? "Saving…" : "Confirm"}
              </button>
            </div>
          </div>
        </div>
      )}

      {modal && (
        <ROEModal
          row={modal === "add" ? null : modal}
          currencies={currencies}
          onClose={() => setModal(null)}
          onSaved={() => { setModal(null); load(); }}
        />
      )}
    </div>
  );
}

// ─── Permissions Matrix ────────────────────────────────────────────────────
const PERM_LABELS = {
  viewRates:     "Rate Repository",
  rateHealth:    "Rate Health",
  editRates:     "Edit Rates",
  importRates:   "Import Rates",
  export:        "Export",
  aiAssistant:   "AI Assistant",
  rebateMonitor: "Rebate Monitor",
  volumeTracker: "Volume Tracker",
  tcoAnalysis:   "TCO Analysis",
  roeTable:      "ROE Table",
  admin:         "Admin Console",
};

const ROLE_LABELS = {
  rate_calculator: "Calc Only",
  read:            "Read",
  edit:            "Rate Focal",
  fbp:             "FBP",
  procurement:     "Procurement",
  admin:           "Admin",
};

const PERM_KEYS = Object.keys(PERM_LABELS);
const ROLE_KEYS = Object.keys(ROLE_LABELS);

function PermissionsMatrix({ currentUser }) {
  const [matrix, setMatrix]   = useState(null);   // { role: { permission: { enabled, updated_by, updated_at } } }
  const [loading, setLoading] = useState(true);
  const [saving, setSaving]   = useState(null);    // "role:perm" while in-flight
  const [error, setError]     = useState("");
  const [toast, setToast]     = useState("");

  useEffect(() => { loadMatrix(); }, []);

  async function loadMatrix() {
    setLoading(true);
    setError("");
    try {
      const rows = await apiFetch("/api/admin/permissions");
      const m = {};
      for (const row of rows) {
        if (!m[row.role]) m[row.role] = {};
        m[row.role][row.permission] = { enabled: row.enabled, updated_by: row.updated_by, updated_at: row.updated_at };
      }
      setMatrix(m);
    } catch (e) {
      setError(`Failed to load permissions: ${e.message}`);
    } finally {
      setLoading(false);
    }
  }

  async function toggle(role, permission) {
    const key = `${role}:${permission}`;
    const current = matrix?.[role]?.[permission]?.enabled ?? false;
    setSaving(key);
    try {
      const updated = await apiFetch("/api/admin/permissions", {
        method: "PUT",
        body: JSON.stringify({ role, permission, enabled: !current }),
      });
      setMatrix(prev => ({
        ...prev,
        [role]: {
          ...(prev[role] || {}),
          [permission]: { enabled: updated.enabled, updated_by: updated.updated_by, updated_at: updated.updated_at },
        },
      }));
      setToast(`${ROLE_LABELS[role]} · ${PERM_LABELS[permission]} → ${updated.enabled ? "ON" : "OFF"}`);
      setTimeout(() => setToast(""), 3000);
    } catch (e) {
      setError(`Save failed: ${e.message}`);
    } finally {
      setSaving(null);
    }
  }

  function fmtDate(iso) {
    if (!iso) return "—";
    try { return new Date(iso).toLocaleString("en-GB", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit" }); }
    catch { return iso; }
  }

  if (loading) return (
    <div style={{ padding: 40, textAlign: "center", color: "var(--ink-mute)" }}>
      <Icon name="spinner" size={20} className="spin" /> Loading permissions…
    </div>
  );

  if (error && !matrix) return (
    <div className="card" style={{ padding: 24, color: "var(--red, #c0392b)", display: "flex", gap: 10, alignItems: "center" }}>
      <Icon name="alert" size={16} /> {error}
      <button className="btn ghost tiny" onClick={loadMatrix} style={{ marginLeft: 8 }}>Retry</button>
    </div>
  );

  return (
    <div>
      {/* header */}
      <div className="row gap-8" style={{ marginBottom: 16, alignItems: "center" }}>
        <div className="muted tiny" style={{ flex: 1 }}>
          Toggle cells to enable or disable a permission for a role. Changes take effect immediately.
        </div>
        <button className="btn ghost tiny" onClick={loadMatrix}><Icon name="refresh" size={12} /> Refresh</button>
      </div>

      {error && (
        <div style={{ marginBottom: 12, padding: "8px 12px", background: "#fef2f2", border: "1px solid #fca5a5", borderRadius: 6, color: "#b91c1c", fontSize: 13 }}>
          {error}
        </div>
      )}

      {toast && (
        <div style={{ marginBottom: 12, padding: "8px 12px", background: "#f0fdf4", border: "1px solid #86efac", borderRadius: 6, color: "#15803d", fontSize: 13 }}>
          {toast}
        </div>
      )}

      <Card>
        <div className="table-wrap" style={{ overflowX: "auto" }}>
          <table className="table" style={{ minWidth: 680 }}>
            <thead>
              <tr>
                <th style={{ width: 160, textAlign: "left" }}>Permission</th>
                {ROLE_KEYS.map(role => (
                  <th key={role} style={{ textAlign: "center", minWidth: 100 }}>
                    <div style={{ fontWeight: 600 }}>{ROLE_LABELS[role]}</div>
                    <div className="mono tiny muted" style={{ fontWeight: 400 }}>{role}</div>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {PERM_KEYS.map(perm => (
                <tr key={perm}>
                  <td>
                    <div style={{ fontWeight: 500 }}>{PERM_LABELS[perm]}</div>
                    <div className="mono tiny muted">{perm}</div>
                  </td>
                  {ROLE_KEYS.map(role => {
                    const cell = matrix?.[role]?.[perm];
                    const enabled = cell?.enabled ?? false;
                    const key = `${role}:${perm}`;
                    const isSaving = saving === key;
                    return (
                      <td key={role} style={{ textAlign: "center" }}>
                        <button
                          onClick={() => toggle(role, perm)}
                          disabled={isSaving}
                          title={cell ? `Last updated by ${cell.updated_by || "—"} on ${fmtDate(cell.updated_at)}` : "No record"}
                          style={{
                            width: 36, height: 20, borderRadius: 10, border: "none", cursor: isSaving ? "wait" : "pointer",
                            background: enabled ? "var(--teal, #14a3bd)" : "var(--paper-2, #e2e8f0)",
                            position: "relative", transition: "background 0.2s", flexShrink: 0,
                            opacity: isSaving ? 0.6 : 1,
                          }}
                        >
                          <span style={{
                            display: "block", width: 14, height: 14, borderRadius: "50%",
                            background: "#fff", position: "absolute", top: 3,
                            left: enabled ? 19 : 3, transition: "left 0.2s",
                            boxShadow: "0 1px 3px rgba(0,0,0,.3)",
                          }} />
                        </button>
                      </td>
                    );
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </Card>

      {/* legend */}
      <div className="row gap-16 muted tiny" style={{ marginTop: 12 }}>
        <span>Hover a toggle to see when it was last changed and by whom.</span>
        <span style={{ marginLeft: "auto" }}>Edited by: <b>{currentUser?.name || "—"}</b></span>
      </div>
    </div>
  );
}

Object.assign(window, { AccessRequests, Admin });
