// screens-org-people.jsx — THE PEOPLE MODALS. Add Employee and Edit Employee.
//
// EXTRACTED FROM screens-org.jsx, 2026-08-27, UNCHANGED.
//
// WHY A SEPARATE FILE RATHER THAN A COPY-PASTE INTO THE ORG CHART. These two
// modals are the ONLY writers of `people` in Vault -- createPerson and
// updatePerson are called from nowhere else. Employee Records is being retired
// in favour of the Org Chart, and the safe order is EXTRACT, then point both
// screens at the extraction, then delete the old screen. Duplicating them would
// mean two writers of one table that drift apart; deleting the old screen in
// the same drop that introduces its replacement would leave no way to add a
// person if the port is wrong.
//
// The code below is a move, not a rewrite. Anything that looks odd here looked
// odd there, and changing it in the same step as moving it would make a
// regression impossible to attribute.
//
// window.OrgEditPersonModal({ personId, onClose, onSaved })
// window.OrgAddPersonModal({ onClose, onSaved })

(function () {
  "use strict";
  // Held as a const, exactly as screens-org.jsx held it. That LOOKS like it
  // would freeze the static fallback, and data-firm.js has a comment warning
  // about module-load captures -- but buildVaultFirm() MUTATES the object in
  // place on rebuild (clears its keys, copies the new ones) and only assigns
  // when there was none. So this reference stays live. Read the whole branch
  // before "fixing" it; the first read of it here was wrong.
  var F_org = window.VAULT_FIRM;
function EditPersonModal({ personId, onClose, onSaved }) {
  const p = F_org.PEOPLE_BY_ID[personId];
  if (!p) return null;
  const [name, setName]   = React.useState(p.name);
  const [title, setTitle] = React.useState(p.title || "");
  const [role, setRole]   = React.useState(p.role || "");
  // READ-ONLY, AND NOT STORED IN VAULT. `people` HAS NO EMAIL COLUMN -- checked
  // against the live schema 2026-08-27. This field has always been a derived
  // string with nowhere to go, so typing in it and pressing Save changed
  // nothing and said nothing. Harvey is the identity authority for email
  // (harvey_users.email), and all 137 active people join to it on
  // harvey_users.user_name = people.id, so the address shown is the real one.
  // To CHANGE an address, change it in Harvey.
  const email = ((window.VAULT_HARVEY_EMAIL || {})[p.id])
    || `${String(p.id).replace("p_", "")}@harveyllc.com`;
  const [supId, setSupId] = React.useState(p.supId || "");
  const [active, setActive] = React.useState(p.active !== false);
  // people.effective_date. probe_90d (2026-08-26) found it populated on ZERO
  // rows firm-wide, so the Position History panel had nothing to draw. Brian's
  // ruling: enter them by hand for now, wire an automatic source later. This
  // field is that hand.
  const [effDate, setEffDate] = React.useState(p.effectiveDate || "");
  const [error, setError] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [confirmDelete, setConfirmDelete] = React.useState(false);

  // WHO WOULD BE ORPHANED. A delete that leaves reports behind puts them under
  // a supervisor who no longer exists, and the chart simply stops drawing them
  // -- the probe_90b failure, created on purpose. Counted from the roster the
  // screen is showing, not assumed.
  const blockers = (F_org.PEOPLE || []).filter(q =>
    q.active !== false && q.id !== p.id &&
    (q.supId === p.id || q.team === p.id));

  // RETIRE, NOT DELETE (Brian, 2026-09-01). A departed person is not an absent
  // one: their attribution, their position history and every note they wrote
  // still have to resolve. Marking them inactive takes them off the chart and
  // out of every picker while keeping them findable in the directory.
  //
  // The database now agrees rather than relying on this: sql_203 changed
  // people_positions.person_id from ON DELETE CASCADE to RESTRICT, so a real
  // delete would take that person's whole history with it and is refused. The
  // old warning about using Inactive was advice, and advice is not a constraint.
  const removePerson = async () => {
    setError(""); setBusy(true);
    try {
      if (!window.VaultAPI?.updatePerson) throw new Error("updatePerson is not available.");
      await window.VaultAPI.updatePerson(p.id, { active: false });
      p.active = false;
      if (window.reloadOrgChart) await window.reloadOrgChart();
      window.dispatchEvent(new CustomEvent("vault:org-updated"));
      onSaved && onSaved();
      onClose && onClose();
    } catch (e) {
      setError("Could not retire: " + (e && e.message ? e.message : e));
    } finally { setBusy(false); }
  };

  // Persist to Supabase people table (get_org_chart source of truth), then
  // refresh the org so the change survives reloads and flows into goals.
  // A TITLE OR TEAM CHANGE IS AMBIGUOUS AND MUST BE ASKED ABOUT (Brian,
  // 2026-09-01). "Editing the title should have a prompt that asks if the intent
  // was to note a promotion or fix an erroneous existing title."
  //
  // They are different facts. A promotion opens a new position row from the date
  // it happened and keeps the old one; a correction edits the current row and
  // writes no history. Guessing wrong in either direction corrupts the record: a
  // typo saved as a promotion invents a career event, a promotion saved as a
  // correction erases one.
  //
  // Name, status and email never prompt -- none of them is a position.
  const [pending, setPending] = React.useState(null);
  const [changeDate, setChangeDate] = React.useState("");
  const [changeNote, setChangeNote] = React.useState("");

  // POSITION HISTORY. Loaded on open, not on save, so the panel shows what is
  // actually stored rather than what this modal thinks it wrote.
  const [history, setHistory] = React.useState(null);   // null = still loading
  const [addingPrior, setAddingPrior] = React.useState(false);
  const [prior, setPrior] = React.useState({ from: "", to: "", title: "", sup: "", note: "" });
  // Which past row is open for editing, and the values being edited. A row is
  // edited IN PLACE rather than deleted-and-re-added: re-adding would lose the
  // created_by and created_at that say who recorded it and when.
  const [editing, setEditing] = React.useState(null);   // {id, from, to, title, sup}
  const loadHistory = React.useCallback(() => {
    if (!window.VaultAPI || !window.VaultAPI.listPersonPositions) { setHistory([]); return; }
    window.VaultAPI.listPersonPositions(p.id)
      .then(rows => setHistory(rows || []))
      .catch(() => setHistory([]));   // a missing history is not a reason to block the modal
  }, [p.id]);
  React.useEffect(() => { loadHistory(); }, [loadHistory]);

  // ANNOUNCE A POSITION WRITE. The drawer on Tree/Map and the Directory profile
  // both fetch position history when they mount and have no way to learn that
  // this modal wrote something, so a promotion only appeared after a full page
  // reload. reloadOrgChart() refreshes `people`, not people_positions -- they
  // are different tables and only the first had a refresh path.
  const announcePositions = React.useCallback(() => {
    try {
      window.dispatchEvent(new CustomEvent("vault:positions-updated", { detail: { personId: p.id } }));
    } catch (e) { /* an old browser without CustomEvent must not break the save */ }
  }, [p.id]);

  // Saving an edit to an existing row. clearLead distinguishes "no supervisor"
  // from "unchanged" -- the server cannot tell them apart from a null alone.
  const saveEdit = async () => {
    setError(""); setBusy(true);
    try {
      await window.VaultAPI.updatePosition(editing.id, {
        title: editing.title || null,
        subTeamLeadId: editing.sup || null,
        clearLead: !editing.sup,
        effectiveFrom: editing.from || null,
        effectiveTo: editing.to || null,
      });
      setEditing(null); setBusy(false);
      loadHistory(); announcePositions();
      if (window.reloadOrgChart) window.reloadOrgChart();
    } catch (e) {
      setBusy(false);
      setError(e && e.message ? e.message : String(e));
    }
  };

  // A CLOSED row before the current one. This is the only way to enter history
  // that predates Vault: every seeded row is stamped 2026-07-20, and
  // recordPositionChange only moves forward, so "Associate in 2019" has nowhere
  // else to go.
  const addPrior = async () => {
    if (!prior.from || !prior.to) { setError("A past role needs both a start and an end date."); return; }
    if (!prior.title.trim()) { setError("A past role needs a title."); return; }
    setError(""); setBusy(true);
    try {
      await window.VaultAPI.insertPriorPosition(p.id, prior.from, prior.to,
        { title: prior.title.trim(), subTeamLeadId: prior.sup || null,
          note: prior.note || null });
      setPrior({ from: "", to: "", title: "", sup: "", note: "" });
      setAddingPrior(false); setBusy(false);
      loadHistory(); announcePositions();
    } catch (e) {
      setBusy(false);
      // The overlap message names the position it collides with, so it is worth
      // showing verbatim rather than replacing with something generic.
      setError(e && e.message ? e.message : String(e));
    }
  };

  const save = async () => {
    setError(""); setBusy(true);
    const patch = {};
    if (name !== p.name) patch.name = name;
    if (title !== (p.title || "")) patch.role = title;            // people.role IS the title
    if (active !== (p.active !== false)) patch.active = active;
    // Empty clears the date rather than writing "". A NOT NULL column would
    // reject "" and a nullable one would store an empty string that no date
    // comparison can use.
    if ((effDate || "") !== (p.effectiveDate || "")) patch.effective_date = effDate || null;
    if ((supId || "") !== (p.supId || "")) {
      // Supervisor is the single source of truth: module = supervisor's branch,
      // sub-team = derived from who you report to. Module/sub-team are not
      // independently editable (they'd be overwritten by topology anyway).
      const branchIds = new Set((F_org.ORG_TREE.branches || []).map(b => b.id));
      const lastOf = x => (x && (x.last || (x.name || "").split(" ").pop())) || "";
      if (supId && branchIds.has(supId)) {
        // Reporting directly to an MD -> becomes a sub-team lead (self-point)
        patch.sub_team_lead_id = p.id;
        patch.team = supId;
        patch.sub_team_name = lastOf(p) || lastOf({ name });
      } else if (supId) {
        patch.sub_team_lead_id = supId;
        patch.team = F_org.ORG_TREE.branchOf?.[supId] || null;
        patch.sub_team_name = lastOf(F_org.PEOPLE_BY_ID[supId]);
      } else {
        patch.sub_team_lead_id = null;
      }
      if (patch.team) {
        const head = F_org.PEOPLE_BY_ID[patch.team];
        if (head) patch.team_name = lastOf(head) + " Module";
      }
    }
    // Does this edit touch a POSITION? role, team and sub_team_lead_id are the
    // three columns people_positions mirrors.
    const touchesPosition =
      ("role" in patch) || ("team" in patch) || ("sub_team_lead_id" in patch);

    // "In Role Since" ON ITS OWN needs no prompt: changing when the CURRENT
    // title began is unambiguously a correction of the open row. It is only
    // ambiguous alongside a title change, which the prompt below handles.
    //
    // It must not go through updatePerson alone. people.effective_date and
    // people_positions.effective_from are two places holding one fact, and
    // writing only the first is exactly what left 137 position rows with no
    // start date while the person carried one.
    if (!touchesPosition && ("effective_date" in patch)) {
      try {
        await window.VaultAPI.correctCurrentPosition(p.id, { effectiveFrom: patch.effective_date });
      } catch (e) {
        setBusy(false);
        setError("Couldn't set the date: " + (e && e.message ? e.message : e));
        return;
      }
    }
    if (touchesPosition && !pending) {
      setBusy(false);
      setPending({
        patch: patch,
        title: "role" in patch ? patch.role : null,
        module: "team" in patch ? patch.team : null,
        subTeamLeadId: "sub_team_lead_id" in patch ? patch.sub_team_lead_id : undefined,
      });
      // Seed with the date already on the person so a correction need not retype
      // it. NOT today -- today is when someone opened Vault, not when anything
      // happened.
      setChangeDate(effDate || "");
      return;
    }

    try {
      if (Object.keys(patch).length && window.VaultAPI?.updatePerson) {
        await window.VaultAPI.updatePerson(p.id, patch);
      }
      // Optimistic in-memory update for instant UI; loader refresh makes it authoritative.
      p.name = name; p.title = title; p.email = email; p.active = active;
      p.effectiveDate = effDate || null;
      p.supId = supId || null;
      p.initials = name.split(" ").map(s => s[0]).join("").slice(0,2).toUpperCase();
      if (window.reloadOrgChart) window.reloadOrgChart();
      onSaved && onSaved();
    } catch (e) {
      setBusy(false);
      setError("Save failed: " + (e && e.message ? e.message : e));
    }
  };

  // PROMOTION: a new row from a typed date; the old one closes the day before.
  const commitPromotion = async () => {
    if (!changeDate) { setError("Enter the date this took effect."); return; }
    setError(""); setBusy(true);
    try {
      await window.VaultAPI.recordPositionChange(p.id, changeDate, {
        title: pending.title, module: pending.module,
        subTeamLeadId: pending.subTeamLeadId, note: changeNote || null,
      });
      await finishLocal(changeDate);
    } catch (e) { setBusy(false); setError("Couldn't record it: " + (e && e.message ? e.message : e)); }
  };

  // CORRECTION: edits the open row. No new row, because nothing happened.
  const commitCorrection = async () => {
    setError(""); setBusy(true);
    try {
      await window.VaultAPI.correctCurrentPosition(p.id, {
        title: pending.title, module: pending.module,
        subTeamLeadId: pending.subTeamLeadId,
        effectiveFrom: changeDate || null, note: changeNote || null,
      });
      await finishLocal(changeDate || effDate);
    } catch (e) { setBusy(false); setError("Couldn't correct it: " + (e && e.message ? e.message : e)); }
  };

  // Both RPCs write people AND people_positions together, so only the
  // non-position fields still need the ordinary update.
  const finishLocal = async (dateUsed) => {
    const rest = Object.assign({}, pending.patch);
    delete rest.role; delete rest.team; delete rest.sub_team_lead_id;
    delete rest.effective_date; delete rest.sub_team_name; delete rest.team_name;
    if (Object.keys(rest).length && window.VaultAPI?.updatePerson) {
      await window.VaultAPI.updatePerson(p.id, rest);
    }
    p.name = name; p.title = title; p.email = email; p.active = active;
    p.effectiveDate = dateUsed || null;
    p.supId = supId || null;
    p.initials = name.split(" ").map(s2 => s2[0]).join("").slice(0, 2).toUpperCase();
    setPending(null); setBusy(false);
    announcePositions();
    if (window.reloadOrgChart) window.reloadOrgChart();
    onSaved && onSaved();
  };

  // SORTED, because F_org.PEOPLE is in ORG-TREE order -- branch by branch,
  // alphabetical only WITHIN a branch. In a 138-name dropdown that reads as
  // alphabetical for the first module and then restarts, which is worse than no
  // order at all: you stop scanning once you pass where the name should be.
  //
  // By full name, matching AddPersonModal's picker in this same file rather
  // than introducing a second sort rule. localeCompare, not <, so accented
  // names land where a reader expects them.
  const allPeople = F_org.PEOPLE
    .filter(x => x.id !== p.id)
    .slice()
    .sort((a, b) => String(a.name || "").localeCompare(String(b.name || "")));

  // ---------------------------------------------------------------- render
  // REBUILT TO THE DESIGN, 2026-09-01 (Edit Employee Modal 2b.dc.html).
  //
  // Two columns: the editable person on the left, position history as a fixed
  // sidebar on the right. Before this the history hung under the form and the
  // modal grew as it filled, so a person with four roles pushed Save off the
  // bottom of a 540px dialog.
  //
  // COLOURS AND SIZES ARE TOKENS, NOT THE HEXES IN THE .dc.html. A design file
  // is standalone and has to spell #196EA7; shipping that would fork the theme
  // and break dark mode. BRANDING §2 is explicit that the token is the value.
  const LBL = { fontSize: "var(--t-micro)", letterSpacing: ".02em",
                color: "var(--muted)", fontWeight: "var(--w-medium)", marginBottom: 4 };
  const FLD = { width: "100%", boxSizing: "border-box", padding: "7px 11px",
                fontSize: "var(--t-body-lg)", fontFamily: "inherit",
                border: "1px solid var(--line-strong)", borderRadius: "var(--r-ctl)",
                background: "var(--surface)", color: "var(--ink)" };
  const RO  = Object.assign({}, FLD, { border: "1px solid var(--line-2)",
                background: "var(--surface-2)", color: "var(--ink-3)",
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" });
  const HINT = { fontSize: "var(--t-small)", color: "var(--muted)", marginTop: 2, lineHeight: 1.4 };
  const BTN = { whiteSpace: "nowrap", flexShrink: 0, display: "inline-flex", alignItems: "center",
                padding: "5px 10px", borderRadius: "var(--r-ctl)", fontSize: "var(--t-body)",
                fontWeight: "var(--w-medium)", fontFamily: "inherit", cursor: "pointer" };
  const BTN_PRIMARY = Object.assign({}, BTN, { background: "var(--accent)",
                border: "1px solid var(--accent)", color: "var(--accent-ink)" });
  const BTN_PLAIN = Object.assign({}, BTN, { background: "var(--surface)",
                border: "1px solid var(--line-strong)", color: "var(--ink)" });
  const BTN_QUIET = Object.assign({}, BTN, { background: "transparent",
                border: "1px solid transparent", color: "var(--risk)" });

  // What the sidebar promises will happen on save. It reads the SAME comparison
  // the save handler uses -- title or supervisor differing from the stored
  // person -- so the promise and the behaviour cannot drift.
  const willChangeTitle = title !== (p.title || "");
  const willChangeSup   = (supId || "") !== (p.supId || "");
  const supName = supId ? ((F_org.PEOPLE_BY_ID[supId] || {}).name || "") : "";

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 60,
      background: "var(--scrim)",
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <div onClick={e => e.stopPropagation()} className="card" style={{
        width: 760, maxWidth: "94vw", maxHeight: "88vh", overflow: "auto",
      }}>
        {/* ---- header ---- */}
        <div style={{ display: "flex", alignItems: "center", gap: 8,
          padding: "14px 18px", borderBottom: "1px solid var(--line)" }}>
          <Avatar id={p.id} size="lg"/>
          <div style={{ flex: 1, marginLeft: 10 }}>
            <div className="micro">Edit Employee</div>
            <div className="display" style={{ fontSize: "var(--t-section)", marginTop: 2 }}>{p.name}</div>
          </div>
          <button className="btn ghost sm" onClick={onClose} style={{ padding: 4 }}>
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
          </button>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) 264px" }}>
          {/* ---- left: the person ---- */}
          <div style={{ padding: "16px 18px", display: "grid",
            gridTemplateColumns: "1fr 1fr", gap: "10px 12px", alignContent: "start" }}>
            <div style={{ gridColumn: "span 2", fontSize: "var(--t-body-lg)",
              fontWeight: "var(--w-medium)", color: "var(--ink)" }}>
              Current Employee Information
            </div>

            <label style={{ gridColumn: "span 2" }}>
              <div style={LBL}>Full Name</div>
              <input value={name} onChange={e => setName(e.target.value)} style={FLD}/>
            </label>

            <label>
              <div style={LBL}>Title</div>
              <TitleSelect value={title} onChange={setTitle} bare/>
            </label>

            <label>
              <div style={LBL}>Status</div>
              <select value={active ? "1" : ""} onChange={e => setActive(e.target.value === "1")}
                style={Object.assign({}, FLD, { cursor: "pointer" })}>
                <option value="1">Active</option>
                <option value="">Inactive</option>
              </select>
            </label>

            <label>
              <div style={LBL}>Email</div>
              <div style={RO}>{email || "\u2014"}</div>
              <div style={HINT}>From Harvey. Change it there, not here.</div>
            </label>

            <label>
              <div style={LBL}>In Role Since</div>
              <input type="date" value={effDate || ""} onChange={e => setEffDate(e.target.value)} style={FLD}/>
              <div style={HINT}>The date this person started this title.</div>
            </label>

            <label>
              <div style={LBL}>Supervisor</div>
              <select value={supId || ""} onChange={e => setSupId(e.target.value)}
                style={Object.assign({}, FLD, { cursor: "pointer" })}>
                <option value="">{"\u2014 none \u2014"}</option>
                {allPeople.map(x => (
                  <option key={x.id} value={x.id}>
                    {x.name + " \u00b7 " + (x.role || "")}{x.active === false ? " (former)" : ""}
                  </option>
                ))}
              </select>
            </label>

            <div style={{ gridColumn: "span 2" }}>
              <div style={LBL}>Module | Sub-Team</div>
              <div style={RO}>
                {(() => {
                  // A MODULE LEAD IS THEIR OWN MODULE. This read "— · Harvey"
                  // for Brian: his supervisor is the CEO, the CEO is not a
                  // branch, so branchOf missed and the module fell back to a
                  // dash while the sub-team printed the CEO's surname.
                  const lastOf = x => (x && (x.last || (x.name || "").split(" ").pop())) || "";
                  const chosenSup = supId ? F_org.PEOPLE_BY_ID[supId] : null;
                  const branchIds = new Set((F_org.ORG_TREE.branches || []).map(b => b.id));
                  if (branchIds.has(p.id)) return lastOf(p) + " Module | leads the module";
                  const branchId = supId && branchIds.has(supId) ? supId
                    : (supId ? F_org.ORG_TREE.branchOf?.[supId] : F_org.ORG_TREE.branchOf?.[p.id]);
                  const head = branchId ? F_org.PEOPLE_BY_ID[branchId] : null;
                  const mod = head ? lastOf(head) + " Module" : "\u2014";
                  const sub = supId && branchIds.has(supId) ? (lastOf(p) + " Sub-Team (lead)")
                    : (chosenSup ? lastOf(chosenSup) + " Sub-Team" : "\u2014");
                  return mod + " | " + sub;
                })()}
              </div>
              <div style={HINT}>Derived from supervisor — change supervisor to move teams.</div>
            </div>
          </div>

          {/* ---- right: position history ---- */}
          <div style={{ borderLeft: "1px solid var(--line)", background: "var(--surface-2)",
            padding: "16px 14px", display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ fontSize: "var(--t-body-lg)", fontWeight: "var(--w-medium)", color: "var(--ink)" }}>
              Position History
            </div>
            <div style={{ fontSize: "var(--t-small)", color: "var(--muted)", lineHeight: 1.45, marginTop: -4 }}>
              Promotions and moves record themselves — change Title or Supervisor and save.
            </div>

            {/* Live consequence. It appears the moment the fields differ, so the
                promotion path is visible BEFORE saving rather than only in the
                prompt afterwards -- which is what led to "do I add the old role
                by hand?". */}
            {(willChangeTitle || willChangeSup) && !pending ? (
              <div style={{ background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
                borderRadius: "var(--r-ctl)", padding: "8px 10px" }}>
                <div className="micro" style={{ color: "var(--accent)" }}>On Save</div>
                <div style={{ fontSize: "var(--t-small)", color: "var(--ink-2)", marginTop: 3, lineHeight: 1.45 }}>
                  The current role closes itself and <strong>{title || p.title}</strong>
                  {willChangeSup && supName ? " under " + supName : ""} opens as a new
                  position — no manual entry needed.
                </div>
              </div>
            ) : null}

            {history === null ? (
              <div className="micro" style={{ color: "var(--muted)" }}>Loading…</div>
            ) : history.length === 0 ? (
              <div className="micro" style={{ color: "var(--muted)" }}>Nothing recorded yet.</div>
            ) : history.map(h => (
              <div key={h.id} onClick={() => {
                  setEditing({ id: h.id, from: h.effectiveFrom || "", to: h.effectiveTo || "",
                               title: h.title || "", sup: h.subTeamLeadId || "" });
                  setAddingPrior(false); setError("");
                }}
                title="Edit this position"
                style={{ display: "flex", gap: 10, cursor: "pointer" }}>
                <div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 5 }}>
                  <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0,
                    background: h.effectiveTo ? "var(--line-strong)" : "var(--accent)" }}/>
                  <span style={{ width: 1, flex: 1, background: "var(--line-strong)", marginTop: 4 }}/>
                </div>
                <div style={{ flex: 1, minWidth: 0, background: "var(--surface)",
                  border: "1px solid " + (editing && editing.id === h.id ? "var(--accent)" : "var(--line)"),
                  borderRadius: "var(--r-ctl)", padding: "8px 10px" }}>
                  <div style={{ fontFamily: "var(--f-mono)", fontSize: "var(--t-micro)", color: "var(--muted)" }}>
                    {(h.effectiveFrom || "no date") + " \u2192 " + (h.effectiveTo || "Now")}
                    {h.isSeed ? " \u00b7 not verified" : ""}
                  </div>
                  <div style={{ fontSize: "var(--t-body)", fontWeight: "var(--w-medium)",
                    color: "var(--ink)", marginTop: 2 }}>{h.title || "\u2014"}</div>
                  {h.subTeamLeadId ? (
                    <div style={{ fontSize: "var(--t-small)", color: "var(--muted)", marginTop: 1 }}>
                      {"Supervisor: " + ((F_org.PEOPLE_BY_ID[h.subTeamLeadId] || {}).name || h.subTeamLeadId)}
                    </div>
                  ) : null}
                </div>
              </div>
            ))}

            {editing ? (
              <div style={{ background: "var(--surface)", border: "1px solid var(--accent)",
                borderRadius: "var(--r-ctl)", padding: 10, display: "grid", gap: 8 }}>
                <div className="micro">Editing this position</div>
                <label>
                  <div style={LBL}>Started</div>
                  <input type="date" value={editing.from}
                    onChange={e => setEditing({ ...editing, from: e.target.value })} style={FLD}/>
                </label>
                <label>
                  <div style={LBL}>Ended</div>
                  {/* The OPEN row must not gain an end date -- closing it leaves
                      the person with no current position, which the one-open-row
                      index cannot express. */}
                  <input type="date" value={editing.to} disabled={!editing.to}
                    onChange={e => setEditing({ ...editing, to: e.target.value })}
                    title={editing.to ? "" : "This is the current position — it has no end date."}
                    style={Object.assign({}, FLD, editing.to ? {} : { background: "var(--surface-2)", color: "var(--muted)" })}/>
                </label>
                <label>
                  <div style={LBL}>Title</div>
                  <TitleSelect value={editing.title} onChange={v => setEditing({ ...editing, title: v })} bare/>
                </label>
                <label>
                  <div style={LBL}>Reported to</div>
                  <select value={editing.sup} onChange={e => setEditing({ ...editing, sup: e.target.value })}
                    style={Object.assign({}, FLD, { cursor: "pointer" })}>
                    <option value="">{"\u2014 not recorded \u2014"}</option>
                    {allPeople.map(x => (
                      <option key={x.id} value={x.id}>{x.name}{x.active === false ? " (former)" : ""}</option>
                    ))}
                  </select>
                </label>
                <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                  <button style={BTN_PLAIN} onClick={() => { setEditing(null); setError(""); }}>Cancel</button>
                  <button style={BTN_PRIMARY} onClick={saveEdit} disabled={busy}>
                    {busy ? "Saving…" : "Save position"}
                  </button>
                </div>
              </div>
            ) : null}

            {addingPrior ? (
              <div style={{ background: "var(--surface)", border: "1px solid var(--line-strong)",
                borderRadius: "var(--r-ctl)", padding: 10, display: "grid", gap: 8 }}>
                <label>
                  <div style={LBL}>Started</div>
                  <input type="date" value={prior.from}
                    onChange={e => setPrior({ ...prior, from: e.target.value })} style={FLD}/>
                </label>
                <label>
                  <div style={LBL}>Ended</div>
                  <input type="date" value={prior.to}
                    onChange={e => setPrior({ ...prior, to: e.target.value })} style={FLD}/>
                  <div style={HINT}>The last day in that role.</div>
                </label>
                <label>
                  <div style={LBL}>Title held</div>
                  <TitleSelect value={prior.title} onChange={v => setPrior({ ...prior, title: v })} bare/>
                </label>
                <label>
                  <div style={LBL}>Reported to (optional)</div>
                  <select value={prior.sup} onChange={e => setPrior({ ...prior, sup: e.target.value })}
                    style={Object.assign({}, FLD, { cursor: "pointer" })}>
                    <option value="">{"\u2014 not recorded \u2014"}</option>
                    {allPeople.map(x => (
                      <option key={x.id} value={x.id}>{x.name}{x.active === false ? " (former)" : ""}</option>
                    ))}
                  </select>
                </label>
                <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                  <button style={BTN_PLAIN} onClick={() => { setAddingPrior(false); setError(""); }}>Cancel</button>
                  <button style={BTN_PRIMARY} onClick={addPrior} disabled={busy}>
                    {busy ? "Saving…" : "Add"}
                  </button>
                </div>
              </div>
            ) : (
              <React.Fragment>
                <button style={Object.assign({}, BTN_PLAIN, { justifyContent: "center", color: "var(--accent)" })}
                  onClick={() => {
                    // PRE-FILL THE END DATE. effective_to is the LAST DAY HELD,
                    // so a role ending the day BEFORE the next one starts is
                    // contiguous and one ending ON that day overlaps by a day.
                    const starts = (history || []).map(h => h.effectiveFrom).filter(Boolean).sort();
                    let endDefault = "";
                    if (starts.length) {
                      const d = new Date(starts[0] + "T00:00:00");
                      d.setDate(d.getDate() - 1);
                      endDefault = new Date(d.getTime() - d.getTimezoneOffset() * 60000)
                        .toISOString().slice(0, 10);
                    }
                    setPrior({ from: "", to: endDefault, title: "", sup: "", note: "" });
                    setAddingPrior(true); setError("");
                  }}>
                  Add Previous Position
                </button>
                <div style={{ fontSize: "var(--t-micro)", color: "var(--muted)",
                  textAlign: "center", marginTop: -4 }}>
                  Only for roles held before Vault.
                </div>
              </React.Fragment>
            )}
          </div>
        </div>

        {/* ---- footer ---- */}
        <div style={{ display: "flex", alignItems: "center", gap: 8,
          padding: "12px 18px", borderTop: "1px solid var(--line)" }}>
          {confirmDelete ? (
            <React.Fragment>
              <div className="micro" style={{ flex: 1, color: "var(--ink-3)" }}>
                {blockers.length
                  ? blockers.length + " active " + (blockers.length === 1 ? "person reports" : "people report")
                    + " to " + p.name + ". Move them first, or set Status to Inactive instead."
                  : "Retire " + p.name + "? They come off the org chart and out of the pickers, and stay findable in the directory with their history and attribution intact."}
              </div>
              <button style={BTN_PLAIN} onClick={() => setConfirmDelete(false)}>Keep</button>
              <button style={BTN_PRIMARY} onClick={removePerson}
                disabled={busy || blockers.length > 0}>Retire</button>
            </React.Fragment>
          ) : pending ? (
            /* THE PROMPT. Two buttons, because there are exactly two things this
               edit could mean and Vault must not choose between them. A typo
               saved as a promotion invents a career event; a promotion saved as
               a correction erases one. */
            <React.Fragment>
              <div style={{ flex: 1, display: "grid", gap: 6 }}>
                <div className="micro" style={{ color: "var(--ink-2)" }}>
                  Is this a promotion or team move, or a fix to a wrong entry?
                </div>
                <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
                  <label className="micro" style={{ display: "flex", gap: 5, alignItems: "center" }}>
                    Effective
                    <input type="date" value={changeDate} onChange={e => setChangeDate(e.target.value)}
                      style={Object.assign({}, FLD, { width: "auto", padding: "4px 6px" })}/>
                  </label>
                  <input placeholder="Note (optional)" value={changeNote}
                    onChange={e => setChangeNote(e.target.value)}
                    style={Object.assign({}, FLD, { flex: 1, padding: "4px 8px" })}/>
                </div>
                {error ? <div className="micro" style={{ color: "var(--risk)" }}>{error}</div> : null}
              </div>
              <button style={BTN_PLAIN} onClick={() => { setPending(null); setError(""); }}>Cancel</button>
              <button style={BTN_PLAIN} onClick={commitCorrection} disabled={busy}
                title="Updates the current entry. No history is written, because nothing changed.">
                Fix a wrong entry
              </button>
              <button style={BTN_PRIMARY} onClick={commitPromotion} disabled={busy}
                title="Opens a new position from the date above and keeps the old title in history.">
                {busy ? "Saving…" : "Promotion or move"}
              </button>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <div className="micro" style={{ flex: 1, color: "var(--risk)" }}>{error}</div>
              <button style={BTN_QUIET} onClick={() => { setError(""); setConfirmDelete(true); }}>Retire</button>
              <button style={BTN_PLAIN} onClick={onClose}>Cancel</button>
              <button style={BTN_PRIMARY} onClick={save} disabled={busy}>
                {busy ? "Saving…" : "Save changes"}
              </button>
            </React.Fragment>
          )}
        </div>
      </div>
    </div>
  );
}

// A native date input. NOT reusing EditField: an <input> with no `type` picks
// up theme.css's `input:not([type])` rule, and a typed one does not, so the
// styling has to be stated here (BRANDING §8).
function EditDate({ label, value, onChange, colSpan, hint }) {
  return (
    <label style={{ gridColumn: colSpan ? `span ${colSpan}` : "auto" }}>
      <div className="micro" style={{ marginBottom: 4 }}>{label}</div>
      <input type="date" value={value || ""} onChange={e => onChange(e.target.value)} style={{
        width: "100%", padding: "6px 10px", fontSize: "var(--t-body-lg)",
        fontFamily: "inherit",
        border: "1px solid var(--line)", borderRadius: "var(--r-ctl)",
        background: "var(--surface)", color: "var(--ink)",
      }}/>
      {hint ? <div className="micro" style={{ marginTop: 3 }}>{hint}</div> : null}
    </label>
  );
}

// A field that shows a value Vault does not own. Rendered as text rather than
// a disabled input: a greyed-out box still invites a click and still looks like
// something that ought to save.
function EditReadOnly({ label, value, colSpan, hint }) {
  return (
    <label style={{ gridColumn: colSpan ? `span ${colSpan}` : "auto" }}>
      <div className="micro" style={{ marginBottom: 4 }}>{label}</div>
      <div style={{
        padding: "6px 10px", fontSize: "var(--t-body-lg)",
        border: "1px solid var(--line-2)", borderRadius: "var(--r-ctl)",
        background: "var(--canvas)", color: "var(--ink-3)",
        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
      }}>{value || "\u2014"}</div>
      {hint ? <div className="micro" style={{ marginTop: 3 }}>{hint}</div> : null}
    </label>
  );
}

function EditField({ label, value, onChange, colSpan }) {
  return (
    <label style={{ gridColumn: colSpan ? `span ${colSpan}` : "auto" }}>
      <div className="micro" style={{ marginBottom: 4 }}>{label}</div>
      <input value={value} onChange={e => onChange(e.target.value)} style={{
        width: "100%", padding: "7px 10px", fontSize: "var(--t-body-lg)",
        border: "1px solid var(--line)", borderRadius: "var(--r-ctl)",
        background: "var(--surface)", color: "var(--ink)",
      }}/>
    </label>
  );
}
function EditSelect({ label, value, onChange, options, colSpan }) {
  return (
    <label style={{ gridColumn: colSpan ? `span ${colSpan}` : "auto" }}>
      <div className="micro" style={{ marginBottom: 4 }}>{label}</div>
      <select value={value} onChange={e => onChange(e.target.value)} style={{
        width: "100%", padding: "7px 10px", fontSize: "var(--t-body-lg)",
        border: "1px solid var(--line)", borderRadius: "var(--r-ctl)",
        background: "var(--surface)", color: "var(--ink)",
      }}>
        {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
    </label>
  );
}

// Title dropdown built from titles currently in the org (no typos), with an
// "Other\u2026" escape hatch for genuinely new titles.
// `bare` renders the control WITHOUT its own "Title" label, for callers that
// supply their own -- the redesigned modal labels every field itself, and two
// labels stacked on one control reads as a bug.
function TitleSelect({ value, onChange, colSpan, bare }) {
  const known = React.useMemo(
    () => Array.from(new Set(F_org.PEOPLE.map(x => x.title).filter(Boolean))).sort((a, b) => a.localeCompare(b)),
    []
  );
  const [other, setOther] = React.useState(!!value && !known.includes(value));
  const inputStyle = {
    width: "100%", boxSizing: "border-box", padding: "7px 11px", fontSize: "var(--t-body-lg)",
    fontFamily: "inherit", border: "1px solid var(--line-strong)", borderRadius: "var(--r-ctl)",
    cursor: "pointer",
    background: "var(--surface)", color: "var(--ink)",
  };
  const control = (
    <React.Fragment>
      <select
        value={other ? "__other__" : (value || "")}
        onChange={e => {
          if (e.target.value === "__other__") { setOther(true); onChange(""); }
          else { setOther(false); onChange(e.target.value); }
        }}
        style={inputStyle}
      >
        <option value="">— pick a title —</option>
        {known.map(t => <option key={t} value={t}>{t}</option>)}
        <option value="__other__">Other…</option>
      </select>
      {other && (
        <input autoFocus placeholder="New title…" value={value} onChange={e => onChange(e.target.value)}
          style={{ ...inputStyle, marginTop: 6 }}/>
      )}
    </React.Fragment>
  );
  if (bare) return control;
  return (
    <label style={{ gridColumn: colSpan ? `span ${colSpan}` : "auto" }}>
      <div className="micro" style={{ marginBottom: 4 }}>Title</div>
      {control}
    </label>
  );
}


function AddPersonModal({ onClose, onSaved }) {
  const [name, setName]   = React.useState("");
  const [title, setTitle] = React.useState("");
  const [supId, setSupId] = React.useState("");
  const [effDate, setEffDate] = React.useState("");
  const [error, setError] = React.useState("");

  const subteams = F_org.SUBTEAMS || [];
  // Default sub-team derived from supervisor — admin can override.
  const supSubteam = supId ? (F_org.PEOPLE_BY_ID[supId]?.subteam || "") : "";
  const [subteam, setSubteam] = React.useState("");
  const effectiveSubteam = subteam || supSubteam;

  const allPeople = F_org.PEOPLE.slice().sort((a, b) => a.name.localeCompare(b.name));

  // PREVIEW ONLY. `people` has no email column, and this modal never sent one
  // to createPerson even when the field was editable -- it went onto a local
  // object and evaporated. The real address is created in Harvey; this shows
  // what the handle will be so a typo in the name is visible before saving.
  const previewId = genId(name);
  const email = previewId ? `${previewId}@harveyllc.com` : "";

  // Same bucketing as data-firm.js roleFor (mirrored locally).
  function bucketRole(t) {
    const s = (t || "").toLowerCase();
    if (s.includes("executive vice president") || s.includes("evp")) return "MD";
    if (s.includes("vice president") || s === "vp") return "VP";
    if (s === "president" || s.includes("ceo")) return "MD";
    if (s.includes("managing director")) return "MD";
    if (s.includes("director")) return "MD";
    if (s.includes("cfo") || s.includes("chief")) return "MD";
    if (s.includes("associate")) return "Associate";
    if (s.includes("analyst")) return "Analyst";
    if (s.includes("manager")) return "VP";
    if (s.includes("coordinator") || s.includes("specialist") || s.includes("admin") || s.includes("recruiter") || s.includes("designer")) return "Associate";
    return "Associate";
  }

  function genId(fullName) {
    const parts = fullName.trim().split(/\s+/);
    if (!parts.length) return "";
    const base = (parts[0][0] + parts[parts.length - 1]).toLowerCase().replace(/[^a-z0-9]/g, "");
    let id = base, i = 2;
    while (F_org.PEOPLE_BY_ID[id]) { id = base + i; i++; }
    return id;
  }

  // Walk up sup chain until we hit a registered branch lead (or president).
  // THE MODULE COMES FROM THE SUPERVISOR'S OWN `team`, full stop.
  //
  // This used to walk ORG_TREE.branches looking for an ancestor that happened
  // to be a branch. That set is built from people who NAME dharvey as their
  // sub-team lead -- and Will Harriss's pointer was NULL, so he was not in it.
  // Adding Test Europe under him therefore fell through to "reports to the
  // president", and the new hire was given A MODULE OF THEIR OWN, named after
  // themselves: team 'teurope', team_name 'Europe Module'. A phantom module,
  // silently, from picking a supervisor.
  //
  // people.team is the module and it is right there on the supervisor's row.
  // Reading it needs no tree, cannot be thrown by a missing pointer, and gives
  // the same answer for every supervisor at every depth. The only case that is
  // genuinely a new branch is reporting to the president.
  function findBranchOwner(sId) {
    if (!sId) return null;
    const sup = F_org.PEOPLE_BY_ID[sId];
    if (!sup) return null;
    if (sId === F_org.ORG_TREE.presidentId) return null;  // a real new branch
    return sup.team || null;
  }

  const save = async () => {
    if (!name.trim())  return setError("Name is required.");
    if (!title.trim()) return setError("Title is required.");
    if (!supId)        return setError("Pick a supervisor — drives team & sub-team.");

    const id = genId(name);
    const role = bucketRole(title);
    const initials = name.split(/\s+/).map(s => s[0]).join("").slice(0, 2).toUpperCase();
    const tones = ["a","b","c","d","e"];
    let h = 0; for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0;
    const tone = tones[Math.abs(h) % tones.length];

    const branchOwner = findBranchOwner(supId);
    const subteamId = effectiveSubteam || (branchOwner ? "st-" + branchOwner : "st-" + id);

    const newPerson = {
      id, name: name.trim(), initials, role, title: title.trim(),
      subteam: subteamId, synth: false, tone,
      supId: supId || null,
      email: `${id}@harveyllc.com`,   // display only; not a people column
    };

    // Persist to Supabase people first, so the new hire survives reloads.
    try {
      const head = branchOwner ? F_org.PEOPLE_BY_ID[branchOwner] : null;
      // Sub-team derives from the supervisor: reporting to a sub-team lead joins
      // their team; reporting directly to an MD = MD-direct (no sub-team yet).
      const supPerson = F_org.PEOPLE_BY_ID[supId];
      const supIsBranchHead = !!(branchOwner && supId === branchOwner);
      const stLabel = supIsBranchHead || !supPerson
        ? null
        : (supPerson.last || (supPerson.name || "").split(" ").pop());
      if (window.VaultAPI?.createPerson) {
        await window.VaultAPI.createPerson({
          // ONE CONVENTION FOR sub_team_lead_id, matching EditPersonModal.
          //
          // That column carries TWO facts: who your sub-team lead is, AND
          // (by SELF-POINTING) that you ARE one. probe_90a found 26 people
          // self-pointing, so the convention is live. This modal wrote the
          // supervisor's id in every case, so a person added here reporting
          // straight to an MD was never marked a sub-team lead -- and
          // lib-org-scope's deriveTier() could therefore never return
          // `team_lead` for them. Worse, EditPersonModal only rewrites this
          // field when the supervisor CHANGES, so editing them to the same MD
          // is a no-op and they could not be promoted without being moved away
          // and back. Fixed 2026-08-26.
          id, name: name.trim(), role: title.trim(), active: true,
          effective_date: effDate || null,
          sub_team_lead_id: supIsBranchHead ? id : (supId || null),
          team: branchOwner || id,
          team_name: head
            ? (head.last || head.name.split(" ").pop()) + " Module"
            : name.trim().split(/\s+/).pop() + " Module",
          sub_team_name: stLabel,
        });
      }
    } catch (e) {
      return setError("Save failed: " + (e && e.message ? e.message : e));
    }

    F_org.PEOPLE.push(newPerson);
    F_org.PEOPLE_BY_ID[id] = newPerson;

    if (branchOwner) {
      // Slots into an existing branch.
      F_org.ORG_TREE.branchOf[id] = branchOwner;
      const b = F_org.ORG_TREE.branches.find(x => x.id === branchOwner);
      if (b) b.count = (b.count || 0) + 1;
    } else {
      // Reports to the president → becomes a new branch lead.
      F_org.ORG_TREE.branchOf[id] = id;
      F_org.ORG_TREE.branches.push({ id, name: newPerson.name, title: newPerson.title, count: 1 });
      // Make sure a sub-team exists for the new branch.
      if (!subteams.find(s => s.id === "st-" + id)) {
        subteams.push({
          id: "st-" + id, moduleId: "deals",
          label: newPerson.name.split(" ").pop(),
          synth: false, lead: id, leadTitle: newPerson.title, headcount: 1,
        });
      }
    }

    if (window.reloadOrgChart) window.reloadOrgChart();
    onSaved && onSaved(id);
  };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 60,
      background: "var(--scrim)",
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <div onClick={e => e.stopPropagation()} className="card" style={{
        width: 560, maxWidth: "92vw", maxHeight: "86vh", overflow: "auto",
      }}>
        <div className="row" style={{ padding: "14px 18px", borderBottom: "1px solid var(--line)" }}>
          <div style={{ flex: 1 }}>
            <div className="micro">Admin</div>
            <div className="display" style={{ fontSize: "var(--t-section)", marginTop: 1 }}>Add employee</div>
          </div>
          <button className="btn ghost sm" onClick={onClose} style={{ padding: 4 }}>
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
          </button>
        </div>
        <div style={{ padding: 18, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <EditField label="Full name" value={name}  onChange={(v) => { setName(v); setError(""); }} colSpan={2}/>
          <TitleSelect value={title} onChange={(v) => { setTitle(v); setError(""); }} colSpan={2}/>
          <EditReadOnly label="Vault Handle" value={previewId || "\u2014"} colSpan={2}
                        hint="Generated from the name. The Harvey account and email are created separately."/>
          <EditDate label="In Role Since" value={effDate} onChange={setEffDate} colSpan={2}
                    hint="Start date in this title. Leave blank if it is not known."/>
          <EditSelect
            label="Supervisor (drives team & sub-team)"
            value={supId}
            onChange={(v) => { setSupId(v); setSubteam(""); setError(""); }}
            colSpan={2}
            options={[{value: "", label: "— pick a supervisor —"}, ...allPeople.map(x => ({value: x.id, label: `${x.name} · ${x.title || x.role}`}))]}
          />
          <div style={{ gridColumn: "span 2" }}>
            <div className="micro" style={{ marginBottom: 4 }}>Module · Sub-Team</div>
            <div style={{ padding: "7px 10px", fontSize: "var(--t-body-lg)", border: "1px solid var(--line)", borderRadius: "var(--r-ctl)", background: "var(--bg, var(--surface-2))", color: "var(--muted, var(--ink-3))" }}>
              {(() => {
                if (!supId) return "— pick a supervisor —";
                const branchIds = new Set((F_org.ORG_TREE.branches || []).map(b => b.id));
                const bId = branchIds.has(supId) ? supId : findBranchOwner(supId);
                const head = bId ? F_org.PEOPLE_BY_ID[bId] : null;
                const mod = head ? (head.last || head.name.split(" ").pop()) + " Module" : "New branch";
                const sup = F_org.PEOPLE_BY_ID[supId];
                const sub = branchIds.has(supId) ? "MD direct" : (sup ? (sup.last || sup.name.split(" ").pop()) : "—");
                return mod + " · " + sub;
              })()}
            </div>
            <div className="micro" style={{ marginTop: 3 }}>Derived from supervisor.</div>
          </div>
          {error && (
            <div style={{
              gridColumn: "span 2",
              padding: "8px 10px",
              fontSize: "var(--t-body)",
              background: "color-mix(in oklab, var(--accent) 12%, transparent)",
              color: "var(--accent)",
              border: "1px solid color-mix(in oklab, var(--accent) 30%, transparent)",
              borderRadius: "var(--r-ctl)",
            }}>{error}</div>
          )}
        </div>
        <div className="row" style={{ padding: "12px 18px", borderTop: "1px solid var(--line)", gap: 8 }}>
          <div className="muted small" style={{ flex: 1 }}>
            Bucketed role: <strong>{title ? bucketRole(title) : "—"}</strong>
          </div>
          <button className="btn sm" onClick={onClose}>Cancel</button>
          <button className="btn primary sm" onClick={save}>Add to org</button>
        </div>
      </div>
    </div>
  );
}
  window.OrgEditPersonModal = EditPersonModal;
  window.OrgAddPersonModal  = AddPersonModal;
})();
