// contracts.jsx — CONTRACTS module + ContractStore + widget.
const HD_APP = APPS.find(a => a.id === 'contracts');

function catLabel(id, lang){ const list = (window.CatalogData && window.CatalogData.contractCats) || CONTRACT_CATEGORIES; const c = list.find(x => x.id === id); return c ? c[lang] || c.vi : id; }
// Đối tác của HĐ theo chiều: bán → Khách hàng; mua → Nhà cung cấp (VendorStore, có sau ở module NCC).
function hdParty(c){
  if (contractDir(c) === 'buy') return (window.VendorStore && VendorStore.byCode(c.customer)) || {};
  return CustomerStore.byCode(c.customer) || {};
}
// Nhận diện hợp đồng cho user: LUÔN dùng SỐ hợp đồng (contractNo); chỉ khi chưa có số mới dùng mã code nội bộ.
function hdNo(c){ return (c && c.contractNo) ? c.contractNo : (c ? c.code : ''); }
// ----- Phiên bản hợp đồng (theo dõi điều chỉnh) -----
// Snapshot phần nội dung "thương lượng được" của HĐ (đính kèm vào mỗi phiên bản): số/ngày/đối tác/hàng hoá/lịch TT
// + ĐÓNG BĂNG lời mở đầu & điều khoản HIỆU LỰC tại thời điểm chốt (override riêng HĐ HOẶC mẫu chung lúc đó) →
// mỗi phiên bản tự chứa đúng câu chữ, KHÔNG đổi khi sau này sửa mẫu chung. Cần app_settings (S) để giải mẫu chung.
function contractSnapshotData(c, S){
  const tpl = window.contractDocTemplate ? window.contractDocTemplate(S || {}, c) : {};
  return {
    contractNo:c.contractNo || '', signDate:c.signDate, direction:c.direction || 'sell', category:c.category,
    customer:c.customer || '', rep:c.rep || null, termType:c.termType, startDate:c.startDate || null, endDate:c.endDate || null,
    remindBefore:c.remindBefore,
    items:(c.items || []).map(it => ({ name:it.name, unit:it.unit, qty:it.qty, price:it.price, vat:it.vat })),
    schedule:(c.schedule || []).map(s => ({ no:s.no, amount:s.amount, dueDate:s.dueDate, condition:s.condition, paid:!!s.paid, paidDate:s.paidDate || null })),
    docOverrides:{
      intro: tpl.intro != null ? tpl.intro : ((c.docOverrides && c.docOverrides.intro) || ''),
      terms: tpl.terms != null ? tpl.terms : ((c.docOverrides && c.docOverrides.terms) || ''),
      partyAuth: (c.docOverrides && c.docOverrides.partyAuth) || null,   // đóng băng uỷ quyền đối tác theo phiên bản
    },
  };
}
// Tạo 1 bản ghi phiên bản mới (số thứ tự kế tiếp) từ hiện trạng HĐ. S = app_settings (để đóng băng điều khoản hiệu lực).
function makeRevision(c, note, by, S){
  const revs = Array.isArray(c.revisions) ? c.revisions : [];
  const no = revs.reduce((m, r) => Math.max(m, r.no || 0), 0) + 1;
  return { no, at:new Date().toISOString(), note:note || '', by:by || '', data:contractSnapshotData(c, S) };
}
function HdDirBadge({ c }){
  const { lang } = useLang(); const d = CONTRACT_DIRECTIONS[contractDir(c)];
  return <span className={'badge ' + d.cls}>{d[lang] || d.vi}</span>;
}
function genContractCode(list, year){
  const yr = year || TODAY.slice(0, 4);
  const max = list.reduce((m, c) => { const mm = (c.code || '').match(new RegExp('HD-' + yr + '-(\\d+)')); return mm ? Math.max(m, +mm[1]) : m; }, 0);
  return 'HD-' + yr + '-' + String(max + 1).padStart(3, '0');
}

function HdStatusBadge({ c }){
  const { lang } = useLang(); const st = contractStatus(c); const s = CONTRACT_STATUS[st];
  return <span className={'badge ' + s.cls}><i className="dot"></i>{s[lang] || s.vi}</span>;
}

// Icon cho từng trạng thái HĐ (dùng ở sidebar "sổ" + toolbar) — đồng bộ để nhận diện nhanh.
const HD_STATUS_ICON = { draft:'bi-pencil', active:'bi-check-circle', expiring:'bi-alarm', warranty:'bi-shield-check', completed:'bi-check2-all', cancelled:'bi-x-circle' };

function ContractModule(){
  const p = usePortal();
  const { t, lang } = useLang();
  const sub = p.route.sub || 'overview';
  const expiring = p.contracts.filter(c => contractStatus(c) === 'expiring').length;
  const sellN = p.contracts.filter(c => contractDir(c) === 'sell').length;
  const buyN = p.contracts.filter(c => contractDir(c) === 'buy').length;
  // "Sổ xuống" theo trạng thái cho từng chiều — mỗi trạng thái kèm số lượng; id = "<dir>:<status>".
  const statusChildren = (dir) => {
    const inDir = p.contracts.filter(c => contractDir(c) === dir);
    return Object.keys(CONTRACT_STATUS).map(k => ({
      id: dir + ':' + k, icon: HD_STATUS_ICON[k], label: CONTRACT_STATUS[k][lang],
      count: inDir.filter(c => contractStatus(c) === k).length,
    }));
  };
  const items = [
    { id:'overview', icon:'bi-pie-chart', label:t('c.overview') },
    { id:'sell', icon:'bi-arrow-down-circle', label:lang === 'vi' ? 'Hợp đồng bán' : 'Sales contracts', count:sellN, children:statusChildren('sell') },
    { id:'buy', icon:'bi-arrow-up-circle', label:lang === 'vi' ? 'Hợp đồng mua' : 'Purchase contracts', count:buyN, children:statusChildren('buy') },
    { id:'report', icon:'bi-bar-chart', label:t('c.report') },
    { id:'settings', icon:'bi-gear', label:t('c.settings') },
  ];
  // Khi xem chi tiết → highlight nhóm theo chiều của hợp đồng. Ở list bán/mua → highlight con theo trạng thái.
  const detailC = sub === 'detail' ? p.contracts.find(c => c.code === p.route.id) : null;
  let activeId;
  if (sub === 'detail') activeId = detailC && contractDir(detailC) === 'buy' ? 'buy' : 'sell';
  else if (sub === 'sell' || sub === 'buy') activeId = sub + ':' + (p.route.status || 'all');
  else if (sub === 'list') activeId = 'sell';
  else activeId = sub;
  // id chứa ":" = "<dir>:<status>" từ sidebar sổ → tách thành sub + status lọc.
  const go = (id, extra) => {
    if (typeof id === 'string' && id.indexOf(':') >= 0) {
      const [dir, status] = id.split(':');
      return p.navigate({ name:'module', module:'contracts', sub:dir, status });
    }
    return p.navigate({ name:'module', module:'contracts', sub:id, ...extra });
  };
  return (<>
    <ModuleSidebar app={HD_APP} items={items} active={activeId} onNavigate={go} />
    <main className="main">
      {sub === 'overview' && <HdOverview go={go} expiring={expiring} />}
      {sub === 'sell' && <HdList go={go} fixedDir="sell" />}
      {sub === 'buy' && <HdList go={go} fixedDir="buy" />}
      {sub === 'list' && <HdList go={go} />}
      {sub === 'detail' && <HdDetail code={p.route.id} go={go} />}
      {sub === 'report' && <HdReport />}
      {sub === 'help' && <ModuleHelp moduleId="contracts" app={HD_APP} />}
      {sub === 'settings' && <HdSettings />}
    </main>
  </>);
}

function RenewBanner({ contracts, go }){
  const { t, lang } = useLang();
  const exp = contracts.filter(c => contractStatus(c) === 'expiring');
  if (!exp.length) return null;
  return (
    <div className="card" style={{ borderColor:'var(--warning)', background:'var(--warning-bg)', marginBottom:14 }}>
      <div className="card-body col g8">
        <div className="flex g8 items-center" style={{ color:'var(--warning)', fontWeight:600 }}><i className="bi bi-alarm" style={{ fontSize:16 }}></i>{t('hd.renewHint')} ({exp.length})</div>
        {exp.map(c => {
          const d = daysBetween(TODAY, c.endDate);
          return (
            <div key={c.code} className="flex items-center g8" style={{ fontSize:'var(--fs-md)' }}>
              <span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{hdNo(c)}</span>
              <span className="grow truncate">{hdParty(c).name}</span>
              <span className="badge badge-warning">{d} {t('hd.daysLeft')}</span>
              <button className="btn btn-sm" onClick={() => go('detail', { id:c.code })}>{t('c.details')}</button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function HdOverview({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const cs = p.contracts;
  const totalValue = cs.reduce((s, c) => s + contractTotals(c).total, 0);
  const byCat = {}; cs.forEach(c => { const v = contractTotals(c).total; byCat[c.category] = (byCat[c.category] || 0) + v; });
  const byStatus = {}; cs.forEach(c => { const st = contractStatus(c); byStatus[st] = (byStatus[st] || 0) + 1; });
  const colors = ['var(--primary)','var(--accent)','#7A5AF8','var(--warning)','var(--success)'];
  // tương tác mới nhất: sự kiện ký hợp đồng + các đợt đã thu
  const recent = [];
  cs.forEach(c => {
    const cust = hdParty(c);
    recent.push({ date:c.signDate, icon:'bi-file-earmark-check', color:'#0B4FBF',
      title:(lang === 'vi' ? 'Ký hợp đồng ' : 'Signed ') + c.code,
      sub:(cust.shortName || c.customer) + ' · ' + catLabel(c.category, lang),
      onClick:() => go('detail', { id:c.code }) });
    (c.schedule || []).forEach(s => { if (s.paid && s.paidDate) recent.push({
      date:s.paidDate, icon:'bi-cash-coin', color:'#16A34A',
      title:(lang === 'vi' ? 'Thu đợt ' : 'Paid #') + s.no + ' · ' + vndShort(s.amount),
      sub:(cust.shortName || c.customer) + ' · ' + c.code,
      onClick:() => go('detail', { id:c.code }) }); });
  });
  // Sự kiện tạo/cập nhật hợp đồng (theo created_at/updated_at).
  cs.forEach(c => { const cust = hdParty(c); const ev = recordActivity(c, { create:(lang === 'vi' ? 'Tạo hợp đồng ' : 'New contract ') + c.code, update:(lang === 'vi' ? 'Cập nhật hợp đồng ' : 'Updated ') + c.code, sub:(cust.shortName || c.customer) + ' · ' + catLabel(c.category, lang) }, () => go('detail', { id:c.code })); if (ev) recent.push(ev); });
  recent.sort((a, b) => ((a.ts || a.date) < (b.ts || b.date) ? 1 : -1));
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('hd.title')}</h1><div className="sub">{lang === 'vi' ? 'Tổng quan danh mục hợp đồng.' : 'Contract portfolio overview.'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('contracts')}><i className="bi bi-plus-lg"></i>{t('hd.new')}</button></div>
      <div className="kpi-strip">
        <Kpi label={t('hd.title')} value={cs.length} icon="bi-file-earmark-text" />
        <Kpi label={CONTRACT_STATUS.active[lang]} value={cs.filter(c => contractStatus(c) === 'active').length} icon="bi-check-circle" />
        <Kpi label={t('hd.expiringSoon')} value={cs.filter(c => contractStatus(c) === 'expiring').length} icon="bi-alarm" />
        <Kpi label={t('hd.value')} value={vndShort(totalValue)} icon="bi-cash-stack" />
      </div>
      <RenewBanner contracts={cs} go={go} />
      <div className="dash-grid">
        <div className="card span2"><div className="card-head"><h3>{lang === 'vi' ? 'Giá trị theo loại HĐ' : 'Value by category'}</h3></div><div className="card-body">
          <DistribBars rows={Object.entries(byCat).sort((a, b) => b[1] - a[1]).map(([k, v], i) => ({ label:catLabel(k, lang), value:Math.round(v / 1e6), color:colors[i % colors.length] }))} />
          <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:6 }}>{lang === 'vi' ? 'Đơn vị: triệu đồng' : 'Unit: million VND'}</div>
        </div></div>
        <div className="card"><div className="card-head"><h3>{t('c.status')}</h3></div><div className="card-body">
          <DistribBars rows={Object.keys(CONTRACT_STATUS).filter(k => byStatus[k]).map((k, i) => ({ label:CONTRACT_STATUS[k][lang], value:byStatus[k] || 0, color:colors[i % colors.length] }))} />
        </div></div>
      </div>
      <RecentInteractions items={recent} onViewAll={() => go('list')} />
    </div>
  );
}

function HdList({ go, fixedDir }){
  const p = usePortal();
  const { t, lang } = useLang();
  const [q, setQ] = React.useState('');
  const [dir, setDir] = React.useState('all');
  const [cat, setCat] = React.useState('all');
  // Trạng thái lọc lấy từ route → đồng bộ với sidebar "sổ"; đổi trạng thái = điều hướng.
  const status = p.route.status || 'all';
  const setStatus = (s) => go(fixedDir || 'list', { status: s });
  const effDir = fixedDir || dir;   // fixedDir (từ sidebar HĐ mua/bán) ưu tiên
  const srt = useTableSort();
  const list = srt.sortRows(p.contracts.filter(c => {
    const party = hdParty(c);
    return (effDir === 'all' || contractDir(c) === effDir) && (cat === 'all' || c.category === cat) && (status === 'all' || contractStatus(c) === status) &&
      (!q || (c.code + (party.name || '') + (party.shortName || '')).toLowerCase().includes(q.toLowerCase()));
  }), { code:c => hdNo(c), dir:c => contractDir(c), cat:c => catLabel(c.category, lang), party:c => { const pt = hdParty(c); return pt.shortName || pt.name || c.customer; },
    sign:c => c.signDate, term:c => c.termType === 'term' ? c.endDate : '', value:c => contractTotals(c).total, status:c => contractStatus(c) });
  const title = fixedDir ? (lang === 'vi' ? 'Hợp đồng ' + CONTRACT_DIRECTIONS[fixedDir].vi.toLowerCase() : CONTRACT_DIRECTIONS[fixedDir].en + ' contracts') : t('c.list');
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{title}</h1><div className="sub">{list.length} {lang === 'vi' ? 'hợp đồng' : 'contracts'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('contracts', fixedDir ? { direction:fixedDir } : null)}><i className="bi bi-plus-lg"></i>{t('hd.new')}</button></div>
      <div className="toolbar">
        <div className="topsearch" style={{ width:220, 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>
        {!fixedDir && <div className="seg">
          {['all','sell','buy'].map(s => <button key={s} className={dir === s ? 'is-active' : ''} onClick={() => setDir(s)}><i className={'bi ' + ({ all:'bi-arrow-down-up', sell:'bi-arrow-down-circle', buy:'bi-arrow-up-circle' }[s])}></i>{s === 'all' ? t('c.all') : CONTRACT_DIRECTIONS[s][lang]}</button>)}
        </div>}
        <select className="select" style={{ width:'auto' }} value={cat} onChange={e => setCat(e.target.value)}>
          <option value="all">{t('hd.category')}: {t('c.all')}</option>
          {(p.catalogs.contractCats || []).map(c => <option key={c.id} value={c.id}>{c[lang]}</option>)}
        </select>
        <div className="seg">
          {['all','active','expiring','draft','completed'].map(s => <button key={s} className={status === s ? 'is-active' : ''} onClick={() => setStatus(s)}><i className={'bi ' + ({ all:'bi-grid-1x2', active:'bi-check-circle', expiring:'bi-alarm', draft:'bi-pencil', completed:'bi-check2-all' }[s])}></i>{s === 'all' ? t('c.all') : CONTRACT_STATUS[s][lang]}</button>)}
        </div>
      </div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="code" sort={srt}>{t('hd.code')}</ThSort><ThSort k="dir" sort={srt}>{t('hd.direction')}</ThSort><ThSort k="cat" sort={srt}>{t('hd.category')}</ThSort><ThSort k="party" sort={srt}>{t('hd.party')}</ThSort><ThSort k="sign" sort={srt}>{t('hd.signDate')}</ThSort><ThSort k="term" sort={srt}>{t('hd.term')}</ThSort><ThSort k="value" sort={srt} className="num">{t('hd.value')}</ThSort><ThSort k="status" sort={srt}>{t('c.status')}</ThSort></tr></thead>
          <tbody>
            {list.map(c => {
              const party = hdParty(c);
              return (
                <tr key={c.code} onClick={() => go('detail', { id:c.code })}>
                  <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{hdNo(c)}</span></td>
                  <td><HdDirBadge c={c} /></td>
                  <td className="muted">{catLabel(c.category, lang)}</td>
                  <td style={{ fontWeight:500 }}>{party.shortName || party.name || c.customer}</td>
                  <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(c.signDate)}</td>
                  <td className="muted">{c.termType === 'term' ? <span className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(c.endDate)}</span> : TERM_TYPES.once[lang]}</td>
                  <td className="num" style={{ fontWeight:600 }}>{vndShort(contractTotals(c).total)}</td>
                  <td><HdStatusBadge c={c} /></td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {!list.length && <EmptyState text={t('c.empty')} />}
      </div></div>
    </div>
  );
}

function HdDetail({ code, go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const [editing, setEditing] = React.useState(false);
  const [payReq, setPayReq] = React.useState(null);
  const [costLinkOpen, setCostLinkOpen] = React.useState(false);
  const [warrantyOpen, setWarrantyOpen] = React.useState(false);
  const [acceptOpen, setAcceptOpen] = React.useState(false);
  const [docOpen, setDocOpen] = React.useState(false);
  const [revNoteOpen, setRevNoteOpen] = React.useState(false);
  const [revView, setRevView] = React.useState(null);
  const [termsOpen, setTermsOpen] = React.useState(false);
  const c = p.contracts.find(x => x.code === code);
  if (!c) return <div className="page"><EmptyState text={t('c.empty')} /></div>;
  const isSell = contractDir(c) === 'sell';
  const showCost = canViewCost(p);
  const margin = isSell && showCost ? dealMargin(c, p.contracts) : null;
  const srcQuote = isSell ? ((p.quotes || []).find(q => q.convertedTo === c.code) || null) : null;
  const ownerDeal = (p.deals || []).find(d => (d.contracts || []).includes(c.code)) || null;
  const cust = hdParty(c);
  const rep = c.rep ? (ContactStore.byCode(c.rep) || {}) : {};
  const tot = contractTotals(c);
  const st = contractStatus(c);
  const paidSum = c.schedule.filter(s => s.paid).reduce((s, x) => s + x.amount, 0);
  // HĐ bán = "thu" (nhận tiền KH); HĐ mua = "trả" (trả NCC).
  const payVerb = isSell
    ? { done:t('hd.paid'), undone:t('hd.unpaid'), mark:t('hd.markPaid') }
    : { done:lang === 'vi' ? 'Đã trả' : 'Paid', undone:lang === 'vi' ? 'Chưa trả' : 'Unpaid', mark:lang === 'vi' ? 'Đánh dấu đã trả' : 'Mark paid' };
  const markPaid = (no) => {
    const ns = { ...c, schedule:c.schedule.map(s => s.no === no ? { ...s, paid:true, paidDate:TODAY } : s) };
    p.patchContract(c.code, ns); toast(lang === 'vi' ? 'Đã ghi nhận thanh toán đợt ' + no : 'Payment ' + no + ' recorded', 'success');
  };
  const renew = async () => {
    if (c.termType !== 'term') return;
    const newStart = c.endDate;
    const d0 = new Date(c.endDate); d0.setFullYear(d0.getFullYear() + 1);
    const newEnd = d0.toISOString().slice(0, 10);
    // HĐ có CHU KỲ thu (thuê dịch vụ): gia hạn tự sinh NỐI TIẾP các đợt cho kỳ mới — giá giữ nguyên theo tổng HĐ.
    const cyc = (c.billingCycle && c.billingCycle !== 'once') ? c.billingCycle : null;
    const maxNo = (c.schedule || []).reduce((m, s) => Math.max(m, +s.no || 0), 0);
    const extra = cyc ? genCycleInstallments(cyc, newStart, newEnd, contractTotals(c).total, maxNo + 1) : [];
    if (extra.length && !(await confirm({
      message: lang === 'vi'
        ? 'Gia hạn đến ' + fmtDate(newEnd) + ' — tự thêm ' + extra.length + ' đợt thanh toán (' + ((CONTRACT_BILLING_CYCLES[cyc] || {}).vi || cyc) + ' · tổng ' + vnd(contractTotals(c).total) + ')?'
        : 'Renew to ' + fmtDate(newEnd) + ' — add ' + extra.length + ' installments (' + ((CONTRACT_BILLING_CYCLES[cyc] || {}).en || cyc) + ')?',
      confirmLabel: lang === 'vi' ? 'Gia hạn' : 'Renew',
    }))) return;
    p.patchContract(c.code, { ...c, startDate:newStart, endDate:newEnd, status:'active', schedule:[...(c.schedule || []), ...extra] });
    toast((lang === 'vi' ? 'Đã gia hạn hợp đồng đến ' + fmtDate(newEnd) : 'Renewed to ' + fmtDate(newEnd)) + (extra.length ? (lang === 'vi' ? ' · thêm ' + extra.length + ' đợt' : ' · +' + extra.length + ' installments') : ''), 'success');
  };
  const closeToWarranty = (months) => {
    const m = +months || 12;
    const end = new Date(TODAY); end.setMonth(end.getMonth() + m);
    const endStr = end.toISOString().slice(0, 10);
    p.patchContract(c.code, { ...c, status:'completed', warranty:{ months:m, start:TODAY, end:endStr } });
    toast((lang === 'vi' ? 'Đã kết thúc HĐ · bảo hành đến ' : 'Contract closed · warranty until ') + fmtDate(endStr), 'success');
  };
  const wInfo = warrantyInfo(c);
  const allPaid = c.schedule.every(s => s.paid);
  // ----- Quy trình bán/mua: hành động "làm bước kế" (bán tự động) -----
  const payNext = () => { const next = c.schedule.find(s => !s.paid); if (next) markPaid(next.no); };
  const confirmDeliver = async () => {
    if (await confirm({ message:lang === 'vi' ? 'Xác nhận đã giao hàng & nghiệm thu hợp đồng này?' : 'Confirm delivery & acceptance?', confirmLabel:lang === 'vi' ? 'Xác nhận' : 'Confirm' })) {
      p.patchContract(c.code, { ...c, acceptedDate:TODAY });
      toast(lang === 'vi' ? 'Đã ghi nhận giao hàng & nghiệm thu' : 'Delivery & acceptance recorded', 'success');
    }
  };
  // ----- Phiên bản (theo dõi điều chỉnh) -----
  const nextRevNo = (c.revisions || []).reduce((m, r) => Math.max(m, r.no || 0), 0) + 1;
  const saveRevision = (note) => {
    const v = makeRevision(c, note, p.me && p.me.name, p.appSettings);
    p.patchContract(c.code, { ...c, revisions:[...(c.revisions || []), v] });
    toast((lang === 'vi' ? 'Đã lưu bản điều chỉnh V' : 'Saved revision V') + v.no, 'success');
    setRevNoteOpen(false);
  };
  const restoreRev = async (v) => {
    if (await confirm({ message:(lang === 'vi' ? 'Khôi phục nội dung hợp đồng về bản V' + v.no + '? Nội dung hiện tại sẽ bị thay bằng bản này (lịch sử phiên bản vẫn được giữ).' : 'Restore contract content to V' + v.no + '? Current content will be replaced.'), confirmLabel:lang === 'vi' ? 'Khôi phục' : 'Restore' })) {
      p.patchContract(c.code, { ...c, ...v.data });
      toast((lang === 'vi' ? 'Đã khôi phục về V' : 'Restored to V') + v.no, 'success');
    }
  };
  const flowActions = isSell ? {
    issue:    { label:lang === 'vi' ? 'Xuất kho' : 'Issue', icon:'bi-box-arrow-up', run:() => p.openNewIssue(c.code) },
    deliver:  { label:lang === 'vi' ? 'Xác nhận đã giao & nghiệm thu' : 'Confirm delivery', icon:'bi-truck', run:confirmDeliver },
    pay:      { label:lang === 'vi' ? 'Đánh dấu thu đợt tới' : 'Mark next paid', icon:'bi-cash-coin', run:payNext },
    warranty: { label:lang === 'vi' ? 'Kết thúc HĐ & bảo hành' : 'Close & warranty', icon:'bi-shield-check', run:() => setWarrantyOpen(true) },
  } : {
    receive:  { label:lang === 'vi' ? 'Nhập kho' : 'Receive', icon:'bi-box-arrow-in-down', run:() => p.openNewReceipt({ source:'buy', buyContract:c.code, vendor:c.customer, fromItems:c.items }) },
    payV:     { label:lang === 'vi' ? 'Đánh dấu trả đợt tới' : 'Mark next paid', icon:'bi-cash-coin', run:payNext },
  };
  return (
    <div className="page pagefade">
      <div className="page-head">
        <div className="flex g12 items-center">
          <button className="btn btn-ghost btn-icon" onClick={() => go(isSell ? 'sell' : 'buy')}><i className="bi bi-arrow-left"></i></button>
          <div><h1 style={{ fontSize:'var(--fs-2xl)' }}>{hdNo(c)}</h1>
            <div className="sub flex g8 items-center"><HdDirBadge c={c} /> · {catLabel(c.category, lang)}{c.termType === 'term' && c.billingCycle && c.billingCycle !== 'once' && <span className="badge badge-info" title={lang === 'vi' ? 'Chu kỳ thanh toán' : 'Billing cycle'}><i className="bi bi-arrow-repeat" style={{ marginRight:3 }}></i>{(CONTRACT_BILLING_CYCLES[c.billingCycle] || {})[lang] || c.billingCycle}</span>} · <HdStatusBadge c={c} /> · <span style={{ fontWeight:600 }}>{vnd(tot.total)}</span>{c.acceptedDate && <span className="badge badge-success" title={lang === 'vi' ? 'Đã giao & nghiệm thu' : 'Delivered & accepted'}><i className="bi bi-truck" style={{ marginRight:3 }}></i>{lang === 'vi' ? 'Đã nghiệm thu ' : 'Accepted '}{fmtDate(c.acceptedDate)}</span>}</div></div>
        </div>
        <div className="flex g8 wrap" style={{ justifyContent:'flex-end' }}>
          {st === 'expiring' && <button className="btn btn-accent" onClick={renew}><i className="bi bi-arrow-repeat"></i>{t('hd.renew')}</button>}
          {isSell && (st === 'active' || st === 'expiring') && <button className="btn" onClick={() => setWarrantyOpen(true)}><i className="bi bi-shield-check"></i>{lang === 'vi' ? 'Kết thúc HĐ' : 'Close & warranty'}</button>}
          <button className="btn" onClick={() => setEditing(true)}><i className="bi bi-pencil"></i>{t('c.edit')}</button>
          <button className="btn btn-icon" title={t('c.delete')} onClick={async () => { if (await confirm({ message:(lang === 'vi' ? 'Xóa hợp đồng ' : 'Delete contract ') + c.code + '?', confirmLabel:lang === 'vi' ? 'Xóa' : 'Delete' })) { p.deleteContract(c.code); toast((lang === 'vi' ? 'Đã xóa ' : 'Deleted ') + c.code, 'success'); go(isSell ? 'sell' : 'buy'); } }}><i className="bi bi-trash"></i></button>
          {isSell && (st === 'active' || st === 'expiring') && <button className="btn" onClick={() => p.openNewIssue(c.code)}><i className="bi bi-box-arrow-up"></i>{lang === 'vi' ? 'Xuất kho' : 'Issue stock'}</button>}
          {!isSell && st !== 'cancelled' && <button className="btn" onClick={() => p.openNewReceipt({ source:'buy', buyContract:c.code, vendor:c.customer, fromItems:c.items })}><i className="bi bi-box-arrow-in-down"></i>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button>}
          {st !== 'draft' && <button className="btn" onClick={() => setAcceptOpen(true)}><i className="bi bi-clipboard2-check"></i>{lang === 'vi' ? 'Biên bản nghiệm thu' : 'Acceptance'}</button>}
          <button className={(c.docOverrides && (c.docOverrides.intro != null || c.docOverrides.terms != null)) ? 'btn btn-accent' : 'btn'} onClick={() => setTermsOpen(true)} title={(c.docOverrides && (c.docOverrides.intro != null || c.docOverrides.terms != null)) ? (lang === 'vi' ? 'Hợp đồng đang dùng điều khoản riêng' : 'Using custom terms') : ''}><i className="bi bi-file-text"></i>{lang === 'vi' ? 'Soạn điều khoản' : 'Edit terms'}</button>
          <button className="btn btn-primary" onClick={() => setDocOpen(true)}><i className="bi bi-file-earmark-pdf"></i>{lang === 'vi' ? 'Xuất hợp đồng' : 'Export contract'}</button>
          {editing && <NewContractModal editing={c} onClose={() => setEditing(false)} />}
        </div>
      </div>

      <PurchaseFlow quote={srcQuote} contract={c} actions={flowActions} />

      {st === 'expiring' && (
        <div className="card" style={{ borderColor:'var(--warning)', background:'var(--warning-bg)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'var(--warning)', fontWeight:600 }}>
          <i className="bi bi-alarm" style={{ fontSize:16 }}></i>{t('hd.renewHint')} — {daysBetween(TODAY, c.endDate)} {t('hd.daysLeft')} ({fmtDate(c.endDate)})
          <button className="btn btn-sm btn-accent" style={{ marginLeft:'auto' }} onClick={renew}><i className="bi bi-arrow-repeat"></i>{t('hd.renew')}</button>
        </div></div>
      )}

      {st === 'warranty' && wInfo && (
        <div className="card" style={{ borderColor:'#7A5AF8', background:'color-mix(in srgb, #7A5AF8 8%, transparent)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'#7A5AF8', fontWeight:600 }}>
          <i className="bi bi-shield-check" style={{ fontSize:16 }}></i>{lang === 'vi' ? 'Đang trong thời gian bảo hành' : 'Under warranty'} — {lang === 'vi' ? 'đến ' : 'until '}{fmtDate(wInfo.end)} ({wInfo.daysLeft} {t('hd.daysLeft')})
        </div></div>
      )}

      <div className="split" style={{ marginBottom:14 }}>
        <div className="card"><div className="card-head"><h3>{t('hd.parties')}</h3></div><div className="card-body">
          <div className="flex g12 items-center" style={{ marginBottom:10 }}>
            <Avatar name={cust.shortName || cust.name} size={36} square color="var(--primary)" />
            <div><div style={{ fontWeight:600 }}>{cust.name}</div><div className="mono subtle" style={{ fontSize:'var(--fs-sm)' }}>{c.customer}</div></div>
          </div>
          <div className="det-row"><span className="dk">{t('cust.mst')}</span><span className="dv mono">{cust.mst}</span></div>
          <div className="det-row"><span className="dk">{t('hd.address')}</span><span className="dv" style={{ maxWidth:600 }}>{cust.address || '—'}</span></div>
          <div className="det-row"><span className="dk">{t('hd.rep')}</span><span className="dv">{rep.name || '—'}</span></div>
          <div className="det-row"><span className="dk">{t('ct.title2')}</span><span className="dv">{rep.title || '—'}</span></div>
        </div></div>
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Thông tin hiệu lực' : 'Term info'}</h3></div><div className="card-body">
          <div className="det-row"><span className="dk">{lang === 'vi' ? 'Số hợp đồng' : 'Contract no.'}</span><span className="dv mono" style={{ fontWeight:600 }}>{c.contractNo || '—'}</span></div>
          <div className="det-row"><span className="dk">{t('hd.signDate')}</span><span className="dv mono">{fmtDate(c.signDate)}</span></div>
          <div className="det-row"><span className="dk">{t('hd.termType')}</span><span className="dv">{TERM_TYPES[c.termType][lang]}</span></div>
          {c.termType === 'term' && <>
            <div className="det-row"><span className="dk">{t('hd.start')}</span><span className="dv mono">{fmtDate(c.startDate)}</span></div>
            <div className="det-row"><span className="dk">{t('hd.end')}</span><span className="dv mono">{fmtDate(c.endDate)}</span></div>
            <div className="det-row"><span className="dk">{lang === 'vi' ? 'Nhắc gia hạn' : 'Renew reminder'}</span><span className="dv">{lang === 'vi' ? 'Trước ' : '' }{c.remindBefore} {lang === 'vi' ? 'ngày' : 'days before'}</span></div>
          </>}
        </div></div>
      </div>

      {/* products */}
      <div className="card" style={{ marginBottom:14 }}>
        <div className="card-head"><h3>{t('hd.items')}</h3></div>
        <div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th style={{ width:36 }}>#</th><th>{t('hd.items')}</th><th>{t('hd.unit')}</th><th className="num">{t('hd.qty')}</th><th className="num">{t('hd.price')}</th><th className="num">{t('hd.amount')}</th><th className="num">{t('hd.vat')}</th><th className="num">{t('hd.total')}</th></tr></thead>
            <tbody>
              {c.items.map((it, i) => { const line = it.qty * it.price; const v = line * it.vat / 100; return (
                <tr key={i} style={{ cursor:'default' }}>
                  <td className="muted">{i + 1}</td><td style={{ fontWeight:500 }}>{it.name}</td><td className="muted">{it.unit}</td>
                  <td className="num">{it.qty}</td><td className="num mono">{vnd(it.price)}</td><td className="num mono">{vnd(line)}</td>
                  <td className="num muted">{it.vat}%</td><td className="num mono" style={{ fontWeight:600 }}>{vnd(line + v)}</td>
                </tr>
              ); })}
            </tbody>
            <tfoot>
              <tr style={{ cursor:'default' }}><td colSpan="5"></td><td className="num muted">{t('hd.subtotal')}</td><td colSpan="2" className="num mono">{vnd(tot.sub)}</td></tr>
              <tr style={{ cursor:'default' }}><td colSpan="5"></td><td className="num muted">{t('hd.vatTotal')}</td><td colSpan="2" className="num mono">{vnd(tot.vat)}</td></tr>
              <tr style={{ cursor:'default', background:'var(--bg-subtle)' }}><td colSpan="5"></td><td className="num" style={{ fontWeight:700 }}>{t('hd.total')}</td><td colSpan="2" className="num mono" style={{ fontWeight:700, color:'var(--primary)' }}>{vnd(tot.total)}</td></tr>
            </tfoot>
          </table>
        </div>
      </div>

      {/* schedule */}
      <div className="card">
        <div className="card-head"><h3>{t('hd.schedule')}</h3>
          <span className="badge badge-info">{payVerb.done}: {vndShort(paidSum)} / {vndShort(tot.total)}</span></div>
        <div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th style={{ width:50 }}>{t('hd.installment')}</th><th className="num">{lang === 'vi' ? 'Số tiền' : 'Amount'}</th><th>{t('hd.dueDate')}</th><th>{t('hd.condition')}</th><th>{t('c.status')}</th><th></th></tr></thead>
            <tbody>
              {c.schedule.map(s => (
                <tr key={s.no} style={{ cursor:'default' }}>
                  <td style={{ fontWeight:600 }}>{s.no}</td>
                  <td className="num mono" style={{ fontWeight:600 }}>{vnd(s.amount)}</td>
                  <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(s.dueDate)}</td>
                  <td className="muted">{s.condition}</td>
                  <td>{s.paid ? <span className="badge badge-success"><i className="dot"></i>{payVerb.done}{s.paidDate ? ' · ' + fmtDate(s.paidDate) : ''}</span> : <span className="badge badge-warning"><i className="dot"></i>{payVerb.undone}</span>}</td>
                  <td style={{ textAlign:'right' }}><div className="flex g8" style={{ justifyContent:'flex-end' }}>
                    {isSell && <button className="btn btn-sm" onClick={() => setPayReq(s)}><i className="bi bi-filetype-pdf"></i>{t('hd.payReqShort')}</button>}
                    {!s.paid && <button className="btn btn-sm btn-primary" onClick={() => markPaid(s.no)}><i className="bi bi-check2"></i>{payVerb.mark}</button>}
                  </div></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* Phiên bản & điều chỉnh hợp đồng — chốt hiện trạng thành V1, V2… để theo dõi thương lượng + xuất PDF từng bản */}
      <div className="card" style={{ marginTop:14 }}>
        <div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}><i className="bi bi-clock-history" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Phiên bản & điều chỉnh' : 'Versions & revisions'}</h3>
          <button className="btn btn-sm btn-primary" onClick={() => setRevNoteOpen(true)}><i className="bi bi-bookmark-plus"></i>{lang === 'vi' ? 'Lưu bản điều chỉnh' : 'Save revision'}</button></div>
        <div className="card-body">
          {(c.revisions && c.revisions.length) ? (
            <div className="col g8">
              {c.revisions.slice().reverse().map(v => (
                <div key={v.no} className="list-row" style={{ alignItems:'center', cursor:'default' }}>
                  <span className="badge badge-info" style={{ fontWeight:700, minWidth:42, justifyContent:'center' }}>V{v.no}</span>
                  <div className="grow" style={{ minWidth:0 }}>
                    <div className="truncate" style={{ fontWeight:500 }}>{v.note || (lang === 'vi' ? '(không ghi chú)' : '(no note)')}</div>
                    <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate((v.at || '').slice(0, 10))}{(v.at || '').length >= 16 ? ' ' + v.at.slice(11, 16) : ''}{v.by ? ' · ' + v.by : ''} · <span className="mono">{vnd(contractTotals({ ...c, ...v.data }).total)}</span></div>
                  </div>
                  <div className="flex g8">
                    <button className="btn btn-sm" onClick={() => setRevView(v)}><i className="bi bi-filetype-pdf"></i>{lang === 'vi' ? 'Xem / PDF' : 'View / PDF'}</button>
                    <button className="btn btn-sm btn-ghost" title={lang === 'vi' ? 'Khôi phục nội dung về bản này' : 'Restore this version'} onClick={() => restoreRev(v)}><i className="bi bi-arrow-counterclockwise"></i>{lang === 'vi' ? 'Khôi phục' : 'Restore'}</button>
                  </div>
                </div>
              ))}
            </div>
          ) : <div className="subtle" style={{ fontSize:'var(--fs-sm)', padding:'4px 0' }}>{lang === 'vi' ? 'Chưa có phiên bản nào. Bấm "Lưu bản điều chỉnh" để chốt hiện trạng thành một phiên bản.' : 'No versions yet.'}</div>}
        </div>
      </div>

      <div style={{ marginTop:14 }}><FileManager scope="contracts" code={c.code} /></div>

      {/* Phiếu xuất kho liên quan — chỉ HĐ bán */}
      {isSell && (() => { const isl = (p.issues || []).filter(i => i.contract === c.code); return isl.length ? (
        <div className="card" style={{ marginTop:14 }}>
          <div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}><i className="bi bi-box-arrow-up" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Phiếu xuất kho' : 'Stock issues'}</h3>
            <button className="btn btn-ghost btn-sm" onClick={() => p.openNewIssue(c.code)}>{lang === 'vi' ? 'Tạo phiếu' : 'New'}</button></div>
          <div className="card-body">{isl.map(i => (
            <div key={i.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'inventory', sub:'issue', id:i.code })}>
              <span className="mono" style={{ fontWeight:600, color:'var(--accent)', width:104 }}>{i.code}</span><span className="grow truncate">{(i.lines || []).length} {lang === 'vi' ? 'mặt hàng' : 'items'}{i.issuedDate ? ' · ' + fmtDate(i.issuedDate) : ''}</span><IssueStatusBadge status={i.status} />
            </div>
          ))}</div>
        </div>
      ) : null; })()}

      {/* Phiếu nhập kho liên quan — chỉ HĐ mua */}
      {!isSell && (() => { const rcl = (p.receipts || []).filter(r => r.buyContract === c.code); return (
        <div className="card" style={{ marginTop:14 }}>
          <div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}><i className="bi bi-box-arrow-in-down" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Phiếu nhập kho' : 'Stock receipts'}</h3>
            {st !== 'cancelled' && <button className="btn btn-ghost btn-sm" onClick={() => p.openNewReceipt({ source:'buy', buyContract:c.code, vendor:c.customer, fromItems:c.items })}>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button>}</div>
          <div className="card-body">{rcl.length ? rcl.map(r => (
            <div key={r.code} className="list-row">
              <span className="mono" style={{ fontWeight:600, color:'var(--accent)', width:120 }}>{r.code}</span><span className="grow truncate">{(r.lines || []).length} {lang === 'vi' ? 'mặt hàng' : 'items'}{r.date ? ' · ' + fmtDate(r.date) : ''}</span><span className="badge badge-success">{lang === 'vi' ? 'Đã nhập' : 'Received'}</span>
              <button className="iconbtn" title={lang === 'vi' ? 'In phiếu nhập' : 'Print'} onClick={() => { if (!printDocument(stockReceiptHTML(r, p))) toast(lang === 'vi' ? 'Trình duyệt chặn cửa sổ in' : 'Pop-up blocked', 'error'); }}><i className="bi bi-printer"></i></button>
            </div>
          )) : <div className="subtle" style={{ fontSize:'var(--fs-sm)', padding:'4px 0' }}>{lang === 'vi' ? 'Chưa có phiếu nhập kho cho hợp đồng này' : 'No receipts for this contract'}</div>}</div>
        </div>
      ); })()}

      {/* QR định danh hỗ trợ + phiếu hỗ trợ liên quan — chỉ HĐ bán */}
      {contractDir(c) === 'sell' && c.status !== 'draft' && contractStatus(c) !== 'cancelled' && (
        <div className="split" style={{ marginTop:14 }}>
          <ContractQRCard c={c} />
          <div className="card"><div className="card-head"><h3 style={{ fontSize:'var(--fs-md)' }}><i className="bi bi-headset" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Phiếu hỗ trợ' : 'Support tickets'}</h3>
            <button className="btn btn-sm" onClick={() => p.quick('support', { contract:c.code })}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Tạo phiếu' : 'New ticket'}</button></div>
            <div className="card-body">
              {(() => { const tks = (p.tickets || []).filter(tk => tk.contract === c.code); return tks.length ? tks.map(tk => (
                <div key={tk.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'support', sub:'detail', id:tk.code })}>
                  <span className="mono" style={{ fontWeight:600, color:'var(--accent)', fontSize:'var(--fs-base)', width:104 }}>{tk.code}</span>
                  <span className="grow truncate">{tk.title}</span>
                  <TicketStatusBadge status={tk.status} />
                </div>
              )) : <div className="subtle" style={{ fontSize:'var(--fs-sm)', padding:'4px 0' }}>{lang === 'vi' ? 'Chưa có phiếu hỗ trợ' : 'No support tickets'}</div>; })()}
            </div>
          </div>
        </div>
      )}

      {/* Giá vốn & Lợi nhuận — chỉ HĐ bán (mua-đi-bán-lại) */}
      {margin && (
        <div className="card" style={{ marginTop:14 }}>
          <div className="card-head"><h3><i className="bi bi-percent" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Lợi nhuận thương vụ' : 'Deal profit'}{margin.costList.length > 0 && <span style={{ marginLeft:8 }}><MarginPill pct={margin.marginPct} /></span>}</h3>
            <div className="flex g8 items-center">
              {ownerDeal && <button className="btn btn-ghost btn-sm" onClick={() => p.navigate({ name:'module', module:'deals', sub:'detail', id:ownerDeal.code })}><i className="bi bi-diagram-3"></i>{lang === 'vi' ? 'Thuộc thương vụ ' : 'Deal '}{ownerDeal.code}</button>}
              <button className="btn btn-ghost btn-sm" onClick={() => setCostLinkOpen(true)}><i className="bi bi-link-45deg"></i>{lang === 'vi' ? 'Liên kết HĐ mua' : 'Link purchases'}</button>
            </div></div>
          <div className="card-body">
            <div className="kpi-strip" style={{ gridTemplateColumns:'repeat(4, 1fr)' }}>
              <Kpi label={lang === 'vi' ? 'Doanh thu' : 'Revenue'} value={vndShort(margin.revenue)} icon="bi-arrow-down-circle" />
              <Kpi label={lang === 'vi' ? 'Giá vốn' : 'Cost'} value={vndShort(margin.cost)} icon="bi-arrow-up-circle" />
              <Kpi label={lang === 'vi' ? 'Lợi nhuận' : 'Profit'} value={vndShort(margin.profit)} icon="bi-graph-up-arrow" />
              <Kpi label={lang === 'vi' ? 'Biên LN' : 'Margin'} value={margin.marginPct + '%'} icon="bi-percent" />
            </div>
            {margin.costList.length ? (
              <div style={{ marginTop:12 }}>
                <div className="subtle" style={{ marginBottom:6, fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'HĐ mua (giá vốn):' : 'Cost contracts:'}</div>
                {margin.costList.map(cc => (
                  <div key={cc.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => go('detail', { id:cc.code })}>
                    <span className="mono" style={{ fontWeight:600, color:'var(--primary)', width:120 }}>{hdNo(cc)}</span>
                    <span className="grow truncate">{hdParty(cc).shortName || cc.customer}</span>
                    <span className="mono" style={{ fontWeight:600, color:'var(--error)' }}>{vndShort(contractTotals(cc).total)}</span>
                  </div>
                ))}
              </div>
            ) : <div className="subtle" style={{ marginTop:10, fontSize:'var(--fs-base)' }}>{lang === 'vi' ? 'Chưa liên kết HĐ mua — bấm "Liên kết HĐ mua" để tính giá vốn & lợi nhuận.' : 'No linked purchases yet.'}</div>}
          </div>
        </div>
      )}

      {payReq && <PaymentRequestModal contract={c} inst={payReq} onClose={() => setPayReq(null)} />}
      {costLinkOpen && <CostLinkModal sell={c} onClose={() => setCostLinkOpen(false)} />}
      {warrantyOpen && <WarrantyModal contract={c} allPaid={allPaid} onClose={() => setWarrantyOpen(false)} onConfirm={(m) => { closeToWarranty(m); setWarrantyOpen(false); }} />}
      {acceptOpen && <AcceptanceModal contract={c} onClose={() => setAcceptOpen(false)} />}
      {docOpen && <ContractDocModal contract={c} onClose={() => setDocOpen(false)} />}
      {revNoteOpen && <RevisionNoteModal nextNo={nextRevNo} onClose={() => setRevNoteOpen(false)} onSave={saveRevision} />}
      {revView && <ContractDocModal contract={{ ...c, ...revView.data }} versionLabel={'V' + revView.no} onClose={() => setRevView(null)} />}
      {termsOpen && <ContractTermsModal contract={c} onClose={() => setTermsOpen(false)} />}
    </div>
  );
}

/* Soạn điều khoản (+ lời mở đầu) RIÊNG cho 1 hợp đồng — ghi đè mẫu chung; vắng thì dùng mẫu chung */
function ContractTermsModal({ contract, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const eff = window.contractDocTemplate ? contractDocTemplate(p.appSettings, contract) : { intro:'', terms:'' };
  const [intro, setIntro] = React.useState(eff.intro || '');
  const [terms, setTerms] = React.useState(eff.terms || '');
  const [previewing, setPreviewing] = React.useState(false);
  const hasOvr = !!(contract.docOverrides && (contract.docOverrides.intro != null || contract.docOverrides.terms != null));
  const save = () => { p.patchContract(contract.code, { ...contract, docOverrides:{ intro, terms } }); toast(L('Đã lưu điều khoản riêng cho hợp đồng này', 'Saved custom terms for this contract'), 'success'); onClose(); };
  const resetGlobal = async () => {
    if (await confirm({ message:L('Khôi phục về mẫu điều khoản CHUNG (xoá phần soạn riêng của hợp đồng này)?', "Revert to the shared terms template (discard this contract's custom terms)?"), confirmLabel:L('Khôi phục', 'Revert') })) {
      p.patchContract(contract.code, { ...contract, docOverrides:{} });
      toast(L('Đã khôi phục về mẫu chung', 'Reverted to the shared template'), 'success'); onClose();
    }
  };
  return (
    <>
    <Modal title={L('Soạn điều khoản · ', 'Custom terms · ') + (contract.contractNo || contract.code)} width={840} onClose={onClose}
      footer={<>
        <button className="btn btn-ghost" disabled={!hasOvr} onClick={resetGlobal} title={hasOvr ? '' : L('Đang dùng mẫu chung', 'Already using shared template')}><i className="bi bi-arrow-counterclockwise"></i>{L('Khôi phục mẫu chung', 'Use shared template')}</button>
        <div className="grow"></div>
        <button className="btn" onClick={onClose}>{t('c.cancel')}</button>
        <button className="btn" onClick={() => setPreviewing(true)}><i className="bi bi-eye"></i>{L('Xem trước', 'Preview')}</button>
        <button className="btn btn-primary" onClick={save}><i className="bi bi-check2"></i>{t('c.save')}</button>
      </>}>
      <div className="col g12">
        <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>
          {hasOvr
            ? <><i className="bi bi-pencil-square" style={{ marginRight:4, color:'var(--accent)' }}></i>{L('Hợp đồng này đang dùng điều khoản RIÊNG (khác mẫu chung). Sửa & Lưu để cập nhật, hoặc Khôi phục mẫu chung.', 'This contract uses custom terms. Edit & Save to update, or revert to the shared template.')}</>
            : <><i className="bi bi-info-circle" style={{ marginRight:4 }}></i>{L('Đang hiển thị nội dung từ mẫu chung. Sửa & Lưu để áp dụng RIÊNG cho hợp đồng này (không ảnh hưởng hợp đồng khác).', 'Showing the shared template. Edit & Save to apply only to this contract (other contracts unaffected).')}</>}
        </div>
        <Field label={L('Lời mở đầu / căn cứ', 'Preamble / recitals')}>
          <textarea className="textarea" rows={5} value={intro} onChange={e => setIntro(e.target.value)} placeholder={window.DEFAULT_CONTRACT_DOC_INTRO} style={{ lineHeight:1.6 }}></textarea>
        </Field>
        <Field label={L('Điều khoản', 'Terms')} hint={L('Giữ {BANG_HANG_HOA} và {LICH_THANH_TOAN} để tự chèn bảng hàng hoá & lịch thanh toán đúng chỗ.', 'Keep {BANG_HANG_HOA} and {LICH_THANH_TOAN} to auto-insert the item & payment tables.')}>
          <textarea className="textarea" rows={16} value={terms} onChange={e => setTerms(e.target.value)} placeholder={window.DEFAULT_CONTRACT_DOC_TERMS} style={{ lineHeight:1.6 }}></textarea>
        </Field>
        <VarHelp list={DOC_VAR_HELP_CONTRACT} lang={lang} />
      </div>
    </Modal>
    {previewing && <ContractDocModal contract={{ ...contract, docOverrides:{ intro, terms } }} versionLabel={L('bản nháp', 'draft')} onClose={() => setPreviewing(false)} />}
    </>
  );
}

/* Lưu hiện trạng HĐ thành một phiên bản (kèm ghi chú lý do điều chỉnh) */
function RevisionNoteModal({ nextNo, onClose, onSave }){
  const { t, lang } = useLang();
  const [note, setNote] = React.useState('');
  return (
    <Modal title={(lang === 'vi' ? 'Lưu bản điều chỉnh · V' : 'Save revision · V') + nextNo} width={460} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={() => onSave(note)}><i className="bi bi-bookmark-plus"></i>{lang === 'vi' ? 'Lưu bản điều chỉnh' : 'Save revision'}</button></>}>
      <div className="col g12">
        <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Chốt hiện trạng hợp đồng thành một phiên bản để theo dõi. Ghi chú giúp nhớ lý do điều chỉnh (vd: yêu cầu của khách hàng).' : 'Freeze the current contract state as a version. The note records why it was revised.'}</div>
        <Field label={lang === 'vi' ? 'Ghi chú điều chỉnh' : 'Revision note'}>
          <input className="input" autoFocus value={note} onChange={e => setNote(e.target.value)} placeholder={lang === 'vi' ? 'VD: Giảm giá đợt 2 theo yêu cầu KH' : 'e.g. Adjusted per customer request'} onKeyDown={e => { if (e.key === 'Enter') onSave(note); }} />
        </Field>
      </div>
    </Modal>
  );
}

/* Kết thúc HĐ → chuyển sang bảo hành (chỉ HĐ bán đang hiệu lực) */
function WarrantyModal({ contract, allPaid, onClose, onConfirm }){
  const { t, lang } = useLang();
  const [months, setMonths] = React.useState('12');
  const end = (() => { const d = new Date(TODAY); d.setMonth(d.getMonth() + (+months || 0)); return d.toISOString().slice(0, 10); })();
  return (
    <Modal title={lang === 'vi' ? 'Kết thúc & chuyển bảo hành' : 'Close & start warranty'} width={460} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={() => onConfirm(months)}><i className="bi bi-shield-check"></i>{lang === 'vi' ? 'Kết thúc HĐ' : 'Close'}</button></>}>
      <div className="col g16">
        <Field label={lang === 'vi' ? 'Thời hạn bảo hành (tháng)' : 'Warranty period (months)'}><input className="input" type="number" min="0" value={months} onChange={e => setMonths(e.target.value)} style={{ width:120 }} /></Field>
        <div className="det-row"><span className="dk">{lang === 'vi' ? 'Bảo hành đến' : 'Warranty until'}</span><span className="dv mono" style={{ fontWeight:600, color:'#7A5AF8' }}>{fmtDate(end)}</span></div>
        {!allPaid && <div className="flex g8 items-start" style={{ color:'var(--warning)', fontSize:'var(--fs-base)' }}><i className="bi bi-exclamation-triangle" style={{ marginTop:2 }}></i><span>{lang === 'vi' ? 'Còn đợt thanh toán chưa thu — vẫn có thể kết thúc nếu đã nghiệm thu.' : 'Some installments unpaid — you can still close if accepted.'}</span></div>}
      </div>
    </Modal>
  );
}

/* Modal liên kết HĐ mua làm giá vốn cho 1 HĐ bán (mua-đi-bán-lại) */
function CostLinkModal({ sell, onClose }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const buys = p.contracts.filter(c => contractDir(c) === 'buy');
  const [sel, setSel] = React.useState(sell.costContracts || []);
  const toggle = (code) => setSel(s => s.includes(code) ? s.filter(x => x !== code) : [...s, code]);
  const save = () => { p.patchContract(sell.code, { ...sell, costContracts:sel }); toast(lang === 'vi' ? 'Đã cập nhật giá vốn' : 'Cost links updated', 'success'); onClose(); };
  return (
    <Modal title={lang === 'vi' ? 'Liên kết HĐ mua (giá vốn)' : 'Link purchase contracts'} width={620} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{t('c.cancel')}</button><button className="btn btn-primary" onClick={save}>{t('c.save')}</button></>}>
      <div className="col g8">
        {buys.length ? buys.map(c => (
          <label key={c.code} className="list-row" style={{ cursor:'pointer' }}>
            <input type="checkbox" checked={sel.includes(c.code)} onChange={() => toggle(c.code)} style={{ marginRight:4 }} />
            <span className="mono" style={{ fontWeight:600, color:'var(--primary)', width:120 }}>{hdNo(c)}</span>
            <span className="grow truncate">{hdParty(c).shortName || c.customer}</span>
            <span className="mono" style={{ fontWeight:600 }}>{vndShort(contractTotals(c).total)}</span>
          </label>
        )) : <EmptyState text={lang === 'vi' ? 'Chưa có hợp đồng mua nào' : 'No purchase contracts'} />}
      </div>
    </Modal>
  );
}

/* Báo cáo GỘP 2 luồng trên 1 màn hình (đồng bộ với báo cáo Báo giá): cột trái = MUA (NCC) · cột phải = BÁN (KH),
   dùng chung bộ lọc kỳ. Hai cột đồng cấu trúc: KPI 2×2 → giá trị ký theo tháng → cơ cấu loại → top đối tác → chi tiết. */
function HdReport(){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const period = useReportPeriod('thisyear');
  const months = monthsInPeriod(period);

  const sideStats = (dir) => {
    const all = p.contracts.filter(c => contractDir(c) === dir && inPeriod(c.signDate, period));
    const byCat = {}; all.forEach(c => { byCat[c.category] = (byCat[c.category] || 0) + contractTotals(c).total; });
    const byParty = {}; all.forEach(c => { byParty[c.customer] = (byParty[c.customer] || 0) + contractTotals(c).total; });
    return {
      all,
      totalValue: all.reduce((s, c) => s + contractTotals(c).total, 0),
      expiring: all.filter(c => contractStatus(c) === 'expiring').length,
      valByMonth: tallyByMonth(all, c => c.signDate, c => contractTotals(c).total),
      catSlices: Object.entries(byCat).sort((a, b) => b[1] - a[1]).map(([k, v], i) => ({ label:catLabel(k, lang), value:v, color:REPORT_COLORS[i % REPORT_COLORS.length] })),
      rankRows: Object.entries(byParty).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([code, v]) => ({ label:((dir === 'buy' ? (window.VendorStore && VendorStore.byCode(code)) : CustomerStore.byCode(code)) || {}).shortName || code, value:v })),
    };
  };
  const buy = sideStats('buy');
  const sell = sideStats('sell');

  const excelSheet = (S, dir) => ({
    name: dir === 'buy' ? (lang === 'vi' ? 'HĐ mua' : 'Purchase contracts') : (lang === 'vi' ? 'HĐ bán' : 'Sales contracts'),
    header:[lang === 'vi' ? 'Số HĐ' : 'Code', dir === 'buy' ? (lang === 'vi' ? 'Nhà cung cấp' : 'Vendor') : (lang === 'vi' ? 'Khách hàng' : 'Customer'), lang === 'vi' ? 'Ngày ký' : 'Signed', lang === 'vi' ? 'Loại' : 'Category', lang === 'vi' ? 'Trạng thái' : 'Status', lang === 'vi' ? 'Giá trị (VND)' : 'Value (VND)'],
    widths:[18, 30, 12, 20, 12, 16],
    rows:S.all.map(c => { const cu = hdParty(c); return [hdNo(c), cu.name || cu.shortName || c.customer, c.signDate, catLabel(c.category, lang), contractStatus(c), contractTotals(c).total]; }),
  });
  const excel = () => exportReportXLSX([excelSheet(buy, 'buy'), excelSheet(sell, 'sell')], 'bao-cao-hop-dong_' + period.from + '_' + period.to, toast, lang);

  // 1 cột báo cáo — hàm render thường (không phải component, tránh remount).
  const side = (dir, S) => {
    const isBuy = dir === 'buy';
    const color = isBuy ? 'var(--accent)' : 'var(--primary)';
    const stat = (label, value) => (
      <div style={{ background:'var(--bg-subtle)', borderRadius:'var(--r-md)', padding:'8px 12px', minWidth:0 }}>
        <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}>{label}</div>
        <div className="k-val truncate" style={{ fontSize:'var(--fs-xl)', fontWeight:700 }}>{value}</div>
      </div>
    );
    return (
      <div className="col g14" style={{ minWidth:0 }}>
        <div className="card">
          <div className="card-head"><h3 className="flex g8 items-center"><i className={'bi ' + (isBuy ? 'bi-box-arrow-in-down' : 'bi-box-arrow-up')} style={{ color }}></i>{isBuy ? (lang === 'vi' ? 'Hợp đồng mua (đầu vào NCC)' : 'Purchase contracts') : (lang === 'vi' ? 'Hợp đồng bán (đầu ra KH)' : 'Sales contracts')}</h3><span className="badge badge-neutral">{S.all.length}</span></div>
          <div className="card-body" style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
            {stat(lang === 'vi' ? 'Số HĐ ký' : 'Signed', S.all.length)}
            {stat(t('hd.value'), vndShort(S.totalValue))}
            {stat(lang === 'vi' ? 'TB / HĐ' : 'Avg / contract', S.all.length ? vndShort(S.totalValue / S.all.length) : '0')}
            {stat(t('hd.expiringSoon'), S.expiring)}
          </div>
        </div>
        {S.all.length === 0 ? (
          <div className="card"><div className="card-body"><EmptyState icon="bi-calendar-x" text={lang === 'vi' ? 'Không có hợp đồng nào trong kỳ này' : 'No contracts in this period'} /></div></div>
        ) : (<>
          <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Giá trị ký theo tháng' : 'Signed value by month'}</h3></div><div className="card-body"><ReportBars months={months} series={S.valByMonth} money color={isBuy ? '#12C5C0' : undefined} /></div></div>
          <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Cơ cấu theo loại HĐ' : 'By category'}</h3></div><div className="card-body"><ReportDonut slices={S.catSlices} money /></div></div>
          <div className="card"><div className="card-head"><h3>{isBuy ? (lang === 'vi' ? 'Top nhà cung cấp' : 'Top vendors') : (lang === 'vi' ? 'Top khách hàng' : 'Top customers')}</h3></div><div className="card-body"><ReportRank rows={S.rankRows} money /></div></div>
          <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Chi tiết' : 'Detail'}</h3><span className="badge badge-neutral">{S.all.length}</span></div><div style={{ overflow:'auto', maxHeight:320 }}>
            <table className="tbl"><thead><tr><th>{lang === 'vi' ? 'Số HĐ' : 'Code'}</th><th>{isBuy ? t('hd.vendor') : t('ct.customer')}</th><th>{lang === 'vi' ? 'Ngày ký' : 'Signed'}</th><th>{lang === 'vi' ? 'Loại' : 'Category'}</th><th className="num">{t('hd.value')}</th></tr></thead>
              <tbody>{S.all.slice().sort((a, b) => String(b.signDate).localeCompare(String(a.signDate))).map(c => { const cu = hdParty(c); return (
                <tr key={c.code} style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'contracts', sub:'detail', id:c.code })}><td><span className="mono" style={{ fontWeight:600, color }}>{hdNo(c)}</span></td><td className="truncate" style={{ maxWidth:160 }}>{cu.shortName || cu.name || c.customer}</td><td className="mono">{fmtDate(c.signDate)}</td><td className="muted">{catLabel(c.category, lang)}</td><td className="num mono" style={{ fontWeight:600 }}>{vnd(contractTotals(c).total)}</td></tr>
              ); })}</tbody></table>
          </div></div>
        </>)}
      </div>
    );
  };

  return (
    <ReportShell title={t('c.report') + ' · ' + t('hd.title')} subtitle={lang === 'vi' ? 'Hai luồng trên cùng một kỳ: Mua (trái) · Bán (phải) — theo ngày ký.' : 'Both flows for the same period: Purchase (left) · Sales (right).'} period={period} onExcel={excel}>
      <div className="split-even">
        {side('buy', buy)}
        {side('sell', sell)}
      </div>
    </ReportShell>
  );
}

/* ---- Trợ giúp biến + tiêu đề mục + nhóm Header/Footer (dùng chung 3 tab Cài đặt) ---- */
function VarHelp({ list, lang }){
  return (
    <div className="subtle" style={{ fontSize:'var(--fs-sm)', lineHeight:1.8 }}>
      {lang === 'vi' ? 'Biến có thể chèn: ' : 'Variables: '}
      {list.map(([k, d], i) => (
        <span key={k}><span className="mono" style={{ background:'var(--bg-subtle)', padding:'1px 5px', borderRadius:4, color:'var(--primary-ink)' }}>{'{' + k + '}'}</span> <span style={{ opacity:.75 }}>{d}</span>{i < list.length - 1 ? '  ·  ' : ''}</span>
      ))}
    </div>
  );
}
function SecTitle({ children, divider }){
  return <div style={{ fontWeight:700, fontSize:'var(--fs-md)', color:'var(--text)', marginTop: divider ? 6 : 0, paddingTop: divider ? 12 : 0, borderTop: divider ? '1px solid var(--border)' : 'none' }}>{children}</div>;
}
// Nhóm trường Header (Quốc hiệu + dòng đầu trang) + Footer — đồng cấu trúc, căn đều (OCD).
function DocHeaderFooterFields({ motto, setMotto, mottoT, setMottoT, mottoS, setMottoS, hdr, setHdr, foot, setFoot, varList, lang }){
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  return (
    <>
      <div className="flex g12 wrap items-start">
        <div style={{ flex:'0 0 200px' }}>
          <Field label={L('Quốc hiệu — tiêu ngữ', 'National header')}>
            <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
              <input type="checkbox" checked={motto} onChange={e => setMotto(e.target.checked)} />{L('Hiển thị', 'Show')}
            </label>
          </Field>
        </div>
        <div style={{ flex:1, minWidth:180 }}><Field label={L('Dòng 1 (in đậm)', 'Line 1 (bold)')}><input className="input" value={mottoT} disabled={!motto} onChange={e => setMottoT(e.target.value)} placeholder={DEFAULT_DOC_MOTTO_TITLE} /></Field></div>
        <div style={{ flex:1, minWidth:180 }}><Field label={L('Dòng 2', 'Line 2')}><input className="input" value={mottoS} disabled={!motto} onChange={e => setMottoS(e.target.value)} placeholder={DEFAULT_DOC_MOTTO_SUB} /></Field></div>
      </div>
      <div className="flex g12 wrap items-start">
        <div style={{ flex:1, minWidth:220 }}><Field label={L('Dòng đầu trang (tuỳ chọn)', 'Header note (optional)')} hint={L('Hiện dưới đường kẻ đầu trang — có thể dùng biến', 'Shown under the header rule — supports variables')}><textarea className="textarea" rows={2} value={hdr} onChange={e => setHdr(e.target.value)} placeholder={L('VD: Hồ sơ mật — chỉ dùng nội bộ', 'e.g. Confidential — internal only')}></textarea></Field></div>
        <div style={{ flex:1, minWidth:220 }}><Field label={L('Chân trang', 'Footer')} hint={L('Dòng cuối chứng từ — có thể dùng biến', 'Bottom line — supports variables')}><textarea className="textarea" rows={2} value={foot} onChange={e => setFoot(e.target.value)} placeholder={DEFAULT_DOC_FOOTER}></textarea></Field></div>
      </div>
      <VarHelp list={varList} lang={lang} />
    </>
  );
}

function HdSettings(){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const S = p.appSettings;
  const [tab, setTab] = React.useState('general');
  const [previewDoc, setPreviewDoc] = React.useState(null);   // 'contract' | 'payreq' | 'accept'

  // --- Tab Hợp đồng: số HĐ + mẫu hợp đồng (bản in / Word) ---
  const [fmt, setFmt] = React.useState(S.contractNoFormat || DEFAULT_CONTRACT_NO_FORMAT);
  const fmtPreview = formatContractNo(fmt, { date:TODAY, kh:'Kizuna', seq:1 });
  const saveFmt = () => { p.setSetting('contractNoFormat', (fmt || '').trim() || DEFAULT_CONTRACT_NO_FORMAT); toast(L('Đã lưu định dạng số HĐ', 'Format saved'), 'success'); };
  const [cTitle, setCTitle] = React.useState(S.contractDocTitle != null ? S.contractDocTitle : DEFAULT_CONTRACT_DOC_TITLE);
  const [cSubtitle, setCSubtitle] = React.useState(S.contractDocSubtitle != null ? S.contractDocSubtitle : '');
  const [cIntro, setCIntro] = React.useState(S.contractDocIntro != null ? S.contractDocIntro : DEFAULT_CONTRACT_DOC_INTRO);
  const [cTerms, setCTerms] = React.useState(S.contractDocTerms != null ? S.contractDocTerms : DEFAULT_CONTRACT_DOC_TERMS);
  const [cSignA, setCSignA] = React.useState(S.contractDocSignA != null ? S.contractDocSignA : DEFAULT_CONTRACT_DOC_SIGN_A);
  const [cSignB, setCSignB] = React.useState(S.contractDocSignB != null ? S.contractDocSignB : DEFAULT_CONTRACT_DOC_SIGN_B);
  const [cMotto, setCMotto] = React.useState(docFlag(S.contractDocShowMotto, true));
  const [cMottoT, setCMottoT] = React.useState(S.contractDocMottoTitle != null ? S.contractDocMottoTitle : DEFAULT_DOC_MOTTO_TITLE);
  const [cMottoS, setCMottoS] = React.useState(S.contractDocMottoSub != null ? S.contractDocMottoSub : DEFAULT_DOC_MOTTO_SUB);
  const [cHdr, setCHdr] = React.useState(S.contractDocHeaderNote != null ? S.contractDocHeaderNote : '');
  const [cFoot, setCFoot] = React.useState(S.contractDocFooter != null ? S.contractDocFooter : DEFAULT_DOC_FOOTER);
  const [cMottoBelow, setCMottoBelow] = React.useState(docFlag(S.contractDocMottoBelow, false));
  const [cParties, setCParties] = React.useState(docFlag(S.contractDocPartiesStacked, false));
  // Người đại diện BÊN MÌNH (công ty) — áp cho mọi HĐ; đối tác chọn từ Liên hệ trên form HĐ.
  const [cRepName, setCRepName] = React.useState(S.contractRepName != null ? S.contractRepName : '');
  const [cRepTitle, setCRepTitle] = React.useState(S.contractRepTitle != null ? S.contractRepTitle : '');
  const [cRepAuth, setCRepAuth] = React.useState(docFlag(S.contractRepAuth, false));
  const [cRepAuthNo, setCRepAuthNo] = React.useState(S.contractRepAuthNo != null ? S.contractRepAuthNo : '');
  const [cRepAuthDate, setCRepAuthDate] = React.useState(S.contractRepAuthDate != null ? S.contractRepAuthDate : '');
  // Cờ CHUNG header (áp cho cả 3 chứng từ): hiện logo / hiện thông tin công ty.
  const [cShowLogo, setCShowLogo] = React.useState(docFlag(S.docShowLogo, true));
  const [cShowCompany, setCShowCompany] = React.useState(docFlag(S.docShowCompany, true));
  const [cLineColor, setCLineColor] = React.useState(S.docHeadLineColor || '#1466E0');                // màu line phân cách header
  const [cLineWidth, setCLineWidth] = React.useState(S.docHeadLineWidth != null ? String(S.docHeadLineWidth) : '2'); // độ dày (px)
  const [cFontSize, setCFontSize] = React.useState(S.docFontSize != null ? String(S.docFontSize) : '13'); // cỡ chữ chứng từ (pt)
  const headHdr = { docShowLogo:cShowLogo ? '1' : '0', docShowCompany:cShowCompany ? '1' : '0', docHeadLineColor:cLineColor, docHeadLineWidth:cLineWidth, docFontSize:cFontSize };   // áp vào mọi preview (3 chứng từ)
  const liveContract = { ...S, ...headHdr, contractDocTitle:cTitle, contractDocSubtitle:cSubtitle, contractDocIntro:cIntro, contractDocTerms:cTerms, contractDocSignA:cSignA, contractDocSignB:cSignB, contractDocShowMotto:cMotto ? '1' : '0', contractDocMottoTitle:cMottoT, contractDocMottoSub:cMottoS, contractDocMottoBelow:cMottoBelow ? '1' : '0', contractDocPartiesStacked:cParties ? '1' : '0', contractDocHeaderNote:cHdr, contractDocFooter:cFoot, contractRepName:cRepName, contractRepTitle:cRepTitle, contractRepAuth:cRepAuth ? '1' : '0', contractRepAuthNo:cRepAuthNo, contractRepAuthDate:cRepAuthDate };
  const saveContractDoc = () => {
    p.setSetting('contractDocTitle', cTitle); p.setSetting('contractDocSubtitle', cSubtitle); p.setSetting('contractDocIntro', cIntro); p.setSetting('contractDocTerms', cTerms);
    p.setSetting('contractDocSignA', cSignA); p.setSetting('contractDocSignB', cSignB);
    p.setSetting('contractDocShowMotto', cMotto ? '1' : '0'); p.setSetting('contractDocMottoTitle', cMottoT); p.setSetting('contractDocMottoSub', cMottoS);
    p.setSetting('contractDocMottoBelow', cMottoBelow ? '1' : '0'); p.setSetting('contractDocPartiesStacked', cParties ? '1' : '0');
    p.setSetting('docShowLogo', cShowLogo ? '1' : '0'); p.setSetting('docShowCompany', cShowCompany ? '1' : '0');
    p.setSetting('docHeadLineColor', cLineColor); p.setSetting('docHeadLineWidth', cLineWidth); p.setSetting('docFontSize', cFontSize);
    p.setSetting('contractDocHeaderNote', cHdr); p.setSetting('contractDocFooter', cFoot);
    p.setSetting('contractRepName', cRepName); p.setSetting('contractRepTitle', cRepTitle);
    p.setSetting('contractRepAuth', cRepAuth ? '1' : '0'); p.setSetting('contractRepAuthNo', cRepAuthNo); p.setSetting('contractRepAuthDate', cRepAuthDate);
    toast(L('Đã lưu mẫu hợp đồng', 'Contract template saved'), 'success');
  };

  // --- Tab Đề nghị thanh toán ---
  const [pTitle, setPTitle] = React.useState(S.payReqTitle != null ? S.payReqTitle : DEFAULT_PAYREQ_TITLE);
  const [pIntro, setPIntro] = React.useState(S.payReqIntro != null ? S.payReqIntro : DEFAULT_PAYREQ_INTRO);
  const [pNote, setPNote] = React.useState(S.payReqNote != null ? S.payReqNote : DEFAULT_PAYREQ_NOTE);
  const [pS1, setPS1] = React.useState(S.payReqSign1 != null ? S.payReqSign1 : DEFAULT_PAYREQ_SIGN[0]);
  const [pS2, setPS2] = React.useState(S.payReqSign2 != null ? S.payReqSign2 : DEFAULT_PAYREQ_SIGN[1]);
  const [pS3, setPS3] = React.useState(S.payReqSign3 != null ? S.payReqSign3 : DEFAULT_PAYREQ_SIGN[2]);
  const [pMotto, setPMotto] = React.useState(docFlag(S.payReqShowMotto, true));
  const [pMottoT, setPMottoT] = React.useState(S.payReqMottoTitle != null ? S.payReqMottoTitle : DEFAULT_DOC_MOTTO_TITLE);
  const [pMottoS, setPMottoS] = React.useState(S.payReqMottoSub != null ? S.payReqMottoSub : DEFAULT_DOC_MOTTO_SUB);
  const [pHdr, setPHdr] = React.useState(S.payReqHeaderNote != null ? S.payReqHeaderNote : '');
  const [pFoot, setPFoot] = React.useState(S.payReqFooter != null ? S.payReqFooter : DEFAULT_DOC_FOOTER);
  const livePayReq = { ...S, ...headHdr, payReqTitle:pTitle, payReqIntro:pIntro, payReqNote:pNote, payReqSign1:pS1, payReqSign2:pS2, payReqSign3:pS3, payReqShowMotto:pMotto ? '1' : '0', payReqMottoTitle:pMottoT, payReqMottoSub:pMottoS, payReqHeaderNote:pHdr, payReqFooter:pFoot };
  const savePayReq = () => {
    p.setSetting('payReqTitle', pTitle); p.setSetting('payReqIntro', pIntro); p.setSetting('payReqNote', pNote);
    p.setSetting('payReqSign1', pS1); p.setSetting('payReqSign2', pS2); p.setSetting('payReqSign3', pS3);
    p.setSetting('payReqShowMotto', pMotto ? '1' : '0'); p.setSetting('payReqMottoTitle', pMottoT); p.setSetting('payReqMottoSub', pMottoS);
    p.setSetting('payReqHeaderNote', pHdr); p.setSetting('payReqFooter', pFoot);
    toast(L('Đã lưu mẫu giấy đề nghị thanh toán', 'Payment request template saved'), 'success');
  };

  // --- Tab Biên bản bàn giao ---
  const [aTitle, setATitle] = React.useState(S.acceptTitle != null ? S.acceptTitle : DEFAULT_ACCEPT_TITLE);
  const [aSub, setASub] = React.useState(S.acceptSubtitle != null ? S.acceptSubtitle : DEFAULT_ACCEPT_SUBTITLE);
  const [aBody, setABody] = React.useState(S.acceptBody != null ? S.acceptBody : DEFAULT_ACCEPT_BODY);
  const [aMotto, setAMotto] = React.useState(docFlag(S.acceptShowMotto, true));
  const [aMottoT, setAMottoT] = React.useState(S.acceptMottoTitle != null ? S.acceptMottoTitle : DEFAULT_DOC_MOTTO_TITLE);
  const [aMottoS, setAMottoS] = React.useState(S.acceptMottoSub != null ? S.acceptMottoSub : DEFAULT_DOC_MOTTO_SUB);
  const [aHdr, setAHdr] = React.useState(S.acceptHeaderNote != null ? S.acceptHeaderNote : '');
  const [aFoot, setAFoot] = React.useState(S.acceptFooter != null ? S.acceptFooter : DEFAULT_DOC_FOOTER);
  const liveAccept = { ...S, ...headHdr, acceptTitle:aTitle, acceptSubtitle:aSub, acceptBody:aBody, acceptShowMotto:aMotto ? '1' : '0', acceptMottoTitle:aMottoT, acceptMottoSub:aMottoS, acceptHeaderNote:aHdr, acceptFooter:aFoot };
  const saveAccept = () => {
    p.setSetting('acceptTitle', aTitle); p.setSetting('acceptSubtitle', aSub); p.setSetting('acceptBody', aBody);
    p.setSetting('acceptShowMotto', aMotto ? '1' : '0'); p.setSetting('acceptMottoTitle', aMottoT); p.setSetting('acceptMottoSub', aMottoS);
    p.setSetting('acceptHeaderNote', aHdr); p.setSetting('acceptFooter', aFoot);
    toast(L('Đã lưu mẫu biên bản nghiệm thu', 'Acceptance template saved'), 'success');
  };

  // --- Preview (dữ liệu minh hoạ + giá trị đang sửa) ---
  const SC = DOC_SAMPLE_CONTRACT, SP = DOC_SAMPLE_PARTY, SR = DOC_SAMPLE_REP;
  const previewMap = {
    contract: { title:L('Xem trước hợp đồng', 'Contract preview'), file:'HopDong-Mau', html:() => contractDocHTML(SC, SP, SR, p.branding, liveContract) },
    payreq:   { title:L('Xem trước giấy đề nghị thanh toán', 'Payment request preview'), file:'GiayDeNghiThanhToan-Mau', html:() => paymentRequestHTML(SC, DOC_SAMPLE_INST, SP, SR, contractTotals(SC).total, p.branding, livePayReq) },
    accept:   { title:L('Xem trước biên bản nghiệm thu', 'Acceptance preview'), file:'BienBanNghiemThu-Mau', html:() => acceptanceHTML(SC, SP, SR, p.branding, liveAccept) },
  };
  const pv = previewDoc ? previewMap[previewDoc] : null;

  const tabs = [
    { id:'general', icon:'bi-sliders', label:L('Chung', 'General') },
    { id:'contract', icon:'bi-file-earmark-text', label:L('Hợp đồng', 'Contract') },
    { id:'payreq', icon:'bi-cash-coin', label:L('Đề nghị thanh toán', 'Payment request') },
    { id:'accept', icon:'bi-clipboard2-check', label:L('Biên bản bàn giao', 'Handover minutes') },
  ];
  const PreviewBtn = ({ which }) => <button className="btn" onClick={() => setPreviewDoc(which)}><i className="bi bi-eye"></i>{L('Xem trước', 'Preview')}</button>;

  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('c.settings')}</h1><div className="sub">{L('Số hợp đồng · mẫu hợp đồng, đề nghị thanh toán & biên bản bàn giao.', 'Contract no. · contract, payment request & handover templates.')}</div></div></div>
      <Tabs tabs={tabs} active={tab} onChange={setTab} />

      {tab === 'general' && (
        <div style={{ marginTop:14 }}>
          <div className="settings-grid">
            <div className="card">
              <div className="card-head"><h3>{L('Định dạng số hợp đồng (HĐ bán)', 'Contract no. format (sell)')}</h3></div>
              <div className="card-body col g12">
                <Field label={L('Định dạng', 'Format')} hint={L('Token: DD MM YY YYYY · {KH} = tên viết tắt KH · {SEQ} = số thứ tự', 'Tokens: DD MM YY YYYY · {KH} = customer abbr · {SEQ} = sequence')}>
                  <input className="input mono" value={fmt} onChange={e => setFmt(e.target.value)} placeholder="DDMM/YYYY/SWO-{KH}" /></Field>
                <div className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Xem trước', 'Preview')}: <span className="mono" style={{ color:'var(--primary)', fontWeight:600 }}>{fmtPreview}</span></div>
                <div className="flex justify-end"><button className="btn btn-primary" onClick={saveFmt}>{t('c.save')}</button></div>
              </div>
            </div>
            <CatalogEditor catalogKey="contractCats" title={L('Danh mục hợp đồng', 'Contract categories')} />
          </div>
        </div>
      )}

      {tab === 'contract' && (
        <div style={{ marginTop:14 }} className="col g14">
          <DocTemplateEditor label={L('Mẫu hợp đồng', 'Contract template')} type="contract" codes={DOC_TPL_CODES_CONTRACT} hasTerms={true}
            legacy={{ title:cTitle, subtitle:cSubtitle, intro:cIntro, terms:cTerms, signA:cSignA, signB:cSignB, footer:cFoot, stackedKey:'contractDocPartiesStacked' }}
            previewHtml={(s) => contractDocHTML(SC, SP, SR, p.branding, s)} fileName="HopDong-Mau" />
          <div className="card">
            <div className="card-head"><h3>{L('Người đại diện bên mình (công ty)', 'Our representative (company)')}</h3>
              <button className="btn btn-primary" onClick={saveContractDoc}>{t('c.save')}</button></div>
            <div className="card-body col g12">
              <div style={{ display:'none' }}>
              <SecTitle>{L('Header (đầu trang)', 'Header')} <span className="subtle" style={{ fontWeight:400, fontSize:'var(--fs-sm)' }}>{L('(áp dụng cho cả Đề nghị thanh toán & Biên bản bàn giao)', '(also applies to Payment request & Handover minutes)')}</span></SecTitle>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:1, minWidth:220 }}><Field label={L('Cột trái — Logo', 'Left — Logo')}>
                  <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                    <input type="checkbox" checked={!cShowLogo} onChange={e => setCShowLogo(!e.target.checked)} />{L('Bỏ logo', 'Hide logo')}
                  </label></Field></div>
                <div style={{ flex:1, minWidth:220 }}><Field label={L('Cột trái — Thông tin công ty', 'Left — Company info')}>
                  <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                    <input type="checkbox" checked={!cShowCompany} onChange={e => setCShowCompany(!e.target.checked)} />{L('Bỏ thông tin công ty', 'Hide company info')}
                  </label></Field></div>
              </div>
              <Field label={L('Cột phải — Dòng đầu trang (tuỳ chọn)', 'Right — Header note (optional)')} hint={L('Hiện dưới đường kẻ — có thể dùng biến', 'Shown under the divider — supports variables')}><textarea className="textarea" rows={2} value={cHdr} onChange={e => setCHdr(e.target.value)} placeholder={L('VD: Hồ sơ mật — chỉ dùng nội bộ', 'e.g. Confidential — internal only')}></textarea></Field>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:'0 0 200px' }}><Field label={L('Đường kẻ ngăn cách', 'Divider line')}>
                  <div className="flex g8 items-center" style={{ height:'var(--control-h)' }}>
                    <input type="color" value={cLineColor} onChange={e => setCLineColor(e.target.value)} style={{ width:36, height:24, padding:0, border:'1px solid var(--border-strong)', borderRadius:'var(--r-sm)', background:'none' }} />
                    <input className="input mono" value={cLineColor} onChange={e => setCLineColor(e.target.value)} style={{ width:96 }} />
                  </div></Field></div>
                <div style={{ flex:'0 0 120px' }}><Field label={L('Độ dày (px)', 'Width (px)')}><input className="input" type="number" min="0" max="8" value={cLineWidth} onChange={e => setCLineWidth(e.target.value)} /></Field></div>
                <div style={{ flex:'0 0 120px' }}><Field label={L('Cỡ chữ chung (pt)', 'Font size (pt)')}><input className="input" type="number" min="8" max="20" value={cFontSize} onChange={e => setCFontSize(e.target.value)} /></Field></div>
              </div>

              {/* 2 — QUỐC HIỆU — TIÊU NGỮ */}
              <SecTitle divider>{L('Quốc hiệu — tiêu ngữ', 'National header')}</SecTitle>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:'0 0 130px' }}><Field label={L('Hiển thị', 'Show')}>
                  <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                    <input type="checkbox" checked={cMotto} onChange={e => setCMotto(e.target.checked)} />{L('Bật', 'On')}
                  </label></Field></div>
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Dòng 1 (in đậm)', 'Line 1 (bold)')}><input className="input" value={cMottoT} disabled={!cMotto} onChange={e => setCMottoT(e.target.value)} placeholder={DEFAULT_DOC_MOTTO_TITLE} /></Field></div>
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Dòng 2', 'Line 2')}><input className="input" value={cMottoS} disabled={!cMotto} onChange={e => setCMottoS(e.target.value)} placeholder={DEFAULT_DOC_MOTTO_SUB} /></Field></div>
              </div>
              <Field label={L('Vị trí', 'Position')}>
                <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:cMotto ? 'pointer' : 'not-allowed', opacity:cMotto ? 1 : .5 }}>
                  <input type="checkbox" checked={cMottoBelow} disabled={!cMotto} onChange={e => setCMottoBelow(e.target.checked)} />{L('Dòng riêng, căn giữa trên tiêu đề (thay vì góc đầu trang)', 'Centered line above title (not header corner)')}
                </label></Field>

              {/* 3 — NỘI DUNG (tiêu đề + phụ đề) */}
              <SecTitle divider>{L('Nội dung', 'Content')}</SecTitle>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:1, minWidth:200 }}><Field label={L('Tiêu đề', 'Title')}><input className="input" value={cTitle} onChange={e => setCTitle(e.target.value)} placeholder={DEFAULT_CONTRACT_DOC_TITLE} /></Field></div>
                <div style={{ flex:1, minWidth:200 }}><Field label={L('Phụ đề (tuỳ chọn)', 'Subtitle (optional)')}><input className="input" value={cSubtitle} onChange={e => setCSubtitle(e.target.value)} placeholder={L('VD: SALES CONTRACT', 'e.g. SALES CONTRACT')} /></Field></div>
              </div>

              {/* 4 — LỜI MỞ ĐẦU / CĂN CỨ */}
              <SecTitle divider>{L('Lời mở đầu / căn cứ', 'Preamble / basis')}</SecTitle>
              <Field label={L('Lời mở đầu / căn cứ', 'Preamble / basis')}><textarea className="textarea" rows={3} value={cIntro} onChange={e => setCIntro(e.target.value)} placeholder={DEFAULT_CONTRACT_DOC_INTRO}></textarea></Field>

              {/* 5 — ĐIỀU KHOẢN */}
              <SecTitle divider>{L('Điều khoản', 'Terms')}</SecTitle>
              <Field label={L('Điều khoản', 'Terms')}><textarea className="textarea" rows={8} value={cTerms} onChange={e => setCTerms(e.target.value)} placeholder={DEFAULT_CONTRACT_DOC_TERMS}></textarea></Field>
              <VarHelp list={DOC_VAR_HELP_CONTRACT} lang={lang} />
              </div>
              {/* NGƯỜI ĐẠI DIỆN BÊN MÌNH (công ty) — hiển thị (không thuộc mẫu v2, dùng ở khối Bên A/B) */}
              <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:-2 }}>{L('Hiển thị ở khối Bên A/Bên B phía công ty trên mọi hợp đồng. Người đại diện của đối tác chọn từ Liên hệ trên form HĐ.', 'Shown on the company-side party block. Partner rep is picked from Contacts on the contract form.')}</div>
              <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:-4 }}>{L('Hiển thị ở khối Bên A/Bên B phía công ty trên mọi hợp đồng. Người đại diện của đối tác chọn từ Liên hệ ngay trên form hợp đồng.', 'Shown on the company-side party block of every contract. The partner’s representative is picked from Contacts on the contract form.')}</div>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Họ tên', 'Full name')}><input className="input" value={cRepName} onChange={e => setCRepName(e.target.value)} placeholder={L('VD: Nguyễn Văn A', 'e.g. John Doe')} /></Field></div>
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Chức vụ', 'Title')}><input className="input" value={cRepTitle} onChange={e => setCRepTitle(e.target.value)} placeholder={L('VD: Giám đốc', 'e.g. Director')} /></Field></div>
              </div>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:'0 0 220px' }}><Field label={L('Uỷ quyền', 'Authorization')}>
                  <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                    <input type="checkbox" checked={cRepAuth} onChange={e => setCRepAuth(e.target.checked)} />{L('Đại diện theo uỷ quyền', 'By authorization')}
                  </label></Field></div>
                {cRepAuth && <div style={{ flex:1, minWidth:160 }}><Field label={L('Số giấy uỷ quyền', 'Authorization no.')}><input className="input" value={cRepAuthNo} onChange={e => setCRepAuthNo(e.target.value)} placeholder={L('VD: 01/2026/UQ', 'e.g. 01/2026/UQ')} /></Field></div>}
                {cRepAuth && <div style={{ flex:'0 0 160px' }}><Field label={L('Ngày uỷ quyền', 'Authorization date')}><DateInput value={cRepAuthDate} onChange={v => setCRepAuthDate(v)} /></Field></div>}
              </div>

              <div style={{ display:'none' }}>
              {/* mục cũ ẩn — đã thay bằng editor 2 cột ở trên */}
              <SecTitle divider>{L('Ô ký', 'Signatures')}</SecTitle>
              <div className="flex g12 wrap items-start">
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Ô ký Bên A', 'Sign — Party A')}><input className="input" value={cSignA} onChange={e => setCSignA(e.target.value)} placeholder={DEFAULT_CONTRACT_DOC_SIGN_A} /></Field></div>
                <div style={{ flex:1, minWidth:180 }}><Field label={L('Ô ký Bên B', 'Sign — Party B')}><input className="input" value={cSignB} onChange={e => setCSignB(e.target.value)} placeholder={DEFAULT_CONTRACT_DOC_SIGN_B} /></Field></div>
              </div>
              <Field label={L('Bố cục Bên A / Bên B', 'Party A / B layout')}>
                <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                  <input type="checkbox" checked={cParties} onChange={e => setCParties(e.target.checked)} />{L('Xếp trên – dưới (thay vì 2 cột)', 'Stacked top–bottom (not 2 columns)')}
                </label></Field>

              {/* 7 — FOOTER (chân trang) */}
              <SecTitle divider>{L('Footer (chân trang)', 'Footer')}</SecTitle>
              <Field label={L('Chân trang', 'Footer')} hint={L('Dòng cuối chứng từ — có thể dùng biến', 'Bottom line — supports variables')}><textarea className="textarea" rows={2} value={cFoot} onChange={e => setCFoot(e.target.value)} placeholder={DEFAULT_DOC_FOOTER}></textarea></Field>

              <div className="flex justify-end g8"><PreviewBtn which="contract" /><button className="btn btn-primary" onClick={saveContractDoc}>{t('c.save')}</button></div>
              </div>
            </div>
          </div>
        </div>
      )}

      {tab === 'payreq' && (
        <div style={{ marginTop:14 }}>
          <DocTemplateEditor label={L('Mẫu giấy đề nghị thanh toán', 'Payment request template')} type="payreq" codes={DOC_TPL_CODES_PAYREQ}
            hasTerms={false} showIntro={false} showSigns={false} bodyLabel={L('Câu đề nghị thanh toán ({N}=đợt, {KH}=khách hàng)', 'Request sentence')}
            legacy={{ title:pTitle, footer:pFoot }} previewHtml={(s) => paymentRequestHTML(SC, DOC_SAMPLE_INST, SP, SR, contractTotals(SC).total, p.branding, s)} fileName="GiayDeNghiThanhToan-Mau" />
        </div>
      )}

      {tab === 'accept' && (
        <div style={{ marginTop:14 }}>
          <DocTemplateEditor label={L('Mẫu biên bản nghiệm thu & bàn giao', 'Acceptance & handover template')} type="accept" codes={DOC_TPL_CODES}
            hasTerms={false} showIntro={false} showSigns={false} showParties={true} bodyLabel={L('Nội dung / điều khoản nghiệm thu', 'Body / acceptance terms')}
            legacy={{ title:aTitle, subtitle:aSub, body:aBody, footer:aFoot }} previewHtml={(s) => acceptanceHTML(SC, SP, SR, p.branding, s)} fileName="BienBanNghiemThu-Mau" />
        </div>
      )}

      {pv && <DocPreviewModal title={pv.title} html={pv.html()} fileName={pv.file} sampleNote={L('Dữ liệu minh hoạ — chỉ để xem bố cục', 'Sample data — layout preview only')} onClose={() => setPreviewDoc(null)} />}
    </div>
  );
}

/* Dashboard widget */
function ContractWidget(){
  const p = usePortal();
  const { t, lang } = useLang();
  const L = (vi, en) => lang === 'vi' ? vi : en;
  const cs = p.contracts;
  const sell = cs.filter(c => contractDir(c) !== 'buy');
  const buy = cs.filter(c => contractDir(c) === 'buy');
  const sellValue = sell.reduce((s, c) => s + contractTotals(c).total, 0);
  const buyValue = buy.reduce((s, c) => s + contractTotals(c).total, 0);
  const active = cs.filter(c => contractStatus(c) === 'active').length;
  const expiring = cs.filter(c => contractStatus(c) === 'expiring');
  const recent = [...cs].sort((a, b) => String(b.signDate || '').localeCompare(String(a.signDate || ''))).slice(0, 4);
  return (
    <WidgetShell wid="contracts" icon="bi-file-earmark-text" iconColor="#0B4FBF" title={t('hd.title')}
      onViewAll={() => p.openApp(HD_APP)} onNew={() => p.quick('contracts')} newLabel={t('hd.new')}>
        <div className="flex g16" style={{ marginBottom:10 }}>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{cs.length}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('hợp đồng', 'contracts')}</div></div>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{vndShort(sellValue + buyValue)}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('tổng giá trị', 'total value')}</div></div>
        </div>
        <div className="flex" style={{ gap:8, marginBottom:8 }}>
          <div className="grow" style={{ background:'var(--bg-subtle)', borderRadius:'var(--r-md)', padding:'6px 10px', minWidth:0 }}>
            <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-arrow-down-circle" style={{ color:'var(--success)', marginRight:4 }}></i>{L('Bán', 'Sell')} · {sell.length}</div>
            <div className="mono truncate" style={{ fontWeight:600, color:'var(--success)' }}>{vndShort(sellValue)}</div>
          </div>
          <div className="grow" style={{ background:'var(--bg-subtle)', borderRadius:'var(--r-md)', padding:'6px 10px', minWidth:0 }}>
            <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-arrow-up-circle" style={{ color:'var(--primary)', marginRight:4 }}></i>{L('Mua', 'Buy')} · {buy.length}</div>
            <div className="mono truncate" style={{ fontWeight:600 }}>{vndShort(buyValue)}</div>
          </div>
        </div>
        <div className="flex g8 items-center" style={{ marginBottom:8, flexWrap:'wrap' }}>
          <span className="badge badge-success"><i className="dot"></i>{active} {L('hiệu lực', 'active')}</span>
          {expiring.length > 0 && <span className="badge badge-warning"><i className="bi bi-alarm"></i>{expiring.length} {t('hd.expiringSoon').toLowerCase()}</span>}
        </div>
        {recent.map(c => { const pt = hdParty(c); return (
          <div key={c.code} className="list-row" style={{ cursor:'pointer', alignItems:'flex-start' }} onClick={() => p.navigate({ name:'module', module:'contracts', sub:'detail', id:c.code })}>
            <div className="grow" style={{ minWidth:0 }}>
              <div className="mono truncate" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)' }}>{hdNo(c)}</div>
              <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}>{pt.shortName || pt.name || c.customer}</div>
            </div>
            <div style={{ textAlign:'right', flexShrink:0, marginLeft:8 }}>
              <div className="mono" style={{ fontWeight:600, fontSize:'var(--fs-sm)' }}>{vndShort(contractTotals(c).total)}</div>
              <HdStatusBadge c={c} />
            </div>
          </div>
        ); })}
    </WidgetShell>
  );
}

Object.assign(window, { ContractModule, ContractWidget, genContractCode, catLabel, HdStatusBadge, contractSnapshotData, makeRevision });
