// quotes.jsx — QUOTES (Báo giá) module + QuoteStore + widget. Links to Email + Contracts.
const BG_APP = APPS.find(a => a.id === 'quotes');

const DEFAULT_QUOTE_NO_FORMAT = 'BG-YYYY-{SEQ}';
const DEFAULT_QUOTE_TERMS = '<ul><li>Đơn giá trên đã bao gồm thuế VAT theo quy định.</li></ul>';
const DEFAULT_QUOTE_PAYMENT = 'Tạm ứng 50% khi ký hợp đồng, 50% sau khi nghiệm thu & bàn giao.';
// Định dạng mã báo giá: ưu tiên quoteNoFormat; nếu chưa có thì suy từ quotePrefix cũ (giữ tương thích).
function quoteNoFormat(){
  const s = window.AppSettings || {};
  if (s.quoteNoFormat && s.quoteNoFormat.trim()) return s.quoteNoFormat.trim();
  return ((s.quotePrefix || 'BG').trim() || 'BG') + '-YYYY-{SEQ}';
}
function genQuoteCode(list, kh, date){
  return formatContractNo(quoteNoFormat(), { date:date || TODAY, kh:kh || '', seq:(list || []).length + 1 });
}

// Đối tác của báo giá theo chiều: bán → Khách hàng; mua → Nhà cung cấp (cùng quy ước hdParty của HĐ).
function bgParty(q){
  if (quoteDir(q) === 'buy') return (window.VendorStore && VendorStore.byCode(q.customer)) || {};
  return CustomerStore.byCode(q.customer) || {};
}

function BgStatusBadge({ q }){
  const { lang } = useLang(); const st = quoteStatus(q); const s = QUOTE_STATUS[st];
  return <span className={'badge ' + s.cls}><i className="dot"></i>{quoteStatusLabel(q, st, lang)}</span>;
}

// Sidebar "dạng sổ": mỗi chiều (bán/mua) sổ xuống theo trạng thái. Icon dùng chung; thứ tự theo luồng.
const BG_STATUS_ICON = { draft:'bi-pencil', sent:'bi-send', accepted:'bi-check2-circle', won:'bi-trophy', converted:'bi-file-earmark-check', rejected:'bi-x-circle', expired:'bi-alarm' };
const BG_SELL_STS = ['draft', 'sent', 'accepted', 'won', 'converted', 'rejected', 'expired'];
const BG_BUY_STS  = ['draft', 'accepted', 'converted', 'won', 'rejected', 'expired'];   // 'sent' không dùng cho chiều mua
// Trang list đích cho nút Quay lại / sau khi xóa: về đúng chiều + trạng thái của báo giá đó ("<dir>:<status>").
function bgHome(q){ return q ? (quoteDir(q) + ':' + quoteStatus(q)) : 'kanban'; }

function QuoteModule(){
  const p = usePortal();
  const { t, lang } = useLang();
  const sub = p.route.sub || 'overview';
  // Tổng quan/Kanban/tab trạng thái/Báo cáo = luồng BÁN (đầu ra); mục "Báo giá mua" = luồng MUA (đầu vào từ NCC).
  const sells = p.quotes.filter(q => quoteDir(q) === 'sell');
  const buys = p.quotes.filter(q => quoteDir(q) === 'buy');
  const sellChildren = BG_SELL_STS.map(st => ({ id:'sell:' + st, icon:BG_STATUS_ICON[st], label:QUOTE_STATUS[st][lang] || QUOTE_STATUS[st].vi, count:sells.filter(q => quoteStatus(q) === st).length }));
  const buyChildren = BG_BUY_STS.map(st => ({ id:'buy:' + st, icon:BG_STATUS_ICON[st], label:quoteStatusLabel({ direction:'buy' }, st, lang), count:buys.filter(q => quoteStatus(q) === st).length }));
  const items = [
    { id:'overview', icon:'bi-pie-chart', label:t('c.overview') },
    { id:'kanban', icon:'bi-kanban', label:'Kanban', count:sells.length },
    { id:'sell', icon:'bi-box-arrow-up', label:lang === 'vi' ? 'Báo giá bán' : 'Sales quotes', count:sells.length, children:sellChildren },
    { id:'buy', icon:'bi-box-arrow-in-down', label:t('bg.buy'), count:buys.length, children:buyChildren },
    { id:'report', icon:'bi-bar-chart', label:t('c.report') },
    { id:'settings', icon:'bi-gear', label:t('c.settings') },
  ];
  // id chứa ":" = "<dir>:<status>" từ sidebar sổ → tách thành sub (chiều) + 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:'quotes', sub:dir, status });
    }
    return p.navigate({ name:'module', module:'quotes', sub:id, ...extra });
  };
  // Highlight: chi tiết → sáng đúng trạng thái của báo giá; list bán/mua → sáng con theo route.status.
  let active;
  if (sub === 'detail') active = bgHome(p.quotes.find(x => x.code === p.route.id));
  else if (sub === 'sell' || sub === 'buy') active = sub + ':' + (p.route.status || 'all');
  else active = sub;
  return (<>
    <ModuleSidebar app={BG_APP} items={items} active={active} onNavigate={go} />
    <main className="main">
      {sub === 'overview' && <BgOverview go={go} />}
      {sub === 'kanban' && <BgKanbanPage go={go} />}
      {sub === 'sell' && <BgDirList key="sell" dir="sell" go={go} />}
      {sub === 'buy' && <BgDirList key="buy" dir="buy" go={go} />}
      {sub === 'detail' && <BgDetail code={p.route.id} go={go} />}
      {sub === 'report' && <BgReport />}
      {sub === 'help' && <ModuleHelp moduleId="quotes" app={BG_APP} />}
      {sub === 'settings' && <BgSettings />}
    </main>
  </>);
}

function ExpiringBanner({ quotes, go }){
  const { t, lang } = useLang();
  const soon = quotes.filter(q => quoteExpiringSoon(q, 7));
  if (!soon.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>{lang === 'vi' ? 'Báo giá sắp hết hiệu lực — nên theo dõi' : 'Quotes expiring soon — follow up'} ({soon.length})</div>
        {soon.map(q => {
          const d = daysBetween(TODAY, q.validUntil);
          return (
            <div key={q.code} className="flex items-center g8" style={{ fontSize:'var(--fs-md)' }}>
              <span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{q.code}</span>
              <span className="grow truncate">{(CustomerStore.byCode(q.customer) || {}).name}</span>
              <span className="badge badge-warning">{d} {t('hd.daysLeft')}</span>
              <button className="btn btn-sm" onClick={() => go('detail', { id:q.code })}>{t('c.details')}</button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function BgOverview({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const qs = p.quotes.filter(q => quoteDir(q) === 'sell');   // tổng quan = luồng bán
  const totalValue = qs.reduce((s, q) => s + quoteTotals(q).total, 0);
  const won = qs.filter(q => ['accepted', 'converted', 'won'].includes(quoteStatus(q)));
  const decided = qs.filter(q => ['accepted', 'converted', 'won', 'rejected', 'expired'].includes(quoteStatus(q)));
  const winRate = decided.length ? Math.round(won.length / decided.length * 100) : 0;
  const byStatus = {}; qs.forEach(q => { const st = quoteStatus(q); byStatus[st] = (byStatus[st] || 0) + 1; });
  const colors = { draft:'var(--gray-400)', sent:'var(--primary)', accepted:'var(--success)', won:'var(--success)', rejected:'var(--error)', expired:'var(--warning)', converted:'#7A5AF8' };
  const actMap = {
    draft:{ icon:'bi-pencil-square', color:'var(--gray-500)', vi:'Soạn báo giá', en:'Quote drafted' },
    sent:{ icon:'bi-send', color:'var(--primary)', vi:'Đã gửi báo giá', en:'Quote sent' },
    accepted:{ icon:'bi-check2-circle', color:'var(--success)', vi:'Khách đồng ý', en:'Quote agreed' },
    won:{ icon:'bi-trophy', color:'var(--success)', vi:'Đã chốt đơn', en:'Deal won' },
    converted:{ icon:'bi-file-earmark-check', color:'#7A5AF8', vi:'Chuyển thành hợp đồng', en:'Converted to contract' },
    rejected:{ icon:'bi-x-circle', color:'var(--error)', vi:'Khách từ chối', en:'Quote declined' },
    expired:{ icon:'bi-alarm', color:'var(--warning)', vi:'Hết hiệu lực', en:'Quote expired' },
  };
  const recent = qs.map(q => {
    const cust = CustomerStore.byCode(q.customer) || {};
    const st = quoteStatus(q); const m = actMap[st] || actMap.draft;
    const date = q.sentDate || q.issueDate;
    return { date, icon:m.icon, color:m.color,
      title:(lang === 'vi' ? m.vi : m.en) + ' · ' + q.code,
      sub:(cust.shortName || cust.name || '—') + ' · ' + vndShort(quoteTotals(q).total),
      time:fmtAgo(date, lang), onClick:() => go('detail', { id:q.code }) };
  }).sort((a, b) => String(b.date || '').localeCompare(String(a.date || '')));
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('bg.title')}</h1><div className="sub">{lang === 'vi' ? 'Tổng quan danh mục báo giá.' : 'Quote portfolio overview.'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('quotes')}><i className="bi bi-plus-lg"></i>{t('bg.new')}</button></div>
      <div className="kpi-strip">
        <Kpi label={t('bg.title')} value={qs.length} icon="bi-receipt" />
        <Kpi label={t('bg.awaitingReply')} value={qs.filter(q => quoteStatus(q) === 'sent').length} icon="bi-send" />
        <Kpi label={t('bg.acceptRate')} value={winRate + '%'} icon="bi-trophy" />
        <Kpi label={t('bg.value')} value={vndShort(totalValue)} icon="bi-cash-stack" />
      </div>
      <ExpiringBanner quotes={qs} go={go} />
      <div className="dash-grid">
        <div className="card span2"><div className="card-head"><h3>{lang === 'vi' ? 'Phễu báo giá' : 'Quote funnel'}</h3></div><div className="card-body">
          <DistribBars rows={['draft', 'sent', 'accepted', 'won', 'converted', 'rejected', 'expired'].filter(k => byStatus[k]).map(k => ({ label:QUOTE_STATUS[k][lang], value:byStatus[k] || 0, color:colors[k] }))} />
          <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:6 }}>{lang === 'vi' ? 'Số lượng báo giá theo trạng thái' : 'Quote count by status'}</div>
        </div></div>
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Giá trị đã chốt' : 'Won value'}</h3></div><div className="card-body">
          <div className="flex g16" style={{ marginBottom:10 }}>
            <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700, color:'var(--success)' }}>{vndShort(won.reduce((s, q) => s + quoteTotals(q).total, 0))}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'khách đồng ý / chốt / chuyển HĐ' : 'agreed / won / converted'}</div></div>
          </div>
          <DistribBars rows={[
            { label:lang === 'vi' ? 'Đã chốt' : 'Won', value:won.length, color:'var(--success)' },
            { label:lang === 'vi' ? 'Đang chờ' : 'Open', value:qs.filter(q => ['draft', 'sent'].includes(quoteStatus(q))).length, color:'var(--primary)' },
            { label:lang === 'vi' ? 'Mất' : 'Lost', value:qs.filter(q => ['rejected', 'expired'].includes(quoteStatus(q))).length, color:'var(--error)' },
          ]} />
        </div></div>
      </div>
      <RecentActivity items={recent} title={lang === 'vi' ? 'Tương tác báo giá mới nhất' : 'Latest quote activity'} />
    </div>
  );
}

/* Kanban các bước báo giá — card bấm mở chi tiết; option "hiện tất cả cột" (mặc định chỉ hiện cột có báo giá). */
const QUOTE_FLOW = ['draft', 'sent', 'accepted', 'won', 'converted', 'rejected', 'expired'];
function BgKanban({ list, allCols, go }){
  const p = usePortal();
  const { t, lang } = useLang();
  // cùng bảng màu trạng thái với BgOverview (phễu báo giá)
  const colors = { draft:'var(--gray-400)', sent:'var(--primary)', accepted:'var(--success)', won:'var(--success)', rejected:'var(--error)', expired:'var(--warning)', converted:'#7A5AF8' };
  const byCol = {};
  list.forEach(x => { const st = quoteStatus(x); (byCol[st] = byCol[st] || []).push(x); });
  const cols = QUOTE_FLOW.filter(s => allCols || (byCol[s] && byCol[s].length));
  if (!cols.length) return <EmptyState text={t('c.empty')} />;
  return (
    <div className="kanban-scroll">
      {cols.map(s => {
        const items = byCol[s] || [];
        const total = items.reduce((a, x) => a + quoteTotals(x).total, 0);
        return (
          <div key={s} className="task-col">
            <div className="task-col-head">
              <span className="flex g8 items-center"><span style={{ width:8, height:8, borderRadius:'50%', background:colors[s], flex:'none' }}></span>{QUOTE_STATUS[s][lang]}</span>
              <span className="flex g8 items-center"><span className="subtle mono" style={{ fontSize:'var(--fs-sm)', fontWeight:500 }}>{items.length ? vndShort(total) : ''}</span><span className="ncount">{items.length}</span></span>
            </div>
            <div className="task-col-body">
              {items.length === 0 && <div className="subtle" style={{ fontSize:'var(--fs-base)', textAlign:'center', padding:'10px 0' }}>—</div>}
              {items.map(x => {
                const cust = CustomerStore.byCode(x.customer) || {};
                const st = quoteStatus(x);
                return (
                  <div key={x.code} className="task-card" onClick={() => go('detail', { id:x.code })}>
                    <div className="flex justify-between items-center g8">
                      <span className="mono truncate" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)' }}>{x.code}</span>
                      {['draft', 'sent', 'expired'].includes(st) && <button className="iconbtn" style={{ width:22, height:22, flex:'none' }} title={t('bg.sendShort')} onClick={e => { e.stopPropagation(); p.sendQuote(x); }}><i className="bi bi-send" style={{ fontSize:12 }}></i></button>}
                    </div>
                    <div className="truncate" style={{ fontWeight:500, fontSize:'var(--fs-md)', margin:'4px 0' }}>{cust.shortName || cust.name || x.customer}</div>
                    <div className="flex justify-between items-center" style={{ fontSize:'var(--fs-base)' }}>
                      <span className="mono" style={{ fontWeight:600 }}>{vndShort(quoteTotals(x).total)}</span>
                      <span className="mono" style={{ fontSize:'var(--fs-sm)', color:st === 'expired' ? 'var(--error)' : 'var(--text-subtle)' }}>{fmtDate(x.validUntil)}</span>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* Trang Kanban (mục sidebar riêng — thay cho "Danh sách" cũ; list chi tiết xem theo từng trạng thái). */
function BgKanbanPage({ go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const [q, setQ] = React.useState('');
  const [allCols, setAllCols] = usePersist('bgKanbanAllCols', false); // hiện cả cột rỗng
  const list = p.quotes.filter(x => {
    const cust = CustomerStore.byCode(x.customer) || {};
    return quoteDir(x) === 'sell' && (!q || (x.code + (cust.name || '') + (cust.shortName || '')).toLowerCase().includes(q.toLowerCase()));
  });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>Kanban</h1><div className="sub">{list.length} {lang === 'vi' ? 'báo giá theo từng bước' : 'quotes by stage'}</div></div>
        <button className="btn btn-primary" onClick={() => p.quick('quotes')}><i className="bi bi-plus-lg"></i>{t('bg.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>
        <label className="flex g8 items-center" style={{ fontSize:'var(--fs-md)', cursor:'pointer', userSelect:'none' }}>
          <input type="checkbox" checked={!!allCols} onChange={e => setAllCols(e.target.checked)} />{lang === 'vi' ? 'Hiện tất cả cột' : 'Show all columns'}
        </label>
      </div>
      <BgKanban list={list} allCols={!!allCols} go={go} />
    </div>
  );
}

/* Bảng báo giá dùng chung cho Danh sách + các list theo trạng thái (+ list Báo giá mua: buy → cột NCC, không có nút Gửi). */
function BgTable({ list, go, buy }){
  const p = usePortal();
  const { t, lang } = useLang();
  const srt = useTableSort();
  list = srt.sortRows(list, { code:x => x.code, party:x => { const c = bgParty(x); return c.shortName || c.name || x.customer; },
    issue:x => x.issueDate, until:x => x.validUntil, value:x => quoteTotals(x).total, status:x => quoteStatusLabel(x, quoteStatus(x), lang) });
  return (
    <div className="card"><div style={{ overflow:'auto' }}>
      <table className="tbl">
        <thead><tr><ThSort k="code" sort={srt}>{t('bg.code')}</ThSort><ThSort k="party" sort={srt}>{buy ? t('hd.vendor') : t('ct.customer')}</ThSort><ThSort k="issue" sort={srt}>{t('bg.issueDate')}</ThSort><ThSort k="until" sort={srt}>{t('bg.validUntil')}</ThSort><ThSort k="value" sort={srt} className="num">{t('bg.value')}</ThSort><ThSort k="status" sort={srt}>{t('c.status')}</ThSort><th></th></tr></thead>
        <tbody>
          {list.map(x => {
            const cust = bgParty(x);
            const st = quoteStatus(x);
            return (
              <tr key={x.code} onClick={() => go('detail', { id:x.code })}>
                <td><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{x.code}</span></td>
                <td style={{ fontWeight:500 }}>{cust.shortName || cust.name || x.customer}</td>
                <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(x.issueDate)}</td>
                <td className="mono" style={{ fontSize:'var(--fs-base)', color:st === 'expired' ? 'var(--error)' : 'inherit' }}>{fmtDate(x.validUntil)}</td>
                <td className="num" style={{ fontWeight:600 }}>{vndShort(quoteTotals(x).total)}</td>
                <td><BgStatusBadge q={x} /></td>
                <td style={{ textAlign:'right' }}>{quoteDir(x) !== 'buy' && ['draft', 'sent', 'expired'].includes(st) && <button className="btn btn-sm btn-primary" onClick={e => { e.stopPropagation(); p.sendQuote(x); }}><i className="bi bi-send"></i>{t('bg.sendShort')}</button>}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
      {!list.length && <EmptyState text={t('c.empty')} />}
    </div></div>
  );
}

/* List báo giá theo CHIỀU (bán/mua) + lọc TRẠNG THÁI từ sidebar "sổ" (route.status; rỗng = tất cả).
   Có bộ lọc kỳ (theo ngày phát hành) + tìm kiếm; bảng dùng chung BgTable. Thay cho BgBuyList/BgStatusList cũ. */
function BgDirList({ dir, go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const [q, setQ] = React.useState('');
  const period = useReportPeriod('thisyear');
  const isBuy = dir === 'buy';
  const status = (p.route.status && p.route.status !== 'all') ? p.route.status : null;
  const inDir = p.quotes.filter(x => quoteDir(x) === dir);
  const list = inDir.filter(x => {
    const c = bgParty(x);
    return (!status || quoteStatus(x) === status) && inPeriod(x.issueDate, period) &&
      (!q || (x.code + (c.name || '') + (c.shortName || '')).toLowerCase().includes(q.toLowerCase()));
  }).sort((a, b) => String(b.issueDate || '').localeCompare(String(a.issueDate || '')));
  const total = list.reduce((s, x) => s + quoteTotals(x).total, 0);
  const s = status ? QUOTE_STATUS[status] : null;
  const title = isBuy ? t('bg.buy') : (lang === 'vi' ? 'Báo giá bán' : 'Sales quotes');
  const statusName = status ? quoteStatusLabel({ direction:dir }, status, lang) : (lang === 'vi' ? 'Tất cả trạng thái' : 'All statuses');
  const flowSub = isBuy
    ? (lang === 'vi' ? 'Đầu vào từ NCC: Ghi nhận → Đồng ý mua → Hợp đồng → Nhập kho' : 'Inbound vendor quotes')
    : (lang === 'vi' ? 'Đầu ra tới khách hàng: Nháp → Gửi → Đồng ý → Chốt / Chuyển HĐ' : 'Outbound customer quotes');
  return (
    <div className="page pagefade">
      <div className="page-head">
        <div><h1 className="flex g8 items-center">{title}<span className={'badge ' + (s ? s.cls : 'badge-neutral')}>{list.length}</span>
          <span className="sub" style={{ fontWeight:400 }}>· {statusName}</span></h1>
          <div className="sub">{flowSub} · {lang === 'vi' ? 'tổng trong kỳ ' : 'total in period '}<span className="mono" style={{ fontWeight:600 }}>{vndShort(total)}</span></div></div>
        <button className="btn btn-primary" onClick={() => p.quick('quotes', isBuy ? { direction:'buy' } : null)}><i className="bi bi-plus-lg"></i>{isBuy ? t('bg.newBuy') : t('bg.new')}</button>
      </div>
      <ReportControls period={period} />
      <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>
      </div>
      <BgTable list={list} go={go} buy={isBuy} />
    </div>
  );
}

function BgDetail({ code, go }){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const q = p.quotes.find(x => x.code === code);
  const [preview, setPreview] = React.useState(false);
  const [payReq, setPayReq] = React.useState(null);        // đợt TT đang mở Giấy đề nghị thanh toán
  const [acceptOpen, setAcceptOpen] = React.useState(false); // Biên bản nghiệm thu (báo giá won không HĐ)
  if (!q) return <div className="page"><EmptyState text={t('c.empty')} /></div>;
  const isBuy = quoteDir(q) === 'buy';
  const cust = bgParty(q);
  const rep = q.rep ? (ContactStore.byCode(q.rep) || {}) : {};
  const tot = quoteTotals(q);
  const st = quoteStatus(q);
  const needContract = tot.total > QUOTE_CONTRACT_THRESHOLD;   // chỉ để KHUYẾN NGHỊ — không chặn chốt
  const linkedContract = q.convertedTo ? p.contracts.find(x => x.code === q.convertedTo) : null;
  // Mồ côi: đã chuyển HĐ nhưng HĐ đó bị xóa (dữ liệu cũ trước fix deleteContract) → cho khôi phục để đi tiếp.
  const orphanConverted = st === 'converted' && q.convertedTo && !linkedContract;
  const restoreAccepted = () => {
    p.patchQuote(q.code, qWithHist({ ...q, status:'accepted', convertedTo:null },
      qEvt('status', (lang === 'vi' ? 'Hợp đồng ' + q.convertedTo + ' không còn tồn tại — quay về bước "' : 'Contract ' + q.convertedTo + ' no longer exists — back to "') + quoteStatusLabel(q, 'accepted', lang) + '"', p.me.name)));
    toast(lang === 'vi' ? 'Đã khôi phục — có thể chuyển hợp đồng lại hoặc chốt không HĐ' : 'Restored — convert again or close without contract', 'success');
  };

  const setStatus = (s, msg) => { p.patchQuote(q.code, qWithHist({ ...q, status:s }, qEvt('status', (lang === 'vi' ? 'Trạng thái: ' : 'Status: ') + quoteStatusLabel(q, s, lang), p.me.name))); toast(msg, 'success'); };
  const closeWon = async () => {
    // >10tr: cảnh báo NÊN có hợp đồng, nhưng vẫn cho chốt nếu user xác nhận.
    if (needContract && !(await confirm({
      message: lang === 'vi'
        ? 'Giá trị ' + vnd(tot.total) + ' > 10 triệu — NÊN lập hợp đồng. Vẫn chốt không hợp đồng?'
        : 'Value ' + vnd(tot.total) + ' > 10M — a contract is recommended. Close without one?',
      confirmLabel: lang === 'vi' ? 'Vẫn chốt' : 'Close anyway',
    }))) return;
    // Chốt không HĐ ≠ hết việc: tạo sẵn 1 đợt thanh toán 100% để vẫn quản lý
    // xuất/nhập kho · bàn giao · đề nghị thanh toán · thanh toán (khối "Thực hiện sau chốt").
    const sched = (q.schedule && q.schedule.length) ? q.schedule
      : [pay(1, tot.total, addDays(TODAY, 7), isBuy ? (lang === 'vi' ? 'Trả NCC khi nhận hàng' : 'On receipt') : (lang === 'vi' ? 'Thanh toán khi bàn giao' : 'On delivery'), false)];
    p.patchQuote(q.code, qWithHist({ ...q, status:'won', schedule:sched }, qEvt('status', isBuy
      ? (lang === 'vi' ? 'Mua trực tiếp — không lập hợp đồng' : 'Bought directly — no contract')
      : (lang === 'vi' ? 'Chốt đơn (Won) — không lập hợp đồng' : 'Closed as won — no contract'), p.me.name)));
    toast(isBuy ? (lang === 'vi' ? 'Đã chốt mua — không lập hợp đồng' : 'Purchase closed — no contract') : (lang === 'vi' ? 'Đã chốt đơn — không lập hợp đồng' : 'Closed as won — no contract'), 'success');
  };
  const [note, setNote] = React.useState('');
  const addNote = () => { if (!note.trim()) return; p.patchQuote(q.code, qWithHist(q, qEvt('note', note.trim(), p.me.name))); setNote(''); toast(lang === 'vi' ? 'Đã thêm ghi chú theo dõi' : 'Note added', 'success'); };
  const convert = () => {
    const ccode = genContractCode(p.contracts);
    const f = 1 - (+q.discountPct || 0) / 100;
    const total = tot.total;
    const half = Math.round(total / 2 / 1000) * 1000;
    const rec = {
      code:ccode,
      // HĐ mua: số HĐ do NCC cấp → để trống, điền sau ở trang hợp đồng; HĐ bán: sinh theo định dạng.
      contractNo:isBuy ? '' : formatContractNo(p.appSettings.contractNoFormat, { date:TODAY, kh:cust.shortName || q.customer, seq:p.contracts.filter(c => contractDir(c) === 'sell').length + 1 }),
      direction:isBuy ? 'buy' : 'sell', category:'mua-ban', signDate:TODAY, customer:q.customer, rep:q.rep || null, fromQuote:q.code,
      termType:'once', startDate:TODAY, endDate:null, remindBefore:30, status:'active',
      items:q.items.map(it => ({ name:it.name, unit:it.unit, qty:it.qty, price:Math.round(it.price * f), vat:it.vat })),
      schedule:[ pay(1, half, addDays(TODAY, 7), lang === 'vi' ? 'Tạm ứng 50% khi ký' : 'On signing', false), pay(2, total - half, addDays(TODAY, 37), lang === 'vi' ? 'Sau nghiệm thu' : 'On acceptance', false) ],
    };
    p.saveContract(rec);
    p.patchQuote(q.code, qWithHist({ ...q, status:'converted', convertedTo:ccode }, qEvt('convert', (isBuy ? (lang === 'vi' ? 'Chuyển thành hợp đồng mua ' : 'Converted to purchase contract ') : (lang === 'vi' ? 'Chuyển thành hợp đồng ' : 'Converted to contract ')) + ccode, p.me.name)));
    toast((isBuy ? (lang === 'vi' ? 'Đã tạo hợp đồng mua ' : 'Created purchase contract ') : (lang === 'vi' ? 'Đã tạo hợp đồng ' : 'Created contract ')) + ccode + (lang === 'vi' ? ' từ báo giá' : ' from quote'), 'success');
    p.navigate({ name:'module', module:'contracts', sub:'detail', id:ccode });
  };

  // ----- "Thực hiện sau chốt" (won không HĐ): xuất/nhập kho · bàn giao · đề nghị TT · thanh toán
  //        chạy trên CHÍNH báo giá — chứng từ tái dùng qua adapter dạng hợp đồng (cùng shape field). -----
  const isWon = st === 'won';
  const asContract = { ...q, contractNo:q.code, direction:isBuy ? 'buy' : 'sell', signDate:q.issueDate, schedule:q.schedule || [] };
  const linkedIssues = (p.issues || []).filter(i => i.contract === q.code);
  const linkedReceipts = (p.receipts || []).filter(r => r.buyContract === q.code);
  const sched = q.schedule || [];
  const paidSum = sched.filter(s => s.paid).reduce((s, x) => s + x.amount, 0);
  const payVerb = isBuy
    ? { done:lang === 'vi' ? 'Đã trả' : 'Paid', undone:lang === 'vi' ? 'Chưa trả' : 'Unpaid', mark:lang === 'vi' ? 'Đã trả' : 'Mark paid' }
    : { done:lang === 'vi' ? 'Đã thu' : 'Paid', undone:lang === 'vi' ? 'Chưa thu' : 'Unpaid', mark:lang === 'vi' ? 'Đã thu' : 'Mark paid' };
  // Báo giá "chốt không HĐ" từ TRƯỚC v183 chưa có lịch thanh toán → tự tạo 1 đợt 100% (heal dữ liệu cũ).
  const payCond = isBuy ? (lang === 'vi' ? 'Trả NCC khi nhận hàng' : 'On receipt') : (lang === 'vi' ? 'Thanh toán khi bàn giao' : 'On delivery');
  const createInstallment = (paid) => {
    p.patchQuote(q.code, qWithHist({ ...q, schedule:[pay(1, tot.total, addDays(TODAY, 7), payCond, !!paid, paid ? TODAY : null)] },
      qEvt('status', paid ? (isBuy ? (lang === 'vi' ? 'Đã trả 100%' : 'Paid 100%') : (lang === 'vi' ? 'Đã thu 100%' : 'Paid 100%')) : (lang === 'vi' ? 'Tạo đợt thanh toán' : 'Installment created'), p.me.name)));
    toast(paid ? (lang === 'vi' ? 'Đã ghi nhận thanh toán' : 'Payment recorded') : (lang === 'vi' ? 'Đã tạo đợt thanh toán' : 'Installment created'), 'success');
  };
  const markPaid = (no) => {
    p.patchQuote(q.code, { ...q, schedule:sched.map(s => s.no === no ? { ...s, paid:true, paidDate:TODAY } : s) });
    toast(lang === 'vi' ? 'Đã ghi nhận thanh toán đợt ' + no : 'Payment ' + no + ' recorded', 'success');
  };
  // Nút "Đã thu" ở quy trình: chưa có đợt nào (báo giá cũ) → tạo luôn 1 đợt 100% ĐÃ thu; ngược lại thu đợt kế chưa thu.
  const payNext = () => { if (!sched.length) return createInstallment(true); const next = sched.find(s => !s.paid); if (next) markPaid(next.no); };
  const confirmDeliver = async () => {
    if (await confirm({ message: isBuy ? (lang === 'vi' ? 'Xác nhận đã nhận hàng & nghiệm thu?' : 'Confirm goods received & accepted?') : (lang === 'vi' ? 'Xác nhận đã giao hàng & nghiệm thu?' : 'Confirm delivery & acceptance?'), confirmLabel:lang === 'vi' ? 'Xác nhận' : 'Confirm' })) {
      p.patchQuote(q.code, qWithHist({ ...q, acceptedDate:TODAY }, qEvt('status', lang === 'vi' ? 'Đã giao hàng & nghiệm thu' : 'Delivered & accepted', p.me.name)));
      toast(lang === 'vi' ? 'Đã ghi nhận giao hàng & nghiệm thu' : 'Delivery & acceptance recorded', 'success');
    }
  };
  const flowActions = isWon ? (isBuy ? {
    receive: { label:lang === 'vi' ? 'Nhập kho' : 'Receive', icon:'bi-box-arrow-in-down', run:() => p.openNewReceipt({ source:'buy', buyContract:q.code, vendor:q.customer, fromItems:q.items }) },
    payV:    { label:payVerb.mark, icon:'bi-cash-coin', run:payNext },
  } : {
    issue:   { label:lang === 'vi' ? 'Xuất kho' : 'Issue', icon:'bi-box-arrow-up', run:() => p.openNewIssue(q.code) },
    deliver: { label:lang === 'vi' ? 'Xác nhận đã giao & nghiệm thu' : 'Confirm delivery', icon:'bi-truck', run:confirmDeliver },
    pay:     { label:payVerb.mark, icon:'bi-cash-coin', run:payNext },
  }) : null;

  const validLeft = daysBetween(TODAY, q.validUntil);
  return (
    <div className="page pagefade">
      <div className="page-head">
        <div className="flex g12 items-center">
          <button className="btn btn-ghost btn-icon" onClick={() => go(bgHome(q))}><i className="bi bi-arrow-left"></i></button>
          <div><h1 style={{ fontSize:'var(--fs-2xl)' }}>{q.code}</h1>
            <div className="sub flex g8 items-center"><BgStatusBadge q={q} /> · <span style={{ fontWeight:600 }}>{vnd(tot.total)}</span> · {cust.shortName || cust.name}</div></div>
        </div>
        <div className="flex g8">
          {!isBuy && ['draft', 'sent', 'expired'].includes(st) && <button className="btn btn-primary" onClick={() => p.sendQuote(q)}><i className="bi bi-send"></i>{st === 'draft' ? t('bg.send') : t('bg.resend')}</button>}
          {!isBuy && st === 'sent' && <button className="btn btn-accent" onClick={() => setStatus('accepted', lang === 'vi' ? 'Đã ghi nhận khách đồng ý' : 'Marked agreed')}><i className="bi bi-check2-circle"></i>{t('bg.accept')}</button>}
          {!isBuy && st === 'sent' && <button className="btn" onClick={() => setStatus('rejected', lang === 'vi' ? 'Đã đánh dấu khách từ chối' : 'Marked declined')}><i className="bi bi-x-circle"></i>{t('bg.reject')}</button>}
          {isBuy && ['draft', 'expired'].includes(st) && <button className="btn btn-accent" onClick={() => setStatus('accepted', lang === 'vi' ? 'Đã duyệt mua' : 'Purchase approved')}><i className="bi bi-check2-circle"></i>{lang === 'vi' ? 'Đồng ý mua' : 'Approve'}</button>}
          {isBuy && ['draft', 'expired'].includes(st) && <button className="btn" onClick={() => setStatus('rejected', lang === 'vi' ? 'Đã đánh dấu không chọn' : 'Marked not selected')}><i className="bi bi-x-circle"></i>{lang === 'vi' ? 'Không chọn' : 'Reject'}</button>}
          {/* accepted: LUÔN cho cả 2 lựa chọn — HĐ (khuyến nghị khi >10tr) hoặc chốt không HĐ (có cảnh báo xác nhận) */}
          {st === 'accepted' && <button className={'btn ' + (needContract ? 'btn-accent' : '')} onClick={convert}><i className="bi bi-file-earmark-check"></i>{isBuy ? (lang === 'vi' ? 'Chuyển HĐ mua' : 'Convert to PO') : t('bg.convert')}</button>}
          {st === 'accepted' && <button className={'btn ' + (needContract ? '' : 'btn-accent')} onClick={closeWon}><i className="bi bi-trophy"></i>{t('bg.closeWon')}</button>}
          {!['converted', 'won'].includes(st) && <button className="btn" onClick={() => p.editQuote(q)}><i className="bi bi-pencil"></i>{t('c.edit')}</button>}
          {!isBuy && <button className="btn" onClick={() => setPreview(true)}><i className="bi bi-filetype-pdf"></i>{t('bg.preview')}</button>}
          {['admin', 'manager'].includes(p.role) && <button className="btn btn-icon" title={t('c.delete')} onClick={async () => { if (await confirm({ message:(lang === 'vi' ? 'Xóa báo giá ' : 'Delete quote ') + q.code + '?', confirmLabel:lang === 'vi' ? 'Xóa' : 'Delete' })) { const home = bgHome(q); p.deleteQuote(q.code); toast((lang === 'vi' ? 'Đã xóa ' : 'Deleted ') + q.code, 'success'); go(home); } }}><i className="bi bi-trash"></i></button>}
        </div>
      </div>

      <PurchaseFlow quote={q} contract={linkedContract} actions={flowActions} />

      {q.confirmedAt && (
        <div className="card" style={{ borderColor:'var(--success)', background:'var(--success-bg)', marginBottom:14 }}>
          <div className="card-body">
            <div className="flex g8 items-center" style={{ color:'var(--success)', fontWeight:700 }}>
              <i className="bi bi-patch-check-fill" style={{ fontSize:16 }}></i>{lang === 'vi' ? 'Khách đã xác nhận báo giá online' : 'Customer confirmed online'}
              <span className="subtle mono" style={{ fontWeight:400, fontSize:'var(--fs-sm)', marginLeft:'auto' }}>{(() => { try { return new Date(q.confirmedAt).toLocaleString(lang === 'vi' ? 'vi-VN' : 'en-US'); } catch (e) { return (q.confirmedAt || '').slice(0, 16).replace('T', ' '); } })()}</span>
            </div>
            {q.confirmation && (
              <div style={{ marginTop:8 }}>
                <div className="det-row"><span className="dk">{lang === 'vi' ? 'Người xác nhận' : 'Confirmed by'}</span><span className="dv" style={{ fontWeight:600 }}>{q.confirmation.name || '—'}</span></div>
                <div className="det-row"><span className="dk">{lang === 'vi' ? 'Điện thoại' : 'Phone'}</span><span className="dv mono">{q.confirmation.phone || '—'}</span></div>
                {q.confirmation.note ? <div className="det-row"><span className="dk">{lang === 'vi' ? 'Ghi chú' : 'Note'}</span><span className="dv">{q.confirmation.note}</span></div> : null}
              </div>
            )}
            {st === 'sent' && <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginTop:8 }}><i className="bi bi-info-circle" style={{ marginRight:4 }}></i>{lang === 'vi' ? 'Khách đã đồng ý online — bấm "Khách đồng ý" để duyệt và chuyển bước tiếp theo.' : 'Customer agreed online — click "Mark agreed" to proceed.'}</div>}
          </div>
        </div>
      )}

      {/* >10tr = CẢNH BÁO khuyến nghị (không chặn): vẫn chốt không HĐ được sau xác nhận */}
      {st === 'accepted' && needContract && (
        <div className="card" style={{ borderColor:'var(--warning)', background:'var(--warning-bg)', marginBottom:14 }}><div className="card-body flex g8 items-center wrap" style={{ color:'var(--warning)', fontWeight:600 }}>
          <i className="bi bi-exclamation-triangle" style={{ fontSize:16 }}></i>{t('bg.needContract')}
          <span className="flex g8 items-center" style={{ marginLeft:'auto' }}>
            <button className="btn btn-sm btn-accent" onClick={convert}><i className="bi bi-file-earmark-check"></i>{isBuy ? (lang === 'vi' ? 'Chuyển HĐ mua' : 'Convert to PO') : t('bg.convert')}</button>
            <button className="btn btn-sm" onClick={closeWon}><i className="bi bi-trophy"></i>{lang === 'vi' ? 'Vẫn chốt không HĐ' : 'Close anyway'}</button>
          </span>
        </div></div>
      )}
      {st === 'accepted' && !needContract && (
        <div className="card" style={{ borderColor:'var(--success)', background:'var(--success-bg, #E8F6EE)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'var(--success)', fontWeight:600 }}>
          <i className="bi bi-check2-circle" style={{ fontSize:16 }}></i>{t('bg.closeWonHint')}
          <button className="btn btn-sm btn-accent" style={{ marginLeft:'auto' }} onClick={closeWon}><i className="bi bi-trophy"></i>{t('bg.closeWon')}</button>
        </div></div>
      )}
      {st === 'won' && (
        <div className="card" style={{ borderColor:'var(--success)', background:'var(--success-bg, #E8F6EE)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'var(--success)', fontWeight:600 }}>
          <i className="bi bi-trophy" style={{ fontSize:16 }}></i>{isBuy ? (lang === 'vi' ? 'Đã chốt mua trực tiếp — không lập hợp đồng.' : 'Bought directly — no contract.') : t('bg.wonNote')}
          <span className="subtle" style={{ fontWeight:400, fontSize:'var(--fs-sm)' }}>{lang === 'vi' ? 'Kho · bàn giao · thanh toán quản lý ở khối bên dưới.' : 'Stock, delivery & payment tracked below.'}</span>
        </div></div>
      )}

      {/* Thực hiện sau chốt (won không HĐ): vẫn quản lý kho · nghiệm thu · đề nghị TT · thanh toán */}
      {isWon && (
        <div className="card" style={{ marginBottom:14 }}>
          <div className="card-head"><h3><i className="bi bi-list-check" style={{ marginRight:6, color:'var(--primary)' }}></i>{lang === 'vi' ? 'Thực hiện sau chốt' : 'Fulfilment'}</h3>
            <div className="flex g8">
              {isBuy
                ? <button className="btn btn-sm" onClick={() => p.openNewReceipt({ source:'buy', buyContract:q.code, vendor:q.customer, fromItems:q.items })}><i className="bi bi-box-arrow-in-down"></i>{lang === 'vi' ? 'Nhập kho' : 'Receive'}</button>
                : <button className="btn btn-sm" onClick={() => p.openNewIssue(q.code)}><i className="bi bi-box-arrow-up"></i>{lang === 'vi' ? 'Xuất kho' : 'Issue stock'}</button>}
              <button className="btn btn-sm" onClick={() => setAcceptOpen(true)}><i className="bi bi-clipboard-check"></i>{lang === 'vi' ? 'Biên bản nghiệm thu' : 'Acceptance doc'}</button>
              {!q.acceptedDate && <button className="btn btn-sm btn-primary" onClick={confirmDeliver}><i className="bi bi-truck"></i>{isBuy ? (lang === 'vi' ? 'Đã nhận hàng & nghiệm thu' : 'Received & accepted') : (lang === 'vi' ? 'Đã giao & nghiệm thu' : 'Delivered & accepted')}</button>}
              {q.acceptedDate && <span className="badge badge-success"><i className="bi bi-truck" style={{ marginRight:3 }}></i>{lang === 'vi' ? 'Đã nghiệm thu ' : 'Accepted '}{fmtDate(q.acceptedDate)}</span>}
            </div>
          </div>
          <div className="card-body col g10">
            {(linkedIssues.length > 0 || linkedReceipts.length > 0) && (
              <div className="col g4">
                {linkedIssues.map(i => (
                  <div key={i.code} className="list-row" style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'inventory', sub:'issue', id:i.code })}>
                    <i className="bi bi-box-arrow-up" style={{ color:'var(--primary)' }}></i><span className="mono" style={{ fontWeight:600 }}>{i.code}</span>
                    <span className="grow subtle" style={{ fontSize:'var(--fs-base)' }}>{(i.lines || []).length} {lang === 'vi' ? 'dòng' : 'lines'}</span><IssueStatusBadge status={i.status} />
                  </div>
                ))}
                {linkedReceipts.map(r => (
                  <div key={r.code} className="list-row" onClick={() => p.navigate({ name:'module', module:'inventory', sub:'moves' })} style={{ cursor:'pointer' }}>
                    <i className="bi bi-box-arrow-in-down" style={{ color:'var(--accent)' }}></i><span className="mono" style={{ fontWeight:600 }}>{r.code}</span>
                    <span className="grow subtle" style={{ fontSize:'var(--fs-base)' }}>{(r.lines || []).length} {lang === 'vi' ? 'dòng' : 'lines'} · {fmtDate(r.date)}</span><span className="badge badge-success">{lang === 'vi' ? 'Đã nhập' : 'Received'}</span>
                  </div>
                ))}
              </div>
            )}
            <div style={{ overflow:'auto' }}>
              <table className="tbl">
                <thead><tr><th style={{ width:44 }}>{lang === 'vi' ? 'Đợt' : '#'}</th><th>{lang === 'vi' ? 'Nội dung' : 'Note'}</th><th>{lang === 'vi' ? 'Hạn' : 'Due'}</th><th className="num">{lang === 'vi' ? 'Số tiền' : 'Amount'}</th><th>{t('c.status')}</th><th></th></tr></thead>
                <tbody>
                  {!sched.length && (
                    <tr style={{ cursor:'default' }}>
                      <td colSpan="6" style={{ textAlign:'center', padding:'18px' }}>
                        <div className="flex g10 items-center" style={{ justifyContent:'center' }}>
                          <span className="subtle">{lang === 'vi' ? 'Chưa có đợt thanh toán cho báo giá này' : 'No payment installment yet'}</span>
                          <button className="btn btn-sm btn-primary" onClick={() => createInstallment(false)}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Tạo đợt thanh toán 100%' : 'Create 100% installment'}</button>
                        </div>
                      </td>
                    </tr>
                  )}
                  {sched.map(s => (
                    <tr key={s.no} style={{ cursor:'default' }}>
                      <td className="muted">{s.no}</td>
                      <td>{s.condition || '—'}</td>
                      <td className="mono">{fmtDate(s.dueDate)}</td>
                      <td className="num mono" style={{ fontWeight:600 }}>{vnd(s.amount)}</td>
                      <td>{s.paid ? <span className="badge badge-success">{payVerb.done}{s.paidDate ? ' · ' + fmtDate(s.paidDate) : ''}</span> : <span className="badge badge-warning">{payVerb.undone}</span>}</td>
                      <td style={{ textAlign:'right' }}>
                        <span className="flex g8" style={{ justifyContent:'flex-end' }}>
                          <button className="btn btn-sm" onClick={() => setPayReq(s)}><i className="bi bi-file-earmark-text"></i>{lang === 'vi' ? 'Đề nghị TT' : 'Pay request'}</button>
                          {!s.paid && <button className="btn btn-sm btn-primary" onClick={() => markPaid(s.no)}><i className="bi bi-cash-coin"></i>{payVerb.mark}</button>}
                        </span>
                      </td>
                    </tr>
                  ))}
                </tbody>
                <tfoot>
                  <tr style={{ cursor:'default', background:'var(--bg-subtle)' }}><td colSpan="3"></td><td className="num" style={{ fontWeight:700 }}>{lang === 'vi' ? (isBuy ? 'Đã trả' : 'Đã thu') : 'Paid'}</td><td colSpan="2" className="mono" style={{ fontWeight:700, color:'var(--success)' }}>{vnd(paidSum)} / {vnd(tot.total)}</td></tr>
                </tfoot>
              </table>
            </div>
          </div>
        </div>
      )}

      {st === 'sent' && validLeft >= 0 && validLeft <= 7 && (
        <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>{lang === 'vi' ? 'Báo giá sắp hết hiệu lực' : 'Quote expiring soon'} — {validLeft} {t('hd.daysLeft')} ({fmtDate(q.validUntil)})
          <button className="btn btn-sm" style={{ marginLeft:'auto' }} onClick={() => p.sendQuote(q)}><i className="bi bi-send"></i>{t('bg.resend')}</button>
        </div></div>
      )}
      {st === 'expired' && (
        <div className="card" style={{ borderColor:'var(--error)', background:'var(--error-bg, #FEECEC)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'var(--error)', fontWeight:600 }}>
          <i className="bi bi-exclamation-octagon" style={{ fontSize:16 }}></i>{lang === 'vi' ? 'Báo giá đã hết hiệu lực' : 'Quote has expired'} ({fmtDate(q.validUntil)}) — {isBuy ? (lang === 'vi' ? 'xin NCC chào lại hoặc vẫn duyệt mua nếu giá còn giữ' : 'ask the vendor to requote, or approve if the price still holds') : (lang === 'vi' ? 'gửi lại bản cập nhật nếu cần' : 'resend an update if needed')}
        </div></div>
      )}
      {st === 'converted' && linkedContract && (
        <div className="card" style={{ borderColor:'var(--success)', background:'var(--success-bg, #E8F6EE)', marginBottom:14 }}><div className="card-body flex g8 items-center" style={{ color:'var(--success)', fontWeight:600 }}>
          <i className="bi bi-file-earmark-check" style={{ fontSize:16 }}></i>{t('bg.converted')} <span className="mono" style={{ fontWeight:700 }}>{q.convertedTo}</span>
          <button className="btn btn-sm" style={{ marginLeft:'auto' }} onClick={() => p.navigate({ name:'module', module:'contracts', sub:'detail', id:q.convertedTo })}>{lang === 'vi' ? 'Mở hợp đồng' : 'Open contract'} <i className="bi bi-arrow-right"></i></button>
        </div></div>
      )}
      {/* HĐ liên kết đã bị xóa (dữ liệu mồ côi) → khôi phục về bước đồng ý để chuyển lại / chốt không HĐ */}
      {orphanConverted && (
        <div className="card" style={{ borderColor:'var(--error)', background:'var(--error-bg, #FEECEC)', marginBottom:14 }}><div className="card-body flex g8 items-center wrap" style={{ color:'var(--error)', fontWeight:600 }}>
          <i className="bi bi-exclamation-octagon" style={{ fontSize:16 }}></i>
          {lang === 'vi' ? <>Hợp đồng <span className="mono" style={{ fontWeight:700 }}>{q.convertedTo}</span> không còn tồn tại (đã bị xóa) — báo giá đang kẹt ở bước "Đã chuyển HĐ".</> : <>Contract <span className="mono" style={{ fontWeight:700 }}>{q.convertedTo}</span> no longer exists — this quote is stuck at "Converted".</>}
          <button className="btn btn-sm btn-primary" style={{ marginLeft:'auto' }} onClick={restoreAccepted}><i className="bi bi-arrow-counterclockwise"></i>{lang === 'vi' ? 'Khôi phục về "' + quoteStatusLabel(q, 'accepted', lang) + '"' : 'Restore to "' + quoteStatusLabel(q, 'accepted', lang) + '"'}</button>
        </div></div>
      )}

      <div className="split" style={{ marginBottom:14 }}>
        <div className="card"><div className="card-head"><h3>{isBuy ? t('hd.vendor') : (lang === 'vi' ? 'Khách hàng' : 'Customer')}</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={isBuy ? 'var(--accent)' : 'var(--primary)'} />
            <div><div style={{ fontWeight:600 }}>{cust.name}</div><div className="mono subtle" style={{ fontSize:'var(--fs-sm)' }}>{q.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('mail.to')}</span><span className="dv mono" style={{ fontSize:'var(--fs-base)' }}>{rep.email || '—'}</span></div>
        </div></div>
        <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Thông tin báo giá' : 'Quote info'}</h3></div><div className="card-body">
          <div className="det-row"><span className="dk">{t('bg.issueDate')}</span><span className="dv mono">{fmtDate(q.issueDate)}</span></div>
          <div className="det-row"><span className="dk">{t('bg.validUntil')}</span><span className="dv mono">{fmtDate(q.validUntil)} <span className="subtle">({q.validDays} {lang === 'vi' ? 'ngày' : 'days'})</span></span></div>
          <div className="det-row"><span className="dk">{t('c.owner')}</span><span className="dv">{q.owner}</span></div>
          {q.sentDate && <div className="det-row"><span className="dk">{t('bg.sentTo')}</span><span className="dv mono" style={{ fontSize:'var(--fs-base)' }}>{(q.sentTo || []).join(', ') || '—'} · {fmtDate(q.sentDate)}</span></div>}
          {q.notes && <div className="det-row"><span className="dk">{t('bg.notes')}</span><span className="dv">{q.notes}</span></div>}
        </div></div>
      </div>

      {/* items */}
      <div className="card">
        <div className="card-head"><h3>{t('hd.items')}</h3>{q.discountPct > 0 && <span className="badge badge-warning">{lang === 'vi' ? 'Chiết khấu' : 'Discount'} {q.discountPct}%</span>}</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>
              {q.items.map((it, i) => { const line = it.qty * it.price; const v = line * (1 - tot.discPct / 100) * 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 * (1 - tot.discPct / 100) + 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>
              {tot.discount > 0 && <tr style={{ cursor:'default' }}><td colSpan="5"></td><td className="num muted">{t('bg.discount')} ({tot.discPct}%)</td><td colSpan="2" className="num mono" style={{ color:'var(--error)' }}>- {vnd(tot.discount)}</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>
      <div style={{ marginTop:14 }}><FileManager scope="quotes" code={q.code} /></div>

      {/* Lịch sử theo dõi */}
      <div className="card" style={{ marginTop:14 }}>
        <div className="card-head"><h3><i className="bi bi-clock-history" style={{ marginRight:6, color:'var(--accent)' }}></i>{lang === 'vi' ? 'Lịch sử theo dõi' : 'Activity log'}</h3><span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{(q.history || []).length}</span></div>
        <div className="card-body">
          <div className="flex g8" style={{ marginBottom:14 }}>
            <input className="input" value={note} onChange={e => setNote(e.target.value)} placeholder={lang === 'vi' ? 'Thêm ghi chú theo dõi (gọi điện, hẹn phản hồi…)' : 'Add a follow-up note…'} onKeyDown={e => { if (e.key === 'Enter') addNote(); }} />
            <button className="btn btn-primary" onClick={addNote}><i className="bi bi-plus-lg"></i>{lang === 'vi' ? 'Thêm' : 'Add'}</button>
          </div>
          {(q.history || []).length ? [...q.history].reverse().map((h, i) => {
            const ic = { create:'bi-plus-circle', send:'bi-send', status:'bi-arrow-right-circle', convert:'bi-file-earmark-check', edit:'bi-pencil', note:'bi-chat-left-text' }[h.type] || 'bi-dot';
            return (
              <div key={i} className="flex g12" style={{ paddingBottom:14 }}>
                <div style={{ display:'flex', flexDirection:'column', alignItems:'center', flex:'none' }}>
                  <div style={{ width:26, height:26, borderRadius:'50%', display:'grid', placeItems:'center', background:'var(--primary-light)', color:'var(--primary-ink)', flex:'none' }}><i className={'bi ' + ic} style={{ fontSize:12 }}></i></div>
                  {i < q.history.length - 1 && <div style={{ width:2, flex:1, background:'var(--border)', marginTop:2 }}></div>}
                </div>
                <div style={{ flex:1, paddingTop:2 }}>
                  <div className="flex g8 items-center" style={{ marginBottom:2 }}><span style={{ fontWeight:600, fontSize:'var(--fs-base)' }}>{h.by || '—'}</span><span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(h.date)}</span></div>
                  <div className="subtle" style={{ fontSize:'var(--fs-base)', lineHeight:1.5 }}>{h.note}</div>
                </div>
              </div>
            );
          }) : <EmptyState icon="bi-clock-history" text={lang === 'vi' ? 'Chưa có hoạt động theo dõi' : 'No activity yet'} />}
        </div>
      </div>

      {preview && <QuoteDocModal quote={q} onClose={() => setPreview(false)} />}
      {payReq && <PaymentRequestModal contract={asContract} inst={payReq} onClose={() => setPayReq(null)} />}
      {acceptOpen && <AcceptanceModal contract={asContract} onClose={() => setAcceptOpen(false)} />}
    </div>
  );
}

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

  // Số liệu 1 chiều — dùng chung cho cả 2 cột (nhãn trạng thái/đối tác theo chiều).
  const sideStats = (dir) => {
    const all = p.quotes.filter(q => quoteDir(q) === dir && inPeriod(q.issueDate, period));
    const stLbl = (s) => quoteStatusLabel({ direction:dir }, s, lang);
    const won = all.filter(q => ['accepted', 'converted', 'won'].includes(quoteStatus(q)));
    const decided = all.filter(q => ['accepted', 'converted', 'won', 'rejected', 'expired'].includes(quoteStatus(q)));
    const byStatus = {}; all.forEach(q => { const s = quoteStatus(q); byStatus[s] = (byStatus[s] || 0) + 1; });
    const byParty = {}; all.forEach(q => { byParty[q.customer] = (byParty[q.customer] || 0) + quoteTotals(q).total; });
    return {
      all, stLbl,
      totalValue: all.reduce((s, q) => s + quoteTotals(q).total, 0),
      wonCount: won.length,
      wonValue: won.reduce((s, q) => s + quoteTotals(q).total, 0),
      winRate: decided.length ? Math.round(won.length / decided.length * 100) : 0,
      valByMonth: tallyByMonth(all, q => q.issueDate, q => quoteTotals(q).total),
      statusSlices: Object.entries(byStatus).sort((a, b) => b[1] - a[1]).map(([s, n], i) => ({ label:stLbl(s), value:n, color:REPORT_COLORS[i % REPORT_COLORS.length] })),
      rankRows: Object.entries(byParty).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([cc, v]) => ({ label:((dir === 'buy' ? (window.VendorStore && VendorStore.byCode(cc)) : CustomerStore.byCode(cc)) || {}).shortName || cc, value:v })),
    };
  };
  const buy = sideStats('buy');
  const sell = sideStats('sell');

  const excelSheet = (S, dir) => ({
    name: dir === 'buy' ? (lang === 'vi' ? 'Báo giá mua' : 'Purchase quotes') : (lang === 'vi' ? 'Báo giá bán' : 'Sales quotes'),
    header:[lang === 'vi' ? 'Mã BG' : 'Code', dir === 'buy' ? (lang === 'vi' ? 'Nhà cung cấp' : 'Vendor') : (lang === 'vi' ? 'Khách hàng' : 'Customer'), lang === 'vi' ? 'Ngày phát hành' : 'Issued', lang === 'vi' ? 'Trạng thái' : 'Status', lang === 'vi' ? 'Giá trị (VND)' : 'Value (VND)'],
    widths:[16, 30, 14, 16, 16],
    rows:S.all.map(q => { const cu = bgParty(q); return [q.code, cu.name || cu.shortName || q.customer, q.issueDate, S.stLbl(quoteStatus(q)), quoteTotals(q).total]; }),
  });
  const excel = () => exportReportXLSX([excelSheet(buy, 'buy'), excelSheet(sell, 'sell')], 'bao-cao-bao-gia_' + period.from + '_' + period.to, toast, lang);

  // 1 cột báo cáo (đồng cấu trúc 2 bên) — 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' ? 'Báo giá mua (đầu vào NCC)' : 'Purchase quotes') : (lang === 'vi' ? 'Báo giá bán (đầu ra KH)' : 'Sales quotes')}</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ố báo giá' : 'Quotes', S.all.length)}
            {stat(t('bg.value'), vndShort(S.totalValue))}
            {stat(isBuy ? (lang === 'vi' ? 'Đã duyệt mua' : 'Approved') : (lang === 'vi' ? 'Giá trị đã chốt' : 'Won value'), vndShort(S.wonValue))}
            {stat(isBuy ? (lang === 'vi' ? 'Tỷ lệ duyệt' : 'Approve rate') : t('bg.acceptRate'), S.winRate + '%')}
          </div>
        </div>
        {S.all.length === 0 ? (
          <div className="card"><div className="card-body"><EmptyState icon="bi-calendar-x" text={lang === 'vi' ? 'Không có báo giá nào trong kỳ này' : 'No quotes in this period'} /></div></div>
        ) : (<>
          <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Giá trị theo tháng' : 'Value by month'}</h3></div><div className="card-body"><ReportBars months={months} series={S.valByMonth} money color={isBuy ? '#12C5C0' : '#D14343'} /></div></div>
          <div className="card"><div className="card-head"><h3>{lang === 'vi' ? 'Cơ cấu theo trạng thái' : 'By status'}</h3></div><div className="card-body"><ReportDonut slices={S.statusSlices} /></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' ? 'Mã BG' : 'Code'}</th><th>{isBuy ? t('hd.vendor') : t('ct.customer')}</th><th>{lang === 'vi' ? 'Ngày PH' : 'Issued'}</th><th>{lang === 'vi' ? 'Trạng thái' : 'Status'}</th><th className="num">{t('bg.value')}</th></tr></thead>
              <tbody>{S.all.slice().sort((a, b) => String(b.issueDate).localeCompare(String(a.issueDate))).map(q => { const cu = bgParty(q); return (
                <tr key={q.code} style={{ cursor:'pointer' }} onClick={() => p.navigate({ name:'module', module:'quotes', sub:'detail', id:q.code })}><td><span className="mono" style={{ fontWeight:600, color }}>{q.code}</span></td><td className="truncate" style={{ maxWidth:160 }}>{cu.shortName || cu.name || q.customer}</td><td className="mono">{fmtDate(q.issueDate)}</td><td className="muted">{S.stLbl(quoteStatus(q))}</td><td className="num mono" style={{ fontWeight:600 }}>{vnd(quoteTotals(q).total)}</td></tr>
              ); })}</tbody></table>
          </div></div>
        </>)}
      </div>
    );
  };

  return (
    <ReportShell title={t('c.report') + ' · ' + t('bg.title')} subtitle={lang === 'vi' ? 'Hai luồng trên cùng một kỳ: Mua (trái) · Bán (phải).' : '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>
  );
}

function BgSettings(){
  const p = usePortal();
  const { t, lang } = useLang();
  const toast = useToast();
  const s = p.appSettings || {};
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [f, setF] = React.useState({
    fmt:s.quoteNoFormat || '', days:s.quoteValidDays || '30', disc:s.quoteDiscount || '5',
    title:s.quoteDocTitle || 'BẢNG BÁO GIÁ', subtitle:(s.quoteDocSubtitle != null ? s.quoteDocSubtitle : 'QUOTATION'), terms:(s.quoteTerms != null ? s.quoteTerms : DEFAULT_QUOTE_TERMS),
    pay:(s.quotePaymentMethod != null ? s.quotePaymentMethod : DEFAULT_QUOTE_PAYMENT), footer:s.quoteFooterNote || '',
    askScan:(s.quoteConfirmAskScan === '1' || s.quoteConfirmAskScan === true),
  });
  const set = (k, v) => setF(x => ({ ...x, [k]:v }));
  const fmtPreview = formatContractNo((f.fmt || '').trim() || quoteNoFormat(), { date:TODAY, kh:'FPT', seq:1 });
  const save = () => {
    p.setSetting('quoteNoFormat', (f.fmt || '').trim());
    p.setSetting('quoteValidDays', String(f.days || 30));
    p.setSetting('quoteDiscount', String(f.disc || 0));
    p.setSetting('quoteDocTitle', (f.title || '').trim() || 'BẢNG BÁO GIÁ');
    p.setSetting('quoteDocSubtitle', (f.subtitle || '').trim());
    p.setSetting('quoteTerms', f.terms || '');
    p.setSetting('quotePaymentMethod', f.pay || '');
    p.setSetting('quoteFooterNote', (f.footer || '').trim());
    p.setSetting('quoteConfirmAskScan', f.askScan ? '1' : '0');
    toast(L('Đã lưu cài đặt báo giá', 'Quote settings saved'), 'success');
  };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{t('c.settings')}</h1><div className="sub">{L('Định dạng mã, mặc định & mẫu tài liệu báo giá.', 'Code format, defaults & quote document template.')}</div></div>
        <button className="btn btn-primary" onClick={save}><i className="bi bi-save"></i>{t('c.save')}</button></div>
      <div className="settings-grid">
        <div className="card">
          <div className="card-head"><h3>{L('Định dạng mã báo giá', 'Quote code format')}</h3></div>
          <div className="card-body col g12">
            <Field label={L('Định dạng', 'Format')} hint={L('Token: DD MM YY YYYY · {KH} = viết tắt KH · {SEQ} = số thứ tự', 'Tokens: DD MM YY YYYY · {KH} · {SEQ}')}>
              <input className="input mono" value={f.fmt} onChange={e => set('fmt', e.target.value)} placeholder={DEFAULT_QUOTE_NO_FORMAT} /></Field>
            <div className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Xem trước', 'Preview')}: <span className="mono" style={{ color:'var(--primary-ink)', fontWeight:600 }}>{fmtPreview}</span></div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>{L('Mặc định báo giá', 'Quote defaults')}</h3></div>
          <div className="card-body col g12">
            <div className="flex g12 wrap items-start">
              <div style={{ flex:1 }}><Field label={L('Số ngày hiệu lực', 'Validity (days)')}><input type="number" className="input" value={f.days} onChange={e => set('days', e.target.value)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Chiết khấu gợi ý (%)', 'Suggested discount (%)')}><input type="number" className="input" value={f.disc} onChange={e => set('disc', e.target.value)} /></Field></div>
            </div>
            <Field label={L('Xác nhận báo giá online', 'Online confirmation')} hint={L('Mọi báo giá gửi đi đều có nút "Xác nhận báo giá" trên trang xem online (khách nhập họ tên · SĐT · ghi chú).', 'Every sent quote shows a confirm button on its online page.')}>
              <label className="flex g8 items-center" style={{ height:'var(--control-h)', cursor:'pointer' }}>
                <input type="checkbox" checked={f.askScan} onChange={e => set('askScan', e.target.checked)} />{L('Nhắc khách gửi kèm bản scan báo giá đã ký/đóng dấu (qua email)', 'Ask customer to email a signed/stamped scan')}
              </label>
            </Field>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>{L('Hình thức thanh toán (báo giá)', 'Payment method')}</h3><button className="btn btn-primary" onClick={save}>{t('c.save')}</button></div>
          <div className="card-body col g12"><Field label={L('Hình thức thanh toán', 'Payment method')} hint={L('Hiện ở khối "Hình thức thanh toán" trên báo giá', 'Shown on the quote')}><textarea className="textarea" rows={2} value={f.pay} onChange={e => set('pay', e.target.value)} placeholder={DEFAULT_QUOTE_PAYMENT} /></Field></div>
        </div>
      </div>
      <div style={{ marginTop:14 }}>
        <DocTemplateEditor label={L('Mẫu tài liệu báo giá', 'Quote template')} type="quote" codes={DOC_TPL_CODES} hasTerms={true} showIntro={false} showSigns={false}
          legacy={{ title:f.title, subtitle:f.subtitle, terms:f.terms, footer:f.footer }}
          previewHtml={(liveS) => { const prev = window.AppSettings; window.AppSettings = liveS; try { return quoteHTML({ code:'BG-2026-MAU', issueDate:TODAY, validUntil:TODAY, validDays:30, notes:'', items:[{ name:'Hạng mục mẫu', unit:'gói', qty:1, price:10000000, vat:8 }] }, { name:'CÔNG TY KHÁCH HÀNG (mẫu)', mst:'0300000000', address:'123 Đường mẫu, TP.HCM', shortName:'KH' }, { name:'Người liên hệ', title:'Chức vụ' }, p.branding); } finally { window.AppSettings = prev; } }}
          fileName="BaoGia-Mau" /></div>
    </div>
  );
}

/* Dashboard widget */
function QuoteWidget(){
  const p = usePortal();
  const { t, lang } = useLang();
  const L = (vi, en) => lang === 'vi' ? vi : en;
  const qs = p.quotes.filter(q => quoteDir(q) === 'sell');   // widget = pipeline bán
  const awaiting = qs.filter(q => quoteStatus(q) === 'sent');
  const won = qs.filter(quoteWon);
  const decided = qs.filter(q => ['accepted', 'converted', 'won', 'rejected', 'expired'].includes(quoteStatus(q)));
  const winRate = decided.length ? Math.round(won.length / decided.length * 100) : 0;
  const wonValue = won.reduce((s, q) => s + quoteTotals(q).total, 0);
  const totalValue = qs.reduce((s, q) => s + quoteTotals(q).total, 0);
  const recent = [...qs].sort((a, b) => String(b.issueDate || '').localeCompare(String(a.issueDate || ''))).slice(0, 4);
  return (
    <WidgetShell wid="quotes" icon="bi-receipt" iconColor="#D14343" title={t('bg.title')}
      onViewAll={() => p.openApp(BG_APP)} onNew={() => p.quick('quotes')} newLabel={t('bg.new')}>
        <div className="flex g16" style={{ marginBottom:10 }}>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{qs.length}</div><div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('báo giá', 'quotes')}</div></div>
          <div><div className="k-val" style={{ fontSize:'var(--fs-2xl)', fontWeight:700 }}>{vndShort(totalValue)}</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-trophy" style={{ color:'var(--success)', marginRight:4 }}></i>{L('Đã chốt', 'Won')} · {won.length}</div>
            <div className="mono truncate" style={{ fontWeight:600, color:'var(--success)' }}>{vndShort(wonValue)}</div>
          </div>
          <div style={{ background:'var(--bg-subtle)', borderRadius:'var(--r-md)', padding:'6px 10px', flex:'0 0 auto', textAlign:'center', minWidth:78 }}>
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{t('bg.acceptRate')}</div>
            <div style={{ fontWeight:700, color:'var(--primary)' }}>{winRate}%</div>
          </div>
        </div>
        {awaiting.length > 0 && <div className="flex g8 items-center" style={{ marginBottom:8 }}><span className="badge badge-info"><i className="bi bi-send"></i>{awaiting.length} {t('bg.awaitingReply').toLowerCase()}</span></div>}
        {recent.map(q => { const cu = CustomerStore.byCode(q.customer) || {}; return (
          <div key={q.code} className="list-row" style={{ cursor:'pointer', alignItems:'flex-start' }} onClick={() => p.navigate({ name:'module', module:'quotes', sub:'detail', id:q.code })}>
            <div className="grow" style={{ minWidth:0 }}>
              <div className="mono truncate" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)' }}>{q.code}</div>
              <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}>{cu.shortName || cu.name || q.customer}</div>
            </div>
            <div style={{ textAlign:'right', flexShrink:0, marginLeft:8 }}>
              <div className="mono" style={{ fontWeight:600, fontSize:'var(--fs-sm)' }}>{vndShort(quoteTotals(q).total)}</div>
              <BgStatusBadge q={q} />
            </div>
          </div>
        ); })}
    </WidgetShell>
  );
}

Object.assign(window, { QuoteModule, QuoteWidget, genQuoteCode, BgStatusBadge });
