/* ============ Scan / Upload + OCR review ============ */
// แปลงข้อความที่ OCR อ่านได้ → เดาฟิลด์ (เลขภาษี/ยอดเงิน/ชื่อร้าน) ผู้ใช้ตรวจ/แก้ได้
function parseReceipt(text) {
  const out = {};
  if (!text) return out;
  const clean = text.replace(/[ \t]+/g, ' ');
  // เลขผู้เสียภาษี 13 หลัก (อาจมีเว้นวรรค/ขีด)
  const taxM = clean.replace(/[ \-.]/g, '').match(/\d{13}/);
  if (taxM) out.vendorTax = taxM[0];
  // ยอดเงิน (มีจุดทศนิยม 2 ตำแหน่ง)
  const nums = (clean.match(/\d{1,3}(?:,\d{3})*\.\d{2}|\d+\.\d{2}/g) || [])
    .map((s) => parseFloat(s.replace(/,/g, ''))).filter((n) => n > 0);
  if (nums.length) {
    const total = Math.max(...nums);
    out.total = total.toFixed(2);
    const vatGuess = total * 7 / 107;
    const vat = nums.find((n) => Math.abs(n - vatGuess) < Math.max(1, vatGuess * 0.06));
    if (vat) { out.vat = vat.toFixed(2); out.pre = (total - vat).toFixed(2); }
    else { out.pre = total.toFixed(2); out.vat = '0.00'; }
  }
  // ชื่อร้าน: บรรทัดแรกๆ ที่เป็นตัวอักษร (ไม่ขึ้นต้นด้วยตัวเลข)
  const lines = text.split('\n').map((l) => l.replace(/^[#>*\-\s|]+/, '').replace(/\|/g, ' ').trim()).filter(Boolean);
  const vline = lines.find((l) => l.length >= 4 && !/^\d/.test(l) && /[ก-๙A-Za-z]/.test(l) && !/ใบ(กำกับ|เสร็จ|เสนอ|ส่งของ)|tax\s*invoice|receipt/i.test(l));
  if (vline) out.vendor = vline.slice(0, 80);
  return out;
}

// โหลด pdf.js worker เป็น blob (same-origin) — กัน worker cross-origin ค้าง
let _pdfWorkerSrc = null;
async function ensurePdfWorker() {
  const WURL = 'https://unpkg.com/pdfjs-dist@3.11.174/build/pdf.worker.min.js';
  if (!_pdfWorkerSrc) {
    try { const resp = await fetch(WURL); _pdfWorkerSrc = URL.createObjectURL(await resp.blob()); }
    catch (e) { _pdfWorkerSrc = WURL; }
  }
  pdfjsLib.GlobalWorkerOptions.workerSrc = _pdfWorkerSrc;
}

// แปลงหน้าแรกของ PDF → รูป (data URL) เพื่อส่งให้ OCR — มี timeout กันค้าง
async function pdfFirstPageDataUrl(url) {
  try {
    if (!window.pdfjsLib) return null;
    await ensurePdfWorker();
    const render = (async () => {
      const pdf = await pdfjsLib.getDocument(url).promise;
      const page = await pdf.getPage(1);
      const base = page.getViewport({ scale: 1 });
      const scale = Math.min(2, 1300 / base.width);
      const viewport = page.getViewport({ scale });
      const c = document.createElement('canvas');
      c.width = Math.round(viewport.width); c.height = Math.round(viewport.height);
      await page.render({ canvasContext: c.getContext('2d'), viewport }).promise;
      return c.toDataURL('image/jpeg', 0.9);
    })();
    const timeout = new Promise((res) => setTimeout(() => res(null), 15000));
    return await Promise.race([render, timeout]);
  } catch (e) { return null; }
}

// โหลดรูปแล้วย่อขนาด → data URL (jpeg) สำหรับส่งให้ OCR ฝั่งเซิร์ฟเวอร์
function imgToDataUrl(url, maxW) {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const scale = Math.min(1, (maxW || 1600) / img.width);
      const w = Math.round(img.width * scale), h = Math.round(img.height * scale);
      const c = document.createElement('canvas'); c.width = w; c.height = h;
      c.getContext('2d').drawImage(img, 0, 0, w, h);
      try { resolve(c.toDataURL('image/jpeg', 0.85)); } catch (e) { resolve(null); }
    };
    img.onerror = () => resolve(null);
    img.src = url;
  });
}

function ScanPage({ role, onNav, toast, period }) {
  const readOnly = role === 'viewer';
  const [step, setStep] = useState('upload'); // upload | processing | review
  const [progress, setProgress] = useState(0);
  const [drafts, setDrafts] = useState([]); // one editable draft per uploaded file
  const [selIdx, setSelIdx] = useState(0);
  const [saving, setSaving] = useState(false);
  const [vSug, setVSug] = useState(false); // vendor autocomplete dropdown
  const [ocrMsg, setOcrMsg] = useState('');
  const fileRef = useRef(null);

  const conf = { docDate: 0.98, docNo: 0.94, vendor: 0.88, vendorTax: 0.99, pre: 0.96, vat: 0.91, total: 0.99 };
  const today = new Date().toISOString().slice(0, 10);
  // วันที่ตั้งต้นของเอกสาร = ตามเดือนที่กำลังดูอยู่ (กันเอกสารไปโผล่ผิดเดือน)
  const p = period || CURRENT_PERIOD;
  const isCurrentP = p.m === CURRENT_PERIOD.m && p.y === CURRENT_PERIOD.y;
  const defaultISO = (p.y - 543) + '-' + String(p.m + 1).padStart(2, '0') + '-' + String(isCurrentP ? new Date().getDate() : 1).padStart(2, '0');

  const makeDraft = (file, i) => ({
    fileName: file ? file.name : 'เอกสาร ' + (i + 1),
    sizeKB: file ? Math.round(file.size / 1024) : 0,
    url: file ? URL.createObjectURL(file) : null,
    isImage: !!(file && file.type && file.type.startsWith('image/')),
    type: 'full', docDate: defaultISO, docNo: file ? file.name.replace(/\.[^.]+$/, '') : '',
    vendor: '', vendorTax: '', branch: 'สำนักงานใหญ่', cat: 'material',
    pre: '', vat: '', total: '', creditable: true, deductible: true, note: '',
  });

  // ค้นหาร้านค้าจากที่เคยบันทึก (ช่วยกรอกชื่อ + แก้เองได้)
  const vendorBook = React.useMemo(() => {
    const m = new Map();
    (D.docs || []).forEach((x) => { if (x.vendor && !m.has(x.vendor)) m.set(x.vendor, { vendor: x.vendor, vendorTax: x.vendorTax, cat: x.cat, type: x.type }); });
    return [...m.values()];
  }, []);
  const pickVendor = (c) => { setDrafts((ds) => ds.map((dd, i) => i === selIdx ? { ...dd, vendor: c.vendor, vendorTax: c.vendorTax && c.vendorTax !== '—' ? c.vendorTax : dd.vendorTax, cat: c.cat || dd.cat, type: c.type || dd.type } : dd)); setVSug(false); };

  const runOcr = async (list) => {
    const total = list.length;
    let serverOn = true;   // Typhoon พร้อมใช้ไหม
    let usedTyphoon = false;
    let completed = 0;
    setOcrMsg('กำลังอ่าน… (0/' + total + ')');

    const processOne = async (i) => {
      const dr = list[i];
      if (dr.url) {
        // แปลงเป็นรูป data URL (PDF → render หน้าแรก, รูป → ย่อขนาด)
        let imgUrl = dr.isImage ? await imgToDataUrl(dr.url, 1300) : await pdfFirstPageDataUrl(dr.url);
        if (imgUrl) setDrafts((ds) => ds.map((x, idx) => (idx === i ? { ...x, imgData: imgUrl, previewImg: x.isImage ? x.previewImg : imgUrl } : x)));
        let parsed = null;
        if (imgUrl) {
          if (serverOn) {
            try {
              const res = await API.ocr(imgUrl);
              if (res.ok && res.data.ok && res.data.text) { parsed = parseReceipt(res.data.text); usedTyphoon = true; }
              else if (res.data && res.data.code === 'noconfig') { serverOn = false; }
            } catch (e) {}
          }
          // Tesseract สำรอง (เฉพาะตอน Typhoon ใช้ไม่ได้ — ช้ากว่า)
          if (!parsed && !serverOn && window.Tesseract) {
            try { const { data } = await Tesseract.recognize(imgUrl, 'tha+eng'); parsed = parseReceipt(data.text); } catch (e) {}
          }
        }
        if (parsed) setDrafts((ds) => ds.map((x, idx) => (idx === i ? { ...x, ...parsed } : x)));
      }
      completed++; setProgress(Math.round((completed / total) * 100)); setOcrMsg('กำลังอ่าน… (' + completed + '/' + total + ')');
    };

    // อ่านพร้อมกันทีละ 3 ใบ (เร็วขึ้นมากเวลามีหลายไฟล์)
    const CONC = 3;
    let next = 0;
    const lanes = Array.from({ length: Math.min(CONC, total) }, async () => {
      while (next < total) { const my = next++; await processOne(my); }
    });
    await Promise.all(lanes);

    setOcrMsg(''); setStep('review');
    if (usedTyphoon) toast('อ่านด้วย Typhoon OCR เรียบร้อย');
  };

  const onFiles = (fileList) => {
    const arr = Array.from(fileList || []);
    if (!arr.length) return;
    const list = arr.map(makeDraft);
    setDrafts(list); setSelIdx(0);
    setStep('processing'); setProgress(0);
    runOcr(list);
  };

  const setDraft = (k, v) => setDrafts((ds) => ds.map((d, i) => (i === selIdx ? { ...d, [k]: v } : d)));
  const recalcVat = (pre) => { const v = (parseFloat(pre || 0) * (D.company.vatRate / 100)); setDrafts((ds) => ds.map((d, i) => i === selIdx ? { ...d, pre, vat: v.toFixed(2), total: (parseFloat(pre || 0) + v).toFixed(2) } : d)); };
  const recalcTotal = (vat) => setDrafts((ds) => ds.map((d, i) => i === selIdx ? { ...d, vat, total: (parseFloat(d.pre || 0) + parseFloat(vat || 0)).toFixed(2) } : d));
  const removeDraft = (idx) => { setDrafts((ds) => ds.filter((_, i) => i !== idx)); setSelIdx((s) => (idx <= s && s > 0 ? s - 1 : s)); };
  const draftReady = (d) => d.vendor.trim() && parseFloat(d.pre) > 0;
  const readyCount = drafts.filter(draftReady).length;

  const thDate = (iso) => { const dt = new Date(iso); return isNaN(dt) ? '' : dt.getDate() + ' ' + D.thMonthsShort[dt.getMonth()] + ' ' + String((dt.getFullYear() + 543) % 100).padStart(2, '0'); };
  const saveAll = async () => {
    const ready = drafts.filter(draftReady);
    if (!ready.length) { toast('กรอกชื่อร้านค้าและยอดก่อน VAT อย่างน้อย 1 ใบก่อนบันทึก', 'warn'); return; }
    setSaving(true);
    let ok = 0;
    for (const d of ready) {
      const pre = +d.pre || 0, vat = +d.vat || 0;
      const rec = { docNo: d.docNo.trim() || '—', docDateISO: d.docDate, docDate: thDate(d.docDate), uploadDate: thDate(today), vendor: d.vendor.trim(), vendorTax: d.vendorTax.trim() || '—', branch: d.branch.trim(), cat: d.cat, type: d.type, pre, vat, total: +d.total || (pre + vat), creditable: d.creditable, deductible: d.deductible, status: 'pending', note: d.note.trim(), file: !!d.imgData, fileData: d.imgData || null };
      const res = await API.addDoc(rec);
      if (res.ok && res.data.ok) { D.docs.push(res.data.record || rec); ok++; }
    }
    setSaving(false);
    toast('บันทึกเข้าทะเบียน ' + ok + ' ใบเรียบร้อย');
    onNav('docs');
  };

  const d = drafts[selIdx] || makeDraft(null, 0);

  if (readOnly) return <Empty icon="lock" title="คุณไม่มีสิทธิ์อัปโหลดเอกสาร" sub="บัญชีผู้ดูอย่างเดียว (Viewer) สามารถดูข้อมูลได้เท่านั้น" />;

  return (
    <>
      <div className="page-head">
        <div><div className="pt">สแกน / อัปโหลดเอกสาร</div><div className="ps">อัปโหลดใบกำกับภาษี ระบบจะอ่านข้อมูลให้อัตโนมัติ — ตรวจและแก้ไขก่อนบันทึก</div></div>
      </div>

      {/* step indicator */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20, flexWrap: 'wrap' }}>
        {[['upload', 'อัปโหลด', 'upload'], ['processing', 'อ่านข้อมูล (OCR)', 'scan'], ['review', 'ตรวจ & บันทึก', 'checkCircle']].map(([id, lbl, ic], i) => {
          const order = ['upload', 'processing', 'review'];
          const cur = order.indexOf(step), mine = order.indexOf(id);
          const state = mine < cur ? 'done' : mine === cur ? 'active' : 'todo';
          return (
            <React.Fragment key={id}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <div style={{ width: 30, height: 30, borderRadius: '50%', display: 'grid', placeItems: 'center', background: state === 'todo' ? 'var(--surface-2)' : 'var(--primary)', color: state === 'todo' ? 'var(--ink-3)' : '#fff', border: state === 'active' ? '3px solid var(--primary-tint2)' : 'none' }}>
                  {state === 'done' ? <Icon name="check" size={15} /> : <Icon name={ic} size={15} />}
                </div>
                <span style={{ fontSize: 13.5, fontWeight: 600, color: state === 'todo' ? 'var(--ink-3)' : 'var(--ink)' }}>{lbl}</span>
              </div>
              {i < 2 && <div style={{ flex: '0 1 50px', height: 2, background: 'var(--line)' }}></div>}
            </React.Fragment>
          );
        })}
      </div>

      {step === 'upload' && (
        <div className="card card-pad">
          <input ref={fileRef} type="file" accept="image/*,application/pdf" multiple style={{ display: 'none' }} onChange={(e) => onFiles(e.target.files)} />
          <label style={{ display: 'block', border: '2px dashed var(--line)', borderRadius: 14, padding: '46px 20px', textAlign: 'center', cursor: 'pointer', transition: '.15s', background: 'var(--surface-2)' }}
            onDragOver={(e) => { e.preventDefault(); e.currentTarget.style.borderColor = 'var(--primary)'; e.currentTarget.style.background = 'var(--primary-tint)'; }}
            onDragLeave={(e) => { e.currentTarget.style.borderColor = 'var(--line)'; e.currentTarget.style.background = 'var(--surface-2)'; }}
            onDrop={(e) => { e.preventDefault(); e.currentTarget.style.borderColor = 'var(--line)'; e.currentTarget.style.background = 'var(--surface-2)'; onFiles(e.dataTransfer.files); }}
            onClick={(e) => { e.preventDefault(); fileRef.current && fileRef.current.click(); }}>
            <div style={{ width: 60, height: 60, borderRadius: 16, background: 'var(--primary-tint)', color: 'var(--primary)', display: 'grid', placeItems: 'center', margin: '0 auto 16px' }}><Icon name="upload" size={28} /></div>
            <div style={{ fontWeight: 700, fontSize: 17 }}>ลากไฟล์มาวาง หรือ คลิกเพื่อเลือกจากเครื่อง</div>
            <div style={{ color: 'var(--ink-3)', fontSize: 13.5, marginTop: 6 }}>รองรับ JPG, PNG, PDF · สูงสุด 10 MB ต่อไฟล์ · อัปโหลดได้หลายไฟล์พร้อมกัน</div>
            <div style={{ marginTop: 18, display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
              <span className="btn btn-primary"><Icon name="folder" className="ic" />เลือกไฟล์จากคอมพิวเตอร์</span>
              <span className="btn btn-ghost"><Icon name="scan" className="ic" />ถ่ายรูป / สแกน</span>
            </div>
          </label>
          <div className="callout info" style={{ marginTop: 16 }}><Icon name="sparkle" className="ic" /><div>อ่านบิลด้วย OCR ภาษาไทยในเครื่องคุณเอง (ฟรี ไม่ต้องตั้งค่า) — เลือก<b>รูปบิล/ใบเสร็จ (JPG/PNG) หรือไฟล์ PDF</b> ระบบจะอ่าน เลขผู้เสียภาษี · ยอดเงิน · VAT · ชื่อร้าน มาให้ แล้วตรวจ/แก้ก่อนบันทึก · ครั้งแรกอาจช้าสักครู่</div></div>
        </div>
      )}

      {step === 'processing' && (
        <div className="card card-pad" style={{ textAlign: 'center', padding: '54px 20px' }}>
          <div style={{ width: 64, height: 64, borderRadius: 18, background: 'var(--primary-tint)', color: 'var(--primary)', display: 'grid', placeItems: 'center', margin: '0 auto 18px', animation: 'pulse 1.2s infinite' }}><Icon name="scan" size={30} /></div>
          <div style={{ fontWeight: 700, fontSize: 17 }}>กำลังอ่านข้อมูลจากเอกสาร…</div>
          <div style={{ color: 'var(--ink-3)', fontSize: 13.5, marginTop: 5 }}>{ocrMsg || 'ตรวจจับเลขผู้เสียภาษี · ยอดเงิน · VAT · ชื่อร้าน'}</div>
          <div className="bar-track" style={{ maxWidth: 380, margin: '20px auto 0' }}><div className="bar-fill" style={{ width: progress + '%', transition: 'width .1s' }}></div></div>
          <style>{`@keyframes pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.08);opacity:.7}}`}</style>
        </div>
      )}

      {step === 'review' && (
        <>
          {/* file strip — เลือกตรวจทีละใบ (อ่านหลายไฟล์พร้อมกัน) */}
          {drafts.length > 1 && (
            <div className="card card-pad" style={{ marginBottom: 14 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
                <h3 style={{ fontSize: 14, fontWeight: 700 }}>ไฟล์ที่อ่าน {drafts.length} ใบ</h3>
                <span className="sub" style={{ color: 'var(--ink-3)', fontSize: 12 }}>กดเลือกแต่ละใบเพื่อตรวจ/แก้</span>
                <span className="badge ok" style={{ marginLeft: 'auto' }}>กรอกครบ {readyCount}/{drafts.length}</span>
              </div>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                {drafts.map((dr, i) => (
                  <button key={i} onClick={() => setSelIdx(i)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px', borderRadius: 9, border: '1px solid ' + (i === selIdx ? 'var(--primary)' : 'var(--line)'), background: i === selIdx ? 'var(--primary-tint)' : 'var(--surface)' }}>
                    <span style={{ width: 20, height: 20, borderRadius: '50%', display: 'grid', placeItems: 'center', background: draftReady(dr) ? 'var(--ok)' : 'var(--surface-2)', color: draftReady(dr) ? '#fff' : 'var(--ink-3)', fontSize: 11, fontWeight: 700, flexShrink: 0 }}>{draftReady(dr) ? <Icon name="check" size={12} /> : (i + 1)}</span>
                    <span style={{ fontSize: 12, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 130 }}>{dr.fileName}</span>
                    <span onClick={(e) => { e.stopPropagation(); removeDraft(i); }} className="x-btn" style={{ width: 20, height: 20 }}><Icon name="x" size={13} /></span>
                  </button>
                ))}
              </div>
            </div>
          )}

          <div className="grid" style={{ gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1.25fr)', alignItems: 'start' }}>
            {/* receipt preview */}
            <div className="card" style={{ position: 'sticky', top: 78 }}>
              <div className="card-head"><h3>ต้นฉบับเอกสาร{drafts.length > 1 ? ` (${selIdx + 1}/${drafts.length})` : ''}</h3><span className="badge ok" style={{ marginLeft: 'auto' }}><Icon name="check" size={12} />อ่านสำเร็จ</span></div>
              <div style={{ padding: 16 }}>
                <div style={{ aspectRatio: '3/4', maxHeight: 440, background: 'repeating-linear-gradient(135deg, var(--surface-2), var(--surface-2) 11px, oklch(0.94 0.005 165) 11px, oklch(0.94 0.005 165) 22px)', borderRadius: 10, border: '1px solid var(--line)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, color: 'var(--ink-3)', position: 'relative', overflow: 'hidden' }}>
                  {(d.isImage && d.url) || d.previewImg ? (
                    <img src={d.isImage ? d.url : d.previewImg} alt="receipt" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'contain', background: '#fff' }} />
                  ) : (
                    <>
                      <Icon name="receipt" size={44} />
                      <div className="mono" style={{ fontSize: 12, padding: '0 14px', textAlign: 'center', wordBreak: 'break-all' }}>{d.fileName}</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{d.url ? 'กด “ดูเต็มจอ” เพื่อเปิดไฟล์' : ''}</div>
                    </>
                  )}
                </div>
                <div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 8, textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.fileName} · {d.sizeKB} KB</div>
                <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
                  <button className="btn btn-ghost btn-sm" style={{ flex: 1 }} disabled={!d.url} onClick={() => d.url && window.open(d.url, '_blank')}><Icon name="eye" className="ic" />ดูเต็มจอ</button>
                  <button className="btn btn-ghost btn-sm" style={{ flex: 1 }} onClick={() => { setStep('upload'); setDrafts([]); }}><Icon name="refresh" className="ic" />เปลี่ยนไฟล์</button>
                </div>
              </div>
            </div>

            {/* extracted form (bound to selected draft) */}
            <div className="card">
              <div className="card-head"><div><h3>ข้อมูลที่อ่านได้</h3><div className="sub">ตรวจสอบและแก้ไขให้ถูกต้องก่อนบันทึก{drafts.length > 1 ? ' · ใบที่ ' + (selIdx + 1) : ''}</div></div></div>
              <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
                <Field label="ประเภทเอกสาร">
                  <select className="select" value={d.type} onChange={(e) => setDraft('type', e.target.value)}>
                    {Object.entries(D.docTypes).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
                  </select>
                </Field>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                  <Field label="วันที่เอกสาร" conf={conf.docDate}><ThaiDatePicker value={d.docDate} onChange={(v) => setDraft('docDate', v)} /></Field>
                  <Field label="เลขที่เอกสาร" conf={conf.docNo}><input className="input" value={d.docNo} onChange={(e) => setDraft('docNo', e.target.value)} /></Field>
                </div>
                <div style={{ position: 'relative' }}>
                  <Field label="ชื่อร้านค้า / ผู้ขาย" conf={conf.vendor}><input className="input" value={d.vendor} onFocus={() => setVSug(true)} onChange={(e) => { setDraft('vendor', e.target.value); setVSug(true); }} onBlur={() => setTimeout(() => setVSug(false), 160)} placeholder="กรอกชื่อร้าน หรือพิมพ์เพื่อค้นหา (จำเป็น)" /></Field>
                  {vSug && (() => {
                    const q = (d.vendor || '').trim().toLowerCase();
                    const list = (q ? vendorBook.filter((c) => c.vendor.toLowerCase().includes(q) || (c.vendorTax || '').toLowerCase().includes(q)) : vendorBook).slice(0, 6);
                    return list.length > 0 ? (
                      <div className="card" style={{ position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 6, marginTop: 4, boxShadow: 'var(--shadow-lg)', maxHeight: 230, overflowY: 'auto', padding: 4 }}>
                        <div style={{ fontSize: 11, color: 'var(--ink-3)', padding: '6px 10px 4px', display: 'flex', alignItems: 'center', gap: 6 }}><Icon name="search" size={12} />ร้านค้าที่เคยบันทึก — กดเพื่อเติม</div>
                        {list.map((c, i) => (
                          <button type="button" key={i} onMouseDown={(e) => { e.preventDefault(); pickVendor(c); }} style={{ display: 'flex', width: '100%', textAlign: 'left', padding: '8px 10px', borderRadius: 8 }}
                            onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-2)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                            <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontWeight: 600, fontSize: 13 }}>{c.vendor}</div><div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{c.vendorTax || '—'} · {D.catLabel(c.cat)}</div></div>
                          </button>
                        ))}
                      </div>
                    ) : null;
                  })()}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 12 }}>
                  <Field label="เลขประจำตัวผู้เสียภาษีผู้ขาย" conf={conf.vendorTax}><input className="input mono" value={d.vendorTax} onChange={(e) => setDraft('vendorTax', e.target.value)} /></Field>
                  <Field label="สาขา"><input className="input" value={d.branch} onChange={(e) => setDraft('branch', e.target.value)} /></Field>
                </div>
                <Field label="หมวดรายจ่าย">
                  <select className="select" value={d.cat} onChange={(e) => setDraft('cat', e.target.value)}>
                    {D.expCats.map((c) => <option key={c.id} value={c.id}>{c.label}</option>)}
                  </select>
                </Field>

                <div style={{ background: 'var(--surface-2)', borderRadius: 11, padding: 14, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
                  <Field label="ยอดก่อน VAT" conf={conf.pre}><input className="input right mono" value={d.pre} onChange={(e) => recalcVat(e.target.value)} placeholder="0.00" /></Field>
                  <Field label={'VAT ' + D.company.vatRate + '%'} conf={conf.vat}><input className="input right mono" value={d.vat} onChange={(e) => recalcTotal(e.target.value)} placeholder="0.00" /></Field>
                  <Field label="ยอดรวมสุทธิ" conf={conf.total}><input className="input right mono" value={d.total} onChange={(e) => setDraft('total', e.target.value)} style={{ fontWeight: 700 }} placeholder="0.00" /></Field>
                </div>

                <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                  <Toggle on={d.creditable} onClick={() => setDraft('creditable', !d.creditable)} label="ใช้เป็น VAT ซื้อ (เครดิตได้)" />
                  <Toggle on={d.deductible} onClick={() => setDraft('deductible', !d.deductible)} label="หักเป็นค่าใช้จ่ายได้" />
                </div>
                {!d.creditable && <div className="callout warn" style={{ padding: '9px 12px' }}><Icon name="alert" className="ic" />ไม่ใช่ใบกำกับภาษีเต็มรูป — บันทึกเป็นค่าใช้จ่ายได้ แต่จะไม่นำ VAT ไปเครดิต</div>}

                <Field label="หมายเหตุ"><textarea className="input" rows="2" value={d.note} onChange={(e) => setDraft('note', e.target.value)} placeholder="เช่น เลขที่โครงการ / รายละเอียดเพิ่มเติม"></textarea></Field>
              </div>
              <div className="modal-foot" style={{ borderRadius: '0 0 12px 12px', flexWrap: 'wrap' }}>
                {drafts.length > 1 && (
                  <div style={{ marginRight: 'auto', display: 'flex', gap: 6 }}>
                    <button className="btn btn-ghost btn-sm" disabled={selIdx === 0} onClick={() => setSelIdx((s) => Math.max(0, s - 1))}>‹ ใบก่อน</button>
                    <button className="btn btn-ghost btn-sm" disabled={selIdx === drafts.length - 1} onClick={() => setSelIdx((s) => Math.min(drafts.length - 1, s + 1))}>ใบถัดไป ›</button>
                  </div>
                )}
                <button className="btn btn-ghost" onClick={() => { setStep('upload'); setDrafts([]); }}>ยกเลิก</button>
                <button className="btn btn-primary" onClick={saveAll} disabled={saving || readyCount === 0}><Icon name="check" className="ic" />{saving ? 'กำลังบันทึก…' : (drafts.length > 1 ? `บันทึกทั้งหมด ${readyCount} ใบ` : 'บันทึกเข้าทะเบียน')}</button>
              </div>
            </div>
          </div>
        </>
      )}
    </>
  );
}

function Field({ label, conf, children }) {
  return (
    <div className="field">
      <label style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
        {label}
        {conf != null && <ConfBadge c={conf} />}
      </label>
      {children}
    </div>
  );
}
function ConfBadge({ c }) {
  const pct = Math.round(c * 100);
  const tone = c >= 0.95 ? 'ok' : c >= 0.88 ? 'warn' : 'danger';
  return <span className={'badge ' + tone} style={{ padding: '1px 7px', fontSize: 10.5 }}><Icon name="sparkle" size={10} />{pct}%</span>;
}
function Toggle({ on, onClick, label }) {
  return (
    <button onClick={onClick} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '8px 13px', borderRadius: 9, border: '1px solid ' + (on ? 'var(--primary)' : 'var(--line)'), background: on ? 'var(--primary-tint)' : 'var(--surface)', transition: '.13s' }}>
      <span style={{ width: 34, height: 20, borderRadius: 999, background: on ? 'var(--primary)' : 'var(--line)', position: 'relative', transition: '.15s', flexShrink: 0 }}>
        <span style={{ position: 'absolute', top: 2, left: on ? 16 : 2, width: 16, height: 16, borderRadius: '50%', background: '#fff', transition: '.15s', boxShadow: 'var(--shadow-sm)' }}></span>
      </span>
      <span style={{ fontSize: 13, fontWeight: 600, color: on ? 'var(--primary-dark)' : 'var(--ink-2)' }}>{label}</span>
    </button>
  );
}
window.ScanPage = ScanPage;
window.Field = Field; window.Toggle = Toggle; window.ConfBadge = ConfBadge;

/* ============ Document register table ============ */
function DocsPage({ role, onNav, toast, pShort, period, setPeriod }) {
  const canEdit = role === 'owner' || role === 'staff' || role === 'accountant';
  const canDelete = role === 'owner';
  const [, setVer] = useState(0);
  const refresh = () => setVer((v) => v + 1);
  const [q, setQ] = useState('');
  const [catF, setCatF] = useState('all');
  const [statusF, setStatusF] = useState('all');
  const [sel, setSel] = useState([]);
  const [detail, setDetail] = useState(null);
  const [newDoc, setNewDoc] = useState(null);  // manual-entry (กรอกเอง) blank doc
  const [delDoc, setDelDoc] = useState(null);  // single-row delete confirm
  const [bulkDel, setBulkDel] = useState(false); // bulk delete confirm

  const isCurrent = period.m === CURRENT_PERIOD.m && period.y === CURRENT_PERIOD.y;
  const defaultISO = (period.y - 543) + '-' + String(period.m + 1).padStart(2, '0') + '-' + String(isCurrent ? new Date().getDate() : 1).padStart(2, '0');
  const blankDoc = () => ({ id: '', docNo: '', docDateISO: defaultISO, docDate: '', uploadDate: '', vendor: '', vendorTax: '', branch: 'สำนักงานใหญ่', cat: 'material', type: 'full', pre: 0, vat: 0, total: 0, creditable: true, deductible: true, status: 'pending', note: '', file: false });

  const removeLocal = (id) => { const i = D.docs.findIndex((x) => x.id === id); if (i >= 0) D.docs.splice(i, 1); };

  const doDeleteOne = async () => {
    const res = await API.deleteDoc(delDoc.id);
    if (res.ok && res.data.ok) { removeLocal(delDoc.id); setSel((s) => s.filter((x) => x !== delDoc.id)); toast('ลบเอกสารแล้ว'); refresh(); }
    else toast('ลบไม่สำเร็จ', 'warn');
    setDelDoc(null);
  };
  const doDeleteBulk = async () => {
    const ids = [...sel];
    const results = await Promise.all(ids.map((id) => API.deleteDoc(id)));
    let okCount = 0;
    results.forEach((res, i) => { if (res.ok && res.data.ok) { removeLocal(ids[i]); okCount++; } });
    toast('ลบ ' + okCount + ' รายการแล้ว');
    setSel([]); setBulkDel(false); refresh();
  };
  const markSent = async () => {
    const ids = [...sel];
    await Promise.all(ids.map((id) => {
      const d = D.docs.find((x) => x.id === id);
      return d ? API.updateDoc({ ...d, status: 'sent' }) : Promise.resolve();
    }));
    ids.forEach((id) => { const d = D.docs.find((x) => x.id === id); if (d) d.status = 'sent'; });
    toast('ทำเครื่องหมาย "ส่งบัญชีแล้ว" ' + ids.length + ' รายการ');
    setSel([]); refresh();
  };
  const applyEdit = (saved) => { const i = D.docs.findIndex((x) => x.id === saved.id); if (i >= 0) D.docs[i] = saved; refresh(); };
  const applyAdd = (saved) => { D.docs.push(saved); refresh(); };

  let rows = D.docs.filter((d) => d.status !== 'deleted' && inPeriod(d.docDateISO, period));
  if (q) rows = rows.filter((d) => (d.vendor + d.docNo + d.vendorTax).toLowerCase().includes(q.toLowerCase()));
  if (catF !== 'all') rows = rows.filter((d) => d.cat === catF);
  if (statusF !== 'all') rows = rows.filter((d) => d.status === statusF);

  const totals = rows.reduce((a, d) => ({ pre: a.pre + d.pre, vat: a.vat + d.vat, total: a.total + d.total }), { pre: 0, vat: 0, total: 0 });
  const toggle = (id) => setSel((s) => s.includes(id) ? s.filter((x) => x !== id) : [...s, id]);
  const allSel = rows.length > 0 && sel.length === rows.length;

  return (
    <>
      <div className="page-head">
        <div><div className="pt">ทะเบียนเอกสาร</div><div className="ps">เอกสารรายจ่าย/ใบกำกับภาษีซื้อทั้งหมด · รอบ {pShort}</div></div>
        <div className="spacer"></div>
        <button className="btn btn-ghost"><Icon name="download" className="ic" />ส่งออก</button>
        {canEdit && <button className="btn btn-ghost" onClick={() => setNewDoc(blankDoc())}><Icon name="edit" className="ic" />กรอกเอง</button>}
        <button className="btn btn-primary" onClick={() => onNav('scan')}><Icon name="scan" className="ic" />สแกน/เพิ่ม</button>
      </div>

      {/* filters */}
      <div className="card card-pad" style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center', marginBottom: 14 }}>
        <div style={{ position: 'relative', flex: '1 1 240px' }}>
          <Icon name="search" size={17} style={{ position: 'absolute', left: 12, top: 10, color: 'var(--ink-3)' }} />
          <input className="input" style={{ paddingLeft: 36 }} placeholder="ค้นหาร้านค้า เลขที่เอกสาร เลขผู้เสียภาษี…" value={q} onChange={(e) => setQ(e.target.value)} />
        </div>
        <select className="select" style={{ width: 'auto', minWidth: 150 }} value={catF} onChange={(e) => setCatF(e.target.value)}>
          <option value="all">ทุกหมวด</option>
          {D.expCats.map((c) => <option key={c.id} value={c.id}>{c.label}</option>)}
        </select>
        <select className="select" style={{ width: 'auto', minWidth: 130 }} value={statusF} onChange={(e) => setStatusF(e.target.value)}>
          <option value="all">ทุกสถานะ</option>
          {Object.entries(D.statusInfo).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
        </select>
      </div>

      {sel.length > 0 && (
        <div className="card card-pad" style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14, background: 'var(--primary-tint)', borderColor: 'var(--primary-tint2)' }}>
          <span style={{ fontWeight: 600, fontSize: 13.5 }}>เลือก {sel.length} รายการ</span>
          <div style={{ flex: 1 }}></div>
          <button className="btn btn-ghost btn-sm" onClick={markSent}><Icon name="send" className="ic" />ทำเครื่องหมายส่งบัญชี</button>
          {canDelete && <button className="btn btn-danger btn-sm" onClick={() => setBulkDel(true)}><Icon name="trash" className="ic" />ลบ {sel.length} รายการ</button>}
        </div>
      )}

      <div className="card">
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th style={{ width: 34 }}><input type="checkbox" checked={allSel} onChange={() => setSel(allSel ? [] : rows.map((r) => r.id))} style={{ accentColor: 'var(--primary)' }} /></th>
                <th>วันที่เอกสาร</th><th>เลขที่</th><th>ร้านค้า</th><th>หมวด</th>
                <th className="r">ก่อน VAT</th><th className="r">VAT</th><th className="r">รวม</th>
                <th className="c">เครดิต</th><th>สถานะ</th><th className="c sticky-act">จัดการ</th>
              </tr>
            </thead>
            <tbody>
              {rows.map((d) => (
                <tr key={d.id} className="row-link" onClick={() => setDetail(d)}>
                  <td onClick={(e) => e.stopPropagation()}><input type="checkbox" checked={sel.includes(d.id)} onChange={() => toggle(d.id)} style={{ accentColor: 'var(--primary)' }} /></td>
                  <td className="num" style={{ whiteSpace: 'nowrap', color: 'var(--ink-2)' }}>{d.docDate}</td>
                  <td className="mono" style={{ fontSize: 12.5 }}>{d.docNo}</td>
                  <td style={{ fontWeight: 500, minWidth: 160 }}>{d.vendor}<div style={{ fontSize: 11, color: 'var(--ink-3)' }} className="mono">{d.vendorTax}</div></td>
                  <td><span className="chip" style={{ background: 'var(--surface-2)', borderColor: 'transparent' }}><span style={{ width: 7, height: 7, borderRadius: 2, background: D.catColor(d.cat) }}></span>{D.catLabel(d.cat)}</span></td>
                  <td className="r num">{D.fmt(d.pre)}</td>
                  <td className="r num" style={{ color: d.creditable ? 'var(--ink)' : 'var(--ink-3)' }}>{d.vat > 0 ? D.fmt(d.vat) : '—'}</td>
                  <td className="r num" style={{ fontWeight: 600 }}>{D.fmt(d.total)}</td>
                  <td className="c">{d.creditable ? <Icon name="checkCircle" size={17} style={{ color: 'var(--ok)' }} /> : <span style={{ color: 'var(--ink-3)' }}>—</span>}</td>
                  <td><Status status={d.status} /></td>
                  <td className="c sticky-act" onClick={(e) => e.stopPropagation()}>
                    <div style={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
                      {canEdit && <button className="x-btn" title="แก้ไข" onClick={() => setDetail(d)}><Icon name="edit" size={16} /></button>}
                      {canDelete && <button className="x-btn" title="ลบ" style={{ color: 'var(--danger)' }} onClick={() => setDelDoc(d)}><Icon name="trash" size={16} /></button>}
                      <button className="x-btn" title="รายละเอียด" onClick={() => setDetail(d)}><Icon name="chevR" size={17} /></button>
                    </div>
                  </td>
                </tr>
              ))}
            </tbody>
            <tfoot>
              <tr style={{ background: 'var(--surface-2)', fontWeight: 700 }}>
                <td colSpan="5" style={{ padding: '12px', fontSize: 13 }}>รวม {rows.length} รายการ</td>
                <td className="r num" style={{ padding: 12 }}>{D.fmt(totals.pre)}</td>
                <td className="r num" style={{ padding: 12 }}>{D.fmt(totals.vat)}</td>
                <td className="r num" style={{ padding: 12 }}>{D.fmt(totals.total)}</td>
                <td colSpan="3"></td>
              </tr>
            </tfoot>
          </table>
        </div>
        {rows.length === 0 && (() => {
          const allDocs = D.docs.filter((x) => x.status !== 'deleted');
          const inThisPeriod = allDocs.some((x) => inPeriod(x.docDateISO, period));
          if (allDocs.length > 0 && !inThisPeriod) {
            const latest = allDocs.slice().sort((a, b) => (b.docDateISO || '').localeCompare(a.docDateISO || ''))[0];
            const ld = latest && latest.docDateISO ? new Date(latest.docDateISO) : null;
            const target = ld && !isNaN(ld) ? { m: ld.getMonth(), y: ld.getFullYear() + 543 } : null;
            return <Empty icon="calendar" title={'ไม่มีเอกสารในเดือน ' + pShort}
              sub={'มีเอกสาร ' + allDocs.length + ' ใบในระบบ แต่อยู่เดือนอื่น'}
              action={target && setPeriod ? <button className="btn btn-primary" onClick={() => { setQ(''); setCatF('all'); setStatusF('all'); setPeriod(target); }}><Icon name="calendar" className="ic" />ไปเดือนที่มีเอกสาร ({D.thMonthsShort[target.m]} {target.y})</button> : null} />;
          }
          return <Empty icon="search" title="ไม่พบเอกสารที่ตรงกับเงื่อนไข" sub={statusF !== 'all' ? 'ลองตั้งสถานะเป็น "ทุกสถานะ" หรือล้างคำค้นหา' : 'ลองปรับตัวกรองหรือคำค้นหา'} />;
        })()}
      </div>

      {detail && <DocDetail doc={detail} onClose={() => setDetail(null)} canEdit={canEdit} canDelete={canDelete} toast={toast}
        onSaved={applyEdit} onAskDelete={(d) => { setDetail(null); setDelDoc(d); }} />}
      {newDoc && <DocDetail doc={newDoc} isNew canEdit canDelete={false} toast={toast}
        onClose={() => setNewDoc(null)} onSaved={(saved) => { applyAdd(saved); setNewDoc(null); }} onAskDelete={() => {}} />}

      {delDoc && (
        <Modal title="ยืนยันการลบเอกสาร" icon="trash" onClose={() => setDelDoc(null)}
          footer={<><button className="btn btn-ghost" onClick={() => setDelDoc(null)}>ยกเลิก</button><button className="btn btn-danger" onClick={doDeleteOne}><Icon name="trash" className="ic" />ลบเอกสาร</button></>}>
          <div style={{ fontSize: 14, lineHeight: 1.7 }}>
            ต้องการลบเอกสาร <b>{delDoc.docNo}</b> · {delDoc.vendor} ยอด <b>{D.baht(delDoc.total)}</b> ใช่หรือไม่?
            <div style={{ marginTop: 10 }} className="callout warn"><Icon name="alert" className="ic" />การลบจะมีผลทันที และทำให้ยอดสรุปภาษีซื้อ/ภ.พ.30 ของเดือนนี้เปลี่ยนตาม</div>
          </div>
        </Modal>
      )}
      {bulkDel && (
        <Modal title="ยืนยันการลบหลายรายการ" icon="trash" onClose={() => setBulkDel(false)}
          footer={<><button className="btn btn-ghost" onClick={() => setBulkDel(false)}>ยกเลิก</button><button className="btn btn-danger" onClick={doDeleteBulk}><Icon name="trash" className="ic" />ลบ {sel.length} รายการ</button></>}>
          <div style={{ fontSize: 14, lineHeight: 1.7 }}>ต้องการลบเอกสารที่เลือกไว้ <b>{sel.length} รายการ</b> ใช่หรือไม่? การลบจะมีผลทันที</div>
        </Modal>
      )}
    </>
  );
}

function DocDetail({ doc, onClose, canEdit, canDelete, toast, onSaved, onAskDelete, isNew }) {
  const [f, setF] = useState({
    docNo: doc.docNo || '', vendor: doc.vendor, vendorTax: doc.vendorTax, branch: doc.branch || 'สำนักงานใหญ่',
    dateISO: doc.docDateISO || '', type: doc.type, cat: doc.cat,
    pre: doc.pre ? String(doc.pre) : '', vat: doc.vat ? String(doc.vat) : '', total: doc.total ? String(doc.total) : '',
    creditable: doc.creditable, deductible: doc.deductible, status: doc.status, note: doc.note || '',
  });
  const [saving, setSaving] = useState(false);
  const [vSug, setVSug] = useState(false);
  const [docImg, setDocImg] = useState(null);
  const [imgLoading, setImgLoading] = useState(false);
  useEffect(() => {
    if (!isNew && doc.file && doc.id) {
      setImgLoading(true);
      API.getFile(doc.id).then((res) => { if (res.ok && res.data.ok && res.data.data) setDocImg(res.data.data); }).finally(() => setImgLoading(false));
    }
  }, []);
  const openImg = (u) => { fetch(u).then((r) => r.blob()).then((b) => window.open(URL.createObjectURL(b), '_blank')).catch(() => {}); };
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  // VAT auto-calc from ก่อน VAT × อัตรา (กรอกเองสะดวก — อ่านใบเสร็จไม่ออก)
  const recalc = (pre, vat) => set('total', (parseFloat(pre || 0) + parseFloat(vat || 0)).toFixed(2));
  const calcVat = (pre) => { const v = (parseFloat(pre || 0) * (D.company.vatRate / 100)); set('vat', v.toFixed(2)); set('total', (parseFloat(pre || 0) + v).toFixed(2)); };
  const thDate = (iso) => { const d = new Date(iso); if (isNaN(d)) return doc.docDate || ''; return d.getDate() + ' ' + D.thMonthsShort[d.getMonth()] + ' ' + String((d.getFullYear() + 543) % 100).padStart(2, '0'); };

  // vendor autocomplete (จากร้านค้าที่เคยบันทึก)
  const vendorBook = React.useMemo(() => {
    const m = new Map();
    (D.docs || []).forEach((x) => { if (x.vendor && !m.has(x.vendor)) m.set(x.vendor, { vendor: x.vendor, vendorTax: x.vendorTax, cat: x.cat, type: x.type }); });
    return [...m.values()];
  }, []);
  const vq = f.vendor.trim().toLowerCase();
  const vSuggestions = (vq ? vendorBook.filter((c) => c.vendor.toLowerCase().includes(vq) || (c.vendorTax || '').toLowerCase().includes(vq)) : vendorBook).slice(0, 6);
  const pickVendor = (c) => { setF((p) => ({ ...p, vendor: c.vendor, vendorTax: c.vendorTax && c.vendorTax !== '—' ? c.vendorTax : '', cat: c.cat || p.cat, type: c.type || p.type })); setVSug(false); };

  const save = async () => {
    if (!f.vendor.trim()) { toast('กรุณากรอกชื่อร้านค้า/ผู้ขาย', 'warn'); return; }
    if (!(parseFloat(f.pre) > 0)) { toast('กรุณากรอกยอดก่อน VAT', 'warn'); return; }
    setSaving(true);
    const rec = {
      id: doc.id, docNo: f.docNo.trim() || '—', uploadDate: doc.uploadDate || thDate(f.dateISO), file: doc.file,
      docDateISO: f.dateISO, docDate: thDate(f.dateISO),
      vendor: f.vendor.trim(), vendorTax: f.vendorTax.trim() || '—', branch: f.branch.trim(),
      type: f.type, cat: f.cat, pre: +f.pre || 0, vat: +f.vat || 0, total: +f.total || 0,
      creditable: f.creditable, deductible: f.deductible, status: f.status, note: f.note.trim(),
    };
    const res = isNew ? await API.addDoc(rec) : await API.updateDoc(rec);
    setSaving(false);
    if (res.ok && res.data.ok) { onSaved(res.data.record || rec); toast(isNew ? 'เพิ่มเอกสารแล้ว' : 'บันทึกการแก้ไขแล้ว'); onClose(); }
    else toast((res.data && (res.data.msg || res.data.error)) || 'บันทึกไม่สำเร็จ', 'warn');
  };

  return (
    <Modal title={isNew ? 'เพิ่มรายจ่าย / ภาษีซื้อ (กรอกเอง)' : 'เอกสาร ' + doc.docNo} icon="fileText" onClose={onClose} wide
      footer={<>
        {canDelete && <button className="btn btn-danger" style={{ marginRight: 'auto' }} onClick={() => onAskDelete(doc)}><Icon name="trash" className="ic" />ลบ</button>}
        <button className="btn btn-ghost" onClick={onClose}>ยกเลิก</button>
        {canEdit && <button className="btn btn-primary" onClick={save} disabled={saving}><Icon name="save" className="ic" />{saving ? 'กำลังบันทึก…' : (isNew ? 'บันทึก' : 'บันทึกการแก้ไข')}</button>}
      </>}>
      <div style={{ display: 'grid', gridTemplateColumns: '180px 1fr', gap: 18 }}>
        <div>
          <div style={{ aspectRatio: '3/4', borderRadius: 10, overflow: 'hidden', border: '1px solid var(--line)', background: 'var(--surface-2)', display: 'grid', placeItems: 'center' }}>
            {docImg
              ? <img src={docImg} alt="bill" style={{ width: '100%', height: '100%', objectFit: 'contain', cursor: 'zoom-in' }} onClick={() => openImg(docImg)} />
              : imgLoading ? <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>กำลังโหลดรูป…</div>
                : <ReceiptThumb />}
          </div>
          {docImg ? (
            <>
              <button className="btn btn-ghost btn-sm btn-block" style={{ marginTop: 8 }} onClick={() => openImg(docImg)}><Icon name="eye" className="ic" />ดูเต็มจอ</button>
              <a className="btn btn-ghost btn-sm btn-block" style={{ marginTop: 6 }} href={docImg} download={(doc.docNo || 'bill').replace(/[^\w.-]/g, '_') + '.jpg'}><Icon name="download" className="ic" />ดาวน์โหลด</a>
            </>
          ) : isNew
            ? <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 8, textAlign: 'center', lineHeight: 1.5 }}>กรอกเอง — แนบไฟล์ภายหลังได้ที่หน้าสแกน</div>
            : !imgLoading && <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 8, textAlign: 'center' }}>ไม่มีไฟล์แนบ</div>}
          {!isNew && <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 8, textAlign: 'center' }}>อัปโหลด {doc.uploadDate}</div>}
        </div>
        {canEdit ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ position: 'relative' }}>
              <Field label="ชื่อร้านค้า / ผู้ขาย"><input className="input" value={f.vendor} onFocus={() => setVSug(true)} onChange={(e) => { set('vendor', e.target.value); setVSug(true); }} onBlur={() => setTimeout(() => setVSug(false), 160)} placeholder="พิมพ์ชื่อเพื่อค้นหาร้านที่เคยบันทึก" /></Field>
              {vSug && vSuggestions.length > 0 && (
                <div className="card" style={{ position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 6, marginTop: 4, boxShadow: 'var(--shadow-lg)', maxHeight: 240, overflowY: 'auto', padding: 4 }}>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)', padding: '6px 10px 4px', display: 'flex', alignItems: 'center', gap: 6 }}><Icon name="search" size={12} />ร้านค้าที่เคยบันทึก — กดเพื่อเติม</div>
                  {vSuggestions.map((c, i) => (
                    <button type="button" key={i} onMouseDown={(e) => { e.preventDefault(); pickVendor(c); }} style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', textAlign: 'left', padding: '8px 10px', borderRadius: 8 }}
                      onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-2)'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                      <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontWeight: 600, fontSize: 13 }}>{c.vendor}</div><div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{c.vendorTax || '—'} · {D.catLabel(c.cat)}</div></div>
                    </button>
                  ))}
                </div>
              )}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr 1fr', gap: 12 }}>
              <Field label="เลขที่เอกสาร"><input className="input mono" value={f.docNo} onChange={(e) => set('docNo', e.target.value)} placeholder="เลขใบกำกับ" /></Field>
              <Field label="เลขผู้เสียภาษี"><input className="input mono" value={f.vendorTax} onChange={(e) => set('vendorTax', e.target.value)} placeholder="13 หลัก" /></Field>
              <Field label="วันที่เอกสาร"><ThaiDatePicker value={f.dateISO} onChange={(v) => set('dateISO', v)} /></Field>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <Field label="ประเภทเอกสาร"><select className="select" value={f.type} onChange={(e) => set('type', e.target.value)}>{Object.entries(D.docTypes).map(([k, v]) => <option key={k} value={k}>{v}</option>)}</select></Field>
              <Field label="หมวดรายจ่าย"><select className="select" value={f.cat} onChange={(e) => set('cat', e.target.value)}>{D.expCats.map((c) => <option key={c.id} value={c.id}>{c.label}</option>)}</select></Field>
            </div>
            <div style={{ background: 'var(--surface-2)', borderRadius: 11, padding: 14, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
              <Field label="ยอดก่อน VAT"><input className="input right mono" value={f.pre} onChange={(e) => { set('pre', e.target.value); calcVat(e.target.value); }} placeholder="0.00" /></Field>
              <Field label={'VAT ' + D.company.vatRate + '%'}><input className="input right mono" value={f.vat} onChange={(e) => { set('vat', e.target.value); recalc(f.pre, e.target.value); }} placeholder="0.00" /></Field>
              <Field label="ยอดรวมสุทธิ"><input className="input right mono" value={f.total} onChange={(e) => set('total', e.target.value)} style={{ fontWeight: 700 }} /></Field>
            </div>
            <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
              <Toggle on={f.creditable} onClick={() => set('creditable', !f.creditable)} label="ใช้เป็น VAT ซื้อ (เครดิตได้)" />
              <Toggle on={f.deductible} onClick={() => set('deductible', !f.deductible)} label="หักเป็นค่าใช้จ่ายได้" />
            </div>
            <Field label="สถานะเอกสาร"><select className="select" value={f.status} onChange={(e) => set('status', e.target.value)}>{Object.entries(D.statusInfo).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}</select></Field>
            <Field label="หมายเหตุ"><textarea className="input" rows="2" value={f.note} onChange={(e) => set('note', e.target.value)}></textarea></Field>
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <Status status={doc.status} /><span className="badge muted">{D.docTypes[doc.type]}</span>
              {doc.creditable && <span className="badge ok"><Icon name="check" size={11} />เครดิต VAT ได้</span>}
            </div>
            {[['ร้านค้า', doc.vendor], ['เลขผู้เสียภาษี', doc.vendorTax], ['สาขา', doc.branch], ['วันที่เอกสาร', doc.docDate], ['หมวดรายจ่าย', D.catLabel(doc.cat)]].map(([k, v]) => (
              <div key={k} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13.5, paddingBottom: 7, borderBottom: '1px solid var(--line-2)' }}><span style={{ color: 'var(--ink-3)' }}>{k}</span><span style={{ fontWeight: 600, textAlign: 'right' }}>{v}</span></div>
            ))}
            <div style={{ background: 'var(--surface-2)', borderRadius: 10, padding: 12, marginTop: 4 }}>
              {[['ยอดก่อน VAT', doc.pre], ['VAT ' + D.company.vatRate + '%', doc.vat], ['ยอดรวมสุทธิ', doc.total]].map(([k, v], i) => (
                <div key={k} style={{ display: 'flex', justifyContent: 'space-between', fontSize: i === 2 ? 15 : 13.5, fontWeight: i === 2 ? 700 : 500, paddingTop: i === 2 ? 8 : 0, marginTop: i === 2 ? 4 : 0, borderTop: i === 2 ? '1px solid var(--line)' : 'none' }}><span style={{ color: i === 2 ? 'var(--ink)' : 'var(--ink-2)' }}>{k}</span><span className="num">{D.baht(v)}</span></div>
              ))}
            </div>
            {doc.note && <div style={{ fontSize: 13, color: 'var(--ink-2)' }}><b>หมายเหตุ:</b> {doc.note}</div>}
          </div>
        )}
      </div>
    </Modal>
  );
}
window.DocsPage = DocsPage;
