// ===== App shell: sidebar, topbar, tab switch, import, state =====
// 資料來源改為 D1 (API), localStorage 只保留:
//   - LS_VIEW: 最後檢視的 month/tab/dim (每裝置獨立合理)
//
// 啟動流程:
//   1) /api/auth/me → 未登入 → LoginView
//   2) 已登入 → 平行載入 /api/months + /api/state
//   3) 使用者切月時 → /api/months/:month 拉該月 rows
//   4) 使用者改任何設定 → 打對應 PUT/POST, 前端立刻反映 (optimistic)
const { useState, useMemo, useEffect, useRef, useCallback } = React;

const TABS = [
  { k: 'overview', label: '月度總覽', ic: '◧' },
  { k: 'statement', label: '對帳單', ic: '▤' },
  { k: 'creators', label: '主播明細', ic: '≡' },
  { k: 'rules', label: '分潤規則', ic: '%' },
];

function RateInput({ rate, setRate, rows }) {
  const [v, setV] = useState(String(rate));
  useEffect(() => setV(String(rate)), [rate]);
  const commit = () => { const n = parseFloat(v); if (isFinite(n) && n >= 0) setRate(n); else setV(String(rate)); };
  const diff = rows.reduce((s, r) => s + r.bIncr - r.bIncrPlatform, 0);
  return (
    <div className="rate">
      <label>增量收益比例</label>
      <div className="rate-field">
        <span className="muted">鑽石 ×</span>
        <input className="num" value={v} onChange={(e) => setV(e.target.value)} onBlur={commit} onKeyDown={(e) => e.key === 'Enter' && e.target.blur()} />
        <span>%</span>
      </div>
      <small className={'num ' + (Math.abs(diff) < 0.005 ? 'muted' : diff > 0 ? 'up' : 'down')}>
        {Math.abs(diff) < 0.005 ? '與平台數據一致' : `較平台 ${diff > 0 ? '+' : '−'}${fmtMoney(Math.abs(diff))}`}
      </small>
    </div>
  );
}

function FxInput({ fx, setFx, isSet }) {
  const [v, setV] = useState(String(fx));
  useEffect(() => setV(String(fx)), [fx]);
  const commit = () => { const n = parseFloat(v); if (isFinite(n) && n > 0) setFx(n); else setV(String(fx)); };
  return (
    <div className="rate">
      <label>匯款匯率</label>
      <div className="rate-field">
        <span className="muted">1 USD =</span>
        <input className="num" value={v} onChange={(e) => setV(e.target.value)} onBlur={commit} onKeyDown={(e) => e.key === 'Enter' && e.target.blur()} />
        <span>TWD</span>
      </div>
      <small className={isSet ? 'muted' : 'down'}>{isSet ? '本月已設定' : '預設值，請輸入實際匯率'}</small>
    </div>
  );
}

// MonthPicker: 頂欄的明確月份切換入口
function MonthPicker({ month, setMonth, monthKeys, allMonths, imported, onImportClick }) {
  const [open, setOpen] = useState(false);
  const wrapRef = useRef();
  useEffect(() => {
    if (!open) return;
    const off = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', off);
    return () => document.removeEventListener('mousedown', off);
  }, [open]);
  return (
    <div className="month-picker" ref={wrapRef}>
      <button className={'month-picker-btn' + (open ? ' open' : '')} onClick={() => setOpen((v) => !v)} aria-haspopup="listbox" aria-expanded={open}>
        <span className="mp-icon">◧</span>
        <span className="mp-label">{monthLabel(month)}</span>
        <span className="mp-count num muted">{allMonths[month]?.length ?? 0} 筆</span>
        <span className="mp-caret" aria-hidden>▾</span>
      </button>
      {open && (
        <div className="month-picker-menu" role="listbox">
          <div className="mp-head">切換結算月份 <span className="muted">（單月檢視，不合併）</span></div>
          {monthKeys.map((m) => (
            <button key={m} role="option" aria-selected={m === month}
              className={'mp-item' + (m === month ? ' on' : '')}
              onClick={() => { setMonth(m); setOpen(false); }}>
              <span className="mp-item-label num">{m.slice(0, 4)}-{m.slice(4)}</span>
              <span className="mp-item-src num muted">{allMonths[m]?.length ?? 0} 筆{imported[m] ? ' · 匯入' : ' · 種子'}</span>
              {m === month && <span className="mp-item-tick">✓</span>}
            </button>
          ))}
          <div className="mp-divider" />
          <button className="mp-item mp-item-add" onClick={() => { setOpen(false); onImportClick && onImportClick(); }}>
            <span className="mp-item-label">＋ 匯入其他月份</span>
            <span className="mp-item-src muted">上傳 Task_YYYYMM.xlsx</span>
          </button>
        </div>
      )}
    </div>
  );
}

// UserMenu: 頂欄右上顯示登入 email + 登出
function UserMenu({ email, onLogout }) {
  const [open, setOpen] = useState(false);
  const wrapRef = useRef();
  useEffect(() => {
    if (!open) return;
    const off = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', off);
    return () => document.removeEventListener('mousedown', off);
  }, [open]);
  const initial = (email || '?')[0].toUpperCase();
  return (
    <div className="user-menu" ref={wrapRef}>
      <button className="user-btn" onClick={() => setOpen((v) => !v)} title={email}>
        <span className="user-avatar">{initial}</span>
      </button>
      {open && (
        <div className="user-menu-dropdown">
          <div className="user-menu-email">{email}</div>
          <button className="user-menu-item" onClick={onLogout}>登出</button>
        </div>
      )}
    </div>
  );
}

// ---- App ----
function App() {
  // ==== 登入狀態 ====
  const [authState, setAuthState] = useState('loading');   // 'loading' | 'anonymous' | 'authed'
  const [me, setMe] = useState(null);                      // { email }

  // 初次載入 → 檢查 session
  useEffect(() => {
    (async () => {
      const r = await API.authMe();
      if (r.ok) { setMe(r.data); setAuthState('authed'); }
      else setAuthState('anonymous');
    })();
    // 任何 401 都會觸發這個事件, 統一把 App 送回登入頁
    const onExpired = () => { setAuthState('anonymous'); setMe(null); };
    window.addEventListener('auth:expired', onExpired);
    return () => window.removeEventListener('auth:expired', onExpired);
  }, []);

  if (authState === 'loading') {
    return <div className="app-loading">載入中…</div>;
  }
  if (authState === 'anonymous') {
    return <LoginView onLogin={(email) => { setMe({ email }); setAuthState('authed'); }} />;
  }
  return <AppMain me={me} onLogout={async () => { await API.logout(); setAuthState('anonymous'); setMe(null); }} />;
}

// 已登入的主畫面 (原本 App 內容)
function AppMain({ me, onLogout }) {
  // ==== 遠端資料 ====
  // months: { "202608": [tuple,...], ... }  ← 從 D1 拉的月報表, 加上 window.RECON_SEED 保底
  // status: { "月|dim|對象": "pending" }
  // rules:  { defaultRate, guildRate, groupRates{}, guildIds{} }
  // incrRate: { "202608": 0.06 }
  // fx:       { "202608": 32 }
  // invoiceMap: { "群組": "invoice"|"labor" }
  // extras:   { "月|dim|對象": [{id, label, amount}] }
  const [months, setMonths] = useState({});               // 月份 → tuples (含本地載入的月份, 從 D1 一個一個抓)
  const [monthsList, setMonthsList] = useState([]);       // [{month, count, imported}]
  const [status, setStatusMap] = useState({});
  const [rules, setRulesState] = useState({ defaultRate: 75, guildRate: 50, groupRates: {}, guildIds: {} });
  const [incrRate, setIncrRateMap] = useState({});
  const [fxMap, setFxMap] = useState({});
  const [invoiceMap, setInvoiceMap] = useState({});
  const [extrasMap, setExtrasMap] = useState({});
  const [loadingMonth, setLoadingMonth] = useState(false);

  // ==== 檢視狀態 (localStorage 本地存, 每裝置獨立) ====
  const saved = loadJSON(LS_VIEW, {});
  const [month, setMonth] = useState(saved.month || '');
  const [tab, setTab] = useState(saved.tab || 'overview');
  const [dim, setDim] = useState(saved.dim || 'agent');
  const [stmtName, setStmtName] = useState(saved.stmtName || '');
  const [picked, setPicked] = useState(null);
  const [toast, setToast] = useState(null);
  const [drag, setDrag] = useState(false);
  const [sideOpen, setSideOpen] = useState(false);
  const [syncBanner, setSyncBanner] = useState(null);  // {summary} | null
  const [syncing, setSyncing] = useState(false);
  const fileRef = useRef();
  const flash = (msg, err) => { setToast({ msg, err }); setTimeout(() => setToast(null), 3200); };

  // 進入主畫面後檢查有沒有舊版本地資料
  useEffect(() => {
    const { snap, summary } = collectLegacyData();
    if (summary.total > 0) setSyncBanner({ snap, summary });
  }, []);

  const doSync = async () => {
    if (!syncBanner || syncing) return;
    setSyncing(true);
    const r = await API.syncFromLocal(syncBanner.snap);
    setSyncing(false);
    if (!r.ok) { flash('同步失敗：' + (r.data.error || r.status), true); return; }
    // 清掉舊本地資料
    clearLegacyData();
    setSyncBanner(null);
    flash(`已同步至雲端：${r.data.summary.months_rows} 筆主播、${r.data.summary.invoice_types + r.data.summary.group_rates + r.data.summary.status + r.data.summary.extras} 筆設定`);
    // 重新拉一次雲端資料
    const [stateR, monthsR] = await Promise.all([API.getState(), API.listMonths()]);
    if (stateR.ok) {
      setStatusMap(stateR.data.status || {});
      setRulesState({
        defaultRate: stateR.data.rules?.defaultRate ?? 75,
        guildRate:   stateR.data.rules?.guildRate ?? 50,
        groupRates:  stateR.data.rules?.groupRates ?? {},
        guildIds:    stateR.data.rules?.guildIds ?? {},
      });
      setIncrRateMap(stateR.data.incrRate || {});
      setFxMap(stateR.data.fx || {});
      setInvoiceMap(stateR.data.invoiceTypes || {});
      setExtrasMap(stateR.data.extras || {});
    }
    if (monthsR.ok) setMonthsList(monthsR.data.months || []);
    // 清 months 本地 cache, 下次切月自動重拉
    setMonths({});
  };
  const dismissSync = () => setSyncBanner(null);

  useEffect(() => saveJSON(LS_VIEW, { month, tab, dim, stmtName }), [month, tab, dim, stmtName]);
  useEffect(() => { setSideOpen(false); }, [tab, month]);
  useEffect(() => {
    if (!sideOpen) return;
    const h = (e) => e.key === 'Escape' && setSideOpen(false);
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [sideOpen]);

  // ==== 初次載入: /api/state + /api/months ====
  useEffect(() => {
    (async () => {
      const [stateR, monthsR] = await Promise.all([API.getState(), API.listMonths()]);
      if (stateR.ok) {
        setStatusMap(stateR.data.status || {});
        setRulesState({
          defaultRate: stateR.data.rules?.defaultRate ?? 75,
          guildRate:   stateR.data.rules?.guildRate ?? 50,
          groupRates:  stateR.data.rules?.groupRates ?? {},
          guildIds:    stateR.data.rules?.guildIds ?? {},
        });
        setIncrRateMap(stateR.data.incrRate || {});
        setFxMap(stateR.data.fx || {});
        setInvoiceMap(stateR.data.invoiceTypes || {});
        setExtrasMap(stateR.data.extras || {});
      }
      if (monthsR.ok) {
        setMonthsList(monthsR.data.months || []);
      }
    })();
  }, []);

  // ==== 合併: D1 的月份 + window.RECON_SEED 種子; monthKeys 用來排序顯示 ====
  const allMonths = useMemo(() => {
    const merged = { ...(window.RECON_SEED || {}), ...months };
    return merged;
  }, [months]);

  const monthKeys = useMemo(() => {
    const set = new Set([
      ...Object.keys(window.RECON_SEED || {}),
      ...monthsList.map((m) => m.month),
      ...Object.keys(months),
    ]);
    return [...set].sort().reverse();
  }, [monthsList, months]);

  // imported map (哪些月份是 D1 有的, 非 seed)
  const imported = useMemo(() => {
    const m = {};
    for (const x of monthsList) if (x.imported) m[x.month] = true;
    return m;
  }, [monthsList]);

  // 挑預設月份 (第一次載入時 month 是空的)
  useEffect(() => {
    if (!month && monthKeys.length) setMonth(monthKeys[0]);
  }, [monthKeys, month]);

  // ==== 切月時, 若該月未在本地 cache 就從 D1 拉 ====
  useEffect(() => {
    if (!month) return;
    // seed 月份不用打 API
    if (window.RECON_SEED?.[month]) return;
    // 已 cache 過就跳過
    if (months[month]) return;
    // 若 monthsList 沒這筆, 也不打 (可能是選錯月了)
    if (!monthsList.find((m) => m.month === month)) return;
    setLoadingMonth(true);
    API.getMonth(month).then((r) => {
      setLoadingMonth(false);
      if (r.ok) setMonths((s) => ({ ...s, [month]: r.data.rows || [] }));
      else flash('載入月份失敗：' + (r.data.error || r.status), true);
    });
  }, [month, monthsList, months]);

  // ==== 每月設定: rate / fx ====
  const rate = incrRate[month] ?? 0.06;
  const setRate = (v) => {
    setIncrRateMap((s) => ({ ...s, [month]: v }));
    API.putMonthSetting(month, { incrRate: v });
  };
  const fxSet = fxMap[month] != null;
  const fx = fxMap[month] ?? 32;
  const setFx = (v) => {
    setFxMap((s) => ({ ...s, [month]: v }));
    API.putMonthSetting(month, { fx: v });
  };

  // ==== rules 的 setter: 要跟 D1 同步 ====
  const setRules = useCallback((fn) => {
    setRulesState((s) => {
      const n = typeof fn === 'function' ? fn(s) : fn;
      // 判斷差異然後打對應 API
      if (n.defaultRate !== s.defaultRate || n.guildRate !== s.guildRate) {
        debouncedCall('put-rules', 400, () => API.putRules({ defaultRate: n.defaultRate, guildRate: n.guildRate }));
      }
      // groupRates diff (add/change/remove)
      const oldGR = s.groupRates || {}, newGR = n.groupRates || {};
      for (const k of Object.keys(newGR)) if (newGR[k] !== oldGR[k]) API.putGroupRate(k, newGR[k]);
      for (const k of Object.keys(oldGR)) if (!(k in newGR)) API.putGroupRate(k, null);
      // guildIds diff (add/remove) — 差異就打單筆
      const oldGI = s.guildIds || {}, newGI = n.guildIds || {};
      for (const k of Object.keys(newGI)) if (newGI[k] && !oldGI[k]) API.putGuild(k, true);
      for (const k of Object.keys(oldGI)) if (oldGI[k] && !newGI[k]) API.putGuild(k, false);
      return n;
    });
  }, []);
  const toggleGuild = (id) => setRules((s) => {
    const g = { ...s.guildIds };
    if (g[id]) delete g[id]; else g[id] = true;
    return { ...s, guildIds: g };
  });

  // ==== rows: 依當月資料 + rate + fx + rules 算出的 UI rows ====
  const rows = useMemo(() => (allMonths[month] || []).map((t) => {
    const o = toObj(t);
    o.bIncrPlatform = o.bIncr || 0;
    o.bIncr = Math.round(o.diamonds * (rate / 100) * 10000) / 10000;
    o.bonus = (o.bonus || 0) - o.bIncrPlatform + o.bIncr;
    applyShare(o, rules);
    o.payoutTWD = Math.round(o.payout * fx);
    return o;
  }), [allMonths, month, rate, rules, fx]);
  window.RECON_FX = fx;
  window.RECON_RATE = rate;

  // ==== 對帳狀態 setter ====
  const setStatus = (k, v) => {
    setStatusMap((s) => ({ ...s, [k]: v }));
    const [m, d, ...rest] = k.split('|');
    API.putStatus(m, d, rest.join('|'), v);
  };

  // ==== 結算方式 (invoice) setter ====
  const setInvoice = (groupName, type) => {
    setInvoiceMap((s) => ({ ...s, [groupName]: type }));
    API.putInvoice(groupName, type);
  };

  // ==== 額外獎金 add/remove ====
  const addExtra = async (mnth, d, name, label, amount) => {
    const r = await API.addExtra(mnth, d, name, label, amount);
    if (!r.ok) { flash('新增失敗', true); return; }
    setExtrasMap((s) => {
      const key = `${mnth}|${d}|${name}`;
      const arr = [...(s[key] || []), { id: r.data.id, label, amount }];
      return { ...s, [key]: arr };
    });
  };
  const removeExtra = async (mnth, d, name, extraId) => {
    await API.delExtra(extraId);
    setExtrasMap((s) => {
      const key = `${mnth}|${d}|${name}`;
      const arr = (s[key] || []).filter((x) => x.id !== extraId);
      const n = { ...s };
      if (arr.length) n[key] = arr; else delete n[key];
      return n;
    });
  };

  // ==== 匯入月報表 ====
  const handleFile = async (file) => {
    if (!file) return;
    try {
      const byMonth = parseWorkbook(await file.arrayBuffer());
      // 依月份逐個推到 D1
      let totalRows = 0;
      const importedMonths = Object.keys(byMonth).sort();
      for (const m of importedMonths) {
        const r = await API.importMonth(m, byMonth[m]);
        if (!r.ok) { flash(`匯入 ${m} 失敗：${r.data.error || r.status}`, true); return; }
        totalRows += byMonth[m].length;
      }
      // 更新本地 cache
      setMonths((s) => ({ ...s, ...byMonth }));
      // 重拉 months list (讓 sidebar 立即出現新月份)
      const listR = await API.listMonths();
      if (listR.ok) setMonthsList(listR.data.months || []);
      // 跳到最新一個匯入月
      setMonth(importedMonths[importedMonths.length - 1]);
      flash(`已匯入 ${importedMonths.map(monthLabel).join('、')}（${totalRows} 筆）`);
    } catch (e) { flash('匯入失敗：' + e.message, true); }
  };
  const removeMonth = async (m) => {
    if (!confirm(`移除 ${monthLabel(m)} 的匯入資料？`)) return;
    const r = await API.deleteMonth(m);
    if (!r.ok) { flash('刪除失敗', true); return; }
    setMonths((s) => { const n = { ...s }; delete n[m]; return n; });
    setMonthsList((s) => s.filter((x) => x.month !== m));
    if (m === month) {
      const remain = monthKeys.filter((x) => x !== m);
      setMonth(remain[0] || '');
    }
  };

  const openStatement = (name) => { setStmtName(name); setTab('statement'); window.scrollTo(0, 0); };
  const dimLabel = DIMENSIONS.find((d) => d.key === dim).label;
  const titles = { overview: '月度總覽', statement: `${dimLabel}對帳單`, creators: '主播明細', rules: '分潤規則' };

  return (
    <div className={'app' + (sideOpen ? ' side-open' : '')}>
      {sideOpen && <div className="side-scrim" onClick={() => setSideOpen(false)} />}
      <aside className="side">
        <div className="brand">
          <img src="/static/recon/assets/logo-mark.png" alt="JDi 脈動傳媒" className="brand-logo" />
          <div className="brand-text">
            <div className="brand-name">JDi 脈動傳媒</div>
            <div className="brand-sub">對帳中心</div>
          </div>
        </div>
        <div>
          <h6>報表</h6>
          <nav className="nav">
            {TABS.map((t) => (
              <button key={t.k} className={tab === t.k ? 'on' : ''} onClick={() => setTab(t.k)}>
                <span className="ic">{t.ic}</span>{t.label}
              </button>
            ))}
          </nav>
        </div>
        <div style={{ minHeight: 0, display: 'flex', flexDirection: 'column' }}>
          <h6>結算月份</h6>
          <div className="months">
            {monthKeys.map((m) => (
              <button key={m} className={m === month ? 'on' : ''} onClick={() => setMonth(m)}
                onContextMenu={(e) => { if (imported[m]) { e.preventDefault(); removeMonth(m); } }}
                title={imported[m] ? '右鍵可移除匯入資料' : ''}>
                <span>{m.slice(0, 4)}-{m.slice(4)}</span>
                <span className="src">{allMonths[m]?.length ?? 0} 筆{imported[m] ? ' · 匯入' : ''}</span>
              </button>
            ))}
          </div>
        </div>
        <div className={'import' + (drag ? ' drag' : '')}
          onClick={() => fileRef.current.click()}
          onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
          onDragLeave={() => setDrag(false)}
          onDrop={(e) => { e.preventDefault(); setDrag(false); handleFile(e.dataTransfer.files[0]); }}>
          ＋ 匯入月報表
          <small>拖曳或點擊上傳 .xlsx</small>
          <input ref={fileRef} type="file" accept=".xlsx,.xls,.csv" hidden onChange={(e) => { handleFile(e.target.files[0]); e.target.value = ''; }} />
        </div>
      </aside>

      <main className="main">
        {syncBanner && (
          <div className="sync-banner no-print">
            <div className="sync-banner-body">
              <b>偵測到舊版本機資料</b>
              <span className="muted">
                {syncBanner.summary.months > 0 && ` ${syncBanner.summary.months} 個月份 (${syncBanner.summary.monthsRows} 筆主播)`}
                {syncBanner.summary.statuses > 0 && `、${syncBanner.summary.statuses} 筆對帳狀態`}
                {syncBanner.summary.groupRates > 0 && `、${syncBanner.summary.groupRates} 筆群組分潤`}
                {syncBanner.summary.guildIds > 0 && `、${syncBanner.summary.guildIds} 位公會代運營`}
                {syncBanner.summary.monthSettings > 0 && `、${syncBanner.summary.monthSettings} 筆月度設定`}
                {syncBanner.summary.invoiceTypes > 0 && `、${syncBanner.summary.invoiceTypes} 筆結算方式`}
                {syncBanner.summary.extras > 0 && `、${syncBanner.summary.extras} 筆額外獎金`}
                {' — 一鍵推到雲端後可跨裝置共用'}
              </span>
            </div>
            <div className="sync-banner-actions">
              <button className="btn" onClick={dismissSync} disabled={syncing}>稍後</button>
              <button className="btn primary" onClick={doSync} disabled={syncing}>
                {syncing ? '同步中…' : '推到雲端'}
              </button>
            </div>
          </div>
        )}
        <header className="topbar">
          <button className="menu-btn" onClick={() => setSideOpen(true)} aria-label="開啟選單">
            <span /><span /><span />
          </button>
          <div className="topbar-main">
            <div className="crumb">結算月份 · 資料來源 Task_{month}{loadingMonth && ' · 載入中…'}</div>
            <div className="topbar-title">
              <h1>{titles[tab]}</h1>
              <MonthPicker
                month={month}
                setMonth={setMonth}
                monthKeys={monthKeys}
                allMonths={allMonths}
                imported={imported}
                onImportClick={() => fileRef.current && fileRef.current.click()}
              />
            </div>
          </div>
          <div className="spacer" />
          <RateInput rate={rate} setRate={setRate} rows={rows} />
          <FxInput fx={fx} setFx={setFx} isSet={fxSet} />
          {(tab === 'overview' || tab === 'statement') && (
            <div className="seg">
              {DIMENSIONS.map((d) => <button key={d.key} className={dim === d.key ? 'on' : ''} onClick={() => setDim(d.key)}>依{d.label}</button>)}
            </div>
          )}
          <UserMenu email={me?.email} onLogout={onLogout} />
        </header>
        {/* 手機底部 tab bar */}
        <nav className="tabbar no-print">
          {TABS.map((t) => (
            <button key={t.k} className={tab === t.k ? 'on' : ''} onClick={() => setTab(t.k)} aria-label={t.label}>
              <span className="tabbar-ic">{t.ic}</span>
              <span className="tabbar-label">{t.label}</span>
            </button>
          ))}
        </nav>

        {tab === 'overview'  && <OverviewView  rows={rows} month={month} dim={dim} setDim={setDim} status={status} setStatus={setStatus} openStatement={openStatement} />}
        {tab === 'statement' && <StatementView rows={rows} month={month} dim={dim} name={stmtName} setName={setStmtName}
                                               status={status} setStatus={setStatus}
                                               invoiceMap={invoiceMap} setInvoice={setInvoice}
                                               extrasMap={extrasMap} onAddExtra={addExtra} onRemoveExtra={removeExtra} />}
        {tab === 'creators'  && <CreatorsView  rows={rows} month={month} onPick={(r) => setPicked(r.id)} toggleGuild={toggleGuild} />}
        {tab === 'rules'     && <RulesView     rows={rows} rules={rules} setRules={setRules} toggleGuild={toggleGuild}
                                               invoiceMap={invoiceMap} setInvoice={setInvoice} />}
      </main>

      {picked && rows.find((x) => x.id === picked) && <CreatorDrawer r={rows.find((x) => x.id === picked)} onClose={() => setPicked(null)} toggleGuild={toggleGuild} />}
      {toast && <div className={'toast' + (toast.err ? ' err' : '')}>{toast.msg}</div>}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
