// allapps.jsx — All-apps grid, Admin console, and secondary portal pages.
function AppTile({ app, pinned, onPin, onOpen }) {
  const { tt, t } = useLang();
  return (
    <button className="app-tile" onClick={onOpen}>
      <span className="ic" style={{ background:app.color }}><i className={'bi ' + app.icon}></i></span>
      <div style={{ minWidth:0, flex:1 }}>
        <div className="at-name">{tt(app.name)}</div>
        <div className="at-desc">{tt(app.desc)}</div>
        {!app.module && <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:4 }}><i className="bi bi-lock" style={{ fontSize:10 }}></i> {t('apps.noModule')}</div>}
      </div>
      <span className={'pin' + (pinned ? ' pinned' : '')} title={pinned ? t('apps.unpin') : t('apps.pin')}
        onClick={e => { e.stopPropagation(); onPin(); }}>
        <i className={'bi ' + (pinned ? 'bi-pin-angle-fill' : 'bi-pin-angle')}></i>
      </span>
    </button>
  );
}

function AllApps() {
  const p = usePortal();
  const { t, tt } = useLang();
  const [q, setQ] = React.useState('');
  const [grp, setGrp] = React.useState('all');
  const groups = APP_GROUPS.filter(g => g.id !== 'admin' || p.role === 'admin');
  const list = p.apps.filter(a =>
    (grp === 'all' || a.group === grp) &&
    (!q || (tt(a.name) + tt(a.desc)).toLowerCase().includes(q.toLowerCase())));
  const byGroup = groups.map(g => ({ g, apps:list.filter(a => a.group === g.id) })).filter(x => x.apps.length);

  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('apps.title')}</h1><div className="sub">{t('apps.sub')}</div></div></div>
      <div className="toolbar">
        <div className="topsearch" style={{ width:260, background:'var(--bg)', border:'1px solid var(--border-strong)' }}>
          <i className="bi bi-search"></i>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder={t('c.search')} style={{ border:'none', outline:'none', background:'transparent', flex:1 }} />
        </div>
        <div className="seg">
          <button className={grp === 'all' ? 'is-active' : ''} onClick={() => setGrp('all')}>{t('c.all')}</button>
          {groups.map(g => <button key={g.id} className={grp === g.id ? 'is-active' : ''} onClick={() => setGrp(g.id)}>{tt(g.name)}</button>)}
        </div>
      </div>

      {byGroup.map(({ g, apps }) => (
        <div key={g.id} style={{ marginBottom:22 }}>
          <div className="flex items-center g8" style={{ marginBottom:10 }}>
            <h3 style={{ margin:0, fontSize:'var(--fs-lg)' }}>{tt(g.name)}</h3><span className="badge badge-neutral">{apps.length}</span>
          </div>
          <div className="app-grid">
            {apps.map(a => (
              <AppTile key={a.id} app={a} pinned={p.pinned.includes(a.id)}
                onPin={() => p.togglePin(a.id)} onOpen={() => p.openApp(a)} />
            ))}
          </div>
        </div>
      ))}
      {byGroup.length === 0 && <EmptyState text={t('c.empty')} />}
    </div>
  );
}

function AdminConsole() {
  const p = usePortal();
  const { t, tt, lang } = useLang();
  const toast = useToast();
  const [tab, setTab] = React.useState(p.route && p.route.tab ? p.route.tab : 'apps');
  React.useEffect(() => { if (p.route && p.route.tab) setTab(p.route.tab); }, [p.route && p.route.tab]);
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('admin.title')}</h1><div className="sub">{lang === 'vi' ? 'Cấu hình hiển thị ứng dụng và theo dõi hoạt động.' : 'Configure app visibility and monitor activity.'}</div></div></div>
      <Tabs active={tab} onChange={setTab} tabs={[
        { id:'apps', icon:'bi-grid-3x3-gap', label:t('admin.apps') },
        { id:'system', icon:'bi-sliders2', label:t('admin.system') },
        { id:'log', icon:'bi-clipboard-data', label:t('admin.log') },
      ]} />

      {tab === 'system' && <SystemAdminPanel />}

      {tab === 'apps' && (
        <div className="card" style={{ marginTop:14 }}>
          <div className="card-head"><h3>{t('admin.apps')}</h3><span className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-info-circle"></i> {t('admin.hint')}</span></div>
          <div style={{ overflow:'auto' }}>
            <table className="tbl">
              <thead><tr><th>{lang === 'vi' ? 'Ứng dụng' : 'Application'}</th><th>{lang === 'vi' ? 'Nhóm' : 'Group'}</th><th>{lang === 'vi' ? 'Quyền' : 'Access'}</th><th style={{ textAlign:'right' }}>{t('admin.showPortal')}</th></tr></thead>
              <tbody>
                {APPS.map(a => {
                  const hidden = p.hidden.includes(a.id);
                  const grp = APP_GROUPS.find(g => g.id === a.group);
                  return (
                    <tr key={a.id} style={{ cursor:'default' }}>
                      <td><span className="flex g8 items-center"><span className="ic" style={{ width:24, height:24, borderRadius:6, background:a.color, color:'#fff', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:12 }}><i className={'bi ' + a.icon}></i></span><span style={{ fontWeight:500 }}>{tt(a.name)}</span>{a.module && <span className="badge badge-accent">module</span>}</span></td>
                      <td className="muted">{tt(grp.name)}</td>
                      <td>{a.adminOnly ? <span className="badge badge-info">Admin</span> : <span className="badge badge-neutral">{lang === 'vi' ? 'Mọi người' : 'Everyone'}</span>}</td>
                      <td style={{ textAlign:'right' }}>
                        <button className={'switch' + (hidden ? '' : ' on')} onClick={() => { p.toggleHidden(a.id); toast(hidden ? (lang==='vi'?'Đã hiển thị '+tt(a.name):'Shown '+tt(a.name)) : (lang==='vi'?'Đã ẩn '+tt(a.name):'Hidden '+tt(a.name)), hidden ? 'success' : 'info'); }}
                          aria-pressed={!hidden} style={{ border:'none', width:38, height:22, borderRadius:999, background:hidden ? 'var(--gray-300)' : 'var(--primary)', position:'relative', transition:'background .15s', cursor:'pointer' }}>
                          <span style={{ position:'absolute', top:2, left:hidden ? 2 : 18, width:18, height:18, borderRadius:'50%', background:'#fff', transition:'left .15s', boxShadow:'var(--sh-xs)' }}></span>
                        </button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {tab === 'log' && <AuditLogPanel />}
    </div>
  );
}

/* ---- simple workspace pages ---- */
function SimpleListPage({ title, sub, children }) {
  return <div className="page pagefade"><div className="page-head"><div><h1>{title}</h1>{sub && <div className="sub">{sub}</div>}</div></div>{children}</div>;
}
function ApprovalsPage() {
  const { t, tt, lang } = useLang();
  const toast = useToast();
  const [apprs, setApprs] = React.useState(APPROVALS);
  return (
    <SimpleListPage title={t('nav.approvals')} sub={lang === 'vi' ? 'Các yêu cầu chờ bạn xử lý.' : 'Requests awaiting your decision.'}>
      <div className="card"><div className="card-body">
        {apprs.length === 0 && <EmptyState icon="bi-check2-all" text={lang === 'vi' ? 'Đã xử lý hết' : 'All clear'} />}
        {apprs.map(a => (
          <div key={a.id} className="appr-row">
            <div style={{ flex:1 }}><div className="flex g8 items-center"><span style={{ fontWeight:600 }}>{tt(a.kind)}</span>{a.amount && <span className="badge badge-neutral mono">{a.amount}</span>}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{a.who} · {tt(a.detail)}</div></div>
            <div className="appr-actions">
              <button className="btn btn-sm" onClick={() => { setApprs(s => s.filter(x => x.id !== a.id)); toast(lang === 'vi' ? 'Đã từ chối' : 'Rejected', 'error'); }}>{t('c.reject')}</button>
              <button className="btn btn-sm btn-primary" onClick={() => { setApprs(s => s.filter(x => x.id !== a.id)); toast(lang === 'vi' ? 'Đã duyệt' : 'Approved', 'success'); }}>{t('c.approve')}</button>
            </div>
          </div>
        ))}
      </div></div>
    </SimpleListPage>
  );
}
function NotifsPage() {
  const p = usePortal();
  const { t, tt, lang } = useLang();
  const [filter, setFilter] = React.useState('all');
  const unread = p.notifs.filter(n => n.unread).length;
  const list = p.notifs.filter(n => filter === 'all' || n.unread);
  const open = (n) => { p.markNotifRead(n.id); if (n.link) p.navigate(n.link); };
  return (
    <div className="page pagefade">
      <div className="page-head">
        <div><h1>{t('nav.notifs')}</h1><div className="sub">{unread > 0 ? (lang === 'vi' ? unread + ' thông báo chưa đọc' : unread + ' unread') : (lang === 'vi' ? 'Đã đọc hết' : 'All caught up')}</div></div>
        {unread > 0 && <button className="btn" onClick={() => p.markAllNotifsRead()}><i className="bi bi-check2-all"></i>{t('notif.markAll')}</button>}
      </div>
      <div className="toolbar">
        <div className="seg">
          <button className={filter === 'all' ? 'is-active' : ''} onClick={() => setFilter('all')}>{t('c.all')} · {p.notifs.length}</button>
          <button className={filter === 'unread' ? 'is-active' : ''} onClick={() => setFilter('unread')}>{t('notif.unread')} · {unread}</button>
        </div>
      </div>
      <div className="card"><div className="card-body" style={{ paddingTop:6 }}>
        {list.length === 0 && <EmptyState icon="bi-bell-slash" text={t('notif.empty')} />}
        {list.map(n => { const ty = NOTIF_TYPES[n.type] || {}; return (
          <div key={n.id} className={'recent-row is-link' + (n.unread ? ' is-unread' : '')} style={{ padding:'10px 8px' }} onClick={() => open(n)}>
            <span className="recent-ic" style={{ color:ty.color }}><i className={'bi ' + ty.icon}></i></span>
            <div className="grow" style={{ minWidth:0 }}>
              <div style={{ fontWeight:n.unread ? 600 : 500 }}>{tt(n.text)}</div>
              <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{n.time}{n.link ? ' · ' + (lang === 'vi' ? 'Bấm để xem' : 'Open') : ''}</div>
            </div>
            {n.unread ? <span className="rdot" style={{ width:7, height:7, borderRadius:'50%', background:'var(--primary)', flex:'none' }}></span> : <i className="bi bi-check2" style={{ color:'var(--text-subtle)' }}></i>}
          </div>
        ); })}
      </div></div>
    </div>
  );
}
function SettingsPage() {
  const p = usePortal();
  const { t, lang, setLang } = useLang();
  return (
    <SimpleListPage title={t('nav.settings')} sub={lang === 'vi' ? 'Hồ sơ, chữ ký & giao diện.' : 'Profile, signature & appearance.'}>
     <NamecardEditor />
     <div className="settings-grid" style={{ marginTop:14 }}>
      <div className="card">
        <div className="card-head"><h3>{lang === 'vi' ? 'Giao diện' : 'Appearance'}</h3></div>
        <div className="card-body col g16">
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Giao diện màu' : 'Theme'}</span>
            <Segmented value={p.theme} onChange={p.setTheme} options={[{ value:'light', label:lang==='vi'?'Sáng':'Light' }, { value:'dark', label:lang==='vi'?'Tối':'Dark' }, { value:'system', label:lang==='vi'?'Hệ thống':'System' }]} /></div>
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Mật độ' : 'Density'}</span>
            <Segmented value={p.density} onChange={p.setDensity} options={[{ value:'compact', label:lang==='vi'?'Gọn':'Compact' }, { value:'comfortable', label:lang==='vi'?'Vừa':'Cozy' }, { value:'spacious', label:lang==='vi'?'Rộng':'Spacious' }]} /></div>
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Cỡ chữ' : 'Font size'}</span>
            <Segmented value={p.fontsize} onChange={p.setFontsize} options={[{ value:'sm', label:'S' }, { value:'md', label:'M' }, { value:'lg', label:'L' }]} /></div>
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Độ rộng trang' : 'Page width'}</span>
            <Segmented value={p.pageWidth} onChange={p.setPageWidth} options={[{ value:'1280', label:'1280' }, { value:'1800', label:'1800' }, { value:'full', label:lang === 'vi' ? 'Tràn' : 'Full' }]} /></div>
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Định dạng ngày' : 'Date format'}</span>
            <Segmented value={p.dateFmt || ''} onChange={p.setDateFmt} options={[{ value:'', label:lang === 'vi' ? 'Hệ thống' : 'System' }, { value:'dmy', label:'DD/MM/YYYY' }, { value:'mdy', label:'MM/DD/YYYY' }]} /></div>
          <div className="flex items-center justify-between"><span style={{ fontWeight:500 }}>{lang === 'vi' ? 'Ngôn ngữ' : 'Language'}</span>
            <Segmented value={lang} onChange={setLang} options={[{ value:'vi', label:'Tiếng Việt' }, { value:'en', label:'English' }]} /></div>
        </div>
      </div>
      <DriveBackupCard />
     </div>
    </SimpleListPage>
  );
}

/* ===== Nhật ký hệ thống (tab "Nhật ký" của Bảng điều khiển) — đọc core_audit_log (migration 0044) =====
   Trigger DB tự ghi Thêm/Sửa/Xóa trên các bảng nghiệp vụ chính; RLS chỉ admin đọc. */
const AUDIT_TBL_LABEL = {
  profiles:{ vi:'Người dùng', en:'Users' }, app_settings:{ vi:'Cấu hình', en:'Settings' },
  customer_customers:{ vi:'Khách hàng', en:'Customers' }, contact_contacts:{ vi:'Liên hệ', en:'Contacts' },
  contract_contracts:{ vi:'Hợp đồng', en:'Contracts' }, quote_quotes:{ vi:'Báo giá', en:'Quotes' },
  product_products:{ vi:'Sản phẩm', en:'Products' }, product_costs:{ vi:'Giá vốn', en:'Costs' },
  vendor_vendors:{ vi:'Nhà cung cấp', en:'Vendors' }, deal_deals:{ vi:'Thương vụ', en:'Deals' },
  task_tasks:{ vi:'Công việc', en:'Tasks' }, support_tickets:{ vi:'Hỗ trợ', en:'Support' },
  finance_expenses:{ vi:'Chi phí', en:'Expenses' }, stock_receipts:{ vi:'Phiếu nhập', en:'Receipts' },
  stock_issues:{ vi:'Phiếu xuất', en:'Issues' }, cms_posts:{ vi:'Website', en:'Website' },
};
const AUDIT_ACTION = {
  INSERT:{ vi:'Thêm', en:'Insert', cls:'badge-success' },
  UPDATE:{ vi:'Sửa',  en:'Update', cls:'badge-info' },
  DELETE:{ vi:'Xóa',  en:'Delete', cls:'badge-error' },
};
// Nhãn cột thân thiện cho phần "Chi tiết thay đổi" (thiếu key → hiện tên cột thô).
const AUDIT_COL_LABEL = {
  status:'Trạng thái', role:'Vai trò', customer:'Đối tác', vendor:'NCC', name:'Tên', title:'Tiêu đề', subject:'Tiêu đề',
  end_date:'Ngày kết thúc', start_date:'Ngày bắt đầu', sign_date:'Ngày ký', due_date:'Đến hạn', issue_date:'Ngày phát hành',
  valid_until:'Hiệu lực đến', accepted_date:'Ngày nghiệm thu', remind_before:'Nhắc trước (ngày)', billing_cycle:'Chu kỳ',
  direction:'Chiều', category:'Loại', cat:'Loại', amount:'Số tiền', total:'Tổng', price:'Đơn giá', cost:'Giá vốn', vat:'VAT',
  discount_pct:'Chiết khấu %', note:'Ghi chú', notes:'Ghi chú', email:'Email', phone:'SĐT', address:'Địa chỉ', mst:'MST',
  schedule:'Lịch thanh toán', items:'Hạng mục', lines:'Dòng hàng', stage:'Giai đoạn', owner:'Phụ trách', rep:'Đại diện',
  contract_no:'Số HĐ', sku:'Mã hàng', unit:'Đơn vị', item_type:'Loại hàng', stock_tracked:'Quản lý tồn', min_stock:'Tồn tối thiểu',
  published:'Đăng', description:'Mô tả', descr:'Mô tả', warranty:'Bảo hành', revisions:'Phiên bản', doc_overrides:'Điều khoản riêng',
  price_history:'Lịch sử giá', interactions:'Trao đổi', result:'Kết quả', report:'Biên bản', contracts:'HĐ liên kết',
  cost_contracts:'HĐ giá vốn', dept:'Bộ phận', meta:'Hồ sơ', value:'Nội dung', paid:'Đã thu',
};
// Giá trị → chữ dễ đọc: trạng thái theo module, ngày dd/mm/yyyy, tiền có phân cách, bool Có/Không, mảng/object gọn.
function auditValueLabel(tbl, k, v, lang){
  if (v == null || v === '') return '—';
  if (typeof v === 'boolean') return v ? (lang === 'vi' ? 'Có' : 'Yes') : (lang === 'vi' ? 'Không' : 'No');
  if (Array.isArray(v)) return (lang === 'vi' ? '(' + v.length + ' mục)' : '(' + v.length + ' items)');
  if (typeof v === 'object') return lang === 'vi' ? '(đã cập nhật)' : '(updated)';
  const s = String(v);
  if (k === 'status'){
    const m = { contract_contracts:window.CONTRACT_STATUS, quote_quotes:window.QUOTE_STATUS,
      stock_issues:window.ISSUE_STATUS, support_tickets:window.TICKET_STATUS, task_tasks:window.TASK_STATUS }[tbl];
    if (m && m[s]) return m[s][lang] || m[s].vi || s;
  }
  if (k === 'direction' && window.CONTRACT_DIRECTIONS && CONTRACT_DIRECTIONS[s]) return CONTRACT_DIRECTIONS[s][lang] || CONTRACT_DIRECTIONS[s].vi;
  if (k === 'billing_cycle' && window.CONTRACT_BILLING_CYCLES && CONTRACT_BILLING_CYCLES[s]) return CONTRACT_BILLING_CYCLES[s][lang] || CONTRACT_BILLING_CYCLES[s].vi;
  if (/^\d{4}-\d{2}-\d{2}/.test(s)) return fmtDate(s.slice(0, 10));
  if (/(amount|total|price|cost)$/.test(k) && s !== '' && !isNaN(+s)) return (+s).toLocaleString('vi-VN');
  return s.length > 42 ? s.slice(0, 42) + '…' : s;
}
const AUDIT_PAGE = 50;
function AuditLogPanel(){
  const p = usePortal();
  const { lang } = useLang();
  const [rows, setRows] = React.useState([]);
  const [state, setState] = React.useState('loading');   // loading | ok | missing | offline | error
  const [errText, setErrText] = React.useState('');
  const [hasMore, setHasMore] = React.useState(false);
  const [busyMore, setBusyMore] = React.useState(false);
  const [q, setQ] = React.useState('');
  const [act, setAct] = React.useState('');
  const [tbl, setTbl] = React.useState('');
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  // Đổi bộ lọc → nạp lại từ đầu (debounce nhẹ cho ô tìm). Nạp thêm giữ nguyên bộ lọc.
  React.useEffect(() => {
    if (!window.SB_READY) { setState('offline'); return; }
    let alive = true;
    const timer = setTimeout(async () => {
      try {
        const data = await DB.loadAudit({ offset:0, limit:AUDIT_PAGE, action:act || null, tbl:tbl || null, q:q.trim() || null });
        if (!alive) return;
        setRows(data || []); setHasMore((data || []).length === AUDIT_PAGE); setState('ok');
      } catch (e){
        if (!alive) return;
        const m = (e && e.message) || '';
        if (/does not exist|42P01|relation/i.test(m)) setState('missing');
        else { setErrText(m); setState('error'); }
      }
    }, q ? 350 : 0);
    return () => { alive = false; clearTimeout(timer); };
  }, [q, act, tbl]);
  const loadMore = async () => {
    if (busyMore) return;
    setBusyMore(true);
    try {
      const data = await DB.loadAudit({ offset:rows.length, limit:AUDIT_PAGE, action:act || null, tbl:tbl || null, q:q.trim() || null });
      setRows(s => [...s, ...(data || [])]); setHasMore((data || []).length === AUDIT_PAGE);
    } catch (_){ setHasMore(false); }
    setBusyMore(false);
  };
  const who = (r) => { const u = (p.users || []).find(x => x.id === r.actor); return (u && u.name) || r.actor_email || 'System'; };
  const fmtAt = (iso) => { const d = new Date(iso); const p2 = (n) => String(n).padStart(2, '0'); return fmtDate(d.toISOString().slice(0, 10)) + ' ' + p2(d.getHours()) + ':' + p2(d.getMinutes()); };
  // Tóm tắt jsonb → chuỗi dễ đọc "Nhãn → giá trị" (tối đa 3 cặp, +N nếu còn).
  const sumText = (r) => {
    const s = r.summary;
    if (!s || typeof s !== 'object') return '';
    const es = Object.entries(s);
    const ps = es.slice(0, 3).map(([k, v]) => (lang === 'vi' ? (AUDIT_COL_LABEL[k] || k) : k) + ' → ' + auditValueLabel(r.tbl, k, v, lang));
    return ps.join(' · ') + (es.length > 3 ? ' · +' + (es.length - 3) : '');
  };
  return (
    <div className="card" style={{ marginTop:14 }}>
      <div className="card-head" style={{ flexWrap:'wrap', gap:8 }}>
        <h3>{L('Nhật ký hệ thống', 'System audit log')}</h3>
        <div className="flex g8 items-center wrap">
          <div className="topsearch" style={{ width:200, background:'var(--bg)', border:'1px solid var(--border-strong)' }}><i className="bi bi-search"></i>
            <input value={q} onChange={e => setQ(e.target.value)} placeholder={L('Email / mã bản ghi…', 'Email / record code…')} style={{ border:'none', outline:'none', background:'transparent', flex:1 }} /></div>
          <div className="seg">
            {['', 'INSERT', 'UPDATE', 'DELETE'].map(a => <button key={a || 'all'} className={act === a ? 'is-active' : ''} onClick={() => setAct(a)}>{a ? (AUDIT_ACTION[a][lang] || a) : L('Tất cả', 'All')}</button>)}
          </div>
          <select className="select" style={{ width:'auto' }} value={tbl} onChange={e => setTbl(e.target.value)}>
            <option value="">{L('Mọi phân hệ', 'All areas')}</option>
            {Object.keys(AUDIT_TBL_LABEL).map(k => <option key={k} value={k}>{AUDIT_TBL_LABEL[k][lang] || k}</option>)}
          </select>
        </div>
      </div>
      {state === 'offline' && <div className="card-body"><EmptyState icon="bi-cloud-slash" text={L('Chưa kết nối dữ liệu — đăng nhập để xem nhật ký.', 'Not connected — sign in to view the log.')} /></div>}
      {state === 'missing' && <div className="card-body"><EmptyState icon="bi-database-x" text={L('Chưa có bảng nhật ký — chạy migration 0044_core_audit_log.sql ở Supabase SQL Editor rồi tải lại.', 'Audit table missing — run migration 0044 in Supabase SQL Editor.')} /></div>}
      {state === 'error' && <div className="card-body"><EmptyState icon="bi-exclamation-triangle" text={(L('Không đọc được nhật ký: ', 'Cannot load log: ')) + errText} /></div>}
      {state === 'loading' && <div className="card-body subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Đang tải…', 'Loading…')}</div>}
      {state === 'ok' && (
        <div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th style={{ width:130 }}>{L('Thời gian', 'Time')}</th><th>{L('Người thực hiện', 'Actor')}</th><th style={{ width:80 }}>{L('Hành động', 'Action')}</th><th>{L('Phân hệ', 'Area')}</th><th>{L('Bản ghi', 'Record')}</th><th>{L('Chi tiết thay đổi', 'Changes')}</th></tr></thead>
            <tbody>
              {rows.map(r => { const a = AUDIT_ACTION[r.action] || { vi:r.action, cls:'badge-neutral' }; const tl = AUDIT_TBL_LABEL[r.tbl]; return (
                <tr key={r.id} style={{ cursor:'default' }}>
                  <td className="mono muted" style={{ fontSize:'var(--fs-sm)', whiteSpace:'nowrap' }}>{fmtAt(r.at)}</td>
                  <td><span className="flex g8 items-center"><Avatar name={who(r)} size={22} /><span className="truncate" style={{ maxWidth:180 }}>{who(r)}</span></span></td>
                  <td><span className={'badge ' + a.cls}>{a[lang] || a.vi}</span></td>
                  <td className="muted">{(tl && (tl[lang] || tl.vi)) || r.tbl}</td>
                  <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{r.record_code || '—'}</span></td>
                  <td className="muted truncate" style={{ maxWidth:340, fontSize:'var(--fs-sm)' }} title={r.summary ? JSON.stringify(r.summary) : ''}>{sumText(r)}</td>
                </tr>
              ); })}
            </tbody>
          </table>
          {!rows.length && <EmptyState icon="bi-clipboard-data" text={L('Chưa có mục nhật ký nào khớp bộ lọc.', 'No log entries match.')} />}
          {hasMore && <div style={{ padding:'10px 14px' }}><button className="btn btn-sm" disabled={busyMore} onClick={loadMore}>{busyMore ? L('Đang tải…', 'Loading…') : L('Tải thêm', 'Load more')}</button></div>}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { AllApps, AdminConsole, AuditLogPanel, ApprovalsPage, NotifsPage, SettingsPage });
