/* page_debt_leasit.jsx — แท็บ "ลีซอิท" ในหน้า #debt
   - Upload ไฟล์ตารางคำนวณดอกเบี้ยลีซอิท (.xlsx 94 ชีต)
   - แสดงรายสัญญา + รวม prepaid/actual/variance/refund/outstanding
   - คลิกแถว → drawer ดู schedule prepaid/actual/refund + ปุ่ม Export ราย loan
   - Export Excel ทั้งหมด (ชีตต่อสัญญา มีสูตร =Principal*Rate*Days/365)

   identifiers prefix Leasit / lit  (กัน global-scope name collision)
*/
'use strict';

/* ── helpers (ภายใน) ──────────────────────────────────────────────────── */
const LIT_fmt = (n, d = 2) =>
  (typeof window.fmtNum === 'function' ? window.fmtNum(n, d) : Number(n || 0).toFixed(d));

const LIT_fmtDate = (iso) =>
  (typeof window.fmtDate === 'function' ? window.fmtDate(iso) : (iso || ''));

const litRowId = (loanId, kind, seq) => 'lit_' + kind + '_' + loanId + '_' + seq;

/* ★ debtMaster.leasitLoanId เป็นคอลัมน์ text ใน Postgres → อ่านกลับมาได้ "82" (string)
   แต่ตารางลูก (prepaid/actual/refund) เก็บเป็น jsonb → loanId ยังเป็น 82 (number)
   ถ้าเทียบด้วย === ตรง ๆ จะไม่ตรงกัน → หลังนำเข้าเห็นข้อมูลแวบเดียว พอ sync กลับมาก็หาย
   ทุกที่ที่เทียบ loanId ต้องผ่าน LIT_lid / LIT_sameLoan */
const LIT_lid = (v) => {
  if (v == null || v === '') return v;
  const n = Number(v);
  return Number.isFinite(n) ? n : v;
};
const LIT_sameLoan = (a, b) => a === b || (a != null && b != null && LIT_lid(a) === LIT_lid(b));

/* ── LeasitImportModal — preview diff ก่อน import ─────────────────────── */
function LeasitImportModal({ open, onClose, onConfirm }) {
  const [file, setFile] = React.useState(null);
  const [parsed, setParsed] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    if (!open) { setFile(null); setParsed(null); setErr(''); }
  }, [open]);

  const handleFile = async (f) => {
    if (!f) return;
    setFile(f); setErr(''); setBusy(true); setParsed(null);
    try {
      const XLSX = window.XLSX;
      if (!XLSX) throw new Error('ไม่พบ XLSX library');
      const buf = await f.arrayBuffer();
      const wb = XLSX.read(buf, { type: 'array', cellDates: false });
      const result = window.LeasitEngine.parseLeasitWorkbook(wb);
      setParsed(result);
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  if (!open) return null;
  const Modal = window.Modal;
  return (
    <Modal open={open} onClose={onClose} title="📥 นำเข้าตารางคำนวณดอกเบี้ยลีซอิท" maxWidth={1100}>
      <div style={{ padding: '8px 4px', fontSize: 14 }}>
        <div style={{
          border: '2px dashed var(--ink-200)', borderRadius: 12, padding: 24,
          textAlign: 'center', background: 'var(--ink-50)', cursor: 'pointer'
        }}
          onClick={() => document.getElementById('lit-file-in').click()}
          onDragOver={(e) => e.preventDefault()}
          onDrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer.files[0]); }}
        >
          <div style={{ fontSize: 24 }}>📂</div>
          <div style={{ marginTop: 4, color: 'var(--ink-700)' }}>
            ลากไฟล์ <code>.xlsx</code> มาวาง หรือคลิกเพื่อเลือก
          </div>
          <div style={{ fontSize: 12, color: 'var(--ink-500)', marginTop: 4 }}>
            ไฟล์ต้นทาง: ตารางคำนวณดอกเบี้ยเงินกู้-ลีซอิท.xlsx
          </div>
          {file && <div style={{ marginTop: 8, fontWeight: 600 }}>{file.name}</div>}
          <input
            id="lit-file-in"
            type="file"
            accept=".xlsx,.xls"
            style={{ display: 'none' }}
            onChange={(e) => handleFile(e.target.files[0])}
          />
        </div>

        {busy && <div style={{ marginTop: 12, color: 'var(--brand-500)' }}>⏳ กำลังอ่านไฟล์…</div>}
        {err && <div style={{ marginTop: 12, color: 'var(--bad)' }}>❌ {err}</div>}

        {parsed && (
          <div style={{ marginTop: 16 }}>
            <div style={{
              display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 8, marginBottom: 12
            }}>
              <div className="card" style={{ padding: 10 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>สัญญา</div>
                <div style={{ fontSize: 22, fontWeight: 700 }}>{parsed.loans.length}</div>
              </div>
              <div className="card" style={{ padding: 10 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>งวด Prepaid</div>
                <div style={{ fontSize: 22, fontWeight: 700 }}>{parsed.prepaid.length}</div>
              </div>
              <div className="card" style={{ padding: 10 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>งวด Actual</div>
                <div style={{ fontSize: 22, fontWeight: 700 }}>{parsed.actual.length}</div>
              </div>
              <div className="card" style={{ padding: 10 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ดอกรับคืน</div>
                <div style={{ fontSize: 22, fontWeight: 700 }}>
                  {parsed.refund.filter(r => r.kind !== 'principal').length}
                </div>
              </div>
              {/* ★ จ่ายคืนเงินต้น — ตัวที่ใช้คำนวณดอกเบี้ยเกิดจริงแบบหักยอดคงเหลือรายงวด */}
              <div className="card" style={{ padding: 10, background: 'oklch(96% 0.05 145)' }}>
                <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>จ่ายคืนเงินต้น</div>
                <div style={{ fontSize: 22, fontWeight: 700 }}>
                  {parsed.refund.filter(r => r.kind === 'principal').length}
                </div>
              </div>
            </div>

            {parsed.errors.length > 0 && (
              <div style={{
                padding: 10, background: 'oklch(95% 0.05 80)', borderRadius: 8,
                fontSize: 12, color: 'var(--ink-700)', marginBottom: 12
              }}>
                ⚠️ มี {parsed.errors.length} ชีตที่อ่านไม่ครบ:
                <ul style={{ margin: '4px 0 0 16px' }}>
                  {parsed.errors.slice(0, 8).map((e, i) => <li key={i}>{e}</li>)}
                  {parsed.errors.length > 8 && <li>… อีก {parsed.errors.length - 8} รายการ</li>}
                </ul>
              </div>
            )}

            <div style={{ maxHeight: 280, overflowY: 'auto', border: '1px solid var(--ink-200)', borderRadius: 8 }}>
              <table className="tbl" style={{ width: '100%', fontSize: 12 }}>
                <thead>
                  <tr><th>ลำดับ</th><th>เลขที่สัญญา</th><th>โครงการ</th><th style={{ textAlign: 'right' }}>วงเงิน</th><th>สถานะ</th><th style={{ textAlign: 'right' }}>ค้างรับคืน</th></tr>
                </thead>
                <tbody>
                  {parsed.loans.slice(0, 50).map(L => (
                    <tr key={L.loanId}>
                      <td>{L.loanId}</td>
                      <td>{L.contractNo}</td>
                      <td title={L.projectName} style={{ maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{L.projectName}</td>
                      <td style={{ textAlign: 'right' }}>{LIT_fmt(L.principal, 0)}</td>
                      <td>{L.status === 'Active' ? '🟢 Active' : '⚫ Close'}</td>
                      <td style={{ textAlign: 'right', color: L.refundOutstanding > 0.01 ? 'var(--bad)' : 'var(--ink-500)' }}>
                        {LIT_fmt(L.refundOutstanding, 2)}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            {parsed.loans.length > 50 && (
              <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 4 }}>
                แสดง 50 จาก {parsed.loans.length} สัญญา
              </div>
            )}

            <div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={onClose}>ยกเลิก</button>
              <button className="btn btn-primary" onClick={() => onConfirm(parsed)}>
                ✓ นำเข้า ({parsed.loans.length} สัญญา)
              </button>
            </div>
          </div>
        )}
      </div>
    </Modal>
  );
}

/* ── LeasitLoanDrawer — ดู schedule prepaid/actual/refund ────────────── */
function LeasitLoanDrawer({ loan, assignee, prepaid, actual, refund, onClose, onExportOne, onEdit, canEdit, onCalcPrepaid, onCalcActual, onSaveScheduleRow }) {
  if (!loan) return null;
  const Modal = window.Modal;

  // ★ inline edit state for prepaid/actual rows — {section:'pre'|'act', idx:number, draft:row}
  const [editing, setEditing] = React.useState(null);

  const pre = prepaid.filter(r => LIT_sameLoan(r.loanId, loan.leasitLoanId)).sort((a, b) => (a.seq || 0) - (b.seq || 0));
  const act = actual.filter(r => LIT_sameLoan(r.loanId, loan.leasitLoanId)).sort((a, b) => (a.seq || 0) - (b.seq || 0));
  // ★ "รับคืน/จ่ายคืน" tab = ดอกเบี้ยเท่านั้น (kind!=='principal')
  //    เงินต้นจ่ายคืน เห็นได้จากการ์ด "จ่ายคืนเงินต้น" บนสุดแล้ว
  const refAll = refund.filter(r => LIT_sameLoan(r.loanId, loan.leasitLoanId)).sort((a, b) => (a.refundDate || '').localeCompare(b.refundDate || ''));
  const ref = refAll.filter(r => r.kind !== 'principal');
  const refPrincipalCount = refAll.length - ref.length;

  // ── PRE/POS/NON classification + principal summary ──
  const ticketRaw = String(loan.leasitTicketType || '').toUpperCase();
  const ticketLabel = ticketRaw === 'NON' ? 'NON' : ticketRaw === 'PRE' ? 'PRE' : 'POS';
  const ticketColor = ticketLabel === 'PRE' ? 'oklch(52% 0.16 250)'
                    : ticketLabel === 'NON' ? 'oklch(48% 0.14 305)'
                    : 'oklch(56% 0.18 25)';
  const ticketDesc = ticketLabel === 'PRE' ? 'Pre-financing'
                   : ticketLabel === 'NON' ? 'Non-PRE (Term Loan)'
                   : 'Post-financing';
  const principal = Number(loan.principalAmount) || 0;
  const isActive = loan.status === 'Active';
  const drawn = principal;
  const repaidPrincipal = isActive ? 0 : principal;
  const outstandingPrincipal = isActive ? principal : 0;

  // ── Maturity countdown ──
  const today = new Date();
  const todayISO = today.toISOString().slice(0, 10);
  const dueISO = loan.leasitDateDueRoll || loan.leasitDateDue || '';
  const daysToMaturity = dueISO ? Math.ceil((new Date(dueISO).getTime() - today.getTime()) / 86400000) : null;
  const maturityNear = isActive && daysToMaturity != null && daysToMaturity >= 0 && daysToMaturity <= 30;
  const maturityOverdue = isActive && daysToMaturity != null && daysToMaturity < 0;

  // ── helper สำหรับ inline edit ──
  const openEditRow = (section, row, idx) => {
    setEditing({ section: section, idx: idx, draft: { ...row } });
  };
  const saveEditRow = () => {
    if (!editing || !onSaveScheduleRow) { setEditing(null); return; }
    const cleaned = {
      ...editing.draft,
      principal: Number(editing.draft.principal) || 0,
      intRate: Number(editing.draft.intRate) || 0,
      days: Number(editing.draft.days) || 0,
      intAmount: Number(editing.draft.intAmount) || 0
    };
    onSaveScheduleRow(loan, editing.section === 'pre' ? 'prepaid' : 'actual', cleaned);
    setEditing(null);
  };
  const recalcInt = () => {
    // ดอกเบี้ย = principal × rate × days / 365
    if (!editing) return;
    const p = Number(editing.draft.principal) || 0;
    const r = Number(editing.draft.intRate) || 0;
    const d = Number(editing.draft.days) || 0;
    const amt = Math.round(p * r * d / 365 * 100) / 100;
    setEditing(e => ({ ...e, draft: { ...e.draft, intAmount: amt } }));
  };

  return (
    <Modal open={!!loan} onClose={onClose} title={`📑 ${loan.contractNo} · ลำดับ ${loan.leasitLoanId}`} maxWidth={3000}>
      {/* Inline edit popup */}
      {editing && (
        <div style={{
          position: 'fixed', inset: 0, zIndex: 9999,
          background: 'rgba(15, 23, 42, 0.5)',
          display: 'flex', alignItems: 'center', justifyContent: 'center'
        }} onClick={() => setEditing(null)}>
          <div className="card" style={{
            padding: 16, width: 480, maxWidth: '92vw',
            background: '#fff', borderRadius: 12, boxShadow: '0 12px 40px rgba(0,0,0,0.18)'
          }} onClick={e => e.stopPropagation()}>
            <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 12 }}>
              ✏️ แก้ไขแถว # {editing.draft.seq} · {editing.section === 'pre' ? 'ดอกล่วงหน้า' : 'ดอกเกิดจริง'}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={{ fontSize: 11 }}>เริ่ม
                <input type="date" className="input" style={{ width: '100%' }}
                  value={editing.draft.dateStart || ''}
                  onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, dateStart: e.target.value } }))} />
              </label>
              <label style={{ fontSize: 11 }}>สิ้นสุด
                <input type="date" className="input" style={{ width: '100%' }}
                  value={editing.draft.dateEnd || ''}
                  onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, dateEnd: e.target.value } }))} />
              </label>
              <label style={{ fontSize: 11 }}>เงินต้น
                <input type="number" className="input" style={{ width: '100%', textAlign: 'right' }}
                  value={editing.draft.principal || 0}
                  onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, principal: e.target.value } }))} />
              </label>
              <label style={{ fontSize: 11 }}>อัตรา/ปี (เช่น 0.15)
                <input type="number" step="0.0001" className="input" style={{ width: '100%', textAlign: 'right' }}
                  value={editing.draft.intRate || 0}
                  onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, intRate: e.target.value } }))} />
              </label>
              <label style={{ fontSize: 11 }}>จำนวนวัน
                <input type="number" className="input" style={{ width: '100%', textAlign: 'right' }}
                  value={editing.draft.days || 0}
                  onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, days: e.target.value } }))} />
              </label>
              <label style={{ fontSize: 11 }}>ดอกเบี้ย
                <div style={{ display: 'flex', gap: 4 }}>
                  <input type="number" step="0.01" className="input" style={{ flex: 1, textAlign: 'right' }}
                    value={editing.draft.intAmount || 0}
                    onChange={e => setEditing(s => ({ ...s, draft: { ...s.draft, intAmount: e.target.value } }))} />
                  <button type="button" className="btn btn-ghost" style={{ padding: '2px 6px', fontSize: 11 }}
                    title="คำนวณ = เงินต้น × อัตรา × วัน / 365" onClick={recalcInt}>↻</button>
                </div>
              </label>
            </div>
            <div style={{ display: 'flex', gap: 8, marginTop: 14, justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={() => setEditing(null)}>ยกเลิก</button>
              <button className="btn btn-primary" onClick={saveEditRow}>💾 บันทึก</button>
            </div>
            <div style={{ marginTop: 8, fontSize: 11, color: 'var(--ink-500)' }}>
              💡 กด ↻ เพื่อคำนวณดอกเบี้ยอัตโนมัติจากเงินต้น × อัตรา × วัน / 365
            </div>
          </div>
        </div>
      )}
      <div style={{ padding: '8px 4px', fontSize: 13 }}>
        {/* ★ NON (Term Loan) banner */}
        {ticketLabel === 'NON' && (
          <div className="card" style={{
            padding: 10, marginBottom: 10,
            background: 'oklch(96% 0.04 305)',
            borderLeft: '4px solid oklch(48% 0.14 305)',
            fontSize: 12
          }}>
            📌 <b>เงินกู้ระยะยาว (Term Loan)</b> — ไม่มีจ่ายล่วงหน้า/รับคืนแบบ Pre-financing
            <div style={{ marginTop: 4, color: 'var(--ink-700)' }}>
              ทยอยขึ้นเช็ครายเดือนตามตาราง amortization · ดอกเบี้ยคำนวณจาก outstanding principal ที่ลดลงเรื่อยๆ · "ดอกล่วงหน้า" = schedule ที่ยังไม่ขึ้นเช็ค, "ดอกเกิดจริง" = ที่ขึ้นเช็คแล้ว
            </div>
          </div>
        )}

        {/* Maturity warning banner */}
        {(maturityNear || maturityOverdue) && (
          <div className="card" style={{
            padding: 12, marginBottom: 12,
            background: maturityOverdue ? 'oklch(95% 0.06 22)' : 'oklch(95% 0.08 80)',
            borderLeft: '4px solid ' + (maturityOverdue ? 'var(--bad)' : 'oklch(70% 0.18 80)')
          }}>
            <div style={{ fontWeight: 700, fontSize: 14 }}>
              {maturityOverdue
                ? `⚠️ เกินกำหนดแล้ว ${Math.abs(daysToMaturity)} วัน`
                : `⏰ ครบกำหนดในอีก ${daysToMaturity} วัน`}
            </div>
            <div style={{ fontSize: 12, color: 'var(--ink-700)', marginTop: 4 }}>
              ครบกำหนด: {LIT_fmtDate(dueISO)} · เงินต้นคงเหลือ {LIT_fmt(outstandingPrincipal, 0)}
            </div>
          </div>
        )}

        {/* Principal summary — เบิก/จ่ายคืน/คงเหลือ + PRE/POS tag */}
        <div className="card" style={{ padding: 12, marginBottom: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
            <div style={{ fontWeight: 700, fontSize: 14 }}>💰 สรุปเงินต้น</div>
            <span style={{
              padding: '2px 10px', borderRadius: 999, fontSize: 11, fontWeight: 700,
              background: ticketColor, color: '#fff'
            }}>{ticketLabel}</span>
            <span style={{ fontSize: 11, color: 'var(--ink-500)' }}>
              ({ticketDesc})
            </span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
            <div style={{ padding: 10, background: 'var(--ink-50)', borderRadius: 8 }}>
              <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>↗ เบิกมาแล้ว</div>
              <div style={{ fontSize: 22, fontWeight: 700 }}>{LIT_fmt(drawn, 0)}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>
                รับเงิน {LIT_fmtDate(loan.leasitDateReceived)}
              </div>
            </div>
            <div style={{ padding: 10, background: 'oklch(96% 0.04 145)', borderRadius: 8 }}>
              <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>↙ จ่ายคืนเงินต้น</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(repaidPrincipal, 0)}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>
                {isActive ? '— (ยังไม่ปิดหนี้)' : 'ปิดหนี้ ' + LIT_fmtDate(loan.leasitDateRepaid)}
              </div>
            </div>
            <div style={{
              padding: 10, borderRadius: 8,
              background: isActive ? 'oklch(96% 0.06 22)' : 'var(--ink-50)'
            }}>
              <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>● คงเหลือเงินต้น</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: isActive ? 'var(--bad)' : 'var(--ink-500)' }}>
                {LIT_fmt(outstandingPrincipal, 0)}
              </div>
              <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>
                {isActive ? 'ครบกำหนด ' + LIT_fmtDate(dueISO) : 'ปิดสัญญาแล้ว'}
              </div>
            </div>
          </div>
        </div>

        {/* Header card */}
        <div className="card" style={{ padding: 12, marginBottom: 12 }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>โครงการ (JOB)</div>
              <div style={{ fontWeight: 600 }}>{loan.projectCode || '—'}</div>
              <div style={{ fontSize: 12, color: 'var(--ink-700)' }}>{loan.projectName}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ผู้รับโอนสิทธิ (โครงการ)</div>
              <div style={{ fontWeight: 600, color: assignee ? 'oklch(52% 0.16 250)' : 'var(--ink-400)' }}>{assignee || '— ยังไม่ระบุในโครงการ —'}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>วงเงินกู้</div>
              <div style={{ fontWeight: 600, fontSize: 18 }}>{LIT_fmt(loan.principalAmount, 0)}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>อัตรา/ปี · ระยะเวลา</div>
              <div style={{ fontWeight: 600 }}>{(loan.interestRate * 100).toFixed(2)}% · {loan.leasitTermDays || '—'} วัน</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>สถานะ</div>
              <div style={{ fontWeight: 600 }}>{loan.status === 'Active' ? '🟢 Active' : '⚫ ปิดแล้ว ' + (LIT_fmtDate(loan.leasitDateRepaid))}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>รับเงิน</div>
              <div>{LIT_fmtDate(loan.leasitDateReceived)}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ครบกำหนด</div>
              <div>{LIT_fmtDate(loan.leasitDateDue)}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ครบกำหนด (โรล)</div>
              <div>{LIT_fmtDate(loan.leasitDateDueRoll) || '—'}</div>
            </div>
            <div><div style={{ fontSize: 11, color: 'var(--ink-500)' }}>เลขเช็คเงินต้น</div>
              <div>{loan.leasitPrincipalChequeNo || '—'}</div>
            </div>
          </div>
        </div>

        {/* Summary card */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 8, marginBottom: 12 }}>
          <div className="card" style={{ padding: 10 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ดอกจ่ายล่วงหน้า</div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>{LIT_fmt(loan.leasitTotalPrepaid, 2)}</div>
          </div>
          <div className="card" style={{ padding: 10 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ดอกเกิดจริง</div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>{LIT_fmt(loan.leasitTotalActual, 2)}</div>
          </div>
          <div className="card" style={{ padding: 10, background: 'oklch(96% 0.05 145)' }}>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ส่วนต่าง (ต้องได้คืน)</div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>{LIT_fmt(loan.leasitVariance, 2)}</div>
          </div>
          <div className="card" style={{ padding: 10 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>รับคืนแล้ว</div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>{LIT_fmt(loan.leasitRefunded, 2)}</div>
          </div>
          <div className="card" style={{ padding: 10, background: loan.leasitRefundOutstanding > 0.01 ? 'oklch(95% 0.06 22)' : 'oklch(96% 0.04 145)' }}>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>ค้างรับคืน</div>
            <div style={{ fontSize: 18, fontWeight: 700, color: loan.leasitRefundOutstanding > 0.01 ? 'var(--bad)' : 'inherit' }}>
              {LIT_fmt(loan.leasitRefundOutstanding, 2)}
            </div>
          </div>
        </div>

        {/* ── 3-col grid: ดอกล่วงหน้า | ดอกเกิดจริง | รับคืน ── */}
        {(() => {
          const tdCell = { padding: '2px 5px', fontSize: 11, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
          const tdR = { ...tdCell, textAlign: 'right' };
          const thStyle = { ...tdCell, fontWeight: 600, background: 'var(--ink-50)', position: 'sticky', top: 0 };
          const thR = { ...thStyle, textAlign: 'right' };
          const sectionCard = { border: '1px solid var(--ink-200)', borderRadius: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' };
          const tableBox = { flex: 1, overflowY: 'auto', maxHeight: 380 };
          return (
            <div style={{ display: 'grid', gridTemplateColumns: '5fr 5fr 3fr', gap: 8, marginTop: 8 }}>

              {/* ── PREPAID (left) ── */}
              <div style={sectionCard}>
                <div style={{ padding: '6px 10px', background: 'oklch(96% 0.05 250)', borderBottom: '1px solid var(--ink-200)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
                  <div style={{ fontWeight: 700, fontSize: 12 }}>📋 ดอกล่วงหน้า ({pre.length})</div>
                  {canEdit && onCalcPrepaid && (
                    <button
                      className="btn btn-primary"
                      style={{ padding: '2px 8px', fontSize: 11 }}
                      title="คำนวณตารางดอกเบี้ยจ่ายล่วงหน้าใหม่ทั้งหมด (จากวันรับเงิน → วันครบกำหนด)"
                      onClick={() => {
                        if (confirm('คำนวณตารางดอกเบี้ยจ่ายล่วงหน้าใหม่? (จะแทนที่ของเดิม)')) onCalcPrepaid(loan);
                      }}
                    >⚡ คำนวณ</button>
                  )}
                </div>
                <div style={tableBox}>
                  <table className="tbl" style={{ width: '100%', fontSize: 11, borderCollapse: 'collapse' }}>
                    <thead><tr>
                      <th style={thStyle}>#</th><th style={thStyle}>เริ่ม</th><th style={thStyle}>สิ้นสุด</th>
                      <th style={thR}>เงินต้น</th><th style={thR}>วัน</th><th style={thR}>ดอกเบี้ย</th>
                      {canEdit && onSaveScheduleRow && <th style={{ ...thStyle, width: 24 }}></th>}
                    </tr></thead>
                    <tbody>
                      {pre.map((p, pi) => (
                        <tr key={p.seq} style={{ borderBottom: '1px solid var(--ink-100)' }}>
                          <td style={tdCell}>{p.seq}</td>
                          <td style={tdCell}>{LIT_fmtDate(p.dateStart)}</td>
                          <td style={tdCell}>{LIT_fmtDate(p.dateEnd)}</td>
                          <td style={tdR} title="เงินต้นที่ใช้คำนวณดอกเบี้ยในงวดนี้">{LIT_fmt(p.principal, 0)}</td>
                          <td style={tdR}>{p.days}</td>
                          <td style={tdR}>{LIT_fmt(p.intAmount, 2)}</td>
                          {canEdit && onSaveScheduleRow && (
                            <td style={tdCell}>
                              <button className="btn btn-ghost" style={{ padding: '0 4px', fontSize: 11 }}
                                title="แก้ไขแถวนี้" onClick={() => openEditRow('pre', p, pi)}>✏️</button>
                            </td>
                          )}
                        </tr>
                      ))}
                    </tbody>
                    <tfoot><tr style={{ fontWeight: 700, background: 'var(--ink-50)' }}>
                      <td colSpan={5} style={{ ...tdCell, textAlign: 'right' }}>รวม</td>
                      <td style={tdR}>{LIT_fmt(pre.reduce((s, p) => s + (Number(p.intAmount) || 0), 0), 2)}</td>
                      {canEdit && onSaveScheduleRow && <td></td>}
                    </tr></tfoot>
                  </table>
                </div>
              </div>

              {/* ── ACTUAL (middle) ── */}
              <div style={sectionCard}>
                <div style={{ padding: '6px 10px', background: 'oklch(96% 0.05 145)', borderBottom: '1px solid var(--ink-200)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
                  <div style={{ fontWeight: 700, fontSize: 12 }}>✅ ดอกเกิดจริง ({act.length})</div>
                  {canEdit && onCalcActual && (
                    <button
                      className="btn btn-primary"
                      style={{ padding: '2px 8px', fontSize: 11 }}
                      title="คำนวณดอกที่เกิดจริง: ถ้าปิดสัญญาแล้ว = จากวันรับ → วันคืนเงิน, ถ้ายัง Active = ถึงวันนี้"
                      onClick={() => {
                        if (confirm('คำนวณดอกที่เกิดจริงใหม่? (จะแทนที่ของเดิม)')) onCalcActual(loan);
                      }}
                    >⚡ คำนวณ</button>
                  )}
                </div>
                <div style={tableBox}>
                  <table className="tbl" style={{ width: '100%', fontSize: 11, borderCollapse: 'collapse' }}>
                    <thead><tr>
                      <th style={thStyle}>#</th><th style={thStyle}>เริ่ม</th><th style={thStyle}>สิ้นสุด</th>
                      <th style={thR}>เงินต้น</th><th style={thR}>วัน</th><th style={thR}>ดอกเบี้ย</th>
                      {canEdit && onSaveScheduleRow && <th style={{ ...thStyle, width: 24 }}></th>}
                    </tr></thead>
                    <tbody>
                      {act.length === 0 ? (
                        <tr><td colSpan={canEdit && onSaveScheduleRow ? 7 : 6} style={{ ...tdCell, textAlign: 'center', padding: '12px 6px', color: 'var(--ink-500)' }}>
                          ยังไม่มีข้อมูลดอกเกิดจริง — กด ⚡ คำนวณ
                        </td></tr>
                      ) : act.map((p, i) => {
                        // ★ highlight แถวที่มีการจ่ายเงินต้น (declining principal) — เห็นชัดว่าเงินต้นเปลี่ยน
                        const prev = i > 0 ? Number(act[i - 1].principal) || 0 : 0;
                        const cur = Number(p.principal) || 0;
                        const declined = i > 0 && cur < prev - 0.01;
                        return (
                          <tr key={p.seq} style={{ borderBottom: '1px solid var(--ink-100)', background: declined ? 'oklch(96% 0.06 145)' : 'transparent' }}>
                            <td style={tdCell}>{p.seq}</td>
                            <td style={tdCell}>{LIT_fmtDate(p.dateStart)}</td>
                            <td style={tdCell}>{LIT_fmtDate(p.dateEnd)}</td>
                            <td style={{ ...tdR, fontWeight: declined ? 700 : 400, color: declined ? 'oklch(50% 0.14 145)' : 'inherit' }} title={declined ? 'เงินต้นลดลง (มีการจ่ายคืน)' : ''}>
                              {LIT_fmt(p.principal, 0)}{declined && ' ↓'}
                            </td>
                            <td style={tdR}>{p.days}</td>
                            <td style={tdR}>{LIT_fmt(p.intAmount, 2)}</td>
                            {canEdit && onSaveScheduleRow && (
                              <td style={tdCell}>
                                <button className="btn btn-ghost" style={{ padding: '0 4px', fontSize: 11 }}
                                  title="แก้ไขแถวนี้" onClick={() => openEditRow('act', p, i)}>✏️</button>
                              </td>
                            )}
                          </tr>
                        );
                      })}
                    </tbody>
                    <tfoot><tr style={{ fontWeight: 700, background: 'var(--ink-50)' }}>
                      <td colSpan={5} style={{ ...tdCell, textAlign: 'right' }}>รวม</td>
                      <td style={tdR}>{LIT_fmt(act.reduce((s, p) => s + (Number(p.intAmount) || 0), 0), 2)}</td>
                      {canEdit && onSaveScheduleRow && <td></td>}
                    </tr></tfoot>
                  </table>
                </div>
              </div>

              {/* ── REFUND (right, narrower) ── */}
              <div style={sectionCard}>
                <div style={{ padding: '6px 10px', background: 'oklch(96% 0.05 80)', borderBottom: '1px solid var(--ink-200)' }}>
                  <div style={{ fontWeight: 700, fontSize: 12 }}>💸 รับคืน/จ่ายคืน ({ref.length})</div>
                </div>
                <div style={tableBox}>
                  {ref.length === 0 ? (
                    <div style={{ padding: 12, color: 'var(--ink-500)', textAlign: 'center', fontSize: 11 }}>
                      ยังไม่มีรับคืน
                    </div>
                  ) : (
                    <table className="tbl" style={{ width: '100%', fontSize: 11, borderCollapse: 'collapse' }}>
                      <thead><tr>
                        <th style={thStyle}>วันที่</th><th style={thStyle}>ประเภท</th><th style={thR}>จำนวน</th>
                      </tr></thead>
                      <tbody>
                        {ref.map((r, i) => (
                          <tr key={i} style={{ borderBottom: '1px solid var(--ink-100)' }} title={`${r.note || ''}\n${r.refDoc || ''}`}>
                            <td style={tdCell}>{LIT_fmtDate(r.refundDate)}</td>
                            <td style={tdCell}>{r.kind === 'principal' ? '💰 เงินต้น' : r.refundType === 'RV' ? '🧾 RV' : '🏦 โอน'}</td>
                            <td style={tdR}>{LIT_fmt(r.amount, 2)}</td>
                          </tr>
                        ))}
                      </tbody>
                      <tfoot><tr style={{ fontWeight: 700, background: 'var(--ink-50)' }}>
                        <td colSpan={2} style={{ ...tdCell, textAlign: 'right' }}>รวม</td>
                        <td style={tdR}>{LIT_fmt(ref.reduce((s, r) => s + (Number(r.amount) || 0), 0), 2)}</td>
                      </tr></tfoot>
                    </table>
                  )}
                </div>
              </div>

            </div>
          );
        })()}

        <div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'space-between' }}>
          <div>
            {onEdit && (
              <button className="btn" style={{ background: 'oklch(60% 0.16 250)', color: '#fff' }} onClick={() => onEdit(loan)}>
                ✏️ แก้ไข / เพิ่มจ่ายคืน
              </button>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-ghost" onClick={onClose}>ปิด</button>
            <button className="btn btn-primary" onClick={() => onExportOne(loan)}>
              📥 Export ชีตสัญญานี้ (มีสูตร)
            </button>
          </div>
        </div>
      </div>
    </Modal>
  );
}

/* ── LeasitProjectAutocomplete — ค้นหาโครงการจาก data.projects ─────────── */
function LeasitProjectAutocomplete({ value, name, projects, onPick, onChangeManual }) {
  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState('');
  const wrapRef = React.useRef(null);

  // ปิด dropdown เมื่อคลิกนอก
  React.useEffect(() => {
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const matches = React.useMemo(() => {
    if (!window.LeasitEngine) return [];
    return window.LeasitEngine.litSearchProjects(projects, query || value, 10);
  }, [projects, query, value]);

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 6 }}>
        <input
          className="input"
          placeholder="รหัสโครงการ"
          value={value || ''}
          onChange={(e) => { setQuery(e.target.value); setOpen(true); onChangeManual('projectCode', e.target.value); }}
          onFocus={() => setOpen(true)}
        />
        <input
          className="input"
          placeholder="ชื่อโครงการ"
          value={name || ''}
          onChange={(e) => onChangeManual('projectName', e.target.value)}
        />
      </div>
      {open && matches.length > 0 && (
        <div style={{
          position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 100,
          background: 'var(--panel)', border: '1px solid var(--ink-200)', borderRadius: 8,
          marginTop: 4, maxHeight: 280, overflowY: 'auto', boxShadow: 'var(--shadow-md)'
        }}>
          {matches.map((m, i) => (
            <div
              key={m.code + '-' + i}
              onClick={() => { onPick(m); setOpen(false); setQuery(''); }}
              style={{
                padding: '8px 12px', cursor: 'pointer', borderBottom: '1px solid var(--ink-100)',
                fontSize: 13
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--ink-50)'}
              onMouseLeave={(e) => e.currentTarget.style.background = ''}
            >
              <div style={{ fontWeight: 600, color: 'var(--brand-700)' }}>{m.code || '— ไม่มีรหัส —'}</div>
              <div style={{ color: 'var(--ink-700)', fontSize: 12 }}>{m.name || '—'}</div>
            </div>
          ))}
          <div style={{ padding: '6px 12px', fontSize: 11, color: 'var(--ink-500)', background: 'var(--ink-50)' }}>
            หรือพิมพ์เองได้เลย (ไม่จำเป็นต้องเลือกจากลิสต์)
          </div>
        </div>
      )}
    </div>
  );
}

/* ── LeasitLoanForm — เพิ่ม/แก้ไขสัญญาลีซอิท ─────────────────────────── */
function LeasitLoanForm({ open, mode, initial, data, onClose, onSave, onDelete, canEdit }) {
  const Modal = window.Modal;
  const E = window.LeasitEngine || {};
  const [draft, setDraft] = React.useState(null);
  const [preRows, setPreRows] = React.useState([]);
  const [actRows, setActRows] = React.useState([]);
  const [refRows, setRefRows] = React.useState([]);
  const [tab, setTab] = React.useState('master');
  const [loadErr, setLoadErr] = React.useState('');

  // โหลด initial เมื่อเปิด — defensive (กัน window.LeasitEngine ยังไม่โหลด / data ว่าง)
  React.useEffect(() => {
    if (!open) return;
    setLoadErr('');
    try {
      const E = window.LeasitEngine;
      if (!E || typeof E.litBlankLoanDraft !== 'function') {
        setLoadErr('engine ยังไม่พร้อม — ลอง refresh หน้า');
        return;
      }
      if (mode === 'edit' && initial) {
        setDraft({ ...initial });
        const loanId = initial.leasitLoanId;
        setPreRows((data?.interestSchedulePrepaid || []).filter(r => LIT_sameLoan(r.loanId, loanId)).slice().sort((a, b) => (a.seq || 0) - (b.seq || 0)));
        setActRows((data?.interestScheduleActual || []).filter(r => LIT_sameLoan(r.loanId, loanId)).slice().sort((a, b) => (a.seq || 0) - (b.seq || 0)));
        setRefRows((data?.interestRefund || []).filter(r => LIT_sameLoan(r.loanId, loanId)).slice());
      } else {
        setDraft(E.litBlankLoanDraft(data?.debtMaster || []));
        setPreRows([]); setActRows([]); setRefRows([]);
      }
      setTab('master');
    } catch (err) {
      console.error('[Leasit] form init error:', err);
      setLoadErr(String(err && err.message || err));
    }
  }, [open, mode, initial]);

  // ถ้า open แต่ engine error → render กล่อง error แทน
  if (open && loadErr) {
    return (
      <Modal open={true} onClose={onClose} title="⚠️ เปิดฟอร์มไม่ได้" maxWidth={500}>
        <div style={{ padding: 12 }}>
          <div style={{ color: 'var(--bad)', fontWeight: 600, marginBottom: 8 }}>{loadErr}</div>
          <div style={{ fontSize: 12, color: 'var(--ink-700)' }}>
            ลอง <button className="btn btn-ghost" onClick={() => window.location.reload()}>↻ refresh หน้า</button> แล้วลองอีกครั้ง
          </div>
        </div>
      </Modal>
    );
  }

  if (!open || !draft) return null;

  const set = (k, v) => setDraft(d => {
    const next = { ...d, [k]: v };
    // auto-compute termDays
    if (k === 'leasitDateReceived' || k === 'leasitDateDue') {
      next.leasitTermDays = E.litCalcTermDays(
        k === 'leasitDateReceived' ? v : next.leasitDateReceived,
        k === 'leasitDateDue' ? v : next.leasitDateDue
      );
    }
    return next;
  });

  const projects = data?.projects || [];
  const principal = Number(draft.principalAmount) || 0;
  const rate = Number(draft.interestRate) || 0;
  const totPre = preRows.reduce((s, r) => s + (Number(r.intAmount) || 0), 0);
  const totAct = actRows.reduce((s, r) => s + (Number(r.intAmount) || 0), 0);
  // ★ "รวม" ใน refund tab = ดอกเบี้ยรับคืนเท่านั้น (ไม่บวกเงินต้น) ตรงตามที่จะใช้คำนวณ refundOutstanding
  const totRef = refRows.reduce((s, r) => s + (r.kind === 'principal' ? 0 : (Number(r.amount) || 0)), 0);
  const totPrincipalRepayInForm = refRows.reduce((s, r) => s + (r.kind === 'principal' ? (Number(r.amount) || 0) : 0), 0);

  // ── Schedule actions ──
  const generatePrepaid = () => {
    if (!draft.leasitDateReceived || !draft.leasitDateDue || !principal || !rate) {
      alert('กรอก วันรับเงิน · วันครบกำหนด · จำนวนเงินกู้ · อัตราดอกเบี้ย ก่อน');
      return;
    }
    const rows = E.litGenerateMonthlySchedule(principal, rate, draft.leasitDateReceived, draft.leasitDateDue);
    setPreRows(rows);
  };

  const generateActualFromClose = () => {
    if (!draft.leasitDateReceived || !draft.leasitDateRepaid || !principal || !rate) {
      alert('กรอก วันรับเงิน · วันคืนเงิน · จำนวนเงินกู้ · อัตรา ก่อน');
      return;
    }
    const rows = E.litGenerateActualFromClose(principal, rate, draft.leasitDateReceived, draft.leasitDateRepaid);
    setActRows(rows);
  };

  const updateRow = (kind, idx, key, val) => {
    const setter = kind === 'pre' ? setPreRows : kind === 'act' ? setActRows : setRefRows;
    setter(rows => rows.map((r, i) => i === idx ? { ...r, [key]: val } : r));
  };
  const removeRow = (kind, idx) => {
    const setter = kind === 'pre' ? setPreRows : kind === 'act' ? setActRows : setRefRows;
    setter(rows => rows.filter((_, i) => i !== idx));
  };
  const addRefundRow = (kind) => {
    setRefRows(rows => [...rows, { refundDate: '', amount: 0, refDoc: '', refundType: kind, note: '', kind }]);
  };
  const addPrepaidRow = () => {
    setPreRows(rows => [...rows, {
      seq: rows.length + 1, month: '', year: null, dateStart: '', dateEnd: '',
      days: 0, principal: principal, intRate: rate, intAmount: 0, chequeNo: '', paymentDate: ''
    }]);
  };

  const handleSave = () => {
    if (!draft.contractNo.trim()) { alert('กรุณากรอกเลขที่สัญญา'); return; }
    if (!principal) { alert('กรุณากรอกจำนวนเงินกู้'); return; }
    // status = Close ถ้ามีวันคืนเงิน
    const status = draft.leasitDateRepaid ? 'Close' : 'Active';
    const finalDraft = { ...draft, status };
    onSave(finalDraft, preRows, actRows, refRows);
  };

  return (
    <Modal
      open={open}
      onClose={onClose}
      title={mode === 'edit' ? `✏️ แก้ไขสัญญา · ลำดับ ${draft.leasitLoanId}` : '➕ เพิ่มสัญญาลีซอิทใหม่'}
      maxWidth={1300}
    >
      <div style={{ padding: '8px 4px', fontSize: 13 }}>
        {/* Tabs */}
        <div style={{ display: 'flex', gap: 8, marginBottom: 12, borderBottom: '1px solid var(--ink-200)' }}>
          {[
            { k: 'master', l: '📋 ข้อมูลสัญญา' },
            { k: 'prepaid', l: `💰 ดอกล่วงหน้า (${preRows.length})` },
            { k: 'actual', l: `✅ ดอกเกิดจริง (${actRows.length})` },
            { k: 'refund', l: `💸 รับคืน/จ่ายคืน (${refRows.length})` }
          ].map(t => (
            <button
              key={t.k}
              className={tab === t.k ? 'btn btn-primary' : 'btn btn-ghost'}
              onClick={() => setTab(t.k)}
              style={{ borderRadius: '8px 8px 0 0', marginBottom: -1 }}
            >{t.l}</button>
          ))}
        </div>

        {/* Tab: master */}
        {tab === 'master' && (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>ลำดับที่</label>
              <input className="input" value={draft.leasitLoanId} disabled />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>เลขที่สัญญา *</label>
              <input className="input" value={draft.contractNo} onChange={(e) => set('contractNo', e.target.value)} placeholder="LITSM/PBF/68-XXXXX" />
            </div>
            <div style={{ gridColumn: '1/-1' }}>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>โครงการ (พิมพ์เพื่อค้นหา / เลือกจากลิสต์ / กรอกเอง)</label>
              <LeasitProjectAutocomplete
                value={draft.projectCode}
                name={draft.projectName}
                projects={projects}
                onPick={(m) => setDraft(d => ({ ...d, projectCode: m.code, projectName: m.name }))}
                onChangeManual={(k, v) => setDraft(d => ({ ...d, [k]: v }))}
              />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>ประเภทตั๋ว</label>
              <select className="input" value={draft.leasitTicketType} onChange={(e) => set('leasitTicketType', e.target.value)}>
                <option value="PRE">PRE — Pre-financing</option>
                <option value="POS">POS — Post-financing</option>
                <option value="NON">NON — Non-PRE</option>
              </select>
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>จำนวนเงินกู้ (เงินต้น) *</label>
              <input type="number" className="input" value={draft.principalAmount} onChange={(e) => set('principalAmount', Number(e.target.value) || 0)} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>อัตราดอกเบี้ย/ปี (decimal เช่น 0.15)</label>
              <input type="number" step="0.01" className="input" value={draft.interestRate} onChange={(e) => set('interestRate', Number(e.target.value) || 0)} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>วันที่รับเงิน *</label>
              <input type="date" className="input" value={draft.leasitDateReceived || ''} onChange={(e) => set('leasitDateReceived', e.target.value)} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>วันที่ครบกำหนด *</label>
              <input type="date" className="input" value={draft.leasitDateDue || ''} onChange={(e) => set('leasitDateDue', e.target.value)} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>ครบกำหนด (โรลตั๋ว)</label>
              <input type="date" className="input" value={draft.leasitDateDueRoll || ''} onChange={(e) => set('leasitDateDueRoll', e.target.value)} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>ระยะเวลากู้ (วัน · auto)</label>
              <input className="input" value={draft.leasitTermDays || 0} disabled />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>เลขที่เช็คเงินต้น</label>
              <input className="input" value={draft.leasitPrincipalChequeNo || ''} onChange={(e) => set('leasitPrincipalChequeNo', e.target.value)} placeholder="10000081" />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>เลขที่เช็คดอกเบี้ย (รวม — รายงวดในแท็บถัดไป)</label>
              <input className="input" value={draft.leasitInterestChequeNo || ''} onChange={(e) => set('leasitInterestChequeNo', e.target.value)} placeholder="10066733" />
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>สถานะ (auto จากการจ่ายคืน)</label>
              {(() => {
                const totRepaid = refRows.filter(r => r.kind === 'principal').reduce((s, r) => s + (Number(r.amount) || 0), 0);
                const isClosed = totRepaid >= (Number(draft.principalAmount) || 0) && totRepaid > 0;
                return <input className="input" value={isClosed ? 'Close · จ่ายคืนครบ' : 'Active'} disabled />;
              })()}
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>วันที่คืนเงินล่าสุด (auto)</label>
              {(() => {
                const principalRows = refRows.filter(r => r.kind === 'principal');
                const latest = principalRows.reduce((m, r) => r.refundDate && r.refundDate > m ? r.refundDate : m, '');
                return <input className="input" value={LIT_fmtDate(latest) || '— ยังไม่มีการคืน'} disabled />;
              })()}
            </div>

            {/* ★ Multi-row principal repayment (วันที่ / ยอด / ref / หมายเหตุ) */}
            <div style={{ gridColumn: '1/-1', border: '1px solid var(--ink-200)', borderRadius: 8, padding: 10, background: 'oklch(98% 0.02 145)' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
                <div style={{ fontWeight: 700, fontSize: 13 }}>
                  💰 การจ่ายคืนเงินต้น
                  <span style={{ fontSize: 11, color: 'var(--ink-500)', fontWeight: 400, marginLeft: 6 }}>
                    (กดเพิ่ม + ระบุแต่ละครั้งที่คืน)
                  </span>
                </div>
                <button
                  className="btn btn-primary"
                  style={{ padding: '3px 10px', fontSize: 12 }}
                  onClick={() => setRefRows(rows => [...rows, {
                    refundDate: '', amount: 0, refDoc: '', refundType: 'Transfer',
                    note: '', kind: 'principal'
                  }])}
                >+ เพิ่มครั้งที่คืน</button>
              </div>
              {(() => {
                const principalIdxs = refRows.map((r, i) => r.kind === 'principal' ? i : -1).filter(i => i >= 0);
                if (principalIdxs.length === 0) {
                  return <div style={{ padding: 12, fontSize: 12, color: 'var(--ink-500)', textAlign: 'center', fontStyle: 'italic' }}>
                    ยังไม่มีการคืน — กด "+ เพิ่มครั้งที่คืน" ด้านบน
                  </div>;
                }
                const totalRepaid = principalIdxs.reduce((s, i) => s + (Number(refRows[i].amount) || 0), 0);
                return (
                  <div>
                    <table style={{ width: '100%', fontSize: 12, borderCollapse: 'collapse' }}>
                      <thead>
                        <tr style={{ fontSize: 11, color: 'var(--ink-500)' }}>
                          <th style={{ textAlign: 'left', padding: '4px 6px', width: 24 }}>#</th>
                          <th style={{ textAlign: 'left', padding: '4px 6px', width: 140 }}>วันที่คืน *</th>
                          <th style={{ textAlign: 'right', padding: '4px 6px', width: 130 }}>ยอดเงินต้น *</th>
                          <th style={{ textAlign: 'left', padding: '4px 6px', width: 150 }}>เลขที่เอกสาร</th>
                          <th style={{ textAlign: 'left', padding: '4px 6px' }}>หมายเหตุ</th>
                          <th style={{ width: 30 }}></th>
                        </tr>
                      </thead>
                      <tbody>
                        {principalIdxs.map((idx, n) => {
                          const r = refRows[idx];
                          return (
                            <tr key={idx}>
                              <td style={{ padding: 2, color: 'var(--ink-500)' }}>{n + 1}</td>
                              <td style={{ padding: 2 }}>
                                <input type="date" className="input" value={r.refundDate || ''} onChange={(e) => updateRow('ref', idx, 'refundDate', e.target.value)} />
                              </td>
                              <td style={{ padding: 2 }}>
                                {/* ★ ช่องยอดเงิน: พิมพ์ได้ตามปกติ (เดิมเป็น type=number → ลบค่าไม่ได้ ต้องกดลูกศรทีละ 1 บาท
                                     และพิมพ์คอมมาแล้วกลายเป็น 0) · เก็บเป็นข้อความระหว่างพิมพ์ แปลงเป็นตัวเลขตอนบันทึก */}
                                <input type="text" inputMode="decimal" className="input" style={{ textAlign: 'right' }}
                                  placeholder="0"
                                  value={r.amount === '' || r.amount == null ? '' : String(r.amount)}
                                  onChange={(e) => {
                                    const raw = e.target.value.replace(/[^\d.,-]/g, '');
                                    updateRow('ref', idx, 'amount', raw);
                                  }}
                                  onBlur={(e) => {
                                    const n = Number(String(e.target.value).replace(/,/g, ''));
                                    updateRow('ref', idx, 'amount', isFinite(n) ? n : 0);
                                  }} />
                              </td>
                              <td style={{ padding: 2 }}>
                                <input className="input" value={r.refDoc || ''} onChange={(e) => updateRow('ref', idx, 'refDoc', e.target.value)} placeholder="PV/RV/..." />
                              </td>
                              <td style={{ padding: 2 }}>
                                <input className="input" value={r.note || ''} onChange={(e) => updateRow('ref', idx, 'note', e.target.value)} placeholder="—" />
                              </td>
                              <td style={{ padding: 2 }}>
                                <button className="btn btn-icon btn-ghost" onClick={() => removeRow('ref', idx)} title="ลบ">✕</button>
                              </td>
                            </tr>
                          );
                        })}
                      </tbody>
                      <tfoot>
                        <tr style={{ fontWeight: 700, borderTop: '1px solid var(--ink-200)' }}>
                          <td colSpan={2} style={{ textAlign: 'right', padding: '6px' }}>รวมจ่ายคืน {principalIdxs.length} ครั้ง:</td>
                          <td style={{ textAlign: 'right', padding: '6px', color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(totalRepaid, 2)}</td>
                          <td colSpan={3} style={{ padding: '6px', fontSize: 11, color: 'var(--ink-500)' }}>
                            คงเหลือ {LIT_fmt((Number(draft.principalAmount) || 0) - totalRepaid, 2)}
                          </td>
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                );
              })()}
            </div>

            <div style={{ gridColumn: '1/-1' }}>
              <label style={{ fontSize: 11, color: 'var(--ink-500)' }}>หมายเหตุสัญญา</label>
              <textarea className="input" rows={2} value={draft.note || ''} onChange={(e) => set('note', e.target.value)} />
            </div>
          </div>
        )}

        {/* Tab: prepaid */}
        {tab === 'prepaid' && (
          <div>
            <div style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
              <button className="btn btn-primary" onClick={generatePrepaid}>
                ⚡ คำนวณตารางดอกเบี้ยอัตโนมัติ (รายเดือน)
              </button>
              <button className="btn btn-ghost" onClick={addPrepaidRow}>+ เพิ่มงวด</button>
              <span style={{ marginLeft: 'auto', fontSize: 13, fontWeight: 600 }}>
                รวม: {LIT_fmt(totPre, 2)}
              </span>
            </div>
            <div style={{ maxHeight: 380, overflowY: 'auto', border: '1px solid var(--ink-200)', borderRadius: 8 }}>
              <table className="tbl" style={{ width: '100%', fontSize: 12 }}>
                <thead><tr>
                  <th>#</th><th>เริ่ม</th><th>สิ้นสุด</th>
                  <th style={{ textAlign: 'right' }}>เงินต้น</th>
                  <th style={{ textAlign: 'right' }}>อัตรา</th>
                  <th style={{ textAlign: 'right' }}>วัน</th>
                  <th style={{ textAlign: 'right' }}>ดอกเบี้ย</th>
                  <th>เลขเช็ค</th><th>วันจ่าย</th><th></th>
                </tr></thead>
                <tbody>
                  {preRows.map((r, i) => (
                    <tr key={i}>
                      <td>{r.seq || i + 1}</td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.dateStart || ''} onChange={(e) => updateRow('pre', i, 'dateStart', e.target.value)} /></td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.dateEnd || ''} onChange={(e) => updateRow('pre', i, 'dateEnd', e.target.value)} /></td>
                      <td><input type="number" className="input" style={{ textAlign: 'right', width: 110 }} value={r.principal || 0} onChange={(e) => updateRow('pre', i, 'principal', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" step="0.01" className="input" style={{ textAlign: 'right', width: 70 }} value={r.intRate || 0} onChange={(e) => updateRow('pre', i, 'intRate', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" className="input" style={{ textAlign: 'right', width: 60 }} value={r.days || 0} onChange={(e) => updateRow('pre', i, 'days', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" step="0.01" className="input" style={{ textAlign: 'right', width: 100 }} value={r.intAmount || 0} onChange={(e) => updateRow('pre', i, 'intAmount', Number(e.target.value) || 0)} /></td>
                      <td><input className="input" style={{ width: 110 }} value={r.chequeNo || ''} onChange={(e) => updateRow('pre', i, 'chequeNo', e.target.value)} placeholder="10066733" /></td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.paymentDate || ''} onChange={(e) => updateRow('pre', i, 'paymentDate', e.target.value)} /></td>
                      <td><button className="btn btn-icon btn-ghost" onClick={() => removeRow('pre', i)} title="ลบ">✕</button></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div style={{ marginTop: 6, fontSize: 11, color: 'var(--ink-500)' }}>
              💡 สูตรคำนวณ: เงินต้น × อัตรา × วัน / 365 — กดปุ่ม "⚡ คำนวณ" เพื่อสร้างใหม่ทั้งตาราง (split รายเดือน)
            </div>
          </div>
        )}

        {/* Tab: actual */}
        {tab === 'actual' && (
          <div>
            <div style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
              <button className="btn btn-primary" onClick={generateActualFromClose} disabled={!draft.leasitDateRepaid}>
                ⚡ คำนวณจากวันรับ → วันคืนเงิน
              </button>
              <span style={{ marginLeft: 'auto', fontSize: 13, fontWeight: 600 }}>
                รวม: {LIT_fmt(totAct, 2)}
              </span>
            </div>
            {!draft.leasitDateRepaid && (
              <div style={{ padding: 10, background: 'var(--ink-50)', borderRadius: 8, fontSize: 12, color: 'var(--ink-700)', marginBottom: 8 }}>
                ℹ️ ดอกเบี้ยเกิดจริง = คำนวณตอนปิดสัญญา (กรอก "วันที่คืนเงิน" ในแท็บ "ข้อมูลสัญญา" ก่อน)
              </div>
            )}
            <div style={{ maxHeight: 320, overflowY: 'auto', border: '1px solid var(--ink-200)', borderRadius: 8 }}>
              <table className="tbl" style={{ width: '100%', fontSize: 12 }}>
                <thead><tr>
                  <th>#</th><th>เริ่ม</th><th>สิ้นสุด</th>
                  <th style={{ textAlign: 'right' }}>เงินต้น</th>
                  <th style={{ textAlign: 'right' }}>อัตรา</th>
                  <th style={{ textAlign: 'right' }}>วัน</th>
                  <th style={{ textAlign: 'right' }}>ดอกเบี้ย</th>
                  <th></th>
                </tr></thead>
                <tbody>
                  {actRows.map((r, i) => (
                    <tr key={i}>
                      <td>{r.seq || i + 1}</td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.dateStart || ''} onChange={(e) => updateRow('act', i, 'dateStart', e.target.value)} /></td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.dateEnd || ''} onChange={(e) => updateRow('act', i, 'dateEnd', e.target.value)} /></td>
                      <td><input type="number" className="input" style={{ textAlign: 'right', width: 110 }} value={r.principal || 0} onChange={(e) => updateRow('act', i, 'principal', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" step="0.01" className="input" style={{ textAlign: 'right', width: 70 }} value={r.intRate || 0} onChange={(e) => updateRow('act', i, 'intRate', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" className="input" style={{ textAlign: 'right', width: 60 }} value={r.days || 0} onChange={(e) => updateRow('act', i, 'days', Number(e.target.value) || 0)} /></td>
                      <td><input type="number" step="0.01" className="input" style={{ textAlign: 'right', width: 100 }} value={r.intAmount || 0} onChange={(e) => updateRow('act', i, 'intAmount', Number(e.target.value) || 0)} /></td>
                      <td><button className="btn btn-icon btn-ghost" onClick={() => removeRow('act', i)} title="ลบ">✕</button></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        )}

        {/* Tab: refund + principal repayment */}
        {tab === 'refund' && (
          <div>
            <div style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
              <button className="btn btn-ghost" onClick={() => addRefundRow('interest')}>+ รับคืนดอก (RV/โอน)</button>
              <button className="btn btn-ghost" onClick={() => addRefundRow('principal')}>+ จ่ายคืนเงินต้น</button>
              <span style={{ marginLeft: 'auto', fontSize: 12, fontWeight: 600, color: 'var(--ink-600)' }}>
                🧾 ดอกรับคืน: {LIT_fmt(totRef, 2)} · 💰 เงินต้นจ่ายคืน: {LIT_fmt(totPrincipalRepayInForm, 2)}
              </span>
            </div>
            <div style={{ maxHeight: 320, overflowY: 'auto', border: '1px solid var(--ink-200)', borderRadius: 8 }}>
              <table className="tbl" style={{ width: '100%', fontSize: 12 }}>
                <thead><tr>
                  <th>ประเภท</th><th>วันที่</th>
                  <th style={{ textAlign: 'right' }}>จำนวน</th>
                  <th>เอกสารอ้างอิง</th><th>หมายเหตุ</th><th></th>
                </tr></thead>
                <tbody>
                  {refRows.map((r, i) => (
                    <tr key={i}>
                      <td>
                        <select className="input" style={{ width: 130 }} value={r.kind || (r.refundType === 'RV' ? 'interest' : 'interest')} onChange={(e) => updateRow('ref', i, 'kind', e.target.value)}>
                          <option value="interest">🧾 รับคืนดอก</option>
                          <option value="principal">💰 จ่ายคืนเงินต้น</option>
                        </select>
                      </td>
                      <td><input type="date" className="input" style={{ minWidth: 130 }} value={r.refundDate || ''} onChange={(e) => updateRow('ref', i, 'refundDate', e.target.value)} /></td>
                      <td><input type="number" step="0.01" className="input" style={{ textAlign: 'right', width: 120 }} value={r.amount || 0} onChange={(e) => updateRow('ref', i, 'amount', Number(e.target.value) || 0)} /></td>
                      <td><input className="input" style={{ minWidth: 130 }} value={r.refDoc || ''} onChange={(e) => updateRow('ref', i, 'refDoc', e.target.value)} placeholder="RV2603-036 / PV…" /></td>
                      <td><input className="input" value={r.note || ''} onChange={(e) => updateRow('ref', i, 'note', e.target.value)} placeholder="โอนคืน/หักตอน RV/…" /></td>
                      <td><button className="btn btn-icon btn-ghost" onClick={() => removeRow('ref', i)} title="ลบ">✕</button></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div style={{ marginTop: 6, fontSize: 11, color: 'var(--ink-500)' }}>
              💡 รับคืนดอก = ส่วนต่างที่ลีซอิทคืนให้ (RV หักกลบ / โอนคืน) · จ่ายคืนเงินต้น = บันทึกการคืนเงินต้น (PV/ใบจ่าย)
            </div>
          </div>
        )}

        {/* Footer actions */}
        <div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'space-between', borderTop: '1px solid var(--ink-200)', paddingTop: 12 }}>
          <div>
            {mode === 'edit' && canEdit && (
              <button
                className="btn"
                style={{ background: 'var(--bad)', color: '#fff' }}
                onClick={() => { if (confirm(`ลบสัญญา ${draft.contractNo}? · จะลบ schedule prepaid/actual/refund ของสัญญานี้ทั้งหมด`)) onDelete(draft); }}
              >
                🗑️ ลบสัญญา
              </button>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-ghost" onClick={onClose}>ยกเลิก</button>
            <button className="btn btn-primary" onClick={handleSave} disabled={!canEdit}>
              💾 บันทึก
            </button>
          </div>
        </div>
      </div>
    </Modal>
  );
}

/* ── BulkCalcActualModal — คำนวณดอกเกิดจริงทุกสัญญาพร้อมกัน ───────────── */
function BulkCalcActualModal({ open, loans, onClose, onConfirm }) {
  const Modal = window.Modal;
  const today = new Date().toISOString().slice(0, 10);
  const [mode, setMode] = React.useState('today');  // 'today' | 'custom' | 'dueDate'
  const [customDate, setCustomDate] = React.useState(today);
  const [filterStatus, setFilterStatus] = React.useState('active');  // 'active' | 'all'

  if (!open) return null;
  const filtered = loans.filter(L => filterStatus === 'all' || L.status === 'Active');
  const previewCount = filtered.length;

  return (
    <Modal open={open} onClose={onClose} title="⚡ คำนวณดอกเกิดจริงทุกสัญญาพร้อมกัน" maxWidth={600}>
      <div style={{ padding: '4px 4px 12px', fontSize: 13 }}>
        <div style={{ marginBottom: 12, padding: 10, background: 'oklch(96% 0.04 250)', borderRadius: 8, fontSize: 12 }}>
          💡 สำหรับสรุปบัญชีรายเดือน — กดปุ่มเดียวคำนวณดอกเกิดจริงให้ทุกสัญญา. ใช้
          <b> วันรับเงิน → end date </b>(หรือ <b>วันคืนเงิน</b>ถ้าปิดแล้ว) เป็นช่วงคำนวณ.
        </div>

        <div style={{ marginBottom: 14 }}>
          <label style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-700)', display: 'block', marginBottom: 6 }}>
            คำนวณถึงวันที่:
          </label>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
              <input type="radio" name="calcMode" checked={mode === 'today'} onChange={() => setMode('today')} />
              <span>📅 วันนี้ ({LIT_fmtDate(today)})</span>
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
              <input type="radio" name="calcMode" checked={mode === 'custom'} onChange={() => setMode('custom')} />
              <span>🗓️ วันที่กำหนดเอง:</span>
              <input type="date" className="input" style={{ width: 160 }} value={customDate} onChange={(e) => { setCustomDate(e.target.value); setMode('custom'); }} />
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
              <input type="radio" name="calcMode" checked={mode === 'dueDate'} onChange={() => setMode('dueDate')} />
              <span>⏰ ถึงวันครบกำหนดของแต่ละสัญญา (เต็มอายุสัญญา)</span>
            </label>
          </div>
        </div>

        <div style={{ marginBottom: 14 }}>
          <label style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-700)', display: 'block', marginBottom: 6 }}>
            เลือกสัญญา:
          </label>
          <div style={{ display: 'flex', gap: 14 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, cursor: 'pointer' }}>
              <input type="radio" name="filter" checked={filterStatus === 'active'} onChange={() => setFilterStatus('active')} />
              <span>🟢 เฉพาะ Active</span>
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, cursor: 'pointer' }}>
              <input type="radio" name="filter" checked={filterStatus === 'all'} onChange={() => setFilterStatus('all')} />
              <span>🌐 ทั้งหมด (รวม Closed คำนวณใหม่)</span>
            </label>
          </div>
        </div>

        <div style={{ padding: 10, background: 'var(--ink-50)', borderRadius: 8, fontSize: 13, marginBottom: 14 }}>
          จะคำนวณดอกเกิดจริงให้ <b style={{ color: 'var(--brand-500)' }}>{previewCount}</b> สัญญา
        </div>

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button className="btn btn-ghost" onClick={onClose}>ยกเลิก</button>
          <button className="btn btn-primary" onClick={() => {
            const endDate = mode === 'today' ? today : mode === 'custom' ? customDate : null;
            onConfirm({ endDate, mode, loans: filtered });
          }}>⚡ คำนวณ ({previewCount} สัญญา)</button>
        </div>
      </div>
    </Modal>
  );
}

/* ── LeasitExportModal — เลือกสัญญาที่จะ export Excel ──────────────────── */
function LeasitExportModal({ open, loans, onClose, onConfirm }) {
  const Modal = window.Modal;
  const [filterStatus, setFilterStatus] = React.useState('all');
  const [selectedIds, setSelectedIds] = React.useState(new Set());
  const [search, setSearch] = React.useState('');

  React.useEffect(() => {
    if (open) { setSelectedIds(new Set(loans.map(L => L.leasitLoanId))); setSearch(''); }
  }, [open, loans]);

  if (!open) return null;
  const filteredLoans = loans.filter(L => {
    if (filterStatus === 'active' && L.status !== 'Active') return false;
    if (filterStatus === 'closed' && L.status === 'Active') return false;
    if (search.trim()) {
      const q = search.toLowerCase();
      if (!(L.contractNo || '').toLowerCase().includes(q) &&
          !(L.projectName || '').toLowerCase().includes(q) &&
          !(L.projectCode || '').toLowerCase().includes(q) &&
          !String(L.leasitLoanId).includes(q)) return false;
    }
    return true;
  });
  const allFilteredSelected = filteredLoans.every(L => selectedIds.has(L.leasitLoanId));
  const toggleAll = () => {
    setSelectedIds(prev => {
      const next = new Set(prev);
      if (allFilteredSelected) filteredLoans.forEach(L => next.delete(L.leasitLoanId));
      else filteredLoans.forEach(L => next.add(L.leasitLoanId));
      return next;
    });
  };
  const toggle = (lid) => setSelectedIds(prev => {
    const next = new Set(prev);
    if (next.has(lid)) next.delete(lid); else next.add(lid);
    return next;
  });

  return (
    <Modal open={open} onClose={onClose} title="📤 Export ตารางคำนวณดอกเบี้ย (เลือกสัญญา)" maxWidth={900}>
      <div style={{ padding: '4px 4px 12px', fontSize: 13 }}>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', gap: 4 }}>
            <button className={'btn ' + (filterStatus === 'all' ? 'btn-primary' : 'btn-ghost')} style={{ padding: '4px 10px', fontSize: 12 }} onClick={() => setFilterStatus('all')}>ทั้งหมด ({loans.length})</button>
            <button className={'btn ' + (filterStatus === 'active' ? 'btn-primary' : 'btn-ghost')} style={{ padding: '4px 10px', fontSize: 12 }} onClick={() => setFilterStatus('active')}>🟢 Active ({loans.filter(L => L.status === 'Active').length})</button>
            <button className={'btn ' + (filterStatus === 'closed' ? 'btn-primary' : 'btn-ghost')} style={{ padding: '4px 10px', fontSize: 12 }} onClick={() => setFilterStatus('closed')}>⚫ ปิดแล้ว ({loans.filter(L => L.status !== 'Active').length})</button>
          </div>
          <input className="input" placeholder="🔍 ค้นหา…" value={search} onChange={(e) => setSearch(e.target.value)} style={{ flex: 1, minWidth: 200 }} />
        </div>

        <div style={{ marginBottom: 8, fontSize: 12, color: 'var(--ink-500)', display: 'flex', justifyContent: 'space-between' }}>
          <span>เลือกแล้ว <b style={{ color: 'var(--brand-500)' }}>{selectedIds.size}</b> / {loans.length} สัญญา</span>
          <button className="btn btn-ghost" style={{ padding: '2px 8px', fontSize: 11 }} onClick={toggleAll}>
            {allFilteredSelected ? '☐ ไม่เลือก (ที่กรอง)' : '☑ เลือกทั้งหมด (ที่กรอง)'}
          </button>
        </div>

        <div style={{ maxHeight: 360, overflowY: 'auto', border: '1px solid var(--ink-200)', borderRadius: 8 }}>
          <table className="tbl" style={{ width: '100%', fontSize: 12, borderCollapse: 'collapse' }}>
            <thead style={{ position: 'sticky', top: 0, background: 'var(--ink-50)' }}>
              <tr>
                <th style={{ padding: '4px 6px', width: 30 }}></th>
                <th style={{ padding: '4px 6px', textAlign: 'left' }}>#</th>
                <th style={{ padding: '4px 6px', textAlign: 'left' }}>เลขสัญญา</th>
                <th style={{ padding: '4px 6px', textAlign: 'left' }}>JOB</th>
                <th style={{ padding: '4px 6px', textAlign: 'left' }}>โครงการ</th>
                <th style={{ padding: '4px 6px', textAlign: 'right' }}>เงินต้น</th>
                <th style={{ padding: '4px 6px' }}>สถานะ</th>
              </tr>
            </thead>
            <tbody>
              {filteredLoans.map(L => (
                <tr key={L.leasitLoanId} onClick={() => toggle(L.leasitLoanId)} style={{ cursor: 'pointer', borderBottom: '1px solid var(--ink-100)', background: selectedIds.has(L.leasitLoanId) ? 'oklch(97% 0.03 250)' : 'transparent' }}>
                  <td style={{ padding: '4px 6px' }}>
                    <input type="checkbox" checked={selectedIds.has(L.leasitLoanId)} onChange={() => toggle(L.leasitLoanId)} onClick={(e) => e.stopPropagation()} />
                  </td>
                  <td style={{ padding: '4px 6px' }}>{L.leasitLoanId}</td>
                  <td style={{ padding: '4px 6px' }}>{L.contractNo}</td>
                  <td style={{ padding: '4px 6px' }}>{L.projectCode || '—'}</td>
                  <td style={{ padding: '4px 6px', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={L.projectName}>{L.projectName}</td>
                  <td style={{ padding: '4px 6px', textAlign: 'right' }}>{LIT_fmt(L.principalAmount, 0)}</td>
                  <td style={{ padding: '4px 6px' }}>{L.status === 'Active' ? '🟢' : '⚫'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
          <button className="btn btn-ghost" onClick={onClose}>ยกเลิก</button>
          <button className="btn btn-primary" onClick={() => onConfirm(Array.from(selectedIds))} disabled={selectedIds.size === 0}>
            📥 Export {selectedIds.size} สัญญา
          </button>
        </div>
      </div>
    </Modal>
  );
}

/* ── LeasitPanel — main component ─────────────────────────────────────── */
function LeasitPanel({ data, setData, toast, canEdit }) {
  const [showImport, setShowImport] = React.useState(false);
  const [showBulkCalc, setShowBulkCalc] = React.useState(false);
  const [showBulkPrepaid, setShowBulkPrepaid] = React.useState(false);
  const [showWipe, setShowWipe] = React.useState(false);
  const [wipeWord, setWipeWord] = React.useState('');
  const [bulkPrepaidScope, setBulkPrepaidScope] = React.useState('active');
  const [bulkPrepaidOverwrite, setBulkPrepaidOverwrite] = React.useState(true);
  const [showExportSel, setShowExportSel] = React.useState(false);
  const [viewLoan, setViewLoan] = React.useState(null);
  // formState: null = ปิด, { mode: 'new'|'edit', initial: row }
  const [formState, setFormState] = React.useState(null);
  const [deleteTarget, setDeleteTarget] = React.useState(null);   // แถวที่กำลังยืนยันลบ
  const [delWord, setDelWord] = React.useState('');
  const [maturityOpen, setMaturityOpen] = React.useState(false);
  const [page, setPage] = React.useState(1);
  const PAGE_SIZE = 15;
  const [query, setQuery] = React.useState('');
  const [statusFilter, setStatusFilter] = React.useState('all');
  // sort object format ที่ FilterableColHeader คาด: {key, dir}
  const [sort, setSort] = React.useState({ key: 'leasitLoanId', dir: 'asc' });
  // colFilters: { colKey: Set<displayValue> }
  const [colFilters, setColFilters] = React.useState({});
  const [openCol, setOpenCol] = React.useState(null);

  // ── dedupe: leasit rows ที่มี loanId เดียวกัน → เก็บ "ลำดับความสำคัญ" ──
  //   1) prefer id ที่ขึ้น 'lit_' (deterministic จาก deploy ใหม่)
  //   2) prefer มี leasitTotalPrepaid > 0 (data ครบ)
  //   3) prefer updated ล่าสุด (ถ้ามี updated_at)
  // leasitSource เก็บเป็นคอลัมน์ text ใน DB (ตาม convention ของ schema) — ค่าที่เพิ่งสร้าง
  // ในเครื่อง (ก่อน save) เป็น boolean `true` แต่หลังโหลดจาก server กลับมาเป็น string "true"
  // (Postgres text column) → ต้องเช็คทั้ง 2 แบบ ไม่งั้น strict === true จะพลาดหลัง refresh
  // ★ normalize leasitLoanId เป็นตัวเลขตั้งแต่ต้นทาง (คอลัมน์เป็น text → DB คืนเป็น string)
  const allRowsRaw = (data?.debtMaster || [])
    .filter(r => r.leasitSource === true || r.leasitSource === 'true')
    .map(r => (typeof r.leasitLoanId === 'string' ? { ...r, leasitLoanId: LIT_lid(r.leasitLoanId) } : r));
  const dupCount = React.useMemo(() => {
    const cnt = {};
    allRowsRaw.forEach(r => { const k = r.leasitLoanId; cnt[k] = (cnt[k] || 0) + 1; });
    let dup = 0;
    Object.values(cnt).forEach(n => { if (n > 1) dup += (n - 1); });
    return dup;
  }, [allRowsRaw]);
  const allRows = React.useMemo(() => {
    const byId = new Map();
    allRowsRaw.forEach(r => {
      const k = r.leasitLoanId;
      if (k == null) return;
      const prev = byId.get(k);
      if (!prev) { byId.set(k, r); return; }
      // เก็บแถวที่ "ดีกว่า"
      const score = (x) => (String(x.id || '').startsWith('lit_') ? 4 : 0)
                        + ((Number(x.leasitTotalPrepaid) || 0) > 0 ? 2 : 0)
                        + (x.leasitDateRepaid ? 1 : 0);
      if (score(r) > score(prev)) byId.set(k, r);
    });
    return Array.from(byId.values());
  }, [allRowsRaw]);
  const prepaid = data?.interestSchedulePrepaid || [];
  const actual = data?.interestScheduleActual || [];
  const refund = data?.interestRefund || [];
  /* ★ เติมข้อมูลจ่ายคืนเงินต้น (จ่ายได้หลายครั้ง) + ระยะเวลาสัญญา เข้าแต่ละแถว
     ใช้ทั้งคอลัมน์ใหม่ในตาราง · ตัวกรอง · และการค้นหาทุกช่อง */
  const rowsWithRepay = React.useMemo(() => {
    const byLoan = {};
    (refund || []).forEach(x => {
      if (x.kind !== 'principal' || !x.refundDate) return;
      (byLoan[x.loanId] = byLoan[x.loanId] || []).push({
        date: x.refundDate, amount: Number(x.amount) || 0,
        ref: x.docNo || x.refNo || x.reference || x.note || '',
      });
    });
    Object.values(byLoan).forEach(a => a.sort((p, q) => (p.date || '').localeCompare(q.date || '')));
    const dayDiff = (a, b) => {
      if (!a || !b) return null;
      const d = (new Date(b + 'T00:00:00') - new Date(a + 'T00:00:00')) / 86400000;
      return isFinite(d) ? Math.round(d) : null;
    };
    return allRows.map(r => {
      const list = byLoan[r.leasitLoanId] || [];
      return {
        ...r,
        _repayList: list,
        _repayDates: list.map(x => LIT_fmtDate(x.date)).join(', '),
        _repayAmounts: list.map(x => LIT_fmt(x.amount, 0)).join(', '),
        _repayRefs: list.map(x => x.ref).filter(Boolean).join(', '),
        _termDays: dayDiff(r.leasitDateReceived, r.leasitDateDue),
      };
    });
  }, [allRows, refund]);

  // ── ผูก JOB → โครงการ เพื่อโชว์ "ผู้รับโอนสิทธิ" ของโครงการ (read-only) ──
  //   ใช้ projectByCode ตัวเดียวกับที่หน้าใบแจ้งหนี้/รายวันใช้ (WTPData.buildLookups)
  const projectByCode = React.useMemo(
    () => (window.WTPData && WTPData.buildLookups ? (WTPData.buildLookups(data).projectByCode || {}) : {}),
    [data.projects]
  );
  //   ทุกแถวในตารางนี้คือสัญญา Leasit → ถ้าโครงการยังไม่ระบุผู้รับโอนสิทธิ ให้ default 'LIT' (auto-derive)
  const projectAssignee = React.useCallback((code) => {
    const raw = String(code || '').trim();
    if (!raw) return 'LIT';
    const m = raw.match(/^(.+)-([A-Z]{2,6})$/);              // ตัด suffix -XXX แบบเดียวกับ buildLookups
    const clean = m ? m[1] : raw;
    const p = projectByCode[raw] || projectByCode[raw.toUpperCase()]
           || projectByCode[clean] || projectByCode[clean.toUpperCase()];
    const a = p && (p['ผู้รับโอนสิทธิ์'] || p.assignee);
    return (a && a !== '—' && a !== '-') ? String(a).trim() : 'LIT';   // โครงการระบุค่าไว้ชนะ · ว่าง → LIT
  }, [projectByCode]);

  // ── คำสั่ง cleanup duplicates: ลบจริงจาก Supabase ──
  const handleCleanupDuplicates = async () => {
    if (!dupCount) return;
    const groupMap = new Map();
    allRowsRaw.forEach(r => {
      const k = r.leasitLoanId; if (k == null) return;
      if (!groupMap.has(k)) groupMap.set(k, []);
      groupMap.get(k).push(r);
    });
    const toDelete = [];
    groupMap.forEach((rows, k) => {
      if (rows.length <= 1) return;
      const winner = allRows.find(w => LIT_sameLoan(w.leasitLoanId, k));
      rows.forEach(r => { if (r.id !== winner.id) toDelete.push(r.id); });
    });
    if (!toDelete.length) return;
    if (!confirm(`ลบ debtMaster ซ้ำ ${toDelete.length} แถว? (คงเหลือ ${allRows.length} สัญญา)`)) return;
    try {
      if (window.WTPData && window.WTPData.forceDeleteRows) {
        await window.WTPData.forceDeleteRows('debtMaster', toDelete);
      }
      setData(d => ({ ...d, debtMaster: (d.debtMaster || []).filter(r => !toDelete.includes(r.id)) }));
      if (toast) toast(`ลบสัญญาซ้ำ ${toDelete.length} แถวแล้ว`, 'success');
    } catch (e) {
      if (toast) toast('ลบไม่สำเร็จ: ' + (e.message || e), 'error');
    }
  };

  // Filtered
  const filtered = React.useMemo(() => {
    let rows = rowsWithRepay;
    if (statusFilter !== 'all') rows = rows.filter(r => r.status === statusFilter);
    if (query.trim()) {
      const q = query.toLowerCase();
      /* ★ ค้นหาครอบทุกช่องในแถว — รวมวันที่ ยอดเงิน สถานะ ประเภท และเลขเอกสารจ่ายคืนแต่ละครั้ง
         (เดิมค้นได้แค่ เลขสัญญา / ชื่อโครงการ / JOB / ลำดับ) */
      rows = rows.filter(r =>
        Object.keys(r).some(k => {
          if (k.startsWith('_')) return false;
          const v = r[k];
          if (v == null || typeof v === 'object') return false;
          return String(v).toLowerCase().includes(q);
        }) ||
        (r._repayList || []).some(x =>
          String(x.date || '').includes(q) ||
          String(x.ref || '').toLowerCase().includes(q) ||
          String(x.amount || '').includes(q)) ||
        (r.contractNo || '').toLowerCase().includes(q) ||
        (r.projectName || '').toLowerCase().includes(q) ||
        (r.projectCode || '').toLowerCase().includes(q) ||
        String(r.leasitLoanId || '').includes(q)
      );
    }
    // ── apply column filters (Excel-style) ──
    for (const [key, vals] of Object.entries(colFilters)) {
      if (vals && vals.size > 0) {
        rows = rows.filter(r => vals.has(getColDisplayValue(r, key)));
      }
    }
    // ── dynamic sort ──
    // helper: เงินต้นจ่ายคืน + คงเหลือ (computed)
    const litRepaid = (r) => r.leasitPrincipalRepaid != null
      ? (Number(r.leasitPrincipalRepaid) || 0)
      : (r.status === 'Close' ? (Number(r.principalAmount) || 0) : 0);
    const getSortVal = (r, key) => {
      switch (key) {
        case 'leasitLoanId': return Number(r.leasitLoanId) || 0;
        case 'principalAmount': return Number(r.principalAmount) || 0;
        case 'principalRepaid': return litRepaid(r);
        case 'principalOutstanding': return (Number(r.principalAmount) || 0) - litRepaid(r);
        case 'interestRate': return Number(r.interestRate) || 0;
        case 'leasitDateReceived': return String(r.leasitDateReceived || '');
        case 'leasitDateDue': return String(r.leasitDateDue || '');
        case 'leasitRefundOutstanding': return Number(r.leasitRefundOutstanding) || 0;
        default: return String(r[sort.key] || '');
      }
    };
    const dir = sort.dir === 'desc' ? -1 : 1;
    return rows.slice().sort((a, b) => {
      const va = getSortVal(a, sort.key);
      const vb = getSortVal(b, sort.key);
      if (typeof va === 'number') return (va - vb) * dir;
      return va.localeCompare(vb, 'th') * dir;
    });
  }, [rowsWithRepay, query, statusFilter, sort, colFilters]);

  // ── display value ต่อคอลัมน์ (ใช้ใน column filter dropdown) ──
  function getColDisplayValue(r, key) {
    switch (key) {
      case 'leasitLoanId': return String(r.leasitLoanId || '');
      case 'leasitTicketType': return String(r.leasitTicketType || '').toUpperCase() || 'POS';
      case 'contractNo': return r.contractNo || '—';
      case 'projectCode': return r.projectCode || '—';
      case 'projectName': return r.projectName || '—';
      case 'principalAmount': return LIT_fmt(r.principalAmount, 0);
      case 'principalRepaid': {
        const v = r.leasitPrincipalRepaid != null ? Number(r.leasitPrincipalRepaid) : (r.status === 'Close' ? Number(r.principalAmount) : 0);
        return LIT_fmt(v || 0, 0);
      }
      case 'principalOutstanding': {
        const rep = r.leasitPrincipalRepaid != null ? Number(r.leasitPrincipalRepaid) : (r.status === 'Close' ? Number(r.principalAmount) : 0);
        return LIT_fmt((Number(r.principalAmount) || 0) - (rep || 0), 0);
      }
      case 'interestRate': return (Number(r.interestRate) * 100).toFixed(2) + '%';
      case 'leasitDateReceived': return LIT_fmtDate(r.leasitDateReceived) || '—';
      case 'leasitDateDue': return LIT_fmtDate(r.leasitDateDue) || '—';
      case 'status': return r.status === 'Active' ? '🟢 Active' : '⚫ ปิดแล้ว';
      case 'leasitTotalPrepaid': return LIT_fmt(r.leasitTotalPrepaid, 2);
      case 'leasitTotalActual': return LIT_fmt(r.leasitTotalActual, 2);
      case 'leasitVariance': return LIT_fmt(r.leasitVariance, 2);
      case 'leasitRefunded': return LIT_fmt(r.leasitRefunded, 2);
      case 'leasitRefundOutstanding': return LIT_fmt(r.leasitRefundOutstanding, 2);
      default: return String(r[key] || '—');
    }
  }
  // sort-value ที่แท้จริง (ISO date/raw number) สำหรับ dropdown ที่ต้องเรียงตามค่าจริง
  function getColSortValue(r, key) {
    const rep = r.leasitPrincipalRepaid != null ? Number(r.leasitPrincipalRepaid) : (r.status === 'Close' ? Number(r.principalAmount) : 0);
    switch (key) {
      case 'leasitLoanId': return Number(r.leasitLoanId) || 0;
      case 'principalAmount': return Number(r.principalAmount) || 0;
      case 'principalRepaid': return rep || 0;
      case 'principalOutstanding': return (Number(r.principalAmount) || 0) - (rep || 0);
      case 'interestRate': return Number(r.interestRate) || 0;
      case 'leasitDateReceived': return r.leasitDateReceived || '';
      case 'leasitDateDue': return r.leasitDateDue || '';
      case 'leasitRefundOutstanding': return Number(r.leasitRefundOutstanding) || 0;
      default: return undefined;
    }
  }

  const sortToggle = (key) => {
    setSort(s => s.key === key ? { key, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' });
  };

  // KPIs
  const totPre = allRows.reduce((s, r) => s + (Number(r.leasitTotalPrepaid) || 0), 0);
  const totAct = allRows.reduce((s, r) => s + (Number(r.leasitTotalActual) || 0), 0);
  const totVar = allRows.reduce((s, r) => s + (Number(r.leasitVariance) || 0), 0);
  const totRef = allRows.reduce((s, r) => s + (Number(r.leasitRefunded) || 0), 0);
  const totOut = allRows.reduce((s, r) => s + (Number(r.leasitRefundOutstanding) || 0), 0);
  const activeCnt = allRows.filter(r => r.status === 'Active').length;
  const closedCnt = allRows.filter(r => r.status !== 'Active').length;

  // Import handler — upsert debtMaster + replace 3 child tables for imported loanIds
  const handleImport = (parsed) => {
    if (!parsed || !parsed.loans.length) return;
    const litLoanIds = new Set(parsed.loans.map(L => LIT_lid(L.loanId)));
    let updated;
    setData(d => {
      // 1) debtMaster — keep non-leasit + replace leasit rows that overlap
      const existing = d.debtMaster || [];
      const keepDm = existing.filter(r => !r.leasitSource || !litLoanIds.has(LIT_lid(r.leasitLoanId)));
      const newDm = parsed.loans.map(L => window.LeasitEngine.litLoanToDebtRow(L));
      const nextDm = keepDm.concat(newDm);

      const keepPre = (d.interestSchedulePrepaid || []).filter(r => !litLoanIds.has(LIT_lid(r.loanId)));
      const newPre = parsed.prepaid.map((p, i) => ({ id: litRowId(p.loanId, 'pre', p.seq || i + 1), ...p }));
      const keepAct = (d.interestScheduleActual || []).filter(r => !litLoanIds.has(LIT_lid(r.loanId)));
      const newAct = parsed.actual.map((p, i) => ({ id: litRowId(p.loanId, 'act', p.seq || i + 1), ...p }));
      const keepRef = (d.interestRefund || []).filter(r => !litLoanIds.has(LIT_lid(r.loanId)));
      const newRef = parsed.refund.map((p, i) => ({ id: litRowId(p.loanId, 'ref', i + 1), ...p }));

      updated = {
        ...d,
        debtMaster: nextDm,
        interestSchedulePrepaid: keepPre.concat(newPre),
        interestScheduleActual: keepAct.concat(newAct),
        interestRefund: keepRef.concat(newRef)
      };
      return updated;
    });
    // ★ ต้องส่ง updated เข้า forceSyncNow ไม่งั้นใช้ cachedData เก่า → push delete แถวที่เพิ่งสร้าง
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    setShowImport(false);
    if (toast) {
      const nRepay = parsed.refund.filter(r => r.kind === 'principal').length;
      toast(`นำเข้าสำเร็จ · ${parsed.loans.length} สัญญา · จ่ายคืนเงินต้น ${nRepay} งวด`, 'success');
    }
  };

  // Map debtMaster row → loan shape ที่ engine ใช้ (shared helper)
  const dmToLoan = (r) => ({
    loanId: r.leasitLoanId, contractNo: r.contractNo, jobNo: r.projectCode,
    projectName: r.projectName, ticketType: r.leasitTicketType, loanType: r.loanType,
    interestRate: r.interestRate, principal: r.principalAmount,
    dateReceived: r.leasitDateReceived, dateDue: r.leasitDateDue,
    dateDueRoll: r.leasitDateDueRoll, termDays: r.leasitTermDays,
    principalChequeNo: r.leasitPrincipalChequeNo, dateRepaid: r.leasitDateRepaid,
    status: r.status, totalPrepaidInterest: r.leasitTotalPrepaid,
    totalActualInterest: r.leasitTotalActual, variance: r.leasitVariance,
    totalRefunded: r.leasitRefunded, refundOutstanding: r.leasitRefundOutstanding
  });

  // Export loans → workbook (รับ loanIds optional)
  const handleExportSelected = (loanIds) => {
    const XLSX = window.XLSX;
    if (!XLSX) return;
    const ids = loanIds && loanIds.length ? new Set(loanIds.map(LIT_lid)) : null;
    const selectedRows = ids ? allRows.filter(r => ids.has(LIT_lid(r.leasitLoanId))) : allRows;
    if (!selectedRows.length) { if (toast) toast('ไม่มีสัญญาที่จะ export', 'error'); return; }
    const loans = selectedRows.map(dmToLoan);
    const wb = window.LeasitEngine.litBuildExportWorkbook(loans, prepaid, actual, refund);
    if (!wb) return;
    const now = new Date();
    const stamp = `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()}`;
    XLSX.writeFile(wb, `WTP-ตารางคำนวณดอกเบี้ยลีซอิท ${stamp}.xlsx`);
    if (toast) toast(`ส่งออก Excel สำเร็จ · ${loans.length} สัญญา`, 'success');
    setShowExportSel(false);
  };
  const handleExportAll = () => setShowExportSel(true);

  // ── BULK calc actual interest — ทุกสัญญาพร้อมกัน ──
  const handleBulkCalcActual = ({ endDate, mode, loans: loansToCalc }) => {
    const E = window.LeasitEngine;
    if (!E || !E.litGenerateMonthlySchedule) return;
    const allRefunds = data?.interestRefund || [];
    let updated;
    let okCount = 0;
    let declineCount = 0;   // ★ ใช้ยอดคงเหลือจริงรายงวด (มีข้อมูลจ่ายคืน)
    let flatCount = 0;      // ★ ไม่มีข้อมูลจ่ายคืน → คิดจากเงินต้นเต็มตลอด
    setData(d => {
      declineCount = 0; flatCount = 0;
      let newActual = (d.interestScheduleActual || []).slice();
      let newMaster = (d.debtMaster || []).slice();
      loansToCalc.forEach(L => {
        const r = newMaster.find(m => LIT_sameLoan(m.leasitLoanId, L.leasitLoanId));
        if (!r) return;
        const principal = Number(r.principalAmount) || 0;
        const rate = Number(r.interestRate) || 0;
        const startISO = r.leasitDateReceived || '';
        // ★ ดึง principal repayments — ใช้คำนวณ declining + หา endDate
        const reps = allRefunds
          .filter(x => LIT_sameLoan(x.loanId, L.leasitLoanId) && x.kind === 'principal' && x.refundDate)
          .map(x => ({ date: x.refundDate, amount: Number(x.amount) || 0 }))
          .sort((a, b) => a.date.localeCompare(b.date));
        const latestRepay = reps.length ? reps[reps.length - 1].date : '';
        // endDate: ถ้ามี dateRepaid + latestRepay → ใช้ max ของทั้งสอง
        //          ถ้าไม่มี → ใช้ตามที่ user เลือก
        const closedEnd = r.leasitDateRepaid && r.leasitDateRepaid > latestRepay
          ? r.leasitDateRepaid : latestRepay;
        const userEnd = mode === 'dueDate' ? (r.leasitDateDue || '') : (endDate || '');
        const finalEnd = closedEnd || userEnd;
        if (!principal || !rate || !startISO || !finalEnd) return;
        const useDeclining = reps.length > 0 && !!E.litGenerateActualDeclining;
        const rows = useDeclining
          ? E.litGenerateActualDeclining(principal, rate, startISO, finalEnd, reps)
          : E.litGenerateMonthlySchedule(principal, rate, startISO, finalEnd);
        if (!rows.length) return;
        if (useDeclining) declineCount++; else flatCount++;
        const total = rows.reduce((s, x) => s + (Number(x.intAmount) || 0), 0);
        const lid = L.leasitLoanId;
        newActual = newActual.filter(x => !LIT_sameLoan(x.loanId, lid)).concat(
          rows.map((p, i) => ({ id: litRowId(lid, 'act', p.seq || i + 1), loanId: lid, ...p }))
        );
        // update master totals
        newMaster = newMaster.map(m => LIT_sameLoan(m.leasitLoanId, lid) ? {
          ...m,
          leasitTotalActual: total,
          leasitVariance: (Number(m.leasitTotalPrepaid) || 0) - total,
          leasitRefundOutstanding: (Number(m.leasitTotalPrepaid) || 0) - total - (Number(m.leasitRefunded) || 0)
        } : m);
        okCount++;
      });
      updated = { ...d, interestScheduleActual: newActual, debtMaster: newMaster };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    setShowBulkCalc(false);
    if (toast) {
      // ★ บอกให้เห็นว่าสัญญาไหนคิดจาก "ยอดคงเหลือจริงรายงวด" และสัญญาไหนยังไม่มีข้อมูลจ่ายคืน
      toast(`คำนวณดอกเกิดจริง ${okCount}/${loansToCalc.length} สัญญา · `
        + `หักเงินต้นตามงวดที่จ่ายจริง ${declineCount} · ยังไม่มีข้อมูลจ่ายคืน ${flatCount}`,
        flatCount ? 'warning' : 'success');
    }
  };

  // Export single loan
  const handleExportOne = (loan) => {
    const XLSX = window.XLSX;
    if (!XLSX) return;
    const single = [{
      loanId: loan.leasitLoanId,
      contractNo: loan.contractNo,
      jobNo: loan.projectCode,
      projectName: loan.projectName,
      ticketType: loan.leasitTicketType,
      loanType: loan.loanType,
      interestRate: loan.interestRate,
      principal: loan.principalAmount,
      dateReceived: loan.leasitDateReceived,
      dateDue: loan.leasitDateDue,
      dateDueRoll: loan.leasitDateDueRoll,
      termDays: loan.leasitTermDays,
      principalChequeNo: loan.leasitPrincipalChequeNo,
      dateRepaid: loan.leasitDateRepaid,
      status: loan.status,
      totalPrepaidInterest: loan.leasitTotalPrepaid,
      totalActualInterest: loan.leasitTotalActual,
      variance: loan.leasitVariance,
      totalRefunded: loan.leasitRefunded,
      refundOutstanding: loan.leasitRefundOutstanding
    }];
    const wb = window.LeasitEngine.litBuildExportWorkbook(single, prepaid, actual, refund);
    if (!wb) return;
    XLSX.writeFile(wb, `WTP-ลีซอิท-${loan.leasitLoanId}-${loan.contractNo.replace(/[\/\\]/g, '_')}.xlsx`);
  };

  // ── Save form (สร้างใหม่/แก้ไข) — upsert debtMaster + replace 3 child tables ──
  const handleSaveForm = (draft, preRowsArr, actRowsArr, refRowsArr) => {
    const loanId = draft.leasitLoanId;
    const E = window.LeasitEngine;
    // recompute totals
    const totals = {
      totalPrepaidInterest: preRowsArr.reduce((s, r) => s + (Number(r.intAmount) || 0), 0),
      totalActualInterest: actRowsArr.reduce((s, r) => s + (Number(r.intAmount) || 0), 0),
      totalRefunded: refRowsArr.filter(r => (r.kind || 'interest') === 'interest').reduce((s, r) => s + (Number(r.amount) || 0), 0)
    };
    totals.variance = totals.totalPrepaidInterest - totals.totalActualInterest;
    totals.refundOutstanding = totals.variance - totals.totalRefunded;
    const principalRows = refRowsArr.filter(r => r.kind === 'principal');
    const principalRepaid = principalRows.reduce((s, r) => s + (Number(r.amount) || 0), 0);
    // ★ status + dateRepaid จาก multi-row: ปิดเมื่อ Σ ≥ principal (>0) — dateRepaid = ล่าสุด
    const principalAmount = Number(draft.principalAmount) || 0;
    const isClosed = principalRepaid > 0 && principalRepaid >= principalAmount - 0.01;
    const latestRepayDate = principalRows.reduce((m, r) => r.refundDate && r.refundDate > m ? r.refundDate : m, '');
    const computedDateRepaid = isClosed ? (latestRepayDate || draft.leasitDateRepaid || '') : '';
    let updated;
    setData(d => {
      const existing = d.debtMaster || [];
      // build/update row
      const rowId = 'lit_' + loanId;
      const baseFields = {
        id: rowId,
        debtCategory: 'LIT',
        contractNo: draft.contractNo,
        borrowerName: 'บจก. ลีซอิท',
        status: isClosed ? 'Close' : 'Active',
        facilityType: 'Loan',
        loanType: draft.leasitTicketType === 'POS' || draft.leasitTicketType === 'NON' ? 'NON' : 'PRE',
        principalAmount: principalAmount,
        interestRate: Number(draft.interestRate) || 0,
        balance: isClosed ? 0 : (principalAmount - principalRepaid),
        currency: 'THB',
        projectCode: draft.projectCode || '',
        projectName: draft.projectName || '',
        note: draft.note || '',
        leasitSource: true,
        leasitLoanId: loanId,
        leasitTicketType: draft.leasitTicketType,
        leasitDateReceived: draft.leasitDateReceived || '',
        leasitDateDue: draft.leasitDateDue || '',
        leasitDateDueRoll: draft.leasitDateDueRoll || '',
        leasitDateRepaid: computedDateRepaid,
        leasitPrincipalRepaid: principalRepaid,
        leasitTermDays: draft.leasitTermDays || E.litCalcTermDays(draft.leasitDateReceived, draft.leasitDateDue),
        leasitPrincipalChequeNo: draft.leasitPrincipalChequeNo || '',
        leasitInterestChequeNo: draft.leasitInterestChequeNo || '',
        leasitTotalPrepaid: totals.totalPrepaidInterest,
        leasitTotalActual: totals.totalActualInterest,
        leasitVariance: totals.variance,
        leasitRefunded: totals.totalRefunded,
        leasitRefundOutstanding: totals.refundOutstanding,
        leasitPrincipalRepaid: principalRepaid,
        maturityDate: draft.leasitDateDueRoll || draft.leasitDateDue || ''
      };
      const idx = existing.findIndex(r => r.id === rowId);
      const nextDm = idx >= 0
        ? existing.map((r, i) => i === idx ? { ...r, ...baseFields } : r)
        : existing.concat([baseFields]);

      // replace child tables for this loanId
      const keepPre = (d.interestSchedulePrepaid || []).filter(r => !LIT_sameLoan(r.loanId, loanId));
      const newPre = preRowsArr.map((p, i) => ({
        id: litRowId(loanId, 'pre', p.seq || i + 1),
        loanId,
        ...p
      }));
      const keepAct = (d.interestScheduleActual || []).filter(r => !LIT_sameLoan(r.loanId, loanId));
      const newAct = actRowsArr.map((p, i) => ({
        id: litRowId(loanId, 'act', p.seq || i + 1),
        loanId,
        ...p
      }));
      const keepRef = (d.interestRefund || []).filter(r => !LIT_sameLoan(r.loanId, loanId));
      const newRef = refRowsArr.map((p, i) => ({
        id: litRowId(loanId, 'ref', i + 1),
        loanId,
        kind: p.kind || 'interest',
        ...p
      }));

      updated = {
        ...d,
        debtMaster: nextDm,
        interestSchedulePrepaid: keepPre.concat(newPre),
        interestScheduleActual: keepAct.concat(newAct),
        interestRefund: keepRef.concat(newRef)
      };
      return updated;
    });
    // ★ ส่ง updated เข้า forceSyncNow (กัน race vs cachedData เก่า)
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    setFormState(null);
    if (toast) toast(`บันทึก ${draft.contractNo} สำเร็จ`, 'success');
  };

  // ── คำนวณดอกล่วงหน้าอัตโนมัติ (inline จาก drawer) ──
  const handleCalcPrepaid = (loanRow) => {
    const E = window.LeasitEngine;
    if (!E || !E.litGenerateMonthlySchedule) return;
    const principal = Number(loanRow.principalAmount) || 0;
    const rate = Number(loanRow.interestRate) || 0;
    const startISO = loanRow.leasitDateReceived || '';
    const endISO = loanRow.leasitDateDue || '';
    if (!principal || !rate || !startISO || !endISO) {
      if (toast) toast('กรอก เงินต้น/อัตรา/วันรับ/วันครบ ใน "แก้ไข" ก่อน', 'error');
      return;
    }
    const rows = E.litGenerateMonthlySchedule(principal, rate, startISO, endISO);
    const loanId = loanRow.leasitLoanId;
    const total = rows.reduce((s, r) => s + (Number(r.intAmount) || 0), 0);
    let updated;
    setData(d => {
      const keep = (d.interestSchedulePrepaid || []).filter(r => !LIT_sameLoan(r.loanId, loanId));
      const fresh = rows.map((p, i) => ({ id: litRowId(loanId, 'pre', p.seq || i + 1), loanId, ...p }));
      const dm = (d.debtMaster || []).map(r => LIT_sameLoan(r.leasitLoanId, loanId) ? {
        ...r,
        leasitTotalPrepaid: total,
        leasitVariance: total - (Number(r.leasitTotalActual) || 0),
        leasitRefundOutstanding: total - (Number(r.leasitTotalActual) || 0) - (Number(r.leasitRefunded) || 0)
      } : r);
      updated = { ...d, interestSchedulePrepaid: keep.concat(fresh), debtMaster: dm };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    if (toast) toast(`คำนวณดอกล่วงหน้า ${rows.length} งวด · รวม ${total.toLocaleString('th-TH', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`, 'success');
  };

  /* ── คำนวณดอกจ่ายล่วงหน้า "ทุกสัญญา" พร้อมกัน ──
     ★ ใช้กติกาเดียวกับรายสัญญาเป๊ะ: ยึด วันรับเงิน (เริ่มต้น) → วันสิ้นสุดสัญญา
       ผ่าน litGenerateMonthlySchedule ตัวเดียวกัน (ไม่คิดสูตรใหม่)
       สัญญาที่ข้อมูลไม่ครบ (ไม่มีเงินต้น/อัตรา/วันเริ่ม/วันสิ้นสุด) จะถูกข้ามและรายงานจำนวนกลับ */
  const handleBulkCalcPrepaid = ({ loans: loansToCalc, overwrite }) => {
    const E = window.LeasitEngine;
    if (!E || !E.litGenerateMonthlySchedule) return;
    let ok = 0, skipped = 0, updated;
    setData(d => {
      let newPre = (d.interestSchedulePrepaid || []).slice();
      let newMaster = (d.debtMaster || []).slice();
      loansToCalc.forEach(L => {
        const r = newMaster.find(m => LIT_sameLoan(m.leasitLoanId, L.leasitLoanId));
        if (!r) { skipped++; return; }
        const principal = Number(r.principalAmount) || 0;
        const rate = Number(r.interestRate) || 0;
        const startISO = r.leasitDateReceived || '';
        const endISO = r.leasitDateDue || '';
        if (!principal || !rate || !startISO || !endISO) { skipped++; return; }
        const loanId = L.leasitLoanId;
        const had = newPre.some(x => LIT_sameLoan(x.loanId, loanId));
        if (had && !overwrite) { skipped++; return; }
        const rows = E.litGenerateMonthlySchedule(principal, rate, startISO, endISO);
        if (!rows || !rows.length) { skipped++; return; }
        const total = rows.reduce((sum, x) => sum + (Number(x.intAmount) || 0), 0);
        newPre = newPre.filter(x => !LIT_sameLoan(x.loanId, loanId))
          .concat(rows.map((pp, i) => ({ id: litRowId(loanId, 'pre', pp.seq || i + 1), loanId, ...pp })));
        newMaster = newMaster.map(m => LIT_sameLoan(m.leasitLoanId, loanId) ? {
          ...m,
          leasitTotalPrepaid: total,
          leasitVariance: total - (Number(m.leasitTotalActual) || 0),
          leasitRefundOutstanding: total - (Number(m.leasitTotalActual) || 0) - (Number(m.leasitRefunded) || 0)
        } : m);
        ok++;
      });
      updated = { ...d, interestSchedulePrepaid: newPre, debtMaster: newMaster };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    setShowBulkPrepaid(false);
    if (toast) toast(`คำนวณดอกจ่ายล่วงหน้า ${ok} สัญญา` + (skipped ? ` · ข้าม ${skipped} (ข้อมูลไม่ครบ/มีอยู่แล้ว)` : ''), 'success');
  };

  // ── คำนวณดอกเกิดจริงอัตโนมัติ (inline จาก drawer) ──
  const handleCalcActual = (loanRow) => {
    const E = window.LeasitEngine;
    if (!E || !E.litGenerateMonthlySchedule) return;
    const principal = Number(loanRow.principalAmount) || 0;
    const rate = Number(loanRow.interestRate) || 0;
    const startISO = loanRow.leasitDateReceived || '';
    const todayISO = new Date().toISOString().slice(0, 10);
    const loanId = loanRow.leasitLoanId;
    // ★ ดึง principal repayments จาก data → คำนวณ declining
    const reps = (data?.interestRefund || [])
      .filter(r => LIT_sameLoan(r.loanId, loanId) && r.kind === 'principal' && r.refundDate)
      .map(r => ({ date: r.refundDate, amount: Number(r.amount) || 0 }))
      .sort((a, b) => a.date.localeCompare(b.date));
    // ถ้ามี repayments → endISO = วันสุดท้ายของ repayment + วันคืนเงินที่ผู้ใช้กรอก
    //                 ถ้าไม่มี repayments → ใช้ dateRepaid หรือ today
    const latestRepay = reps.length ? reps[reps.length - 1].date : '';
    const endISO = (loanRow.leasitDateRepaid && loanRow.leasitDateRepaid > latestRepay)
      ? loanRow.leasitDateRepaid : (latestRepay || todayISO);
    if (!principal || !rate || !startISO) {
      if (toast) toast('กรอก เงินต้น/อัตรา/วันรับ ใน "แก้ไข" ก่อน', 'error');
      return;
    }
    // ★ ใช้ declining ถ้ามี repayments — ไม่งั้น flat
    const rows = reps.length && E.litGenerateActualDeclining
      ? E.litGenerateActualDeclining(principal, rate, startISO, endISO, reps)
      : E.litGenerateMonthlySchedule(principal, rate, startISO, endISO);
    const total = rows.reduce((s, r) => s + (Number(r.intAmount) || 0), 0);
    const loanIdCalc = loanId;  // alias
    let updated;
    setData(d => {
      const keep = (d.interestScheduleActual || []).filter(r => !LIT_sameLoan(r.loanId, loanIdCalc));
      const fresh = rows.map((p, i) => ({ id: litRowId(loanIdCalc, 'act', p.seq || i + 1), loanId: loanIdCalc, ...p }));
      const dm = (d.debtMaster || []).map(r => LIT_sameLoan(r.leasitLoanId, loanId) ? {
        ...r,
        leasitTotalActual: total,
        leasitVariance: (Number(r.leasitTotalPrepaid) || 0) - total,
        leasitRefundOutstanding: (Number(r.leasitTotalPrepaid) || 0) - total - (Number(r.leasitRefunded) || 0)
      } : r);
      updated = { ...d, interestScheduleActual: keep.concat(fresh), debtMaster: dm };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    if (toast) toast(`คำนวณดอกเกิดจริง ${rows.length} งวด · รวม ${total.toLocaleString('th-TH', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`, 'success');
  };

  // ── handleSaveScheduleRow — แก้ไขแถวเดียวใน prepaid/actual จาก drawer ──
  const handleSaveScheduleRow = (loanRow, kind, newRow) => {
    const loanId = loanRow.leasitLoanId;
    const entityKey = kind === 'prepaid' ? 'interestSchedulePrepaid' : 'interestScheduleActual';
    const totalsField = kind === 'prepaid' ? 'leasitTotalPrepaid' : 'leasitTotalActual';
    let updated;
    setData(d => {
      const list = (d[entityKey] || []).slice();
      const idx = list.findIndex(r => LIT_sameLoan(r.loanId, loanId) && r.seq === newRow.seq);
      if (idx >= 0) {
        list[idx] = { ...list[idx], ...newRow, loanId: loanId };
      } else {
        list.push({ id: litRowId(loanId, kind === 'prepaid' ? 'pre' : 'act', newRow.seq), loanId: loanId, ...newRow });
      }
      // recompute totals
      const total = list.filter(r => LIT_sameLoan(r.loanId, loanId)).reduce((s, r) => s + (Number(r.intAmount) || 0), 0);
      const dm = (d.debtMaster || []).map(r => {
        if (!LIT_sameLoan(r.leasitLoanId, loanId)) return r;
        const next = { ...r, [totalsField]: total };
        // ★ refresh variance + outstanding
        const pre = kind === 'prepaid' ? total : (Number(r.leasitTotalPrepaid) || 0);
        const act = kind === 'actual' ? total : (Number(r.leasitTotalActual) || 0);
        const refunded = Number(r.leasitRefunded) || 0;
        next.leasitVariance = pre - act;
        next.leasitRefundOutstanding = pre - act - refunded;
        return next;
      });
      updated = { ...d, [entityKey]: list, debtMaster: dm };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    if (toast) toast(`บันทึกแถว #${newRow.seq} (${kind === 'prepaid' ? 'ดอกล่วงหน้า' : 'ดอกเกิดจริง'}) สำเร็จ`, 'success');
  };

  const handleDeleteForm = (draft) => {
    const loanId = draft.leasitLoanId;
    const rowId = 'lit_' + loanId;
    let updated;
    setData(d => {
      updated = {
        ...d,
        debtMaster: (d.debtMaster || []).filter(r => r.id !== rowId),
        interestSchedulePrepaid: (d.interestSchedulePrepaid || []).filter(r => !LIT_sameLoan(r.loanId, loanId)),
        interestScheduleActual: (d.interestScheduleActual || []).filter(r => !LIT_sameLoan(r.loanId, loanId)),
        interestRefund: (d.interestRefund || []).filter(r => !LIT_sameLoan(r.loanId, loanId))
      };
      return updated;
    });
    if (updated && window.WTPData && window.WTPData.forceSyncNow) {
      setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
    }
    setFormState(null);
    setViewLoan(null);
    if (toast) toast(`ลบสัญญา ${draft.contractNo} แล้ว`, 'info');
  };

  // ── Principal summary: PRE / POS / NON (drawn / repaid / outstanding) ──
  //   PRE = Pre-financing, POS = Post-financing, NON = Term Loan (เช่น 20M)
  const principalSummary = React.useMemo(() => {
    const acc = {
      PRE: { count: 0, active: 0, drawn: 0, repaid: 0, outstanding: 0 },
      POS: { count: 0, active: 0, drawn: 0, repaid: 0, outstanding: 0 },
      NON: { count: 0, active: 0, drawn: 0, repaid: 0, outstanding: 0 }
    };
    allRows.forEach(r => {
      const t = String(r.leasitTicketType || '').toUpperCase();
      const bkt = t === 'PRE' ? acc.PRE : t === 'NON' ? acc.NON : acc.POS;
      const p = Number(r.principalAmount) || 0;
      bkt.count++;
      bkt.drawn += p;
      if (r.status === 'Active') { bkt.active++; bkt.outstanding += p; }
      else { bkt.repaid += p; }
    });
    return acc;
  }, [allRows]);

  // ── Maturity alert: Active loans ครบกำหนดใน 30 วัน (รวม overdue) ──
  const maturityAlerts = React.useMemo(() => {
    const todayMs = Date.now();
    const list = [];
    allRows.forEach(r => {
      if (r.status !== 'Active') return;
      const dueISO = r.leasitDateDueRoll || r.leasitDateDue || '';
      if (!dueISO) return;
      const dueMs = new Date(dueISO).getTime();
      if (!isFinite(dueMs)) return;
      const days = Math.ceil((dueMs - todayMs) / 86400000);
      if (days <= 30) list.push({ row: r, dueISO, days });
    });
    return list.sort((a, b) => a.days - b.days);
  }, [allRows]);

  const PrincipalCard = ({ label, bkt, color }) => (
    <div className="card" style={{ padding: 8, borderTop: '2px solid ' + color }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
        <span style={{
          padding: '1px 7px', borderRadius: 999, fontSize: 10, fontWeight: 700,
          background: color, color: '#fff'
        }}>{label}</span>
        <span style={{ fontSize: 11, color: 'var(--ink-500)' }}>
          {bkt.count} · Active {bkt.active}
        </span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, fontSize: 11 }}>
        <div>
          <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>↗ เบิก</div>
          <div style={{ fontSize: 13, fontWeight: 700 }}>{LIT_fmt(bkt.drawn, 0)}</div>
        </div>
        <div>
          <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>✓ คืน</div>
          <div style={{ fontSize: 13, fontWeight: 700, color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(bkt.repaid, 0)}</div>
        </div>
        <div>
          <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>● คงเหลือ</div>
          <div style={{ fontSize: 13, fontWeight: 700, color: bkt.outstanding > 0 ? 'var(--bad)' : 'var(--ink-500)' }}>
            {LIT_fmt(bkt.outstanding, 0)}
          </div>
        </div>
      </div>
    </div>
  );

  return (
    // ★ เคยใช้ negative margin breakout — overlap sidebar ที่ zoom 100% ในจอกว้าง
    //   ตอนนี้คงอยู่ใน .page (max 1480px) ตาราง minWidth 1300 + scroll x ถ้าจำเป็น
    <div>
      {/* ⏰ Maturity alert banner — Active loans ครบกำหนดใน 30 วัน (ย่อเป็น 1 บรรทัด คลิกขยาย) */}
      {maturityAlerts.length > 0 && (
        <div className="card" style={{
          padding: '8px 12px', marginBottom: 10,
          background: 'oklch(96% 0.06 80)',
          borderLeft: '4px solid oklch(70% 0.18 80)',
          cursor: 'pointer'
        }}
          onClick={() => setMaturityOpen(o => !o)}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div style={{ fontWeight: 700, fontSize: 13 }}>
              {maturityOpen ? '▾' : '▸'} ⏰ ครบกำหนดภายใน 30 วัน ({maturityAlerts.length} สัญญา · เงินต้น {LIT_fmt(maturityAlerts.reduce((s, a) => s + (Number(a.row.principalAmount) || 0), 0), 0)})
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>
              {maturityAlerts.filter(a => a.days < 0).length} เกินกำหนด · {maturityAlerts.filter(a => a.days >= 0 && a.days <= 7).length} ใน 7 วัน
            </div>
          </div>
          {maturityOpen && (
            <div onClick={(e) => e.stopPropagation()} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 6, marginTop: 8 }}>
              {maturityAlerts.slice(0, 12).map(({ row: r, dueISO, days }) => (
                <div
                  key={r.id}
                  onClick={() => setViewLoan(r)}
                  style={{
                    padding: 7, background: 'var(--panel)', borderRadius: 6, cursor: 'pointer', fontSize: 11,
                    border: '1px solid ' + (days < 0 ? 'var(--bad)' : days <= 7 ? 'oklch(70% 0.18 80)' : 'var(--ink-200)')
                  }}
                >
                  <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                    <span style={{ fontWeight: 700 }}>#{r.leasitLoanId} · {r.contractNo}</span>
                    <span style={{
                      padding: '0 6px', borderRadius: 999, fontSize: 10, fontWeight: 700,
                      background: days < 0 ? 'var(--bad)' : days <= 7 ? 'oklch(70% 0.18 80)' : 'var(--ink-300)',
                      color: '#fff'
                    }}>
                      {days < 0 ? `เกิน ${Math.abs(days)}` : days === 0 ? 'วันนี้' : `อีก ${days}`}
                    </span>
                  </div>
                  <div title={r.projectName} style={{ color: 'var(--ink-700)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {r.projectName}
                  </div>
                  <div style={{ color: 'var(--ink-500)' }}>
                    {LIT_fmtDate(dueISO)} · {LIT_fmt(r.principalAmount, 0)}
                  </div>
                </div>
              ))}
              {maturityAlerts.length > 12 && (
                <div style={{ fontSize: 11, color: 'var(--ink-500)', alignSelf: 'center' }}>
                  +{maturityAlerts.length - 12} รายการ
                </div>
              )}
            </div>
          )}
        </div>
      )}

      {/* ── 💰 เงินต้น — Total + แยก PRE/POS/NON ── */}
      <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink-700)', marginBottom: 4 }}>
        💰 เงินต้น — รับ / จ่ายคืน / คงเหลือ
      </div>
      {(() => {
        const tot = ['PRE', 'POS', 'NON'].reduce((acc, k) => {
          const b = principalSummary[k];
          acc.count += b.count; acc.active += b.active;
          acc.drawn += b.drawn; acc.repaid += b.repaid; acc.outstanding += b.outstanding;
          return acc;
        }, { count: 0, active: 0, drawn: 0, repaid: 0, outstanding: 0 });
        return (
          <div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr 1fr', gap: 8, marginBottom: 10 }}>
            {/* TOTAL card — เด่น */}
            <div className="card" style={{
              padding: 10, borderTop: '3px solid var(--brand-500)',
              background: 'linear-gradient(135deg, oklch(98% 0.02 250), oklch(94% 0.05 250))'
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                <span style={{
                  padding: '2px 9px', borderRadius: 999, fontSize: 11, fontWeight: 700,
                  background: 'var(--brand-500)', color: '#fff'
                }}>รวมทั้งหมด</span>
                <span style={{ fontSize: 11, color: 'var(--ink-700)', fontWeight: 600 }}>
                  {tot.count} สัญญา · Active {tot.active}
                </span>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
                <div>
                  <div style={{ color: 'var(--ink-600)', fontSize: 10 }}>↗ รับเงินต้นรวม</div>
                  <div style={{ fontSize: 17, fontWeight: 800 }}>{LIT_fmt(tot.drawn, 0)}</div>
                </div>
                <div>
                  <div style={{ color: 'var(--ink-600)', fontSize: 10 }}>✓ จ่ายคืนรวม</div>
                  <div style={{ fontSize: 17, fontWeight: 800, color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(tot.repaid, 0)}</div>
                </div>
                <div>
                  <div style={{ color: 'var(--ink-600)', fontSize: 10 }}>● คงเหลือรวม</div>
                  <div style={{ fontSize: 17, fontWeight: 800, color: tot.outstanding > 0 ? 'var(--bad)' : 'var(--ink-500)' }}>{LIT_fmt(tot.outstanding, 0)}</div>
                </div>
              </div>
            </div>
            <PrincipalCard label="PRE" bkt={principalSummary.PRE} color="oklch(52% 0.16 250)" />
            <PrincipalCard label="POS" bkt={principalSummary.POS} color="oklch(56% 0.18 25)" />
            <PrincipalCard label="NON" bkt={principalSummary.NON} color="oklch(48% 0.14 305)" />
          </div>
        );
      })()}

      {/* ── 📈 ดอกเบี้ย (รวมทุกสัญญา) — แถวล่าง ── */}
      <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink-700)', marginBottom: 4 }}>
        📈 ดอกเบี้ย — ล่วงหน้า / เกิดจริง / ส่วนต่าง / ค้างรับคืน
      </div>
      <div className="card" style={{ padding: 10, marginBottom: 10 }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 8, fontSize: 12 }}>
          <div>
            <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>สัญญา</div>
            <div style={{ fontSize: 16, fontWeight: 700 }}>{allRows.length}</div>
            <div style={{ fontSize: 10, color: 'var(--ink-500)' }}>🟢 {activeCnt} · ⚫ {closedCnt}</div>
          </div>
          <div>
            <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>ดอกล่วงหน้ารวม</div>
            <div style={{ fontSize: 16, fontWeight: 700 }}>{LIT_fmt(totPre, 0)}</div>
          </div>
          <div>
            <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>ดอกเกิดจริง</div>
            <div style={{ fontSize: 16, fontWeight: 700 }}>{LIT_fmt(totAct, 0)}</div>
          </div>
          <div>
            <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>ส่วนต่าง (ต้องได้คืน)</div>
            <div style={{ fontSize: 16, fontWeight: 700, color: 'oklch(52% 0.16 145)' }}>{LIT_fmt(totVar, 0)}</div>
          </div>
          <div>
            <div style={{ color: 'var(--ink-500)', fontSize: 10 }}>ค้างรับคืน</div>
            <div style={{ fontSize: 16, fontWeight: 700, color: totOut > 0.01 ? 'var(--bad)' : 'inherit' }}>{LIT_fmt(totOut, 0)}</div>
            <div style={{ fontSize: 10, color: 'var(--ink-500)' }}>รับแล้ว {LIT_fmt(totRef, 0)}</div>
          </div>
        </div>
      </div>

      {/* Toolbar */}
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12 }}>
        <input
          className="input"
          placeholder="ค้นหา เลขสัญญา / โครงการ / ลำดับ…"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          style={{ flex: 1 }}
        />
        <select className="input" value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)}>
          <option value="all">ทุกสถานะ</option>
          <option value="Active">🟢 Active</option>
          <option value="Close">⚫ ปิดแล้ว</option>
        </select>
        {canEdit && (
          <>
            <button className="btn btn-primary" onClick={() => setFormState({ mode: 'new', initial: null })}>
              ➕ เพิ่มสัญญา
            </button>
            <button className="btn btn-ghost" onClick={() => setShowImport(true)}>
              📥 นำเข้า Excel
            </button>
            <button className="btn btn-ghost" style={{ color: 'var(--bad,#c0392b)' }}
              onClick={() => setShowWipe(true)} disabled={!allRows.length}
              title="ลบข้อมูลลีซอิททั้งหมดออกจากระบบ เพื่อนำเข้าไฟล์ใหม่แบบสะอาด (ใช้เมื่อข้อมูลซ้ำ/แตกเป็นหลายบรรทัด)">
              🧹 ล้างทั้งหมด
            </button>
            <button
              className="btn"
              style={{ background: 'oklch(58% 0.15 145)', color: '#fff' }}
              onClick={() => setShowBulkCalc(true)}
              disabled={!allRows.length}
              title="คำนวณดอกเกิดจริงให้ทุกสัญญาพร้อมกัน — สำหรับสรุปบัญชีรายเดือน"
            >
              ⚡ ดอกเกิดจริง (ทุกสัญญา)
            </button>
            <button
              className="btn"
              style={{ background: 'oklch(56% 0.15 250)', color: '#fff' }}
              onClick={() => setShowBulkPrepaid(true)}
              disabled={!allRows.length}
              title="คำนวณตารางดอกจ่ายล่วงหน้าให้ทุกสัญญา — ยึดวันเริ่มต้น (วันรับเงิน) ถึงวันสิ้นสุดสัญญา"
            >
              📋 ดอกจ่ายล่วงหน้า (ทุกสัญญา)
            </button>
          </>
        )}
        <button className="btn btn-ghost" onClick={handleExportAll} disabled={!allRows.length}>
          📤 Export (มีสูตร)
        </button>
        <button
          className="btn btn-ghost"
          title="ดึงข้อมูลล่าสุดจาก Supabase (เผื่อ data ตกหล่นหลัง import)"
          onClick={async () => {
            try {
              if (window.WTPData && window.WTPData.refreshFromServer) {
                await window.WTPData.refreshFromServer();
                if (toast) toast('ดึงข้อมูลจาก Supabase สำเร็จ', 'success');
              } else {
                window.location.reload();
              }
            } catch (e) {
              if (toast) toast('ดึงข้อมูลล้มเหลว: ' + (e.message || e), 'error');
            }
          }}
        >
          ↻ ดึงใหม่
        </button>
      </div>

      {/* Duplicate warning + cleanup */}
      {dupCount > 0 && (
        <div className="card" style={{ padding: 10, marginBottom: 10, background: 'oklch(96% 0.05 22)', borderLeft: '4px solid var(--bad)', fontSize: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
          <div>
            ⚠️ <b>พบสัญญาซ้ำ {dupCount} แถว</b> (loanId เดียวกันแต่มีหลาย debtMaster row) — โชว์เหลือ {allRows.length} จาก {allRowsRaw.length}
          </div>
          {canEdit && (
            <button className="btn" style={{ background: 'var(--bad)', color: '#fff', padding: '4px 12px', fontSize: 12 }} onClick={handleCleanupDuplicates}>
              🧹 ลบซ้ำทั้งหมด
            </button>
          )}
        </div>
      )}

      {/* Warning: ถ้า debtMaster มี leasit rows แต่ 3 child tables ว่างหมด → SQL ยังไม่รัน */}
      {allRows.length > 0 && (data?.interestSchedulePrepaid || []).length === 0 && (data?.interestScheduleActual || []).length === 0 && (data?.interestRefund || []).length === 0 && (
        <div className="card" style={{ padding: 10, marginBottom: 10, background: 'oklch(96% 0.05 80)', borderLeft: '4px solid oklch(70% 0.18 80)', fontSize: 12 }}>
          ⚠️ <b>ตาราง schedule ใน Supabase ยังไม่มี — ดอกเบี้ยรายงวดไม่ถูก persist</b>
          <div style={{ marginTop: 4, color: 'var(--ink-700)' }}>
            ให้ admin รัน <code>supabase/leasit-loans.sql</code> ใน Supabase SQL Editor แล้วลองใหม่ (สัญญา {allRows.length} ตัวที่ใส่ไว้ยังอยู่ใน <code>debtMaster</code> ปกติ ไม่หาย).
          </div>
        </div>
      )}

      {/* ★ ไม่มีข้อมูลการจ่ายคืนเงินต้นเลย → ดอกเบี้ยเกิดจริงจะคิดจากเงินต้นเต็มตลอดสัญญา (สูงเกินจริง) */}
      {allRows.length > 0 && refund.filter(r => r.kind === 'principal').length === 0 && (
        <div className="card" style={{ padding: 10, marginBottom: 10, background: 'oklch(96% 0.05 80)', borderLeft: '4px solid oklch(70% 0.18 80)', fontSize: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
          <div>
            ⚠️ <b>ยังไม่มีข้อมูลการจ่ายคืนเงินต้นในระบบเลย</b> — ดอกเบี้ยเกิดจริงจะคิดจากเงินต้นเต็มตลอดสัญญา (สูงกว่าความเป็นจริง)
            <div style={{ marginTop: 4, color: 'var(--ink-700)' }}>
              ข้อมูลส่วนนี้ดึงได้จาก <b>ปุ่ม 📥 นำเข้า Excel</b> เท่านั้น — ปุ่มคำนวณดอกเบี้ยไม่ได้ดึงให้
            </div>
          </div>
          {canEdit && (
            <button className="btn btn-primary" style={{ padding: '4px 12px', fontSize: 12, whiteSpace: 'nowrap' }}
              onClick={() => setShowImport(true)}>📥 นำเข้า Excel</button>
          )}
        </div>
      )}

      {/* Table */}
      {filtered.length === 0 ? (
        <div className="card" style={{ padding: 32, textAlign: 'center', color: 'var(--ink-500)' }}>
          {allRows.length === 0
            ? '⚠️ ยังไม่มีข้อมูลลีซอิท — กด "📥 นำเข้า Excel" เพื่อ upload ไฟล์ตารางคำนวณดอกเบี้ย'
            : 'ไม่พบสัญญาที่ค้นหา'}
        </div>
      ) : (() => {
        // ★ ผู้ใช้ขอ — เลิก pagination ใช้ scroll แทน (15 rows = ~400px maxHeight)
        const pageRows = filtered;
        // ★ nowrap base — แถวสูง 1 บรรทัดเสมอ (จะ ellipsis แทน wrap)
        const tdBase = { padding: '3px 6px', fontSize: 12, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
        const tdRight = { ...tdBase, textAlign: 'right' };
        // sticky header: thead พื้นเทาเข้มขึ้น + position sticky top:0
        return (
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <div style={{ overflow: 'auto', maxHeight: 'calc(100vh - 380px)', minHeight: 350 }}>
              <table className="tbl" style={{ width: '100%', fontSize: 12, borderCollapse: 'collapse', tableLayout: 'fixed', minWidth: 1980 }}>
                <colgroup>
                  {/* ★ ต้องมีให้ครบทุกคอลัมน์ (18) — ถ้าขาด tableLayout:fixed จะบีบคอลัมน์ท้าย ๆ จนชื่อโครงการอ่านไม่ออก
                     # / ประเภท / เลขสัญญา / JOB / โครงการ / เงินต้นรับ / จ่ายคืน / คงเหลือ / อัตรา /
                     รับเงิน / ครบกำหนด / ระยะเวลา / วันที่จ่ายคืน / ยอดจ่ายคืน / เลขที่เอกสาร / สถานะ / ค้างดอก / จัดการ */}
                  <col style={{ width: 38 }} /><col style={{ width: 52 }} /><col style={{ width: 150 }} /><col style={{ width: 84 }} />
                  <col style={{ width: 340 }} />
                  <col style={{ width: 104 }} /><col style={{ width: 104 }} /><col style={{ width: 104 }} /><col style={{ width: 62 }} />
                  <col style={{ width: 92 }} /><col style={{ width: 92 }} /><col style={{ width: 74 }} />
                  <col style={{ width: 96 }} /><col style={{ width: 104 }} /><col style={{ width: 118 }} />
                  <col style={{ width: 52 }} /><col style={{ width: 96 }} /><col style={{ width: 74 }} />
                </colgroup>
                <thead style={{ position: 'sticky', top: 0, zIndex: 5, background: 'var(--ink-50)' }}>
                  {(() => {
                    const FCH = window.FilterableColHeader;
                    // ถ้า FilterableColHeader ไม่พร้อม → fallback simple sort header
                    if (typeof FCH !== 'function') {
                      const Th = ({ k, label, right }) => (
                        <th style={{ ...(right ? tdRight : tdBase), fontWeight: 600, cursor: 'pointer' }} onClick={() => sortToggle(k)}>
                          {label}{sort.key === k ? (sort.dir === 'asc' ? ' ▲' : ' ▼') : ''}
                        </th>
                      );
                      return (
                        <tr style={{ background: 'var(--ink-50)' }}>
                          <Th k="leasitLoanId" label="#" />
                          <Th k="leasitTicketType" label="ประเภท" />
                          <Th k="contractNo" label="เลขที่สัญญา" />
                          <Th k="projectCode" label="JOB" />
                          <Th k="projectName" label="โครงการ" />
                          <Th k="principalAmount" label="เงินต้นรับ" right />
                          <Th k="principalRepaid" label="จ่ายคืน" right />
                          <Th k="principalOutstanding" label="คงเหลือ" right />
                          <Th k="interestRate" label="อัตรา" right />
                          <Th k="leasitDateReceived" label="รับเงิน / เริ่มต้น" />
                          <Th k="leasitDateDue" label="ครบกำหนด" />
                          <Th k="_termDays" label="ระยะเวลา (วัน)" right />
                          <Th k="_repayDates" label="วันที่จ่ายคืน" />
                          <Th k="_repayAmounts" label="ยอดจ่ายคืน" right />
                          <Th k="_repayRefs" label="เลขที่เอกสารจ่าย" />
                          <Th k="status" label="สถานะ" />
                          <Th k="leasitRefundOutstanding" label="ค้างดอก" right />
                          <th style={{ ...tdBase, fontWeight: 600, textAlign: 'center' }}>จัดการ</th>
                        </tr>
                      );
                    }
                    const commonProps = {
                      sort, sortToggle, colFilters, setColFilters, openCol, setOpenCol,
                      allRows: filtered, getValue: getColDisplayValue, getSortValue: getColSortValue
                    };
                    return (
                      <tr style={{ background: 'var(--ink-50)' }}>
                        <FCH {...commonProps} sortKey="leasitLoanId" label="#" align="left" />
                        <FCH {...commonProps} sortKey="leasitTicketType" label="ประเภท" align="left" />
                        <FCH {...commonProps} sortKey="contractNo" label="เลขสัญญา" align="left" />
                        <FCH {...commonProps} sortKey="projectCode" label="JOB" align="left" />
                        <FCH {...commonProps} sortKey="projectName" label="โครงการ" align="left" />
                        <FCH {...commonProps} sortKey="principalAmount" label="เงินต้นรับ" align="right" />
                        <FCH {...commonProps} sortKey="principalRepaid" label="จ่ายคืน" align="right" />
                        <FCH {...commonProps} sortKey="principalOutstanding" label="คงเหลือ" align="right" />
                        <FCH {...commonProps} sortKey="interestRate" label="อัตรา" align="right" />
                        <FCH {...commonProps} sortKey="leasitDateReceived" label="รับเงิน / เริ่มต้น" align="left" />
                        <FCH {...commonProps} sortKey="leasitDateDue" label="ครบกำหนด" align="left" />
                        <FCH {...commonProps} sortKey="_termDays" label="ระยะเวลา (วัน)" align="right" />
                        <FCH {...commonProps} sortKey="_repayDates" label="วันที่จ่ายคืน" align="left" />
                        <FCH {...commonProps} sortKey="_repayAmounts" label="ยอดจ่ายคืน" align="right" />
                        <FCH {...commonProps} sortKey="_repayRefs" label="เลขที่เอกสารจ่าย" align="left" />
                        <FCH {...commonProps} sortKey="status" label="สถานะ" align="left" />
                        <FCH {...commonProps} sortKey="leasitRefundOutstanding" label="ค้างดอก" align="right" />
                        <th style={{ ...tdBase, fontWeight: 600, textAlign: 'center', width: 74 }}>จัดการ</th>
                      </tr>
                    );
                  })()}
                </thead>
                <tbody>
                  {pageRows.map(r => {
                    const t = String(r.leasitTicketType || '').toUpperCase();
                    const label = t === 'NON' ? 'NON' : t === 'PRE' ? 'PRE' : 'POS';
                    const col = label === 'PRE' ? 'oklch(52% 0.16 250)' : label === 'NON' ? 'oklch(48% 0.14 305)' : 'oklch(56% 0.18 25)';
                    // เงินต้นจ่ายคืน: ใช้ฟิลด์ที่ฟอร์มบันทึก ถ้าไม่มี → ปิดสัญญาแล้ว = เต็มจำนวน, ยัง Active = 0
                    const principal = Number(r.principalAmount) || 0;
                    const repaid = r.leasitPrincipalRepaid != null
                      ? (Number(r.leasitPrincipalRepaid) || 0)
                      : (r.status === 'Close' ? principal : 0);
                    const outstanding = principal - repaid;
                    return (
                      <tr key={r.id} onClick={() => setViewLoan(r)} style={{ cursor: 'pointer', borderBottom: '1px solid var(--ink-100)' }}>
                        <td style={tdBase}>{r.leasitLoanId}</td>
                        <td style={tdBase}>
                          <span style={{ padding: '1px 6px', borderRadius: 999, fontSize: 10, fontWeight: 700, background: col, color: '#fff' }}>{label}</span>
                        </td>
                        <td style={tdBase} title={r.contractNo}>{r.contractNo}</td>
                        <td style={tdBase} title={r.projectCode}>{r.projectCode || '—'}</td>
                        <td title={r.projectName} style={{ ...tdBase, whiteSpace: 'normal', lineHeight: 1.3, wordBreak: 'break-word' }}>{r.projectName}
                          {(() => { const a = projectAssignee(r.projectCode); return a
                            ? <div style={{ fontSize: 10.5, color: 'oklch(52% 0.16 250)', marginTop: 1 }} title={'ผู้รับโอนสิทธิ (จากข้อมูลโครงการ)'}>↪ โอนสิทธิ: {a}</div>
                            : null; })()}
                        </td>
                        <td style={tdRight}>{LIT_fmt(principal, 0)}</td>
                        <td style={{ ...tdRight, color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(repaid, 0)}</td>
                        <td style={{ ...tdRight, color: outstanding > 0.01 ? 'var(--bad)' : 'var(--ink-500)', fontWeight: outstanding > 0.01 ? 600 : 400 }}>{LIT_fmt(outstanding, 0)}</td>
                        <td style={tdRight}>{(r.interestRate * 100).toFixed(2)}%</td>
                        <td style={tdBase}>{LIT_fmtDate(r.leasitDateReceived)}</td>
                        <td style={tdBase}>{LIT_fmtDate(r.leasitDateDue)}</td>
                        <td style={tdRight}>{r._termDays == null ? '—' : LIT_fmt(r._termDays, 0)}</td>
                        <td style={tdBase} title={r._repayDates}>
                          {(r._repayList || []).length
                            ? (r._repayList.length === 1 ? LIT_fmtDate(r._repayList[0].date)
                              : <span>{LIT_fmtDate(r._repayList[0].date)} <span style={{ fontSize: 10, color: 'var(--ink-500)' }}>+{r._repayList.length - 1} ครั้ง</span></span>)
                            : (r.leasitDateRepaid ? LIT_fmtDate(r.leasitDateRepaid) : '—')}
                        </td>
                        <td style={tdRight} title={r._repayAmounts}>
                          {(r._repayList || []).length
                            ? (r._repayList.length === 1 ? LIT_fmt(r._repayList[0].amount, 0)
                              : LIT_fmt(r._repayList.reduce((a, x) => a + x.amount, 0), 0))
                            : (repaid > 0 ? LIT_fmt(repaid, 0) : '—')}
                        </td>
                        <td style={tdBase} title={r._repayRefs}>
                          {r._repayRefs
                            ? (r._repayRefs.length > 18 ? r._repayRefs.slice(0, 18) + '…' : r._repayRefs)
                            : '—'}
                        </td>
                        <td style={tdBase}>{r.status === 'Active' ? '🟢' : '⚫'}</td>
                        <td style={{ ...tdRight, color: r.leasitRefundOutstanding > 0.01 ? 'var(--bad)' : 'var(--ink-500)', fontWeight: r.leasitRefundOutstanding > 0.01 ? 600 : 400 }}>
                          {LIT_fmt(r.leasitRefundOutstanding, 2)}
                        </td>
                        <td style={{ ...tdBase, textAlign: 'center', whiteSpace: 'nowrap' }} onClick={(e) => e.stopPropagation()}>
                          {canEdit ? (
                            <React.Fragment>
                              <button className="btn btn-ghost" style={{ padding: '2px 6px', fontSize: 12 }} title="แก้ไขข้อมูลสัญญานี้"
                                onClick={() => setFormState({ mode: 'edit', initial: r })}>✏️</button>
                              <button className="btn btn-ghost" style={{ padding: '2px 6px', fontSize: 12, color: 'var(--bad)' }} title="ลบสัญญานี้"
                                onClick={() => setDeleteTarget(r)}>🗑</button>
                            </React.Fragment>
                          ) : <span style={{ color: 'var(--ink-300)' }}>—</span>}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
                <tfoot>
                  {(() => {
                    const tot = filtered.reduce((a, r) => {
                      const p = Number(r.principalAmount) || 0;
                      const rep = r.leasitPrincipalRepaid != null ? (Number(r.leasitPrincipalRepaid) || 0) : (r.status === 'Close' ? p : 0);
                      a.principal += p; a.repaid += rep; a.outstanding += (p - rep);
                      a.refundOut += Number(r.leasitRefundOutstanding) || 0;
                      return a;
                    }, { principal: 0, repaid: 0, outstanding: 0, refundOut: 0 });
                    return (
                      <tr style={{ fontWeight: 600, background: 'var(--ink-50)' }}>
                        <td colSpan={5} style={{ ...tdBase, textAlign: 'right' }}>รวม {filtered.length} สัญญา</td>
                        <td style={tdRight}>{LIT_fmt(tot.principal, 0)}</td>
                        <td style={{ ...tdRight, color: 'oklch(50% 0.14 145)' }}>{LIT_fmt(tot.repaid, 0)}</td>
                        <td style={{ ...tdRight, color: 'var(--bad)' }}>{LIT_fmt(tot.outstanding, 0)}</td>
                        <td colSpan={4}></td>
                        <td style={tdRight}>{LIT_fmt(tot.refundOut, 2)}</td>
                      </tr>
                    );
                  })()}
                </tfoot>
              </table>
            </div>
            {/* footer แสดงจำนวนแถวทั้งหมด */}
            <div style={{ padding: '4px 12px', borderTop: '1px solid var(--ink-100)', fontSize: 11, color: 'var(--ink-500)', textAlign: 'right' }}>
              ทั้งหมด {filtered.length} สัญญา · เลื่อนเมาส์เพื่อดูทั้งหมด
            </div>
          </div>
        );
      })()}

      {/* Modals */}
      {/* ★ ล้างข้อมูลลีซอิททั้งหมด → นำเข้าไฟล์ใหม่แบบสะอาด (แก้เคสนำเข้าซ้ำจนแตกเป็นหลายบรรทัด) */}
      {showWipe && window.Modal && (() => {
        const litIds = new Set(allRowsRaw.map(r => LIT_lid(r.leasitLoanId)));
        const nRows = allRowsRaw.length, nDup = dupCount;
        const nPre = (prepaid || []).filter(x => litIds.has(LIT_lid(x.loanId))).length;
        const nAct = (actual || []).filter(x => litIds.has(LIT_lid(x.loanId))).length;
        const nRef = (refund || []).filter(x => litIds.has(LIT_lid(x.loanId))).length;
        const doWipe = () => {
          let updated;
          setData(d => {
            const ids = new Set((d.debtMaster || [])
              .filter(r => r.leasitSource === true || r.leasitSource === 'true')
              .map(r => LIT_lid(r.leasitLoanId)));
            updated = {
              ...d,
              debtMaster: (d.debtMaster || []).filter(r => !(r.leasitSource === true || r.leasitSource === 'true')),
              interestSchedulePrepaid: (d.interestSchedulePrepaid || []).filter(x => !ids.has(LIT_lid(x.loanId))),
              interestScheduleActual: (d.interestScheduleActual || []).filter(x => !ids.has(LIT_lid(x.loanId))),
              interestRefund: (d.interestRefund || []).filter(x => !ids.has(LIT_lid(x.loanId))),
            };
            return updated;
          });
          if (updated && window.WTPData && window.WTPData.forceSyncNow) {
            setTimeout(() => window.WTPData.forceSyncNow(updated), 0);
          }
          setShowWipe(false); setWipeWord('');
          if (toast) toast('ล้างข้อมูลลีซอิททั้งหมดแล้ว — นำเข้าไฟล์ใหม่ได้เลย', 'success');
        };
        return (
          <window.Modal open={true} onClose={() => { setShowWipe(false); setWipeWord(''); }}
            title="🧹 ล้างข้อมูลลีซอิททั้งหมด" maxWidth={600}>
            <div style={{ fontSize: 13, lineHeight: 1.75 }}>
              <div style={{ background: '#fdf5f4', border: '1px solid #f5c6c2', borderRadius: 10, padding: '11px 13px' }}>
                จะลบข้อมูลลีซอิทออกจากระบบทั้งหมด แล้วให้นำเข้าไฟล์ใหม่แบบสะอาด:
                <ul style={{ margin: '6px 0 0', paddingLeft: 18 }}>
                  <li>สัญญา <b>{nRows}</b> แถว{nDup > 0 && <span style={{ color: '#c0392b' }}> (ซ้ำอยู่ {nDup} แถว)</span>}</li>
                  <li>ตารางดอกจ่ายล่วงหน้า <b>{nPre}</b> งวด · ดอกเกิดจริง <b>{nAct}</b> งวด</li>
                  <li>รายการรับคืน/จ่ายคืน <b>{nRef}</b> รายการ</li>
                </ul>
                <div style={{ marginTop: 7, fontSize: 12 }}>
                  ★ ข้อมูลหนี้อื่นที่ไม่ใช่ลีซอิท (เงินกู้ธนาคาร ฯลฯ) <b>ไม่ถูกแตะ</b>
                </div>
              </div>
              <div style={{ marginTop: 12, fontWeight: 600 }}>
                พิมพ์คำว่า <b style={{ color: '#c0392b' }}>ยืนยันลบ</b> เพื่อยืนยัน
              </div>
              <input className="input" style={{ marginTop: 6, width: '100%' }} autoFocus placeholder="ยืนยันลบ"
                value={wipeWord} onChange={(e) => setWipeWord(e.target.value)} />
              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
                <button className="btn btn-ghost" onClick={() => { setShowWipe(false); setWipeWord(''); }}>ยกเลิก</button>
                <button className="btn" disabled={wipeWord.trim() !== 'ยืนยันลบ'}
                  style={{ background: wipeWord.trim() === 'ยืนยันลบ' ? 'var(--bad,#c0392b)' : 'var(--ink-100)',
                           color: wipeWord.trim() === 'ยืนยันลบ' ? '#fff' : 'var(--ink-400)' }}
                  onClick={doWipe}>ล้างทั้งหมด</button>
              </div>
            </div>
          </window.Modal>
        );
      })()}
      {showBulkPrepaid && window.Modal && (() => {
        const target = bulkPrepaidScope === 'active' ? allRows.filter(r => r.status === 'Active') : allRows;
        const ready = target.filter(r => (Number(r.principalAmount) || 0) && (Number(r.interestRate) || 0)
          && r.leasitDateReceived && r.leasitDateDue);
        const hasPre = new Set((prepaid || []).map(x => LIT_lid(x.loanId)));
        const already = ready.filter(r => hasPre.has(LIT_lid(r.leasitLoanId))).length;
        return (
          <window.Modal open={true} onClose={() => setShowBulkPrepaid(false)}
            title="📋 คำนวณดอกจ่ายล่วงหน้าทุกสัญญาพร้อมกัน" maxWidth={620}>
            <div style={{ fontSize: 13, lineHeight: 1.75 }}>
              <div style={{ background: 'var(--brand-50,#f0f6ff)', border: '1px solid var(--brand-100,#dceaff)',
                            borderRadius: 10, padding: '10px 13px' }}>
                💡 คำนวณตาม<b>สัญญา</b> — ยึด <b>วันรับเงิน (วันเริ่มต้น) → วันสิ้นสุดสัญญา</b> ของแต่ละสัญญา
                ใช้สูตรเดียวกับปุ่มคำนวณรายสัญญาในหน้าดีเทล
              </div>
              <div style={{ marginTop: 12, fontWeight: 600 }}>เลือกสัญญา:</div>
              <label style={{ display: 'block', marginTop: 4 }}>
                <input type="radio" checked={bulkPrepaidScope === 'active'} onChange={() => setBulkPrepaidScope('active')} />
                {' '}🟢 เฉพาะ Active
              </label>
              <label style={{ display: 'block' }}>
                <input type="radio" checked={bulkPrepaidScope === 'all'} onChange={() => setBulkPrepaidScope('all')} />
                {' '}🌐 ทั้งหมด (รวม Closed คำนวณใหม่)
              </label>
              <div style={{ marginTop: 10, fontWeight: 600 }}>สัญญาที่เคยคำนวณไว้แล้ว:</div>
              <label style={{ display: 'block', marginTop: 4 }}>
                <input type="radio" checked={bulkPrepaidOverwrite} onChange={() => setBulkPrepaidOverwrite(true)} />
                {' '}เขียนทับด้วยผลคำนวณใหม่
              </label>
              <label style={{ display: 'block' }}>
                <input type="radio" checked={!bulkPrepaidOverwrite} onChange={() => setBulkPrepaidOverwrite(false)} />
                {' '}ข้ามไว้ (คำนวณเฉพาะสัญญาที่ยังไม่มีตาราง)
              </label>
              <div style={{ marginTop: 12, background: 'var(--ink-50,#f7f8fa)', borderRadius: 9, padding: '9px 12px' }}>
                จะคำนวณให้ <b style={{ color: 'var(--brand-500)' }}>{bulkPrepaidOverwrite ? ready.length : ready.length - already}</b> สัญญา
                {target.length - ready.length > 0 && (
                  <span style={{ color: 'var(--bad,#c0392b)' }}> · ข้าม {target.length - ready.length} สัญญา (ยังไม่มีเงินต้น/อัตรา/วันเริ่ม/วันสิ้นสุด)</span>
                )}
                {!bulkPrepaidOverwrite && already > 0 && (
                  <span style={{ color: 'var(--ink-500)' }}> · มีตารางอยู่แล้ว {already} สัญญา</span>
                )}
              </div>
              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
                <button className="btn btn-ghost" onClick={() => setShowBulkPrepaid(false)}>ยกเลิก</button>
                <button className="btn" style={{ background: 'oklch(56% 0.15 250)', color: '#fff' }}
                  onClick={() => handleBulkCalcPrepaid({ loans: ready, overwrite: bulkPrepaidOverwrite })}>
                  📋 คำนวณ ({bulkPrepaidOverwrite ? ready.length : ready.length - already} สัญญา)
                </button>
              </div>
            </div>
          </window.Modal>
        );
      })()}
      <BulkCalcActualModal
        open={showBulkCalc}
        loans={allRows}
        onClose={() => setShowBulkCalc(false)}
        onConfirm={handleBulkCalcActual}
      />
      <LeasitExportModal
        open={showExportSel}
        loans={allRows}
        onClose={() => setShowExportSel(false)}
        onConfirm={handleExportSelected}
      />
      <LeasitImportModal
        open={showImport}
        onClose={() => setShowImport(false)}
        onConfirm={handleImport}
      />
      <LeasitLoanDrawer
        loan={viewLoan ? (allRows.find(r => r.id === viewLoan.id) || viewLoan) : null}
        assignee={viewLoan ? projectAssignee((allRows.find(r => r.id === viewLoan.id) || viewLoan).projectCode) : ''}
        prepaid={prepaid}
        actual={actual}
        refund={refund}
        onClose={() => setViewLoan(null)}
        onExportOne={handleExportOne}
        onEdit={canEdit ? (l) => { setFormState({ mode: 'edit', initial: l }); setViewLoan(null); } : null}
        canEdit={canEdit}
        onCalcPrepaid={handleCalcPrepaid}
        onCalcActual={handleCalcActual}
        onSaveScheduleRow={canEdit ? handleSaveScheduleRow : null}
      />
      <LeasitLoanForm
        open={!!formState}
        mode={formState?.mode}
        initial={formState?.initial}
        data={data}
        onClose={() => setFormState(null)}
        onSave={handleSaveForm}
        onDelete={handleDeleteForm}
        canEdit={canEdit}
      />
      {/* ★ ลบสัญญาจากตาราง — ต้องพิมพ์ "ยืนยันลบ" ก่อน (กันลบพลาดตอนนำเข้าไฟล์ผิด) */}
      {deleteTarget && window.Modal && (
        <window.Modal open={true} onClose={() => { setDeleteTarget(null); setDelWord(''); }}
          title="🗑 ลบสัญญาลีซอิท" maxWidth={560}>
          <div style={{ fontSize: 13, lineHeight: 1.7 }}>
            <div style={{ background: '#fdf5f4', border: '1px solid #f5c6c2', borderRadius: 10, padding: '11px 13px' }}>
              <div style={{ fontWeight: 700, color: '#c0392b', marginBottom: 4 }}>
                ลำดับ {deleteTarget.leasitLoanId} · {deleteTarget.contractNo}
              </div>
              <div style={{ color: 'var(--ink-600)' }}>{deleteTarget.projectName}</div>
              <div style={{ marginTop: 8, fontSize: 12, color: 'var(--ink-600)' }}>
                จะลบสัญญานี้พร้อม <b>ตารางดอกจ่ายล่วงหน้า · ดอกเกิดจริง · รายการรับคืน/จ่ายคืน</b> ทั้งหมดของสัญญานี้
              </div>
            </div>
            <div style={{ marginTop: 12, fontWeight: 600 }}>
              พิมพ์คำว่า <b style={{ color: '#c0392b' }}>ยืนยันลบ</b> เพื่อยืนยัน
            </div>
            <input className="input" style={{ marginTop: 6, width: '100%' }} autoFocus placeholder="ยืนยันลบ"
              value={delWord} onChange={(e) => setDelWord(e.target.value)} />
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
              <button className="btn btn-ghost" onClick={() => { setDeleteTarget(null); setDelWord(''); }}>ยกเลิก</button>
              <button className="btn" disabled={delWord.trim() !== 'ยืนยันลบ'}
                style={{ background: delWord.trim() === 'ยืนยันลบ' ? 'var(--bad,#c0392b)' : 'var(--ink-100)',
                         color: delWord.trim() === 'ยืนยันลบ' ? '#fff' : 'var(--ink-400)' }}
                onClick={() => {
                  handleDeleteForm(deleteTarget);
                  setDeleteTarget(null); setDelWord('');
                  if (toast) toast('ลบสัญญา ' + deleteTarget.contractNo + ' แล้ว', 'success');
                }}>ลบถาวร</button>
            </div>
          </div>
        </window.Modal>
      )}
    </div>
  );
}

// expose
window.LeasitPanel = LeasitPanel;
