/* pnl.jsx — รายงานงบประมาณแบบงบกำไรขาดทุน (ผู้ดูแล/ฝ่ายบัญชี ดูได้อย่างเดียว)
 * โครงตามไฟล์ Excel งบประมาณประจำปีของบริษัท:
 *   รายได้ → ต้นทุนงานก่อสร้าง → กำไรขั้นต้น → ค่าใช้จ่ายขาย / บริหาร / ต้นทุนทางการเงิน → กำไร(ขาดทุน)สุทธิ
 * ส่วนค่าใช้จ่าย = ยอดงบที่ทุกฝ่ายขอ จัดกลุ่มด้วย R.plGroupOf (ผู้ดูแลกำหนดรายรหัสได้ที่ ตั้งค่า > รหัสบัญชี)
 * ★ รายได้และต้นทุนงานก่อสร้างยังไม่มีแหล่งข้อมูล — pnlInputs() ด้านล่างเป็นจุดเดียวที่ต้องต่อข้อมูลเข้ามาภายหลัง
 *   เมื่อมีข้อมูลแล้ว กำไรขั้นต้น กำไรสุทธิ และ % ต่อรายได้ จะคำนวณเองทั้งตาราง
 * ใช้ของร่วมจาก report.jsx: usePresentMode, useElementWidth, niceTicks, fmtCompact, signedAmount */

const PNL_COLORS = { sell: '#5146c9', admin: '#1a66c4', finance: '#0b8f9c', other: '#7a869f', prev: '#8a96ad' };
const PNL_EXPENSE_KEYS = ['sell', 'admin', 'finance'];

/**
 * ข้อมูลนำเข้าที่ไม่ได้มาจากงบที่ฝ่ายขอ: รายได้จากการขาย รายได้อื่น ต้นทุนงานก่อสร้าง
 * แต่ละตัวเป็น null (ยังไม่มีข้อมูล) หรือ { m: ยอด 12 เดือนของปีที่ขอ, prev: ยอดทั้งปีของปีอ้างอิง }
 */
function pnlInputs() {
  return { sales: null, otherIncome: null, cogs: null };
}

/** ยอดรายเดือนของปีอ้างอิงที่ใช้เทียบ: Budget ตามที่ตั้งไว้ หรือ Forecast (เดือนที่มี Actual ใช้ Actual ที่เหลือใช้ค่าเฉลี่ย) */
function pnlPrevMonths(l, cfg, mode) {
  if (mode !== 'forecast') return l.budget;
  const n = Math.min(12, Math.max(0, Number(cfg.actualMonths) || 0));
  if (n === 0) return R.zeros();
  const avg = R.sum(l.actual.slice(0, n)) / n;
  return l.actual.map((v, i) => (i < n ? Number(v) || 0 : avg));
}

/** รวมชุดบรรทัด [{ l, ev }] เป็นยอด 12 เดือน + ยอดทั้งปี + ยอดปีอ้างอิง · ส่วนต่างมีความหมายเมื่อกรอกครบ (complete) */
function pnlBucket(items, cfg, mode) {
  const m = R.zeros(), pm = R.zeros();
  let filled = 0, prev = 0;
  for (const { l, ev } of items) {
    if (Array.isArray(l.months)) { filled++; for (let i = 0; i < 12; i++) m[i] += Number(l.months[i]) || 0; }
    const p = pnlPrevMonths(l, cfg, mode);
    for (let i = 0; i < 12; i++) pm[i] += Number(p[i]) || 0;
    prev += mode === 'forecast' ? (ev.P || 0) : ev.B;
  }
  const b = { items, n: items.length, filled, complete: items.length > 0 && filled === items.length, m: m.map(R.round2), prevM: pm.map(R.round2) };
  b.total = R.sum(b.m);
  b.prev = R.round2(prev);
  b.diff = R.round2(b.total - b.prev);
  b.pct = b.prev > 0 ? (b.diff / b.prev) * 100 : null;
  return b;
}

/** จัดกลุ่มบรรทัดตามคีย์ แล้วรวมเป็น bucket เรียงยอดขอมากไปน้อย */
function pnlGroupBy(items, keyOf, labelOf, cfg, mode) {
  const map = new Map();
  for (const x of items) {
    const k = keyOf(x);
    let g = map.get(k);
    if (!g) { g = { key: k, label: labelOf(x), items: [] }; map.set(k, g); }
    g.items.push(x);
  }
  return Array.from(map.values())
    .map((g) => ({ ...g, b: pnlBucket(g.items, cfg, mode) }))
    .sort((a, b) => b.b.total - a.b.total || b.b.prev - a.b.prev || String(a.key).localeCompare(String(b.key), 'th'));
}

/* ═══════════════════════ กราฟแท่งซ้อนรายเดือน ═══════════════════════ */

/** แท่งซ้อน 12 เดือน (stacks = [{ key, label, color, values }]) + เส้นเทียบปีอ้างอิง (line = { label, color, values, total? }) */
function StackedColumns({ labels, stacks, line, height, totalLabel }) {
  const box = useRef(null);
  const width = useElementWidth(box);
  const [hi, setHi] = useState(null);
  const H = height || 280;
  const m = { l: 50, r: 16, t: 10, b: 26 };
  const totals = labels.map((_, i) => stacks.reduce((t, s) => t + (s.values[i] || 0), 0));
  const max = Math.max(0, ...totals, ...(line ? line.values : []));
  const ticks = niceTicks(max, 4);
  const top = ticks[ticks.length - 1] || 1;
  const iw = Math.max(10, width - m.l - m.r), ih = H - m.t - m.b;
  const band = iw / labels.length;
  const bw = Math.min(48, band * 0.62);
  const cx = (i) => m.l + band * (i + 0.5);
  const y = (v) => m.t + ih - (ih * v) / top;
  const val = (arr) => (hi == null ? R.sum(arr) : arr[hi]);
  return (
    <div className="chart" ref={box}>
      <div className="readout">
        <span className="when">{hi == null ? totalLabel : labels[hi]}</span>
        {stacks.map((s) => <span key={s.key}><i className="sq" style={{ background: s.color }} />{s.label} <b>{fmt(val(s.values))}</b></span>)}
        <span>รวม <b>{fmt(hi == null ? R.sum(totals) : totals[hi])}</b></span>
        {line && (
          <span>
            <svg className="sw" width="22" height="8" aria-hidden="true"><line x1="1" x2="21" y1="4" y2="4" stroke={line.color} strokeWidth="3" strokeDasharray="4 3" strokeLinecap="round" /></svg>
            {line.label} <b>{fmt(hi == null && line.total != null ? line.total : val(line.values))}</b>
          </span>
        )}
      </div>
      {width > 0 && (
        <svg width={width} height={H} role="img" aria-label={'กราฟแท่งรายเดือน ' + stacks.map((s) => s.label).join(' / ')} onMouseLeave={() => setHi(null)}>
          {ticks.map((t) => (
            <g key={t}>
              <line x1={m.l} x2={width - m.r} y1={y(t)} y2={y(t)} className="grid" />
              <text x={m.l - 8} y={y(t)} className="tick" textAnchor="end" dominantBaseline="middle">{fmtCompact(t)}</text>
            </g>
          ))}
          {labels.map((lb, i) => <text key={lb} x={cx(i)} y={H - 7} className={'tick' + (hi === i ? ' on' : '')} textAnchor="middle">{lb}</text>)}
          {labels.map((lb, i) => {
            let acc = 0;
            return stacks.map((s) => {
              const v = s.values[i] || 0, y1 = y(acc + v), h = y(acc) - y1;
              acc += v;
              return <rect key={lb + s.key} x={cx(i) - bw / 2} y={y1} width={bw} height={Math.max(0, h)} fill={s.color} opacity={hi == null || hi === i ? 1 : 0.5} />;
            });
          })}
          {line && (
            <>
              <path d={line.values.map((v, i) => (i ? 'L' : 'M') + cx(i).toFixed(1) + ' ' + y(v).toFixed(1)).join('')} fill="none" stroke={line.color} strokeWidth="2.4" strokeDasharray="6 4" strokeLinecap="round" strokeLinejoin="round" />
              {line.values.map((v, i) => <circle key={'p' + i} cx={cx(i)} cy={y(v)} r={hi === i ? 4.5 : 2.6} fill="#fff" stroke={line.color} strokeWidth="2" />)}
            </>
          )}
          {labels.map((lb, i) => <rect key={'hit' + lb} x={m.l + band * i} y={m.t} width={band} height={ih + m.b} fill="transparent" onMouseEnter={() => setHi(i)} />)}
        </svg>
      )}
    </div>
  );
}

/* ═══════════════════════ รายละเอียดของตัวเลขที่คลิก ═══════════════════════ */

/** หน้าต่างรายละเอียด: ตัวเลขนี้มาจากค่าใช้จ่าย/หมวด/ฝ่ายไหนบ้าง · period = 0–11 (เดือน) หรือ null (ทั้งปี) */
function PnlDetail({ title, items, period, cfg, mode, prevLabel, onClose }) {
  const [by, setBy] = useState('acct');
  const spec = {
    acct: { key: (x) => x.l.acct, label: (x) => ({ code: x.l.acct, name: x.l.name, sub: x.l.cat }), col: 'ค่าใช้จ่าย (รหัสบัญชี)', count: 'ฝ่าย' },
    cat: { key: (x) => x.l.cat || 'ไม่ระบุหมวด', label: (x) => ({ code: '', name: x.l.cat || 'ไม่ระบุหมวด', sub: '' }), col: 'หมวดค่าใช้จ่าย', count: 'รายการ' },
    dept: { key: (x) => x.d.code, label: (x) => ({ code: x.d.code, name: x.d.name, sub: '' }), col: 'ฝ่าย', count: 'รายการ' },
  }[by];
  const pick = (b) => (period == null ? { req: b.total, prev: b.prev } : { req: b.m[period], prev: b.prevM[period] });
  const groups = pnlGroupBy(items, spec.key, spec.label, cfg, mode)
    .map((g) => ({ ...g, ...pick(g.b) }))
    .sort((a, b) => b.req - a.req || b.prev - a.prev);
  const T = pick(pnlBucket(items, cfg, mode));
  const unfilled = items.filter((x) => !x.ev.filled).length;
  const when = period == null ? 'ทั้งปี' : R.MONTHS_TH[period];
  return (
    <Modal title={title} wide="xl" onClose={onClose} footer={<button className="btn primary" onClick={onClose}>ปิด</button>}>
      <div className="toolbar plain">
        <div className="seg" role="group" aria-label="แยกรายละเอียดตาม">
          <button className={by === 'acct' ? 'on' : ''} onClick={() => setBy('acct')}>ตามค่าใช้จ่าย</button>
          <button className={by === 'cat' ? 'on' : ''} onClick={() => setBy('cat')}>ตามหมวด</button>
          <button className={by === 'dept' ? 'on' : ''} onClick={() => setBy('dept')}>ตามฝ่าย</button>
        </div>
        <span className="mute small">{groups.length} รายการ{unfilled > 0 && ` · ยังไม่กรอก ${unfilled} บรรทัด (ยอดขอยังไม่รวมส่วนนี้)`}</span>
      </div>
      <div className="tw detailtw">
        <table className="t">
          <thead>
            <tr>
              <th>{spec.col}</th><th className="num">จำนวน{spec.count}</th>
              <th className="num">ยอดขอ {when} {cfg.year}</th><th className="num">{prevLabel} {when}</th><th className="num">ส่วนต่าง</th><th className="num">%</th>
            </tr>
          </thead>
          <tbody>
            {groups.length === 0 && <tr><td colSpan={6} className="empty">ไม่มีรายการ</td></tr>}
            {groups.map((g) => {
              const diff = R.round2(g.req - g.prev);
              return (
                <tr key={g.key}>
                  <td>
                    {g.label.code && (by === 'dept' ? <b>{g.label.code} </b> : <span className="mute small">{g.label.code} </span>)}
                    {by === 'dept' ? <span className="mute small">{g.label.name}</span> : g.label.name}
                    {g.label.sub && <span className="mute small"> · {g.label.sub}</span>}
                  </td>
                  <td className="num">{g.b.n}</td>
                  <td className="num"><b>{g.b.filled ? fmt(g.req) : '–'}</b></td>
                  <td className="num">{fmt(g.prev)}</td>
                  <td className="num">{g.b.complete ? signedAmount(diff) : <span className="mute small">กรอก {g.b.filled}/{g.b.n}</span>}</td>
                  <td className="num">{!g.b.complete ? '' : g.prev > 0 ? fmtPct((diff / g.prev) * 100) : g.req > 0 ? 'ใหม่' : '–'}</td>
                </tr>
              );
            })}
          </tbody>
          {groups.length > 0 && (
            <tfoot>
              <tr>
                <td colSpan={2}>รวม</td>
                <td className="num">{fmt(T.req)}</td><td className="num">{fmt(T.prev)}</td>
                <td className="num">{unfilled ? '' : signedAmount(R.round2(T.req - T.prev))}</td>
                <td className="num">{unfilled || !(T.prev > 0) ? '' : fmtPct(((T.req - T.prev) / T.prev) * 100)}</td>
              </tr>
            </tfoot>
          )}
        </table>
      </div>
    </Modal>
  );
}

/* ═══════════════════════ Excel ═══════════════════════ */

function exportPnl(cfg, rows, prevLabel, groups) {
  const yy = String(cfg.year).slice(-2);
  const head = ['รายการ', ...R.MONTHS_TH.map((m) => m + '-' + yy), 'ปี ' + cfg.year, '% ต่อรายได้', prevLabel, 'ผลต่าง', '% ผลต่าง'];
  const pct1 = (p) => (p == null || !isFinite(p) ? null : Math.round(p * 10) / 10);
  const line = (label, v) => (v
    ? [label, ...v.m, v.total, pct1(v.revPct), v.prev, v.complete === false ? null : R.round2(v.total - v.prev), v.complete === false ? null : pct1(v.pct)]
    : [label, ...Array(12).fill(null), null, null, null, null, null]);
  const sheet = [head];
  for (const r of rows) {
    sheet.push(line((r.indent ? '   ' : '') + r.label + (r.v ? '' : ' (รอข้อมูล)'), r.v));
    for (const s of r.subs || []) sheet.push(line('      ' + s.label, s.b));
  }
  const detail = [['กลุ่มในงบกำไรขาดทุน', 'หมวด', 'รหัสบัญชี', 'ชื่อค่าใช้จ่าย', ...R.MONTHS_TH.map((m) => m + '-' + yy), 'ปี ' + cfg.year, prevLabel, 'จำนวนฝ่าย', 'ฝ่ายที่กรอกแล้ว']];
  for (const g of groups) {
    for (const a of pnlGroupBy(g.b.items, (x) => x.l.acct, (x) => ({ name: x.l.name, cat: x.l.cat }), cfg, g.mode)) {
      detail.push([g.label, a.label.cat, a.key, a.label.name, ...a.b.m, a.b.total, a.b.prev, a.b.n, a.b.filled]);
    }
  }
  const wb = XLSX.utils.book_new();
  const add = (name, aoa, widths) => {
    const ws = XLSX.utils.aoa_to_sheet(aoa);
    ws['!cols'] = widths.map((wch) => ({ wch }));
    XLSX.utils.book_append_sheet(wb, ws, name);
  };
  add('งบกำไรขาดทุน', sheet, [38, ...Array(12).fill(13), 15, 10, 15, 15, 10]);
  add('รายละเอียดค่าใช้จ่าย', detail, [26, 28, 10, 40, ...Array(12).fill(12), 14, 14, 10, 12]);
  const d = new Date();
  const stamp = d.getFullYear() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0');
  XLSX.writeFile(wb, `งบประมาณแบบงบกำไรขาดทุน${cfg.year}_${stamp}.xlsx`);
}

/* ═══════════════════════ หน้ารายงาน ═══════════════════════ */

function PnlPage() {
  const { all, err, load } = useAdminAll();
  const [mode, setMode] = useState('budget');       // เทียบกับ Budget หรือ Forecast ของปีอ้างอิง
  const [sentOnly, setSentOnly] = useState(false);
  const [open, setOpen] = useState({});             // กลุ่มค่าใช้จ่ายที่ขยายดูรายหมวด
  const [detail, setDetail] = useState(null);       // { title, items, period } ของตัวเลขที่คลิก
  const [present, togglePresent] = usePresentMode();
  const flat = useMemo(() => {
    const out = [];
    if (all) for (const d of all.depts) for (const l of d.lines) out.push({ d, l, ev: R.evaluateLine(l, all.round) });
    return out;
  }, [all]);

  if (err) return <div className="card pad"><p className="err">{err}</p><p><button className="btn sm" onClick={load}>ลองใหม่</button></p></div>;
  if (!all) return <div className="boot">กำลังโหลด…</div>;
  const cfg = all.round;
  const L = labelsOf(cfg);
  const yy = String(cfg.year).slice(-2);
  const isSent = (d) => d.status === 'submitted' || d.status === 'locked';
  const scoped = flat.filter((x) => !sentOnly || isSent(x.d));
  const prevLabel = mode === 'forecast' ? 'Forecast ' + cfg.baseYear : L.budget;
  const groupLabel = (key) => (R.PL_GROUPS.find((g) => g.key === key) || { label: key }).label;

  // ── ค่าใช้จ่ายจากงบที่ขอ แยก 3 กลุ่ม + รายการอื่นที่ไม่รวมในงบนี้ ──
  const B = {};
  for (const key of [...PNL_EXPENSE_KEYS, 'other']) B[key] = pnlBucket(scoped.filter((x) => R.plGroupOf(x.l) === key), cfg, mode);
  B.opex = pnlBucket(scoped.filter((x) => PNL_EXPENSE_KEYS.includes(R.plGroupOf(x.l))), cfg, mode);

  // ── รายได้/ต้นทุนงานก่อสร้าง (ยังไม่มีข้อมูล) → ตัวเลขที่คำนวณต่อ ──
  const input = pnlInputs();
  const asRow = (x) => (x ? { m: x.m, total: R.sum(x.m), prev: x.prev, pct: x.prev > 0 ? ((R.sum(x.m) - x.prev) / x.prev) * 100 : null } : null);
  const combine = (a, b, sign) => (a && b ? asRow({ m: a.m.map((v, i) => R.round2(v + sign * b.m[i])), prev: R.round2(a.prev + sign * b.prev) }) : null);
  const sales = asRow(input.sales), otherIncome = asRow(input.otherIncome), cogs = asRow(input.cogs);
  const revenue = combine(sales, otherIncome, 1);
  const gross = combine(revenue, cogs, -1);
  const net = combine(gross, B.opex, -1);
  // % ต่อรวมรายได้ของปีที่ขอ (ยังไม่มีรายได้ = null)
  const revPctOf = (v) => (v && revenue && revenue.total > 0 ? (v.total / revenue.total) * 100 : null);
  const withRev = (v) => (v ? { ...v, revPct: revPctOf(v) } : null);
  const hasRevenue = !!revenue;

  const subsOf = (key) => (open[key] ? pnlGroupBy(B[key].items, (x) => x.l.cat || 'ไม่ระบุหมวด', (x) => x.l.cat || 'ไม่ระบุหมวด', cfg, mode) : []);
  const rows = [
    { key: 'sales', label: 'รายได้จากการขาย', band: 'rev', indent: true, v: withRev(sales) },
    { key: 'otherIncome', label: 'รายได้อื่นๆ', band: 'rev', indent: true, v: withRev(otherIncome) },
    { key: 'revenue', label: 'รวมรายได้', strong: true, v: withRev(revenue) },
    { key: 'cogs', label: 'ต้นทุนงานก่อสร้าง', band: 'cost', indent: true, v: withRev(cogs) },
    { key: 'cogsTotal', label: 'รวมต้นทุนงานก่อสร้าง', strong: true, v: withRev(cogs) },
    { key: 'gross', label: 'กำไรขั้นต้น', band: 'profit', strong: true, v: withRev(gross) },
    ...PNL_EXPENSE_KEYS.map((key) => ({ key, label: groupLabel(key), indent: true, v: withRev(B[key]), items: B[key].items, expandable: true, subs: subsOf(key), color: PNL_COLORS[key] })),
    { key: 'opex', label: 'รวมค่าใช้จ่ายขายและบริหาร', strong: true, v: withRev(B.opex), items: B.opex.items },
    { key: 'net', label: 'กำไร(ขาดทุน)สุทธิ', band: 'profit', strong: true, v: withRev(net) },
  ];

  const showDetail = (label, items, period) => setDetail({
    title: label + ' · ' + (period == null ? 'ทั้งปี ' + cfg.year : R.MONTHS_TH[period] + ' ' + cfg.year), items, period,
  });
  /** ช่องตัวเลข: มีรายการข้างใน = คลิกดูรายละเอียดได้ */
  const cell = (key, value, label, items, period, cls) => (
    items ? (
      <td key={key} className={'click ' + (cls || '')} onClick={() => showDetail(label, items, period)} title="คลิกเพื่อดูว่าตัวเลขนี้มาจากอะไรบ้าง">{fmt(value)}</td>
    ) : <td key={key} className={cls || ''}>{value == null ? '–' : fmt(value)}</td>
  );
  const renderRow = (r, sub) => {
    const v = sub ? sub.b : r.v;
    const label = sub ? sub.label : r.label;
    const items = sub ? sub.b.items : r.items;
    const cls = (sub ? 'subrow' : (r.band || '') + (r.strong ? ' strong' : '')) + (v ? '' : ' pend');
    const isOpen = !!open[r.key];
    return (
      <tr key={sub ? r.key + '/' + sub.key : r.key} className={cls}>
        <td className={sub ? 'in2' : r.indent ? 'in1' : ''}>
          {!sub && r.expandable ? (
            <button className="rowbtn" onClick={() => setOpen((o) => ({ ...o, [r.key]: !isOpen }))} aria-expanded={isOpen} title="คลิกเพื่อดูรายหมวด">
              <i className="sq" style={{ background: r.color }} />{label}<Icon name="chevron" className={'chev' + (isOpen ? ' open' : '')} />
            </button>
          ) : label}
          {!v && <span className="badge wait">รอข้อมูล</span>}
        </td>
        {R.MONTHS_TH.map((mo, i) => cell(mo, v ? v.m[i] : null, label, v && items, i))}
        {cell('total', v ? v.total : null, label, v && items, null, 'tot')}
        <td className="pc">{revPctOf(v) == null ? '–' : revPctOf(v).toFixed(1) + '%'}</td>
        {cell('prev', v ? v.prev : null, label, v && items, null, 'tot')}
        <td>{!v ? '–' : v.complete === false ? <span className="mute small">กรอก {v.filled}/{v.n}</span> : signedAmount(R.round2(v.total - v.prev))}</td>
        <td className="pc">{!v || v.complete === false ? '' : v.pct == null ? (v.total > 0 ? 'ใหม่' : '–') : fmtPct(v.pct)}</td>
      </tr>
    );
  };

  const E = B.opex;
  const expGroups = PNL_EXPENSE_KEYS.map((key) => ({ key, label: groupLabel(key), color: PNL_COLORS[key], b: B[key], mode }));
  const maxCmp = Math.max(1, ...expGroups.map((g) => Math.max(g.b.total, g.b.prev)));
  const anyOpen = PNL_EXPENSE_KEYS.some((k) => open[k]);
  return (
    <div className="report pnl">
      <div className="head">
        <div className="grow">
          <h1>รายงานงบประมาณแบบงบกำไรขาดทุน ปี {cfg.year}</h1>
          <div className="mute small">
            บริษัท วอเทอร์ป๊อก จำกัด · ค่าใช้จ่ายมาจากงบที่{sentOnly ? 'ฝ่ายที่ส่งแล้ว' : 'ทุกฝ่าย'}ขอ · เทียบกับ {prevLabel}
          </div>
        </div>
        <button className="btn noprint" onClick={togglePresent}><Icon name={present ? 'x' : 'screen'} />{present ? 'ออกจากโหมดนำเสนอ' : 'โหมดนำเสนอ'}</button>
        <button className="btn noprint" onClick={() => window.print()}><Icon name="print" />พิมพ์ / PDF</button>
        <button className="btn noprint" onClick={() => exportPnl(cfg, rows.map((r) => ({ ...r, subs: r.expandable ? pnlGroupBy(r.items, (x) => x.l.cat || 'ไม่ระบุหมวด', (x) => x.l.cat || 'ไม่ระบุหมวด', cfg, mode) : [] })), prevLabel, expGroups)}>
          <Icon name="download" />Export Excel
        </button>
      </div>

      {!hasRevenue && (
        <div className="banner">
          <Icon name="alert" />
          <div>
            <b>ยังไม่มีข้อมูลรายได้และต้นทุนงานก่อสร้าง</b> — ตอนนี้รายงานแสดงโครงครบทุกบรรทัด แต่มีตัวเลขเฉพาะส่วนค่าใช้จ่ายจากงบที่ขอ
            <div className="small">เมื่อเชื่อมข้อมูลรายได้และต้นทุนแล้ว กำไรขั้นต้น กำไร(ขาดทุน)สุทธิ และ % ต่อรายได้ จะคำนวณให้เองทั้งตาราง</div>
          </div>
        </div>
      )}

      <div className="card rfilters noprint">
        <div className="toolbar">
          <span className="mute small">เทียบกับ</span>
          <div className="seg lg" role="group" aria-label="ตัวเลขปีอ้างอิงที่ใช้เทียบ">
            <button className={mode === 'budget' ? 'on' : ''} onClick={() => setMode('budget')}>{L.budget}</button>
            <button className={mode === 'forecast' ? 'on' : ''} onClick={() => setMode('forecast')} title="Actual เฉลี่ยต่อเดือน × 12">Forecast {cfg.baseYear}</button>
          </div>
          <span className="sep" />
          <div className="chips">
            <button className={'chip done' + (sentOnly ? ' on' : '')} aria-pressed={sentOnly} onClick={() => setSentOnly(!sentOnly)} title="นับเฉพาะฝ่ายที่กดส่งงบแล้ว (รวมที่ล็อกแล้ว)">เฉพาะฝ่ายที่ส่งแล้ว</button>
          </div>
          <div style={{ flex: 1 }} />
          <button className="btn sm" onClick={() => setOpen(anyOpen ? {} : Object.fromEntries(PNL_EXPENSE_KEYS.map((k) => [k, true])))}>{anyOpen ? 'พับรายหมวด' : 'ขยายดูรายหมวด'}</button>
        </div>
      </div>

      <div className="tiles">
        <div className="tile t-request big">
          <div className="k">รวมค่าใช้จ่ายขายและบริหาร ปี {cfg.year}</div>
          <div className="v">{fmt(E.total)} <span className="s">บาท</span></div>
          <div className="s">
            {E.n === 0 ? 'ยังไม่มีฝ่ายที่ส่งงบ'
              : E.complete ? `${signedAmount(E.diff)} บาท${E.pct != null ? ' (' + fmtPct(E.pct) + ')' : ''} เทียบ ${prevLabel}`
              : `กรอกแล้ว ${E.filled}/${E.n} รายการ · ส่วนต่างจะแสดงเมื่อกรอกครบ`}
          </div>
        </div>
        {expGroups.map((g) => (
          <div className="tile" key={g.key} style={{ '--accent': g.color }}>
            <div className="k">{g.label}</div>
            <div className="v">{fmt(g.b.total)}</div>
            <div className="s">
              {E.total > 0 ? ((g.b.total / E.total) * 100).toFixed(1) + '% ของค่าใช้จ่าย' : prevLabel + ' ' + fmt(g.b.prev)}
              {g.b.complete && g.b.pct != null && ` · ${fmtPct(g.b.pct)} เทียบ ${prevLabel}`}
            </div>
          </div>
        ))}
        <div className="tile t-budget">
          <div className="k">กำไร(ขาดทุน)สุทธิ</div>
          <div className="v">{net ? fmt(net.total) : <span className="s">รอข้อมูลรายได้</span>}</div>
          <div className="s">= รวมรายได้ − ต้นทุนงานก่อสร้าง − ค่าใช้จ่ายขายและบริหาร</div>
        </div>
      </div>

      <div className="card pltw">
        <table className="pl">
          <thead>
            <tr>
              <th>เดือน/ปี</th>
              {R.MONTHS_TH.map((mo) => <th key={mo}>{mo}-{yy}</th>)}
              <th className="tot">ปี {cfg.year}</th><th className="pc" title="% ต่อรวมรายได้">%</th>
              <th className="tot">{prevLabel}</th><th>ผลต่าง</th><th className="pc">%</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => [renderRow(r), ...(r.subs || []).map((s) => renderRow(r, s))])}
            <tr className={'pctrow' + (net ? '' : ' pend')}>
              <td>% กำไรสุทธิต่อรายได้{!net && <span className="badge wait">รอข้อมูล</span>}</td>
              {R.MONTHS_TH.map((mo, i) => <td key={mo}>{net && revenue.m[i] > 0 ? ((net.m[i] / revenue.m[i]) * 100).toFixed(1) + '%' : '–'}</td>)}
              <td className="tot">{net && revenue.total > 0 ? ((net.total / revenue.total) * 100).toFixed(1) + '%' : '–'}</td><td className="pc" />
              <td className="tot">{net && revenue.prev > 0 ? ((net.prev / revenue.prev) * 100).toFixed(1) + '%' : '–'}</td><td /><td className="pc" />
            </tr>
          </tbody>
        </table>
      </div>
      {B.other.n > 0 && (B.other.total > 0 || B.other.prev > 0) && (
        <p className="small" style={{ marginTop: 8 }}>
          <span className="mute">ไม่รวมในงบนี้: </span>
          <button className="link" onClick={() => showDetail(groupLabel('other'), B.other.items, null)}>
            {groupLabel('other')} {fmt(B.other.total)} บาท ({prevLabel} {fmt(B.other.prev)})
          </button>
          <span className="mute"> · เช่น ภาษีเงินได้ เงินปันผล — เปลี่ยนกลุ่มได้ที่ ตั้งค่า &gt; รหัสบัญชี</span>
        </p>
      )}

      <div className="charts" style={{ marginTop: 14 }}>
        <div className="card chartcard">
          <div className="charthead"><h2>ค่าใช้จ่ายรายเดือน ปี {cfg.year} <span className="mute small">· แยกตามประเภท เทียบ {prevLabel}</span></h2></div>
          {E.n === 0 ? <div className="empty">ยังไม่มีข้อมูล</div> : (
            <StackedColumns
              labels={R.MONTHS_TH} totalLabel="รวมทั้งปี" height={280}
              stacks={expGroups.map((g) => ({ key: g.key, label: g.label, color: g.color, values: g.b.m }))}
              line={{ label: prevLabel, color: PNL_COLORS.prev, values: E.prevM, total: E.prev }}
            />
          )}
          <div className="mute small chartnote">
            หน่วย: บาท (K = พัน, M = ล้าน) · ชี้ที่เดือนเพื่อดูตัวเลข
            {E.n > 0 && E.filled < E.n && ` · ยอดปี ${cfg.year} นับเฉพาะรายการที่กรอกแล้ว ${E.filled}/${E.n}`}
          </div>
        </div>
        <div className="card chartcard">
          <div className="charthead"><h2>สัดส่วนและการเปลี่ยนแปลง <span className="mute small">· ปี {cfg.year} เทียบ {prevLabel}</span></h2></div>
          <div className="sharebar" role="img" aria-label="สัดส่วนค่าใช้จ่ายแต่ละประเภท">
            {expGroups.map((g) => (E.total > 0 && g.b.total > 0 ? <i key={g.key} style={{ width: (g.b.total / E.total) * 100 + '%', background: g.color }} title={g.label + ' ' + ((g.b.total / E.total) * 100).toFixed(1) + '%'} /> : null))}
          </div>
          <div className="plcmp">
            {expGroups.map((g) => (
              <button className="row" key={g.key} onClick={() => showDetail(g.label, g.b.items, null)} title="คลิกเพื่อดูรายละเอียด">
                <span className="nm"><i className="sq" style={{ background: g.color }} />{g.label}</span>
                <span className="bars big">
                  <i style={{ width: (g.b.prev / maxCmp) * 100 + '%', background: '#c3cde0' }} />
                  <i style={{ width: (g.b.total / maxCmp) * 100 + '%', background: g.color }} />
                </span>
                <span className="num nums">
                  <b>{fmt(g.b.total)}</b>
                  <span className="mute small">{prevLabel} {fmt(g.b.prev)}</span>
                </span>
                <span className={'num chg' + (g.b.complete && g.b.diff > 0 ? ' up' : g.b.complete && g.b.diff < 0 ? ' down' : '')}>
                  {g.b.complete ? (g.b.pct == null ? (g.b.total > 0 ? 'ใหม่' : '–') : fmtPct(g.b.pct)) : <span className="mute small">กรอก {g.b.filled}/{g.b.n}</span>}
                </span>
              </button>
            ))}
          </div>
          <div className="mute small chartnote">แท่งสีเทา = {prevLabel} · แท่งสี = ยอดขอปี {cfg.year} · คลิกแถวเพื่อดูรายละเอียด</div>
        </div>
      </div>

      <p className="mute small" style={{ marginTop: 10 }}>
        คลิกที่ตัวเลขในตารางเพื่อดูว่ามาจากค่าใช้จ่าย หมวด หรือฝ่ายไหนบ้าง · คลิกชื่อกลุ่มค่าใช้จ่ายเพื่อดูรายหมวด ·
        รหัสบัญชีไหนอยู่กลุ่มไหน กำหนดได้ที่ ตั้งค่า &gt; รหัสบัญชี · "ผลต่าง" แสดงเมื่อกรอกครบทุกรายการในกลุ่มนั้นแล้ว
      </p>

      {detail && <PnlDetail title={detail.title} items={detail.items} period={detail.period} cfg={cfg} mode={mode} prevLabel={prevLabel} onClose={() => setDetail(null)} />}
    </div>
  );
}
