/* WAREHOUSE 2.0 · 權限(Folio 13 · ACCESS)— Swiss 版式,真後端
   讀視圖:成員清單(/api/users,退回 boot.PEOPLE)· 註冊/加入審批(/api/auth/registrations、/api/memberships/pending)
   · 角色×等級矩陣(topology/users/boot.ROLES)· 權限分享(/api/permissions/topology)
   一切改動交秘書:審批、調角色、調職級、重置密碼、停用/啟用/刪除、撤回分享 */
(() => {
const W2 = window.W2;
const { t } = window.W2_LANG;
window.W2_LANG.addEN({
  "權限": "Access",
  "成員 · 角色 · 審批 · 權限分享 —— 頁面只讀,改動交秘書執行": "Members · roles · approvals · delegation — read-only console, all changes go through the Secretary",
  "新增成員": "Add member",
  "問秘書": "Ask Secretary",
  "成員帳號": "Member accounts",
  "人": "", "筆": "", "個": "",
  "啟用 {a} · 停用 {b}": "{a} active · {b} disabled",
  "待審批申請": "Pending approvals",
  "讓秘書逐條審批 →": "Secretary: review all →",
  "全部處理完畢": "All clear",
  "角色": "Roles",
  "最高等級 L{l}": "Top level L{l}",
  "尚未定義角色": "No roles defined",
  "權限分享": "Delegations",
  "{n} 項核心權限不可分享": "{n} core permissions locked",
  "審計標記來源": "Audit-tagged source",
  "註冊 / 加入審批": "Registrations & joins",
  "審批通過即建帳號 · 全程留痕": "Approval creates the account · fully audited",
  "待審批": "Pending", "已通過": "Approved", "已駁回": "Rejected",
  "加入申請": "Join request", "註冊申請": "Registration",
  "期望角色": "Requested role",
  "(未指定)": "(unspecified)",
  "通過": "Approve", "駁回": "Reject",
  "載入中…": "Loading…",
  "暫無待審批申請": "No pending requests",
  "暫無已通過申請": "No approved requests",
  "暫無已駁回申請": "No rejected requests",
  "新申請會第一時間出現在這裡;也可以讓秘書代發邀請。": "New requests appear here instantly; the Secretary can also send invites.",
  "審批人 {r}": "Reviewer {r}",
  "角色 {r}": "Role {r}",
  "備註 {r}": "Note {r}",
  "成員清單": "Members",
  "{n} 個帳號 · 動作全部交秘書": "{n} accounts · all actions via Secretary",
  "{n} 位成員(花名冊視圖)· 動作全部交秘書": "{n} members (roster view) · all actions via Secretary",
  "搜索姓名 / 帳號 / 角色": "Search name / username / role",
  "全部": "All", "啟用": "Active", "停用": "Disabled",
  "成員": "Member", "帳號": "Username", "職級": "Level", "狀態": "Status", "交給秘書": "Via Secretary",
  "(無角色)": "(no role)",
  "調角色": "Change role", "重置密碼": "Reset password",
  "停用帳號": "Disable account", "啟用帳號": "Enable account", "刪除帳號": "Delete account",
  "調整角色": "Change role", "調整職級": "Set level",
  "當前篩選下沒有成員": "No members match the filter",
  "還沒有成員數據": "No member data yet",
  "對秘書說「幫我建立帳號」即可開始。": "Tell the Secretary \"create an account\" to get started.",
  "角色 × 等級矩陣": "Role × level matrix",
  "等級越高授權越大 · 職級不提升角色權限": "Higher level, broader authority · level never elevates permissions",
  "成員數": "Members", "權限數": "Perms",
  "還沒有角色數據": "No role data yet",
  "對秘書說「幫我規劃角色體系」即可開始。": "Tell the Secretary \"design our role system\" to get started.",
  "分享記錄": "Delegation log",
  "只能分享自己持有且非核心的權限": "Only own, non-core permissions can be shared",
  "撤回": "Revoke",
  "至 {d}": "until {d}",
  "暫無有效分享": "No active delegations",
  "權限分享會列在這裡,審計日誌會標記委託來源。": "Delegations show up here; audit logs tag the delegated source.",
  "花名冊": "Roster",
  "建立時間": "Created",
  "職位": "Title",
  "直接吩咐秘書": "Tell the Secretary",
  "2.0 約定:頁面只讀,改動經秘書確認執行,全程留痕。": "2.0 rule: this page is read-only. Changes run through the Secretary with a full audit trail.",
  /* 秘書指令 */
  "權限與帳號現在有什麼需要處理的?有沒有待審批的申請?": "Anything to handle on access & accounts? Any approvals waiting?",
  "我要新增或批量導入成員帳號,請追問名單(姓名、帳號、角色)後建立": "I want to add or bulk-import member accounts. Ask me for the list (name, username, role), then create them.",
  "把待審批的註冊與加入申請逐條給我,建議每條的角色分配,經我確認後執行審批": "Walk me through every pending registration and join request, suggest a role for each, and approve after my confirmation.",
  "審批通過「{u}」的加入申請(#{id}),期望角色「{r}」;請確認分配角色後執行": "Approve join request #{id} from {u}, requested role {r}; confirm the assigned role with me, then execute.",
  "駁回「{u}」的加入申請(#{id}),請追問駁回理由後執行": "Reject join request #{id} from {u}; ask me for the reason, then execute.",
  "審批通過「{u}」的註冊申請(#{id}),期望角色「{r}」;請確認分配角色後執行並建立帳號": "Approve registration #{id} from {u}, requested role {r}; confirm the role with me, then create the account.",
  "駁回「{u}」的註冊申請(#{id}),請追問駁回理由後執行": "Reject registration #{id} from {u}; ask me for the reason, then execute.",
  "調整成員「{u}」的角色(當前:{r}),請列出可選角色,經我確認後執行": "Change the role of member {u} (current: {r}); list the available roles and execute after my confirmation.",
  "調整「{u}」的拓撲職級(當前 L{l}),請追問目標職級與職位標籤後執行": "Adjust the topology level of {u} (now L{l}); ask me for the target level and title, then execute.",
  "重置「{u}」的登入密碼,生成臨時密碼;重置後提醒我線下告知本人": "Reset the password of {u} and generate a temporary one; remind me to hand it over offline.",
  "停用帳號「{u}」,停用後登入立即失效;請與我確認後執行": "Disable the account {u} (sign-in is cut immediately); confirm with me before executing.",
  "啟用帳號「{u}」,請與我確認後執行": "Enable the account {u}; confirm with me before executing.",
  "刪除帳號「{u}」,此操作不可恢復;請再次與我確認後執行": "Delete the account {u} — irreversible; double-confirm with me before executing.",
  "把角色「{r}」(L{l})的權限清單和成員給我,並指出風險點": "Show me the permission list and members of role {r} (L{l}), and flag any risks.",
  "撤回權限分享:{g} 分享給 {e} 的「{p}」;請與我確認後執行": "Revoke the delegation of {p} from {g} to {e}; confirm with me before executing.",
});
const { useState: _s, useEffect: _e } = React;
const { Icon: I, Btn: B, Tag: T, Label: LB, Empty: EM, Kpi, Folio, Band, pad2, num } = W2;
const ask = (p) => W2.openSecretary(p);

const uref = (name, username) => (name || username || "—") + (username ? " @" + username : "");
const REG_TABS = [["pending", "待審批"], ["approved", "已通過"], ["rejected", "已駁回"]];
const REG_EMPTY = { pending: "暫無待審批申請", approved: "暫無已通過申請", rejected: "暫無已駁回申請" };

/* ── 成員詳情抽屜 ── */
const MemberDrawer = ({ m, onClose }) => {
  const u = uref(m.name, m.username);
  const roleStr = m.roles.length ? m.roles.join("、") : t("(無角色)");
  const acts = [
    ["swap", "調整角色", t("調整成員「{u}」的角色(當前:{r}),請列出可選角色,經我確認後執行", { u, r: roleStr })],
    ["layers", "調整職級", t("調整「{u}」的拓撲職級(當前 L{l}),請追問目標職級與職位標籤後執行", { u, l: m.level })],
    ["shield", "重置密碼", t("重置「{u}」的登入密碼,生成臨時密碼;重置後提醒我線下告知本人", { u })],
    m.active
      ? ["x", "停用帳號", t("停用帳號「{u}」,停用後登入立即失效;請與我確認後執行", { u })]
      : ["check", "啟用帳號", t("啟用帳號「{u}」,請與我確認後執行", { u })],
  ];
  return (
    <div className="drawer">
      <div style={{ padding: "16px 18px", borderBottom: "2px solid var(--rule)" }}>
        <div className="row spread" style={{ marginBottom: 10 }}>
          {m.active ? <T tone="ok" dot>{t("啟用")}</T> : <T tone="plain">{t("停用")}</T>}
          <button className="btn ghost sm" style={{ padding: "0 7px" }} onClick={onClose} title="Esc"><I name="x" size={13}/></button>
        </div>
        <div style={{ fontSize: 19, fontWeight: 750, letterSpacing: "-.025em", lineHeight: 1.25 }}>{m.name}</div>
        <div className="num muted" style={{ fontSize: 11.5, marginTop: 5 }}>{m.username ? "@" + m.username : t("花名冊")}</div>
      </div>
      <div style={{ padding: 18, maxHeight: "calc(100vh - 280px)", overflowY: "auto" }}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14, marginBottom: 18 }}>
          {[[t("角色"), roleStr], [t("職級"), "L" + (m.level || 1) + (m.title ? " · " + m.title : "")], [t("狀態"), m.active ? t("啟用") : t("停用")], [t("建立時間"), m.created || "—"]].map(([k, v]) => (
            <div key={k} className="col g4" style={{ borderTop: "1px solid var(--hair)", paddingTop: 8 }}>
              <LB dim style={{ fontSize: 8.5 }}>{k}</LB>
              <span style={{ fontSize: 13.5, fontWeight: 650, lineHeight: 1.4 }}>{v}</span>
            </div>
          ))}
        </div>
        <LB dim style={{ fontSize: 8.5, marginBottom: 8 }}>{t("直接吩咐秘書")}</LB>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
          {acts.map(([icon, label, prompt]) => (
            <button key={label} className="btn" style={{ height: 40, justifyContent: "flex-start", fontSize: 12.5 }} onClick={() => ask(prompt)}>
              <I name={icon} size={14}/>{t(label)}
            </button>
          ))}
        </div>
        <button className="btn red" style={{ width: "100%", marginTop: 8, height: 38, fontSize: 12.5 }}
          onClick={() => ask(t("刪除帳號「{u}」,此操作不可恢復;請再次與我確認後執行", { u }))}>
          <I name="alert" size={13}/>{t("刪除帳號")}
        </button>
        <div className="muted" style={{ fontSize: 10.5, marginTop: 12, lineHeight: 1.6 }}>{t("2.0 約定:頁面只讀,改動經秘書確認執行,全程留痕。")}</div>
      </div>
    </div>
  );
};

const Page = ({ boot, reload }) => {
  const [usersData, setUsersData] = _s(null);   // /api/users → {users, roles}
  const [members, setMembers] = _s(null);       // /api/memberships/pending → {requests, pending_count}
  const [topo, setTopo] = _s(null);             // /api/permissions/topology
  const [regStatus, setRegStatus] = _s("pending");
  const [regs, setRegs] = _s(null);             // /api/auth/registrations?status=…
  const [q, setQ] = _s("");
  const [scope, setScope] = _s("all");
  const [sel, setSel] = _s(null);

  const safe = (d) => (d && typeof d === "object" && !Array.isArray(d)) ? d : {};
  const loadBase = () => {
    W2.json("/api/users").then(d => setUsersData(safe(d))).catch(() => setUsersData({}));
    W2.json("/api/memberships/pending").then(d => setMembers(safe(d))).catch(() => setMembers({}));
    W2.json("/api/permissions/topology").then(d => setTopo(safe(d))).catch(() => setTopo({}));
  };
  const loadRegs = (status) => {
    setRegs(null);
    W2.json("/api/auth/registrations?status=" + encodeURIComponent(status)).then(d => setRegs(safe(d))).catch(() => setRegs({}));
  };
  _e(() => { loadBase(); loadRegs("pending"); }, []);
  _e(() => {
    const h = (e) => { if (e.key === "Escape") setSel(null); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, []);

  /* ── 成員行規整:/api/users 優先,退回 boot.PEOPLE 花名冊 ── */
  const apiUsers = (usersData && Array.isArray(usersData.users)) ? usersData.users : [];
  const bootPeople = Array.isArray(boot.PEOPLE) ? boot.PEOPLE : [];
  const managed = apiUsers.length > 0;
  const rowsAll = (managed
    ? apiUsers.map((u) => ({
        key: "u" + (u.id != null ? u.id : u.username),
        name: u.display_name || u.username || "—",
        username: u.username || "",
        roles: Array.isArray(u.roles) ? u.roles.map(r => r && r.role_name).filter(Boolean)
          : (Array.isArray(u.role_names) ? u.role_names : []),
        level: Math.max(1, Math.min(10, num(u.topology_level) || num(u.role_level) || 1)),
        title: u.topology_title || "",
        active: !(u.active === 0 || u.active === false),
        created: u.created_at || "",
      }))
    : bootPeople.map((p, i) => ({
        key: "p" + (p.id != null ? p.id : i),
        name: p.name || "—",
        username: "",
        roles: (p.role && p.role !== "—") ? [p.role] : [],
        level: Math.max(1, Math.min(10, num(p.level) || 1)),
        title: (p.dept && p.dept !== "—") ? p.dept : "",
        active: p.online !== undefined ? !!p.online : true,
        created: "",
      }))
  ).sort((a, b) => (b.level - a.level) || String(a.name).localeCompare(String(b.name), "zh"));

  let rows = rowsAll;
  if (scope === "on") rows = rows.filter(r => r.active);
  if (scope === "off") rows = rows.filter(r => !r.active);
  if (q) { const k = q.toLowerCase(); rows = rows.filter(r => (r.name + " " + r.username + " " + r.roles.join(" ") + " " + r.title).toLowerCase().includes(k)); }

  /* ── 角色規整:topology → /api/users.roles → boot.ROLES ── */
  const topoRoles = (topo && Array.isArray(topo.roles)) ? topo.roles : [];
  const apiRoles = (usersData && Array.isArray(usersData.roles)) ? usersData.roles : [];
  const bootRoles = Array.isArray(boot.ROLES) ? boot.ROLES : [];
  const rolesRaw = topoRoles.length ? topoRoles : (apiRoles.length ? apiRoles : bootRoles);
  const roles = rolesRaw.map((r) => {
    const name = (r && (r.role_name || r.name)) || "—";
    const fromRows = rowsAll.filter(m => m.roles.indexOf(name) >= 0).length;
    return {
      name,
      level: Math.max(1, Math.min(10, num(r && r.level) || 1)),
      perms: num(r && r.permission_count) || (Array.isArray(r && r.permissions) ? r.permissions.length : (Array.isArray(r && r.perms) ? r.perms.length : 0)),
      members: num(r && r.user_count) || fromRows,
    };
  }).sort((a, b) => (b.level - a.level) || String(a.name).localeCompare(String(b.name), "zh"));
  const topLevel = roles.length ? roles[0].level : 0;
  const LMAX = Math.min(10, Math.max(5, topLevel));
  const levels = []; for (let l = 1; l <= LMAX; l++) levels.push(l);
  const gridCols = `minmax(120px, 1.3fr) repeat(${LMAX}, minmax(22px, 1fr)) 56px 56px`;

  /* ── 審批與分享 ── */
  const memList = (members && Array.isArray(members.requests)) ? members.requests : [];
  const regList = (regs && Array.isArray(regs.requests)) ? regs.requests : [];
  const pendingTotal = num(members ? members.pending_count : 0) + num(regs ? regs.pending_count : 0);
  const dels = (topo && Array.isArray(topo.delegations)) ? topo.delegations : [];
  const delCount = (topo && topo.summary && topo.summary.delegations != null) ? num(topo.summary.delegations) : dels.length;
  const protectedCount = (topo && Array.isArray(topo.protected_permissions)) ? topo.protected_permissions.length : 0;
  const onCount = rowsAll.filter(r => r.active).length;
  const regLoading = regs === null;

  /* ── 審批行 ── */
  const pendRow = (r, i, kind) => {
    const id = r.id != null ? r.id : "—";
    const u = uref(r.display_name, r.username);
    const role = r.requested_role_name || t("(未指定)");
    const meta = [r.department, r.contact, r.reason].filter(v => v && v !== "—").join(" · ");
    const approve = kind === "mem"
      ? t("審批通過「{u}」的加入申請(#{id}),期望角色「{r}」;請確認分配角色後執行", { u, id, r: role })
      : t("審批通過「{u}」的註冊申請(#{id}),期望角色「{r}」;請確認分配角色後執行並建立帳號", { u, id, r: role });
    const reject = kind === "mem"
      ? t("駁回「{u}」的加入申請(#{id}),請追問駁回理由後執行", { u, id })
      : t("駁回「{u}」的註冊申請(#{id}),請追問駁回理由後執行", { u, id });
    return (
      <div key={kind + id + ":" + i} className="ledger-row">
        <span className="lr-idx">{pad2(i + 1)}</span>
        <div className="col g4" style={{ flex: 1.6, minWidth: 0 }}>
          <span className="row g8 wrap" style={{ fontWeight: 650, fontSize: 13.5 }}>
            {r.display_name || r.username || "—"}
            {r.username && <span className="num muted" style={{ fontSize: 11, fontWeight: 400 }}>@{r.username}</span>}
            <T tone={kind === "mem" ? "inv" : "plain"}>{t(kind === "mem" ? "加入申請" : "註冊申請")}</T>
          </span>
          <span className="muted" style={{ fontSize: 11.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={meta}>
            {meta || "—"}{r.created_at ? " · " + r.created_at : ""}
          </span>
        </div>
        <div className="col g4" style={{ width: 132, flexShrink: 0 }}>
          <LB dim style={{ fontSize: 8 }}>{t("期望角色")}</LB>
          <span style={{ fontSize: 12.5, fontWeight: 600 }}>{role}</span>
        </div>
        <div className="row g6">
          <B size="sm" icon="check" onClick={() => ask(approve)}>{t("通過")}</B>
          <B size="sm" kind="red" onClick={() => ask(reject)}>{t("駁回")}</B>
        </div>
      </div>
    );
  };
  const doneRow = (r, i) => {
    const tone = r.status === "approved" ? "ok" : "plain";
    const meta = [
      r.reviewer_name ? t("審批人 {r}", { r: r.reviewer_name }) : "",
      r.assigned_role_name ? t("角色 {r}", { r: r.assigned_role_name }) : "",
      r.review_note ? t("備註 {r}", { r: r.review_note }) : "",
      r.reviewed_at || "",
    ].filter(Boolean).join(" · ");
    return (
      <div key={"d" + (r.id != null ? r.id : i)} className="ledger-row">
        <span className="lr-idx">{pad2(i + 1)}</span>
        <div className="col g4" style={{ flex: 1, minWidth: 0 }}>
          <span className="row g8" style={{ fontWeight: 650, fontSize: 13.5 }}>
            {r.display_name || r.username || "—"}
            {r.username && <span className="num muted" style={{ fontSize: 11, fontWeight: 400 }}>@{r.username}</span>}
          </span>
          <span className="muted" style={{ fontSize: 11.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={meta}>{meta || "—"}</span>
        </div>
        <T tone={tone} dot={r.status === "approved"}>{t(r.status === "approved" ? "已通過" : "已駁回")}</T>
      </div>
    );
  };

  return (
    <>
      <Folio no="13" en="ACCESS" title={t("權限")}
        sub={t("成員 · 角色 · 審批 · 權限分享 —— 頁面只讀,改動交秘書執行")}
        right={<>
          <B icon="refresh" onClick={() => { loadBase(); loadRegs(regStatus); reload && reload(); }}>{t("刷新")}</B>
          <B icon="plus" onClick={() => ask(t("我要新增或批量導入成員帳號,請追問名單(姓名、帳號、角色)後建立"))}>{t("新增成員")}</B>
          <B kind="primary" icon="sparkle" onClick={() => ask(t("權限與帳號現在有什麼需要處理的?有沒有待審批的申請?"))}>{t("問秘書")}</B>
        </>}/>

      <div className="kpi-band">
        <Kpi label={t("成員帳號")} value={rowsAll.length} unit={t("人")} delay={0}
          foot={<><span className="muted" style={{ fontSize: 11.5 }}>{t("啟用 {a} · 停用 {b}", { a: onCount, b: rowsAll.length - onCount })}</span><T tone="plain">{managed ? "MANAGED" : "ROSTER"}</T></>}/>
        <Kpi label={t("待審批申請")} value={pendingTotal} unit={t("筆")} red={pendingTotal > 0} delay={.05}
          foot={pendingTotal
            ? <button className="tag redinv" style={{ cursor: "pointer" }} onClick={() => ask(t("把待審批的註冊與加入申請逐條給我,建議每條的角色分配,經我確認後執行審批"))}>{t("讓秘書逐條審批 →")}</button>
            : <T tone="ok" dot>{t("全部處理完畢")}</T>}/>
        <Kpi label={t("角色")} value={roles.length} unit={t("個")} delay={.1}
          foot={<span className="muted" style={{ fontSize: 11.5 }}>{roles.length ? t("最高等級 L{l}", { l: topLevel }) : t("尚未定義角色")}</span>}/>
        <Kpi label={t("權限分享")} value={delCount} unit={t("筆")} delay={.15}
          foot={<span className="muted" style={{ fontSize: 11.5 }}>{protectedCount ? t("{n} 項核心權限不可分享", { n: protectedCount }) : t("審計標記來源")}</span>}/>
      </div>

      {/* A · 註冊 / 加入審批 */}
      <Band no="A" title={t("註冊 / 加入審批")} sub={t("審批通過即建帳號 · 全程留痕")} delay={.1}
        right={<div className="seg">
          {REG_TABS.map(([id, label]) => (
            <button key={id} className={regStatus === id ? "on" : ""} onClick={() => { setRegStatus(id); loadRegs(id); }}>{t(label)}</button>
          ))}
        </div>}>
        {regLoading && <div className="muted" style={{ fontSize: 12.5, padding: "14px 4px" }}>{t("載入中…")}</div>}
        {!regLoading && regStatus === "pending" && (memList.length || regList.length ? (
          <div style={{ borderTop: "2px solid var(--rule)" }}>
            {memList.map((m, i) => pendRow(m, i, "mem"))}
            {regList.map((r, i) => pendRow(r, memList.length + i, "reg"))}
          </div>
        ) : <EM icon="clipboard" title={t(REG_EMPTY.pending)} sub={t("新申請會第一時間出現在這裡;也可以讓秘書代發邀請。")}
              action={<B size="sm" icon="sparkle" onClick={() => ask(t("我要新增或批量導入成員帳號,請追問名單(姓名、帳號、角色)後建立"))}>{t("新增成員")}</B>}/>)}
        {!regLoading && regStatus !== "pending" && (regList.length ? (
          <div style={{ borderTop: "2px solid var(--rule)" }}>{regList.map(doneRow)}</div>
        ) : <EM icon="doc" title={t(REG_EMPTY[regStatus] || REG_EMPTY.pending)}/>)}
      </Band>

      {/* B · 成員清單 + 抽屜 */}
      <Band no="B" title={t("成員清單")}
        sub={managed ? t("{n} 個帳號 · 動作全部交秘書", { n: rowsAll.length }) : t("{n} 位成員(花名冊視圖)· 動作全部交秘書", { n: rowsAll.length })}
        delay={.15}
        right={<div className="row g12 wrap">
          <div style={{ position: "relative", width: 220 }}>
            <I name="search" size={14} color="var(--ink-4)" style={{ position: "absolute", left: 0, top: "50%", transform: "translateY(-50%)" }}/>
            <input className="field" style={{ paddingLeft: 22, height: 32, fontSize: 12.5 }} value={q} onChange={e => setQ(e.target.value)} placeholder={t("搜索姓名 / 帳號 / 角色")}/>
          </div>
          <div className="seg">
            {[["all", "全部"], ["on", "啟用"], ["off", "停用"]].map(([id, label]) => (
              <button key={id} className={scope === id ? "on" : ""} onClick={() => setScope(id)}>{t(label)}</button>
            ))}
          </div>
        </div>}>
        <div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ overflowX: "auto" }}>
              <table className="tbl2">
                <thead><tr>
                  <th style={{ width: 34 }}>#</th><th>{t("成員")}</th><th>{t("帳號")}</th><th>{t("角色")}</th><th>{t("職級")}</th><th>{t("狀態")}</th><th style={{ width: 118 }}>{t("交給秘書")}</th>
                </tr></thead>
                <tbody>
                  {rows.map((m, i) => {
                    const u = uref(m.name, m.username);
                    return (
                      <tr key={m.key} className={sel && sel.key === m.key ? "on" : ""} onClick={() => setSel(m)} style={{ cursor: "pointer" }}>
                        <td className="num muted" style={{ fontSize: 11 }}>{pad2(i + 1)}</td>
                        <td>
                          <div className="col g2">
                            <span style={{ fontWeight: 650 }}>{m.name}</span>
                            {m.title && <span className="muted" style={{ fontSize: 11 }}>{m.title}</span>}
                          </div>
                        </td>
                        <td className="num muted">{m.username ? "@" + m.username : "—"}</td>
                        <td>
                          <div className="row g6 wrap">
                            {m.roles.length ? m.roles.map(r => <T key={r} tone="plain">{r}</T>) : <span className="muted" style={{ fontSize: 12 }}>{t("(無角色)")}</span>}
                          </div>
                        </td>
                        <td><span className="num" style={{ fontWeight: 700 }}>L{m.level}</span></td>
                        <td>{m.active ? <T tone="ok" dot>{t("啟用")}</T> : <T tone="plain">{t("停用")}</T>}</td>
                        <td onClick={e => e.stopPropagation()}>
                          <div className="row g4">
                            <button className="btn sm" title={t("調角色")} style={{ padding: "0 8px" }}
                              onClick={() => ask(t("調整成員「{u}」的角色(當前:{r}),請列出可選角色,經我確認後執行", { u, r: m.roles.join("、") || t("(無角色)") }))}><I name="swap" size={12}/></button>
                            <button className="btn sm" title={t("重置密碼")} style={{ padding: "0 8px" }}
                              onClick={() => ask(t("重置「{u}」的登入密碼,生成臨時密碼;重置後提醒我線下告知本人", { u }))}><I name="shield" size={12}/></button>
                            <button className="btn sm" title={m.active ? t("停用帳號") : t("啟用帳號")} style={{ padding: "0 8px" }}
                              onClick={() => ask(m.active
                                ? t("停用帳號「{u}」,停用後登入立即失效;請與我確認後執行", { u })
                                : t("啟用帳號「{u}」,請與我確認後執行", { u }))}><I name={m.active ? "x" : "check"} size={12}/></button>
                          </div>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
            {!rows.length && (rowsAll.length
              ? <EM icon="search" title={t("當前篩選下沒有成員")}/>
              : <EM icon="user" title={t("還沒有成員數據")} sub={t("對秘書說「幫我建立帳號」即可開始。")}
                  action={<B size="sm" icon="sparkle" onClick={() => ask(t("我要新增或批量導入成員帳號,請追問名單(姓名、帳號、角色)後建立"))}>{t("新增成員")}</B>}/>)}
          </div>
          {sel && <MemberDrawer m={sel} onClose={() => setSel(null)}/>}
        </div>
      </Band>

      {/* C+D · 角色×等級矩陣 / 權限分享 */}
      <div style={{ display: "grid", gridTemplateColumns: "3fr 2fr", gap: 0 }}>
        <div style={{ paddingRight: 28, minWidth: 0 }}>
        <Band no="C" title={t("角色 × 等級矩陣")} sub={t("等級越高授權越大 · 職級不提升角色權限")} delay={.2}>
          {roles.length ? (
            <div style={{ borderTop: "2px solid var(--rule)" }}>
              <div style={{ display: "grid", gridTemplateColumns: gridCols, alignItems: "center", padding: "8px 0", borderBottom: "1px solid var(--hair)" }}>
                <LB dim style={{ fontSize: 8.5 }}>{t("角色")}</LB>
                {levels.map(l => <span key={l} className="mono" style={{ fontSize: 9, color: "var(--ink-3)", textAlign: "center" }}>L{l}</span>)}
                <LB dim style={{ fontSize: 8.5, textAlign: "right" }}>{t("成員數")}</LB>
                <LB dim style={{ fontSize: 8.5, textAlign: "right" }}>{t("權限數")}</LB>
              </div>
              {roles.map((r, i) => (
                <div key={r.name + ":" + i}
                  style={{ display: "grid", gridTemplateColumns: gridCols, alignItems: "center", padding: "11px 0", borderBottom: "1px solid var(--hair-soft)", cursor: "pointer" }}
                  title={t("把角色「{r}」(L{l})的權限清單和成員給我,並指出風險點", { r: r.name, l: r.level })}
                  onClick={() => ask(t("把角色「{r}」(L{l})的權限清單和成員給我,並指出風險點", { r: r.name, l: r.level }))}>
                  <span className="row g6" style={{ fontWeight: 650, fontSize: 12.5, minWidth: 0 }}>
                    <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.name}</span>
                  </span>
                  {levels.map(l => (
                    <span key={l} style={{
                      height: 13, margin: "0 2px", display: "block",
                      background: l < r.level ? "var(--paper-2)" : l === r.level ? (r.level === topLevel ? "var(--red)" : "var(--ink)") : "transparent",
                      border: "1px solid " + (l <= r.level ? "var(--hair)" : "var(--hair-soft)"),
                    }}/>
                  ))}
                  <span className="num" style={{ fontWeight: 700, textAlign: "right", fontSize: 13 }}>{r.members}</span>
                  <span className="num muted" style={{ textAlign: "right", fontSize: 12 }}>{r.perms || "—"}</span>
                </div>
              ))}
            </div>
          ) : <EM icon="layers" title={t("還沒有角色數據")} sub={t("對秘書說「幫我規劃角色體系」即可開始。")}/>}
        </Band>
        </div>

        <div style={{ borderLeft: "1px solid var(--hair)", paddingLeft: 28, minWidth: 0 }}>
        <Band no="D" title={t("分享記錄")} sub={t("只能分享自己持有且非核心的權限")} delay={.25}>
          {dels.length ? (
            <div style={{ borderTop: "2px solid var(--rule)" }}>
              {dels.slice(0, 8).map((d, i) => {
                const g = d.grantor_name || d.grantor_username || "—";
                const e2 = d.grantee_name || d.grantee_username || "—";
                const p = d.permission_label || d.permission_key || "—";
                return (
                  <div key={d.id != null ? d.id : i} className="ledger-row">
                    <span className="lr-idx">{pad2(i + 1)}</span>
                    <div className="col g4" style={{ flex: 1, minWidth: 0 }}>
                      <span className="mono" style={{ fontSize: 12, fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p}</span>
                      <span className="muted" style={{ fontSize: 11.5 }}>
                        {g} → {e2}{d.expires_at ? " · " + t("至 {d}", { d: String(d.expires_at).slice(0, 10) }) : ""}
                      </span>
                    </div>
                    <B size="sm" onClick={() => ask(t("撤回權限分享:{g} 分享給 {e} 的「{p}」;請與我確認後執行", { g, e: e2, p: d.permission_key || p }))}>{t("撤回")}</B>
                  </div>
                );
              })}
            </div>
          ) : <EM icon="shield" title={t("暫無有效分享")} sub={t("權限分享會列在這裡,審計日誌會標記委託來源。")}/>}
        </Band>
        </div>
      </div>
    </>
  );
};

window.W2.PAGES["perms"] = Page;
})();
