/* ==========================================================================
 * ADMIN NEXT — layout.jsx  (แยกจาก index.html เดิม; โหลดผ่าน <script type="text/babel">)
 * โหลดตามลำดับใน index.html — ทุกไฟล์แชร์ scope เดียวกัน (babel-standalone)
 * ========================================================================== */

/* ---------- เมนู (โครง+ลำดับ+กลุ่ม ตามแอดมินเดิม) ---------- */
const NAV = [
  { g:"ภาพรวม", items:[
    { label:"หน้าหลัก", icon:"home", col:"#6366F1", page:"home", perm:"home" },
    { label:"แดชบอร์ด", icon:"grid", col:"#3B82F6", page:"overview", perm:"summary" },
    { label:"รายงาน", icon:"chart", col:"#8B5CF6", group:"reports", children:[
      ["กำไร/ขาดทุน","profitloss","tool_report_pl"],["โปรโมชั่น/โบนัส","promosummary","tool_report_promo"],["Leaderboard","leaderboard","tool_ranking"],["รายงานค่ายเกม","providerreports","tool_provider_reports"]] },
    { label:"ประวัติต่างๆ", icon:"clock", col:"#06B6D4", group:"histories", children:[
      ["ประวัติทั้งหมด","allhist","tool_history_all"],["ประวัติวงล้อ","wheelhist","tool_history_wheel"],["ประวัติชวนเพื่อน","referralhist","tool_history_referral"],["ประวัติคืนยอดเสีย","cashbackhist","tool_history_cashback"],["โปรโมชั่น/โบนัส","bonushist","tool_history_bonus"],["รายงานเครดิต","manualreport","tool_manual_report"],["ดู OTP Log","otplog","tool_otp_log"],["โปรฟรีรออนุมัติ","promoreq","tool_promo_requests"],["ประวัติทายผล","predhist","tool_history_prediction"]] },
  ]},
  { g:"ผู้ใช้งาน", items:[
    { label:"สมาชิก", icon:"users", col:"#F59E0B", group:"members", children:[
      ["สมาชิกทั้งหมด","members","users"],["ระดับสมาชิก","memberlevels","tool_member_levels"],["รายชื่อมิจฉาชีพ","fraud","tool_fraud_list"],["จัดการเครดิต","creditmanage","tool_manage_credit"],["20 อันดับฝากสูงสุด","topdep","tool_top_depositors"]] },
  ]},
  { g:"ปฏิบัติการ", items:[
    { label:"รายการเงิน", icon:"money", col:"#10B981", group:"finance", children:[
      ["รายการฝาก","deposits","deposits"],["รายการถอน","withdrawals","wdr"],["อนุมัติสลิป","slipapprove","tool_slip_approve"],["รายงาน Autopeer","autopeer","tool_autopeer"],["ฝากน่าสงสัย","suspicious","tool_suspicious_deposit"],["ประวัติ SMS","smshist","tool_sms_history"],["Payment","payment","payment"]] },
    { label:"ธนาคาร", icon:"bank", col:"#2563EB", group:"banking", children:[
      ["บัญชีรับเงิน","bankreceive","bank-receive"],["บัญชีถอน","bankpayout","bank-payout"],["บัญชีกลาง","bankcentral","bank-central"],["จัดการช่องทางฝาก","depositchannels","tool_payment_manager"],["ฝาก USDT (คริปโต)","cryptodeposit",["tool_payment_manager","payment"]],["ABD Console","abdconsole","tool_abd_console"],["LINE Connect","lineconnect","tool_line_abd"]] },
    { label:"เกม", icon:"game", col:"#EC4899", group:"gameops", children:[
      ["ตั้งค่าค่ายเกม","gamemanager","game-manager"],["เรียงลำดับเกมหน้าเว็บ","gamesort","game-sort"],["รูปเมนูค่ายเกม","providerlogos","provider-logos"],["ค่ายเกมยอดนิยม","featuredproviders","tool_featured_providers"],["เกมยอดนิยม","featuredgames","tool_featured_games"]] },
    { label:"การตลาด", icon:"mega", col:"#F97316", group:"marketing", children:[
      ["รายงานสายการตลาด","mktreport","tool_mkt_report"],["จัดการสายการตลาด","mktorgs","tool_mkt_orgs"],["ช่องทางสมัคร","mktlinks","tool_mkt_links"],["ตั้งค่าพันธมิตร","affiliate","tool_affiliate"],["ประวัติพันธมิตร","affiliatehist","tool_affiliate"],["ทายผล","prediction","prediction"],["กงล้อ","wheel","tool_wheel"],["โปรโมชั่น","promo","promo"],["คูปอง","codes","codes"],["เช็คอิน","checkin","tool_checkin"],["คืนยอดเสีย","cashback","tool_cashback"]] },
  ]},
  { g:"ตั้งค่า", items:[
    { label:"ตั้งค่าระบบ", icon:"cog", col:"#14B8A6", group:"settings", children:[
      ["ตั้งค่าเว็บไซต์","sitesettings","settings"],["ตั้งค่าลิงก์ & เชื่อมโดเมน","domains","tool_domains"],["การแจ้งเตือน","notifications","tool_notifications"],["Popup / ช่องทางติดต่อ","popupcontact","tool_popup_contact"],["ผู้ดูแลระบบ","admins",["perm_edit_permissions","perm_change_admin_password"]],["KPI พนักงาน","staffkpi","tool_staff_kpi"],["Activity Logs","logs","logs"]] },
    { label:"จัดการ API", icon:"api", col:"#A855F7", group:"api_manage", children:[
      ["API Game","apigame",NX_API_GAME_PERMS],["API ฝาก","apideposit",NX_API_DEPOSIT_PERMS],["API ฝาก พิเศษ","apidepositspecial",NX_API_DEPOSIT_SPECIAL_PERMS],["API SMS-KUB","apisms","tool_sms_gateway"],["สุขภาพเซิร์ฟเวอร์","serverhealth","tool_server_health"],["White Label","whitelabel","tool_white_label"]] },
  ]},
];
function NavChip({ icon, col, active }) {
  return <span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg transition-colors" style={{background: active?"transparent":col+"1A", color: col}}><Duo name={icon} size={21}/></span>;
}
function Sidebar({ open, onClose, page, onNav, admin, onLogout }) {
  const [exp, setExp] = useState(() => new Set());
  useEffect(() => {
    for (const sec of NAV) for (const it of sec.items)
      if (it.children && it.children.some(c => c[1] === page)) setExp(s => new Set(s).add(it.group));
  }, [page]);
  const toggle = (g) => setExp(s => { const n = new Set(s); n.has(g) ? n.delete(g) : n.add(g); return n; });
  return (<>
    <div className={"fixed inset-0 z-30 bg-black/40 fade-in lg:hidden "+(open?"":"hidden")} onClick={onClose}/>
    <aside className={"fixed z-40 flex h-full w-64 shrink-0 flex-col border-r border-line bg-surface transition-transform lg:static lg:translate-x-0 "+(open?"translate-x-0":"-translate-x-full")}>
      <div className="flex h-16 items-center gap-2.5 px-5">
        <div className="relative grid h-9 w-9 shrink-0 place-items-center rounded-xl shadow-sm" style={{background:"linear-gradient(135deg,var(--accent) 0%,#8B5CF6 100%)",boxShadow:"0 4px 14px color-mix(in srgb,var(--accent) 38%,transparent)"}}>
          <svg viewBox="0 0 24 24" width="21" height="21" fill="none" stroke="#fff" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M16.5 5.5H8.5V18.5H16.5"/><path d="M8.5 12H14.5"/></svg>
          <span className="absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full ring-2 ring-surface" style={{background:"#22D3A5"}}/>
        </div>
        <span className="text-[19px] font-extrabold leading-none tracking-tight" style={{backgroundImage:"linear-gradient(95deg,var(--accent) 10%,#8B5CF6 85%)",WebkitBackgroundClip:"text",backgroundClip:"text",WebkitTextFillColor:"transparent",color:"transparent"}}>EGIX</span>
      </div>
      <nav className="flex-1 overflow-y-auto px-3 py-2">
        {NAV.map(sec=>{
          const items = sec.items.map(it=>{
            if (it.page) return can(it.perm) ? it : null;
            const kids = it.children.filter(c=>can(c[2]));
            return kids.length ? {...it, children:kids} : null;
          }).filter(Boolean);
          if (!items.length) return null;
          return (
          <div key={sec.g} className="mb-4">
            <div className="px-2.5 pb-1.5 text-[11px] font-semibold tracking-wider text-faint">{sec.g}</div>
            {items.map(it=>{
              if (it.page) { const active = it.page===page; return (
                <a key={it.label} href="#" onClick={(e)=>{e.preventDefault(); onNav(it.page,it.label);}} className={"mb-0.5 flex items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm font-medium transition-colors "+(active?"bg-accent-soft text-accent":"text-fg-2 hover:bg-sunken hover:text-fg")}>
                  <NavChip icon={it.icon} col={it.col} active={active}/><span>{it.label}</span>
                </a>); }
              const openG = exp.has(it.group);
              const hasActive = it.children.some(c=>c[1]===page);
              return (<div key={it.label} className="mb-0.5">
                <button type="button" onClick={()=>toggle(it.group)} className={"flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm font-medium transition-colors "+(hasActive&&!openG?"text-accent":"text-fg-2 hover:bg-sunken hover:text-fg")}>
                  <NavChip icon={it.icon} col={it.col} active={false}/><span>{it.label}</span>
                  <Ic d={I.chevD} size={15} cls={"ml-auto text-faint transition-transform "+(openG?"rotate-180":"")}/>
                </button>
                {openG && <div className="mb-1 mt-0.5 space-y-0.5 pl-4">
                  {it.children.map(([clabel,cpage])=>{ const cactive=cpage&&cpage===page; return (
                    <a key={clabel} href="#" onClick={(e)=>{e.preventDefault(); onNav(cpage,clabel);}} className={"group flex items-center gap-2 rounded-lg py-1.5 pl-4 pr-2 text-[13px] transition-colors "+(cactive?"bg-accent-soft font-medium text-accent":"text-muted hover:bg-sunken hover:text-fg")}>
                      <span className="h-1.5 w-1.5 shrink-0 rounded-full" style={{background: cactive?it.col:"var(--line)"}}/>
                      <span className="truncate">{clabel}</span>
                      {!cpage && <span className="ml-auto text-[9px] text-faint opacity-0 group-hover:opacity-100">เร็วๆนี้</span>}
                    </a>);})}
                </div>}
              </div>);
            })}
          </div>);
        })}
      </nav>
      <div className="border-t border-line p-3"><div className="flex items-center gap-3 rounded-lg p-1.5">
        <div className="grid h-9 w-9 place-items-center rounded-full text-xs font-semibold text-white" style={{background:avc(admin?.username||"A")}}>{initials(admin?.full_name||admin?.username||"A")}</div>
        <div className="min-w-0 flex-1"><div className="truncate text-sm font-medium">{admin?.full_name||admin?.username||"ผู้ดูแล"}</div><div className="truncate text-xs text-muted">{admin?.role||(admin?.isSuper?"ผู้ดูแลสูงสุด":"ผู้ดูแลระบบ")}</div></div>
        <button type="button" onClick={onLogout} title="ออกจากระบบ" aria-label="ออกจากระบบ" className="grid h-8 w-8 place-items-center rounded-lg text-muted hover:bg-sunken hover:text-fg"><Ic d={I.logout} size={17}/></button>
      </div></div>
    </aside>
  </>);
}

function ThemeToggle({ theme, setTheme }) {
  const opts=[["system","mon","ตามระบบ"],["light","sun","สว่าง"],["dark","moon","มืด"]];
  return (<div className="flex items-center rounded-lg border border-line bg-surface p-0.5">
    {opts.map(([k,ic,tt])=>(
      <button key={k} title={tt} aria-pressed={theme===k} onClick={()=>setTheme(k)} className={"grid h-7 w-7 place-items-center rounded-md transition-colors "+(theme===k?"bg-accent-soft text-accent":"text-faint hover:text-fg-2")}><Ic d={I[ic]} size={15}/></button>
    ))}
  </div>);
}
function _playNotifySound() {
  try {
    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return;
    const ctx = new AC(); ctx.resume?.();
    const now = ctx.currentTime;
    [880, 1175].forEach((freq, i) => {
      const osc = ctx.createOscillator(), gain = ctx.createGain();
      osc.type = "sine"; osc.frequency.value = freq;
      gain.gain.setValueAtTime(0.0001, now + i * 0.13);
      gain.gain.exponentialRampToValueAtTime(0.08, now + i * 0.13 + 0.015);
      gain.gain.exponentialRampToValueAtTime(0.0001, now + i * 0.13 + 0.11);
      osc.connect(gain); gain.connect(ctx.destination);
      osc.start(now + i * 0.13); osc.stop(now + i * 0.13 + 0.12);
    });
  } catch (_) {}
}
function _notifyTimeFmt(iso) {
  if (!iso) return "";
  const d = new Date(iso), now = new Date(), diff = (now - d) / 1000;
  if (diff < 60) return "เมื่อกี้";
  if (diff < 3600) return Math.floor(diff / 60) + " นาทีที่แล้ว";
  if (diff < 86400) return Math.floor(diff / 3600) + " ชม.ที่แล้ว";
  return d.toLocaleDateString("th-TH", { day: "2-digit", month: "short" }) + " " + d.toLocaleTimeString("th-TH", { hour: "2-digit", minute: "2-digit" });
}
const _FNOTI_DISMISSED_KEY = "nxadmin-fnoti-dismissed";
function _fnotiDismissedIds() { try { return new Set(JSON.parse(localStorage.getItem(_FNOTI_DISMISSED_KEY) || "[]")); } catch { return new Set(); } }
function _fnotiDismiss(ids) { const s = _fnotiDismissedIds(); ids.filter(Boolean).forEach(id => s.add(String(id))); try { localStorage.setItem(_FNOTI_DISMISSED_KEY, JSON.stringify([...s].slice(-500))); } catch {} }
const _chMap = { askmepay:"AskMePay", sms:"ฝากทศนิยม", line_auto:"ฝากผ่าน LINE", slip:"อัพโหลดสลิป", tmnone:"ฝาก TrueMoney (TMN)", truewallet:"ฝาก TrueMoney (Webhook)", truewallet_webhook:"ฝาก TrueMoney (Webhook)", truemoney:"ฝาก TrueMoney", qr:"QR Code", autopeer:"ฝากผ่าน AutoPeer", code:"คูปอง", angpao:"อังเปา", manual:"เติมเครดิตโดยแอดมิน", bank:"ธนาคาร", bank_auto:"ฝากเงินผ่าน SMS", smsgateway:"ฝากเงินผ่าน SMS", lineconnect:"ฝากผ่าน LINE", wallet:"วอลเล็ท", deposit:"ฝากเงิน", deposit_slip:"อัพโหลดสลิป", deposit_truemoney:"ฝาก TrueMoney (ซองของขวัญ)", deposit_tmnone:"ฝาก TrueMoney (TMN)", deposit_truewallet:"ฝาก TrueMoney (Webhook)", deposit_truewallet_transfer:"โอนจาก TrueMoney Wallet", deposit_line_abd:"ฝากผ่าน LINE", deposit_line_auto:"ฝากผ่าน LINE", deposit_smsgateway:"ฝากเงินผ่าน SMS ธนาคาร", deposit_askmepay:"ฝากผ่าน AskMePay", deposit_autopeer:"ฝากผ่าน AutoPeer", deposit_code:"คูปองเติมเงิน", deposit_admin_manual:"เติมเครดิตโดยแอดมิน", manual_deposit:"เติมเครดิตโดยแอดมิน" };
const _bankSlug = { "01002":"bbl","01004":"kbank","01006":"ktb","01014":"scb","01025":"bay","01022":"cimb","01030":"gsb","01034":"baac","01066":"isbt","01067":"tisco","01069":"kk","01070":"icbc","01071":"tcrb","01073":"lhb","01098":"ttb" };
const _bankShort = { "01002":"BBL","01004":"KBANK","01006":"KTB","01014":"SCB","01025":"BAY","01022":"CIMB","01030":"GSB","01034":"BAAC","01066":"ISBT","01067":"TISCO","01069":"KK","01070":"ICBC","01071":"TCRB","01073":"LH","01098":"TTB" };
function _chLabel(ch, bankCode) {
  const short = bankCode && _bankShort[bankCode];
  if ((ch === "bank_auto" || ch === "smsgateway" || ch === "sms") && short) return (ch === "sms" ? "ทศนิยม" : "ฝากเงิน") + "(" + short + ")";
  if (ch === "slip" && short) return "อัพสลิป(" + short + ")";
  return _chMap[ch] || ch || "—";
}
function _thaiOnly(name) { if (!name) return ""; return String(name).split(" - ")[0].trim(); }
function _stLabel(s) { return ({ completed:"สำเร็จ", paid:"จ่ายแล้ว", success:"สำเร็จ", pending:"รอตรวจ", approved:"อนุมัติ", rejected:"ปฏิเสธ", failed:"ล้มเหลว", cancelled:"ยกเลิก" }[s] || s || "—"); }
function _stColor(s) { return ({ completed:"var(--success)", paid:"var(--success)", success:"var(--success)", approved:"var(--accent)", pending:"var(--warn)", rejected:"var(--danger)", failed:"var(--danger)", cancelled:"var(--faint)" }[s] || "var(--muted)"); }
function _fnotiRefId(item) { const ref = item.refId || item.ref_id || ""; if (ref) return String(ref); const id = String(item.id || ""); return id.includes(":") ? id.split(":").slice(1).join(":") : id; }

function NotifyIcon({ item }) {
  const ch = String(item.channel || "").toLowerCase();
  const bankCode = item.bankCode || item.bank_code || "";
  const accType = item.accountType || item.account_type || "";
  const slug = bankCode && _bankSlug[bankCode];
  if (item.type === "withdraw") return <div className="mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-rose-500 text-white"><Ic d={I.wdr} size={16}/></div>;
  if (ch === "autopeer") return <img src="/admin/assets/autopeer-logo.png" className="mt-0.5 h-9 w-9 shrink-0 rounded-lg object-contain" onError={e => { e.target.style.display = "none"; }}/>;
  if (accType === "wallet") return <img src="/assets/banks/truewallet.png" className="mt-0.5 h-9 w-9 shrink-0 rounded-lg border border-line bg-white object-contain p-0.5" onError={e => { e.target.style.display = "none"; }}/>;
  if (slug) return <img src={"/assets/banks/" + slug + ".png"} className="mt-0.5 h-9 w-9 shrink-0 rounded-lg border border-line bg-white object-contain p-0.5" onError={e => { e.target.style.display = "none"; }}/>;
  return <div className="mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-emerald-500 text-white"><Ic d={I.dep} size={16}/></div>;
}

function FinanceNotifyBell({ onNav }) {
  const [open, setOpen] = useState(false);
  const [items, setItems] = useState([]);
  const [unread, setUnread] = useState(0);
  const [enabled, setEnabled] = useState(() => { try { return localStorage.getItem("nxadmin-fnoti") !== "0"; } catch { return true; } });
  const [soundOn, setSoundOn] = useState(() => { try { return localStorage.getItem("nxadmin-fnoti-sound") !== "0"; } catch { return true; } });
  const sseRef = useRef(null);
  const soundRef = useRef(soundOn);
  soundRef.current = soundOn;

  const addItem = useCallback((item, isNew) => {
    const dismissed = _fnotiDismissedIds();
    if (dismissed.has(String(item.id))) return;
    setItems(prev => {
      if (prev.some(x => x.id === item.id)) return prev;
      return [{ ...item, unread: isNew }, ...prev].slice(0, 50);
    });
    if (isNew) setUnread(u => u + 1);
  }, []);

  const load = useCallback(async () => {
    try {
      const rows = await api("/finance-notifications?limit=30");
      const dismissed = _fnotiDismissedIds();
      setItems(rows.filter(x => !dismissed.has(String(x.id))).map(x => ({ ...x, unread: false })));
      setUnread(0);
    } catch (_) {}
  }, []);

  const startSSE = useCallback(() => {
    if (sseRef.current) { sseRef.current.close(); sseRef.current = null; }
    if (typeof EventSource !== "function") return;
    const token = getToken();
    if (!token) return;
    const es = new EventSource("/api/admin/finance-events?token=" + encodeURIComponent(token));
    es.addEventListener("finance", (ev) => {
      try {
        const item = JSON.parse(ev.data || "{}");
        addItem(item, true);
        if (soundRef.current) _playNotifySound();
      } catch (_) {}
    });
    es.onerror = () => {};
    sseRef.current = es;
  }, [addItem]);

  useEffect(() => { load(); }, [load]);
  useEffect(() => {
    if (enabled) startSSE(); else if (sseRef.current) { sseRef.current.close(); sseRef.current = null; }
    return () => { if (sseRef.current) { sseRef.current.close(); sseRef.current = null; } };
  }, [enabled, startSSE]);

  const toggleEnabled = () => {
    const next = !enabled;
    setEnabled(next);
    try { localStorage.setItem("nxadmin-fnoti", next ? "1" : "0"); } catch {}
  };
  const toggleSound = () => {
    const next = !soundOn;
    setSoundOn(next);
    try { localStorage.setItem("nxadmin-fnoti-sound", next ? "1" : "0"); } catch {}
    if (next) _playNotifySound();
  };
  const markRead = () => { setUnread(0); setItems(prev => prev.map(x => ({ ...x, unread: false }))); };
  const clearAll = () => { _fnotiDismiss(items.map(x => x.id)); setItems([]); setUnread(0); };
  const openItem = (item) => {
    setItems(prev => prev.map(x => x.id === item.id ? { ...x, unread: false } : x));
    setUnread(u => item.unread ? Math.max(0, u - 1) : u);
    setOpen(false);
    if (onNav) onNav(item.type === "withdraw" ? "withdrawals" : "deposits", item.type === "withdraw" ? "รายการถอน" : "รายการฝาก");
  };

  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (!e.target.closest("[data-fnoti-root]")) setOpen(false); };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, [open]);

  const acctName = (item) => _thaiOnly(item.accountName || item.account_name || "");

  return (
    <div className="relative" data-fnoti-root>
      <button title={enabled ? "แจ้งเตือนฝากถอน" : "แจ้งเตือนปิดอยู่"} onClick={() => setOpen(o => !o)}
        className={"grid h-9 w-9 place-items-center rounded-lg border transition-colors " + (enabled ? "border-line bg-surface text-fg-2 hover:bg-sunken hover:text-fg" : "border-line bg-surface text-faint")}>
        <Ic d={I.bell}/>
        {unread > 0 && <span className="absolute -right-1 -top-1 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-danger px-1 text-[10px] font-bold text-white">{unread > 99 ? "99+" : unread}</span>}
      </button>
      {open && (
        <div className="absolute right-0 top-full z-50 mt-2 w-[380px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-line bg-surface shadow-xl">
          <div className="flex items-center justify-between border-b border-line px-4 py-2.5">
            <div><div className="text-sm font-semibold">กล่องแจ้งเตือน</div><div className="text-[11px] text-muted">รายการฝาก-ถอนล่าสุด</div></div>
            <div className="flex items-center gap-1">
              <button title={soundOn ? "ปิดเสียง" : "เปิดเสียง"} onClick={toggleSound}
                className={"grid h-7 w-7 place-items-center rounded-md text-xs transition-colors " + (soundOn ? "text-accent hover:bg-accent-soft" : "text-faint hover:bg-sunken")}>
                <Ic d={soundOn ? ["M11 5L6 9H2v6h4l5 4V5z","M19.1 4.9a10 10 0 0 1 0 14.2","M15.5 8.5a5 5 0 0 1 0 7"] : ["M11 5L6 9H2v6h4l5 4V5z","M23 9l-6 6","M17 9l6 6"]} size={14}/>
              </button>
              <button title={enabled ? "ปิดแจ้งเตือน" : "เปิดแจ้งเตือน"} onClick={toggleEnabled}
                className={"grid h-7 w-7 place-items-center rounded-md text-xs transition-colors " + (enabled ? "text-accent hover:bg-accent-soft" : "text-faint hover:bg-sunken")}>
                <Ic d={enabled ? I.bell : ["M13.7 21a2 2 0 0 1-3.4 0","M18 8a6 6 0 0 0-9.3-5","M2 2l20 20","M6.3 6.3C5.5 7.8 5 9.4 5 11c0 7-3 9-3 9h14"]} size={14}/>
              </button>
              {unread > 0 && <button title="อ่านทั้งหมด" onClick={markRead} className="grid h-7 w-7 place-items-center rounded-md text-faint hover:bg-sunken hover:text-fg"><Ic d={I.check} size={14}/></button>}
              {items.length > 0 && <button title="ล้างทั้งหมด" onClick={clearAll} className="grid h-7 w-7 place-items-center rounded-md text-faint hover:bg-sunken hover:text-danger"><Ic d={I.trash} size={14}/></button>}
            </div>
          </div>
          <div className="max-h-[420px] overflow-y-auto">
            {items.length === 0 ? (
              <div className="py-10 text-center text-sm text-muted">ยังไม่มีรายการฝาก-ถอน</div>
            ) : items.map(item => (
              <button key={item.id} type="button" onClick={() => openItem(item)} className={"flex w-full items-start gap-3 border-b border-line/50 px-4 py-3 text-left transition-colors hover:bg-sunken " + (item.unread ? "bg-accent-soft/30" : "")}>
                <NotifyIcon item={item}/>
                <div className="min-w-0 flex-1">
                  <div className="flex items-baseline justify-between gap-2">
                    <span className="text-sm font-semibold">{item.type === "withdraw" ? "ถอนเงิน" : "ฝากเงิน"} จาก {item.fullName || item.username || item.phone || "—"}</span>
                    <span className="shrink-0 text-[11px] text-faint">{_notifyTimeFmt(item.createdAt)}</span>
                  </div>
                  <div className="mt-0.5 flex items-center gap-2 text-[11px]">
                    <span className="text-muted">{_chLabel(item.channel, item.bankCode || item.bank_code)}</span>
                    <span className="inline-flex items-center gap-1"><span className="h-1.5 w-1.5 rounded-full" style={{ background: _stColor(item.status) }}/>{_stLabel(item.status)}</span>
                  </div>
                  {acctName(item) && <div className="mt-0.5 text-[11px] text-faint">{acctName(item)}</div>}
                  <div className="mt-0.5 flex items-baseline justify-between">
                    <span className={"text-sm font-bold " + (item.type === "withdraw" ? "text-rose-500" : "text-emerald-500")}>{baht(item.amount)}</span>
                    <span className="text-[10px] text-faint">#{_fnotiRefId(item)}</span>
                  </div>
                </div>
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
function Topbar({ onMenu, theme, setTheme, crumb, admin, onNav }) {
  return (<header className="sticky top-0 z-20 flex h-16 items-center gap-3 border-b border-line bg-app/85 px-4 backdrop-blur-md sm:px-6" style={{top:"env(safe-area-inset-top,0px)"}}>
    <button onClick={onMenu} className="grid h-9 w-9 place-items-center rounded-lg text-fg-2 hover:bg-sunken lg:hidden"><Ic d={I.grid}/></button>
    <div className="hidden items-center gap-1.5 text-sm text-muted sm:flex"><span>หลังบ้าน</span><Ic d={I.chevR} size={14} cls="text-faint"/><span className="font-medium text-fg">{crumb}</span></div>
    <div className="ml-auto flex items-center gap-2">
      <ThemeToggle theme={theme} setTheme={setTheme}/>
      <FinanceNotifyBell onNav={onNav}/>
      <div className="grid h-9 w-9 place-items-center rounded-full text-xs font-semibold text-white" style={{background:avc(admin?.username||"A")}}>{initials(admin?.full_name||admin?.username||"A")}</div>
    </div>
  </header>);
}
function CreditChip({ label, value, loading, color, hidden }) {
  return (<div className="flex h-9 shrink-0 items-center gap-2 rounded-lg border border-line bg-surface px-2.5">
    <span className="grid h-5 w-5 place-items-center rounded-full text-[10px] font-bold text-white" style={{background:color}}>฿</span>
    <span className="text-[11px] font-medium text-muted">{label}</span>
    {loading ? <span className="skel h-4 w-14"/> : <span className="text-sm font-semibold tnum">{hidden?"••••••":value}</span>}
  </div>);
}
function CreditStrip() {
  const [bal,setBal]=useState(null);
  const [sms,setSms]=useState(null);
  const [online,setOnline]=useState([]);
  const [loading,setLoading]=useState(true);
  const [hidden,setHidden]=useState(()=>{try{return localStorage.getItem("nxadmin-credit-hidden")==="1";}catch{return false;}});
  const toggleHide=()=>setHidden(h=>{const n=!h; try{localStorage.setItem("nxadmin-credit-hidden",n?"1":"0");}catch{} return n;});
  const load=useCallback(async()=>{
    setLoading(true);
    const [b,s,o]=await Promise.allSettled([api("/reports/agent-balance"), api("/sms-credit"), api("/online-admins")]);
    if(b.status==="fulfilled") setBal(b.value);
    if(s.status==="fulfilled") setSms(s.value);
    if(o.status==="fulfilled" && Array.isArray(o.value)) setOnline(o.value);
    setLoading(false);
  },[]);
  useEffect(()=>{ load(); const t=setInterval(load, 60000); return ()=>clearInterval(t); },[load]);
  const agent = bal && bal.amb && bal.amb.ok ? bal.amb.balance : null;
  const pg = bal && bal.pghard && bal.pghard.ok ? bal.pghard.balance : null;
  const ufa = bal && bal.ufa && bal.ufa.ok ? bal.ufa.balance : null;
  /* AGENT กับ SMS โชว์ตลอด ส่วนค่ายอื่นโชว์เฉพาะที่ต่อ API แล้ว */
  const linked = k => !!(bal && bal[k] && (bal[k].configured ?? bal[k].ok));
  return (<div className="border-b border-line bg-app/60 px-4 py-2 sm:px-6">
    <div className="flex flex-wrap items-center gap-2">
      <button onClick={toggleHide} title={hidden?"แสดงยอดเครดิต":"ซ่อนยอดเครดิต"} className={"grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-line bg-surface transition-colors hover:bg-sunken "+(hidden?"text-accent":"text-muted hover:text-accent")}><Ic d={hidden?I.eyeOff:I.eye} size={16}/></button>
      {!hidden && <button onClick={load} title="รีเฟรชยอด" className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-line bg-surface text-muted transition-colors hover:bg-sunken hover:text-accent"><Ic d={I.refresh} size={16} cls={loading?"animate-spin":""}/></button>}
      {hidden ? (
        <span className="text-xs text-faint">ซ่อนยอดเครดิตอยู่ — กดรูปตาเพื่อแสดง</span>
      ) : (
      <div className="flex flex-1 flex-nowrap items-center gap-2 overflow-x-auto">
        <CreditChip label="AGENT" value={cash(agent)} loading={loading&&!bal} color="#D9A100"/>
        {linked("pghard") && <CreditChip label="PGHARD" value={cash(pg)} loading={loading&&!bal} color="#111827"/>}
        {linked("ufa") && <CreditChip label="UFA" value={cash(ufa)} loading={loading&&!bal} color="#1D4ED8"/>}
        <CreditChip label="SMS" value={sms&&sms.credit!=null?cashInt(sms.credit):"—"} loading={loading&&!sms} color="#0EA5E9"/>
      </div>)}
      {online.length>0 && <div className="flex items-center gap-1.5 overflow-x-auto">
        <span className="flex items-center gap-1 text-[11px] font-medium text-muted"><span className="h-1.5 w-1.5 rounded-full bg-success"/>ออนไลน์:</span>
        {online.slice(0,6).map((a,i)=>(<span key={i} title={a.fullName||a.username} className="inline-flex items-center gap-1 rounded-full border border-line bg-surface px-2 py-0.5 text-[11px] font-medium">
          <span className="grid h-4 w-4 place-items-center rounded-full text-[9px] font-semibold text-white" style={{background:avc(a.username||a.fullName||"A")}}>{initials(a.fullName||a.username||"A")}</span>
          <span className="max-w-[80px] truncate">{a.fullName||a.username}</span>
        </span>))}
      </div>}
    </div>
  </div>);
}
