/* form.jsx — หน้ากรอกงบของ 1 ฝ่าย (ฝ่ายกรอกเอง / บัญชีกรอกรายการส่วนกลาง / ผู้ดูแลตรวจ)
 * 1 บรรทัดต่อ 1 รหัสบัญชี: พิมพ์ยอดทั้งปีช่องเดียว ระบบกระจายลง 12 เดือนให้
 * เปิดลูกศรท้ายบรรทัดเมื่อต้องการกำหนดรายเดือน · บันทึกอัตโนมัติทุกครั้งที่แก้ */

/** น้ำหนักสำหรับกระจายยอดเมื่อผู้ใช้แก้ยอดรวม: null = เฉลี่ยเท่ากัน, array = คงสัดส่วนรายเดือนเดิม */
function patternWeights(months) {
  if (!Array.isArray(months) || R.sum(months) <= 0) return null;
  return R.patternOf(months).kind === 'even' ? null : months.slice();
}

/** บอกเป็นภาษาคนว่ายอดทั้งปีถูกแบ่งลงเดือนอย่างไร เช่น "เดือนละ 122,000" */
function patternText(months) {
  const p = R.patternOf(months);
  if (p.kind === 'zero') return 'ไม่ขอปีหน้า';
  if (p.kind === 'even') return 'เดือนละ ' + (p.max > p.min ? '~' : '') + fmt(p.min);
  if (p.kind === 'once') return 'จ่ายครั้งเดียว ' + R.MONTHS_TH[p.first];
  if (p.kind === 'some') return 'จ่าย ' + p.count + ' เดือน';
  return 'แต่ละเดือนไม่เท่ากัน';
}

function LineRow({ v, cfg, open, onToggle, showErrors, actions }) {
  const { l, ev, editable } = v;
  const hasSub = open || ev.flag !== 'none' || !!l.note;
  const noteEditor = editable && (open || ev.flag === 'red');
  const pctText = !ev.filled ? '' : ev.pct == null ? (ev.R > 0 ? 'ใหม่' : '–') : fmtPct(ev.pct);
  return (
    <div id={'row-' + l.acct} className={'lrow' + (ev.flag !== 'none' ? ' ' + ev.flag : '') + (ev.filled ? '' : ' todo')}>
      <div className="lgrid">
        <div className="lname" title={l.acct + ' ' + l.name}>
          <span className="code">{l.acct}</span>
          <span className="nm">{l.name}</span>
          {l.added && <span className="badge">เพิ่มเอง</span>}
        </div>
        <div className="num ref">{fmt(ev.B)}</div>
        <div className="num ref">
          {fmt(ev.A)}
          {ev.usagePct != null && <small>{Math.round(ev.usagePct)}%</small>}
        </div>
        <div className="num ref">{ev.P == null ? '–' : fmt(ev.P)}</div>
        {editable ? (
          <NumInput
            className="amt" nav="amt" placeholder="พิมพ์ยอด" ariaLabel={'ขอปีหน้า ' + l.name}
            value={ev.filled ? ev.R : null} onValue={(x) => actions.setTotal(l.acct, x)}
          />
        ) : (
          <div className="num">{ev.filled ? fmt(ev.R) : <span className="mute small">ยังไม่กรอก</span>}</div>
        )}
        {ev.filled ? (
          // กรอกแล้ว: บอกว่าระบบแบ่งลงเดือนอย่างไร (กดเพื่อแก้รายเดือน) + ส่วนต่างจากงบปีนี้
          <>
            <button className="pat" onClick={onToggle} title="ดูหรือแก้ยอดรายเดือน">
              <span className="pt">{patternText(l.months)}</span>
              <span className="edit">{editable ? 'แก้' : 'ดู'}</span>
            </button>
            <div className={'num pct' + (ev.pct != null && ev.pct < 0 ? ' down' : '')}>{pctText}</div>
          </>
        ) : editable ? (
          // ยังไม่กรอก: ปุ่มใส่ยอดให้ในคลิกเดียว (กินพื้นที่ 2 คอลัมน์ เพราะยังไม่มี % ให้แสดง)
          <div className="quickfill">
            {ev.B > 0 && (
              <button className="pill" title={'ใส่ ' + fmt(ev.B) + ' เท่ากับงบปีนี้'} onClick={() => actions.copyBudget(l.acct)}>เท่างบปีนี้</button>
            )}
            {ev.P > 0 && (
              <button className="pill" title={'ใส่ ' + fmt(ev.P) + ' เท่ากับคาดการณ์ใช้จริงทั้งปี'} onClick={() => actions.setTotal(l.acct, ev.P)}>เท่าคาดการณ์</button>
            )}
            <button className="pill" title="ไม่ขอรหัสนี้ในปีหน้า (ใส่ 0)" onClick={() => actions.setTotal(l.acct, 0)}>ไม่ขอ</button>
          </div>
        ) : (
          <><div /><div /></>
        )}
        <button className="btn ghost icon-only" onClick={onToggle} aria-expanded={open} aria-label={'รายเดือนและหมายเหตุ ' + l.name}>
          <Icon name="chevron" className={'chev' + (open ? ' open' : '')} />
        </button>
      </div>

      {hasSub && (
        <div className="lsub">
          {open && (
            <>
              <div className="months">
                {R.MONTHS_TH.map((m, i) => (
                  <div className="m" key={i}>
                    <label>{m}</label>
                    <NumInput
                      nav={'m-' + l.acct} disabled={!editable} ariaLabel={l.name + ' ' + m}
                      value={l.months ? l.months[i] : null} onValue={(x) => actions.setMonth(l.acct, i, x)}
                    />
                    <div className="act">{i < cfg.actualMonths && l.actual[i] ? fmt(l.actual[i]) : ''}</div>
                  </div>
                ))}
              </div>
              <div className="quick">
                <span>ตัวเลขเล็กใต้ช่อง = ใช้จริงปีนี้</span>
                {editable && (
                  <>
                    <button className="btn sm" onClick={() => actions.setPattern(l.acct, null)}>แบ่งเท่ากัน 12 เดือน</button>
                    <select
                      value="" aria-label="ลงทั้งก้อนที่เดือนเดียว"
                      onChange={(e) => { if (e.target.value !== '') actions.setPattern(l.acct, Number(e.target.value)); }}
                    >
                      <option value="">จ่ายครั้งเดียว เดือน</option>
                      {R.MONTHS_TH.map((m, i) => <option key={i} value={i}>{m}</option>)}
                    </select>
                    {l.added && <button className="btn sm danger" onClick={() => actions.remove(l.acct)}>ลบรายการนี้</button>}
                  </>
                )}
              </div>
            </>
          )}
          {ev.flag !== 'none' && (
            <div className="flagmsg"><Icon name="alert" />{R.flagText(ev, cfg)}</div>
          )}
          {noteEditor ? (
            <div>
              <textarea
                rows={2} value={l.note} maxLength={2000}
                className={showErrors && ev.noteMissing ? 'invalid' : ''}
                aria-label={'เหตุผลหรือคำอธิบาย ' + l.name}
                placeholder={ev.flag === 'red' ? 'เหตุผลที่ขอเพิ่ม เช่น เพิ่มพนักงาน 1 อัตรา เริ่ม มี.ค.' : 'คำอธิบายกิจกรรมหรือหมายเหตุ (ไม่บังคับ)'}
                onChange={(e) => actions.setNote(l.acct, e.target.value)}
              />
              {showErrors && ev.noteMissing && <div className="err">ใส่เหตุผลก่อนส่ง</div>}
            </div>
          ) : l.note ? (
            <div className="small"><span className="mute">เหตุผล/คำอธิบาย: </span>{l.note}</div>
          ) : ev.flag === 'red' ? (
            <div className="small mute">ยังไม่ได้ใส่เหตุผล</div>
          ) : null}
          {open && l.updatedBy && (
            <div className="small mute">แก้ล่าสุดโดย {l.updatedBy} เมื่อ {fmtDateTime(l.updatedAt)}</div>
          )}
        </div>
      )}
    </div>
  );
}

function BudgetForm({ deptCode, me, onBack }) {
  const [data, setData] = useState(null);
  const [loadErr, setLoadErr] = useState('');
  const [lines, setLines] = useState([]);
  const [open, setOpen] = useState({});
  const [filter, setFilter] = useState('all');
  const [q, setQ] = useState('');
  const [save, setSave] = useState({ state: 'idle', at: null, error: '' });
  const [modal, setModal] = useState(null);
  const [busy, setBusy] = useState(false);
  const [showErrors, setShowErrors] = useState(false);
  const [returnNote, setReturnNote] = useState('');
  const [addQ, setAddQ] = useState('');

  // สถานะที่ต้องอ่านค่าล่าสุดจาก timer/async (ไม่ผูกกับรอบ render)
  const linesRef = useRef([]);
  const dirty = useRef(new Set());
  const weights = useRef({});
  const timer = useRef(null);
  const saving = useRef(false);

  const flush = useCallback(async () => {
    clearTimeout(timer.current);
    if (saving.current) { timer.current = setTimeout(flush, 300); return false; }
    const accts = Array.from(dirty.current);
    if (!accts.length) return true;
    saving.current = true;
    dirty.current = new Set();
    setSave((s) => ({ ...s, state: 'saving' }));
    const body = accts.map((acct) => {
      const l = linesRef.current.find((x) => x.acct === acct);
      return { acct, months: l ? l.months : null, note: l ? l.note : '' };
    });
    try {
      const r = await API.saveLines(deptCode, body);
      setSave({ state: dirty.current.size ? 'pending' : 'saved', at: r.savedAt, error: '' });
      return true;
    } catch (e) {
      accts.forEach((a) => dirty.current.add(a));
      setSave({ state: 'error', at: null, error: e.message });
      return false;
    } finally {
      saving.current = false;
    }
  }, [deptCode]);

  /** รอให้รอบบันทึกที่ค้างอยู่จบ แล้วบันทึกส่วนที่เหลือ — ใช้ก่อนกดส่ง */
  const flushAll = useCallback(async () => {
    while (saving.current) await new Promise((r) => setTimeout(r, 100));
    return flush();
  }, [flush]);

  const load = useCallback(async () => {
    try {
      const d = await API.form(deptCode);
      weights.current = {};
      d.lines.forEach((l) => { weights.current[l.acct] = patternWeights(l.months); });
      dirty.current = new Set();
      linesRef.current = d.lines;
      setLines(d.lines); setData(d); setLoadErr(''); setShowErrors(false);
      setSave({ state: 'idle', at: null, error: '' });
    } catch (e) { setLoadErr(e.message); }
  }, [deptCode]);

  useEffect(() => {
    setData(null); setOpen({}); setFilter('all'); setQ('');
    load();
    // ออกจากหน้านี้ทั้งที่ยังมีรายการค้าง → ส่งบันทึกทันที
    return () => { if (dirty.current.size) flush(); else clearTimeout(timer.current); };
  }, [load, flush]);

  useEffect(() => {
    const warn = (e) => { if (dirty.current.size || saving.current) { e.preventDefault(); e.returnValue = ''; } };
    window.addEventListener('beforeunload', warn);
    return () => window.removeEventListener('beforeunload', warn);
  }, []);

  const commit = (next, changedAccts) => {
    linesRef.current = next;
    setLines(next);
    if (changedAccts && changedAccts.length) {
      changedAccts.forEach((a) => dirty.current.add(a));
      setSave((s) => ({ ...s, state: 'pending' }));
      clearTimeout(timer.current);
      timer.current = setTimeout(flush, 800);
    }
  };
  const patch = (acct, fn) => commit(linesRef.current.map((l) => (l.acct === acct ? { ...l, ...fn(l) } : l)), [acct]);

  const actions = {
    // พิมพ์ยอดทั้งปี → กระจายตามรูปแบบเดิมของบรรทัดนั้น (เริ่มต้น = เฉลี่ย 12 เดือน)
    setTotal: (acct, total) => patch(acct, () => ({ months: total == null ? null : R.spread(total, weights.current[acct] || null) })),
    setMonth: (acct, i, x) => patch(acct, (l) => {
      const months = (l.months || R.zeros()).slice();
      months[i] = x == null ? 0 : x;
      weights.current[acct] = patternWeights(months);
      return { months };
    }),
    // w: null = เฉลี่ย 12 เดือน, ตัวเลข 0–11 = ลงทั้งก้อนที่เดือนนั้น
    setPattern: (acct, w) => {
      const wt = w == null ? null : R.spreadOnce(1, w);
      weights.current[acct] = wt;
      const l = linesRef.current.find((x) => x.acct === acct);
      if (l && Array.isArray(l.months)) patch(acct, (cur) => ({ months: R.spread(R.sum(cur.months), wt) }));
      else toast('เลือกรูปแบบแล้ว ใส่ยอดที่ช่อง "ขอปีหน้า" ได้เลย');
    },
    setNote: (acct, note) => patch(acct, () => ({ note })),
    copyBudget: (acct) => patch(acct, (l) => {
      const months = l.budget.map((x) => Math.max(0, R.round2(x)));
      weights.current[acct] = patternWeights(months);
      return { months };
    }),
    remove: (acct) => {
      const l = linesRef.current.find((x) => x.acct === acct);
      commit(linesRef.current.filter((x) => x.acct !== acct), [acct]);
      if (l) setData((d) => ({ ...d, available: [...d.available, { code: l.acct, name: l.name, cat: l.cat, filler: l.filler }].sort((a, b) => (a.code < b.code ? -1 : 1)) }));
    },
  };

  const cfg = data ? data.round : null;
  const canEdit = (l) => (l.filler === 'central' ? data.canEditCentral : data.canEditDept);
  const view = useMemo(
    () => (data ? lines.map((l) => ({ l, ev: R.evaluateLine(l, data.round), editable: l.filler === 'central' ? data.canEditCentral : data.canEditDept })) : []),
    [lines, data],
  );

  if (loadErr) {
    return <div className="card pad"><p className="err">{loadErr}</p><p><button className="btn sm" onClick={load}>ลองใหม่</button></p></div>;
  }
  if (!data) return <div className="boot">กำลังโหลด…</div>;

  const sum = summarize(lines, cfg);
  const counts = {
    all: view.length,
    todo: view.filter((v) => v.editable && !v.ev.filled).length,
    red: view.filter((v) => v.ev.flag === 'red').length,
    yellow: view.filter((v) => v.ev.flag === 'yellow').length,
  };
  const needle = q.trim().toLowerCase();
  const shown = view.filter((v) => {
    if (filter === 'todo' && !(v.editable && !v.ev.filled)) return false;
    if (filter === 'red' && v.ev.flag !== 'red') return false;
    if (filter === 'yellow' && v.ev.flag !== 'yellow') return false;
    return !needle || v.l.acct.includes(needle) || v.l.name.toLowerCase().includes(needle);
  });
  const groupsOf = (rows) => {
    const out = [];
    for (const v of rows) {
      let g = out.find((x) => x.cat === v.l.cat);
      if (!g) { g = { cat: v.l.cat, rows: [] }; out.push(g); }
      g.rows.push(v);
    }
    out.forEach((g) => g.rows.sort((a, b) => (a.l.acct < b.l.acct ? -1 : 1)));
    return out;
  };
  const ownShown = shown.filter((v) => v.l.filler !== 'central');
  const centralShown = shown.filter((v) => v.l.filler === 'central');

  const isOwner = me.role === 'dept' && me.dept === data.dept.code;
  const canSubmit = isOwner && data.canEditDept && (data.status === 'draft' || data.status === 'returned');
  const anyEditable = data.canEditDept || data.canEditCentral;

  const prefill = () => {
    const changed = [];
    const next = linesRef.current.map((l) => {
      if (!canEdit(l) || Array.isArray(l.months) || R.sum(l.budget) <= 0) return l;
      const months = l.budget.map((x) => Math.max(0, R.round2(x)));
      weights.current[l.acct] = patternWeights(months);
      changed.push(l.acct);
      return { ...l, months };
    });
    if (!changed.length) { toast('ไม่มีรายการว่างที่มีงบปีนี้ให้เติม'); return; }
    commit(next, changed);
    toast(`เติมยอดเท่างบปีนี้ ${changed.length} รายการ แก้เฉพาะรหัสที่เปลี่ยนได้เลย`);
  };

  const addLine = (a) => {
    const line = {
      acct: a.code, name: a.name, cat: a.cat, filler: a.filler, redPct: null,
      budget: R.zeros(), actual: R.zeros(), months: null, note: '', updatedBy: '', updatedAt: null, added: true,
    };
    commit([...linesRef.current, line], []);
    setData((d) => ({ ...d, available: d.available.filter((x) => x.code !== a.code) }));
    setModal(null); setAddQ(''); setFilter('all'); setQ('');
    setTimeout(() => {
      const row = document.getElementById('row-' + a.code);
      if (row) { row.scrollIntoView({ block: 'center' }); const inp = row.querySelector('input.amt'); if (inp) inp.focus(); }
    }, 60);
  };

  const trySubmit = () => {
    const own = view.filter((v) => v.l.filler !== 'central');
    const todo = own.filter((v) => !v.ev.filled).length;
    const miss = own.filter((v) => v.ev.noteMissing).length;
    if (todo) { setFilter('todo'); setQ(''); toast(`ยังกรอกไม่ครบ เหลือ ${todo} รายการ ถ้าไม่ขอให้ใส่ 0`, 'error'); return; }
    if (miss) { setShowErrors(true); setFilter('red'); setQ(''); toast(`ใส่เหตุผลให้ครบ ${miss} รายการที่ขึ้นแถบแดงก่อนส่ง`, 'error'); return; }
    setModal('submit');
  };
  const doSubmit = async () => {
    setBusy(true);
    try {
      if (!(await flushAll())) throw new Error('บันทึกรายการล่าสุดไม่สำเร็จ ลองใหม่อีกครั้ง');
      await API.submit(deptCode);
      setModal(null); toast('ส่งงบประมาณแล้ว');
      await load();
    } catch (e) { toast(e.message, 'error'); } finally { setBusy(false); }
  };
  const doStatus = async (action, note) => {
    setBusy(true);
    try {
      await flushAll();
      await API.setStatus(deptCode, action, note);
      setModal(null); setReturnNote('');
      toast(action === 'return' ? 'ตีกลับให้ฝ่ายแก้ไขแล้ว' : action === 'lock' ? 'ล็อกงบของฝ่ายนี้แล้ว' : 'ปลดล็อกแล้ว');
      await load();
    } catch (e) { toast(e.message, 'error'); } finally { setBusy(false); }
  };

  const renderRows = (rows) => groupsOf(rows).map((g) => {
    const gs = summarize(g.rows.map((v) => v.l), cfg);
    return (
      <div key={g.cat || '-'}>
        <div className="lgroup">
          <span>{g.cat || 'ไม่ระบุหมวด'}</span>
          <span className="sub">{g.rows.length} รายการ · งบปีนี้ {fmt(gs.B)} · คาดการณ์ทั้งปี {fmt(gs.P)} · ขอปีหน้า {fmt(gs.R)}</span>
        </div>
        {g.rows.map((v) => (
          <LineRow
            key={v.l.acct} v={v} cfg={cfg} showErrors={showErrors} actions={actions}
            open={!!open[v.l.acct]} onToggle={() => setOpen((o) => ({ ...o, [v.l.acct]: !o[v.l.acct] }))}
          />
        ))}
      </div>
    );
  });

  const addList = data.available.filter((a) => {
    const n = addQ.trim().toLowerCase();
    return !n || a.code.includes(n) || a.name.toLowerCase().includes(n);
  });

  return (
    <div>
      <div className="head">
        {onBack && <button className="btn ghost sm" onClick={onBack}><Icon name="back" />ภาพรวม</button>}
        <div className="grow">
          <h1>{data.dept.name}</h1>
          <div className="mute small">
            งบประมาณปี {cfg.year} · อ้างอิงงบและยอดใช้จริงปี {cfg.baseYear} ({actualRange(cfg)})
            {cfg.actualMonths > 0 && ` · คาดการณ์ทั้งปี = ใช้จริงเฉลี่ยต่อเดือน × 12`}
          </div>
        </div>
        <StatusBadge status={data.status} />
        {me.role === 'admin' && (data.status === 'submitted' || data.status === 'locked') && (
          <button className="btn sm" onClick={() => setModal('return')}><Icon name="undo" />ตีกลับให้แก้</button>
        )}
        {me.role === 'admin' && data.status === 'submitted' && (
          <button className="btn sm primary" disabled={busy} onClick={() => doStatus('lock')}><Icon name="lock" />ล็อก</button>
        )}
        {me.role === 'admin' && data.status === 'locked' && (
          <button className="btn sm" disabled={busy} onClick={() => doStatus('unlock')}>ปลดล็อก</button>
        )}
      </div>

      {data.status === 'returned' && (
        <div className="banner yellow">
          <Icon name="undo" />
          <div><b>ผู้ดูแลตีกลับให้แก้ไข:</b> {data.returnNote}<div className="small">แก้แล้วกดส่งงบประมาณอีกครั้ง</div></div>
        </div>
      )}
      {data.status === 'submitted' && (
        <div className="banner green">
          <Icon name="check" />
          <div>ส่งงบประมาณแล้วเมื่อ {fmtDateTime(data.submittedAt)} โดย {data.submittedBy} · รอผู้ดูแลตรวจ{isOwner ? ' ระหว่างนี้แก้ไขไม่ได้' : ''}</div>
        </div>
      )}
      {data.status === 'locked' && (
        <div className="banner green"><Icon name="lock" /><div>งบของฝ่ายนี้ล็อกแล้วเมื่อ {fmtDateTime(data.reviewedAt)}</div></div>
      )}
      {cfg.status !== 'open' && (
        <div className="banner"><Icon name="lock" /><div>ปิดรอบขออนุมัติงบแล้ว ดูได้อย่างเดียว</div></div>
      )}

      <div className="tiles">
        <div className="tile t-budget"><div className="k">งบปีนี้ ({cfg.baseYear})</div><div className="v">{fmt(sum.B)}</div><div className="s">{sum.n} รหัสบัญชี</div></div>
        <div className="tile t-actual">
          <div className="k">ใช้จริง {actualRange(cfg)}</div><div className="v">{fmt(sum.A)}</div>
          <div className="s">
            {sum.B > 0 ? Math.round((sum.A / sum.B) * 100) + '% ของงบ' : ''}
            {cfg.actualMonths > 0 && ` · คาดการณ์ทั้งปี ${fmt(sum.P)}`}
          </div>
        </div>
        <div className="tile t-request">
          <div className="k">ขอปีหน้า ({cfg.year})</div><div className="v">{fmt(sum.R)}</div>
          <div className="s">{sum.filled === sum.n && sum.pct != null ? fmtPct(sum.pct) + ' จากงบปีนี้' : `กรอกแล้ว ${sum.filled}/${sum.n} รายการ`}</div>
        </div>
        <div className="tile t-flag">
          <div className="k">รายการที่ต้องดู</div>
          <div className="v">{sum.red} <span className="s">แดง</span> · {sum.yellow} <span className="s">เหลือง</span></div>
          <div className="s">{sum.noteMissing ? `ยังไม่ใส่เหตุผล ${sum.noteMissing} รายการ` : 'แดงต้องใส่เหตุผลจึงส่งได้'}</div>
        </div>
      </div>

      <div className="card">
        <div className="toolbar">
          <div className="chips">
            <button className={'chip' + (filter === 'all' ? ' on' : '')} onClick={() => setFilter('all')}>ทั้งหมด {counts.all}</button>
            <button className={'chip' + (filter === 'todo' ? ' on' : '')} onClick={() => setFilter('todo')}>ยังไม่กรอก {counts.todo}</button>
            <button className={'chip red' + (filter === 'red' ? ' on' : '')} onClick={() => setFilter('red')}>แดง {counts.red}</button>
            <button className={'chip yellow' + (filter === 'yellow' ? ' on' : '')} onClick={() => setFilter('yellow')}>เหลือง {counts.yellow}</button>
          </div>
          <div className="search">
            <Icon name="search" />
            <input type="text" value={q} onChange={(e) => setQ(e.target.value)} placeholder="ค้นรหัสหรือชื่อบัญชี" aria-label="ค้นรหัสหรือชื่อบัญชี" />
          </div>
          <div style={{ flex: 1 }} />
          {anyEditable && <button className="btn sm" onClick={prefill} title="ใส่ยอดเท่างบปีนี้ให้ทุกบรรทัดที่ยังว่าง แล้วแก้เฉพาะรหัสที่เปลี่ยน"><Icon name="copy" />เติมบรรทัดว่างด้วยงบปีนี้</button>}
          {anyEditable && data.available.length > 0 && (
            <button className="btn sm" onClick={() => setModal('add')}><Icon name="plus" />เพิ่มรหัสบัญชี</button>
          )}
        </div>

        <div className="tw">
          <div className="tw-in">
            <div className="lgrid lhead">
              <div>รหัสบัญชี</div>
              <div className="num">งบปีนี้</div>
              <div className="num">ใช้จริง {actualRange(cfg)}</div>
              <div className="num" title="ใช้จริงเฉลี่ยต่อเดือน × 12">คาดการณ์ทั้งปี</div>
              <div className="num">ขอปีหน้า (ทั้งปี)</div>
              <div>แบ่งรายเดือน</div>
              <div className="num">เทียบงบ</div>
              <div />
            </div>

            {shown.length === 0 && (
              <div className="empty">
                {view.length === 0 ? 'ฝ่ายนี้ยังไม่มีรายการจากปีนี้ กด "เพิ่มรหัสบัญชี" เพื่อเริ่มกรอก'
                  : filter === 'todo' ? 'กรอกครบทุกรายการแล้ว' : filter === 'red' ? 'ไม่มีรายการแถบแดง' : filter === 'yellow' ? 'ไม่มีรายการแถบเหลือง' : 'ไม่พบรายการ'}
              </div>
            )}

            {renderRows(ownShown)}

            {centralShown.length > 0 && (
              <div>
                <div className="lgroup" style={{ borderTop: '1px solid var(--line2)', marginTop: 8, paddingTop: 16 }}>
                  <span>รายการที่บัญชีกรอกให้</span>
                  <span className="sub">
                    {data.canEditCentral ? 'ค่าเสื่อมราคา ต้นทุนทางการเงิน ภาษี และรายการปรับปรุงทางบัญชี' : 'ฝ่ายไม่ต้องกรอก บัญชีจะใส่ตัวเลขให้'}
                  </span>
                </div>
                {renderRows(centralShown)}
              </div>
            )}
          </div>
        </div>
      </div>

      <div className="footbar">
        <div className="footbar-in">
          <span className={'savestate' + (save.state === 'error' ? ' error' : '')}>
            {save.state === 'saving' || save.state === 'pending' ? 'กำลังบันทึก…'
              : save.state === 'saved' ? <><Icon name="check" />บันทึกแล้ว {fmtTime(save.at)}</>
              : save.state === 'error' ? <>บันทึกไม่สำเร็จ: {save.error} <button className="link" onClick={flush}>ลองใหม่</button></>
              : anyEditable ? 'บันทึกอัตโนมัติทุกครั้งที่แก้' : 'ดูได้อย่างเดียว'}
          </span>
          <span className="grow" />
          <span className="small mute">
            ฝ่ายกรอกแล้ว {sum.ownFilled}/{sum.own}
            {sum.central > 0 && ` · บัญชีกรอกแล้ว ${sum.centralFilled}/${sum.central}`}
            {' · '}ขอปีหน้ารวม <b style={{ color: 'var(--ink)' }}>{fmt(sum.R)}</b> บาท
          </span>
          {canSubmit && <button className="btn primary" onClick={trySubmit}>ส่งงบประมาณ</button>}
        </div>
      </div>

      {modal === 'submit' && (
        <Modal
          title="ส่งงบประมาณ" onClose={() => setModal(null)}
          footer={<><button className="btn" onClick={() => setModal(null)}>ยังก่อน</button><button className="btn primary" disabled={busy} onClick={doSubmit}>ส่งงบประมาณ</button></>}
        >
          <p>ส่งงบประมาณปี {cfg.year} ของ {data.dept.name}</p>
          <p>ขอรวม <b>{fmt(sum.R)}</b> บาท {sum.pct != null && <span className="mute">({fmtPct(sum.pct)} จากงบปีนี้)</span>} · แถบแดง {sum.red} รายการ</p>
          <p className="mute small">ส่งแล้วจะแก้ไขไม่ได้ จนกว่าผู้ดูแลจะตีกลับให้แก้</p>
        </Modal>
      )}

      {modal === 'return' && (
        <Modal
          title={'ตีกลับให้ ' + data.dept.code + ' แก้ไข'} onClose={() => setModal(null)}
          footer={<><button className="btn" onClick={() => setModal(null)}>ยกเลิก</button><button className="btn primary" disabled={busy || !returnNote.trim()} onClick={() => doStatus('return', returnNote)}>ตีกลับ</button></>}
        >
          <label className="field">สิ่งที่ต้องการให้ฝ่ายแก้ไข (ฝ่ายจะเห็นข้อความนี้)
            <textarea rows={4} value={returnNote} onChange={(e) => setReturnNote(e.target.value)} style={{ marginTop: 4, color: 'var(--ink)', fontSize: 15 }} />
          </label>
        </Modal>
      )}

      {modal === 'add' && (
        <Modal title="เพิ่มรหัสบัญชี" wide onClose={() => { setModal(null); setAddQ(''); }}>
          <p className="mute small">รหัสที่ปีนี้ฝ่ายไม่มีงบและไม่มียอดใช้จริง เลือกแล้วใส่ยอดที่ช่อง "ขอปีหน้า"</p>
          <div className="search">
            <Icon name="search" />
            <input type="text" autoFocus value={addQ} onChange={(e) => setAddQ(e.target.value)} placeholder="ค้นรหัสหรือชื่อบัญชี" aria-label="ค้นรหัสบัญชีที่จะเพิ่ม" style={{ width: '100%', height: 36 }} />
          </div>
          <div className="picklist">
            {addList.length === 0 && <div className="empty">ไม่พบรหัสบัญชี</div>}
            {addList.map((a) => (
              <button key={a.code} onClick={() => addLine(a)}>
                <span className="mute num" style={{ width: 70, textAlign: 'left' }}>{a.code}</span>
                <span style={{ flex: 1 }}>{a.name}</span>
                <span className="mute small">{a.cat}</span>
              </button>
            ))}
          </div>
        </Modal>
      )}
    </div>
  );
}
