/* ============================================================
   KPIレポート（管理者のみ）：営業KPI表と同じ構成＝上に年間KPIサマリー（1〜12月横並び）、
   下に選択月の週次詳細(1W〜5W)＋転換率。◀▶／左右スワイプ／月ピル／年間表の月ヘッダで月切替。
   全社のみ・CRM実データから自動集計。権限 viewKpiReport（既定＝管理者のみ）で三重ゲート。

   集計規則（ユーザー確定）：母集団＝発注ナビ＋ReadyCrew 由来（source hnavi/scrape・!mergedInto）
   ・エントリー数＝案件（取得月=createdAt）／アポイント数＝初回商談日を持つ案件（取得月・同コホート）
   ・一次〜四次＝その回数目の商談を実施した案件（商談日）／五次以上＝5回目以上の商談を実施した案件（<b>1案件=1件</b>・期間内ユニーク）／受注数＝成約(won)+受注(done)（成約日）
   ・受注金額＝手入力の成約金額（無ければ見積書合計で近似・案件詳細で編集可）／アポ率=アポ÷エントリー、二次商談率=二次÷一次、受注率=受注÷アポ、売上達成率=受注額÷目標
   ★集計開始日(startYmd)の適用：エントリー/アポ＝取得日(createdAt)が開始日以降のみ。
     商談・受注＝イベント日（商談日/成約日）が開始日以降なら数える＝開始日前に取得した案件でも
     いま実施した商談・いま決まった受注は実績に入る（受注0誤りの修正・2026-07-29）。
   ・各行 ▸ で「経由」（ReadyCrew/発注ナビ）＋「担当」（owner別）の2セット内訳（年間・月次とも）。
   ※HP経由・架電は計測ソースが無いため「—」。上流/開発の受注内訳は種別が無いため受注数に合算。
   ============================================================ */
function KpiReportScreen() {
  const { can } = useStore();
  if (!can('viewKpiReport')) return <Page title={t('page.kpi')}><div style={{ padding: '48px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('kpi.noPerm')}</div></Page>;
  return <KpiReportContent />;
}
function KpiReportContent() {
  const { cases: realCases, can, kpiTargets, saveKpiTargets } = useStore(); // realCases＝リード(rcIsLeadCase)・統合済み除外のチョークポイント。未エントリーの未回答リードをKPIに混ぜない
  const isMobile = useIsMobile();
  const curY = Number(today().slice(0, 4));
  const curM = Number(today().slice(5, 7)) - 1;
  const [year, setYear] = React.useState(curY);
  const [month, setMonth] = React.useState(curM);
  const [drill, setDrill] = React.useState(null);
  // レポート種別：time=時間別（年間/月次/週次） / owner=担当者別。ヘッダのボタンで切替（拡張可能な構造）
  const [view, setView] = React.useState(() => { try { return localStorage.getItem('anken_kpi_view') === 'owner' ? 'owner' : 'time'; } catch (_) { return 'time'; } });
  React.useEffect(() => { try { localStorage.setItem('anken_kpi_view', view); } catch (_) {} }, [view]);
  const [chartIdx, setChartIdx] = React.useState(0); // 担当者別グラフカードのカルーセル位置（0=案件量/1=温度感/2=満足度）
  const [ownerPg, setOwnerPg] = React.useState(0); // 担当者別テーブルのページ（0=商談活動/1=成果・質）
  const ownerTouchX = React.useRef(null);
  const [editOpen, setEditOpen] = React.useState(false);
  const [form, setForm] = React.useState(null);
  const [expandedM, setExpandedM] = React.useState({}); // 月次詳細の内訳開閉
  const [expandedY, setExpandedY] = React.useState({}); // 年間サマリーの内訳開閉


  const DEF = { entry: 20, hp: 5, call: 100, appo: 10, m1: 5, m2: 2, won: 3, amt: 10000000, startYmd: '2026-06-01' };
  const TARGET = Object.assign({}, DEF, kpiTargets || {});
  // 年間目標：y_◯◯ が保存されていればそれ、無ければ月次目標×12（目標設定モーダルで個別に編集可）
  const yTargetOf = k => { const v = TARGET['y_' + k]; return (v != null && v !== '') ? +v : (Number(TARGET[k]) || 0) * 12; };
  const startYmd = TARGET.startYmd || '';
  const startD = startYmd ? startYmd.slice(0, 10) : '';

  const years = React.useMemo(() => {
    const s = new Set([curY]);
    realCases.forEach(c => { const y = String(c.createdAt || '').slice(0, 4); if (/^\d{4}$/.test(y)) s.add(Number(y)); });
    return [...s].sort((a, b) => b - a);
  }, [realCases]);

  // 年間12ヶ月分＋選択月の週次を一括集計
  const data = React.useMemo(() => {
    const pad2 = n => String(n).padStart(2, '0');
    const selMk = `${year}-${pad2(month + 1)}`;
    const inYear = d => String(d).slice(0, 4) === String(year);
    const monIdx = d => Number(String(d).slice(5, 7)) - 1;
    const wk = ymd => Math.min(4, Math.floor((Number(String(ymd).slice(8, 10)) - 1) / 7));
    const blank = () => ({ entry: [], appo: [], m1: [], m2: [], m3: [], m4: [], m5p: [], m5pAll: [], won: [], lost: [], wonAmt: 0 }); // m5p=5回目以上を実施した案件（1案件1件）/ m5pAll=実レコード（担当者別の回数集計用）
    const months = Array.from({ length: 12 }, blank);
    const weeks = Array.from({ length: 5 }, blank);
    const actBase = realCases.filter(c => !c.mergedInto && (c.source === 'hnavi' || c.source === 'scrape')); // エントリー/アポのコホート＝発注ナビ＋ReadyCrew流入のみ
    const evBase = realCases.filter(c => !c.mergedInto); // 商談・受注のイベント母集団＝全案件（手動・Fireflies案件化も含む。fireflies由来の成約が受注から消えていた2026-07-29の指摘対応）
    // エントリー/アポ＝取得日が開始日以降のコホート
    actBase.forEach(c => {
      const cd = String(c.createdAt || '');
      if (startD && cd < startYmd) return;
      if (!inYear(cd)) return;
      const mi = monIdx(cd);
      const hasMtg = caseMeetingDates(c).length > 0;
      months[mi].entry.push(c);
      if (hasMtg) months[mi].appo.push(c);
      if (cd.slice(0, 7) === selMk) { const w = wk(cd); weeks[w].entry.push(c); if (hasMtg) weeks[w].appo.push(c); }
    });
    // 一次/二次商談＝商談日が開始日以降（過去取得の案件でも現商談は数える）
    const m5SeenM = Array.from({ length: 12 }, () => ({})), m5SeenW = Array.from({ length: 5 }, () => ({}));
    heldStageRecords(evBase).forEach(({ c, day, stage }) => {
      if (startD && day < startD) return;
      if (!inYear(day)) return;
      const mi = monIdx(day), inSel = day.slice(0, 7) === selMk, w = wk(day);
      if (stage >= 5) {
        // 五次以上＝「1案件1件」（その期間に5回目以上の商談を実施した案件数）。実レコードは m5pAll に保持（担当者別・平均商談用）
        months[mi].m5pAll.push({ c, day });
        if (!m5SeenM[mi][c.id]) { m5SeenM[mi][c.id] = 1; months[mi].m5p.push({ c, day }); }
        if (inSel) { weeks[w].m5pAll.push({ c, day }); if (!m5SeenW[w][c.id]) { m5SeenW[w][c.id] = 1; weeks[w].m5p.push({ c, day }); } }
        return;
      }
      const key = stage === 1 ? 'm1' : stage === 2 ? 'm2' : stage === 3 ? 'm3' : 'm4'; // 1〜4次は案件毎に各1回しか存在しない
      months[mi][key].push({ c, day });
      if (inSel) weeks[w][key].push({ c, day });
    });
    // 受注＝成約日が開始日以降（成約(won)+受注(done)・過去取得の案件でも現受注は数える）
    evBase.forEach(c => {
      if (!isCaseWonStatus(c.status)) return;
      const w = caseWonYmd(c);
      if (!w || (startD && w < startD) || !inYear(w)) return;
      const amt = caseWonAmount(c); // 手入力の成約金額があれば最優先・無ければ見積合計
      months[monIdx(w)].won.push(c); months[monIdx(w)].wonAmt += amt;
      if (w.slice(0, 7) === selMk) { weeks[wk(w)].won.push(c); weeks[wk(w)].wonAmt += amt; }
    });
    // 失注＝失注にした日（updatedAtで近似・MonthlyStatsTableと同規則）が開始日以降。全案件対象
    evBase.forEach(c => {
      if (c.status !== 'lost') return;
      const dd = String(c.updatedAt || '').slice(0, 10);
      if (!dd || (startD && dd < startD) || !inYear(dd)) return;
      months[monIdx(dd)].lost.push(c);
      if (dd.slice(0, 7) === selMk) weeks[wk(dd)].lost.push(c);
    });
    return { months, weeks };
  }, [realCases, year, month, startYmd]);

  const MS = data.months, W = data.weeks, TOT = MS[month];
  const YTOT = React.useMemo(() => {
    const o = { entry: [], appo: [], m1: [], m2: [], m3: [], m4: [], m5p: [], m5pAll: [], won: [], lost: [], wonAmt: 0 };
    const m5SeenY = {};
    MS.forEach(m => { ['entry', 'appo', 'm1', 'm2', 'm3', 'm4', 'm5pAll', 'won', 'lost'].forEach(k => o[k].push(...m[k])); m.m5p.forEach(r => { if (!m5SeenY[r.c.id]) { m5SeenY[r.c.id] = 1; o.m5p.push(r); } }); o.wonAmt += m.wonAmt; });
    return o;
  }, [MS]);

  const yen = n => '¥' + Math.round(n || 0).toLocaleString('ja-JP');
  // コンパクト金額（表のセル用・見切れ防止）：1.2億 / 720万 / ¥9,800。title に正確な金額
  const yenC = n => { n = Math.round(n || 0); const a = Math.abs(n); if (a >= 1e8) return (n / 1e8).toFixed(1).replace(/\.0$/, '') + '億'; if (a >= 1e4) return Math.round(n / 1e4).toLocaleString('ja-JP') + '万'; return '¥' + n.toLocaleString('ja-JP'); };
  const pct = (num, den) => (den > 0 ? (num / den * 100) : null);
  const fmtPct = v => (v == null ? '–' : v.toFixed(1) + '%');
  const rateColor = v => (v == null ? '#c4c9d0' : v >= 100 ? '#16a34a' : v >= 70 ? '#d97706' : v >= 40 ? '#4a5af0' : '#9aa1ab');
  // 達成率のチップ表示（ソフト背景で視認性を上げる）
  const rateChip = v => { if (v == null) return <span style={{ color: '#c9ced6' }}>—</span>; const m = v >= 100 ? ['#e3f5e9', '#15803d'] : v >= 70 ? ['#fdf0db', '#b45309'] : v >= 40 ? ['#eef0fe', '#3a49d8'] : ['#eef0f2', '#6b7280']; return <span style={{ display: 'inline-block', minWidth: 52, textAlign: 'center', padding: '2px 8px', borderRadius: 999, fontSize: 11.5, fontWeight: 800, background: m[0], color: m[1] }}>{fmtPct(v)}</span>; };
  const openDrill = (title, arr, kind) => { if (!arr || !arr.length) return; setDrill(kind === 'recs' ? { title, records: arr } : { title, cases: uniqCases(arr) }); };
  const prevMonth = () => { if (month > 0) setMonth(month - 1); else { setYear(year - 1); setMonth(11); } };
  const nextMonth = () => { if (month < 11) setMonth(month + 1); else { setYear(year + 1); setMonth(0); } };
  const touchX = React.useRef(null);
  const onTouchStart = e => { touchX.current = e.touches && e.touches[0] ? e.touches[0].clientX : null; };
  const onTouchEnd = e => { if (touchX.current == null) return; const end = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : touchX.current; const dx = end - touchX.current; touchX.current = null; if (Math.abs(dx) > 50) { if (dx < 0) nextMonth(); else prevMonth(); } };
  // 年ナビ（年間サマリー用）：ページ全体の月スワイプと衝突しないよう stopPropagation で分離
  const touchYX = React.useRef(null);
  const onYTouchStart = e => { e.stopPropagation(); touchYX.current = e.touches && e.touches[0] ? e.touches[0].clientX : null; };
  const onYTouchEnd = e => { e.stopPropagation(); if (touchYX.current == null) return; const end = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : touchYX.current; const dx = end - touchYX.current; touchYX.current = null; if (Math.abs(dx) > 50) { setYear(dx < 0 ? year + 1 : year - 1); } };

  const monthLabel = window.t("label.extra97",{v0:(year),v1:(month + 1)});
  const WKS = ['1W', '2W', '3W', '4W', '5W'];

  const rows = [
    { key: 'entry', get label(){return window.t("label.extra98");}, target: TARGET.entry, kind: 'cases', color: '#4a5af0' },
    { key: 'hp', get label(){return window.t("label.extra99");}, target: TARGET.hp, kind: 'na' },
    { key: 'call', get label(){return window.t("label.extra100");}, target: TARGET.call, kind: 'na' },
    { key: 'appo', get label(){return window.t("label.extra101");}, target: TARGET.appo, kind: 'cases' },
    { key: 'm1', get label(){return window.t("label.extra102");}, target: TARGET.m1, kind: 'recs' },
    { key: 'm2', get label(){return window.t("label.extra103");}, target: TARGET.m2, kind: 'recs' },
    { key: 'm3', get label(){return window.t("label.extra104");}, target: null, kind: 'recs', noTarget: true },
    { key: 'm4', get label(){return window.t("label.extra105");}, target: null, kind: 'recs', noTarget: true },
    { key: 'm5p', get label(){return window.t("label.extra106");}, target: null, kind: 'recs', noTarget: true },
    { key: 'won', get label(){return window.t("label.extra107");}, target: TARGET.won, kind: 'cases', color: '#16a34a' },
    { key: 'lost', get label(){return window.t("label.extra108");}, target: null, kind: 'cases', color: '#6b7280', noTarget: true },
  ];
  // 転換率の計算式（月次・年間で共通）：アポ率=アポ÷エントリー、二次商談率=二次÷一次、受注率=受注÷アポ、売上達成率=受注額÷目標
  const rateRowsOf = (S, amtTarget, amtBench) => ([
    { key: 'appoRate', get label(){return window.t("label.extra109");}, get bench(){return window.t("label.extra110");}, num: S.appo.length, den: S.entry.length },
    { key: 'm2Rate', get label(){return window.t("label.extra111");}, get bench(){return window.t("label.extra112");}, num: S.m2.length, den: S.m1.length },
    { key: 'wonRate', get label(){return window.t("label.extra113");}, get bench(){return window.t("label.extra114");}, num: S.won.length, den: S.appo.length },
    { key: 'salesRate', get label(){return window.t("label.extra115");}, bench: amtBench, num: S.wonAmt, den: amtTarget },
  ]);
  const rateRows = rateRowsOf(TOT, TARGET.amt, window.t("label.extra116",{v0:(yenC(TARGET.amt))}));
  const rateRowsY = rateRowsOf(YTOT, yTargetOf('amt'), window.t("label.extra117",{v0:(yenC(yTargetOf('amt')))}));
  // 転換率タイル（プログレスバー付き）＝月次/年間カードで共用
  const rateTilesGrid = (rrs) => (
    <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(4, 1fr)', gap: 12 }}>
      {rrs.map(r => { const v = pct(r.num, r.den); return (
        <div key={r.key} style={{ background: '#f6f7fa', borderRadius: 12, padding: '14px 16px' }}>
          <div style={{ fontSize: 12, color: '#9aa1ab', fontWeight: 600, marginBottom: 6 }}>{r.label}</div>
          <div style={{ fontSize: 24, fontWeight: 800, color: rateColor(v), lineHeight: 1 }}>{fmtPct(v)}</div>
          <div style={{ height: 4, borderRadius: 999, background: '#e7e9ee', marginTop: 9, overflow: 'hidden' }}>
            <div style={{ width: (v == null ? 0 : Math.min(100, v)) + '%', height: '100%', borderRadius: 999, background: rateColor(v), transition: 'width .3s' }} />
          </div>
          <div style={{ fontSize: 11, color: '#b0b6bf', marginTop: 7 }}>{r.bench}</div>
        </div>
      ); })}
    </div>
  );

  // ---- 担当者別レポート：期間集計(S)を担当者ごとに分解（view='owner'） ----
  // 列＝1回目/2回目/3回目以上/合計/商談客数/受注/受注金額/受注率（分析の担当者別内訳と同体系・2026-07-29）
  // メイン担当(ownerId)＝実績、サポート担当(subIds)＝「+n」で併記。受注率＝受注÷商談客数
  const ownerRowsOf = (S) => {
    const map = {};
    const ensureK = (uid) => { const k = uid || '_none'; if (!map[k]) { const u = (uid && uid !== '_none') ? window.APP_DATA.user(uid) : null; map[k] = { key: k, label: u ? (u.name || u.short || k) : window.t("cases.unassigned"), color: u ? (u.color || '#4a5af0') : '#9aa1ab', order: u ? 0 : 99, m1: [], m1Sub: [], m2: [], m2Sub: [], m3: [], m3Sub: [], won: [], wonSub: [], lost: [], lostSub: [], wonAmt: 0 }; } return map[k]; };
    const attr = (c, mainKey, subKey, item) => {
      ensureK(c.ownerId || '_none')[mainKey].push(item);
      (c.subIds || []).forEach(id => { if (id && id !== c.ownerId) ensureK(id)[subKey].push(item); });
    };
    S.m1.forEach(r => attr(r.c, 'm1', 'm1Sub', r));
    S.m2.forEach(r => attr(r.c, 'm2', 'm2Sub', r));
    [...(S.m3 || []), ...(S.m4 || []), ...(S.m5pAll || S.m5p || [])].forEach(r => attr(r.c, 'm3', 'm3Sub', r)); // 担当者別の「3回目以上」列＝3次+4次+5次以上（実レコード）
    S.won.forEach(c => { const amt = caseWonAmount(c); ensureK(c.ownerId || '_none').won.push(c); map[c.ownerId || '_none'].wonAmt += amt; (c.subIds || []).forEach(id => { if (id && id !== c.ownerId) ensureK(id).wonSub.push(c); }); });
    (S.lost || []).forEach(c => attr(c, 'lost', 'lostSub', c));
    return Object.values(map).sort((a, b) => (a.order - b.order) || String(a.label).localeCompare(String(b.label), 'ja'));
  };
  const uniqCaseCountOf = (recs) => { const seen = {}; let n = 0; recs.forEach(r => { const id = (r.c || r).id; if (!seen[id]) { seen[id] = 1; n++; } }); return n; };
  const uniqCasesOf = (recs) => { const seen = {}, out = []; recs.forEach(r => { const c = r.c || r; if (!seen[c.id]) { seen[c.id] = 1; out.push(c); } }); return out; };
  // メイン実績＋サポート関与バッジ。数字は常に右端フラッシュ（合計行・他列と完全整列）、
  // +n はソフト背景の小ピルを数字の下に積む。メイン0でサポートありは「0」を出す
  const ownerCellSub = (mainArr, subArr, kind, label) => (
    <span style={{ display: 'inline-flex', flexDirection: 'column', alignItems: 'flex-end', gap: 2 }}>
      {mainArr.length
        ? ownerCell(mainArr, kind, label)
        : <span style={{ color: subArr.length ? '#9aa1ab' : '#c9ced6', fontWeight: subArr.length ? 600 : 400 }}>{subArr.length ? 0 : '–'}</span>}
      {subArr.length > 0 && (
        <button onClick={() => openDrill(label + (""+window.t("label.extra118")+" ") + subArr.length + window.t("label.extra119"), subArr, kind)} title={(""+window.t("extra.kpi.support")+" ") + subArr.length + window.t("unit.count")}
          style={{ border: 'none', borderRadius: 999, background: '#eef0f4', color: '#6b7280', cursor: 'pointer', fontFamily: 'inherit', fontSize: 10, fontWeight: 800, padding: '1px 7px', lineHeight: 1.3 }}>+{subArr.length}</button>
      )}
    </span>
  );
  const ownerCell = (arr, kind, label) => {
    const n = arr.length;
    return n ? <button onClick={() => openDrill(label + '（' + n + window.t("label.extra119"), arr, kind)} style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: '#2b2f38', textDecoration: 'underline', textDecorationColor: '#d3d7de', textUnderlineOffset: 3, padding: 0 }}>{n}</button> : <span style={{ color: '#c9ced6' }}>–</span>;
  };
  const ownerTableCard = (S, scopeLabel, title) => {
    const rows = ownerRowsOf(S);
    const allRecsS = [...S.m1, ...S.m2, ...(S.m3 || []), ...(S.m4 || []), ...(S.m5pAll || S.m5p || [])];
    const totalCust = uniqCaseCountOf(allRecsS);
    const RSC = { A: 4, B: 3, C: 2, D: 1, E: 0 }, RLB = ['E', 'D', 'C', 'B', 'A'];
    const metricsOf = (custCases, allRecs, m2Recs, m3Recs, wonCases) => {
      const ranks = custCases.map(c => c.rank).filter(rk => RSC[rk] != null);
      const avgRank = ranks.length ? ranks.reduce((a, rk) => a + RSC[rk], 0) / ranks.length : null;
      const sats = custCases.map(c => c.ordererReview && Number(c.ordererReview.satisfaction)).filter(v => v > 0);
      const avgSat = sats.length ? sats.reduce((a, b) => a + b, 0) / sats.length : null;
      const avgMtg = custCases.length ? allRecs.length / custCases.length : null;
      const m2Ids = {}, m3Ids = {}, wonIds = {};
      m2Recs.forEach(x => m2Ids[x.c.id] = 1); m3Recs.forEach(x => m3Ids[x.c.id] = 1); wonCases.forEach(c => wonIds[c.id] = 1);
      const cnt = { 1: 0, 2: 0, 3: 0, 4: 0 };
      custCases.forEach(c => { cnt[wonIds[c.id] ? 4 : m3Ids[c.id] ? 3 : m2Ids[c.id] ? 2 : 1]++; });
      let best = null, bn = 0; [4, 3, 2, 1].forEach(k => { if (cnt[k] > bn) { bn = cnt[k]; best = k; } });
      return { avgRank, avgSat, avgMtg, bestStage: best };
    };
    // 温度感＝平均ランクを 0〜100° に換算（A=100°〜E=0°）。●ドット＋度数表示、70°以上=緑/45°以上=橙/未満=赤
    const rankChip = (avgRank) => {
      if (avgRank == null) return <span style={{ color: '#c9ced6' }}>—</span>;
      const deg = Math.round(avgRank / 4 * 100);
      const col = deg >= 70 ? '#16a34a' : deg >= 45 ? '#d97706' : '#dc2626';
      const lb = RLB[Math.round(avgRank)];
      return <span title={(""+window.t("extra.kpi.averageRank")+" ") + lb + (""+window.t("extra.kpi.scorePrefix")+" ") + avgRank.toFixed(1) + '/4）'} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontWeight: 800, color: col, fontSize: 13, whiteSpace: 'nowrap' }}><span style={{ width: 8, height: 8, borderRadius: 999, background: col, display: 'inline-block' }} />{deg}°</span>;
    };
    const stageChip = (st) => {
      if (!st) return <span style={{ color: '#c9ced6' }}>–</span>;
      const m = { 1: [window.t("an.meeting.1st"), '#eef0f2', '#6b7280'], 2: [window.t("an.meeting.2nd"), '#eef0fe', '#3a49d8'], 3: [window.t("label.extra120"), '#e0f4f8', '#0e7490'], 4: [window.t("dash2.table.won"), '#e3f5e9', '#15803d'] }[st];
      return <span style={{ display: 'inline-block', padding: '2px 8px', borderRadius: 999, fontSize: 11, fontWeight: 800, background: m[1], color: m[2], whiteSpace: 'nowrap' }}>{m[0]}</span>;
    };
    const satTxt = (v) => v == null ? <span style={{ color: '#c9ced6' }}>–</span> : <span style={{ color: '#b45309', fontWeight: 700, fontSize: 12 }}>★{v.toFixed(1)}</span>;
    const mtgTxt = (v) => v == null ? <span style={{ color: '#c9ced6' }}>–</span> : <span style={{ fontWeight: 600, fontSize: 12.5 }}>{v.toFixed(1)}{window.t("extra.unit.times")}</span>;
    const shareTxt = (v) => v == null ? <span style={{ color: '#c9ced6' }}>–</span> : <span style={{ fontWeight: 700, fontSize: 12, color: '#4a5af0' }}>{v.toFixed(0)}%</span>;
    const totalM = metricsOf(uniqCasesOf(allRecsS), allRecsS, S.m2, [...(S.m3 || []), ...(S.m4 || []), ...(S.m5pAll || S.m5p || [])], S.won);
    const nameTd = { padding: '9px 6px', fontWeight: 600, whiteSpace: 'nowrap' };
    const pg = ((ownerPg % 2) + 2) % 2;
    const pages = [['activity', window.t("label.extra121")], ['result', window.t("label.extra122")]];
    // ページ0＝商談活動（回数・客数・占有率）／ページ1＝成果・質（受注・失注・金額・率・温度感・満足度・平均商談・得意段階）
    const headsAct = [window.t("an.meeting.1st"), window.t("an.meeting.2nd"), window.t("dash2.table.thirdOrMore"), [window.t("cd.qs.total"), '#4a5af0'], window.t("extra.meetings.clients"), window.t("an.col.share")];
    const headsRes = [[window.t("dash2.table.won"), '#16a34a'], window.t("cust.stat.lost"), window.t("extra.kpi.orderAmount"), window.t("label.extra113"), window.t("an.col.temperature"), window.t("extra.kpi.satisfaction"), window.t("extra.kpi.averageMeetings"), window.t("an.col.bestStage")];
    const heads = pg === 0 ? headsAct : headsRes;
    return (
      <Card title={title} style={{ marginBottom: 18 }}
        action={(
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 11.5, color: '#9aa1ab' }}>{window.t("extra.kpi.swipeHint")}</span>
            <span style={{ display: 'inline-flex', gap: 2, background: '#eceef1', borderRadius: 10, padding: 3 }}>
              {pages.map((p, i) => (
                <button key={p[0]} onClick={() => setOwnerPg(i)} style={{ border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 700, padding: '5px 12px', borderRadius: 8, background: i === pg ? '#fff' : 'transparent', color: i === pg ? '#4a5af0' : '#7b828d', boxShadow: i === pg ? '0 1px 2px rgba(20,22,40,.08)' : 'none' }}>{p[1]}</button>
              ))}
            </span>
          </span>
        )}>
        <div
          onTouchStart={e => { e.stopPropagation(); ownerTouchX.current = e.touches && e.touches[0] ? e.touches[0].clientX : null; }}
          onTouchEnd={e => { e.stopPropagation(); if (ownerTouchX.current == null) return; const end = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : ownerTouchX.current; const dx = end - ownerTouchX.current; ownerTouchX.current = null; if (Math.abs(dx) > 50) setOwnerPg(dx < 0 ? pg + 1 : pg + 1); }}
          style={{ overflowX: 'auto' }}>
          <table style={{ borderCollapse: 'collapse', fontSize: 13, width: '100%', minWidth: pg === 0 ? 560 : 700 }}>
            <thead>
              <tr style={{ borderBottom: '2px solid #eef0f3' }}>
                <th style={{ ...headStyle, textAlign: 'left' }}>{window.t("an.col.owner")}</th>
                {heads.map((h, i) => { const lb = Array.isArray(h) ? h[0] : h; const col = Array.isArray(h) ? h[1] : undefined; return <th key={i} style={{ ...headStyle, textAlign: [window.t("an.col.temperature"), window.t("an.col.bestStage")].includes(lb) ? 'center' : 'right', color: col }}>{lb}</th>; })}
              </tr>
            </thead>
            <tbody>
              {rows.length === 0 && <tr><td colSpan={heads.length + 1} style={{ padding: '18px 8px', color: '#9aa1ab', fontSize: 13, textAlign: 'center' }}>{window.t("extra.kpi.noData")}</td></tr>}
              {rows.map(r => {
                const allMain = [...r.m1, ...r.m2, ...r.m3];
                const allSub = [...r.m1Sub, ...r.m2Sub, ...r.m3Sub];
                const custMain = uniqCasesOf(allMain), custSub = uniqCasesOf(allSub);
                const mx = metricsOf(custMain, allMain, r.m2, r.m3, r.won);
                return (
                  <tr key={r.key} className="row-hover" style={{ borderTop: '1px solid #f0f1f4' }}>
                    <td style={{ ...nameTd, color: r.color }}>{r.label}</td>
                    {pg === 0 ? (
                      <>
                        <td style={cellStyle}>{ownerCellSub(r.m1, r.m1Sub, 'recs', scopeLabel + ' ' + r.label + (" "+window.t("an.meeting.1st")+""))}</td>
                        <td style={cellStyle}>{ownerCellSub(r.m2, r.m2Sub, 'recs', scopeLabel + ' ' + r.label + (" "+window.t("an.meeting.2nd")+""))}</td>
                        <td style={cellStyle}>{ownerCellSub(r.m3, r.m3Sub, 'recs', scopeLabel + ' ' + r.label + (" "+window.t("dash2.table.thirdOrMore")+""))}</td>
                        <td style={{ ...cellStyle, background: '#fafbfc', fontWeight: 700 }}>{ownerCellSub(allMain, allSub, 'recs', scopeLabel + ' ' + r.label + (" "+window.t("label.extra123")+""))}</td>
                        <td style={cellStyle}>{ownerCellSub(custMain, custSub, 'cases', scopeLabel + ' ' + r.label + (" "+window.t("label.extra124")+""))}</td>
                        <td style={cellStyle}>{shareTxt(pct(custMain.length, totalCust))}</td>
                      </>
                    ) : (
                      <>
                        <td style={cellStyle}>{ownerCellSub(r.won, r.wonSub, 'cases', scopeLabel + ' ' + r.label + (" "+window.t("dash2.table.won")+""))}</td>
                        <td style={cellStyle}>{ownerCellSub(r.lost, r.lostSub, 'cases', scopeLabel + ' ' + r.label + (" "+window.t("cust.stat.lost")+""))}</td>
                        <td style={{ ...cellStyle, fontSize: 12, color: r.wonAmt ? '#15803d' : '#c9ced6', fontWeight: 600 }} title={yen(r.wonAmt)}>{r.wonAmt ? yenC(r.wonAmt) : '–'}</td>
                        <td style={cellStyle}>{rateChip(pct(r.won.length, custMain.length))}</td>
                        <td style={{ ...cellStyle, textAlign: 'center' }}>{rankChip(mx.avgRank)}</td>
                        <td style={cellStyle}>{satTxt(mx.avgSat)}</td>
                        <td style={cellStyle}>{mtgTxt(mx.avgMtg)}</td>
                        <td style={{ ...cellStyle, textAlign: 'center' }}>{stageChip(mx.bestStage)}</td>
                      </>
                    )}
                  </tr>
                );
              })}
              {rows.length > 0 && (
                <tr style={{ borderTop: '2px solid #eef0f3', background: '#fafbfc' }}>
                  <td style={{ ...nameTd, fontWeight: 800, color: '#1f2430' }}>{window.t("cd.qs.total")}</td>
                  {pg === 0 ? (
                    <>
                      <td style={{ ...cellStyle, fontWeight: 700 }}>{S.m1.length}</td>
                      <td style={{ ...cellStyle, fontWeight: 700 }}>{S.m2.length}</td>
                      <td style={{ ...cellStyle, fontWeight: 700 }}>{(S.m3 || []).length}</td>
                      <td style={{ ...cellStyle, fontWeight: 800 }}>{allRecsS.length}</td>
                      <td style={{ ...cellStyle, fontWeight: 700 }}>{totalCust}</td>
                      <td style={cellStyle}>{shareTxt(totalCust ? 100 : null)}</td>
                    </>
                  ) : (
                    <>
                      <td style={{ ...cellStyle, fontWeight: 700, color: '#16a34a' }}>{S.won.length}</td>
                      <td style={{ ...cellStyle, fontWeight: 700, color: '#6b7280' }}>{(S.lost || []).length}</td>
                      <td style={{ ...cellStyle, fontWeight: 700, color: '#15803d', fontSize: 12 }} title={yen(S.wonAmt)}>{yenC(S.wonAmt)}</td>
                      <td style={cellStyle}>{rateChip(pct(S.won.length, totalCust))}</td>
                      <td style={{ ...cellStyle, textAlign: 'center' }}>{rankChip(totalM.avgRank)}</td>
                      <td style={cellStyle}>{satTxt(totalM.avgSat)}</td>
                      <td style={cellStyle}>{mtgTxt(totalM.avgMtg)}</td>
                      <td style={{ ...cellStyle, textAlign: 'center' }}>{stageChip(totalM.bestStage)}</td>
                    </>
                  )}
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </Card>
    );
  };

  // ---- 担当者別グラフカード（◀▶／左右スワイプで1枚ずつ：案件量／温度感ランク構成／発注者満足度） ----
  const chartTouchX = React.useRef(null);
  const ownerChartsCard = (S) => {
    const RANK_COL = { A: '#16a34a', B: '#d97706', C: '#6b7280', D: '#2563eb', E: '#dc2626' };
    const rows = ownerRowsOf(S).map(r => {
      const allMain = [...r.m1, ...r.m2, ...r.m3];
      const cust = uniqCasesOf(allMain);
      const ranks = { A: 0, B: 0, C: 0, D: 0, E: 0 }; let ranked = 0;
      cust.forEach(c => { if (ranks[c.rank] != null) { ranks[c.rank]++; ranked++; } });
      const sats = cust.map(c => c.ordererReview && Number(c.ordererReview.satisfaction)).filter(v => v > 0);
      return { key: r.key, label: r.label, color: r.color, n: cust.length, ranks, ranked, sat: sats.length ? sats.reduce((x, y) => x + y, 0) / sats.length : null };
    }).filter(r => r.n > 0);
    const charts = [
      { key: 'vol', get title(){return window.t("label.extra125");} },
      { key: 'rank', get title(){return window.t("an.tempStack.title");} },
      { key: 'sat', get title(){return window.t("an.satOwner.title");} },
    ];
    const ci = ((chartIdx % 3) + 3) % 3;
    const maxN = Math.max(1, ...rows.map(r => r.n));
    const nameW = isMobile ? 76 : 110;
    const barRow = (label, color, content, right) => (
      <div key={label} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '5px 0' }}>
        <div style={{ width: nameW, flex: '0 0 auto', fontSize: 12, fontWeight: 600, color, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
        <div style={{ flex: 1, minWidth: 0 }}>{content}</div>
        <div style={{ width: 52, flex: '0 0 auto', textAlign: 'right', fontSize: 12, fontWeight: 700, color: '#2b2f38' }}>{right}</div>
      </div>
    );
    const track = (pctW, color) => (
      <div style={{ height: 14, borderRadius: 999, background: '#eef0f4', overflow: 'hidden' }}>
        <div style={{ width: pctW + '%', height: '100%', borderRadius: 999, background: color, transition: 'width .3s' }} />
      </div>
    );
    const body = ci === 0 ? (
      <div>{rows.slice().sort((a, b) => b.n - a.n).map(r => barRow(r.label, r.color, track(r.n / maxN * 100, '#4a5af0'), r.n + '件'))}</div>
    ) : ci === 1 ? (
      <div>
        {rows.slice().sort((a, b) => b.ranked - a.ranked).map(r => barRow(r.label, r.color, (
          <div style={{ display: 'flex', height: 14, borderRadius: 999, overflow: 'hidden', background: '#eef0f4' }}>
            {['A', 'B', 'C', 'D', 'E'].map(k => r.ranked ? <div key={k} title={k + '：' + r.ranks[k] + window.t("unit.count")} style={{ width: (r.ranks[k] / Math.max(1, r.ranked) * 100) + '%', background: RANK_COL[k], transition: 'width .3s' }} /> : null)}
          </div>
        ), r.ranked ? r.ranked + '件' : '–'))}
        <div style={{ display: 'flex', gap: 12, justifyContent: 'center', marginTop: 10, flexWrap: 'wrap' }}>
          {['A', 'B', 'C', 'D', 'E'].map(k => <span key={k} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11, color: '#6b7280', fontWeight: 600 }}><span style={{ width: 9, height: 9, borderRadius: 999, background: RANK_COL[k], display: 'inline-block' }} />{k}</span>)}
        </div>
      </div>
    ) : (
      <div>{rows.slice().sort((a, b) => (b.sat || 0) - (a.sat || 0)).map(r => barRow(r.label, r.color,
        track(r.sat ? Math.min(100, r.sat / 4 * 100) : 0, '#d97706'),
        r.sat ? '★' + r.sat.toFixed(1) : '–'))}</div>
    );
    return (
      <Card title={charts[ci].title} style={{ marginBottom: 18 }}
        action={(
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            <button onClick={() => setChartIdx(ci - 1)} style={{ width: 30, height: 30, borderRadius: 9, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 15, fontWeight: 700 }}>‹</button>
            {charts.map((c, i) => <span key={c.key} onClick={() => setChartIdx(i)} style={{ width: 7, height: 7, borderRadius: 999, background: i === ci ? '#4a5af0' : '#d3d7de', cursor: 'pointer', display: 'inline-block' }} />)}
            <button onClick={() => setChartIdx(ci + 1)} style={{ width: 30, height: 30, borderRadius: 9, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 15, fontWeight: 700 }}>›</button>
          </span>
        )}>
        <div
          onTouchStart={e => { e.stopPropagation(); chartTouchX.current = e.touches && e.touches[0] ? e.touches[0].clientX : null; }}
          onTouchEnd={e => { e.stopPropagation(); if (chartTouchX.current == null) return; const end = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : chartTouchX.current; const dx = end - chartTouchX.current; chartTouchX.current = null; if (Math.abs(dx) > 50) setChartIdx(dx < 0 ? ci + 1 : ci - 1); }}>
          {rows.length ? body : <div style={{ padding: '14px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{window.t("extra.kpi.noData")}</div>}
        </div>
      </Card>
    );
  };

  // ---- 内訳（経由/担当）＝バケット配列に汎用化。recs(商談)は record.c で案件判定 ----
  // keysOf は「その案件が属するキーの配列」を返す＝担当はメイン(ownerId)＋サポート(subIds)の複数計上に対応
  const breakdownBy = (buckets, total, kind, keysOf, metaOf) => {
    const getC = kind === 'recs' ? (x => x.c) : (x => x);
    const metaMap = {}, order = [];
    total.forEach(x => { const c = getC(x); keysOf(c).forEach(k => { if (!(k in metaMap)) { metaMap[k] = metaOf(c, k); order.push(k); } }); });
    order.sort((a, b) => (metaMap[a].order || 0) - (metaMap[b].order || 0));
    return order.map(k => ({ key: k, meta: metaMap[k], buckets: buckets.map(arr => arr.filter(x => keysOf(getC(x)).indexOf(k) >= 0)), tot: total.filter(x => keysOf(getC(x)).indexOf(k) >= 0) }));
  };
  const viaKey = c => [caseViaMeta(c).key], viaMeta = c => caseViaMeta(c);
  // 担当＝メイン担当＋サポート担当の両方に計上（ユーザー指定 2026-07-29）。どちらも無ければ未割当
  const ownKey = c => { const ids = []; if (c.ownerId) ids.push(c.ownerId); (c.subIds || []).forEach(id => { if (id && ids.indexOf(id) < 0) ids.push(id); }); return ids.length ? ids : ['_none']; };
  const ownMeta = (c, k) => { const u = (k && k !== '_none') ? window.APP_DATA.user(k) : null; return { label: u ? (u.name || u.short || k) : window.t("cases.unassigned"), color: u ? (u.color || '#4a5af0') : '#9aa1ab', order: u ? 0 : 99 }; };

  const cellStyle = { padding: '10px 6px', textAlign: 'right', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
  const headStyle = { padding: '9px 6px', fontSize: 12, fontWeight: 600, color: '#9aa1ab', whiteSpace: 'nowrap' };
  const numBtn = (n, onClick, bold) => (
    n ? <button onClick={onClick} style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: bold ? 14 : 13, fontWeight: bold ? 700 : 600, color: '#1f2430', textDecoration: 'underline', textDecorationColor: '#cfd3da', textUnderlineOffset: 3, padding: 0 }}>{n}</button>
      : <span style={{ color: '#c9ced6', fontWeight: bold ? 700 : 400 }}>{bold ? 0 : '–'}</span>
  );
  const cellFor = (arr, kind, label, titlePrefix, bold) => numBtn(arr.length, () => openDrill(window.t("label.extra126",{v0:(titlePrefix),v1:(label),v2:(arr.length)}), arr, kind === 'recs' ? 'recs' : 'cases'), bold);

  const toggleBtn = (isOpen, onClick, label, color) => (
    <button onClick={onClick} style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: color || '#1f2430', padding: 0 }} title={window.t("extra.kpi.breakdown")}>{isOpen ? '▾' : '▸'} {label}</button>
  );

  const scopeSel = (
    <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
      <div style={{ display: 'inline-flex', gap: 2, background: '#eceef1', borderRadius: 10, padding: 3 }}>
        {[['time', window.t("label.extra127")], ['owner', window.t("label.extra128")]].map(([v, lbl]) => (
          <button key={v} onClick={() => setView(v)} style={{ border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, padding: '6px 14px', borderRadius: 8, background: view === v ? '#fff' : 'transparent', color: view === v ? '#4a5af0' : '#7b828d', boxShadow: view === v ? '0 1px 2px rgba(20,22,40,.08)' : 'none' }}>{lbl}</button>
        ))}
      </div>
      <select value={year} onChange={e => setYear(Number(e.target.value))}
        style={{ padding: '7px 10px', borderRadius: 8, border: '1px solid #e2e4e9', background: '#fff', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: '#2b2f38', cursor: 'pointer' }}>
        {years.map(y => <option key={y} value={y}>{y}{window.t("extra.kpi.year")}</option>)}
      </select>
      <button onClick={() => { setForm(Object.assign({}, TARGET, { y_entry: yTargetOf('entry'), y_hp: yTargetOf('hp'), y_call: yTargetOf('call'), y_appo: yTargetOf('appo'), y_m1: yTargetOf('m1'), y_m2: yTargetOf('m2'), y_won: yTargetOf('won'), y_amt: yTargetOf('amt') })); setEditOpen(true); }}
        style={{ padding: '7px 14px', borderRadius: 8, border: '1px solid #d9d7f7', background: '#f3f2fe', color: '#4a5af0', fontFamily: 'inherit', fontSize: 13, fontWeight: 700, cursor: 'pointer' }}>{window.t("extra.kpi.targets")}</button>
    </div>
  );
  const editField = (k, label, isDate) => (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      <span style={{ fontSize: 12, color: '#6b7280', fontWeight: 600 }}>{label}</span>
      <input type={isDate ? 'date' : 'number'} value={form[k] == null ? '' : form[k]} onChange={e => setForm(Object.assign({}, form, { [k]: e.target.value }))}
        style={{ padding: '8px 10px', borderRadius: 8, border: '1px solid #e2e4e9', fontFamily: 'inherit', fontSize: 13, color: '#2b2f38' }} />
    </label>
  );

  const MLBL = Array.from({ length: 12 }, (_, i) => window.t("label.extra129",{v0:(i + 1)}));

  return (
    <Page title={t('page.kpi')} right={scopeSel}>
      <div onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>

        {/* ===== 年ナビ（◀▶／左右スワイプ／年ピル）＝月ナビと同じ操作感 ===== */}
        <div onTouchStart={onYTouchStart} onTouchEnd={onYTouchEnd}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, marginBottom: 14 }}>
            <button onClick={() => setYear(year - 1)} style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 18, fontWeight: 700 }}>‹</button>
            <div style={{ textAlign: 'center', minWidth: 170 }}>
              <div style={{ fontSize: 22, fontWeight: 800, color: '#1f2430', lineHeight: 1.1 }}>{year}{window.t("extra.kpi.year")}</div>
              <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{window.t("extra.kpi.annual")}{isMobile ? '' : window.t("extra.kpi.swipeYear")}</div>
            </div>
            <button onClick={() => setYear(year + 1)} style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 18, fontWeight: 700 }}>›</button>
          </div>
          {years.length > 1 && (
            <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 16 }}>
              <div style={{ display: 'inline-flex', gap: 2, background: '#eceef1', borderRadius: 12, padding: 4, flexWrap: 'wrap', justifyContent: 'center' }}>
                {years.slice().sort((a, b) => a - b).map(y => (
                  <button key={y} onClick={() => setYear(y)} style={{ minWidth: 60, padding: '6px 10px', borderRadius: 9, border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5, fontWeight: 700, background: y === year ? '#4a5af0' : 'transparent', color: y === year ? '#fff' : '#7b828d', boxShadow: y === year ? '0 2px 6px rgba(91,87,216,.35)' : 'none', transition: 'all .12s' }}>{y}</button>
                ))}
              </div>
            </div>
          )}
        </div>

        {view === 'owner' && ownerChartsCard(YTOT)}
        {view === 'owner' && ownerTableCard(YTOT, window.t("label.extra130",{v0:(year)}), window.t("label.extra131",{v0:(year)}))}

        {view === 'time' && <>
        {/* ===== 年間KPIサマリー（月別の上・スプレッドシート形式） ===== */}
        <Card title={window.t("extra.kpi.annualTitle",{v0:(year)})} action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{window.t("extra.kpi.annualHint")}</span>} style={{ marginBottom: 18 }}>
          <div style={{ overflowX: 'auto' }}>
            <table style={{ tableLayout: 'fixed', borderCollapse: 'collapse', fontSize: 13, width: '100%', minWidth: 1220 }}>
              <colgroup>
                <col style={{ width: 130 }} /><col style={{ width: 76 }} /><col style={{ width: 76 }} /><col style={{ width: 64 }} />
                {MLBL.map((_, i) => <col key={i} style={{ width: 60 }} />)}
              </colgroup>
              <thead>
                <tr style={{ borderBottom: '2px solid #eef0f3' }}>
                  <th style={{ ...headStyle, textAlign: 'left' }}>{window.t("extra.kpi.metric")}</th>
                  <th style={{ ...headStyle, textAlign: 'right' }}>{window.t("extra.kpi.annualTarget")}</th>
                  <th style={{ ...headStyle, textAlign: 'right', color: '#4a5af0' }}>{window.t("extra.kpi.annualActual")}</th>
                  <th style={{ ...headStyle, textAlign: 'right' }}>{window.t("extra.kpi.achievement")}</th>
                  {MLBL.map((ml, i) => (
                    <th key={i} style={{ ...headStyle, textAlign: 'right', cursor: 'pointer', color: i === month ? '#4a5af0' : '#9aa1ab' }} onClick={() => setMonth(i)} title={window.t("extra.kpi.monthDetails",{v0:(ml)})}>{ml}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {rows.map(r => {
                  const na = r.kind === 'na';
                  const yTarget = r.noTarget ? null : yTargetOf(r.key);
                  const yN = na ? null : YTOT[r.key].length;
                  const ach = (na || r.noTarget) ? null : pct(yN, yTarget);
                  const canExpand = !na && yN > 0;
                  const isOpen = !!expandedY[r.key];
                  const mainRow = (
                    <tr key={r.key} className="row-hover" style={{ borderTop: '1px solid #f0f1f4' }}>
                      <td style={{ padding: '9px 6px', fontWeight: 600, color: r.color || '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {canExpand ? toggleBtn(isOpen, () => setExpandedY(prev => Object.assign({}, prev, { [r.key]: !prev[r.key] })), r.label, r.color) : r.label}
                      </td>
                      <td style={{ ...cellStyle, color: '#9aa1ab' }}>{r.noTarget ? '—' : yTarget}</td>
                      <td style={{ ...cellStyle, fontWeight: 700 }}>{na ? <span style={{ color: '#c9ced6' }}>—</span> : cellFor(YTOT[r.key], r.kind, r.label, window.t("label.extra130",{v0:(year)}), true)}</td>
                      <td style={cellStyle}>{na ? <span style={{ color: '#c9ced6' }}>—</span> : rateChip(ach)}</td>
                      {MS.map((m, i) => (
                        <td key={i} style={{ ...cellStyle, background: i === month ? '#f7f7fd' : undefined }}>
                          {na ? <span style={{ color: '#c9ced6' }}>—</span> : cellFor(m[r.key], r.kind, r.label, window.t("label.extra97",{v0:(year),v1:(i + 1)}))}
                        </td>
                      ))}
                    </tr>
                  );
                  if (!canExpand || !isOpen) return mainRow;
                  const renderSet = (label, groups, gid) => (
                    <React.Fragment key={gid + r.key}>
                      <tr style={{ background: '#f2f3f7' }}><td colSpan={16} style={{ padding: '4px 6px 4px 22px', fontSize: 11, fontWeight: 700, color: '#8b91a0', letterSpacing: 1 }}>{label}</td></tr>
                      {groups.map(g => (
                        <tr key={gid + r.key + g.key} style={{ borderTop: '1px solid #f6f7f9', background: '#fcfcfd' }}>
                          <td style={{ padding: '6px 6px 6px 28px', fontSize: 12, fontWeight: 600, color: g.meta.color, whiteSpace: 'nowrap' }}>└ {g.meta.label}</td>
                          <td style={{ ...cellStyle, color: '#c9ced6' }}>—</td>
                          <td style={{ ...cellStyle, fontSize: 12 }}>{cellFor(g.tot, r.kind, `${g.meta.label} ${r.label}`, window.t("label.extra130",{v0:(year)}))}</td>
                          <td style={{ ...cellStyle, color: '#c9ced6' }}>—</td>
                          {g.buckets.map((arr, i) => <td key={i} style={{ ...cellStyle, fontSize: 12, background: i === month ? '#f7f7fd' : undefined }}>{cellFor(arr, r.kind, `${g.meta.label} ${r.label}`, window.t("label.extra97",{v0:(year),v1:(i + 1)}))}</td>)}
                        </tr>
                      ))}
                    </React.Fragment>
                  );
                  return (
                    <React.Fragment key={r.key}>
                      {mainRow}
                      {renderSet(window.t("extra.common.source"), breakdownBy(MS.map(m => m[r.key]), YTOT[r.key], r.kind, viaKey, viaMeta), 'yv')}
                      {r.key !== 'entry' && renderSet(window.t("label.extra132"), breakdownBy(MS.map(m => m[r.key]), YTOT[r.key], r.kind, ownKey, ownMeta), 'yo')}
                    </React.Fragment>
                  );
                })}
                {/* 受注金額（年間） */}
                <tr className="row-hover" style={{ borderTop: '1px solid #f0f1f4', background: '#fbfcfd' }}>
                  <td style={{ padding: '9px 6px', fontWeight: 600, color: '#15803d', whiteSpace: 'nowrap' }}>{window.t("extra.kpi.orderAmount")}</td>
                  <td style={{ ...cellStyle, color: '#9aa1ab', fontSize: 12 }} title={yen(yTargetOf('amt'))}>{yenC(yTargetOf('amt'))}</td>
                  <td style={{ ...cellStyle, fontWeight: 700, color: '#15803d', fontSize: 12 }} title={yen(YTOT.wonAmt)}>{yenC(YTOT.wonAmt)}</td>
                  <td style={cellStyle}>{rateChip(pct(YTOT.wonAmt, yTargetOf('amt')))}</td>
                  {MS.map((m, i) => (
                    <td key={i} style={{ ...cellStyle, fontSize: 12, background: i === month ? '#f7f7fd' : undefined, color: m.wonAmt ? '#15803d' : '#c9ced6' }} title={m.wonAmt ? yen(m.wonAmt) : ''}>
                      {m.wonAmt ? <button onClick={() => openDrill(window.t("label.extra133",{v0:(year),v1:(i + 1),v2:(m.won.length)}), m.won, 'cases')} style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 700, color: '#15803d', textDecoration: 'underline', textDecorationColor: '#cfe3d5', textUnderlineOffset: 3, padding: 0 }}>{yenC(m.wonAmt)}</button> : '–'}
                    </td>
                  ))}
                </tr>
              </tbody>
            </table>
          </div>
        </Card>

        {/* ===== 転換率（年間）＝月次と同じ計算式を年間合計に適用 ===== */}
        <Card title={window.t("extra.kpi.annualConversion",{v0:(year)})} style={{ marginBottom: 18 }}>
          {rateTilesGrid(rateRowsY)}
        </Card>
        </>}

        {/* ===== 月ナビ（◀▶／スワイプ／ピル） ===== */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, marginBottom: 14 }}>
          <button onClick={prevMonth} style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 18, fontWeight: 700 }}>‹</button>
          <div style={{ textAlign: 'center', minWidth: 150 }}>
            <div style={{ fontSize: 22, fontWeight: 800, color: '#1f2430', lineHeight: 1.1 }}>{month + 1}{window.t("an.mode.month")}</div>
            <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{year}{window.t("extra.kpi.year")}{isMobile ? '' : window.t("extra.kpi.swipeMonth")}</div>
          </div>
          <button onClick={nextMonth} style={{ width: 40, height: 40, borderRadius: 12, border: '1px solid #e2e4e9', background: '#fff', cursor: 'pointer', color: '#4a5af0', fontSize: 18, fontWeight: 700 }}>›</button>
        </div>
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 18 }}>
          <div style={{ display: 'inline-flex', gap: 2, background: '#eceef1', borderRadius: 12, padding: 4, flexWrap: 'wrap', justifyContent: 'center' }}>
            {MLBL.map((ml, i) => (
              <button key={i} onClick={() => setMonth(i)} style={{ minWidth: 38, padding: '6px 0', borderRadius: 9, border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5, fontWeight: 700, background: i === month ? '#4a5af0' : 'transparent', color: i === month ? '#fff' : '#7b828d', boxShadow: i === month ? '0 2px 6px rgba(91,87,216,.35)' : 'none', transition: 'all .12s' }}>{i + 1}</button>
            ))}
          </div>
        </div>

        {view === 'owner' && ownerTableCard(TOT, monthLabel, window.t("label.extra134",{v0:(monthLabel)}))}

        {view === 'time' && <>
        {/* ===== 選択月の週次詳細 ===== */}
        <Card title={window.t("extra.kpi.details",{v0:(monthLabel)})} action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{window.t("extra.kpi.detailHint")}</span>}>
          <div style={{ overflowX: 'auto' }}>
            <table style={{ tableLayout: 'fixed', borderCollapse: 'collapse', fontSize: 13, width: '100%', minWidth: 760 }}>
              <colgroup>
                <col style={{ width: '16%' }} />
                <col style={{ width: '9%' }} /><col style={{ width: '9%' }} /><col style={{ width: '9%' }} /><col style={{ width: '9%' }} /><col style={{ width: '9%' }} />
                <col style={{ width: '11%' }} />
                <col style={{ width: '14%' }} />
                <col style={{ width: '14%' }} />
              </colgroup>
              <thead>
                <tr style={{ borderBottom: '2px solid #eef0f3' }}>
                  <th style={{ ...headStyle, textAlign: 'left' }}>{window.t("extra.kpi.item")}</th>
                  {WKS.map(w => <th key={w} style={{ ...headStyle, textAlign: 'right' }}>{w}</th>)}
                  <th style={{ ...headStyle, textAlign: 'right', color: '#4a5af0' }}>{window.t("extra.kpi.monthTotal")}</th>
                  <th style={{ ...headStyle, textAlign: 'right' }}>{window.t("extra.kpi.target")}</th>
                  <th style={{ ...headStyle, textAlign: 'right' }}>{window.t("extra.kpi.achievement")}</th>
                </tr>
              </thead>
              <tbody>
                {rows.map(r => {
                  const na = r.kind === 'na';
                  const totN = na ? null : TOT[r.key].length;
                  const ach = (na || r.noTarget) ? null : pct(totN, r.target);
                  const canExpand = !na && totN > 0;
                  const isOpen = !!expandedM[r.key];
                  const mainRow = (
                    <tr key={r.key} className="row-hover" style={{ borderTop: '1px solid #f0f1f4' }}>
                      <td style={{ padding: '10px 6px', fontWeight: 600, color: r.color || '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {canExpand ? toggleBtn(isOpen, () => setExpandedM(prev => Object.assign({}, prev, { [r.key]: !prev[r.key] })), r.label, r.color) : r.label}
                      </td>
                      {W.map((w, i) => <td key={i} style={cellStyle}>{na ? <span style={{ color: '#c9ced6' }}>—</span> : cellFor(w[r.key], r.kind, r.label, `${monthLabel} ${WKS[i]}`)}</td>)}
                      <td style={{ ...cellStyle, background: '#fafbfc' }}>{na ? <span style={{ color: '#c9ced6' }}>—</span> : cellFor(TOT[r.key], r.kind, r.label, monthLabel, true)}</td>
                      <td style={{ ...cellStyle, color: '#9aa1ab' }}>{r.noTarget ? '—' : r.target}</td>
                      <td style={cellStyle}>{na ? <span style={{ color: '#c9ced6' }}>—</span> : rateChip(ach)}</td>
                    </tr>
                  );
                  if (!canExpand || !isOpen) return mainRow;
                  const renderSet = (label, groups, gid) => (
                    <React.Fragment key={gid + r.key}>
                      <tr style={{ background: '#f2f3f7' }}><td colSpan={9} style={{ padding: '4px 6px 4px 22px', fontSize: 11, fontWeight: 700, color: '#8b91a0', letterSpacing: 1 }}>{label}</td></tr>
                      {groups.map(g => (
                        <tr key={gid + r.key + g.key} style={{ borderTop: '1px solid #f6f7f9', background: '#fcfcfd' }}>
                          <td style={{ padding: '7px 6px 7px 30px', fontSize: 12, fontWeight: 600, color: g.meta.color, whiteSpace: 'nowrap' }}>└ {g.meta.label}</td>
                          {g.buckets.map((arr, i) => <td key={i} style={{ ...cellStyle, fontSize: 12 }}>{cellFor(arr, r.kind, `${g.meta.label} ${r.label}`, `${monthLabel} ${WKS[i]}`)}</td>)}
                          <td style={{ ...cellStyle, background: '#fafbfc', fontSize: 12 }}>{cellFor(g.tot, r.kind, `${g.meta.label} ${r.label}`, monthLabel)}</td>
                          <td style={{ ...cellStyle, color: '#c9ced6' }}>—</td>
                          <td style={{ ...cellStyle, color: '#c9ced6' }}>—</td>
                        </tr>
                      ))}
                    </React.Fragment>
                  );
                  return (
                    <React.Fragment key={r.key}>
                      {mainRow}
                      {renderSet(window.t("extra.common.source"), breakdownBy(W.map(w => w[r.key]), TOT[r.key], r.kind, viaKey, viaMeta), 'mv')}
                      {r.key !== 'entry' && renderSet(window.t("label.extra132"), breakdownBy(W.map(w => w[r.key]), TOT[r.key], r.kind, ownKey, ownMeta), 'mo')}
                    </React.Fragment>
                  );
                })}
                {/* 受注金額（月次） */}
                <tr className="row-hover" style={{ borderTop: '1px solid #f0f1f4', background: '#fbfcfd' }}>
                  <td style={{ padding: '10px 6px', fontWeight: 600, color: '#15803d', whiteSpace: 'nowrap' }}>{window.t("extra.kpi.orderAmount")}</td>
                  {W.map((w, i) => <td key={i} style={{ ...cellStyle, fontSize: 12, color: w.wonAmt ? '#15803d' : '#c9ced6' }} title={w.wonAmt ? yen(w.wonAmt) : ''}>{w.wonAmt ? yenC(w.wonAmt) : '–'}</td>)}
                  <td style={{ ...cellStyle, background: '#f3f7f4', fontWeight: 700, color: '#15803d', fontSize: 12 }} title={yen(TOT.wonAmt)}>{yenC(TOT.wonAmt)}</td>
                  <td style={{ ...cellStyle, color: '#9aa1ab', fontSize: 12 }} title={yen(TARGET.amt)}>{yenC(TARGET.amt)}</td>
                  <td style={cellStyle}>{rateChip(pct(TOT.wonAmt, TARGET.amt))}</td>
                </tr>
              </tbody>
            </table>
          </div>
        </Card>

        {/* ===== 転換率（選択月） ===== */}
        <Card title={window.t("extra.kpi.conversion",{v0:(monthLabel)})} style={{ marginTop: 16 }}>
          {rateTilesGrid(rateRows)}
        </Card>
        </>}

        {/* 定義ノート */}
        <div style={{ marginTop: 14, padding: '12px 14px', background: '#f6f7fa', borderRadius: 10, fontSize: 12, color: '#7b828d', lineHeight: 1.7 }}>
          <b style={{ color: '#4a5af0' }}>{window.t("extra.kpi.definitions")}</b>{window.t("extra.kpi.company")}<b>{window.t("extra.kpi.startDate")}{startYmd || window.t("extra.kpi.allTime")}</b>{window.t("extra.kpi.startDefinition")}<br />{window.t("extra.kpi.entryDefinition")}<br />{window.t("extra.kpi.meetingDefinition")}<b>{window.t("extra.kpi.uniqueCase")}</b>{window.t("extra.kpi.orderDefinition")}<b>{window.t("extra.kpi.allCases")}</b>{window.t("extra.kpi.amountDefinition")}<br />{window.t("extra.kpi.ratioDefinition")}<br />{window.t("extra.kpi.weekDefinition")}<br />
          ・<b>{window.t("extra.kpi.hpCalls")}</b>{window.t("extra.kpi.noSource")}<b>{window.t("extra.kpi.phases")}</b>{window.t("extra.kpi.targetDefinition")}<br />{window.t("extra.kpi.ownerDefinition")}<b>{window.t("an.col.share")}</b>{window.t("extra.kpi.shareDefinition")}<b>{window.t("an.col.temperature")}</b>{window.t("extra.kpi.rankDefinition")}<b>{window.t("extra.kpi.satisfaction")}</b>{window.t("extra.kpi.satisfactionDefinition")}<b>{window.t("extra.kpi.averageMeetings")}</b>{window.t("extra.kpi.averageDefinition")}<b>{window.t("an.col.bestStage")}</b>{window.t("extra.kpi.stageDefinition")}</div>
      </div>

      {editOpen && form && (
        <Modal open onClose={() => setEditOpen(false)} width={520} title={window.t("extra.kpi.settingsTitle")}
          subtitle={window.t("extra.kpi.settingsHint")}
          footer={<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
            <button onClick={() => setEditOpen(false)} style={{ padding: '8px 16px', borderRadius: 8, border: '1px solid #e2e4e9', background: '#fff', color: '#6b7280', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}>{window.t("btn.cancel")}</button>
            <button onClick={() => { saveKpiTargets({ entry: +form.entry || 0, hp: +form.hp || 0, call: +form.call || 0, appo: +form.appo || 0, m1: +form.m1 || 0, m2: +form.m2 || 0, won: +form.won || 0, amt: +form.amt || 0, y_entry: +form.y_entry || 0, y_hp: +form.y_hp || 0, y_call: +form.y_call || 0, y_appo: +form.y_appo || 0, y_m1: +form.y_m1 || 0, y_m2: +form.y_m2 || 0, y_won: +form.y_won || 0, y_amt: +form.y_amt || 0, startYmd: form.startYmd || '' }); setEditOpen(false); }}
              style={{ padding: '8px 18px', borderRadius: 8, border: 'none', background: '#4a5af0', color: '#fff', fontFamily: 'inherit', fontSize: 13, fontWeight: 700, cursor: 'pointer' }}>{window.t("btn.save")}</button>
          </div>}>
          <div style={{ fontSize: 12, fontWeight: 800, color: '#4a5af0', marginBottom: 10 }}>{window.t("extra.kpi.monthTarget")}</div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            {editField('entry', window.t("label.extra135"))}
            {editField('appo', window.t("label.extra136"))}
            {editField('m1', window.t("label.extra137"))}
            {editField('m2', window.t("label.extra138"))}
            {editField('won', window.t("label.extra139"))}
            {editField('amt', window.t("label.extra140"))}
            {editField('hp', window.t("label.extra141"))}
            {editField('call', window.t("label.extra142"))}
          </div>
          <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid #f0f1f4' }}>
            <div style={{ fontSize: 12, fontWeight: 800, color: '#4a5af0', marginBottom: 10 }}>{window.t("extra.kpi.yearTargetHint")}</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              {editField('y_entry', window.t("label.extra143"))}
              {editField('y_appo', window.t("label.extra144"))}
              {editField('y_m1', window.t("label.extra145"))}
              {editField('y_m2', window.t("label.extra146"))}
              {editField('y_won', window.t("label.extra147"))}
              {editField('y_amt', window.t("label.extra148"))}
              {editField('y_hp', window.t("label.extra149"))}
              {editField('y_call', window.t("label.extra150"))}
            </div>
          </div>
          <div style={{ marginTop: 14, paddingTop: 14, borderTop: '1px solid #f0f1f4' }}>
            {editField('startYmd', window.t("extra.kpi.startDate"), true)}
          </div>
        </Modal>
      )}

      {drill && <CaseListModal title={drill.title} cases={drill.cases} records={drill.records} onClose={() => setDrill(null)} />}
    </Page>
  );
}

Object.assign(window, { KpiReportScreen });
