/* mapping.jsx — หน้าจัดรหัสบัญชี (#mapping · ฝ่ายบัญชีและผู้ดูแลเท่านั้น)
 * ฝ่ายพิมพ์ชื่อรายการในกิจกรรมเอง (เช่น "ค่าที่พัก") ฝ่ายบัญชีเลือกว่าแต่ละรายการเข้ารหัสบัญชีไหน (1 รายการ = 1 รหัส)
 * ระบบแนะนำรหัสให้ (R.suggestAccounts ใน shared/rules.js — ในตัว ไม่ส่งข้อมูลออกนอก) แต่ฝ่ายบัญชีเป็นคนเลือกเองเสมอ
 * จัดแล้วยอดเข้าบรรทัดรหัสนั้นทันที (server รวมใหม่) · ฝ่ายเห็นรหัสที่จัดแล้วเป็นตัวสีเทา แก้ไม่ได้
 * สถานะรายการ: รอจัดรหัส · รอตรวจ (ฝ่ายเลือกไว้เองก่อนเปลี่ยนวิธี หรือแก้ชื่อรายการหลังจัดแล้ว) · จัดแล้ว
 * หน้าจอ: ส่วนหัวบอกความคืบหน้า · ตัวกรองสถานะ 4 ช่อง · รายชื่อฝ่ายด้านซ้าย (จอแคบเป็นช่องเลือก) · รายการจัดกลุ่ม ฝ่าย → กิจกรรม */

const MAP_LABEL = Object.fromEntries(R.MAP_STATES.map((s) => [s.key, s.label]));
const MAP_STAT = [
  { key: 'todo', icon: 'alert', tone: 'amber' },
  { key: 'check', icon: 'eye', tone: 'blue' },
  { key: 'done', icon: 'checkCircle', tone: 'green' },
  { key: 'all', icon: 'layers', tone: 'navy', label: 'ทั้งหมด' },
];

/** แถบแจ้งบนหน้าภาพรวม/รายงาน: มีรายการในกิจกรรมที่ยังไม่จัดเข้ารหัส (ยังไม่อยู่ในบรรทัดรหัสบัญชี จึงไม่อยู่ในตัวเลขตามรหัส) */
function UnmappedNote({ depts, go, inReport }) {
  const u = R.unmappedOf((depts || []).flatMap((d) => d.activities || []));
  if (!u.n) return null;
  return (
    <div className="banner yellow noprint">
      <Icon name="alert" />
      <div className="grow">
        <b>รายการในกิจกรรม {u.n} รายการ ({fmt(u.amount)} บาท) รอฝ่ายบัญชีจัดเข้ารหัสบัญชี</b>
        <div className="small">{inReport ? 'ยอดเหล่านี้ยังไม่อยู่ในตัวเลขตามรหัสบัญชีด้านล่าง จะเข้ามาเมื่อจัดรหัสแล้ว' : 'เป็นยอดขอของฝ่ายแล้ว แต่ยังไม่อยู่ในบรรทัดรหัสบัญชีไหน'}</div>
      </div>
      {go && <button className="btn sm" onClick={() => go('mapping')}>ไปจัดรหัสบัญชี</button>}
    </div>
  );
}

/** ผลของการจัดรหัส: ตัวเลขปีอ้างอิงของรหัสนี้ในฝ่าย และยอดที่ฝ่ายขอรหัสนี้รวม (ถ้าจัดรายการนี้เข้าไป) เทียบ Budget */
function MapImpact({ x, cfg }) {
  const over = x.pct != null && x.pct > Number(cfg.redPct) && x.after - x.B > Number(cfg.redMinBaht || 0);
  return (
    <div className={'mapimpact' + (over ? ' warn' : '')} title="ยอดของรหัสนี้ทั้งฝ่าย (ค่าใช้จ่ายประจำ + ทุกรายการในกิจกรรมที่จัดเข้ารหัสนี้)">
      {x.hasRef ? <>ปี {cfg.baseYear}: Budget <b>{fmt(x.B)}</b> · ใช้จริง <b>{cfg.actualMonths ? fmt(x.A) : '–'}</b></> : <>ปี {cfg.baseYear} ฝ่ายไม่มีงบรหัสนี้</>}
      <span className="sep">·</span>
      {x.pending ? 'ถ้าจัดรายการนี้ ฝ่ายขอรหัสนี้รวม ' : 'ฝ่ายขอรหัสนี้รวม '}<b>{fmt(x.after)}</b>
      {x.pct != null && <span className="pc"> ({fmtPct(x.pct)}{over ? ' · เกินเกณฑ์' : ''})</span>}
    </div>
  );
}

/** ช่องรหัสบัญชีของ 1 รายการ: รหัสปัจจุบัน (ป้ายสี) · ปุ่มรหัสที่ระบบแนะนำพร้อมเหตุผล · เลือกรหัสอื่นจากผังบัญชี */
function MapCell({ row, accounts, known, nameOf, can, busy, editing, onEdit, onPick, impact, cfg }) {
  const { it, state, sugg } = row;
  const cur = it.acct;
  const others = sugg.filter((s) => s.code !== cur);
  if (editing) {
    return (
      <div className="mapcell">
        <AcctPicker value={cur} accounts={accounts} known={known} place="activity" onPick={(code) => onPick(code)} />
        <span className="mapacts">
          {cur && <button className="btn sm ghost" disabled={busy} onClick={() => onPick('')} title="ล้างกลับเป็นยังไม่จัด">ล้างรหัส</button>}
          <button className="btn sm ghost" onClick={() => onEdit(false)}>ยกเลิก</button>
        </span>
      </div>
    );
  }
  return (
    <div className="mapcell">
      {cur && (
        <div className="mapcur">
          <span className={'mapcode ' + state}>
            {state === 'done' && <Icon name="check" />}
            <b>{cur}</b><span className="nm">{nameOf(cur)}</span>
          </span>
          {state === 'check' && <span className="mapwhy">{it.stale ? 'ฝ่ายแก้ชื่อรายการหลังจัดรหัส' : 'ฝ่ายเลือกรหัสนี้ไว้เองก่อนเปลี่ยนวิธี'}</span>}
          {state === 'done' && it.mappedBy && <span className="mapwhy">จัดโดย {it.mappedBy}{it.mappedAt ? ' · ' + fmtShort(it.mappedAt) : ''}</span>}
          <MapImpact x={impact(row, cur)} cfg={cfg} />
        </div>
      )}
      {state !== 'done' && others.length > 0 && (
        <div className="mapsugg">
          <span className="lbl">{cur ? 'หรือ' : 'แนะนำ'}</span>
          {others.map((s, i) => (
            <button key={s.code} className={'sugg' + (i === 0 && !cur ? ' top' : '')} disabled={!can || busy} onClick={() => onPick(s.code)} title={s.why}>
              {i === 0 && !cur && <Icon name="check" />}<span className="code">{s.code}</span> {nameOf(s.code)}
            </button>
          ))}
          {!cur && others[0].why && <span className="mapwhy">{others[0].why}</span>}
          {!cur && <MapImpact x={impact(row, others[0].code)} cfg={cfg} />}
        </div>
      )}
      {!cur && !others.length && <div className="mapwhy">ระบบยังไม่มีคำแนะนำ — เลือกรหัสเองจากผังบัญชี</div>}
      {can && (
        <span className="mapacts">
          {state === 'check' && <button className="btn sm primary" disabled={busy} onClick={() => onPick(cur)}><Icon name="check" />ยืนยันรหัสนี้</button>}
          <button className="link" disabled={busy} onClick={() => onEdit(true)}>{cur ? 'เปลี่ยนรหัส' : 'เลือกรหัสเอง'}</button>
        </span>
      )}
    </div>
  );
}

function MappingPage({ go }) {
  const [d, setD] = useState(null);
  const [err, setErr] = useState('');
  const [stF, setStF] = useState('todo');        // todo | check | done | all
  const [deptF, setDeptF] = useState('all');
  const [q, setQ] = useState('');
  const [busy, setBusy] = useState(false);
  const [editKey, setEditKey] = useState(null);
  const [bulk, setBulk] = useState(null);        // { title, rows: [{ row, acct }] }
  const load = useCallback(async () => {
    try { setD(await API.mapping()); setErr(''); } catch (e) { setErr(e.message); }
  }, []);
  useEffect(() => { load(); }, [load]);

  const idx = useMemo(() => (d ? R.suggestIndex(d.accounts, d.examples) : null), [d && d.accounts, d && d.examples]);
  const acctOf = useMemo(() => new Map((d ? d.accounts : []).map((a) => [a.code, a])), [d && d.accounts]);
  const nameOf = (c) => (acctOf.get(c) || {}).name || '';
  const pickable = useMemo(() => (d ? d.accounts.filter((a) => a.filler !== 'central') : []), [d && d.accounts]);
  const deptOf = useMemo(() => new Map((d ? d.depts : []).map((x) => [x.code, x])), [d && d.depts]);
  const ctxOf = useMemo(() => {
    const m = new Map();
    for (const x of (d ? d.depts : [])) {
      const known = new Set(x.known);
      const mk = x.known.filter((c) => /ฝ่ายการตลาด/.test(nameOf(c))).length;
      m.set(x.code, { known, marketing: mk * 2 > x.known.length });
    }
    return m;
  }, [d && d.depts, acctOf]);
  // ทุกรายการในกิจกรรมของทุกฝ่าย + คำแนะนำ (คำนวณครั้งเดียวต่อข้อมูลชุดใหม่)
  const rows = useMemo(() => {
    if (!d) return [];
    const out = [];
    for (const a of d.activities) {
      const dept = deptOf.get(a.dept);
      if (!dept) continue;
      const ctx = ctxOf.get(a.dept);
      a.items.forEach((it, i) => {
        const itemId = R.itemKey(it, i);
        const label = it.name || nameOf(it.acct) || '–';
        out.push({
          key: a.id + ':' + itemId, a, it, itemId, dept, label, state: R.mapState(it), amount: R.itemAmount(it),
          sugg: R.suggestAccounts(idx, { name: label, note: it.note }, { activityName: a.name, known: ctx.known, marketing: ctx.marketing }),
        });
      });
    }
    return out;
  }, [d, idx, deptOf, ctxOf]);

  // ยอดของแต่ละฝ่าย × รหัสจากรายการในกิจกรรมที่จัดรหัสแล้ว (คำนวณจากข้อมูลล่าสุดบนหน้าจอ อัปเดตทันทีหลังจัด)
  const mappedSum = useMemo(() => {
    const m = new Map();
    for (const a of (d ? d.activities : [])) for (const it of a.items) if (it.acct) { const k = a.dept + '\u0000' + it.acct; m.set(k, R.round2((m.get(k) || 0) + R.itemAmount(it))); }
    return m;
  }, [d && d.activities]);
  /** ถ้ารายการนี้อยู่ในรหัส acct: ยอดที่ฝ่ายขอรหัสนั้นรวม (ค่าใช้จ่ายประจำ + รายการที่จัดแล้ว) เทียบ Budget ปีอ้างอิง */
  const impact = (row, acct) => {
    const ref = (row.dept.ref || {})[acct];
    const base = R.round2(((row.dept.rec || {})[acct] || 0) + (mappedSum.get(row.dept.code + '\u0000' + acct) || 0));
    const pending = row.it.acct !== acct;
    const after = pending ? R.round2(base + row.amount) : base;
    return { hasRef: !!ref, B: ref ? ref.B : 0, A: ref ? ref.A : 0, after, pending, pct: ref && ref.B > 0 ? ((after - ref.B) / ref.B) * 100 : null };
  };
  const save = async (list, msg) => {
    if (!list.length) return;
    setBusy(true);
    try {
      let changed = 0;
      const fresh = new Map();
      for (let i = 0; i < list.length; i += 500) {
        const r = await API.saveMapping(list.slice(i, i + 500));
        changed += r.changed;
        r.activities.forEach((a) => fresh.set(a.id, a));
      }
      setD((x) => ({ ...x, activities: x.activities.map((a) => fresh.get(a.id) || a) }));
      setEditKey(null);
      toast(msg || (changed ? 'จัดรหัสแล้ว ' + changed + ' รายการ' : 'ไม่มีอะไรเปลี่ยน'));
    } catch (e) {
      toast(e.message, 'error');
      if (e.status === 409) load();
    } finally { setBusy(false); }
  };
  const pickOne = (row, acct) => save([{ activityId: row.a.id, itemId: row.itemId, acct }],
    acct ? (acct === row.it.acct ? 'ยืนยันรหัส ' : 'จัดเข้ารหัส ') + acct + ' ' + nameOf(acct) + ' แล้ว' : 'ล้างรหัสแล้ว');

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

  const sumOf = (rs) => R.round2(R.sum(rs.map((r) => r.amount)));
  const byState = (rs, st) => (st === 'all' ? rs : rs.filter((r) => r.state === st));
  const inDept = deptF === 'all' ? rows : rows.filter((r) => r.dept.code === deptF);
  const needle = q.trim().toLowerCase();
  const shown = byState(inDept, stF).filter((r) => !needle || r.label.toLowerCase().includes(needle) || r.a.name.toLowerCase().includes(needle)
    || (r.it.note || '').toLowerCase().includes(needle) || (r.it.acct || '').includes(needle) || nameOf(r.it.acct).toLowerCase().includes(needle));
  // จัดกลุ่ม ฝ่าย → กิจกรรม ตามลำดับสายงาน
  const order = new Map(R.sortByLine(d.depts).map((x, i) => [x.code, i]));
  const groups = [];
  for (const r of shown.slice().sort((x, y) => (order.get(x.dept.code) - order.get(y.dept.code)) || (x.a.id - y.a.id))) {
    let g = groups[groups.length - 1];
    if (!g || g.dept !== r.dept) { g = { dept: r.dept, acts: [] }; groups.push(g); }
    let ga = g.acts[g.acts.length - 1];
    if (!ga || ga.a !== r.a) { ga = { a: r.a, rows: [] }; g.acts.push(ga); }
    ga.rows.push(r);
  }
  const subName = (id) => (d.subunits.find((s) => s.id === id) || {}).name || '';
  const bulkSugg = shown.filter((r) => r.state === 'todo' && r.dept.canMap && r.sugg.length);
  const bulkCheck = shown.filter((r) => r.state === 'check' && r.dept.canMap);
  const deptsWithItems = R.sortByLine(d.depts.filter((x) => rows.some((r) => r.dept === x)));
  const deptRows = (code) => rows.filter((r) => r.dept.code === code);
  const nDone = byState(rows, 'done').length;
  const nCheck = byState(rows, 'check').length;
  const todoAll = byState(rows, 'todo');
  const pctDone = rows.length ? Math.floor((nDone / rows.length) * 100) : 100;

  return (
    <div className="mappage">
      <div className="maphero">
        <div className="mh-l">
          <div className="mh-eyebrow">ฝ่ายบัญชี · งบประมาณปี {d.round.year}</div>
          <h1>จัดรหัสบัญชีให้รายการของฝ่าย</h1>
          <p>ฝ่ายพิมพ์ชื่อรายการในกิจกรรมเอง ฝ่ายบัญชีเลือกว่าแต่ละรายการเข้ารหัสบัญชีไหน — ระบบแนะนำให้ แต่เลือกเองได้เสมอ ยอดเข้าบรรทัดรหัสนั้นทันที และฝ่ายเห็นรหัสที่จัดแล้ว (แก้ไม่ได้)</p>
        </div>
        <div className="mh-prog">
          <div className="mh-big">{pctDone}<span>%</span></div>
          <div className="mh-txt">จัดแล้ว <b>{nDone}</b> จาก {rows.length} รายการ{nCheck ? ' · รอตรวจ ' + nCheck : ''}</div>
          <div className="mh-bar" title={'จัดแล้ว ' + nDone + ' · รอตรวจ ' + nCheck + ' · รอจัดรหัส ' + todoAll.length}>
            <i className="done" style={{ width: (rows.length ? (nDone / rows.length) * 100 : 0) + '%' }} />
            <i className="check" style={{ width: (rows.length ? (nCheck / rows.length) * 100 : 0) + '%' }} />
          </div>
          <div className="mh-sub">{todoAll.length ? 'รอจัดรหัส ' + todoAll.length + ' รายการ · ' + fmt(sumOf(todoAll)) + ' บาท' : 'ไม่มีรายการที่รอจัดรหัส'}</div>
        </div>
      </div>

      <div className="mapstats">
        {MAP_STAT.map((s) => {
          const rs = byState(inDept, s.key);
          return (
            <button key={s.key} className={'mapstat ' + s.tone + (stF === s.key ? ' on' : '')} onClick={() => setStF(s.key)} aria-pressed={stF === s.key}>
              <span className="ic"><Icon name={s.icon} /></span>
              <span className="tx"><span className="k">{s.label || MAP_LABEL[s.key]}</span><b>{rs.length}</b><small>{fmt(sumOf(rs))} บาท</small></span>
            </button>
          );
        })}
      </div>

      <div className="maplayout">
        <aside className="card mapside" aria-label="เลือกฝ่าย">
          <div className="ms-h">ฝ่าย <span className="mute">({deptsWithItems.length})</span></div>
          <button className={'ms-d' + (deptF === 'all' ? ' on' : '')} onClick={() => setDeptF('all')}>
            <span className="nm">ทุกฝ่าย</span>
            {todoAll.length > 0 ? <span className="cnt">{todoAll.length}</span> : <Icon name="checkCircle" className="okic" />}
          </button>
          {deptsWithItems.map((x) => {
            const rs = deptRows(x.code);
            const todo = byState(rs, 'todo').length;
            const done = byState(rs, 'done').length;
            return (
              <button key={x.code} className={'ms-d' + (deptF === x.code ? ' on' : '')} onClick={() => setDeptF(x.code)} title={x.name}>
                <LineBadge line={R.lineOf(x)} />
                <span className="nm">{x.name}</span>
                {todo > 0 ? <span className="cnt">{todo}</span> : done === rs.length ? <Icon name="checkCircle" className="okic" /> : <span className="cnt blue">{rs.length - done}</span>}
                <span className="pb"><i style={{ width: (rs.length ? (done / rs.length) * 100 : 0) + '%' }} /></span>
              </button>
            );
          })}
          {!deptsWithItems.length && <div className="empty small">ยังไม่มีฝ่ายไหนใส่รายการในกิจกรรม</div>}
        </aside>

        <div className="mapmain">
          <div className="card maptools">
            <select className="mapdeptsel" value={deptF} onChange={(e) => setDeptF(e.target.value)} aria-label="เลือกฝ่าย">
              <option value="all">ทุกฝ่าย</option>
              <DeptOptions depts={deptsWithItems} />
            </select>
            <div className="search">
              <Icon name="search" />
              <input type="text" value={q} onChange={(e) => setQ(e.target.value)} placeholder="ค้นรายการ กิจกรรม หรือรหัสบัญชี" aria-label="ค้นรายการ" />
            </div>
            <span className="grow" />
            <span className="small mute">แสดง {shown.length} รายการ · {fmt(sumOf(shown))} บาท</span>
            {bulkSugg.length > 0 && (
              <button className="btn sm primary" disabled={busy} onClick={() => setBulk({ title: 'จัดรหัสตามที่ระบบแนะนำ', rows: bulkSugg.map((r) => ({ row: r, acct: r.sugg[0].code })) })}>
                <Icon name="checkCircle" />ใช้รหัสที่แนะนำ ({bulkSugg.length})
              </button>
            )}
            {bulkCheck.length > 0 && (
              <button className="btn sm" disabled={busy} onClick={() => setBulk({ title: 'ยืนยันรหัสที่มีอยู่', rows: bulkCheck.map((r) => ({ row: r, acct: r.it.acct })) })}>
                <Icon name="check" />ยืนยันรหัสที่มีอยู่ ({bulkCheck.length})
              </button>
            )}
          </div>

          {groups.length === 0 && (
            <div className="card mapempty">
              <Icon name={rows.length && stF === 'todo' ? 'checkCircle' : 'layers'} />
              <b>{rows.length === 0 ? 'ยังไม่มีฝ่ายไหนใส่รายการในกิจกรรม' : stF === 'todo' ? 'จัดรหัสครบทุกรายการแล้ว' : stF === 'check' ? 'ไม่มีรายการที่รอตรวจ' : 'ไม่พบรายการ'}</b>
              {rows.length > 0 && stF !== 'all' && <button className="link" onClick={() => setStF('all')}>ดูรายการทั้งหมด</button>}
            </div>
          )}
          {groups.map((g) => {
            const rs = deptRows(g.dept.code);
            const done = byState(rs, 'done').length;
            return (
              <section key={g.dept.code} className="card mapdept">
                <div className="mapdept-h">
                  <LineBadge line={R.lineOf(g.dept)} />
                  <span className="dn"><b>{g.dept.name}</b><span className="mute small">{g.dept.code}</span></span>
                  {g.dept.status !== 'draft' && <StatusBadge status={g.dept.status} />}
                  {!g.dept.canMap && <span className="small mute"><Icon name="lock" className="inl" />{g.dept.status === 'locked' ? 'ล็อกแล้ว จัดรหัสไม่ได้' : 'ปิดรอบแล้ว'}</span>}
                  <span className="grow" />
                  <span className="dprog" title={'จัดแล้ว ' + done + ' จาก ' + rs.length + ' รายการ'}>
                    <span className="small mute">จัดแล้ว <b>{done}</b>/{rs.length}</span>
                    <span className="pb"><i style={{ width: (rs.length ? (done / rs.length) * 100 : 0) + '%' }} /></span>
                  </span>
                  {go && <button className="btn sm ghost" onClick={() => go('dept/' + encodeURIComponent(g.dept.code))}>เปิดหน้าฝ่าย</button>}
                </div>
                {g.acts.map((ga) => (
                  <div key={ga.a.id} className="mapact">
                    <div className="mapact-h" title={ga.a.detail || ''}>
                      <Icon name="calendar" />
                      <b>{ga.a.name}</b>
                      <span className="chip-s">{R.periodText(ga.a)}</span>
                      <span className="small mute">{subName(ga.a.subunitId)}{ga.a.detail ? ' · ' + ga.a.detail : ''}</span>
                      <span className="grow" />
                      <span className="small mute">รวม <b className="ink">{fmt(R.activityTotal(ga.a))}</b> บาท</span>
                    </div>
                    {ga.rows.map((r) => (
                      <div key={r.key} className={'maprow ' + r.state}>
                        <span className={'mapst ' + r.state}>{MAP_LABEL[r.state]}</span>
                        <span className="mapitem">
                          <b>{r.label}</b>
                          <small>{r.it.qty != null && r.it.unitPrice != null ? fmt(r.it.qty) + ' × ' + fmt(r.it.unitPrice) + (r.it.note ? ' · ' : '') : ''}{r.it.note}</small>
                        </span>
                        <span className="mapamt">{fmt(r.amount)}<small>บาท</small></span>
                        <MapCell
                          row={r} accounts={pickable} known={new Set([...r.sugg.map((s) => s.code), ...ctxOf.get(r.dept.code).known])} nameOf={nameOf}
                          can={r.dept.canMap} busy={busy} editing={editKey === r.key} impact={impact} cfg={d.round}
                          onEdit={(on) => setEditKey(on ? r.key : null)} onPick={(code) => pickOne(r, code)}
                        />
                      </div>
                    ))}
                  </div>
                ))}
              </section>
            );
          })}
        </div>
      </div>

      {bulk && (
        <Modal title={bulk.title + ' · ' + bulk.rows.length + ' รายการ'} wide="xl" onClose={() => setBulk(null)} footer={<>
          <span className="small mute" style={{ marginRight: 'auto', alignSelf: 'center' }}>รวม {fmt(sumOf(bulk.rows.map((x) => x.row)))} บาท · แก้ทีละรายการภายหลังได้</span>
          <button className="btn" onClick={() => setBulk(null)}>ยกเลิก</button>
          <button className="btn primary" disabled={busy} onClick={async () => {
            const b = bulk;
            setBulk(null);
            await save(b.rows.map((x) => ({ activityId: x.row.a.id, itemId: x.row.itemId, acct: x.acct })), b.title + ' ' + b.rows.length + ' รายการแล้ว');
          }}><Icon name="check" />ยืนยัน</button>
        </>}>
          <p className="small mute">ตรวจรหัสของแต่ละรายการก่อนกดยืนยัน (แสดงเฉพาะรายการที่อยู่ในตัวกรองตอนนี้)</p>
          <div className="tw">
            <table className="t">
              <thead><tr><th>ฝ่าย</th><th>กิจกรรม · รายการ</th><th className="num">ยอด</th><th>รหัสบัญชี</th></tr></thead>
              <tbody>
                {bulk.rows.map(({ row, acct }) => (
                  <tr key={row.key}>
                    <td className="small">{row.dept.code}</td>
                    <td><span className="small mute">{row.a.name} · </span><b>{row.label}</b></td>
                    <td className="num">{fmt(row.amount)}</td>
                    <td><span className="mute small">{acct}</span> {nameOf(acct)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </Modal>
      )}
    </div>
  );
}
