/* ============================================================
   ダッシュボード — ソフトカード / ドーナツ / アクティビティ棒グラフ
   ============================================================ */

/* Card / linkBtn は ui.jsx（共有レイヤ）へ移設。以下は dashboard 固有の SoftStat/Donut/ActivityBars/MyTodoCard のみ */

/* ソフトなスタットカード（淡いラベンダー + ダークアイコンチップ） */
function SoftStat({ label, value, unit, icon, tint, onClick }) {
  return (
    <button className="asm-stat" onClick={onClick} style={{ width:'100%', textAlign:'left', background:'#fff', border:'1px solid #e9eaee', borderRadius:16, padding:'18px', cursor:onClick?'pointer':'default', boxShadow:'0 1px 2px rgba(20,22,40,.04)' }}>
      <div><div style={{fontSize:12,color:'#7b828d',fontWeight:600}}>{label}</div><div style={{fontSize:29,fontWeight:800,color:'#1c1f26',marginTop:7}}>{value}<span style={{fontSize:12,color:'#a8aeb8',marginLeft:5}}>{unit}</span></div></div>
      <span style={{width:36,height:36,borderRadius:10,background:'#eef0fe',color:'#4a5af0',display:'flex',alignItems:'center',justifyContent:'center'}}><Icon name={icon} size={18}/></span>
    </button>
  );
}

/* ドーナツ（ステータス別） */
function Donut({ data, size = 168, thickness = 26 }) {
  const r = (size - thickness) / 2, c = 2 * Math.PI * r, cx = size / 2;
  const total = data.reduce((s, d) => s + d.value, 0) || 1;
  let acc = 0;
  const gap = 3;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <g transform={`rotate(-90 ${cx} ${cx})`}>
        {data.map((d, i) => {
          const frac = d.value / total; const len = Math.max(frac * c - gap, 0);
          const seg = d.value > 0 ? (
            <circle key={i} cx={cx} cy={cx} r={r} fill="none" stroke={d.color} strokeWidth={thickness}
              strokeDasharray={`${len} ${c - len}`} strokeDashoffset={-acc} strokeLinecap="round" />
          ) : null;
          acc += frac * c; return seg;
        })}
      </g>
      <text x={cx} y={cx - 6} textAnchor="middle" style={{ fontSize: 30, fontWeight: 700, fill: '#1c1f26' }}>{total}</text>
      <text x={cx} y={cx + 16} textAnchor="middle" style={{ fontSize: 12, fill: '#9aa1ab' }}>{t('dash2.donutLabel')}</text>
    </svg>
  );
}

/* アクティビティ棒グラフ（2トーン） */
function ActivityBars({ data }) {
  const max = Math.max(...data.map(d => Math.max(d.a, d.b))) || 1;
  const H = 150;
  const [hi, setHi] = React.useState(-1); // ホバー中の月index（-1=なし）。固定ハイライトをやめ、滑った月だけ数値を表示
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 22, height: H + 28, padding: '0 4px' }}>
        {data.map((d, idx) => {
          const on = idx === hi;
          return (
            <div key={d.m} onMouseEnter={() => setHi(idx)} onMouseLeave={() => setHi(-1)} onClick={() => setHi(on ? -1 : idx)} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, cursor: 'default' }}>
              <div style={{ position: 'relative', display: 'flex', alignItems: 'flex-end', justifyContent: 'center', gap: 6, height: H, width: '100%' }}>
                {on && (
                  <div style={{ position: 'absolute', top: -4, left: '50%', transform: 'translateX(-50%)', background: '#20232f', color: '#fff', fontSize: 11.5, fontWeight: 600, padding: '5px 9px', borderRadius: 7, whiteSpace: 'nowrap', boxShadow: '0 4px 10px rgba(20,22,40,.18)', zIndex: 3, lineHeight: 1.4 }}>
                    {t('dash2.legend.meetings')} {d.a}・{t('dash.newCases')} {d.b}
                  </div>
                )}
                <div style={{ width: 16, height: (d.a / max) * H, background: on ? '#4a5af0' : '#20232f', borderRadius: '6px 6px 0 0', transition: 'height .3s, background .15s' }} />
                <div style={{ width: 16, height: (d.b / max) * H, background: on ? '#7dccf7' : '#a9defa', borderRadius: '6px 6px 0 0', transition: 'height .3s, background .15s' }} />
              </div>
              <div style={{ fontSize: 12, color: on ? '#4a5af0' : '#9aa1ab', fontWeight: on ? 700 : 500 }}>{d.m}</div>
            </div>
          );
        })}
      </div>
      <div style={{ display: 'flex', gap: 18, marginTop: 6, paddingLeft: 4 }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#7b828d' }}><span style={{ width: 9, height: 9, borderRadius: 3, background: '#20232f' }} />{t('dash2.legend.meetings')}</span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#7b828d' }}><span style={{ width: 9, height: 9, borderRadius: 3, background: '#a9defa' }} />{t('dash.newCases')}</span>
      </div>
    </div>
  );
}

// マイ ToDo・期限：ログイン中の担当者(主/副)の「対応が必要」を緊急度順に集約（業務担当の個人アクション一覧）
function MyTodoCard({ pool, navigate, allScope }) {
  const D = window.APP_DATA;
  // 完了扱い（ToDoに出さない）：ステータスの closed フラグ優先（設定→ステータスで切替）。未設定の既定は won/lost/done
  const closed = (c) => { const s = D.STATUS && D.STATUS[c.status]; return (s && s.closed != null) ? !!s.closed : ['won', 'lost', 'done'].includes(c.status); };
  const TY = {
    nextAction: { label: t('na.todoLabel'), color: '#4f46e5' },
    mail: { get label(){return window.t("cd.proposalMail");}, color: '#4a5af0' },
    thanks: { get label(){return window.t("label.extra53");}, color: '#e08a1e' },
    meeting: { get label(){return window.t("extra.meetings.label");}, color: '#2e9e6b' },
    stale: { get label(){return window.t("notif.idle");}, color: '#b45309' },
  };
  const items = [];
  pool.forEach(c => {
    if (closed(c)) return;
    const cust = D.customer(c.customerId) || {};
    const title = cust.company || cust.shortName || c.title || '案件'; // 正式社名を優先（shortNameは取込時5文字カットで途中切れするため）。行は省略記号付きなので長くてもOK
    // 次の一手（AI提案／手動のToDo）：未完了分を最優先で表示（やることが具体的に分かる）
    (c.nextActions || []).forEach(na => {
      if (na.done) return;
      const d = na.due ? daysUntil(na.due) : null;
      items.push({ c, title: na.text || title, type: 'nextAction', naType: na.type, d: d == null ? 0 : d, sort: d == null ? -0.5 : d - 0.5 });
    });
    const mt = caseMeetingAt(c);
    // 提案メール期限（未送信／不要以外・7日以内 or 超過）
    const pm = c.proposalMailStatus || 'none';
    if (pm !== 'sent' && pm !== 'na') { const due = caseMailDue(c); const d = due != null ? daysUntil(due) : null; if (d != null && d <= 7) items.push({ c, title, type: 'mail', d, sort: d }); }
    // お礼メール（商談実施済・未送信）
    const tm = c.thanksMailStatus || 'none';
    if (mt && mt.slice(0, 10) <= today() && tm !== 'sent' && tm !== 'na') { const d = daysUntil(mt); items.push({ c, title, type: 'thanks', d: d == null ? 0 : d, sort: d == null ? 0 : d }); }
    // これからの商談（7日以内・準備リマインド）
    if (mt && mt.slice(0, 10) >= today()) { const d = daysUntil(mt); if (d != null && d <= 7) items.push({ c, title, type: 'meeting', d, sort: d }); }
    // 放置（7日以上動きなし・次回商談なし）— 期限系の後ろに回す。判定は通知の「放置」と同じ caseIdleInfo（単一の真実）
    const idle = caseIdleInfo(c);
    if (idle) items.push({ c, title, type: 'stale', d: idle.idleDays, sort: 100 });
  });
  items.sort((a, b) => a.sort - b.sort);
  const top = items.slice(0, 8);
  const chipOf = (it) => {
    if (it.type === 'stale') return { txt: it.d + '日 動きなし', col: '#b45309', bg: '#fdf0db' };
    if (it.d < 0) return { txt: (-it.d) + '日超過', col: '#c0392b', bg: '#fdecea' };
    if (it.d === 0) return { txt: '今日', col: '#d97706', bg: '#fef3e2' };
    return { txt: 'あと' + it.d + '日', col: '#2e9e6b', bg: '#eef6f0' };
  };
  return (
    <Card title={(allScope ? window.t("extra.dashboard.companyTodos") : window.t("extra.dashboard.myTodos")) + items.length + '）'}>
      {top.length === 0
        ? <div style={{ fontSize: 13, color: '#9aa1ab', padding: '8px 2px' }}>{allScope ? window.t("extra.dashboard.companyEmpty") : window.t("extra.dashboard.empty")}</div>
        : top.map((it, i) => {
          // 次の一手は種別（電話/メール等）をそのままラベルに出す＝今日やる行動が一目で分かる
          const naMeta = it.type === 'nextAction' && it.naType && it.naType !== 'other' ? naTypeMeta(it.naType) : null;
          const ty = naMeta ? { label: naMeta.label, color: naMeta.color } : TY[it.type]; const ch = chipOf(it);
          const owner = allScope ? D.user(it.c.ownerId) : null; // 全社表示時は担当者を併記（誰のToDoか分かるように）
          return (
            <div key={it.type + it.c.id + i} className="row-hover" onClick={() => navigate('case', it.c.id)}
              style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 4px', cursor: 'pointer', borderTop: i ? '1px solid #f0f1f3' : 'none' }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: ty.color, flexShrink: 0 }} />
              <span style={{ fontSize: 12, color: ty.color, fontWeight: 700, flexShrink: 0, width: 64 }}>{ty.label}</span>
              <span style={{ fontSize: 13, color: '#2b2f38', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{it.title}</span>
              {allScope && <span title={owner ? owner.name : window.t("cases.unassigned")} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, flexShrink: 0, width: 92, overflow: 'hidden' }}><Avatar user={owner} size={20} /><span style={{ fontSize: 11.5, color: '#7b828d', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{owner ? owner.short : window.t("cases.unassigned")}</span></span>}
              <span style={{ fontSize: 11.5, fontWeight: 700, color: ch.col, background: ch.bg, borderRadius: 6, padding: '2px 7px', flexShrink: 0 }}>{ch.txt}</span>
            </div>
          );
        })}
      {items.length > top.length && <div style={{ fontSize: 12, color: '#9aa1ab', padding: '9px 4px 2px', textAlign: 'center', borderTop: '1px solid #f0f1f3' }}>{window.t("extra.common.others")}{items.length - top.length}{window.t("unit.count")}</div>}
    </Card>
  );
}

/* 朝のAIブリーフィング：今日の商談・期限/ToDo・放置案件・優先リードをAIが1枚に要約（1日1回・localStorageキャッシュ）。
   朝ダッシュボードを開けば「今日やるべきこと」が上から順に分かる */
function DailyBriefing({ pool, navigate }) {
  const { rcLeads, currentUser, cases: allCases, workspaceId } = useStore(); // ref解決はスコープに関わらず全案件から（材料はpool=スコープ連動のまま）
  const D = window.APP_DATA;
  const key = 'anken_brief_' + workspaceId + '_' + (currentUser && currentUser.id) + '_' + today();
  const [brief, setBrief] = React.useState(() => { try { return JSON.parse(localStorage.getItem(key)); } catch (_) { return null; } });
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const custName = (c) => { const cu = D.customer(c.customerId); return cu ? (cu.shortName || cu.company) : ''; };
  const gen = async () => {
    if (busy) return; setBusy(true); setErr('');
    try {
      // 行頭の [ID] はクリック遷移用（AIが actions.ref に入れて返す。本文には出さないようプロンプトで指示）
      const meetings = pool.filter(c => (caseMeetingAt(c) || '').slice(0, 10) === today())
        .map(c => '[' + c.id + '] ' + fmtTime(caseMeetingAt(c)) + ' ' + custName(c) + '（' + (c.title || '') + '）').slice(0, 8);
      const dues = pool.filter(c => !['won', 'lost', 'done'].includes(c.status)).map(c => {
        const pm = c.proposalMailStatus || 'none';
        if (pm === 'sent' || pm === 'na') return null;
        const due = caseMailDue(c); const d = due != null ? daysUntil(due) : null;
        if (d == null || d > 3) return null;
        return '[' + c.id + '] ' + custName(c) + '：提案メール' + (d < 0 ? (-d) + '日超過' : d === 0 ? '今日が期限' : 'あと' + d + '日');
      }).filter(Boolean).slice(0, 8);
      const stales = pool.map(c => { const i = caseIdleInfo(c); return i ? { c, n: i.idleDays } : null; }).filter(Boolean)
        .sort((a, b) => b.n - a.n).slice(0, 5).map(x => '[' + x.c.id + '] ' + custName(x.c) + '：' + (x.c.title || '') + '（' + x.n + '日動きなし）');
      const leads = (rcLeads || []).filter(l => !['entry', 'applied'].includes(l.apoStatus) && l.aiScore != null)
        .sort((a, b) => (b.aiScore || 0) - (a.aiScore || 0)).slice(0, 3)
        .map(l => '[' + l.id + '] ' + (l.company || l.title || '') + '（スコア' + l.aiScore + '・' + (l.category || '') + '）');
      const r = await API.dailyBrief({ date: today(), user: currentUser.name, meetings, dues, stales, leads });
      setBrief(r.brief); try { localStorage.setItem(key, JSON.stringify(r.brief)); } catch (_) {}
    } catch (e) { setErr(e.message || '生成に失敗しました'); }
    setBusy(false);
  };
  React.useEffect(() => { if (!brief) gen(); }, []);
  // アクションの種類（サーバのtype優先・無ければ文言から推定）→ 色分けアイコンで一目で種類が分かる
  const typeOf = (a) => {
    if (a.type && ['meeting', 'due', 'stale', 'lead'].includes(a.type)) return a.type;
    const s = (a.head || '') + (a.body || '');
    if (/商談|面談|アポ|打合せ/.test(s)) return 'meeting';
    if (/期限|〆切|締切|超過|送付|提出/.test(s)) return 'due';
    if (/放置|動きなし|フォロー/.test(s)) return 'stale';
    if (/リード|エントリー/.test(s)) return 'lead';
    return 'other';
  };
  const TYPE_META = {
    meeting: { icon: 'calendar', c: '#4a5af0', bg: '#eef0fe', get label(){return window.t("extra.meetings.label");} },
    due: { icon: 'alert', c: '#dc2626', bg: '#fdecec', get label(){return window.t("cd.due");} },
    stale: { icon: 'clock', c: '#b45309', bg: '#fdf0db', get label(){return window.t("notif.idle");} },
    lead: { icon: 'spark', c: '#15803d', bg: '#e3f5e9', get label(){return window.t("extra.common.lead");} },
    other: { icon: 'check', c: '#5b626d', bg: '#eef0f2', get label(){return window.t("na.type.other");} },
  };
  const timeOf = (a) => ((a.body || '').match(/\b(\d{1,2}:\d{2})\b/) || [])[1] || null;
  // 表示用：万一AIが本文に [ID] を書いてしまっても隠す
  const stripRef = (s) => String(s || '').replace(/[\[［]\s*(?:k|rcl|khn|chn)[\w-]*\s*[\]］]\s*/g, '').trim();
  // クリック先の解決：①AIが返す ref（[ID]をそのまま）→ ②本文の社名から自動マッチ（旧キャッシュ/サーバ未更新でも飛べる）
  const resolveTarget = (a) => {
    const ref = String(a.ref || '').replace(/[\[\]［］\s]/g, '');
    if (ref) {
      if (ref.indexOf('rcl') === 0 && (rcLeads || []).some(l => l.id === ref)) return { screen: 'apo', id: ref };
      if ((allCases || []).some(c => c.id === ref)) return { screen: 'case', id: ref };
    }
    const norm = (x) => String(x || '').replace(/株式会社|有限会社|合同会社|学校法人|\s/g, '');
    const hay = norm((a.head || '') + (a.body || ''));
    let best = null;
    (allCases || []).forEach(c => {
      const cu = D.customer(c.customerId); const nm = norm(cu && (cu.shortName || cu.company));
      if (nm && nm.length >= 2 && hay.includes(nm)) { if (!best || String(c.updatedAt || '') > String(best._u || '')) best = { screen: 'case', id: c.id, _u: c.updatedAt }; }
    });
    if (best) return { screen: best.screen, id: best.id };
    let lead = null;
    (rcLeads || []).forEach(l => { if (lead) return; const nm = norm(l.company || l.title); if (nm && nm.length >= 2 && hay.includes(nm)) lead = { screen: 'apo', id: l.id }; });
    return lead;
  };
  const d0 = parseDT(today() + 'T00:00');
  return (
    <div style={{ marginBottom: 16, borderRadius: 16, border: '1px solid #e3e2fb', background: 'linear-gradient(135deg,#f7f6ff 0%,#eef4ff 60%,#f2fbff 100%)', padding: '16px 18px 14px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ width: 30, height: 30, borderRadius: 9, background: 'linear-gradient(135deg,#6f6ae8,#4f8df0)', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', boxShadow: '0 3px 8px rgba(91,87,216,.35)' }}>
          <Icon name="spark" size={16} fill="#fff" />
        </div>
        <div>
          <div style={{ fontSize: 14, fontWeight: 800, color: '#2b2882', letterSpacing: '.01em' }}>{window.t("extra.dashboard.briefing")}</div>
          <div style={{ fontSize: 11, color: '#8d93b8', marginTop: 1 }}>{window.t("extra.dashboard.date",{v0:(d0.getMonth() + 1),v1:(d0.getDate()),v2:(wd(d0.getDay()))})}{window.t("extra.dashboard.aiSummaryHint")}</div>
        </div>
        <div style={{ flex: 1 }} />
        <button onClick={gen} disabled={busy} title={window.t("extra.dashboard.refreshBriefing")} style={{ flexShrink:0, whiteSpace:'nowrap', display: 'inline-flex', alignItems: 'center', gap: 5, border: '1px solid #d9d7f5', background: '#ffffffcc', color: '#4a5af0', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 700, borderRadius: 8, padding: '5px 11px' }}>
          <Icon name="refresh" size={12} stroke={2.2} />{busy ? window.t("an.aiReview.generating") : window.t("an.aiReview.regenerate")}
        </button>
      </div>
      {err ? (
        <div style={{ fontSize: 12.5, color: '#9aa1ab', marginTop: 10 }}>{window.t("extra.dashboard.briefingFailed")}<button onClick={gen} style={{ border: 'none', background: 'transparent', color: '#4a5af0', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5, fontWeight: 700 }}>{window.t("login.retry")}</button></div>
      ) : !brief ? (
        <div style={{ fontSize: 12.5, color: '#8d93b8', marginTop: 10 }}>{window.t("extra.dashboard.briefingLoading")}</div>
      ) : (
        <div style={{ marginTop: 11 }}>
          <div style={{ fontSize: 15, fontWeight: 800, color: '#1f2430', lineHeight: 1.55 }}>{brief.headline}</div>
          {(brief.actions || []).length > 0 && (
            <div style={{ marginTop: 10, background: '#fff', border: '1px solid #ecebfa', borderRadius: 12, boxShadow: '0 1px 3px rgba(40,40,90,.05)', overflow: 'hidden' }}>
              {brief.actions.map((a, i) => { const m = TYPE_META[typeOf(a)]; const tm = typeOf(a) === 'meeting' ? timeOf(a) : null; const tgt = resolveTarget(a); return (
                <div key={i} className={tgt ? 'row-hover' : undefined} onClick={tgt ? () => navigate(tgt.screen, tgt.id) : undefined}
                  title={tgt ? (tgt.screen === 'apo' ? window.t("extra.dashboard.openLead") : window.t("triage.openCase")) : undefined}
                  style={{ display: 'flex', gap: 11, padding: '10px 14px', alignItems: 'flex-start', borderTop: i ? '1px solid #f4f4f9' : 'none', cursor: tgt ? 'pointer' : 'default' }}>
                  <div style={{ width: 28, height: 28, borderRadius: 8, background: m.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', marginTop: 1 }}>
                    <Icon name={m.icon} size={14} stroke={2.2} style={{ color: m.c }} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                      <span style={{ fontSize: 13, fontWeight: 800, color: '#23262e' }}>{stripRef(a.head)}</span>
                      <span style={{ fontSize: 10, fontWeight: 700, color: m.c, background: m.bg, padding: '1px 7px', borderRadius: 999 }}>{m.label}</span>
                      {tm && <span style={{ fontSize: 10.5, fontWeight: 800, color: '#4a5af0', background: '#eef0fe', padding: '1px 8px', borderRadius: 999, fontFamily: 'var(--mono)' }}>{tm}</span>}
                    </div>
                    {a.body && <div style={{ fontSize: 12.5, color: '#5b626d', lineHeight: 1.6, marginTop: 2 }}>{stripRef(a.body)}</div>}
                  </div>
                  {tgt && <Icon name="chevronRight" size={15} stroke={2.2} style={{ color: '#c4c9d0', flex: '0 0 auto', alignSelf: 'center' }} />}
                </div>
              ); })}
            </div>
          )}
          {brief.note && (
            <div style={{ display: 'flex', gap: 7, alignItems: 'flex-start', marginTop: 9, fontSize: 12, color: '#6d739c', lineHeight: 1.6 }}>
              <span style={{ flex: '0 0 auto' }}>💡</span><span>{brief.note}</span>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* CaseListModal / heldStageRecords / uniqCases / MonthlyStatsTable / StageOwnerTable は
   ui.jsx(L0) へ移設済み（2026-07-19・分析画面と共用のため・SDD R2）。 */

function Dashboard() {
  const { cases: activeCases, navigate, currentUser, claimCase, meetings, can, bodyDupMap, bodyDupCount } = useStore();
  // 案件一覧（既定=アクティブ表示）と対象を揃える：アーカイブ済み案件は ToDo・期限・集計から除外（封存した案件の古い未送信メールが溜まり続けるのを防ぐ）
  const cases = activeCases.filter(c => !c.archived);
  const isMobile = useIsMobile();
  const D = window.APP_DATA;
  const canAssign = can('assign'); const canApo = can('apoOps');
  const myCases = cases.filter(c => c.ownerId === currentUser.id || c.subIds.includes(currentUser.id));
  // 自分 ⇄ 全員 切替：集計・一覧・グラフ・ToDo の対象を切り替える（全員=全社の対応待ちを担当者付きで表示／新着は未アサイン共通でスコープ非依存）。選択は端末ごと localStorage に保持
  const [scope, setScope] = React.useState(() => { try { return localStorage.getItem('anken_dash_scope') === 'all' ? 'all' : 'mine'; } catch (_) { return 'mine'; } });
  React.useEffect(() => { try { localStorage.setItem('anken_dash_scope', scope); } catch (_) {} }, [scope]);
  const scoped = scope === 'mine' ? myCases : cases;
  const scopedIds = new Set(scoped.map(c => c.id));
  // 対応中/商談中（スコープ対象。カードのドリルダウンと一致）
  const workingN = scoped.filter(c => c.status === 'working').length;
  const negoN = scoped.filter(c => c.status === 'negotiating').length;
  // 新着 = ステータス新規かつ未アサイン（誰でも引き受け可＝スコープ非依存）
  const newCases = cases.filter(c => c.status === 'new' && !c.ownerId);
  // 本日の商談 = 商談日が今日（スコープ対象）
  const todayMeetings = scoped.filter(c => (caseMeetingAt(c) || '').slice(0, 10) === today());
  // 直近の商談 = 今日以降の商談日を持つ案件（スコープ対象・日付順）
  const upcoming = scoped
    .map(c => ({ c, mt: caseMeetingAt(c) }))
    .filter(({ mt }) => mt && mt.slice(0, 10) >= today())
    .sort((a, b) => a.mt.localeCompare(b.mt)).slice(0, 4);
  const dueSoon = scoped
    .map(c => ({ c, due: caseDue(c) }))
    .filter(({ c, due }) => {
      const d = daysUntil(due);
      if (d === null || d < -7 || d > 4 || ['won', 'lost', 'done'].includes(c.status)) return false;
      // 提案書がすでに提出済み／不要なら「提出期限」アラートから除外（提出済みを催さない）
      return !['submitted', 'na'].includes(c.proposalStatus || 'none');
    })
    .sort((a, b) => a.due.localeCompare(b.due)).slice(0, 6);
  const count = (s) => scoped.filter(c => c.status === s).length;
  // 全ステータス（マスタ追加分も含む）を実数で集計。0件は表示しない
  const donutData = D.STATUS_ORDER
    .map(s => ({ label: D.STATUS[s].label, value: count(s), color: D.STATUS[s].color }))
    .filter(d => d.value > 0);
  // 活動量：直近5ヶ月を実データで集計（a=商談数 / b=新規案件数）
  // 商談数 = 手動の商談記録 + 会議記録（Fireflies等）。同一案件・同日の重複は1件として数える
  const ym = (s) => (s || '').slice(0, 7);
  const activity = (() => {
    const base = parseDT(today() + 'T00:00');
    const months = [];
    for (let i = 4; i >= 0; i--) { const d = new Date(base.getFullYear(), base.getMonth() - i, 1); months.push({ key: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`, m: `${d.getMonth() + 1}月` }); }
    const held = new Set(); // caseId+日
    meetings.forEach(x => { if (x.datetime && scopedIds.has(x.caseId)) held.add(x.caseId + (x.datetime || '').slice(0, 10)); });
    scoped.forEach(c => (c.meetingDocs || []).forEach(doc => { if (doc.datetime) held.add(c.id + doc.datetime.slice(0, 10)); }));
    return months.map(mo => ({
      m: mo.m,
      a: [...held].filter(k => k.slice(-10, -3) === mo.key).length,
      b: scoped.filter(c => ym(c.createdAt) === mo.key).length,
    }));
  })();

  /* 月別テーブルの母集団：スコープ（自分/全員）で絞ったアーカイブ込みの案件（成約後はアーカイブされる運用のため
     除外すると受注実績が過去から消える）。集計・描画・ドリルダウンは共有部品 <MonthlyStatsTable> に統一。 */
  const statBase = scope === 'mine' ? activeCases.filter(c => c.ownerId === currentUser.id || c.subIds.includes(currentUser.id)) : activeCases;

  return (
    <Page title={t('page.dashboard')}>
      <section className="asm-hero">
        <div><div style={{color:'#7de3f7',fontSize:10,fontWeight:700,letterSpacing:'.16em',marginBottom:9}}>ASM / DAILY OVERVIEW</div><h1>{t('dash.hello', {name:currentUser.short})}</h1><p>{today()}{window.t("extra.dashboard.tagline")}</p></div>
        <div className="asm-hero-stats">
          <button className="asm-hero-stat" onClick={() => navigate('cases',null,{mtgDate:today(),mine:scope==='mine'})}><span>{window.t("extra.dashboard.todayMeetings")}</span><strong>{todayMeetings.length}</strong></button>
          <button className="asm-hero-stat" onClick={() => navigate('cases',null,{status:'new'})}><span>{window.t("nav.new")}</span><strong style={{color:'#7de3f7'}}>{newCases.length}</strong></button>
          <button className="asm-hero-stat" onClick={() => navigate('cases',null,{status:'negotiating',mine:scope==='mine'})}><span>{window.t("extra.dashboard.negotiating")}</span><strong>{negoN}</strong></button>
        </div>
      </section>
      <div style={{ marginBottom: 20, display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
        <div>
          <div style={{ fontSize: 15, fontWeight: 700, color: '#1c1f26' }}>{window.t("extra.dashboard.activity")}</div>
          <div style={{ fontSize: 13.5, color: '#7b828d', marginTop: 4 }}><span style={{ color: '#4a5af0' }}>{(() => { const d = parseDT(today() + 'T00:00'); return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日(${wd(d.getDay())})`; })()}</span></div>
        </div>
        {/* 自分 ⇄ 全員 ビュー切替：統計・一覧・グラフの集計対象を「自分の担当」か「全案件」で切替 */}
        <div style={{ display: 'inline-flex', background: '#eceef1', borderRadius: 10, padding: 3 }}>
          {[['mine', window.t("label.extra54")], ['all', window.t("calendar.owner.all")]].map(([v, lbl]) => (
            <button key={v} onClick={() => setScope(v)} style={{ border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, padding: '6px 18px', borderRadius: 8, background: scope === v ? '#fff' : 'transparent', color: scope === v ? '#4a5af0' : '#7b828d', boxShadow: scope === v ? '0 1px 2px rgba(20,22,40,.08)' : 'none', transition: 'all .12s' }}>{lbl}</button>
          ))}
        </div>
      </div>

      {/* データ整合性の警告：本文が別案件と同一＝取込ミスの疑い（安全網）。最初の1件へ飛べる */}
      {bodyDupCount > 0 && (() => { const firstId = Object.keys(bodyDupMap)[0]; return (
        <div onClick={() => firstId && navigate('case', firstId)} style={{ display: 'flex', alignItems: 'center', gap: 10, background: '#fef3f2', border: '1px solid #fbd5cf', borderRadius: 11, padding: '12px 16px', marginBottom: 16, cursor: firstId ? 'pointer' : 'default' }}>
          <Icon name="alert" size={17} stroke={2} style={{ color: '#c0392b', flex: '0 0 auto' }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: '#7a2e26' }}>{t('dash.bodyDup.title', { n: bodyDupCount })}</div>
            <div style={{ fontSize: 12, color: '#96463d', marginTop: 2 }}>{t('dash.bodyDup.desc')}</div>
          </div>
          {firstId && <Icon name="chevronRight" size={16} stroke={2} style={{ color: '#c9928c', flex: '0 0 auto' }} />}
        </div>
      ); })()}

      {/* 朝のAIブリーフィング：今日やるべきことを1枚に（スコープ連動） */}
      <DailyBriefing pool={scoped} navigate={navigate} />

      {/* スタット：クリックで該当の絞り込みへ。スコープ(自分/全員)で集計対象が変わる */}
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? 'repeat(2,1fr)' : 'repeat(4,1fr)', gap: isMobile ? 10 : 14, marginBottom: 16 }}>
        <SoftStat label={t('dash.todayMeetings')} value={todayMeetings.length} unit={t('unit.count')} icon="calendar" onClick={() => navigate('cases', null, { mtgDate: today(), mine: scope === 'mine' })} />
        <SoftStat label={t('dash.newUnassigned')} value={newCases.length} unit={t('unit.count')} icon="inbox" onClick={() => navigate('cases', null, { status: 'new' })} />
        <SoftStat label={scope === 'mine' ? t('dash.myWorking') : window.t("extra.dashboard.companyWorking")} value={workingN} unit={t('unit.count')} icon="cases" onClick={() => navigate('cases', null, { status: 'working', mine: scope === 'mine' })} />
        <SoftStat label={scope === 'mine' ? t('dash.myNego') : window.t("extra.dashboard.companyNegotiating")} value={negoN} unit={t('unit.count')} icon="cases" onClick={() => navigate('cases', null, { status: 'negotiating', mine: scope === 'mine' })} />
      </div>

      {/* マイ ToDo・期限（業務担当の個人アクション一覧）：未送信メール期限・お礼メール・直近商談・停滞を緊急度順に */}
      <div style={{ marginBottom: 16 }}>
        <MyTodoCard pool={scoped} allScope={scope === 'all'} navigate={navigate} />
      </div>

      {/* アクティビティ（全幅：グラフは横幅があるほど見やすく、下の左右カラムの高さも均す） */}
      <div style={{ marginBottom: 16 }}>
        <Card title={t("dash.activity")} action={<span style={{ fontSize: 12, color: "#9aa1ab" }}>{t("dash.last5m")}</span>}>
          <ActivityBars data={activity} />
        </Card>
      </div>

      {/* minmax(0,…)：日本語の長い案件名（nowrap）が列の最小幅を押し広げて横はみ出しするのを防ぐ */}
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'minmax(0,1.4fr) minmax(0,1fr)', gap: 16, alignItems: 'stretch' }}>
        {/* 左列 */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
          {/* 本日の商談 */}
          <Card title={t("dash.upcoming")} pad={0} action={<button onClick={() => navigate('calendar')} style={linkBtn}>{t('nav.calendar')} <Icon name="arrowRight" size={13} stroke={2} /></button>}>
            {upcoming.map(({ c, mt }, i) => {
              const cust = D.customer(c.customerId); const owner = D.user(c.ownerId);
              return (
                <div key={c.id} className="row-hover" onClick={() => navigate('case', c.id)}
                  style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '13px 18px', cursor: 'pointer', borderBottom: i === upcoming.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
                  <div style={{ textAlign: 'center', width: 50, flex: '0 0 auto' }}>
                    <div style={{ fontSize: 16, fontWeight: 700, color: '#1c1f26', fontFamily: 'var(--mono)' }}>{fmtTime(mt)}</div>
                    <div style={{ fontSize: 12, color: '#4a5af0' }}>{fmtDate(mt, true)}</div>
                  </div>
                  <div style={{ width: 1, height: 32, background: '#eef0f3' }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.title}</div>
                    <div style={{ fontSize: 12, color: '#7b828d', marginTop: 2 }}>{cust.company}</div>
                  </div>
                  <StatusBadge status={c.status} size="sm" />
                  <Avatar user={owner} size={26} />
                </div>
              );
            })}
            {upcoming.length === 0 && <div style={{ padding: 30, textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('dash2.empty.noUpcoming')}</div>}
          </Card>

          {/* 新着案件（左列の末尾カード：flex:1 で右列と底辺を揃える） */}
          <Card title={t('dash2.newCasesTitle', { count: newCases.length })} pad={0} style={{ flex: 1 }} action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{canAssign ? t('dash2.newCasesAction') : t('dash2.newCasesActionRestricted')}</span>}>
            {newCases.slice(0, 5).map((c, i, arr) => {
              const cust = D.customer(c.customerId);
              return (
                <div key={c.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '13px 18px', borderBottom: i === arr.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
                  <div className="row-hover-plain" onClick={() => navigate('case', c.id)} style={{ flex: 1, minWidth: 0, cursor: 'pointer' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#2563eb', flex: '0 0 auto' }} />
                      <span style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.title}</span>
                    </div>
                    <div style={{ fontSize: 12, color: '#7b828d', marginTop: 3, marginLeft: 14 }}>{cust.company} · {t('dash2.acquiredTime', { time: relTime(c.createdAt) })}</div>
                  </div>
                  <Button size="sm" variant="primary" onClick={() => claimCase(c.id)}>{t('dash2.accept')}</Button>{/* 自己接続：全員可（claimCase は未割当のみ・本人を担当に）。指派他人は下の QuickAssign（マネジャー＋） */}
                  <div onClick={(e) => e.stopPropagation()}><QuickAssign caseId={c.id} compact /></div>
                </div>
              );
            })}
            {newCases.length > 5 && <div className="row-hover" onClick={() => navigate('cases', null, { status: 'new' })} style={{ padding: '12px 18px', cursor: 'pointer', fontSize: 12.5, fontWeight: 600, color: '#4a5af0', textAlign: 'center', borderTop: '1px solid #f4f5f7' }}>{window.t("extra.common.others")}{newCases.length - 5}{window.t("extra.dashboard.viewAllCount")}</div>}
            {newCases.length === 0 && <div style={{ padding: 30, textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('dash2.empty.noNewCases')}</div>}
          </Card>
        </div>

        {/* 右列 */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
          {/* 分析ドーナツ */}
          <Card title={t("dash.statusAnalysis")}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
              <Donut data={donutData} />
              <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 11 }}>
                {donutData.map(d => (
                  <div key={d.label} style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
                    <span style={{ width: 10, height: 10, borderRadius: 3, background: d.color, flex: '0 0 auto' }} />
                    <span style={{ fontSize: 12.5, color: '#5b626d', flex: 1 }}>{d.label}</span>
                    <span style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}>{d.value}</span>
                  </div>
                ))}
              </div>
            </div>
          </Card>

          {/* 期限間近 */}
          <Card title={t("dash.dueSoon")} pad={0}>
            {dueSoon.map(({ c, due }, i) => {
              const du = daysUntil(due); const cust = D.customer(c.customerId);
              return (
                <div key={c.id} className="row-hover" onClick={() => navigate('case', c.id)}
                  style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 18px', cursor: 'pointer', borderBottom: i === dueSoon.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
                  <div style={{ width: 36, height: 36, flex: '0 0 auto', borderRadius: 11, background: du <= 1 ? '#fdecec' : '#fdf0db', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
                    <div style={{ fontSize: 14, fontWeight: 700, color: du <= 1 ? '#dc2626' : '#d97706', lineHeight: 1 }}>{du <= 0 ? '!' : du}</div>
                    {du > 0 && <div style={{ fontSize: 12, color: '#a8aeb8' }}>{t('dash2.daysLater')}</div>}
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.title}</div>
                    <div style={{ fontSize: 12, color: '#7b828d', marginTop: 2 }}>{cust.shortName} · {t('dash2.proposalDeadline')} <span style={{ color: '#4a5af0' }}>{fmtDate(due, true)}</span></div>
                  </div>
                  <StatusBadge status={c.status} size="sm" dot={false} />
                </div>
              );
            })}
            {dueSoon.length === 0 && <div style={{ padding: 24, textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('dash2.empty.noDueSoon')}</div>}
          </Card>

          {/* 案件ソース取込み状況（右列の末尾カード：flex:1 で左列と底辺を揃える） */}
          <Card title={t('dash.source')} style={{ flex: 1 }} action={canApo
            ? <button onClick={() => navigate('integrations')} style={linkBtn}>{t('btn.connect')} <Icon name="arrowRight" size={13} stroke={2} /></button> : null}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
              <div style={{ width: 40, height: 40, borderRadius: 12, background: '#eef0fe', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Icon name="refresh" size={20} stroke={2} style={{ color: '#4a5af0' }} />
              </div>
              <div>
                <div style={{ fontSize: 13.5, fontWeight: 700, color: '#1c1f26' }}>{t('dash2.imported', { count: cases.filter(c => c.source === 'scrape').length })}</div>
                <div style={{ fontSize: 12, color: '#7b828d', marginTop: 2 }}>{t('dash2.importedFrom', { source: 'alion.partner.readycrew.cloud' })}</div>
              </div>
            </div>
            <div style={{ fontSize: 12, color: '#9aa1ab', lineHeight: 1.6, padding: '10px 12px', background: '#f6f7fa', borderRadius: 10 }}>
              {t('dash2.readyCrewIntro')}{canApo ? t('dash2.readyCrewImportAuthorized') : t('dash2.readyCrewImportRestricted')}
            </div>
          </Card>
        </div>
      </div>

      {/* 月別 商談回数・受注/失注（分析）＝全社の受注/失注を含むため viewAnalytics（既定＝管理者のみ）でゲート。分析画面の同テーブルと権限を揃える（2026-07-28） */}
      {can('viewAnalytics') && (
        <Card title={t('dash2.monthlyMeetingStats')} style={{ marginTop: 16 }}
          action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{t('dash2.monthlyStatsDescription')}</span>}>
          <MonthlyStatsTable cases={statBase} months={6} />
        </Card>
      )}
    </Page>
  );
}

Object.assign(window, { Dashboard, SoftStat, Donut, ActivityBars });
