// website.jsx — QUẢN TRỊ WEBSITE (tin tức cho www.swo.vn) + PostEditModal + widget.
// Bài viết lưu cms_posts (0033); website công khai đọc bài published bằng anon key (RLS).
// Ảnh bìa upload Storage bucket 'website' (public-read).
const WEB_APP = APPS.find(a => a.id === 'website');

const POST_STATUS = {
  draft:     { vi:'Nháp',     en:'Draft',     cls:'badge-neutral' },
  published: { vi:'Đã đăng',  en:'Published', cls:'badge-success' },
  hidden:    { vi:'Đã ẩn',    en:'Hidden',    cls:'badge-warning' },
};
function PostStatusBadge({ status }){
  const { lang } = useLang();
  const st = POST_STATUS[status] || POST_STATUS.draft;
  return <span className={'badge ' + st.cls}>{lang === 'vi' ? st.vi : st.en}</span>;
}
function genPostId(list){
  const max = (list || []).reduce((m, w) => { const mm = (w.id || '').match(/BV-(\d+)/); return mm ? Math.max(m, +mm[1]) : m; }, 0);
  return 'BV-' + String(max + 1).padStart(3, '0');
}
// slug từ tiêu đề: bỏ dấu → chữ thường → gạch nối (dùng cho URL bài trên website).
function postSlug(title){
  return (title || '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd').replace(/Đ/g, 'D')
    .toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80);
}
function postDate(w){ return (w.publishedAt || w.createdAt || '').slice(0, 10); }

function WebsiteModule(){
  const p = usePortal();
  const { t, lang } = useLang();
  const sub = p.route.sub || 'overview';
  const posts = p.posts;
  const items = [
    { id:'overview', icon:'bi-pie-chart', label:t('c.overview') },
    { id:'posts', icon:'bi-newspaper', label:lang === 'vi' ? 'Bài viết' : 'Posts', count:posts.length },
    { id:'content', icon:'bi-sliders', label:lang === 'vi' ? 'Thiết lập trang' : 'Site content' },
    { id:'settings', icon:'bi-gear', label:t('c.settings') },
  ];
  const go = (id, extra) => p.navigate({ name:'module', module:'website', sub:id, ...extra });
  return (<>
    <ModuleSidebar app={WEB_APP} items={items} active={sub} onNavigate={go} />
    <main className="main">
      {sub === 'overview' && <WebOverview go={go} />}
      {sub === 'posts' && <PostList go={go} />}
      {sub === 'content' && <SiteContentEditor />}
      {sub === 'help' && <ModuleHelp moduleId="website" app={WEB_APP} />}
      {sub === 'settings' && <WebSettings />}
    </main>
  </>);
}

function WebOverview({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const posts = p.posts;
  const pub = posts.filter(w => w.status === 'published');
  const draft = posts.filter(w => w.status === 'draft');
  const recent = posts.slice(0, 6);
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Quản trị website' : 'Website admin'}</h1><div className="sub">{lang === 'vi' ? 'Tin tức & nội dung hiển thị trên www.swo.vn.' : 'News & content for www.swo.vn.'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('website')}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Viết bài' : 'New post'}</button></div>
      <div className="kpi-strip">
        <Kpi label={lang === 'vi' ? 'Tổng bài viết' : 'Posts'} value={posts.length} icon="bi-newspaper" />
        <Kpi label={lang === 'vi' ? 'Đã đăng' : 'Published'} value={pub.length} icon="bi-globe2" />
        <Kpi label={lang === 'vi' ? 'Nháp' : 'Drafts'} value={draft.length} icon="bi-pencil-square" />
        <Kpi label={lang === 'vi' ? 'Danh mục' : 'Categories'} value={(p.catalogs.websiteCats || []).length} icon="bi-tags" />
      </div>
      <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Bài mới nhất' : 'Latest posts'}</h3>
        <button className="btn btn-ghost btn-sm" onClick={() => go('posts')}>{t('c.viewAll')}</button></div>
        <div className="card-body">
          {recent.length === 0 && <EmptyState icon="bi-newspaper" text={lang === 'vi' ? 'Chưa có bài viết — bấm "Viết bài" để bắt đầu.' : 'No posts yet.'} />}
          {recent.map(w => (
            <div key={w.id} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.openPostEdit(w)}>
              <span className="mono" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)', width:64 }}>{w.id}</span>
              <span className="grow truncate" style={{ fontWeight:500 }}>{w.title}</span>
              <span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{w.category}</span>
              <PostStatusBadge status={w.status} />
            </div>
          ))}
        </div>
      </div>
      <div className="card" style={{ marginTop:14, background:'var(--primary-light)', borderColor:'color-mix(in srgb, var(--primary) 24%, transparent)' }}><div className="card-body flex g10 items-start" style={{ fontSize:'var(--fs-base)' }}>
        <i className="bi bi-info-circle" style={{ color:'var(--primary)', marginTop:2, fontSize:15 }}></i>
        <span>{lang === 'vi' ? 'Chỉ bài ở trạng thái "Đã đăng" mới hiển thị trên website công khai. Nháp và bài đã ẩn chỉ thấy trong portal này.' : 'Only "Published" posts appear on the public website. Drafts and hidden posts stay internal.'}</span>
      </div></div>
    </div>
  );
}

function PostList({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const [q, setQ] = React.useState('');
  const [status, setStatus] = React.useState('all');
  const srt = useTableSort();
  const list = srt.sortRows(p.posts.filter(w =>
    (status === 'all' || w.status === status) &&
    (!q || (w.title + ' ' + (w.category || '') + ' ' + w.id).toLowerCase().includes(q.toLowerCase()))),
    { id:w => w.id, title:w => w.title, cat:w => w.category, date:w => postDate(w), author:w => w.author, status:w => w.status });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{lang === 'vi' ? 'Bài viết' : 'Posts'}</h1><div className="sub">{list.length} {lang === 'vi' ? 'bài' : 'posts'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('website')}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Viết bài' : 'New post'}</button></div>
      <div className="toolbar">
        <div className="topsearch" style={{ width:240, 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">
          {['all','published','draft','hidden'].map(s => <button key={s} className={status === s ? 'is-active' : ''} onClick={() => setStatus(s)}>{s === 'all' ? t('c.all') : (lang === 'vi' ? POST_STATUS[s].vi : POST_STATUS[s].en)}</button>)}
        </div>
      </div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="id" sort={srt} style={{ width:70 }}>ID</ThSort><ThSort k="title" sort={srt}>{lang === 'vi' ? 'Tiêu đề' : 'Title'}</ThSort><ThSort k="cat" sort={srt}>{lang === 'vi' ? 'Danh mục' : 'Category'}</ThSort><ThSort k="date" sort={srt}>{lang === 'vi' ? 'Ngày đăng' : 'Published'}</ThSort><ThSort k="author" sort={srt}>{lang === 'vi' ? 'Tác giả' : 'Author'}</ThSort><ThSort k="status" sort={srt}>{t('c.status')}</ThSort></tr></thead>
          <tbody>
            {list.map(w => (
              <tr key={w.id} onClick={() => p.openPostEdit(w)}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{w.id}</span></td>
                {/* slot thumbnail CỐ ĐỊNH mọi hàng (không ảnh → khung mờ) → cột tiêu đề luôn thẳng */}
                <td><span className="flex g8 items-center">
                  {w.coverUrl
                    ? <img src={w.coverUrl} alt="" style={{ width:34, height:22, objectFit:'cover', borderRadius:'var(--r-sm)', flex:'none' }} />
                    : <span style={{ width:34, height:22, borderRadius:'var(--r-sm)', flex:'none', display:'flex', alignItems:'center', justifyContent:'center', background:'var(--bg-sunken)', color:'var(--text-subtle)', fontSize:11 }}><i className="bi bi-image"></i></span>}
                  <span style={{ fontWeight:500 }}>{w.title}</span></span></td>
                <td className="muted">{w.category || '—'}</td>
                <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{w.publishedAt ? fmtDate(postDate(w)) : '—'}</td>
                <td>{w.authorName || '—'}</td>
                <td><PostStatusBadge status={w.status} /></td>
              </tr>
            ))}
          </tbody>
        </table>
        {list.length === 0 && <EmptyState text={t('c.empty')} />}
      </div></div>
    </div>
  );
}

/* Modal viết/sửa bài (quick action 'website') */
function PostEditModal({ editing, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const cats = p.catalogs.websiteCats || [];
  const [autoSlug, setAutoSlug] = React.useState(!editing);
  const [f, setF] = React.useState(editing
    ? { title:editing.title, slug:editing.slug, category:editing.category || '', coverUrl:editing.coverUrl || '', excerpt:editing.excerpt || '', content:editing.content || '', status:editing.status }
    : { title:'', slug:'', category:cats[0] || '', coverUrl:'', excerpt:'', content:'', status:'draft' });
  const set = (k, v) => setF(s => ({ ...s, [k]: v }));
  const slug = autoSlug ? postSlug(f.title) : f.slug;
  const dupSlug = p.posts.some(w => w.slug === slug && (!editing || w.id !== editing.id)) && slug;
  const [uploading, setUploading] = React.useState(false);
  const fileRef = React.useRef();
  const uploadCover = async (e) => {
    const file = e.target.files && e.target.files[0]; e.target.value = '';
    if (!file) return;
    if (!window.SB_READY){ toast(lang === 'vi' ? 'Supabase chưa cấu hình' : 'Supabase not configured', 'error'); return; }
    setUploading(true);
    try {
      const ext = (file.name.split('.').pop() || 'jpg').toLowerCase();
      const path = 'covers/' + (editing ? editing.id : 'new') + '_' + Date.now() + '.' + ext;
      const { error } = await sb.storage.from('website').upload(path, file, { upsert:true });
      if (error) throw error;
      const { data } = sb.storage.from('website').getPublicUrl(path);
      set('coverUrl', data.publicUrl);
      toast(lang === 'vi' ? 'Đã tải ảnh bìa' : 'Cover uploaded', 'success');
    } catch (err){ toast((lang === 'vi' ? 'Tải ảnh thất bại: ' : 'Upload failed: ') + (err.message || err), 'error'); }
    finally { setUploading(false); }
  };
  const remove = async () => {
    if (!(await confirm({ message:(lang === 'vi' ? 'Xóa bài viết "' : 'Delete post "') + editing.title + '"?', confirmLabel:lang === 'vi' ? 'Xóa' : 'Delete' }))) return;
    p.deletePost(editing.id); toast((lang === 'vi' ? 'Đã xóa ' : 'Deleted ') + editing.id, 'success'); onClose();
  };
  const save = (statusOverride) => {
    if (!f.title.trim()){ toast(lang === 'vi' ? 'Nhập tiêu đề bài viết' : 'Enter a title', 'error'); return; }
    if (!slug || dupSlug){ toast(lang === 'vi' ? 'Slug trống hoặc trùng với bài khác' : 'Slug empty or duplicated', 'error'); return; }
    const status = statusOverride || f.status;
    const rec = {
      ...(editing || {}), ...f, slug, status,
      id: editing ? editing.id : genPostId(p.posts),
      // publishedAt: set lần đầu chuyển sang published; gỡ xuống vẫn giữ mốc đăng cũ.
      publishedAt: status === 'published' ? ((editing && editing.publishedAt) || new Date().toISOString()) : (editing ? editing.publishedAt : null),
      authorName: (editing && editing.authorName) || p.me.name,
    };
    if (editing){ p.patchPost(editing.id, rec); toast(lang === 'vi' ? 'Đã lưu bài viết' : 'Saved', 'success'); }
    else { p.addPost(rec); toast((lang === 'vi' ? 'Đã tạo ' : 'Created ') + rec.id, 'success'); }
    onClose();
  };
  return (
    <Modal title={editing ? (lang === 'vi' ? 'Sửa bài viết · ' : 'Edit post · ') + editing.id : (lang === 'vi' ? 'Viết bài mới' : 'New post')} width={760} onClose={onClose}
      footer={<>
        {editing && <button className="btn btn-icon" title={t('c.delete')} onClick={remove}><i className="bi bi-trash"></i></button>}
        <div className="grow"></div>
        <button className="btn" onClick={onClose}>{t('c.cancel')}</button>
        {f.status !== 'published' && <button className="btn" onClick={() => save()}><i className="bi bi-save"></i>{lang === 'vi' ? 'Lưu nháp' : 'Save draft'}</button>}
        <button className="btn btn-primary" onClick={() => save('published')}><i className="bi bi-globe2"></i>{f.status === 'published' ? (lang === 'vi' ? 'Lưu & đăng' : 'Save & publish') : (lang === 'vi' ? 'Đăng ngay' : 'Publish')}</button>
      </>}>
      <div className="col g16">
        <Field label={lang === 'vi' ? 'Tiêu đề' : 'Title'}><input className="input" autoFocus value={f.title} onChange={e => set('title', e.target.value)} placeholder={lang === 'vi' ? 'VD: S.W.O triển khai thành công dự án…' : 'Post title'} /></Field>
        <div className="flex g12 wrap items-start">
          <div style={{ flex:'2 1 260px' }}><Field label="Slug (URL)" hint={lang === 'vi' ? 'Tự sinh từ tiêu đề — sửa được' : 'Auto from title — editable'} hintError={dupSlug ? (lang === 'vi' ? 'Slug trùng với bài khác' : 'Duplicated slug') : ''}>
            <div className="flex g8 items-center">
              <input className={'input mono' + (dupSlug ? ' is-error' : '')} value={slug} onChange={e => { setAutoSlug(false); set('slug', postSlug(e.target.value) || e.target.value); }} />
              <button className={'btn btn-sm' + (autoSlug ? ' btn-primary' : '')} title={lang === 'vi' ? 'Tự sinh theo tiêu đề' : 'Auto slug'} onClick={() => setAutoSlug(a => !a)}><i className="bi bi-magic"></i></button>
            </div>
          </Field></div>
          <div style={{ flex:'1 1 170px' }}><Field label={lang === 'vi' ? 'Danh mục' : 'Category'}><select className="select" value={f.category} onChange={e => set('category', e.target.value)}>{cats.map(c => <option key={c}>{c}</option>)}</select></Field></div>
          <div style={{ flex:'1 1 150px' }}><Field label={t('c.status')}><select className="select" value={f.status} onChange={e => set('status', e.target.value)}>{Object.keys(POST_STATUS).map(s => <option key={s} value={s}>{lang === 'vi' ? POST_STATUS[s].vi : POST_STATUS[s].en}</option>)}</select></Field></div>
        </div>
        <Field label={lang === 'vi' ? 'Ảnh bìa' : 'Cover image'} hint={lang === 'vi' ? 'Hiện ở danh sách tin trên website — nên dùng ảnh ngang (16:9)' : 'Shown on the website news list'}>
          <div className="flex g10 items-center">
            {f.coverUrl
              ? <img src={f.coverUrl} alt="cover" style={{ width:120, height:68, objectFit:'cover', borderRadius:'var(--r-md)', border:'1px solid var(--border)' }} />
              : <div className="subtle" style={{ width:120, height:68, display:'flex', alignItems:'center', justifyContent:'center', border:'1px dashed var(--border-strong)', borderRadius:'var(--r-md)', fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Chưa có ảnh' : 'No image'}</div>}
            <div className="col g8">
              <button className="btn btn-sm" onClick={() => fileRef.current && fileRef.current.click()} disabled={uploading}>{uploading ? <i className="bi bi-arrow-repeat spin"></i> : <i className="bi bi-upload"></i>}{lang === 'vi' ? 'Tải ảnh lên' : 'Upload'}</button>
              {f.coverUrl && <button className="btn btn-sm btn-ghost" onClick={() => set('coverUrl', '')}><i className="bi bi-x-lg"></i>{lang === 'vi' ? 'Bỏ ảnh' : 'Remove'}</button>}
            </div>
            <input ref={fileRef} type="file" accept="image/*" onChange={uploadCover} style={{ display:'none' }} />
          </div>
        </Field>
        <Field label={lang === 'vi' ? 'Mô tả ngắn' : 'Excerpt'} hint={lang === 'vi' ? 'Hiện dưới tiêu đề ở danh sách tin (1–2 câu)' : 'Shown under the title on the list'}>
          <textarea className="textarea" rows={2} value={f.excerpt} onChange={e => set('excerpt', e.target.value)} placeholder={lang === 'vi' ? 'Tóm tắt nội dung bài viết…' : 'Short summary…'} />
        </Field>
        <Field label={lang === 'vi' ? 'Nội dung' : 'Content'}>
          <RichEditor html={f.content} onInput={v => set('content', v)} placeholder={lang === 'vi' ? 'Nội dung bài viết…' : 'Post content…'} />
        </Field>
      </div>
    </Modal>
  );
}

/* ===================== Thiết lập trang — chỉnh TOÀN BỘ nội dung www.swo.vn =====================
   Chia 7 TAB theo khu vực trang. Nội dung lưu cms_settings key 'site' (0034); website đọc anon.
   Mặc định + merge từ /site/components/Content.jsx (SITE_DEFAULTS/mergeSite — 1 nguồn).
   Liên hệ từ website lưu cms_contacts (0035) — hộp thư trong tab Liên hệ. */

// Upload file lên bucket public 'website' → trả publicUrl.
async function siteUpload(file, folder){
  const ext = (file.name && file.name.split('.').pop() || 'png').toLowerCase().replace(/[^a-z0-9]/g, '') || 'png';
  const path = folder + '/' + Date.now() + '.' + ext;
  const { error } = await sb.storage.from('website').upload(path, file, { upsert:true });
  if (error) throw error;
  return sb.storage.from('website').getPublicUrl(path).data.publicUrl;
}

const SITE_TABS = [
  { id:'hf',        icon:'bi-layout-text-window-reverse', vi:'Header & footer', en:'Header & footer' },
  { id:'hero',      icon:'bi-stars',    vi:'Màn hình đầu',         en:'Hero' },
  { id:'about',     icon:'bi-gem',      vi:'Giới thiệu',           en:'About' },
  { id:'solutions', icon:'bi-boxes',    vi:'Sản phẩm & giải pháp', en:'Solutions' },
  { id:'projects',  icon:'bi-kanban',   vi:'Dự án',                en:'Projects' },
  { id:'clients',   icon:'bi-people',   vi:'Khách hàng & đối tác', en:'Clients' },
  { id:'contact',   icon:'bi-envelope-paper', vi:'Liên hệ',        en:'Contact' },
];

function SiteContentEditor(){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => lang === 'vi' ? vi : en;
  const [tab, setTab] = React.useState('hf');
  const [m, setM] = React.useState(null);          // null = đang tải
  const [busy, setBusy] = React.useState(false);
  const [msgs, setMsgs] = React.useState(null);    // hộp thư liên hệ (null = chưa tải)
  // MỌI hook nằm TRƯỚC early-return (Rules of Hooks).
  const fileRef = React.useRef();
  const uploadTo = React.useRef(null);
  React.useEffect(() => {
    let alive = true;
    DB.loadSiteContent().then(v => { if (alive) setM(mergeSite(v)); })
      .catch(() => { if (alive) setM(mergeSite(null)); });
    return () => { alive = false; };
  }, []);
  const loadMsgs = React.useCallback(() => { DB.loadContactMsgs().then(ms => setMsgs(ms || [])); }, []);
  React.useEffect(() => { if (tab === 'contact' && msgs === null) loadMsgs(); }, [tab]);
  if (!m) return <div className="page"><div className="subtle" style={{ padding:24 }}>{L('Đang tải…', 'Loading…')}</div></div>;

  // ---- state helpers (path 'a.b.c' — số = index mảng) ----
  const setPath = (path, v) => setM(s => {
    const next = JSON.parse(JSON.stringify(s)); const ks = path.split('.');
    let o = next; for (let i = 0; i < ks.length - 1; i++) o = o[ks[i]];
    o[ks[ks.length - 1]] = v; return next;
  });
  const setArr = (path, idx, key, v) => setM(s => {
    const next = JSON.parse(JSON.stringify(s)); const ks = path.split('.');
    let o = next; for (let i = 0; i < ks.length; i++) o = o[ks[i]];
    if (key == null) o[idx] = v; else o[idx][key] = v; return next;
  });
  const arrOp = (path, op, idx, blank) => setM(s => {
    const next = JSON.parse(JSON.stringify(s)); const ks = path.split('.');
    let o = next; for (let i = 0; i < ks.length; i++) o = o[ks[i]];
    if (op === 'add') o.push(JSON.parse(JSON.stringify(blank)));
    else if (op === 'del') o.splice(idx, 1);
    else if (op === 'up' && idx > 0) { const x = o[idx]; o[idx] = o[idx - 1]; o[idx - 1] = x; }
    else if (op === 'down' && idx < o.length - 1) { const x = o[idx]; o[idx] = o[idx + 1]; o[idx + 1] = x; }
    return next;
  });
  const get = (path) => path.split('.').reduce((o, k) => (o || {})[k], m);

  const save = async () => {
    setBusy(true);
    toast(L('Đã lưu — website cập nhật ngay lần tải sau', 'Saved — live on next page load'), 'success');
    try { await DB.saveSiteContent(m); } catch (e){ toast(L('Lưu thất bại: ', 'Save failed: ') + (e.message || e), 'error'); }
    finally { setBusy(false); }
  };

  // ---- upload ảnh dùng chung (input file ẩn + path đích) ----
  const pickFile = (path, folder) => { uploadTo.current = { path, folder }; fileRef.current && fileRef.current.click(); };
  const onFile = async (e) => {
    const f = e.target.files && e.target.files[0]; e.target.value = '';
    if (!f || !uploadTo.current) return;
    if (!window.SB_READY){ toast(L('Supabase chưa cấu hình', 'Supabase not configured'), 'error'); return; }
    try { const url = await siteUpload(f, uploadTo.current.folder); setPath(uploadTo.current.path, url); toast(L('Đã tải ảnh', 'Uploaded'), 'success'); }
    catch (err){ toast(L('Tải thất bại: ', 'Upload failed: ') + (err.message || err), 'error'); }
  };
  const syncPortalLogo = async (path) => {
    const src = (p.branding && p.branding.logo) || '';
    if (!src){ toast(L('Portal chưa có logo (Quản trị hệ thống → Logo)', 'Portal has no logo yet'), 'error'); return; }
    try {
      const blob = await (await fetch(src)).blob();
      const ext = (blob.type.split('/')[1] || 'png').replace('svg+xml', 'svg');
      const url = await siteUpload(new File([blob], 'logo.' + ext, { type:blob.type }), 'branding');
      setPath(path, url);
      toast(L('Đã lấy logo từ portal — bấm Lưu để áp dụng', 'Portal logo copied — press Save'), 'success');
    } catch (err){ toast(L('Không lấy được logo: ', 'Failed: ') + (err.message || err), 'error'); }
  };

  // ---- helper JSX (GỌI DẠNG HÀM {txt(...)} — không <Txt/> để khỏi remount input mất focus) ----
  const txt = (label, path, ph) => (
    <Field label={label}><input className="input" value={get(path) || ''} onChange={e => setPath(path, e.target.value)} placeholder={ph} /></Field>
  );
  const area = (label, path, rows, hint) => (
    <Field label={label} hint={hint}><textarea className="textarea" rows={rows || 2} value={get(path) || ''} onChange={e => setPath(path, e.target.value)} /></Field>
  );
  const logoBox = (label, path, dark) => { const v = get(path); return (
    <Field label={label}>
      <div className="flex g10 items-center wrap">
        <div style={{ width:150, height:52, display:'flex', alignItems:'center', justifyContent:'center', border:'1px dashed var(--border-strong)', borderRadius:'var(--r-md)', background:dark ? 'var(--ink)' : 'var(--bg)', padding:6 }}>
          {v ? <img src={v} alt="" style={{ maxWidth:'100%', maxHeight:'100%', objectFit:'contain' }} /> : <span className="subtle" style={{ fontSize:'var(--fs-sm)', color:dark ? 'rgba(255,255,255,.55)' : undefined }}>{L('Mặc định (SVG)', 'Default')}</span>}
        </div>
        <div className="col g8">
          <div className="flex g8">
            <button className="btn btn-sm" onClick={() => pickFile(path, 'branding')}><i className="bi bi-upload"></i>{L('Tải lên', 'Upload')}</button>
            <button className="btn btn-sm" onClick={() => syncPortalLogo(path)} title={L('Lấy logo đang dùng trong portal', 'Use portal logo')}><i className="bi bi-arrow-repeat"></i>{L('Dùng logo portal', 'Portal logo')}</button>
          </div>
          {v && <button className="btn btn-sm btn-ghost" onClick={() => setPath(path, '')}><i className="bi bi-x-lg"></i>{L('Về mặc định', 'Reset')}</button>}
        </div>
      </div>
    </Field>
  ); };
  // ô ảnh nhỏ cho item trong danh sách (upload / bỏ)
  const imgBox = (path, folder, w, h) => { const v = get(path); return (
    <div className="flex g8 items-center">
      {v ? <img src={v} alt="" style={{ width:w, height:h, objectFit:'cover', borderRadius:'var(--r-md)', border:'1px solid var(--border)', flex:'none' }} />
         : <div className="subtle" style={{ width:w, height:h, display:'flex', alignItems:'center', justifyContent:'center', border:'1px dashed var(--border-strong)', borderRadius:'var(--r-md)', fontSize:'var(--fs-sm)', flex:'none' }}><i className="bi bi-image"></i></div>}
      <div className="col g4">
        <button className="btn btn-sm" onClick={() => pickFile(path, folder)}><i className="bi bi-upload"></i>{L('Ảnh', 'Image')}</button>
        {v && <button className="btn btn-sm btn-ghost" onClick={() => setPath(path, '')}><i className="bi bi-x-lg"></i></button>}
      </div>
    </div>
  ); };
  const statRows = (path) => (
    <div className="col g8">
      {(get(path) || []).map((s, i) => (
        <div key={i} className="flex g8">
          <input className="input mono" style={{ width:96 }} value={s.n} onChange={e => setArr(path, i, 'n', e.target.value)} placeholder="100+" />
          <input className="input" style={{ flex:1 }} value={s.l} onChange={e => setArr(path, i, 'l', e.target.value)} placeholder={L('Nhãn', 'Label')} />
        </div>
      ))}
    </div>
  );
  const linesArea = (path, rows) => (
    <textarea className="textarea" rows={rows || 3} value={(get(path) || []).join('\n')}
      onChange={e => setPath(path, e.target.value.split('\n'))} onBlur={e => setPath(path, e.target.value.split('\n').map(x => x.trim()).filter(Boolean))} />
  );
  // khung 1 item trong danh sách động: số thứ tự + nút lên/xuống/xoá
  const itemBox = (label, path, i, total, children) => (
    <div key={i} className="col g8" style={{ padding:'10px 12px', background:'var(--bg-subtle)', borderRadius:'var(--r-lg)', border:'1px solid var(--border-light)' }}>
      <div className="flex g8 items-center">
        <span className="mono" style={{ width:22, height:22, flex:'none', display:'flex', alignItems:'center', justifyContent:'center', borderRadius:'var(--r-sm)', background:'var(--primary-light)', color:'var(--primary-ink)', fontWeight:700, fontSize:'var(--fs-sm)' }}>{i + 1}</span>
        <span className="grow" style={{ fontWeight:600, fontSize:'var(--fs-base)' }}>{label} {i + 1}</span>
        <button className="iconbtn" title={L('Lên', 'Up')} onClick={() => arrOp(path, 'up', i)} disabled={i === 0}><i className="bi bi-chevron-up"></i></button>
        <button className="iconbtn" title={L('Xuống', 'Down')} onClick={() => arrOp(path, 'down', i)} disabled={i === total - 1}><i className="bi bi-chevron-down"></i></button>
        <button className="iconbtn" title={L('Xoá', 'Delete')} onClick={() => arrOp(path, 'del', i)}><i className="bi bi-trash"></i></button>
      </div>
      {children}
    </div>
  );
  const addBtn = (label, path, blank) => (
    <button className="btn btn-subtle btn-sm" style={{ alignSelf:'flex-start' }} onClick={() => arrOp(path, 'add', null, blank)}><i className="bi bi-plus-lg"></i>{label}</button>
  );

  // ================= nội dung từng tab =================
  const tabHF = () => (
    <div className="masonry-grid">
      <div className="card"><div className="card-head"><h3><i className="bi bi-image" style={{ marginRight:6, color:'var(--primary)' }}></i>Logo</h3></div><div className="card-body col g14">
        {logoBox(L('Logo đầu trang (nền sáng)', 'Header logo (light bg)'), 'logoUrl')}
        {logoBox(L('Logo chân trang (nền tối) — trống = dùng logo trên', 'Footer logo (dark bg)'), 'logoDarkUrl', true)}
      </div></div>
      <div className="card"><div className="card-head"><h3><i className="bi bi-layout-text-window-reverse" style={{ marginRight:6, color:'var(--primary)' }}></i>Footer</h3></div><div className="card-body col g14">
        {txt(L('Tên công ty', 'Company'), 'footer.company')}
        {area(L('Mô tả ngắn', 'Description'), 'footer.desc')}
        <div className="flex g8 wrap">
          <div style={{ flex:2, minWidth:200 }}>{txt('Copyright', 'footer.copyright')}</div>
          <div style={{ flex:1, minWidth:130 }}>{txt('MST', 'footer.mst')}</div>
        </div>
        <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-info-circle" style={{ marginRight:4 }}></i>{L('Địa chỉ / SĐT / email ở footer lấy từ tab Liên hệ.', 'Address/phone/email come from the Contact tab.')}</div>
      </div></div>
    </div>
  );
  const tabHero = () => (
    <div className="masonry-grid">
      <div className="card"><div className="card-head"><h3><i className="bi bi-stars" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Màn hình đầu (Hero)', 'Hero')}</h3></div><div className="card-body col g14">
        {txt(L('Dòng badge', 'Badge'), 'hero.badge')}
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:120 }}>{txt(L('Tiêu đề dòng 1', 'Title 1'), 'hero.title1')}</div>
          <div style={{ flex:1, minWidth:120 }}>{txt(L('Dòng 2 (gradient)', 'Title 2'), 'hero.title2')}</div>
          <div style={{ flex:1, minWidth:120 }}>{txt(L('Dòng 3', 'Title 3'), 'hero.title3')}</div>
        </div>
        {area(L('Mô tả', 'Lead'), 'hero.lead', 3)}
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:140 }}>{txt(L('Nút chính', 'Primary CTA'), 'hero.ctaPrimary')}</div>
          <div style={{ flex:1, minWidth:140 }}>{txt(L('Nút phụ', 'Secondary CTA'), 'hero.ctaSecondary')}</div>
        </div>
        <Field label={L('4 số liệu', 'Stats')}>{statRows('hero.stats')}</Field>
      </div></div>
    </div>
  );
  const tabAbout = () => (
    <div className="masonry-grid">
      <div className="card"><div className="card-head"><h3><i className="bi bi-gem" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Giá trị cốt lõi & định hướng', 'About')}</h3></div><div className="card-body col g14">
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:140 }}>{txt('Eyebrow', 'about.eyebrow')}</div>
          <div style={{ flex:2, minWidth:200 }}>{txt(L('Tiêu đề', 'Heading'), 'about.heading')}</div>
        </div>
        {area(L('Mô tả', 'Lead'), 'about.lead', 3)}
        {(m.about.values || []).map((v, i) => (
          <div key={i} className="flex g8 wrap items-start" style={{ padding:'8px 10px', background:'var(--bg-subtle)', borderRadius:'var(--r-md)' }}>
            <div style={{ flex:'0 0 110px' }}><Field label={'EN ' + (i + 1)}><input className="input" value={v.en} onChange={e => setArr('about.values', i, 'en', e.target.value)} /></Field></div>
            <div style={{ flex:'0 0 130px' }}><Field label={L('Tên', 'Name')}><input className="input" value={v.vi} onChange={e => setArr('about.values', i, 'vi', e.target.value)} /></Field></div>
            <div style={{ flex:1, minWidth:180 }}><Field label={L('Mô tả', 'Desc')}><input className="input" value={v.d} onChange={e => setArr('about.values', i, 'd', e.target.value)} /></Field></div>
          </div>
        ))}
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:200 }}>{txt(L('Tầm nhìn — tiêu đề', 'Vision title'), 'about.visionTitle')}</div>
          <div style={{ flex:1, minWidth:200 }}>{txt(L('Sứ mệnh — tiêu đề', 'Mission title'), 'about.missionTitle')}</div>
        </div>
        {area(L('Tầm nhìn — nội dung', 'Vision'), 'about.vision')}
        {area(L('Sứ mệnh — nội dung', 'Mission'), 'about.mission')}
      </div></div>
      <div className="card"><div className="card-head"><h3><i className="bi bi-123" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Dải con số', 'Stats band')}</h3></div><div className="card-body col g14">
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:140 }}>{txt('Eyebrow', 'band.eyebrow')}</div>
          <div style={{ flex:2, minWidth:200 }}>{txt(L('Tiêu đề', 'Heading'), 'band.heading')}</div>
        </div>
        <Field label={L('4 số liệu', 'Stats')}>{statRows('band.stats')}</Field>
      </div></div>
    </div>
  );
  const tabSolutions = () => (
    <div className="card"><div className="card-head"><h3><i className="bi bi-boxes" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Sản phẩm & giải pháp', 'Solutions')}</h3></div><div className="card-body col g14">
      <div className="flex g8 wrap">
        <div style={{ flex:1, minWidth:140 }}>{txt('Eyebrow', 'solutions.eyebrow')}</div>
        <div style={{ flex:2, minWidth:200 }}>{txt(L('Tiêu đề', 'Heading'), 'solutions.heading')}</div>
      </div>
      {area(L('Mô tả', 'Lead'), 'solutions.lead')}
      {(m.solutions.items || []).map((it, i, arr) => itemBox(L('Giải pháp', 'Solution'), 'solutions.items', i, arr.length, (
        <div className="flex g10 wrap items-start">
          <div style={{ flex:'0 0 auto' }}>{imgBox('solutions.items.' + i + '.img', 'solutions', 52, 52)}</div>
          <div className="col g8" style={{ flex:1, minWidth:220 }}>
            <input className="input" style={{ fontWeight:600 }} value={it.h} onChange={e => setArr('solutions.items', i, 'h', e.target.value)} placeholder={L('Tên giải pháp', 'Name')} />
            <textarea className="textarea" rows={2} value={it.p} onChange={e => setArr('solutions.items', i, 'p', e.target.value)} placeholder={L('Mô tả', 'Description')} />
          </div>
        </div>
      )))}
      {addBtn(L('Thêm giải pháp', 'Add solution'), 'solutions.items', { h:'', p:'', img:'' })}
      <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-info-circle" style={{ marginRight:4 }}></i>{L('Chưa có ảnh → dùng icon mặc định. Ảnh nên vuông (≥200×200).', 'No image → default icon. Square images work best.')}</div>
    </div></div>
  );
  const tabProjects = () => (
    <div className="card"><div className="card-head"><h3><i className="bi bi-kanban" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Dự án tiêu biểu', 'Projects')}</h3></div><div className="card-body col g14">
      <div className="flex g8 wrap">
        <div style={{ flex:1, minWidth:140 }}>{txt('Eyebrow', 'projects.eyebrow')}</div>
        <div style={{ flex:2, minWidth:200 }}>{txt(L('Tiêu đề mục', 'Heading'), 'projects.heading')}</div>
      </div>
      {(m.projects.items || []).map((it, i, arr) => itemBox(L('Dự án', 'Project'), 'projects.items', i, arr.length, (
        <div className="col g8">
          <div className="flex g10 wrap items-start">
            <div style={{ flex:'0 0 auto' }}>{imgBox('projects.items.' + i + '.imageUrl', 'project', 150, 86)}</div>
            <div className="col g8" style={{ flex:1, minWidth:220 }}>
              <input className="input" style={{ fontWeight:600 }} value={it.title || ''} onChange={e => setArr('projects.items', i, 'title', e.target.value)} placeholder={L('Tên dự án', 'Title')} />
              <div className="flex g8 wrap">
                <input className="input" style={{ flex:1, minWidth:130 }} value={it.tag || ''} onChange={e => setArr('projects.items', i, 'tag', e.target.value)} placeholder={L('Nhãn (VD: Software · AppSheet)', 'Tag')} />
                <input className="input" style={{ flex:1, minWidth:130 }} value={it.client || ''} onChange={e => setArr('projects.items', i, 'client', e.target.value)} placeholder={L('Khách hàng', 'Client')} />
              </div>
            </div>
          </div>
          <Field label={L('Các ý chính (mỗi dòng 1 ý)', 'Points (1/line)')}>
            <textarea className="textarea" rows={3} value={(it.points || []).join('\n')}
              onChange={e => setArr('projects.items', i, 'points', e.target.value.split('\n'))}
              onBlur={e => setArr('projects.items', i, 'points', e.target.value.split('\n').map(x => x.trim()).filter(Boolean))} />
          </Field>
        </div>
      )))}
      {addBtn(L('Thêm dự án', 'Add project'), 'projects.items', { tag:'', title:'', client:'', points:[], imageUrl:'', ctaLabel:'Xem chi tiết dự án' })}
      <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-info-circle" style={{ marginRight:4 }}></i>{L('Ảnh dự án nên ngang (16:9). Không có dự án nào → website tự ẩn mục này.', 'Landscape (16:9) images. Empty list hides the section.')}</div>
    </div></div>
  );
  const tabClients = () => (
    <div className="masonry-grid">
      <div className="card"><div className="card-head"><h3><i className="bi bi-people" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Khách hàng', 'Clients')}</h3></div><div className="card-body col g14">
        {txt(L('Tiêu đề', 'Heading'), 'clients.heading')}
        {(m.clients.items || []).map((c, i, arr) => itemBox(L('Khách hàng', 'Client'), 'clients.items', i, arr.length, (
          <div className="flex g10 wrap items-start">
            <div style={{ flex:'0 0 auto' }}>{imgBox('clients.items.' + i + '.logoUrl', 'clients', 96, 54)}</div>
            <div className="col g8" style={{ flex:1, minWidth:200 }}>
              <div className="flex g8 wrap">
                <input className="input" style={{ flex:1, minWidth:140 }} value={c.name} onChange={e => setArr('clients.items', i, 'name', e.target.value)} placeholder={L('Tên khách hàng', 'Name')} />
                <input className="input mono" style={{ flex:'0 0 120px' }} value={c.mark} onChange={e => setArr('clients.items', i, 'mark', e.target.value)} placeholder={L('Chữ logo', 'Mark')} title={L('Hiện khi chưa tải logo ảnh', 'Shown when no logo image')} />
              </div>
              <input className="input" value={c.cat} onChange={e => setArr('clients.items', i, 'cat', e.target.value)} placeholder={L('Lĩnh vực (VD: IT / Software)', 'Category')} />
            </div>
          </div>
        )))}
        {addBtn(L('Thêm khách hàng', 'Add client'), 'clients.items', { name:'', cat:'', mark:'', logoUrl:'' })}
      </div></div>
      <div className="card"><div className="card-head"><h3><i className="bi bi-diagram-3" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Đối tác', 'Partners')}</h3></div><div className="card-body col g14">
        {txt(L('Tiêu đề đối tác', 'Partners heading'), 'clients.partnersHeading')}
        <Field label={L('Đối tác (mỗi dòng 1 tên — hiện dạng chip chữ)', 'Partners (1 per line)')}>{linesArea('clients.partners', 5)}</Field>
      </div></div>
    </div>
  );
  const unread = (msgs || []).filter(x => !x.read).length;
  const tabContact = () => (
    <div className="masonry-grid">
      <div className="card"><div className="card-head"><h3><i className="bi bi-telephone" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Thông tin liên hệ (hiện trên web + footer)', 'Contact info')}</h3></div><div className="card-body col g14">
        {area(L('Tiêu đề (xuống dòng = ngắt dòng trên web)', 'Heading'), 'contact.heading', 2)}
        {area(L('Mô tả', 'Lead'), 'contact.lead')}
        {txt(L('Địa chỉ', 'Address'), 'contact.address')}
        <div className="flex g8 wrap">
          <div style={{ flex:1, minWidth:150 }}>{txt(L('Điện thoại', 'Phone'), 'contact.phone')}</div>
          <div style={{ flex:1, minWidth:150 }}>{txt('Email', 'contact.email')}</div>
        </div>
        <Field label={L('Form nhận liên hệ', 'Contact form')}>
          <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
            <input type="checkbox" checked={get('contact.formEnabled') !== false} onChange={e => setPath('contact.formEnabled', e.target.checked)} />
            {L('Hiển thị form trên web (tắt = chỉ hiện email/điện thoại)', 'Show form on site (off = show email/phone only)')}
          </label>
        </Field>
        <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-shield-check" style={{ marginRight:4 }}></i>{L('Form trên web được bảo vệ nhiều lớp: ô bẫy bot ẩn, chặn gửi quá nhanh/dồn dập và xác thực chống bot.', 'The web form is protected by honeypot, rate-limits and anti-bot verification.')}</div>
      </div></div>
      <div className="card"><div className="card-head"><h3><i className="bi bi-inbox" style={{ marginRight:6, color:'var(--primary)' }}></i>{L('Liên hệ ghi nhận từ website', 'Submissions')}{unread > 0 && <span className="badge badge-accent" style={{ marginLeft:8 }}>{unread} {L('mới', 'new')}</span>}</h3>
        <button className="btn btn-ghost btn-sm" onClick={loadMsgs}><i className="bi bi-arrow-clockwise"></i>{L('Tải lại', 'Refresh')}</button></div>
        <div className="card-body col g8">
          {msgs === null && <div className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Đang tải…', 'Loading…')}</div>}
          {msgs && msgs.length === 0 && <EmptyState icon="bi-inbox" text={L('Chưa có liên hệ nào từ website.', 'No submissions yet.')} />}
          {(msgs || []).map(x => (
            <div key={x.id} className="col g4" style={{ padding:'10px 12px', borderRadius:'var(--r-md)', background:x.read ? 'var(--bg-subtle)' : 'var(--unread-bg)', border:'1px solid var(--border-light)' }}>
              <div className="flex g8 items-center">
                {!x.read && <span style={{ width:7, height:7, borderRadius:'50%', background:'var(--primary)', flex:'none' }}></span>}
                <span style={{ fontWeight:600 }}>{x.name}</span>
                <a className="mono" style={{ fontSize:'var(--fs-sm)', color:'var(--primary)' }} href={'mailto:' + x.email}>{x.email}</a>
                <span className="grow"></span>
                <span className="mono subtle" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate((x.createdAt || '').slice(0, 10))}</span>
                <button className="iconbtn" title={x.read ? L('Đánh dấu chưa xử lý', 'Mark unread') : L('Đánh dấu đã xử lý', 'Mark read')}
                  onClick={() => { setMsgs(s => s.map(y => y.id === x.id ? { ...y, read:!x.read } : y)); DB.markContactRead(x.id, !x.read).catch(() => {}); }}>
                  <i className={'bi ' + (x.read ? 'bi-envelope' : 'bi-check2')}></i></button>
                <button className="iconbtn" title={t('c.delete')} onClick={async () => {
                  if (!(await confirm({ message:L('Xóa liên hệ của ', 'Delete message from ') + x.name + '?', confirmLabel:L('Xóa', 'Delete') }))) return;
                  setMsgs(s => s.filter(y => y.id !== x.id)); DB.deleteContactMsg(x.id).catch(() => {});
                }}><i className="bi bi-trash"></i></button>
              </div>
              <div style={{ fontSize:'var(--fs-base)', whiteSpace:'pre-line' }}>{x.message}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );

  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Thiết lập trang', 'Site content')}</h1><div className="sub">{L('Toàn bộ chữ, số liệu, logo & ảnh trên www.swo.vn — Lưu là áp dụng ngay.', 'All text, numbers, logo & images on www.swo.vn.')}</div></div>
        <div className="flex g8">
          <a className="btn" href="https://www.swo.vn" target="_blank" rel="noreferrer"><i className="bi bi-box-arrow-up-right"></i>{L('Xem website', 'Open site')}</a>
          <button className="btn btn-primary" onClick={save} disabled={busy}><i className="bi bi-save"></i>{t('c.save')}</button>
        </div>
      </div>
      <input ref={fileRef} type="file" accept="image/*" onChange={onFile} style={{ display:'none' }} />
      <div className="tabs" style={{ marginBottom:14 }}>
        {SITE_TABS.map(tb => (
          <button key={tb.id} className={'tab' + (tab === tb.id ? ' is-active' : '')} onClick={() => setTab(tb.id)}>
            <i className={'bi ' + tb.icon} style={{ marginRight:6 }}></i>{lang === 'vi' ? tb.vi : tb.en}
          </button>
        ))}
      </div>
      {tab === 'hf' && tabHF()}
      {tab === 'hero' && tabHero()}
      {tab === 'about' && tabAbout()}
      {tab === 'solutions' && tabSolutions()}
      {tab === 'projects' && tabProjects()}
      {tab === 'clients' && tabClients()}
      {tab === 'contact' && tabContact()}
      <div className="flex justify-end" style={{ marginTop:14 }}>
        <button className="btn btn-primary" onClick={save} disabled={busy}><i className="bi bi-save"></i>{t('c.save')}</button>
      </div>
    </div>
  );
}

function WebSettings(){
  const { t, lang } = useLang();
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('c.settings')}</h1><div className="sub">{lang === 'vi' ? 'Danh mục tin & thông tin kết nối website.' : 'News categories & website connection.'}</div></div></div>
      <div className="settings-grid">
        <CatalogEditor catalogKey="websiteCats" title={lang === 'vi' ? 'Danh mục tin tức' : 'News categories'} stringMode={true} />
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Kết nối website' : 'Website connection'}</h3></div><div className="card-body col g8" style={{ fontSize:'var(--fs-base)' }}>
          <div className="det-row"><span className="dk">Website</span><span className="dv mono">www.swo.vn</span></div>
          <div className="det-row"><span className="dk">{lang === 'vi' ? 'Nguồn dữ liệu' : 'Data source'}</span><span className="dv">{lang === 'vi' ? 'Bài "Đã đăng" tự xuất hiện (đọc trực tiếp, không cần deploy lại)' : 'Published posts appear automatically'}</span></div>
          <div className="det-row"><span className="dk">{lang === 'vi' ? 'Ảnh bài viết' : 'Images'}</span><span className="dv">{lang === 'vi' ? 'Lưu kho ảnh công khai (bucket website)' : 'Public storage bucket'}</span></div>
        </div></div>
      </div>
    </div>
  );
}

/* Dashboard widget */
function WebsiteWidget(){
  const p = usePortal();
  const { lang } = useLang();
  const posts = p.posts;
  const pub = posts.filter(w => w.status === 'published').length;
  const top = posts.slice(0, 4);
  return (
    <WidgetShell wid="website" icon="bi-globe2" iconColor="#0FB0AB" title={lang === 'vi' ? 'Quản trị website' : 'Website'}
      onViewAll={() => p.openApp(WEB_APP)} onNew={() => p.quick('website')} newLabel={lang === 'vi' ? 'Viết bài' : 'New post'}>
      <div className="flex g16" style={{ marginBottom:8 }}>
        <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{posts.length}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'bài viết' : 'posts'}</div></div>
        <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700, color:'var(--success)' }}>{pub}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'đã đăng' : 'published'}</div></div>
      </div>
      {top.map(w => (
        <div key={w.id} className="list-row" style={{ cursor:'pointer' }} onClick={() => { p.openApp(WEB_APP); }}>
          <span className="mono" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)', width:56 }}>{w.id}</span>
          <span className="grow truncate">{w.title}</span>
          <PostStatusBadge status={w.status} />
        </div>
      ))}
    </WidgetShell>
  );
}

Object.assign(window, { WebsiteModule, PostEditModal, WebsiteWidget, POST_STATUS, postSlug });
