// Main Revenue Dashboard App
const { GROUPS, MONTHS, PERIODS, TOP_ACCOUNTS, ALERTS } = window.REVENUE_DATA;

// Auth-aware fetch helper (mirrors apiFetch in api.js)
function authFetch(url, opts = {}) {
  const token = localStorage.getItem('auth_token');
  const headers = { ...(opts.headers || {}) };
  if (token) headers['Authorization'] = `Bearer ${token}`;
  return fetch(url, { ...opts, headers });
}

// ============================================================
// Mobile breakpoint hook
// ============================================================
function useIsMobile(bp = 768) {
  const [m, setM] = React.useState(() => window.innerWidth <= bp);
  React.useEffect(() => {
    const mq = window.matchMedia(`(max-width: ${bp}px)`);
    const h = e => setM(e.matches);
    mq.addEventListener('change', h);
    return () => mq.removeEventListener('change', h);
  }, [bp]);
  return m;
}

// ============================================================
// Sidebar (Overview + Settings w/ submenu)
// ============================================================
// sidebarMode: 'expanded' | 'collapsed' | 'auto'
const Sidebar = ({ active, onNavigate, sidebarMode, setSidebarMode, authUser, onLogout, onChangePw, mobileOpen, onMobileClose }) => {
  const isViewer = authUser?.role === 'viewer';
  const isSettingItem = ['upload', 'group-setting', 'product-setting', 'target-setting', 'user-setting'].includes(active);
  const isPipelineItem = ['pipeline', 'lost-analysis'].includes(active);
  const [settingsOpen, setSettingsOpen] = useState(isSettingItem);
  const [pipelineOpen, setPipelineOpen] = useState(isPipelineItem);
  const [userMenuOpen, setUserMenuOpen] = useState(false);
  const [hovered, setHovered] = useState(false);
  useEffect(() => { if (isSettingItem) setSettingsOpen(true); }, [isSettingItem]);
  useEffect(() => { if (isPipelineItem) setPipelineOpen(true); }, [isPipelineItem]);

  // In 'auto' mode the sidebar expands on hover, collapses when not
  const isCollapsed = sidebarMode === 'collapsed' || (sidebarMode === 'auto' && !hovered);

  const submenu = [
    { id: 'upload', label: 'Upload file', icon: 'download' },
    { id: 'group-setting', label: 'Group setting', icon: 'grid' },
    { id: 'product-setting', label: 'Product setting', icon: 'list' },
    { id: 'target-setting', label: 'Target setting', icon: 'chart' },
    ...(authUser?.role === 'admin' ? [{ id: 'user-setting', label: 'User setting', icon: 'cog' }] : []),
  ];

  // Cycle mode on toggle button: expanded → collapsed → auto → expanded
  const cycleMode = () => {
    setSidebarMode(m => m === 'expanded' ? 'collapsed' : m === 'collapsed' ? 'auto' : 'expanded');
  };

  const modeIcon = sidebarMode === 'expanded' ? '«' : sidebarMode === 'collapsed' ? '»' : '⇔';
  const modeTitle = sidebarMode === 'expanded' ? 'Collapse sidebar' : sidebarMode === 'collapsed' ? 'Expand sidebar' : 'Auto-hide: on';

  const TopItem = ({ id, icon, label, expandable, expanded, onClick }) => (
    <div
      onClick={onClick}
      title={isCollapsed ? label : undefined}
      style={{
        display: 'flex', alignItems: 'center',
        justifyContent: isCollapsed ? 'center' : 'flex-start',
        gap: 10,
        padding: isCollapsed ? '11px 0' : '9px 16px',
        background: active === id ? '#2a2d35' : 'transparent',
        color: active === id ? '#fff' : '#cfd1d8',
        fontSize: 13,
        cursor: 'pointer',
        borderLeft: `2px solid ${active === id ? '#d97b2e' : 'transparent'}`,
        marginLeft: isCollapsed ? 0 : -8,
        paddingLeft: isCollapsed ? 0 : 22,
        transition: 'background 120ms',
        overflow: 'hidden',
        whiteSpace: 'nowrap',
      }}
      onMouseEnter={e => active !== id && (e.currentTarget.style.background = '#22252d')}
      onMouseLeave={e => active !== id && (e.currentTarget.style.background = 'transparent')}>
      <Icon name={icon} size={isCollapsed ? 20 : 14} style={{ flexShrink: 0 }} />
      {!isCollapsed && <span style={{ flex: 1 }}>{label}</span>}
      {!isCollapsed && expandable && (
        <span style={{ transform: expanded ? 'rotate(90deg)' : 'none', transition: 'transform 160ms', display: 'inline-flex', color: '#6b6f78' }}>
          <Icon name="chevron" size={10} />
        </span>
      )}
    </div>
  );

  const isMobile = useIsMobile();

  // On mobile: hide if not open
  if (isMobile && !mobileOpen) return null;

  return (
    <>
    {/* Mobile backdrop */}
    {isMobile && <div className="mob-sidebar-backdrop open" onClick={onMobileClose} />}
    <aside
      onMouseEnter={() => !isMobile && setHovered(true)}
      onMouseLeave={() => !isMobile && setHovered(false)}
      style={{
        width: isMobile ? 260 : (isCollapsed ? 52 : 220),
        background: '#1a1d24',
        color: '#cfd1d8',
        display: 'flex',
        flexDirection: 'column',
        borderRight: '1px solid #0d0f14',
        flexShrink: 0,
        transition: 'width 200ms ease',
        overflow: 'hidden',
        position: isMobile ? 'fixed' : 'relative',
        ...(isMobile ? {
          top: 0, left: 0, bottom: 0,
          zIndex: 200, boxShadow: '4px 0 24px rgba(0,0,0,0.4)',
        } : {}),
      }}>

      {/* Logo / branding */}
      <div style={{ padding: isCollapsed ? '14px 0' : '16px 18px 14px', borderBottom: '1px solid #2a2d35', minHeight: 80, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
        {!isCollapsed && (
          <>
            {/* Line 1: logo image */}
            <img
              src="https://business.true.th/hubfs/raw_assets/public/TrueB2B2024/images/header-footer/logo-true-business.png"
              alt="True Business"
              style={{ height: 28, width: 'auto', maxWidth: 140, objectFit: 'contain', objectPosition: 'left center', filter: 'brightness(0) invert(1)', marginBottom: 6 }}
            />
            {/* Line 2: bold header */}
            <div style={{ fontSize: 13, fontWeight: 700, color: '#fff', letterSpacing: '-0.01em', whiteSpace: 'nowrap' }}>B2B Product</div>
            {/* Line 3 */}
            <div style={{ fontSize: 10.5, color: '#9b9fa8', marginTop: 2, whiteSpace: 'nowrap', letterSpacing: '0.01em' }}>Revenue Dashboard</div>
          </>
        )}
      </div>

      {/* Nav items */}
      <div style={{ padding: '14px 8px', flex: 1, overflow: 'hidden' }}>
        {!isCollapsed && (
          <div style={{ fontSize: 10, color: '#5a5d65', textTransform: 'uppercase', letterSpacing: '0.08em', padding: '6px 14px 10px', whiteSpace: 'nowrap' }}>Workspace</div>
        )}
        {isCollapsed && <div style={{ height: 22 }} />}

        <TopItem id="overview" icon="grid" label="Revenue" onClick={() => onNavigate('overview')} />
        <TopItem
          id="pipeline"
          icon="pipeline"
          label="Pipeline"
          expandable
          expanded={pipelineOpen}
          onClick={() => { if (!isCollapsed) setPipelineOpen(s => !s); else onNavigate('pipeline'); }}
        />
        {!isCollapsed && pipelineOpen && (
          <div style={{ paddingLeft: 0, marginTop: 2, marginBottom: 4 }}>
            {[{ id: 'pipeline', label: 'Dashboard' }, { id: 'lost-analysis', label: 'Lost Analysis' }].map(s => (
              <div
                key={s.id}
                onClick={() => onNavigate(s.id)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '7px 16px 7px 44px',
                  marginLeft: -8,
                  fontSize: 12.5,
                  cursor: 'pointer',
                  color: active === s.id ? '#fff' : '#9a9da6',
                  background: active === s.id ? '#22252d' : 'transparent',
                  borderLeft: `2px solid ${active === s.id ? '#d97b2e' : 'transparent'}`,
                  position: 'relative',
                  whiteSpace: 'nowrap',
                }}
                onMouseEnter={e => active !== s.id && (e.currentTarget.style.color = '#cfd1d8')}
                onMouseLeave={e => active !== s.id && (e.currentTarget.style.color = '#9a9da6')}>
                <span style={{ position: 'absolute', left: 30, top: 0, bottom: 0, width: 1, background: '#2a2d35' }} />
                <span style={{ width: 5, height: 5, borderRadius: '50%', background: active === s.id ? '#d97b2e' : '#3a3d45', flexShrink: 0 }} />
                {s.label}
              </div>
            ))}
          </div>
        )}

        {!isViewer && (
          <>
            <TopItem
              id="settings"
              icon="cog"
              label="Settings"
              expandable
              expanded={settingsOpen}
              onClick={() => { if (!isCollapsed) setSettingsOpen(s => !s); else onNavigate('upload'); }}
            />
            {!isCollapsed && settingsOpen && (
              <div style={{ paddingLeft: 0, marginTop: 2, marginBottom: 4 }}>
                {submenu.map(s => (
                  <div
                    key={s.id}
                    onClick={() => onNavigate(s.id)}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 10,
                      padding: '7px 16px 7px 44px',
                      marginLeft: -8,
                      fontSize: 12.5,
                      cursor: 'pointer',
                      color: active === s.id ? '#fff' : '#9a9da6',
                      background: active === s.id ? '#22252d' : 'transparent',
                      borderLeft: `2px solid ${active === s.id ? '#d97b2e' : 'transparent'}`,
                      position: 'relative',
                      whiteSpace: 'nowrap',
                    }}
                    onMouseEnter={e => active !== s.id && (e.currentTarget.style.color = '#cfd1d8')}
                    onMouseLeave={e => active !== s.id && (e.currentTarget.style.color = '#9a9da6')}>
                    <span style={{ position: 'absolute', left: 30, top: 0, bottom: 0, width: 1, background: '#2a2d35' }} />
                    <span style={{ width: 5, height: 5, borderRadius: '50%', background: active === s.id ? '#d97b2e' : '#3a3d45', flexShrink: 0 }} />
                    {s.label}
                  </div>
                ))}
              </div>
            )}
          </>
        )}
      </div>

      {/* Footer: user + toggle */}
      <div style={{ borderTop: '1px solid #2a2d35' }}>
        {/* Toggle button */}
        <div
          onClick={cycleMode}
          title={modeTitle}
          style={{
            display: 'flex', alignItems: 'center', justifyContent: isCollapsed ? 'center' : 'flex-end',
            padding: '6px 12px',
            cursor: 'pointer',
            color: '#5a5d65',
            fontSize: 13,
            letterSpacing: '-0.02em',
            borderBottom: '1px solid #22252d',
          }}
          onMouseEnter={e => e.currentTarget.style.color = '#cfd1d8'}
          onMouseLeave={e => e.currentTarget.style.color = '#5a5d65'}>
          {!isCollapsed && (
            <span style={{ fontSize: 10, color: '#5a5d65', marginRight: 8, textTransform: 'uppercase', letterSpacing: '0.06em' }}>
              {sidebarMode === 'auto' ? 'Auto-hide' : ''}
            </span>
          )}
          <span style={{ fontFamily: 'monospace', fontSize: 14, lineHeight: 1 }}>{modeIcon}</span>
        </div>

        {/* User */}
        <div style={{ position: 'relative' }}>
          <div
            onClick={() => setUserMenuOpen(s => !s)}
            title={authUser?.display_name}
            style={{ padding: isCollapsed ? '10px 0' : 12, display: 'flex', alignItems: 'center', justifyContent: isCollapsed ? 'center' : 'flex-start', gap: 10, cursor: 'pointer' }}
            onMouseEnter={e => e.currentTarget.style.background = '#22252d'}
            onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
            <div style={{ width: 26, height: 26, borderRadius: '50%', background: '#2d3a8c', display: 'grid', placeItems: 'center', fontSize: 11, fontWeight: 700, color: '#fff', flexShrink: 0 }}>
              {(authUser?.display_name || 'U')[0].toUpperCase()}
            </div>
            {!isCollapsed && (
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 11.5, color: '#fff', fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{authUser?.display_name || 'User'}</div>
                <div style={{ fontSize: 10, color: '#6b6f78', whiteSpace: 'nowrap', textTransform: 'capitalize' }}>{authUser?.role || 'viewer'}</div>
              </div>
            )}
            {!isCollapsed && <span style={{ fontSize: 10, color: '#5a5d65' }}>▲</span>}
          </div>

          {/* User dropdown menu */}
          {userMenuOpen && (
            <div style={{ position: 'absolute', bottom: '100%', left: isCollapsed ? 52 : 0, right: 0, background: '#22252d', border: '1px solid #2a2d35', borderRadius: 4, overflow: 'hidden', zIndex: 200, minWidth: 180, boxShadow: '0 4px 16px rgba(0,0,0,0.4)' }}>
              <div style={{ padding: '10px 14px', borderBottom: '1px solid #2a2d35' }}>
                <div style={{ fontSize: 12, color: '#fff', fontWeight: 500 }}>{authUser?.display_name}</div>
                <div style={{ fontSize: 10.5, color: '#6b6f78', marginTop: 1 }}>{authUser?.username} · {authUser?.role}</div>
              </div>
              <div onClick={() => { setUserMenuOpen(false); onChangePw(); }}
                style={{ padding: '9px 14px', fontSize: 12.5, color: '#cfd1d8', cursor: 'pointer' }}
                onMouseEnter={e => e.currentTarget.style.background = '#2a2d35'}
                onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                🔑 เปลี่ยน Password
              </div>
              {authUser?.role === 'admin' && (
                <div onClick={() => { setUserMenuOpen(false); onNavigate('user-setting'); }}
                  style={{ padding: '9px 14px', fontSize: 12.5, color: '#cfd1d8', cursor: 'pointer' }}
                  onMouseEnter={e => e.currentTarget.style.background = '#2a2d35'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                  👥 User management
                </div>
              )}
              <div style={{ borderTop: '1px solid #2a2d35' }}
                onClick={() => { setUserMenuOpen(false); onLogout(); }}
                onMouseEnter={e => e.currentTarget.style.background = '#2a2d35'}
                onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                <div style={{ padding: '9px 14px', fontSize: 12.5, color: '#e07070', cursor: 'pointer' }}>↩ ออกจากระบบ</div>
              </div>
            </div>
          )}
        </div>
      </div>
    </aside>
    </>
  );
};

// ============================================================
// Top bar (breadcrumb + filters)
// ============================================================
const PIPELINE_STAGE_FILTER_OPTIONS = ['Prospecting','Qualification','Proposal','POC / Demo','Negotiation','Closed Won'];

const TopBar = ({ crumbs, onCrumbClick, period, setPeriod, compare, setCompare, onSearchSelect, onExport, isPipeline, pipelineStage, setPipelineStage, onHamburger }) => {
  const [search, setSearch] = useState('');
  const [searchOpen, setSearchOpen] = useState(false);
  const [searchResults, setSearchResults] = useState([]);
  const [searchLoading, setSearchLoading] = useState(false);
  const searchRef = useRef(null);
  const searchTimerRef = useRef(null);

  useEffect(() => {
    const onDoc = (e) => {
      if (searchRef.current && !searchRef.current.contains(e.target)) setSearchOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        setSearchOpen(true);
        setTimeout(() => searchRef.current?.querySelector('input')?.focus(), 0);
      }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  // Debounced real DB search
  useEffect(() => {
    clearTimeout(searchTimerRef.current);
    const q = search.trim();
    if (!q) { setSearchResults([]); setSearchLoading(false); return; }
    setSearchLoading(true);
    searchTimerRef.current = setTimeout(async () => {
      const results = await window.API.search(q, 10);
      setSearchResults(results);
      setSearchLoading(false);
    }, 280);
    return () => clearTimeout(searchTimerRef.current);
  }, [search]);

  const isMobile = useIsMobile();

  return (
  <header style={{
    height: 56,
    background: 'var(--panel)',
    borderBottom: '1px solid var(--line)',
    display: 'flex',
    alignItems: 'center',
    padding: isMobile ? '0 12px' : '0 24px',
    gap: isMobile ? 8 : 16,
    flexShrink: 0,
    position: 'relative',
    zIndex: 10,
  }}>
    {/* Hamburger on mobile */}
    {isMobile && (
      <button onClick={onHamburger} style={{ display: 'flex', flexDirection: 'column', gap: 5, padding: 6, background: 'none', border: 'none', cursor: 'pointer', flexShrink: 0 }}>
        <span style={{ width: 20, height: 2, background: 'var(--ink)', borderRadius: 1 }} />
        <span style={{ width: 20, height: 2, background: 'var(--ink)', borderRadius: 1 }} />
        <span style={{ width: 20, height: 2, background: 'var(--ink)', borderRadius: 1 }} />
      </button>
    )}
    {/* breadcrumb */}
    <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: isMobile ? 12 : 13, flex: 1, minWidth: 0, overflow: 'hidden' }}>
      {crumbs.map((c, i) => (
        <React.Fragment key={i}>
          {i > 0 && <span style={{ color: 'var(--ink-3)', flexShrink: 0 }}><Icon name="chevron" size={10} /></span>}
          <span
            onClick={() => i < crumbs.length - 1 && onCrumbClick && onCrumbClick(i)}
            style={{
              color: i === crumbs.length - 1 ? 'var(--ink)' : 'var(--ink-3)',
              fontWeight: i === crumbs.length - 1 ? 500 : 400,
              cursor: i < crumbs.length - 1 ? 'pointer' : 'default',
            }}>{c}</span>
        </React.Fragment>
      ))}
    </div>

    {/* search — hidden on mobile and pipeline page */}
    {!isMobile && isPipeline ? (
      <div style={{ display:'flex', alignItems:'center', gap:6 }}>
        <span style={{ fontSize:12, color:'var(--ink-3)', whiteSpace:'nowrap' }}>Sale Stage</span>
        <select
          value={pipelineStage || ''}
          onChange={e => setPipelineStage && setPipelineStage(e.target.value || null)}
          style={{ padding:'5px 28px 5px 10px', fontSize:12.5, border:`1px solid ${pipelineStage ? 'var(--accent)' : 'var(--line)'}`, borderRadius:6, background: pipelineStage ? 'var(--accent)' : 'var(--panel)', color: pipelineStage ? '#fff' : 'var(--ink)', cursor:'pointer', outline:'none', fontFamily:'inherit', fontWeight: pipelineStage ? 600 : 400, appearance:'none', WebkitAppearance:'none', backgroundImage:`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='${pipelineStage ? '%23ffffff' : '%23888888'}'/%3E%3C/svg%3E")`, backgroundRepeat:'no-repeat', backgroundPosition:'right 9px center' }}>
          <option value="">All stages</option>
          {PIPELINE_STAGE_FILTER_OPTIONS.map(s => (
            <option key={s} value={s}>{s}</option>
          ))}
        </select>
      </div>
    ) : (!isMobile && (
    <div ref={searchRef} style={{ position: 'relative' }}>
      <div onClick={() => setSearchOpen(true)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: searchOpen ? 'var(--panel)' : '#f1eee5', border: `1px solid ${searchOpen ? 'var(--ink)' : 'transparent'}`, borderRadius: 3, width: 260, color: 'var(--ink-3)' }}>
        <Icon name="search" size={12} />
        <input
          value={search}
          onChange={(e) => { setSearch(e.target.value); setSearchOpen(true); }}
          onFocus={() => setSearchOpen(true)}
          placeholder="Search products, accounts, SKU…"
          style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', fontSize: 12, color: 'var(--ink)', width: '100%' }} />
        <kbd style={{ fontSize: 10, padding: '1px 5px', background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 2, fontFamily: 'IBM Plex Mono', color: 'var(--ink-3)' }}>⌘K</kbd>
      </div>
      {searchOpen && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0,
          background: 'var(--panel)', border: '1px solid var(--line)',
          boxShadow: '0 12px 32px rgba(0,0,0,0.10)', borderRadius: 4,
          maxHeight: 380, overflowY: 'auto', zIndex: 100,
        }}>
          {!search.trim() && (
            <div style={{ padding: '12px 14px', fontSize: 11, color: 'var(--ink-3)' }}>
              พิมพ์เพื่อค้นหา Group, Product หรือ Account…
            </div>
          )}
          {search.trim() && searchLoading && (
            <div style={{ padding: 20, textAlign: 'center', fontSize: 12, color: 'var(--ink-3)' }}>กำลังค้นหา…</div>
          )}
          {search.trim() && !searchLoading && searchResults.length === 0 && (
            <div style={{ padding: 24, textAlign: 'center', fontSize: 12, color: 'var(--ink-3)' }}>ไม่พบผลลัพธ์สำหรับ "{search}"</div>
          )}
          {!searchLoading && searchResults.map((r, i) => (
            <div key={i} onClick={() => { onSearchSelect(r); setSearchOpen(false); setSearch(''); }}
              style={{ padding: '10px 14px', cursor: 'pointer', borderTop: i > 0 ? '1px solid var(--line-2)' : 'none', display: 'flex', alignItems: 'center', gap: 10 }}
              onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <span style={{ fontSize: 9, padding: '2px 6px', borderRadius: 2, background: r.kind === 'Group' ? '#e8eaf3' : r.kind === 'Product' ? '#fbf6ec' : '#e3f1ea', color: r.kind === 'Group' ? 'var(--accent)' : r.kind === 'Product' ? 'var(--accent-2)' : 'var(--positive)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 500 }}>{r.kind}</span>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 12.5, fontWeight: 500 }}>{r.label}</div>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{r.sub}</div>
              </div>
              <Icon name="chevron" size={10} />
            </div>
          ))}
        </div>
      )}
    </div>
    ))}

    {/* period + compare — hidden on pipeline and compare hidden on mobile */}
    {!isPipeline && <Segmented options={isMobile ? ['MTD','YTD'] : PERIODS} value={period} onChange={(v) => { setPeriod(v); showToast(`Period: ${v}`); }} />}
    {!isPipeline && !isMobile && (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--ink-2)' }}>
      <label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
        <input type="checkbox" checked={compare} onChange={(e) => setCompare(e.target.checked)} style={{ accentColor: 'var(--accent)' }} />
        vs Plan
      </label>
    </div>
    )}

    {/* export */}
    {!isMobile && <Dropdown
      trigger={
        <button style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500 }}>
          <Icon name="download" size={12} />
          Export
          <Icon name="chevron" size={9} />
        </button>
      }
      items={[
        { label: 'Download as CSV',  icon: 'file',     hint: '.csv',  onClick: () => onExport('csv')  },
        '---',
        { label: 'Download as PDF',  icon: 'download', hint: '.pdf',  onClick: () => onExport('pdf')  },
      ]}
      align="right"
    />}
  </header>
  );
};

// ============================================================
// KPI item master list (shared by KPIStrip + Add KPI modal)
const KPI_ITEMS_DEFAULT = [
  { id: 'totalRev',  label: 'Total revenue',      desc: 'Sum across all groups · period filter',   visible: true  },
  { id: 'pacing',    label: 'Pacing vs plan',      desc: 'Actual ÷ target · progress to date',     visible: true  },
  { id: 'momGrowth', label: 'MoM growth',          desc: 'Weighted average · current vs prev',     visible: true  },
  { id: 'topDriver', label: 'Top growth driver',   desc: 'Group with highest MoM growth',          visible: true  },
  { id: 'churn',     label: 'Lost rate',           desc: 'Weighted revenue loss across active products',  visible: false },
  { id: 'arpu',      label: 'Blended ARPU',        desc: 'Revenue per customer · group weighted',  visible: false },
  { id: 'newLogos',  label: 'New logos · MTD',     desc: 'New accounts onboarded this month',      visible: false },
];

// ============================================================
// KPI Strip (up to 4 cards, ordered by kpiItems array)
// ============================================================
const KPIStrip = ({ groups, period, kpiItems = KPI_ITEMS_DEFAULT }) => {
  const total = groups.reduce((s, g) => s + (g.revenue || 0), 0);
  const groupsWithTarget = groups.filter(g => g.target !== null && g.target > 0);
  const totalTarget = groupsWithTarget.reduce((s, g) => s + g.target, 0);
  const hasPacing = totalTarget > 0 && total > 0;
  const pacing = hasPacing ? (total / totalTarget) * 100 : null;
  const wAvgGrowth = total > 0 ? groups.reduce((s, g) => s + (g.growth || 0) * (g.revenue || 0), 0) / total : 0;
  const wAvgYoy = total > 0 ? groups.reduce((s, g) => s + (g.yoy || 0) * (g.revenue || 0), 0) / total : 0;
  const topGroup = [...groups].sort((a, b) => b.growth - a.growth)[0];
  const maxTrendLen = Math.max(...groups.map(g => (g.trend || []).length), 1);
  const totalTrend = Array.from({ length: maxTrendLen }, (_, i) =>
    groups.reduce((s, g) => s + ((g.trend || [])[i] || 0), 0));

  // All possible KPI card definitions (keyed by id)
  const allCards = {
    totalRev: (
      <KPICard key="totalRev"
        label={`Total revenue · ${period}`}
        value={fmtTHB(total)}
        delta={isFinite(wAvgYoy) ? wAvgYoy : undefined}
        deltaLabel="vs same period last year"
        trend={totalTrend}
        accent="var(--accent)"
      />
    ),
    pacing: (
      <KPICard key="pacing"
        label="Pacing vs plan"
        value={hasPacing ? pacing.toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})+'%' : '—'}
        sublabel={hasPacing
          ? `฿${(totalTarget - numFmt(total),0)}M to target · ${PERIODS.indexOf(period) >= 2 ? '24 days left' : '8 days left'}`
          : 'ตั้งค่าเป้าหมายใน Target setting'}
        delta={hasPacing ? pacing - 100 : undefined}
        accent={hasPacing && pacing >= 100 ? 'var(--positive)' : 'var(--accent-2)'}
        trend={hasPacing ? totalTrend.map(v => v / (totalTarget / maxTrendLen) * 100) : []}
        trendColor="var(--accent-2)"
      />
    ),
    momGrowth: (
      <KPICard key="momGrowth"
        label="MoM Growth"
        value={isFinite(wAvgGrowth) ? fmtPct(wAvgGrowth) : '—'}
        sublabel={`${groups.filter(g => g.growth > 0).length} of ${groups.length} groups growing`}
        delta={isFinite(wAvgGrowth) ? wAvgGrowth - 3.8 : undefined}
        accent="var(--positive)"
        trend={totalTrend.slice(-6)}
        trendColor="var(--positive)"
      />
    ),
    topDriver: (
      <KPICard key="topDriver"
        label="Top growth driver"
        value={topGroup ? topGroup.name : '—'}
        sublabel={topGroup ? `${fmtTHB(topGroup.revenue)} · ${fmtPct(topGroup.growth)} MoM` : ''}
        delta={topGroup && isFinite(topGroup.yoy) ? topGroup.yoy : undefined}
        accent={topGroup ? (GROUP_COLORS[topGroup.id] || 'var(--accent)') : 'var(--accent)'}
        trend={topGroup ? (topGroup.trend || []) : []}
        trendColor={topGroup ? (GROUP_COLORS[topGroup.id] || 'var(--accent)') : 'var(--accent)'}
      />
    ),
    churn: (
      <KPICard key="churn"
        label="Lost rate"
        value="2.3%"
        sublabel="Weighted across active products"
        delta={-0.4}
        deltaLabel="vs last period"
        accent="var(--negative)"
        trend={[2.8, 2.9, 2.6, 2.5, 2.4, 2.3]}
        trendColor="var(--negative)"
      />
    ),
    arpu: (() => {
      const totalCustomers = groups.reduce((s, g) => s + (g.customers || 0), 0);
      const arpu = totalCustomers > 0 ? (total / totalCustomers) * 1000 : 0;
      return (
        <KPICard key="arpu"
          label="Blended ARPU"
          value={totalCustomers > 0 ? `฿${arpu.toFixed(0)}` : '—'}
          sublabel={`Revenue per customer · ${totalCustomers > 0 ? totalCustomers.toLocaleString() + ' customers' : 'no customer data'}`}
          delta={totalCustomers > 0 ? 4.2 : undefined}
          deltaLabel="vs last period"
          accent="var(--accent)"
          trend={[820, 835, 848, 862, 875, arpu || 880].slice(-6)}
        />
      );
    })(),
    pipeline: (
      <KPICard key="pipeline"
        label="Pipeline coverage"
        value="2.4×"
        sublabel="Late-stage pipeline ÷ remaining target"
        delta={12}
        deltaLabel="vs last quarter"
        accent="var(--accent-2)"
        trend={[1.8, 2.0, 2.1, 2.2, 2.3, 2.4]}
        trendColor="var(--accent-2)"
      />
    ),
    newLogos: (
      <KPICard key="newLogos"
        label="New logos · MTD"
        value="14"
        sublabel="New accounts onboarded this month"
        delta={16.7}
        deltaLabel="vs last month"
        accent="var(--positive)"
        trend={[8, 10, 9, 12, 11, 14]}
        trendColor="var(--positive)"
      />
    ),
  };

  const visible = kpiItems.filter(k => k.visible);
  const cols = Math.max(1, Math.min(visible.length, 4));
  const isMob = useIsMobile();

  return (
    <div className="mob-scroll">
      <div style={{ display: 'grid', gridTemplateColumns: isMob ? `repeat(${cols}, minmax(160px, 1fr))` : `repeat(${cols}, 1fr)`, gap: 1, background: 'var(--line)', border: '1px solid var(--line)', minWidth: isMob ? 640 : undefined }}>
        {visible.map(k => allCards[k.id])}
      </div>
    </div>
  );
};

// ============================================================
// Section header
// ============================================================
const SectionHead = ({ title, subtitle, children }) => (
  <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 12, gap: 16 }}>
    <div>
      <h2 className="serif" style={{ fontSize: 18, fontWeight: 500, letterSpacing: '-0.01em', marginBottom: 2 }}>{title}</h2>
      {subtitle && <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>{subtitle}</div>}
    </div>
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>{children}</div>
  </div>
);

// ============================================================
// Group Card
// ============================================================
const GroupCard = ({ group, total, onClick, rank }) => {
  const share = (group.revenue / total) * 100;
  const hasTarget = group.target !== null && group.target > 0;
  const pacing = hasTarget ? (group.revenue / group.target) * 100 : null;
  const color = GROUP_COLORS[group.id];

  return (
    <div onClick={onClick} style={{
      background: 'var(--panel)',
      border: '1px solid var(--line)',
      padding: '18px 20px',
      cursor: 'pointer',
      display: 'flex',
      flexDirection: 'column',
      gap: 14,
      transition: 'all 140ms',
      position: 'relative',
    }}
      onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink)'; e.currentTarget.style.transform = 'translateY(-1px)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.04)'; }}
      onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line)'; e.currentTarget.style.transform = 'none'; e.currentTarget.style.boxShadow = 'none'; }}>
      {/* header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
          <div style={{ width: 4, height: 36, background: color, marginTop: 2 }} />
          <div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
              <span style={{ fontSize: 11, color: 'var(--ink-3)', fontFamily: 'IBM Plex Mono' }}>0{rank}</span>
              <h3 style={{ fontSize: 16, fontWeight: 600, letterSpacing: '-0.01em' }}>{group.name}</h3>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>{group.desc}</div>
          </div>
        </div>
        <div style={{ color: 'var(--ink-3)' }}><Icon name="chevron" size={11} /></div>
      </div>

      {/* big number */}
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
        <div>
          <div className="num" style={{ fontSize: 28, fontWeight: 500, letterSpacing: '-0.02em', lineHeight: 1 }}>{fmtTHB(group.revenue)}</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }}>{numFmt(share)}% of total revenue</div>
        </div>
        <Sparkline data={group.trend} width={100} height={36} color={color} />
      </div>

      {/* prior full year */}
      {group.prior_full_year != null && (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 10px', background: 'var(--bg)', borderRadius: 4 }}>
          <span style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{new Date().getFullYear() - 1} Full Year (Jan–Dec)</span>
          <span className="num" style={{ fontSize: 12, fontWeight: 500, color: 'var(--ink-2)' }}>{fmtTHB(group.prior_full_year)}</span>
        </div>
      )}

      {/* metrics row */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12, paddingTop: 12, borderTop: '1px solid var(--line-2)' }}>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>MoM</div>
          <Delta value={group.growth} size="lg" />
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>YoY</div>
          <Delta value={group.yoy} size="lg" />
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>vs Plan</div>
          {hasTarget
            ? <span className="num" style={{ fontSize: 13, fontWeight: 500, color: pacing >= 100 ? 'var(--positive)' : 'var(--ink)' }}>{numFmt(pacing,0)}%</span>
            : <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>—</span>}
        </div>
      </div>

      {/* progress to target */}
      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--ink-3)', marginBottom: 4, fontFamily: 'IBM Plex Mono' }}>
          {hasTarget
            ? <><span>To target {fmtTHB(group.target)}</span><span>{numFmt(pacing,0)}%</span></>
            : <span style={{ fontStyle: 'italic' }}>No target set</span>}
        </div>
        <div style={{ height: 4, background: '#f1eee5', borderRadius: 2, overflow: 'hidden', position: 'relative' }}>
          {hasTarget && <div style={{ width: `${Math.min(pacing, 100)}%`, height: '100%', background: color }} />}
          {hasTarget && <div style={{ position: 'absolute', left: '100%', top: -2, height: 8, width: 1, background: 'var(--ink)' }} />}
        </div>
      </div>
    </div>
  );
};

// ============================================================
// Overview view
// ============================================================
const DEFAULT_SECTIONS = [
  { id: 'kpi',       label: 'KPI strip',              visible: true },
  { id: 'trend',     label: 'Revenue trend',          visible: true },
  { id: 'comp',      label: 'Composition bar',        visible: true },
  { id: 'multiyear', label: '3-Year Comparison',      visible: true },
  { id: 'groups',    label: 'Product group cards',    visible: true },
  { id: 'signals',   label: 'Signals / anomalies',    visible: false },
  { id: 'accounts',  label: 'Top accounts table',     visible: true },
  { id: 'pipeline',  label: 'Pipeline funnel',        visible: false },
];

// ============================================================
// ProductDots — renders colored dots per product with individual tooltips
// Up to MAX_DOTS shown; remainder collapsed into a "+N" indicator with combined tooltip
const MAX_DOTS = 6;

function ExtraDotsTooltip({ products, anchorRef }) {
  const [pos, setPos] = React.useState({ x: 0, y: 0 });
  React.useLayoutEffect(() => {
    if (anchorRef.current) {
      const r = anchorRef.current.getBoundingClientRect();
      setPos({ x: r.left + r.width / 2, y: r.top });
    }
  }, []);
  return ReactDOM.createPortal(
    <span style={{
      position: 'fixed',
      left: pos.x, top: pos.y - 6,
      transform: 'translate(-50%, -100%)',
      background: '#1a1a2e', color: '#fff',
      padding: '6px 10px', borderRadius: 6, fontSize: 11.5,
      whiteSpace: 'nowrap', zIndex: 99999, pointerEvents: 'none',
      boxShadow: '0 3px 10px rgba(0,0,0,0.28)',
      display: 'flex', flexDirection: 'column', gap: 4,
    }}>
      {products.map((p, i) => (
        <span key={i} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 8, height: 8, borderRadius: '50%', background: strToColor(p.name), flexShrink: 0 }} />
          {p.name}
        </span>
      ))}
      <span style={{
        position: 'absolute', top: '100%', left: '50%', transform: 'translateX(-50%)',
        width: 0, height: 0,
        borderLeft: '5px solid transparent', borderRight: '5px solid transparent',
        borderTop: '5px solid #1a1a2e',
      }} />
    </span>,
    document.body
  );
}

function ProductDots({ products = [] }) {
  const [showMore, setShowMore] = React.useState(false);
  const moreRef = React.useRef(null);
  if (!products.length) return <span style={{ color: '#b0bac7' }}>—</span>;

  const visible = products.slice(0, MAX_DOTS);
  const extra   = products.slice(MAX_DOTS);

  return (
    <span style={{ display: 'inline-flex', flexWrap: 'wrap', gap: 4, alignItems: 'center' }}>
      {visible.map((p, i) => (
        <ColorDot key={`${p.name}-${i}`} label={p.name} color={strToColor(p.name)} size={9} />
      ))}
      {extra.length > 0 && (
        <span ref={moreRef} style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}
          onMouseEnter={() => setShowMore(true)}
          onMouseLeave={() => setShowMore(false)}>
          <span style={{
            fontSize: 10, fontWeight: 600, color: '#5a6779',
            background: '#edf0f5', borderRadius: 10, padding: '1px 5px',
            cursor: 'default', lineHeight: '14px',
          }}>+{extra.length}</span>
          {showMore && <ExtraDotsTooltip products={extra} anchorRef={moreRef} />}
        </span>
      )}
    </span>
  );
}

// MonthDrillPanel — slide-in panel for month revenue detail
// ============================================================
const MONTH_TH = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
const MONTH_EN = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];

const MonthDrillPanel = ({ month, onClose, groupId = null, groupName = null }) => {
  const isMobile = useIsMobile();
  // month = "2026-04", groupId/groupName optional (scopes to a product group)
  const [data, setData]         = useState(null);
  const [loading, setLoading]   = useState(true);
  const [error, setError]       = useState(null);
  const [search, setSearch]     = useState('');
  const [segment, setSegment]   = useState('all');
  const [page, setPage]         = useState(1);
  const [searchInput, setSearchInput] = useState('');
  const [sortBy, setSortBy]     = useState('revenue');
  const [sortDir, setSortDir]   = useState('desc');
  const LIMIT = 20;

  const handleSortClick = (col) => {
    if (sortBy === col) {
      setSortDir(d => d === 'desc' ? 'asc' : 'desc');
    } else {
      setSortBy(col);
      setSortDir('desc');
    }
    setPage(1);
  };

  const fmtM = v => {
    if (v == null) return '—';
    if (v >= 1000) return `฿${(v / 1000).toFixed(2)}B`;
    return `฿${v.toFixed(2)}M`;
  };
  const fmtMonthLabel = (m) => {
    if (!m) return '';
    const [y, mo] = m.split('-');
    return `${MONTH_EN[parseInt(mo) - 1]} ${y}`;
  };

  const loadData = (seg, srch, pg, sb, sd) => {
    setLoading(true);
    setError(null);
    window.API.getMonthDetail(month, { segment: seg, search: srch, page: pg, limit: LIMIT, groupId: groupId || '', sortBy: sb, sortDir: sd })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  };

  useEffect(() => { loadData(segment, search, page, sortBy, sortDir); }, [month, segment, search, page, groupId, sortBy, sortDir]);

  const handleSearch = (e) => {
    if (e.key === 'Enter' || e.type === 'click') {
      setSearch(searchInput);
      setPage(1);
    }
  };
  const handleSegment = (seg) => { setSegment(seg); setPage(1); };

  const handleExportCSV = () => {
    if (!data) return;
    showToast('กำลังดาวน์โหลด…', { variant: 'info' });
    window.API.getMonthDetail(month, { segment, search, page: 1, limit: 9999, groupId: groupId || '', sortBy, sortDir })
      .then(d => {
        const customers = d.customers || [];
        const sum = d.summary || {};

        // Recalculate totals from the exported set (respects segment/search filter)
        const exportRevTotal = customers.reduce((a, c) => a + (parseFloat(c.total_invoice) || 0), 0);
        const exportRCTotal  = customers.reduce((a, c) => a + (parseFloat(c.rc) || 0), 0);
        const exportOCTotal  = customers.reduce((a, c) => a + (parseFloat(c.oc) || 0), 0);

        const fmt = v => (typeof v === 'number' ? v.toFixed(3) : String(v ?? ''));
        const pct  = v => exportRevTotal > 0 ? ((parseFloat(v) || 0) / exportRevTotal * 100).toFixed(1) + '%' : '0.0%';

        // Metadata rows at the top
        const meta = [
          ['Month', month],
          ['Product Group', groupName || groupId || 'All Groups'],
          ['Segment Filter', segment === 'all' ? 'All Segments' : segment],
          ['Search Filter', search || '-'],
          [''],
          ['Summary'],
          ['Total Revenue (M THB)', fmt(sum.total_invoice), 'RC (M THB)', fmt(sum.rc), 'OC (M THB)', fmt(sum.oc)],
          ['Customers in Export', customers.length, 'Total Customers (month)', sum.customer_count],
          ['MoM Change', sum.mom_pct != null ? sum.mom_pct + '%' : 'N/A'],
          [''],
          // Column headers
          ['#', 'Business ID', 'Customer', 'Segment', 'Products',
           'Revenue (M THB)', 'RC (M THB)', 'OC (M THB)', '% of Exported Total'],
        ];

        const rows = customers.map((c, i) => [
          i + 1,
          c.business_id || '',
          c.customer_name,
          c.segment,
          (c.products || []).map(p => p.name).join(', '),
          fmt(c.total_invoice),
          fmt(c.rc),
          fmt(c.oc),
          pct(c.total_invoice),
        ]);

        // Summary totals row at the bottom
        const totalsRow = [
          '', '', 'TOTAL', '', '',
          fmt(exportRevTotal), fmt(exportRCTotal), fmt(exportOCTotal), '100.0%',
        ];

        const allRows = [...meta, ...rows, [''], totalsRow];
        const csv = '﻿' + allRows
          .map(r => r.map(c => `"${String(c ?? '').replace(/"/g, '""')}"`).join(','))
          .join('\n');

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        const gSuffix = groupId ? `_${groupId}` : '';
        const segSuffix = segment !== 'all' ? `_${segment}` : '';
        a.href = url;
        a.download = `revenue_detail_${month}${gSuffix}${segSuffix}.csv`;
        document.body.appendChild(a); a.click();
        setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 300);
        showToast(`Exported ${customers.length} customers`, { variant: 'success' });
      })
      .catch(e => showToast(`Export failed: ${e.message}`, { variant: 'error' }));
  };

  const sum = data?.summary;
  const customers = data?.customers || [];
  const totalPages = data?.pages || 1;
  const totalCount = data?.total || 0;

  const SEG_COLORS = {
    'Enterprise': { bg: '#e8f0fe', color: '#1a56b0' },
    'SMB':        { bg: '#fff3e0', color: '#b35f00' },
    'Government': { bg: '#f3e8fd', color: '#7a1fa2' },
    'SME':        { bg: '#fff3e0', color: '#b35f00' },
  };
  const segStyle = (seg) => SEG_COLORS[seg] || { bg: '#f0f3f7', color: '#5a6779' };

  const pageNums = [];
  if (totalPages <= 7) {
    for (let i = 1; i <= totalPages; i++) pageNums.push(i);
  } else {
    pageNums.push(1);
    if (page > 3) pageNums.push('…');
    for (let i = Math.max(2, page - 1); i <= Math.min(totalPages - 1, page + 1); i++) pageNums.push(i);
    if (page < totalPages - 2) pageNums.push('…');
    pageNums.push(totalPages);
  }

  return (
    <div style={{ position:'fixed', inset:0, background:'rgba(15,25,40,0.45)', backdropFilter:'blur(2px)', display:'flex', alignItems:'flex-start', justifyContent:'flex-end', zIndex:9000 }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={{ width: isMobile ? '100vw' : 860, height:'100vh', background:'#fff', display:'flex', flexDirection:'column', boxShadow:'-4px 0 32px rgba(0,0,0,0.18)', animation:'slideInRight .22s ease-out' }}>
        <style>{`@keyframes slideInRight{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}`}</style>

        {/* ── Header ── */}
        <div style={{ padding:'18px 24px 14px', borderBottom:'1px solid #e8ecf1', display:'flex', alignItems:'flex-start', justifyContent:'space-between', flexShrink:0 }}>
          <div>
            <div style={{ fontSize:11, color:'#8896a7', display:'flex', alignItems:'center', gap:4, marginBottom:4 }}>
              <span>Revenue Dashboard</span>
              <span style={{ color:'#c5ced8' }}>›</span>
              {groupName ? (
                <>
                  <span>{groupName}</span>
                  <span style={{ color:'#c5ced8' }}>›</span>
                  <span>Revenue Trajectory</span>
                  <span style={{ color:'#c5ced8' }}>›</span>
                </>
              ) : (
                <>
                  <span>Monthly Trend</span>
                  <span style={{ color:'#c5ced8' }}>›</span>
                </>
              )}
              <span style={{ color:'#1a2332', fontWeight:600 }}>{fmtMonthLabel(month)}</span>
            </div>
            <div style={{ fontSize:17, fontWeight:700, color:'#1a2332', display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
              Revenue Detail
              {groupName && <span style={{ background:'#f0f4ff', color:'#3b5bdb', padding:'2px 10px', borderRadius:20, fontSize:12, fontWeight:600 }}>{groupName}</span>}
              <span style={{ background:'#e8f7f3', color:'#007a58', padding:'2px 10px', borderRadius:20, fontSize:12, fontWeight:600 }}>{fmtMonthLabel(month)}</span>
            </div>
            <div style={{ fontSize:12, color:'#8896a7', marginTop:2 }}>
              {groupName ? `ลูกค้าใน ${groupName} ที่มี revenue ในเดือนนี้` : 'แสดงรายชื่อลูกค้าที่นำมาคำนวณในเดือนนี้'}
            </div>
          </div>
          <button onClick={onClose} style={{ background:'none', border:'none', cursor:'pointer', color:'#8896a7', fontSize:20, padding:'2px 6px', borderRadius:6 }}>✕</button>
        </div>

        {/* ── KPI Cards ── */}
        <div style={{ display:'grid', gridTemplateColumns: isMobile ? 'repeat(2,1fr)' : 'repeat(4,1fr)', gap:10, padding: isMobile ? '10px 14px' : '14px 24px', borderBottom:'1px solid #e8ecf1', flexShrink:0, background:'#fafbfc' }}>
          {[
            { label:'Total Revenue', value: sum ? fmtM(sum.total_invoice) : '…', sub: sum?.mom_pct != null ? `${sum.mom_pct >= 0 ? '▲' : '▼'} ${Math.abs(sum.mom_pct).toFixed(1)}% vs prev month` : '—', subColor: sum?.mom_pct >= 0 ? '#00a878' : '#e85d45', bar: 100, barColor:'#00a878' },
            { label:'RC (Recurring)',  value: sum ? fmtM(sum.rc)  : '…', sub: sum ? `${sum.rc_pct}% of total` : '—',  subColor:'#1a56b0', bar: sum?.rc_pct || 0, barColor:'#1a56b0' },
            { label:'OC (One-time)',   value: sum ? fmtM(sum.oc)  : '…', sub: sum ? `${sum.oc_pct}% of total` : '—',  subColor:'#7a1fa2', bar: sum?.oc_pct || 0, barColor:'#7a1fa2' },
            { label:'Customers',       value: sum ? `${sum.customer_count.toLocaleString()} ราย` : '…', sub:'', subColor:'#5a6779', bar: 55, barColor:'#f09a30' },
          ].map((k, i) => (
            <div key={i} style={{ background:'#fff', border:'1px solid #e8ecf1', borderRadius:10, padding:'12px 14px' }}>
              <div style={{ fontSize:10, color:'#8896a7', textTransform:'uppercase', letterSpacing:'.5px', marginBottom:5 }}>{k.label}</div>
              <div style={{ fontSize:18, fontWeight:700, color:'#1a2332', lineHeight:1.2 }}>{k.value}</div>
              {k.sub && <div style={{ fontSize:11, color: k.subColor, marginTop:3 }}>{k.sub}</div>}
              <div style={{ height:3, borderRadius:2, background:'#e8ecf1', marginTop:7, overflow:'hidden' }}>
                <div style={{ width:`${Math.min(k.bar, 100)}%`, height:'100%', background:k.barColor, borderRadius:2 }} />
              </div>
            </div>
          ))}
        </div>

        {/* ── Toolbar ── */}
        <div style={{ padding: isMobile ? '8px 14px' : '10px 24px', display:'flex', alignItems:'center', justifyContent:'space-between', gap:8, flexShrink:0, borderBottom:'1px solid #e8ecf1', flexWrap:'wrap' }}>
          <div style={{ display:'flex', alignItems:'center', gap:8 }}>
            {/* Search */}
            <div style={{ display:'flex', alignItems:'center', gap:6, background:'#f4f6f9', border:'1px solid #e0e5ec', borderRadius:7, padding:'5px 10px', width:200 }}>
              <span style={{ fontSize:12, color:'#8896a7' }}>🔍</span>
              <input
                value={searchInput}
                onChange={e => setSearchInput(e.target.value)}
                onKeyDown={handleSearch}
                placeholder="ค้นหาลูกค้า…"
                style={{ border:'none', background:'none', outline:'none', fontSize:12, color:'#1a2332', width:'100%' }}
              />
            </div>
            {/* Segment filter */}
            <div style={{ display:'flex', gap:3 }}>
              {['all','Enterprise','SMB'].map(s => (
                <button key={s} onClick={() => handleSegment(s)} style={{ padding:'4px 10px', borderRadius:6, fontSize:11, border:`1px solid ${segment===s ? '#1a2332' : '#e0e5ec'}`, background: segment===s ? '#1a2332' : '#fff', color: segment===s ? '#fff' : '#5a6779', cursor:'pointer', fontWeight: segment===s ? 600 : 400 }}>
                  {s === 'all' ? 'ทั้งหมด' : s}
                </button>
              ))}
            </div>
          </div>
          <div style={{ display:'flex', gap:6 }}>
            <button onClick={handleExportCSV} style={{ display:'flex', alignItems:'center', gap:5, padding:'5px 12px', borderRadius:7, fontSize:12, fontWeight:500, border:'none', background:'#1a2332', color:'#fff', cursor:'pointer' }}>
              ↓ Export CSV
            </button>
          </div>
        </div>

        {/* ── Table ── */}
        <div style={{ flex:1, overflowY:'auto' }}>
          {loading ? (
            <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height:200, color:'#8896a7', fontSize:13 }}>กำลังโหลด…</div>
          ) : error ? (
            <div style={{ display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', height:200, color:'#e85d45', gap:8 }}>
              <div style={{ fontSize:13 }}>ไม่สามารถโหลดข้อมูลได้</div>
              <div style={{ fontSize:11, color:'#8896a7' }}>{error}</div>
              <button onClick={() => loadData(segment, search, page)} style={{ padding:'4px 12px', borderRadius:6, border:'1px solid #e0e5ec', background:'#fff', cursor:'pointer', fontSize:12 }}>ลองใหม่</button>
            </div>
          ) : customers.length === 0 ? (
            <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height:200, color:'#8896a7', fontSize:13 }}>ไม่พบข้อมูลลูกค้า</div>
          ) : (
            <table style={{ width:'100%', borderCollapse:'collapse', fontSize:12 }}>
              <thead>
                <tr style={{ borderBottom:'2px solid #e8ecf1' }}>
                  {/* Static headers */}
                  {['#','ลูกค้า','Segment','Products'].map((h, i) => (
                    <th key={h} style={{ padding:'9px 10px', fontSize:10, fontWeight:600, color:'#8896a7', textTransform:'uppercase', letterSpacing:'.4px', textAlign:'left', whiteSpace:'nowrap', position:'sticky', top:0, background:'#fff', zIndex:1 }}>{h}</th>
                  ))}
                  {/* Sortable numeric headers */}
                  {[['revenue','Revenue'],['rc','RC'],['oc','OC']].map(([col, label]) => {
                    const active = sortBy === col;
                    const arrow = active ? (sortDir === 'desc' ? ' ↓' : ' ↑') : ' ↕';
                    return (
                      <th key={col} onClick={() => handleSortClick(col)}
                        style={{ padding:'9px 10px', fontSize:10, fontWeight:600, color: active ? '#1a2332' : '#8896a7', textTransform:'uppercase', letterSpacing:'.4px', textAlign:'right', whiteSpace:'nowrap', position:'sticky', top:0, background:'#fff', zIndex:1, cursor:'pointer', userSelect:'none' }}>
                        {label}<span style={{ color: active ? '#00a878' : '#c5ced8', marginLeft:2 }}>{arrow}</span>
                      </th>
                    );
                  })}
                  <th style={{ padding:'9px 10px', fontSize:10, fontWeight:600, color:'#8896a7', textTransform:'uppercase', letterSpacing:'.4px', minWidth:110, position:'sticky', top:0, background:'#fff', zIndex:1 }}>% of Total</th>
                </tr>
              </thead>
              <tbody>
                {customers.map((c, idx) => {
                  const rank = (page - 1) * LIMIT + idx + 1;
                  const ss = segStyle(c.segment);
                  const maxPct = customers[0]?.pct_of_total || 1;
                  return (
                    <tr key={c.business_id} style={{ borderBottom:'1px solid #f0f3f7', transition:'background .1s' }}
                      onMouseEnter={e => e.currentTarget.style.background='#f8fafb'}
                      onMouseLeave={e => e.currentTarget.style.background=''}>
                      <td style={{ padding:'9px 10px', color: rank <= 3 ? '#e89c00' : '#b0bac7', fontWeight:600, fontSize:11 }}>{rank}</td>
                      <td style={{ padding:'9px 10px', maxWidth:200 }}>
                        <div style={{ fontWeight:600, color:'#1a2332', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }} title={c.customer_name}>{c.customer_name}</div>
                        <div style={{ fontSize:10, color:'#8896a7', marginTop:1 }}>{c.business_id}</div>
                      </td>
                      <td style={{ padding:'9px 10px' }}>
                        {c.segment ? <span style={{ display:'inline-block', padding:'1px 7px', borderRadius:4, fontSize:10, fontWeight:600, background:ss.bg, color:ss.color }}>{c.segment}</span> : <span style={{ color:'#b0bac7' }}>—</span>}
                      </td>
                      <td style={{ padding:'9px 6px' }}>
                        <ProductDots products={c.products} />
                      </td>
                      <td style={{ padding:'9px 10px', textAlign:'right', fontWeight:600, color:'#1a2332', fontVariantNumeric:'tabular-nums' }}>{fmtM(c.total_invoice)}</td>
                      <td style={{ padding:'9px 10px', textAlign:'right', fontWeight:600, color:'#1a56b0', fontVariantNumeric:'tabular-nums' }}>{c.rc > 0 ? fmtM(c.rc) : '—'}</td>
                      <td style={{ padding:'9px 10px', textAlign:'right', fontWeight:600, color:'#7a1fa2', fontVariantNumeric:'tabular-nums' }}>{c.oc > 0 ? fmtM(c.oc) : '—'}</td>
                      <td style={{ padding:'9px 10px', minWidth:100 }}>
                        <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                          <div style={{ flex:1, height:5, borderRadius:3, background:'#e8ecf1', overflow:'hidden' }}>
                            <div style={{ width:`${Math.min((c.pct_of_total / maxPct) * 100, 100)}%`, height:'100%', background:'#00a878', borderRadius:3 }} />
                          </div>
                          <span style={{ fontSize:11, color:'#5a6779', minWidth:36, textAlign:'right' }}>{c.pct_of_total.toFixed(1)}%</span>
                        </div>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>

        {/* ── Footer / Pagination ── */}
        <div style={{ padding:'10px 24px', borderTop:'1px solid #e8ecf1', display:'flex', alignItems:'center', justifyContent:'space-between', background:'#fafbfc', flexShrink:0, fontSize:11, color:'#8896a7' }}>
          <span>แสดง {Math.min((page-1)*LIMIT+1, totalCount)}–{Math.min(page*LIMIT, totalCount)} จาก {totalCount.toLocaleString()} ลูกค้า</span>
          <div style={{ display:'flex', alignItems:'center', gap:4 }}>
            <button onClick={() => setPage(p => Math.max(1, p-1))} disabled={page <= 1} style={{ width:26, height:26, borderRadius:5, border:'1px solid #e0e5ec', background:'#fff', cursor: page>1 ? 'pointer':'default', color:'#5a6779', display:'flex', alignItems:'center', justifyContent:'center', opacity: page<=1 ? 0.4 : 1 }}>‹</button>
            {pageNums.map((n, i) => n === '…'
              ? <span key={`e${i}`} style={{ padding:'0 4px', color:'#b0bac7' }}>…</span>
              : <button key={n} onClick={() => setPage(n)} style={{ width:26, height:26, borderRadius:5, border:'1px solid #e0e5ec', background: page===n ? '#1a2332' : '#fff', color: page===n ? '#fff' : '#5a6779', cursor:'pointer', fontSize:11, display:'flex', alignItems:'center', justifyContent:'center', fontWeight: page===n ? 600 : 400 }}>{n}</button>
            )}
            <button onClick={() => setPage(p => Math.min(totalPages, p+1))} disabled={page >= totalPages} style={{ width:26, height:26, borderRadius:5, border:'1px solid #e0e5ec', background:'#fff', cursor: page<totalPages ? 'pointer':'default', color:'#5a6779', display:'flex', alignItems:'center', justifyContent:'center', opacity: page>=totalPages ? 0.4 : 1 }}>›</button>
          </div>
        </div>
      </div>
    </div>
  );
};

const OverviewView = ({ groups, onDrill, period, compare, onViewAllAccounts, accounts = TOP_ACCOUNTS, alerts = ALERTS, sections = DEFAULT_SECTIONS, kpiItems }) => {
  const total = groups.reduce((s, g) => s + g.revenue, 0);
  const sorted = [...groups].sort((a, b) => b.revenue - a.revenue);
  const [metric, setMetric] = useState('Revenue');
  const isMobile = useIsMobile();
  const [drillMonth, setDrillMonth] = useState(null); // ISO "YYYY-MM" or null

  // Multi-year data for bar chart
  const [multiYear, setMultiYear] = useState(null);
  const [multiYearError, setMultiYearError] = useState(false);
  useEffect(() => {
    window.API?.getGroupsMultiYear()
      .then(d => { if (d) setMultiYear(d); })
      .catch(() => setMultiYearError(true));
  }, []);

  // Use trendMonths from API as source-of-truth — only months with real data, up to latest available
  const trendIso    = window.__trendMonths || [];
  const trendLabels = trendIso.map(iso => new Date(iso + '-02').toLocaleString('en', { month: 'short' }));
  const totalSeries = trendIso.map((_, i) => groups.reduce((s, g) => s + (g.trend?.[i] ?? 0), 0));
  const growthSeries = totalSeries.map((v, i, a) => i === 0 || !a[i - 1] ? 0 : ((v - a[i - 1]) / a[i - 1]) * 100);

  const seriesByMetric = {
    'Revenue': [{ label: 'Actual revenue', data: totalSeries }],
    'Growth %': [{ label: 'MoM growth %', data: growthSeries }],
  };
  const series = seriesByMetric[metric] || seriesByMetric['Revenue'];
  const lastVal = series[0].data.length > 0 ? series[0].data[series[0].data.length - 1] : 0;
  const annLabel = metric === 'Revenue' ? fmtTHB(lastVal) : (lastVal ?? 0).toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})+'%';
  const trendAnnotation = trendIso.length > 0 ? { index: trendIso.length - 1, value: lastVal, label: annLabel } : null;
  const trendSubtitle = trendLabels.length >= 2
    ? `Monthly · ${trendLabels[0]} – ${trendLabels[trendLabels.length - 1]} · ${metric} · คลิกเดือนเพื่อดูรายละเอียด`
    : `Monthly · Last 12 months · ${metric} · คลิกเดือนเพื่อดูรายละเอียด`;

  const [sortBy, setSortBy] = useState('revenue');
  const sortedForDisplay = useMemo(() => {
    const arr = [...groups];
    if (sortBy === 'revenue') arr.sort((a, b) => b.revenue - a.revenue);
    if (sortBy === 'growth') arr.sort((a, b) => b.growth - a.growth);
    if (sortBy === 'gap') arr.sort((a, b) => (a.revenue / a.target) - (b.revenue / b.target));
    return arr;
  }, [groups, sortBy]);

  // ── Section renderers ─────────────────────────────────────────────────────
  const vis = new Set(sections.filter(s => s.visible).map(s => s.id));

  const sectionBlocks = {
    kpi: () => (
      <KPIStrip key="kpi" groups={groups} period={period} kpiItems={kpiItems} />
    ),
    trend: () => (
      <div key="trend" style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
        <SectionHead title="Revenue trend" subtitle={trendSubtitle}>
          <Segmented dense options={['Revenue', 'Growth %']} value={metric} onChange={setMetric} />
        </SectionHead>
        {trendIso.length > 0 ? (
          <LineChart
            series={series}
            labels={trendLabels}
            height={240}
            annotation={trendAnnotation}
            onClickPoint={(idx) => {
              const iso = trendIso[idx];
              if (iso) setDrillMonth(iso);
            }}
          />
        ) : (
          <div style={{ height: 240, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-3)', fontSize: 12 }}>
            กำลังโหลดข้อมูล…
          </div>
        )}
        <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4, textAlign: 'right' }}>
          👆 คลิกที่จุดบนกราฟเพื่อดูรายละเอียดลูกค้าในเดือนนั้น
        </div>
      </div>
    ),
    comp: () => (
      <div key="comp" style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
        <SectionHead title="Composition" subtitle="Share of total revenue" />
        <StackedBar total={total} data={sorted.map(g => ({ name: g.name, value: g.revenue, color: GROUP_COLORS[g.id] }))} />
        <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
          {sorted.map(g => {
            const share = (g.revenue / total) * 100;
            return (
              <div key={g.id} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto auto', gap: 10, alignItems: 'center', fontSize: 12 }}>
                <div style={{ width: 8, height: 8, background: GROUP_COLORS[g.id], borderRadius: 1 }} />
                <span style={{ color: 'var(--ink-2)' }}>{g.name}</span>
                <span className="num" style={{ color: 'var(--ink-3)' }}>{numFmt(share)}%</span>
                <span className="num" style={{ fontWeight: 500, minWidth: 56, textAlign: 'right' }}>{fmtTHB(g.revenue)}</span>
              </div>
            );
          })}
        </div>
      </div>
    ),
    multiyear: () => (
      <div key="multiyear" style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '2fr 1fr', gap: 24 }}>
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
          <SectionHead
            title="Revenue by Group · 3-Year Comparison"
            subtitle={multiYear ? `${multiYear.years?.map((y, i) => i === multiYear.years.length - 1 ? `${y} YTD` : y).join(' · ')} · คลิก Group เพื่อ drill down` : multiYearError ? 'ไม่สามารถโหลดข้อมูลได้' : 'กำลังโหลด…'}
          />
          {multiYearError ? (
            <div style={{ height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-3)', fontSize: 12 }}>ไม่สามารถโหลดข้อมูล 3-Year Comparison ได้ในขณะนี้</div>
          ) : (
            <GroupBarChart data={multiYear?.groups || []} years={multiYear?.years || []} onDrill={onDrill} />
          )}
        </div>
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20, display: 'flex', flexDirection: 'column' }}>
          <SectionHead title="YoY Growth" subtitle={`Jan–${new Date().toLocaleString('en', { month: 'short' })} ${new Date().getFullYear()} vs same period last year`} />
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 0, marginTop: 4 }}>
            {(multiYear?.groups || []).map(g => {
              if (g.yoy == null) return null;
              const pos = g.yoy >= 0;
              const curY = multiYear.years[multiYear.years.length - 1];
              const rev = g.revenue?.[curY] || 0;
              return (
                <div key={g.id} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 8, alignItems: 'center', cursor: 'pointer', padding: '5px 0', borderBottom: '1px solid var(--line-2)' }} onClick={() => onDrill(g.id)}>
                  <div style={{ width: 8, height: 8, borderRadius: 1, background: g.color || '#ccc', flexShrink: 0 }} />
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 11.5, fontWeight: 500, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{g.name}</div>
                    <div style={{ marginTop: 3, height: 3, background: '#ece9df', borderRadius: 2 }}>
                      <div style={{ width: `${Math.min(Math.abs(g.yoy), 100)}%`, height: '100%', background: pos ? '#1f7a4d' : '#b8492f', borderRadius: 2 }} />
                    </div>
                  </div>
                  <div style={{ textAlign: 'right', flexShrink: 0 }}>
                    <div className="num" style={{ fontSize: 11, fontWeight: 700, color: pos ? '#1f7a4d' : '#b8492f' }}>{pos ? '+' : ''}{numFmt(g.yoy)}%</div>
                    <div className="num" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{rev >= 1000 ? `฿${(rev/1000).toFixed(1)}B` : `฿${Math.round(rev)}M`}</div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    ),
    groups: () => (
      <div key="groups">
        <SectionHead title="Revenue by Product Group" subtitle="คลิกการ์ดเพื่อ drill down ดูผลิตภัณฑ์ในกลุ่ม">
          <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>Sort by</span>
          <Segmented dense options={['Revenue', 'Growth', 'Gap to plan']} value={
            sortBy === 'revenue' ? 'Revenue' : sortBy === 'growth' ? 'Growth' : 'Gap to plan'
          } onChange={(v) => setSortBy(v === 'Revenue' ? 'revenue' : v === 'Growth' ? 'growth' : 'gap')} />
        </SectionHead>
        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 16 }}>
          {sortedForDisplay.map((g, i) => <GroupCard key={g.id} group={g} total={total} rank={i + 1} onClick={() => onDrill(g.id)} />)}
        </div>
      </div>
    ),
    signals: () => (
      <div key="signals" style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
        <SectionHead title="Signals" subtitle="Automated anomaly detection · 4 active" />
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          {alerts.map((a, i) => {
            const sevColor = a.sev === 'high' ? 'var(--negative)' : a.sev === 'pos' ? 'var(--positive)' : 'var(--accent-2)';
            return (
              <div key={i} onClick={() => onDrill(a.group)} style={{ display: 'flex', gap: 12, padding: '12px 0', cursor: 'pointer', borderTop: i > 0 ? '1px solid var(--line-2)' : 'none' }}>
                <div style={{ width: 3, background: sevColor, flexShrink: 0 }} />
                <div style={{ flex: 1 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{a.title}</div>
                    <span style={{ fontSize: 10, fontFamily: 'IBM Plex Mono', textTransform: 'uppercase', color: sevColor, letterSpacing: '0.06em' }}>{a.sev === 'high' ? 'Action' : a.sev === 'pos' ? 'Opportunity' : 'Monitor'}</span>
                  </div>
                  <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.4 }}>{a.detail}</div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    ),
    accounts: () => (
      <div key="accounts" style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
        <SectionHead title="Top accounts" subtitle="By total revenue contribution this period">
          <button onClick={onViewAllAccounts} style={{ fontSize: 11, color: 'var(--ink-2)', display: 'flex', alignItems: 'center', gap: 4 }}>View all <Icon name="chevron" size={10} /></button>
        </SectionHead>
        <div className="mob-scroll">
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12, minWidth: isMobile ? 500 : undefined }}>
          <thead>
            <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
              {['Account', 'Sector', 'Products', 'Revenue', 'YoY'].map(h => <th key={h} style={{ textAlign: h === 'Revenue' || h === 'YoY' ? 'right' : 'left', padding: '8px 6px', fontWeight: 500 }}>{h}</th>)}
            </tr>
          </thead>
          <tbody>
            {accounts.length === 0 ? (
              <tr><td colSpan={5} style={{ padding: '24px 6px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>ยังไม่มีข้อมูล — กรุณาอัปโหลดไฟล์ billing</td></tr>
            ) : accounts.map(a => (
              <tr key={a.id} style={{ borderBottom: '1px solid var(--line-2)' }}>
                <td style={{ padding: '10px 6px', fontWeight: 500, maxWidth: 160, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }} title={a.name}>{a.name}</td>
                <td style={{ padding: '10px 6px', color: 'var(--ink-3)', maxWidth: 100, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>{a.sector}</td>
                <td style={{ padding: '10px 6px' }}><div style={{ display: 'flex', gap: 4 }}>{(a.groups||[]).map(g => <GroupDot key={g} groupId={g} />)}</div></td>
                <td className="num" style={{ padding: '10px 6px', textAlign: 'right', fontWeight: 500 }}>฿{numFmt(a.revenue)}M</td>
                <td style={{ padding: '10px 6px', textAlign: 'right' }}><Delta value={a.growth} /></td>
              </tr>
            ))}
          </tbody>
        </table>
        </div>{/* mob-scroll */}
      </div>
    ),
    pipeline: () => (
      <div key="pipeline" style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
        <SectionHead title="Pipeline funnel" subtitle="Deal stages · current quarter" />
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 8 }}>
          {[{ stage: 'Qualified', value: 4200, pct: 100 }, { stage: 'Proposal', value: 2800, pct: 67 }, { stage: 'Negotiation', value: 1600, pct: 38 }, { stage: 'Closing', value: 860, pct: 20 }].map(s => (
            <div key={s.stage} style={{ display: 'grid', gridTemplateColumns: '100px 1fr 60px', gap: 10, alignItems: 'center' }}>
              <span style={{ fontSize: 11.5, color: 'var(--ink-2)' }}>{s.stage}</span>
              <div style={{ height: 8, background: '#ece9df', borderRadius: 2 }}>
                <div style={{ width: `${s.pct}%`, height: '100%', background: 'var(--accent)', borderRadius: 2 }} />
              </div>
              <span className="num" style={{ fontSize: 11, textAlign: 'right', color: 'var(--ink-2)' }}>฿{s.value}M</span>
            </div>
          ))}
        </div>
      </div>
    ),
  };

  // ── Render sections in user-defined order ───────────────────────────────
  // Pair trend+comp side-by-side when adjacent; pair signals+accounts side-by-side when adjacent
  const visibleOrder = sections.filter(s => s.visible).map(s => s.id);
  const rendered = [];
  let i = 0;
  while (i < visibleOrder.length) {
    const id = visibleOrder[i];
    const next = visibleOrder[i + 1];
    // Pair trend + comp (in either order)
    if ((id === 'trend' && next === 'comp') || (id === 'comp' && next === 'trend')) {
      const left = id, right = next;
      rendered.push(
        <div key="trend-comp-row" style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '2fr 1fr', gap: 24 }}>
          {sectionBlocks[left]()}
          {sectionBlocks[right]()}
        </div>
      );
      i += 2; continue;
    }
    // Pair signals + accounts (in either order)
    if ((id === 'signals' && next === 'accounts') || (id === 'accounts' && next === 'signals')) {
      const left = id, right = next;
      rendered.push(
        <div key="signals-accounts-row" style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1.4fr', gap: 24 }}>
          {sectionBlocks[left]()}
          {sectionBlocks[right]()}
        </div>
      );
      i += 2; continue;
    }
    rendered.push(sectionBlocks[id] ? sectionBlocks[id]() : null);
    i++;
  }

  return (
    <React.Fragment>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>{rendered}</div>
      {drillMonth && <MonthDrillPanel month={drillMonth} onClose={() => setDrillMonth(null)} />}
    </React.Fragment>
  );
};

// ============================================================
// Drill-down (single group)
// ============================================================
const GroupDetail = ({ group, totalGroups, onBack, onProductClick, selectedProduct, onSwitchGroup }) => {
  if (!group) return null;
  const color = GROUP_COLORS[group.id];
  const isMobile = useIsMobile();
  const totalRev = totalGroups.reduce((s, g) => s + g.revenue, 0);
  const share = (group.revenue / totalRev) * 100;
  const hasTarget = group.target !== null && group.target > 0;
  const pacing = hasTarget ? (group.revenue / group.target) * 100 : null;
  const [range, setRange] = useState('12M');
  const [filterText, setFilterText] = useState('');
  const [showFilter, setShowFilter] = useState(false);

  const [sort, setSort] = useState({ key: 'revenue', dir: 'desc' });
  const [groupAccounts, setGroupAccounts] = useState([]);

  useEffect(() => {
    authFetch(`/api/groups/${group.id}/accounts?limit=10`)
      .then(r => r.json())
      .then(data => setGroupAccounts(data.accounts || []))
      .catch(() => setGroupAccounts([]));
  }, [group.id]);

  const productsSorted = useMemo(() => {
    let arr = [...group.products];
    if (filterText.trim()) {
      const q = filterText.toLowerCase();
      arr = arr.filter(p => p.name.toLowerCase().includes(q) || p.sku.toLowerCase().includes(q) || p.deals.toLowerCase().includes(q));
    }
    arr.sort((a, b) => {
      let av, bv;
      if (sort.key === 'pacing') {
        av = a.target ? a.revenue / a.target : -Infinity;
        bv = b.target ? b.revenue / b.target : -Infinity;
      } else {
        av = a[sort.key] ?? -Infinity;
        bv = b[sort.key] ?? -Infinity;
      }
      return sort.dir === 'desc' ? bv - av : av - bv;
    });
    return arr;
  }, [group, sort, filterText]);

  const [drillMonth, setDrillMonth] = useState(null); // ISO "YYYY-MM" or null

  // Use trendMonths from API as source-of-truth for labels so they match actual data points.
  // trendMonths = ['2025-05','2025-06',...,'2026-03'] (11 items from DB)
  // group.trend  = [val0, val1, ..., val10]           (same length, same order)
  const allIso    = window.__trendMonths || [];
  const isoToLabel = iso => { const d = new Date(iso + '-01'); return d.toLocaleString('en', { month: 'short' }); };
  const allLabels  = allIso.map(isoToLabel);   // ['May','Jun',...,'Mar']

  const trendSlice  = range === '12M' ? group.trend            : range === '6M' ? group.trend.slice(-6)  : group.trend.slice(-3);
  const isoSlice    = range === '12M' ? allIso                 : range === '6M' ? allIso.slice(-6)       : allIso.slice(-3);
  const labelsSlice = range === '12M' ? allLabels              : range === '6M' ? allLabels.slice(-6)    : allLabels.slice(-3);
  const lastYearSlice = trendSlice.map(v => v * (1 - group.yoy / 100));

  // Map chart click index → ISO month directly from isoSlice (now always aligned with data).
  const handleTrajClick = (idx) => {
    const iso = isoSlice[idx];
    if (iso) { setDrillMonth(iso); return; }
    const now = new Date();
    // labelsSlice length tells us how many months the chart spans
    const labelsLen = labelsSlice.length; // 12, 6, or 3
    const stepsBack = labelsLen - 1 - idx;
    const d = new Date(now.getFullYear(), now.getMonth() - stepsBack, 1);
    setDrillMonth(`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`);
  };

  const exportCSV = () => {
    const headers = ['Product', 'SKU', 'Revenue (THB M)', 'Target (THB M)', 'Pacing %', 'MoM %', 'Customers', 'Lost %', 'Segment'];
    const rows = productsSorted.map(p => {
      const pac = p.target ? ((p.revenue / p.target) * 100).toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})+'%' : '—';
      return [p.name, p.sku, p.revenue, p.target ?? '—', pac, p.growth, p.customers, p.churn, p.deals];
    });
    const csv = [headers, ...rows].map(r => r.map(c => `"${c}"`).join(',')).join('\n');
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = `${group.name.replace(/\s+/g, '_')}_products.csv`;
    document.body.appendChild(a); a.click(); a.remove();
    URL.revokeObjectURL(url);
    showToast(`Exported ${productsSorted.length} products to CSV`, { variant: 'success', detail: a.download });
  };

  const headers = [
    { key: 'name', label: 'Product', w: '22%', align: 'left' },
    { key: 'sku', label: 'SKU', w: '9%', align: 'left' },
    { key: 'revenue', label: `Revenue (THB M)`, w: '10%', align: 'right' },
    { key: 'target', label: 'Target (THB M)', w: '10%', align: 'right' },
    { key: 'pacing', label: 'Pacing', w: '9%', align: 'right' },
    { key: 'growth', label: 'MoM', w: '7%', align: 'right' },
    { key: 'customers', label: 'Customers', w: '9%', align: 'right' },
    { key: 'churn', label: 'Lost %', w: '7%', align: 'right' },
    { key: 'trend', label: 'Trend 12M', w: '10%', align: 'left' },
    { key: 'share', label: 'Share', w: '7%', align: 'left' },
  ];

  if (group.revenue === 0 && group.products.length === 0) {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: '22px 24px', position: 'relative', overflow: 'hidden' }}>
          <div style={{ position: 'absolute', top: 0, left: 0, width: 4, height: '100%', background: color }} />
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 4 }}>
            <span style={{ width: 10, height: 10, borderRadius: '50%', background: color, flexShrink: 0 }} />
            <span style={{ fontWeight: 600, fontSize: 16 }}>{group.name}</span>
          </div>
          <div style={{ color: 'var(--ink-3)', fontSize: 13 }}>Group นี้ยังไม่มีข้อมูล revenue — กรุณาตรวจสอบการกำหนด product ใน Group Setting หรืออัปโหลดไฟล์ billing ที่มีข้อมูลของ group นี้</div>
        </div>
      </div>
    );
  }

  return (
    <React.Fragment>
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Group header banner */}
      <div style={{
        background: 'var(--panel)',
        border: '1px solid var(--line)',
        padding: isMobile ? '16px 16px' : '22px 24px',
        display: 'grid',
        gridTemplateColumns: isMobile ? '1fr 1fr' : '1fr 1fr 1fr 1fr 1.4fr',
        gap: isMobile ? 16 : 32,
        alignItems: 'center',
        position: 'relative',
        overflow: 'hidden',
      }}>
        <div style={{ position: 'absolute', top: 0, left: 0, width: 4, height: '100%', background: color }} />
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4 }}>Product Group</div>
          <h1 className="serif" style={{ fontSize: 26, fontWeight: 500, letterSpacing: '-0.02em', marginBottom: 4 }}>{group.name}</h1>
          <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>{group.desc}</div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>Revenue YTD</div>
          <div className="num" style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.02em' }}>{fmtTHB(group.revenue)}</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>{numFmt(share)}% of company</div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>Growth</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
            <div><div style={{ fontSize: 10, color: 'var(--ink-3)' }}>MoM</div><Delta value={group.growth} size="lg" /></div>
            <div><div style={{ fontSize: 10, color: 'var(--ink-3)' }}>YoY</div><Delta value={group.yoy} size="lg" /></div>
          </div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>Pacing vs plan</div>
          <div className="num" style={{ fontSize: 22, fontWeight: 500, color: hasTarget ? (pacing >= 100 ? 'var(--positive)' : 'var(--ink)') : 'var(--ink-3)' }}>
            {hasTarget ? pacing.toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})+'%' : '—'}
          </div>
          <div style={{ height: 4, background: '#f1eee5', marginTop: 6, borderRadius: 2, overflow: 'hidden' }}>
            {hasTarget && <div style={{ width: `${Math.min(pacing, 100)}%`, height: '100%', background: color }} />}
          </div>
        </div>
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>12-month trend</div>
          <Sparkline data={group.trend} width={220} height={48} color={color} strokeWidth={1.75} />
        </div>
      </div>

      {/* trend + composition for products */}
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '2fr 1fr', gap: 20 }}>
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
          <SectionHead title="Revenue trajectory" subtitle={`${group.name} group · ${range === '12M' ? 'last 12 months' : range === '6M' ? 'last 6 months' : 'last 3 months'} · คลิกเดือนเพื่อดูรายละเอียด`}>
            <Segmented dense options={['12M', '6M', '3M']} value={range} onChange={setRange} />
          </SectionHead>
          <LineChart
            series={[
              { label: 'Actual', data: trendSlice },
              { label: 'Last year', data: lastYearSlice, dashed: true }
            ]}
            labels={labelsSlice}
            height={220}
            colors={[color, 'var(--ink-3)']}
            annotation={{ index: trendSlice.length - 1, value: trendSlice[trendSlice.length - 1], label: fmtTHB(trendSlice[trendSlice.length - 1]) }}
            onClickPoint={handleTrajClick}
          />
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4, textAlign: 'right' }}>
            👆 คลิกที่จุดบนกราฟเพื่อดูรายละเอียดลูกค้า
          </div>
        </div>
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
          <SectionHead title="Product mix" subtitle={`${group.products.length} products`} />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {productsSorted.slice(0, 6).map((p, i) => {
              const pct = Math.min((p.revenue / group.revenue) * 100, 100);
              return (
                <div key={p.id}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11.5, marginBottom: 4 }}>
                    <span style={{ color: 'var(--ink-2)', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', maxWidth: '70%' }}>{p.name}</span>
                    <span className="num" style={{ fontWeight: 500, flexShrink: 0 }}>{numFmt(pct)}%</span>
                  </div>
                  <div style={{ height: 6, background: '#f1eee5', borderRadius: 1, overflow: 'hidden' }}>
                    <div style={{ width: `${pct}%`, height: '100%', background: color, opacity: 0.4 + (i === 0 ? 0.6 : 0) - i * 0.06 }} />
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>

      {/* Products table */}
      <div style={{ background: 'var(--panel)', border: '1px solid var(--line)' }}>
        <div style={{ padding: '16px 20px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div>
            <h3 className="serif" style={{ fontSize: 16, fontWeight: 500 }}>Products in {group.name}</h3>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>คลิกแถวเพื่อดูรายละเอียดของผลิตภัณฑ์</div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <button onClick={() => setShowFilter(s => !s)} style={{ fontSize: 11, color: showFilter ? 'var(--ink)' : 'var(--ink-2)', display: 'flex', gap: 4, alignItems: 'center', padding: '5px 10px', border: `1px solid ${showFilter ? 'var(--ink)' : 'var(--line)'}`, borderRadius: 3, background: showFilter ? '#fbf6ec' : 'var(--panel)' }}>
              <Icon name="filter" size={11} /> Filter {filterText && <span style={{ fontFamily: 'IBM Plex Mono', fontSize: 10, padding: '0 4px', background: 'var(--accent)', color: '#fff', borderRadius: 8, marginLeft: 2 }}>1</span>}
            </button>
            <button onClick={exportCSV} style={{ fontSize: 11, color: 'var(--ink-2)', display: 'flex', gap: 4, alignItems: 'center', padding: '5px 10px', border: '1px solid var(--line)', borderRadius: 3 }}>
              <Icon name="download" size={11} /> CSV
            </button>
          </div>
        </div>
        {showFilter && (
          <div style={{ padding: '10px 20px', borderBottom: '1px solid var(--line-2)', background: '#fbfaf6', display: 'flex', alignItems: 'center', gap: 10 }}>
            <Icon name="search" size={12} />
            <input
              value={filterText}
              onChange={e => setFilterText(e.target.value)}
              placeholder="Search by product name, SKU, or segment…"
              autoFocus
              style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', fontSize: 12.5, color: 'var(--ink)' }}
            />
            {filterText && <button onClick={() => setFilterText('')} style={{ fontSize: 11, color: 'var(--ink-3)' }}><Icon name="close" size={12} /></button>}
            <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{productsSorted.length} of {group.products.length}</span>
          </div>
        )}
        <div className="mob-scroll">
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5, minWidth: isMobile ? 700 : undefined }}>
          <thead>
            <tr style={{ background: '#fbfaf6', borderBottom: '1px solid var(--line)' }}>
              {headers.map(h => (
                <th key={h.key} onClick={() => ['revenue', 'target', 'pacing', 'growth', 'customers', 'churn'].includes(h.key) && setSort({ key: h.key, dir: sort.key === h.key && sort.dir === 'desc' ? 'asc' : 'desc' })}
                  style={{
                    padding: '10px 16px',
                    textAlign: h.align,
                    fontSize: 10,
                    textTransform: 'uppercase',
                    letterSpacing: '0.06em',
                    color: 'var(--ink-3)',
                    fontWeight: 500,
                    width: h.w,
                    cursor: ['revenue', 'target', 'pacing', 'growth', 'customers', 'churn'].includes(h.key) ? 'pointer' : 'default',
                    userSelect: 'none',
                  }}>
                  <span style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
                    {h.label}
                    {sort.key === h.key && <span style={{ fontSize: 9 }}>{sort.dir === 'desc' ? '▼' : '▲'}</span>}
                  </span>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {productsSorted.map((p, i) => {
              const pct = (p.revenue / group.revenue) * 100;
              const isSelected = selectedProduct === p.id;
              return (
                <tr key={p.id} onClick={() => onProductClick(p.id)} style={{
                  borderBottom: '1px solid var(--line-2)',
                  cursor: 'pointer',
                  background: isSelected ? '#fbf6ec' : 'transparent',
                }}
                  onMouseEnter={e => !isSelected && (e.currentTarget.style.background = '#fbfaf6')}
                  onMouseLeave={e => !isSelected && (e.currentTarget.style.background = 'transparent')}>
                  <td style={{ padding: '12px 16px', fontWeight: 500 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <span style={{ width: 4, height: 16, background: color, opacity: 0.3 + (1 - i / productsSorted.length) * 0.7 }} />
                      {p.name}
                    </div>
                    <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2, paddingLeft: 12 }}>{p.deals}</div>
                  </td>
                  <td style={{ padding: '12px 16px', fontFamily: 'IBM Plex Mono', fontSize: 11, color: 'var(--ink-3)' }}>{p.sku}</td>
                  <td className="num" style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 500 }}>{p.revenue.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 })}</td>
                  <td className="num" style={{ padding: '12px 16px', textAlign: 'right', color: 'var(--ink-3)' }}>
                    {p.target != null ? p.target.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) : '—'}
                  </td>
                  <td style={{ padding: '12px 16px', textAlign: 'right' }}>
                    {p.target ? (() => {
                      const pac = (p.revenue / p.target) * 100;
                      return (
                        <span className="num" style={{ fontSize: 11.5, fontWeight: 500, color: pac >= 100 ? 'var(--positive)' : pac >= 75 ? 'var(--ink)' : 'var(--negative)' }}>
                          {numFmt(pac,0)}%
                        </span>
                      );
                    })() : <span style={{ color: 'var(--ink-3)' }}>—</span>}
                  </td>
                  <td style={{ padding: '12px 16px', textAlign: 'right' }}><Delta value={p.growth} /></td>
                  <td className="num" style={{ padding: '12px 16px', textAlign: 'right' }}>{fmtCustomers(p.customers)}</td>
                  <td className="num" style={{ padding: '12px 16px', textAlign: 'right', color: p.churn > 3 ? 'var(--negative)' : 'var(--ink)' }}>{numFmt(p.churn)}</td>
                  <td style={{ padding: '8px 16px' }}><Sparkline data={p.trend} width={90} height={26} color={color} fill={false} strokeWidth={1.25} /></td>
                  <td style={{ padding: '12px 16px' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <div style={{ flex: 1, height: 4, background: '#f1eee5', borderRadius: 1, minWidth: 30 }}>
                        <div style={{ width: `${pct}%`, height: '100%', background: color }} />
                      </div>
                      <span className="num" style={{ fontSize: 11, color: 'var(--ink-3)', minWidth: 32, textAlign: 'right' }}>{numFmt(pct,0)}%</span>
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
          <tfoot>
            <tr style={{ background: '#fbfaf6', borderTop: '1px solid var(--line)' }}>
              <td style={{ padding: '10px 16px', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Total · {group.products.length} products</td>
              <td></td>
              <td className="num" style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 600 }}>{numFmt(group.revenue)}</td>
              <td className="num" style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 600, color: 'var(--ink-3)' }}>
                {group.target != null ? numFmt(group.target) : '—'}
              </td>
              <td className="num" style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 600 }}>
                {group.target ? (() => {
                  const pac = (group.revenue / group.target) * 100;
                  return <span style={{ color: pac >= 100 ? 'var(--positive)' : pac >= 75 ? 'var(--ink)' : 'var(--negative)' }}>{numFmt(pac,0)}%</span>;
                })() : '—'}
              </td>
              <td style={{ padding: '10px 16px', textAlign: 'right' }}><Delta value={group.growth} /></td>
              <td className="num" style={{ padding: '10px 16px', textAlign: 'right' }}>{fmtCustomers(group.customers)}</td>
              <td></td>
              <td></td>
              <td className="num" style={{ padding: '10px 16px', fontSize: 11, color: 'var(--ink-3)' }}>100%</td>
            </tr>
          </tfoot>
        </table>
        </div>{/* mob-scroll */}
      </div>

      {/* Top customers */}
      {groupAccounts.length > 0 && (
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 20 }}>
          <SectionHead title="Top customers" subtitle={`By revenue contribution in ${group.name} · YTD`} />
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
                <th style={{ textAlign: 'left', padding: '8px 6px', fontWeight: 500 }}>Account</th>
                <th style={{ textAlign: 'left', padding: '8px 6px', fontWeight: 500 }}>Sector</th>
                <th style={{ textAlign: 'left', padding: '8px 6px', fontWeight: 500 }}>Products in {group.name}</th>
                <th style={{ textAlign: 'right', padding: '8px 6px', fontWeight: 500 }}>Revenue</th>
                <th style={{ textAlign: 'right', padding: '8px 6px', fontWeight: 500 }}>YoY</th>
              </tr>
            </thead>
            <tbody>
              {groupAccounts.map(a => (
                <tr key={a.id} style={{ borderBottom: '1px solid var(--line-2)' }}
                  onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                  <td style={{ padding: '10px 6px', fontWeight: 500, maxWidth: 200, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }} title={a.name}>{a.name}</td>
                  <td style={{ padding: '10px 6px', color: 'var(--ink-3)', maxWidth: 130, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }} title={a.sector}>{a.sector}</td>
                  <td style={{ padding: '10px 6px' }}>
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                      {(a.sub_cats && a.sub_cats.length > 0)
                        ? a.sub_cats.map(sc => <ColorDot key={sc} label={sc} color={strToColor(sc)} />)
                        : (a.groups || []).map(g => <GroupDot key={g} groupId={g} />)
                      }
                    </div>
                  </td>
                  <td className="num" style={{ padding: '10px 6px', textAlign: 'right', fontWeight: 500 }}>฿{numFmt(a.revenue)}M</td>
                  <td style={{ padding: '10px 6px', textAlign: 'right' }}><Delta value={a.growth} /></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>{/* end flex column */}
    {drillMonth && (
      <MonthDrillPanel
        month={drillMonth}
        onClose={() => setDrillMonth(null)}
        groupId={group.id}
        groupName={group.name}
      />
    )}
    </React.Fragment>
  );
};

// ============================================================
// Product detail side panel (drill 2)
// ============================================================
const ProductPanel = ({ product, group, onClose, period = 'YTD' }) => {
  if (!product) return null;
  const color = GROUP_COLORS[group.id];
  const isMobile = useIsMobile();

  const [productAccounts, setProductAccounts] = useState([]);
  const [accountsLoading, setAccountsLoading] = useState(false);
  const [showReport, setShowReport] = useState(false);

  useEffect(() => {
    if (!product.product_cat || !product.product_sub_cat) return;
    setAccountsLoading(true);
    authFetch(`/api/accounts/by-product?product_cat=${encodeURIComponent(product.product_cat)}&product_sub_cat=${encodeURIComponent(product.product_sub_cat)}&period=${period}&limit=5`)
      .then(r => r.json())
      .then(d => { setProductAccounts(d.accounts || []); })
      .catch(() => {})
      .finally(() => setAccountsLoading(false));
  }, [product.product_cat, product.product_sub_cat, period]);
  return (
    <div style={{
      position: 'fixed', top: 0, right: 0, bottom: 0,
      width: isMobile ? '100vw' : 460,
      background: 'var(--panel)',
      borderLeft: '1px solid var(--line)',
      boxShadow: '-12px 0 32px rgba(0,0,0,0.06)',
      zIndex: 50,
      display: 'flex', flexDirection: 'column',
      animation: 'slideIn 200ms ease-out',
    }}>
      <style>{`@keyframes slideIn { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }`}</style>
      <div style={{ padding: '20px 24px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'flex-start', gap: 16 }}>
        <div style={{ width: 4, height: 40, background: color }} />
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: 'var(--ink-3)', marginBottom: 4 }}>
            <span>{group.name}</span>
            <Icon name="chevron" size={9} />
            <span style={{ fontFamily: 'IBM Plex Mono' }}>{product.sku}</span>
          </div>
          <h2 className="serif" style={{ fontSize: 20, fontWeight: 500, letterSpacing: '-0.01em' }}>{product.name}</h2>
        </div>
        <button onClick={onClose} style={{ color: 'var(--ink-3)' }}><Icon name="close" size={14} /></button>
      </div>

      <div style={{ overflowY: 'auto', flex: 1, padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 22 }}>
        {/* big number */}
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>Revenue YTD</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
            <div className="num" style={{ fontSize: 32, fontWeight: 500, letterSpacing: '-0.02em' }}>฿{numFmt(product.revenue)}M</div>
            <Delta value={product.growth} size="lg" />
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }}>{((product.revenue / group.revenue) * 100)}% of {group.name} · segment: {product.deals}</div>
        </div>

        {/* trend */}
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>12-month revenue trend</div>
          <LineChart
            series={[
              { label: String(new Date().getFullYear()), data: product.trend || [] },
              { label: String(new Date().getFullYear() - 1), data: product.prior_trend || [], dashed: true },
            ]}
            labels={(window.__trendMonths || []).map(iso => new Date(iso + '-02').toLocaleString('en', { month: 'short' }))}
            height={180}
            colors={[color, '#9ca3af']}
            showLegend={true}
          />
        </div>

        {/* metrics */}
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>Performance metrics</div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1, background: 'var(--line)', border: '1px solid var(--line)' }}>
            {[
              { label: 'Active customers', value: fmtCustomers(product.customers), delta: 4.6 },
              { label: 'Monthly lost', value: product.churn.toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})+'%', delta: -0.3 },
              { label: 'ARPU', value: '฿' + Math.round(product.revenue * 1_000_000 / product.customers).toLocaleString(), delta: 2.4 },
            ].map(m => (
              <div key={m.label} style={{ background: 'var(--panel)', padding: '14px 16px' }}>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>{m.label}</div>
                <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
                  <span className="num" style={{ fontSize: 16, fontWeight: 500 }}>{m.value}</span>
                  <Delta value={m.delta} />
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* top accounts using this product — from DB */}
        <div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>Top accounts</div>
          {accountsLoading ? (
            <div style={{ fontSize: 12, color: 'var(--ink-3)', padding: '12px 0' }}>Loading…</div>
          ) : productAccounts.length === 0 ? (
            <div style={{ fontSize: 12, color: 'var(--ink-3)', padding: '12px 0' }}>No account data available</div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column' }}>
              {productAccounts.map((a, i) => (
                <div key={a.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', borderTop: i > 0 ? '1px solid var(--line-2)' : 'none' }}>
                  <div>
                    <div style={{ fontSize: 12.5, fontWeight: 500, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', maxWidth: 180 }} title={a.name}>{a.name}</div>
                    <div style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{a.sector}</div>
                  </div>
                  <div style={{ textAlign: 'right' }}>
                    <div className="num" style={{ fontSize: 12.5, fontWeight: 500 }}>฿{numFmt(a.revenue)}M</div>
                    {a.growth !== null ? <Delta value={a.growth} /> : <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>—</span>}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* action */}
        <button onClick={() => setShowReport(true)} style={{ width: '100%', padding: '11px', background: 'var(--ink)', color: '#fff', fontSize: 12.5, fontWeight: 500, borderRadius: 3 }}>
          Open full report
        </button>
      </div>

      {/* ── Full report modal ── */}
      {showReport && (
        <div onClick={() => setShowReport(false)} style={{ position: 'fixed', inset: 0, background: 'rgba(20,22,28,0.5)', zIndex: 300, display: 'grid', placeItems: 'center', padding: 32 }}>
          <div id="product-report-printable" onClick={e => e.stopPropagation()} style={{ background: 'var(--panel)', width: '100%', maxWidth: 860, maxHeight: '90vh', display: 'flex', flexDirection: 'column', boxShadow: '0 24px 64px rgba(0,0,0,0.22)', borderRadius: 4, overflow: 'hidden' }}>

            {/* header */}
            <div style={{ padding: '20px 28px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 14, flexShrink: 0 }}>
              <div style={{ width: 5, height: 48, background: color, borderRadius: 2, flexShrink: 0 }} />
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 11, color: 'var(--ink-3)', marginBottom: 3, display: 'flex', gap: 6, alignItems: 'center' }}>
                  <span>{group.name}</span><Icon name="chevron" size={9} /><span className="num">{product.sku}</span>
                </div>
                <h2 className="serif" style={{ fontSize: 22, fontWeight: 500 }}>{product.name}</h2>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>Segment: {product.deals} · Period: {period}</div>
              </div>
              <button onClick={() => setShowReport(false)} style={{ color: 'var(--ink-3)', padding: 4 }}><Icon name="close" size={15} /></button>
            </div>

            {/* body */}
            <div style={{ overflowY: 'auto', flex: 1, padding: '24px 28px', display: 'flex', flexDirection: 'column', gap: 28 }}>

              {/* KPI row */}
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 1, background: 'var(--line)', border: '1px solid var(--line)' }}>
                {[
                  { label: 'Revenue YTD', value: `฿${numFmt(product.revenue)}M`, delta: product.growth },
                  { label: 'vs Target', value: product.target > 0 ? `${((product.revenue / product.target) * 100).toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})}%` : '—', sub: product.target > 0 ? `Target ฿${numFmt(product.target)}M` : 'No target set' },
                  { label: 'Active Customers', value: fmtCustomers(product.customers), delta: 4.6 },
                  { label: 'Monthly Lost', value: `${numFmt(product.churn)}%`, delta: -0.3 },
                ].map(k => (
                  <div key={k.label} style={{ background: 'var(--panel)', padding: '16px 20px' }}>
                    <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>{k.label}</div>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' }}>
                      <span className="num" style={{ fontSize: 20, fontWeight: 500 }}>{k.value}</span>
                      {k.delta != null && <Delta value={k.delta} />}
                    </div>
                    {k.sub && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }}>{k.sub}</div>}
                  </div>
                ))}
              </div>

              {/* trend chart */}
              <div>
                <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 12 }}>12-Month Revenue Trend</div>
                <LineChart
                  series={[
                    { label: String(new Date().getFullYear()), data: product.trend || [] },
                    { label: String(new Date().getFullYear() - 1), data: product.prior_trend || [], dashed: true },
                  ]}
                  labels={(window.__trendMonths || []).map(iso => new Date(iso + '-02').toLocaleString('en', { month: 'short' }))}
                  height={200} colors={[color, '#9ca3af']} showLegend={true}
                />
              </div>

              {/* ARPU + share row */}
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
                <div style={{ background: '#fbfaf6', border: '1px solid var(--line)', padding: '16px 20px', borderRadius: 2 }}>
                  <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>ARPU</div>
                  <div className="num" style={{ fontSize: 22, fontWeight: 500 }}>฿{Math.round(product.revenue * 1_000_000 / product.customers).toLocaleString()}</div>
                  <Delta value={2.4} />
                </div>
                <div style={{ background: '#fbfaf6', border: '1px solid var(--line)', padding: '16px 20px', borderRadius: 2 }}>
                  <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 6 }}>Share of {group.name}</div>
                  <div className="num" style={{ fontSize: 22, fontWeight: 500 }}>{((product.revenue / group.revenue) * 100)}%</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }}>฿{numFmt(product.revenue)}M of ฿{numFmt(group.revenue)}M</div>
                  <div style={{ marginTop: 8, height: 6, background: 'var(--line)', borderRadius: 3 }}>
                    <div style={{ width: `${(product.revenue / group.revenue) * 100}%`, height: '100%', background: color, borderRadius: 3 }} />
                  </div>
                </div>
              </div>

              {/* top accounts */}
              {(productAccounts.length > 0) && (
                <div>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>Top Accounts</div>
                  <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
                    <thead>
                      <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
                        {['Account', 'Sector', 'Revenue', 'YoY'].map(h => (
                          <th key={h} style={{ padding: '8px 10px', textAlign: h === 'Revenue' || h === 'YoY' ? 'right' : 'left', fontWeight: 500 }}>{h}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {productAccounts.map((a, i) => (
                        <tr key={a.id} style={{ borderBottom: '1px solid var(--line-2)' }}>
                          <td style={{ padding: '10px 10px', fontWeight: 500 }}>{a.name}</td>
                          <td style={{ padding: '10px 10px', color: 'var(--ink-3)', fontSize: 11.5 }}>{a.sector}</td>
                          <td className="num" style={{ padding: '10px 10px', textAlign: 'right' }}>฿{numFmt(a.revenue)}M</td>
                          <td style={{ padding: '10px 10px', textAlign: 'right' }}><Delta value={a.growth} /></td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
            </div>

            {/* footer */}
            <div style={{ padding: '14px 28px', borderTop: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0 }}>
              <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>
                {product.name} · {product.sku} · {new Date().toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
              </div>
              <div style={{ display: 'flex', gap: 8 }}>
                <button onClick={() => setShowReport(false)} style={{ padding: '8px 18px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12.5, color: 'var(--ink-2)' }}>Close</button>
                <button onClick={() => {
                  const el = document.getElementById('product-report-printable');
                  if (!el) return;

                  // Collect CSS variable values from root
                  const rootStyle = getComputedStyle(document.documentElement);
                  const vars = ['--bg','--panel','--ink','--ink-2','--ink-3','--line','--line-2','--accent','--accent-2','--positive','--negative']
                    .map(v => `${v}:${rootStyle.getPropertyValue(v)}`)
                    .join(';');

                  // Clone modal HTML (exclude the footer buttons row = last child of modal card)
                  const clone = el.cloneNode(true);
                  const footer = clone.lastElementChild;
                  if (footer) footer.remove();
                  // Fix close button (top-right X) — remove it
                  clone.querySelectorAll('button').forEach(b => b.remove());
                  // Remove overflow/maxHeight so content fully expands
                  clone.style.cssText = 'max-height:none;overflow:visible;box-shadow:none;border-radius:0;width:100%;display:flex;flex-direction:column;';

                  // Grab all stylesheets text that are same-origin
                  const sheetText = Array.from(document.styleSheets)
                    .map(s => { try { return Array.from(s.cssRules).map(r => r.cssText).join('\n'); } catch { return ''; } })
                    .join('\n');

                  const win = window.open('', '_blank');
                  win.document.write(`<!DOCTYPE html>
<html lang="th"><head>
<meta charset="UTF-8">
<title>${product.name} — Full Report</title>
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@200;300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
  :root{${vars}}
  *{box-sizing:border-box;margin:0;padding:0;}
  body{font-family:'Kanit',sans-serif;background:#fff;color:var(--ink);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased;padding:24px 28px;}
  .num{font-family:'IBM Plex Mono',monospace;font-variant-numeric:tabular-nums;letter-spacing:-0.01em;}
  .serif{font-family:'Kanit',sans-serif;font-weight:500;}
  button{display:none!important;}
  ${sheetText}
  @media print{
    @page{margin:12mm 14mm;size:A4;}
    body{padding:0;}
    *{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;}
  }
</style>
</head><body>${clone.outerHTML}</body></html>`);
                  win.document.close();
                  win.focus();
                  setTimeout(() => { win.print(); }, 800);
                }} style={{ padding: '8px 18px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12.5, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 6 }}>
                  <Icon name="download" size={12} /> Save as PDF
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// ============================================================
// Settings views
// ============================================================
const SettingsPageShell = ({ title, subtitle, children, actions }) => (
  <div>
    <div style={{ marginBottom: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
      <div>
        <h1 className="serif" style={{ fontSize: 28, fontWeight: 500, letterSpacing: '-0.02em', marginBottom: 4 }}>{title}</h1>
        <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>{subtitle}</div>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>{actions}</div>
    </div>
    {children}
  </div>
);

// Modal showing full details of one upload record
const UploadDetailModal = ({ uploadId, onClose, onReprocess }) => {
  const [detail, setDetail] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [reprocessing, setReprocessing] = React.useState(false);

  React.useEffect(() => {
    if (!uploadId) return;
    setLoading(true);
    window.API.getUploadDetails(uploadId)
      .then(d => { setDetail(d); setLoading(false); })
      .catch(e => { setDetail({ error: e.message }); setLoading(false); });
  }, [uploadId]);

  const handleReprocess = async () => {
    setReprocessing(true);
    try {
      await window.API.reprocessUpload(uploadId);
      showToast('Re-processing started', { variant: 'info', detail: 'Status will update once complete' });
      onClose();
      if (onReprocess) onReprocess();
    } catch (e) {
      showToast('Re-process failed', { variant: 'error', detail: e.message });
    } finally {
      setReprocessing(false);
    }
  };

  const fmt = (bytes) => {
    if (!bytes) return '—';
    const n = parseInt(bytes);
    return n > 1024*1024 ? (n/1024/1024).toFixed(1)+' MB' : Math.round(n/1024)+' KB';
  };

  const isOk = detail?.status === 'success';

  return (
    <Modal open={!!uploadId} onClose={onClose} title="Upload details" subtitle={detail?.filename || ''} width={520}>
      {loading ? (
        <div style={{ textAlign: 'center', padding: 32, color: 'var(--ink-3)' }}>Loading…</div>
      ) : detail?.error ? (
        <div style={{ color: 'var(--negative)', padding: 16 }}>{detail.error}</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
          {[
            ['File', detail.filename],
            ['Size', fmt(detail.file_size)],
            ['Rows imported', detail.row_count?.toLocaleString() || '—'],
            ['Uploaded', detail.uploaded_at ? new Date(detail.uploaded_at).toLocaleString('th-TH') : '—'],
            ['Processed', detail.processed_at ? new Date(detail.processed_at).toLocaleString('th-TH') : '—'],
            ['Uploaded by', detail.uploaded_by || 'Admin'],
            ['Status', null],
          ].map(([label, value], i) => (
            <div key={label} style={{
              display: 'grid', gridTemplateColumns: '140px 1fr', padding: '10px 0',
              borderBottom: i < 6 ? '1px solid var(--line-2)' : 'none', fontSize: 13,
            }}>
              <span style={{ color: 'var(--ink-3)', fontWeight: 500 }}>{label}</span>
              {label === 'Status' ? (
                <UploadStatusBadge status={detail.status === 'success' ? 'processed' : detail.status} errorMsg={detail.error_msg} />
              ) : (
                <span className="num" style={{ wordBreak: 'break-word' }}>{value}</span>
              )}
            </div>
          ))}

          {detail.error_msg && (
            <div style={{ marginTop: 16, padding: '12px 14px', background: '#fff5f5', border: '1px solid #ffd0d0', borderRadius: 4, fontSize: 12, color: 'var(--negative)', lineHeight: 1.6 }}>
              <strong>Error detail:</strong><br />{detail.error_msg}
            </div>
          )}

          <div style={{ display: 'flex', gap: 10, marginTop: 20, justifyContent: 'flex-end' }}>
            {detail.file_available && (
              <a
                href={window.API.getDownloadUrl(uploadId)}
                download={detail.filename}
                style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12.5, color: 'var(--ink-2)', textDecoration: 'none' }}
              >
                <Icon name="download" size={11} /> Download original
              </a>
            )}
            {detail.file_available && (
              <button
                onClick={handleReprocess}
                disabled={reprocessing}
                style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12.5, fontWeight: 500 }}
              >
                <Icon name="chart" size={11} /> {reprocessing ? 'Processing…' : 'Re-process'}
              </button>
            )}
            {!detail.file_available && (
              <div style={{ fontSize: 11.5, color: 'var(--ink-3)', alignSelf: 'center' }}>
                ⚠ ไฟล์ต้นฉบับไม่พบในระบบ — ไม่สามารถ download หรือ re-process ได้
              </div>
            )}
          </div>
        </div>
      )}
    </Modal>
  );
};

// Tooltip-bearing status badge for upload history rows
const UploadStatusBadge = ({ status, errorMsg }) => {
  const [tip, setTip] = React.useState(false);
  const isOk = status === 'processed';
  return (
    <span style={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 4 }}>
      <span style={{
        fontSize: 10, padding: '2px 8px', borderRadius: 10,
        background: isOk ? '#e3f1ea' : '#fdecdf',
        color: isOk ? 'var(--positive)' : 'var(--negative)',
        textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 500,
      }}>● {status}</span>
      {!isOk && errorMsg && (
        <span
          onMouseEnter={() => setTip(true)}
          onMouseLeave={() => setTip(false)}
          style={{ cursor: 'help', color: 'var(--negative)', fontSize: 13, lineHeight: 1 }}>ⓘ
          {tip && (
            <span style={{
              position: 'absolute', bottom: 'calc(100% + 6px)', right: 0,
              background: '#1e1e1e', color: '#fff',
              fontSize: 11, lineHeight: 1.5, padding: '8px 12px',
              borderRadius: 5, width: 280, whiteSpace: 'pre-wrap', zIndex: 999,
              boxShadow: '0 4px 16px rgba(0,0,0,0.25)',
              pointerEvents: 'none',
            }}>
              {errorMsg}
            </span>
          )}
        </span>
      )}
    </span>
  );
};

const UploadFileView = () => {
  const [drag, setDrag] = useState(false);
  const [dataType, setDataType] = useState('actuals');
  const [importMode, setImportMode] = useState('update');
  const [progress, setProgress] = useState(null); // {name, pct, status}
  const fileInputRef = useRef(null);
  const [recent, setRecent] = useState([]);
  const [showTemplates, setShowTemplates] = useState(false);
  const [showAllUploads, setShowAllUploads] = useState(false);
  const [menuFor, setMenuFor] = useState(null);
  const [detailUploadId, setDetailUploadId] = useState(null);

  // Load upload history from API on mount
  useEffect(() => {
    if (window.API) {
      window.API.getUploadHistory().then(history => {
        if (history && history.length > 0) {
          setRecent(history.map(h => ({
            id:       h.id,
            name:     h.filename,
            size:     h.file_size ? (h.file_size > 1024*1024 ? (h.file_size/1024/1024).toFixed(1)+' MB' : Math.round(h.file_size/1024)+' KB') : '—',
            rows:     h.row_count ? h.row_count.toLocaleString() : '—',
            when:     h.uploaded_at ? new Date(h.uploaded_at).toLocaleString('th-TH', {dateStyle:'short',timeStyle:'short'}) : '—',
            status:   h.status === 'success' ? 'processed' : h.status,
            by:       h.uploaded_by || 'Admin',
            errorMsg: h.error_msg || null,
          })));
        }
      });
    }
  }, []);

  const handleFiles = async (files) => {
    if (!files || !files.length) return;
    const file = files[0];
    if (file.size > 100 * 1024 * 1024) {
      showToast('File too large', { variant: 'error', detail: 'Max 100 MB allowed' });
      return;
    }
    // Confirm destructive replace action
    if (importMode === 'replace') {
      const label = dataType === 'targets' ? 'targets ทุก fiscal year ที่มีในไฟล์' : 'ข้อมูลทุก period ที่มีในไฟล์';
      const ok = window.confirm(`⚠️ โหมด "ลบและแทนที่" จะลบ ${label} ออกก่อน แล้ว import ใหม่ทั้งหมด\n\nยืนยันดำเนินการ?`);
      if (!ok) return;
    }
    setProgress({ name: file.name, pct: 0, status: 'uploading' });
    try {
      let result;
      if (dataType === 'targets') {
        // Target import: send to dedicated endpoint, no progress streaming
        setProgress({ name: file.name, pct: 50, status: 'uploading' });
        const fd = new FormData();
        fd.append('file', file);
        fd.append('mode', importMode);
        const r = await authFetch('/api/targets/import', { method: 'POST', body: fd });
        result = await r.json();
        if (!r.ok) throw new Error(result.error || 'Import failed');
        result.rows = result.upserted;
        setProgress({ name: file.name, pct: 100, status: 'processing' });
      } else {
        result = await window.API.uploadFile(file, (pct) => {
          setProgress({ name: file.name, pct, status: pct < 100 ? 'uploading' : 'processing' });
        }, importMode);
      }
      setProgress(null);
      showToast('อัปโหลดสำเร็จ', { variant: 'success', detail: `${file.name} · ${result.rows?.toLocaleString()} rows imported` });
      // Refresh upload history for all data types (targets now also logs to upload_history)
      if (window.API) {
        const history = await window.API.getUploadHistory();
        setRecent(history.map(h => ({
          id:     h.id,
          name:   h.filename,
          size:   h.file_size ? (h.file_size > 1024*1024 ? (h.file_size/1024/1024).toFixed(1)+' MB' : Math.round(h.file_size/1024)+' KB') : '—',
          rows:   h.row_count ? h.row_count.toLocaleString() : '—',
          when:   h.uploaded_at ? new Date(h.uploaded_at).toLocaleString('th-TH', {dateStyle:'short',timeStyle:'short'}) : '—',
          status: h.status === 'success' ? 'processed' : h.status,
          by:     h.uploaded_by || 'Admin',
        })));
      }
    } catch (err) {
      setProgress(null);
      showToast('อัปโหลดล้มเหลว', { variant: 'error', detail: err.message });
    }
  };

  const refreshHistory = async () => {
    if (!window.API) return;
    const history = await window.API.getUploadHistory().catch(() => []);
    setRecent(history.map(h => ({
      id:       h.id,
      name:     h.filename,
      size:     h.file_size ? (h.file_size > 1024*1024 ? (h.file_size/1024/1024).toFixed(1)+' MB' : Math.round(h.file_size/1024)+' KB') : '—',
      rows:     h.row_count ? h.row_count.toLocaleString() : '—',
      when:     h.uploaded_at ? new Date(h.uploaded_at).toLocaleString('th-TH', {dateStyle:'short',timeStyle:'short'}) : '—',
      status:   h.status === 'success' ? 'processed' : h.status,
      by:       h.uploaded_by || 'Admin',
      errorMsg: h.error_msg || null,
    })));
  };

  const downloadSchema = () => {
    const json = JSON.stringify({
      dataset: dataType,
      version: '3.1',
      columns: [
        { name: 'period_month', type: 'date', format: 'YYYY-MM', required: true },
        { name: 'group_id', type: 'enum', values: GROUPS.map(g => g.id), required: true },
        { name: 'product_sku', type: 'string', required: true },
        { name: 'revenue_thb', type: 'decimal(14,2)', required: true },
        { name: 'customers', type: 'integer', required: false },
        { name: 'arpu_thb', type: 'decimal(12,2)', required: false },
      ],
    }, null, 2);
    const blob = new Blob([json], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = `revenue_schema_${dataType}_v3.1.json`;
    document.body.appendChild(a); a.click(); a.remove();
    URL.revokeObjectURL(url);
    showToast('Schema downloaded', { variant: 'success', detail: a.download });
  };

  const dataTypes = [
    { id: 'actuals', label: 'Revenue actuals', desc: 'Monthly recognized revenue per product' },
    { id: 'pipeline', label: 'Pipeline / deals', desc: 'In-flight opportunities + forecast' },
    { id: 'customers', label: 'Customer master', desc: 'Account list & segmentation' },
    { id: 'targets', label: 'Targets / plan', desc: 'Budget targets by product & quarter' },
  ];

  return (
    <SettingsPageShell
      title="Upload file"
      subtitle="Import revenue actuals, customer master, or pipeline data · CSV / XLSX up to 100 MB"
      actions={<>
        <button onClick={() => setShowTemplates(true)} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)' }}>
          <Icon name="file" size={11} /> View templates
        </button>
        <button onClick={downloadSchema} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500 }}>
          <Icon name="download" size={11} /> Download schema
        </button>
      </>}>
      <input
        ref={fileInputRef}
        type="file"
        accept=".csv,.xlsx,.xls"
        style={{ display: 'none' }}
        onChange={e => handleFiles(e.target.files)}
      />
      <div style={{ display: 'grid', gridTemplateColumns: '2fr 3fr', gap: 24, alignItems: 'start' }}>
        {/* Dropzone */}
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: 24 }}>
          <h3 className="serif" style={{ fontSize: 16, fontWeight: 500, marginBottom: 16 }}>Upload data</h3>
          <div
            onDragOver={e => { e.preventDefault(); setDrag(true); }}
            onDragLeave={() => setDrag(false)}
            onDrop={e => { e.preventDefault(); setDrag(false); handleFiles(e.dataTransfer.files); }}
            onClick={() => !progress && fileInputRef.current?.click()}
            style={{
              border: `1.5px dashed ${drag ? 'var(--accent)' : 'var(--line)'}`,
              background: drag ? '#f3f1eb' : '#fbfaf6',
              padding: progress ? '32px 24px' : '48px 24px',
              textAlign: 'center',
              borderRadius: 4,
              transition: 'all 140ms',
              cursor: progress ? 'default' : 'pointer',
            }}>
            {progress ? (
              <div>
                <Icon name="file" size={22} />
                <div style={{ fontSize: 13, fontWeight: 500, marginTop: 10 }}>{progress.name}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4 }}>{progress.status === 'uploading' ? 'Uploading…' : 'Processing rows…'}</div>
                <div style={{ width: '60%', margin: '14px auto 0', height: 6, background: '#e7e4dc', borderRadius: 3, overflow: 'hidden' }}>
                  <div style={{ width: `${progress.pct}%`, height: '100%', background: 'var(--accent)', transition: 'width 100ms' }} />
                </div>
                <div className="num" style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 6 }}>{Math.round(progress.pct)}%</div>
              </div>
            ) : (
              <>
                <div style={{ width: 48, height: 48, margin: '0 auto 14px', border: '1.5px solid var(--line)', borderRadius: '50%', display: 'grid', placeItems: 'center', color: 'var(--ink-2)', background: 'var(--panel)' }}>
                  <Icon name="download" size={20} />
                </div>
                <div style={{ fontSize: 14, fontWeight: 500, marginBottom: 6 }}>ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือก</div>
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 16 }}>CSV, XLSX · สูงสุด 100 MB · UTF-8 encoding</div>
                <button onClick={e => { e.stopPropagation(); fileInputRef.current?.click(); }} style={{ padding: '8px 18px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3 }}>เลือกไฟล์</button>
              </>
            )}
          </div>

          <div style={{ marginTop: 20 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>Data type</div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8 }}>
              {dataTypes.map(t => (
                <label key={t.id} onClick={() => setDataType(t.id)} style={{
                  display: 'flex', gap: 10, padding: '10px 12px',
                  border: `1px solid ${dataType === t.id ? 'var(--ink)' : 'var(--line)'}`,
                  background: dataType === t.id ? '#fbfaf6' : 'var(--panel)',
                  cursor: 'pointer', borderRadius: 3,
                }}>
                  <input type="radio" name="dtype" checked={dataType === t.id} onChange={() => setDataType(t.id)} style={{ accentColor: 'var(--accent)', marginTop: 2 }} />
                  <div>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.label}</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{t.desc}</div>
                  </div>
                </label>
              ))}
            </div>
          </div>

          {/* Upload mode selector */}
          <div style={{ marginTop: 16 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 10 }}>Upload mode</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              {[
                { id: 'update', label: 'เพิ่ม / อัปเดต', desc: 'Upsert — คงข้อมูลเดิม อัปเดตที่ซ้ำกัน' },
                { id: 'replace', label: 'ลบและแทนที่', desc: 'ลบข้อมูลเดิมทั้งหมดก่อน แล้ว import ใหม่' },
              ].map(m => (
                <label key={m.id} onClick={() => setImportMode(m.id)} style={{
                  display: 'flex', gap: 10, padding: '10px 12px',
                  border: `1px solid ${importMode === m.id ? (m.id === 'replace' ? 'var(--negative)' : 'var(--ink)') : 'var(--line)'}`,
                  background: importMode === m.id ? (m.id === 'replace' ? '#fff5f5' : '#fbfaf6') : 'var(--panel)',
                  cursor: 'pointer', borderRadius: 3,
                }}>
                  <input type="radio" name="imode" checked={importMode === m.id} onChange={() => setImportMode(m.id)} style={{ accentColor: m.id === 'replace' ? 'var(--negative)' : 'var(--accent)', marginTop: 2 }} />
                  <div>
                    <div style={{ fontSize: 12.5, fontWeight: 500, color: importMode === m.id && m.id === 'replace' ? 'var(--negative)' : 'var(--ink)' }}>{m.label}</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{m.desc}</div>
                  </div>
                </label>
              ))}
            </div>
            {importMode === 'replace' && (
              <div style={{ marginTop: 10, padding: '10px 12px', background: '#fff5f5', border: '1px solid #ffd0d0', borderRadius: 3, display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12, color: 'var(--negative)', lineHeight: 1.5 }}>
                <span style={{ fontSize: 14, flexShrink: 0 }}>⚠️</span>
                <span><strong>คำเตือน:</strong> โหมดนี้จะลบข้อมูลทุก period / fiscal year ที่มีในไฟล์ออกก่อน แล้ว import ใหม่ทั้งหมด — ข้อมูลเดิมจะหายไปถาวร</span>
              </div>
            )}
          </div>
        </div>

        {/* Upload history — right column */}
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)' }}>
          <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <h3 className="serif" style={{ fontSize: 16, fontWeight: 500 }}>Upload history</h3>
              <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>Latest 30 days · {recent.filter(r => r.status === 'processed').length} successful · {recent.filter(r => r.status === 'failed').length} failed</div>
            </div>
            <button onClick={() => setShowAllUploads(true)} style={{ fontSize: 11, color: 'var(--ink-2)' }}>View all →</button>
          </div>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ background: '#fbfaf6', borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
                {['File', 'Size', 'Rows', 'Uploaded', 'By', 'Status', ''].map(h => (
                  <th key={h} style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {recent.slice(0, 8).map((r, idx) => (
                <tr key={idx} style={{ borderBottom: '1px solid var(--line-2)', background: r.status === 'failed' ? '#fffafa' : 'transparent' }}>
                  <td style={{ padding: '12px 16px' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                      <Icon name="file" size={13} />
                      <div>
                        <div style={{ fontWeight: 500 }} title={r.name.length > 30 ? r.name : undefined}>
                          {r.name.length > 30 ? r.name.slice(0, 30) + '…' : r.name}
                        </div>
                        {r.status === 'failed' && r.errorMsg && (
                          <div style={{ fontSize: 10.5, color: 'var(--negative)', marginTop: 2, maxWidth: 260 }}>
                            ⚠ {r.errorMsg}
                          </div>
                        )}
                      </div>
                    </div>
                  </td>
                  <td className="num" style={{ padding: '12px 16px', color: 'var(--ink-3)' }}>{r.size}</td>
                  <td className="num" style={{ padding: '12px 16px' }}>{r.rows}</td>
                  <td style={{ padding: '12px 16px', color: 'var(--ink-3)' }}>{r.when}</td>
                  <td style={{ padding: '12px 16px' }}>{r.by}</td>
                  <td style={{ padding: '12px 16px' }}>
                    <UploadStatusBadge status={r.status} errorMsg={r.errorMsg} />
                  </td>
                  <td style={{ padding: '12px 16px', textAlign: 'right', position: 'relative' }}>
                    <Dropdown
                      trigger={<button style={{ fontSize: 14, color: 'var(--ink-2)', padding: '2px 6px' }}>•••</button>}
                      align="right"
                      items={[
                        { label: 'View details', icon: 'info', onClick: () => setDetailUploadId(r.id) },
                        { label: 'Re-process', icon: 'chart', onClick: async () => {
                            try {
                              await window.API.reprocessUpload(r.id);
                              showToast('Re-processing started', { variant: 'info', detail: 'Refresh history in a moment to see the result' });
                              setTimeout(refreshHistory, 3000);
                            } catch (e) {
                              showToast('Re-process failed', { variant: 'error', detail: e.message });
                            }
                        }},
                        { label: 'Download original', icon: 'download', onClick: () => {
                            const url = window.API.getDownloadUrl(r.id);
                            const a = document.createElement('a');
                            a.href = url; a.download = r.name;
                            document.body.appendChild(a); a.click(); a.remove();
                        }},
                        '---',
                        { label: 'Delete', icon: 'close', danger: true, onClick: async () => {
                            if (r.id && window.API) { await window.API.deleteUpload(r.id).catch(() => {}); }
                            setRecent(rec => rec.filter((_, i) => i !== idx));
                            showToast(`Deleted ${r.name}`, { variant: 'success' });
                        }},
                      ]}
                    />
                  </td>
                </tr>
              ))}
              {recent.length === 0 && (
                <tr>
                  <td colSpan={7} style={{ padding: '32px 16px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No uploads yet</td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      {/* Templates modal */}
      <Modal open={showTemplates} onClose={() => setShowTemplates(false)} title="Data templates" subtitle="Download a pre-formatted template for each data type" width={560}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {dataTypes.map(t => (
            <div key={t.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: 14, border: '1px solid var(--line)', borderRadius: 3 }}>
              <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
                <Icon name="file" size={16} />
                <div>
                  <div style={{ fontSize: 13, fontWeight: 500 }}>{t.label}</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{t.desc}</div>
                </div>
              </div>
              <a href={`/api/upload/template/${t.id}`} download={`${t.id}_template.xlsx`} style={{ display: 'flex', gap: 5, alignItems: 'center', padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 11.5, color: 'var(--ink-2)', textDecoration: 'none' }}>
                <Icon name="download" size={11} /> XLSX
              </a>
            </div>
          ))}
        </div>
      </Modal>

      {/* All uploads modal */}
      <Modal open={showAllUploads} onClose={() => setShowAllUploads(false)} title="All uploads" subtitle={`${recent.length} files in total`} width={760}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
              {['File', 'Size', 'Rows', 'Uploaded', 'By', 'Status'].map(h => (
                <th key={h} style={{ padding: '10px 12px', textAlign: 'left', fontWeight: 500 }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {recent.map((r, i) => (
              <tr key={i} style={{ borderBottom: '1px solid var(--line-2)', background: r.status === 'failed' ? '#fffafa' : 'transparent' }}>
                <td style={{ padding: '10px 12px' }}>
                  <div style={{ fontWeight: 500 }} title={r.name.length > 30 ? r.name : undefined}>
                    {r.name.length > 30 ? r.name.slice(0, 30) + '…' : r.name}
                  </div>
                  {r.status === 'failed' && r.errorMsg && (
                    <div style={{ fontSize: 10.5, color: 'var(--negative)', marginTop: 2 }}>⚠ {r.errorMsg}</div>
                  )}
                </td>
                <td className="num" style={{ padding: '10px 12px', color: 'var(--ink-3)' }}>{r.size}</td>
                <td className="num" style={{ padding: '10px 12px' }}>{r.rows}</td>
                <td style={{ padding: '10px 12px', color: 'var(--ink-3)' }}>{r.when}</td>
                <td style={{ padding: '10px 12px' }}>{r.by}</td>
                <td style={{ padding: '10px 12px' }}>
                  <UploadStatusBadge status={r.status} errorMsg={r.errorMsg} />
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </Modal>

      {/* Upload detail modal */}
      <UploadDetailModal
        uploadId={detailUploadId}
        onClose={() => setDetailUploadId(null)}
        onReprocess={refreshHistory}
      />
    </SettingsPageShell>
  );
};

const PRESET_COLORS = ['#2d3a8c','#d97b2e','#1f7a4d','#6b4a8a','#3a6b8a','#a8553a','#b8492f','#1f8a8a','#5a7a3a','#8a3a6b'];
const slugify = s => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');

const GroupSettingView = () => {
  const [draft, setDraft] = useState([]);
  const [allProducts, setAllProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [selectedGroup, setSelectedGroup] = useState(null);
  const [dirty, setDirty] = useState(false);
  const [showProductPicker, setShowProductPicker] = useState(false);
  const [productSearch, setProductSearch] = useState('');
  const [productWorking, setProductWorking] = useState(false);
  // New group modal
  const [showNewGroup, setShowNewGroup] = useState(false);
  const [newGroup, setNewGroup] = useState({ group_id: '', name: '', color: PRESET_COLORS[0] });
  const [creating, setCreating] = useState(false);
  // Delete confirm
  const [confirmDelete, setConfirmDelete] = useState(null);

  const reload = () => Promise.all([
    authFetch('/api/groups/config').then(r => r.json()),
    authFetch('/api/products').then(r => r.json()),
  ]).then(([gd, pd]) => {
    const mapped = (gd.groups || []).map(g => ({
      id: g.group_id, name: g.group_name,
      color: g.group_color || '#2d3a8c',
      products: g.products || [],
    }));
    setDraft(mapped);
    setAllProducts(pd.products || []);
    return mapped;
  });

  useEffect(() => {
    reload()
      .then(mapped => setSelectedGroup(s => s || mapped[0]?.id || null))
      .catch(() => showToast('ไม่สามารถโหลดข้อมูล group ได้', { variant: 'error' }))
      .finally(() => setLoading(false));
  }, []);

  const group = draft.find(g => g.id === selectedGroup);
  const color = group?.color || '#2d3a8c';

  const updateGroup = patch => {
    setDraft(d => d.map(g => g.id === selectedGroup ? { ...g, ...patch } : g));
    setDirty(true);
  };

  const saveGroup = async () => {
    if (!group) return;
    setSaving(true);
    try {
      const r = await authFetch(`/api/groups/${group.id}`, {
        method: 'PUT', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: group.name, color: group.color }),
      });
      if (!r.ok) throw new Error((await r.json()).error);
      setDirty(false);
      showToast('บันทึกแล้ว', { variant: 'success', detail: group.name });
    } catch (e) {
      showToast('บันทึกไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally { setSaving(false); }
  };

  const createGroup = async () => {
    if (!newGroup.name.trim() || !newGroup.group_id.trim()) return;
    setCreating(true);
    try {
      const r = await authFetch('/api/groups', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ group_id: newGroup.group_id, name: newGroup.name, color: newGroup.color }),
      });
      if (!r.ok) throw new Error((await r.json()).error);
      const mapped = await reload();
      setSelectedGroup(newGroup.group_id);
      setShowNewGroup(false);
      setNewGroup({ group_id: '', name: '', color: PRESET_COLORS[0] });
      showToast('สร้าง group แล้ว', { variant: 'success', detail: newGroup.name });
    } catch (e) {
      showToast('สร้างไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally { setCreating(false); }
  };

  const deleteGroup = async (g) => {
    try {
      const r = await authFetch(`/api/groups/${g.id}`, { method: 'DELETE' });
      if (!r.ok) throw new Error((await r.json()).error);
      const mapped = await reload();
      setSelectedGroup(mapped[0]?.id || null);
      setConfirmDelete(null);
      showToast('ลบ group แล้ว', { variant: 'success', detail: g.name });
    } catch (e) {
      showToast('ลบไม่สำเร็จ', { variant: 'error', detail: e.message });
    }
  };

  const addProduct = async (p) => {
    setProductWorking(true);
    try {
      const r = await authFetch(`/api/groups/${group.id}/products`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ product_id: p.id }),
      });
      if (!r.ok) throw new Error((await r.json()).error);
      await reload();
      showToast('เพิ่ม product แล้ว', { variant: 'success', detail: p.display_name });
    } catch (e) {
      showToast('เพิ่มไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally { setProductWorking(false); }
  };

  const removeProduct = async (p) => {
    setProductWorking(true);
    try {
      const r = await authFetch(`/api/groups/${group.id}/products/${p.id}`, { method: 'DELETE' });
      if (!r.ok) throw new Error((await r.json()).error);
      await reload();
      showToast('นำออกแล้ว', { variant: 'success', detail: p.display_name });
    } catch (e) {
      showToast('นำออกไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally { setProductWorking(false); }
  };

  // Products not yet in this group
  const assignedIds = new Set((group?.products || []).map(p => p.id));
  const availableProducts = allProducts.filter(p =>
    !assignedIds.has(p.id) &&
    (productSearch === '' ||
      (p.display_name || p.product_sub_cat).toLowerCase().includes(productSearch.toLowerCase()) ||
      p.product_cat.toLowerCase().includes(productSearch.toLowerCase()))
  );

  if (loading) return <div style={{ padding: 40, color: 'var(--ink-3)' }}>กำลังโหลด...</div>;

  return (
    <SettingsPageShell
      title="Group setting"
      subtitle="จัดการ Product Group, ชื่อ, สี และ product mapping"
      actions={<>
        <button onClick={() => { setShowNewGroup(true); setNewGroup({ group_id: '', name: '', color: PRESET_COLORS[0] }); }}
          style={{ display: 'flex', alignItems: 'center', gap: 5, padding: '6px 12px', background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, cursor: 'pointer' }}>
          <Icon name="plus" size={11} /> New group
        </button>
        {dirty && (
          <button onClick={saveGroup} disabled={saving}
            style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 14px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, cursor: 'pointer', border: 'none' }}>
            {saving ? 'กำลังบันทึก...' : 'Save changes'}
          </button>
        )}
      </>}>

      <div style={{ display: 'grid', gridTemplateColumns: '240px 1fr', gap: 20 }}>
        {/* Groups list */}
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 4 }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div className="serif" style={{ fontSize: 13, fontWeight: 500 }}>Groups</div>
            <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{draft.length}</span>
          </div>
          {draft.length === 0 && (
            <div style={{ padding: '24px 16px', textAlign: 'center', fontSize: 12, color: 'var(--ink-3)' }}>ยังไม่มี group</div>
          )}
          {draft.map(g => (
            <div key={g.id} onClick={() => { setSelectedGroup(g.id); setShowProductPicker(false); setProductSearch(''); setDirty(false); }}
              style={{ padding: '10px 14px', borderBottom: '1px solid var(--line-2)', cursor: 'pointer',
                background: selectedGroup === g.id ? '#fbf6ec' : 'transparent',
                borderLeft: `3px solid ${selectedGroup === g.id ? g.color : 'transparent'}`,
                display: 'flex', alignItems: 'center', gap: 10 }}
              onMouseEnter={e => selectedGroup !== g.id && (e.currentTarget.style.background = '#fafaf8')}
              onMouseLeave={e => selectedGroup !== g.id && (e.currentTarget.style.background = 'transparent')}>
              <div style={{ width: 10, height: 10, background: g.color, borderRadius: 2, flexShrink: 0 }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{g.name}</div>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{g.products.length} products</div>
              </div>
              <button onClick={e => { e.stopPropagation(); setConfirmDelete(g); }}
                style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ink-3)', fontSize: 12, padding: '2px 4px', borderRadius: 2, lineHeight: 1 }}
                title="ลบ group">✕</button>
            </div>
          ))}
        </div>

        {/* Group editor */}
        {!group ? (
          <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-3)', fontSize: 13 }}>
            เลือก group หรือสร้าง group ใหม่
          </div>
        ) : (
          <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 4 }}>
            {/* Header */}
            <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'flex-start', gap: 14 }}>
              <div style={{ width: 4, height: 44, background: color, borderRadius: 2, flexShrink: 0 }} />
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4 }}>
                  ID: <span style={{ fontFamily: 'IBM Plex Mono' }}>{group.id}</span>
                </div>
                <input value={group.name} onChange={e => updateGroup({ name: e.target.value })}
                  style={{ fontSize: 20, fontWeight: 500, letterSpacing: '-0.01em', border: 'none', outline: 'none', background: 'transparent', fontFamily: 'Kanit', width: '100%', padding: 0, color: 'var(--ink)' }} />
                {dirty && <div style={{ fontSize: 11, color: 'var(--accent-2)', marginTop: 2 }}>มีการเปลี่ยนแปลง — กด Save changes เพื่อบันทึก</div>}
              </div>
            </div>

            {/* Color picker */}
            <div style={{ padding: '16px 24px', borderBottom: '1px solid var(--line)' }}>
              <label style={{ display: 'block', fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Brand color</label>
              <div style={{ display: 'flex', gap: 7, alignItems: 'center', flexWrap: 'wrap' }}>
                {PRESET_COLORS.map(c => (
                  <div key={c} onClick={() => updateGroup({ color: c })} title={c}
                    style={{ width: 24, height: 24, background: c, border: c === color ? '3px solid var(--ink)' : '2px solid transparent', borderRadius: 3, cursor: 'pointer' }} />
                ))}
                <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginLeft: 4 }}>
                  <div style={{ width: 24, height: 24, background: color, borderRadius: 3, border: '1px solid var(--line)' }} />
                  <input type="text" value={group.color}
                    onChange={e => /^#[0-9a-fA-F]{0,6}$/.test(e.target.value) && updateGroup({ color: e.target.value })}
                    style={{ width: 78, padding: '3px 7px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, fontFamily: 'IBM Plex Mono' }} />
                </div>
              </div>
            </div>

            {/* Products in this group */}
            <div style={{ padding: '16px 24px' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
                  Products ({group.products.length})
                </div>
                <div style={{ position: 'relative' }}>
                  <button onClick={() => { setShowProductPicker(v => !v); setProductSearch(''); }} disabled={productWorking}
                    style={{ display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', fontSize: 11.5, background: color + '18', color, border: `1px solid ${color}50`, borderRadius: 3, cursor: 'pointer', fontWeight: 500 }}>
                    <Icon name="plus" size={10} /> เพิ่ม product
                  </button>
                  {showProductPicker && (
                    <div style={{ position: 'absolute', right: 0, top: 'calc(100% + 4px)', zIndex: 50, background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 4, boxShadow: '0 4px 20px rgba(0,0,0,0.13)', width: 340 }}>
                      <div style={{ padding: 8, borderBottom: '1px solid var(--line)' }}>
                        <input autoFocus type="text" placeholder="ค้นหา product…" value={productSearch}
                          onChange={e => setProductSearch(e.target.value)}
                          style={{ width: '100%', padding: '5px 8px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, boxSizing: 'border-box', outline: 'none' }} />
                      </div>
                      <div style={{ maxHeight: 300, overflowY: 'auto' }}>
                        {availableProducts.length === 0 && (
                          <div style={{ padding: '14px 16px', fontSize: 12, color: 'var(--ink-3)' }}>ไม่พบ product ที่ยังไม่ได้อยู่ใน group นี้</div>
                        )}
                        {availableProducts.map(p => {
                          const pGrp = draft.find(g => g.id === p.group_id);
                          return (
                            <div key={p.id} onClick={() => { addProduct(p); setShowProductPicker(false); }}
                              style={{ padding: '8px 14px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}
                              onMouseEnter={e => e.currentTarget.style.background = 'var(--bg)'}
                              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                              <div style={{ flex: 1, minWidth: 0 }}>
                                <div style={{ fontSize: 12.5, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.display_name}</div>
                                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', fontFamily: 'IBM Plex Mono' }}>{p.product_cat}</div>
                              </div>
                              {pGrp ? (
                                <span style={{ fontSize: 10, padding: '1px 6px', background: pGrp.color + '20', color: pGrp.color, border: `1px solid ${pGrp.color}40`, borderRadius: 8, flexShrink: 0 }}>{pGrp.name}</span>
                              ) : (
                                <span style={{ fontSize: 10, color: 'var(--ink-3)', flexShrink: 0 }}>ไม่มี group</span>
                              )}
                            </div>
                          );
                        })}
                      </div>
                    </div>
                  )}
                </div>
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {group.products.map(p => (
                  <span key={p.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12, padding: '4px 8px 4px 11px', background: color + '15', color, border: `1px solid ${color}35`, borderRadius: 14 }}>
                    <span style={{ maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.display_name}</span>
                    <button onClick={() => removeProduct(p)} disabled={productWorking}
                      style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 14, height: 14, background: color + '30', border: 'none', borderRadius: '50%', cursor: 'pointer', color, fontSize: 10, padding: 0, flexShrink: 0 }}>
                      ✕
                    </button>
                  </span>
                ))}
                {group.products.length === 0 && (
                  <div style={{ fontSize: 12, color: 'var(--ink-3)', fontStyle: 'italic' }}>ยังไม่มี product ใน group นี้ — กด "เพิ่ม product" เพื่อเพิ่ม</div>
                )}
              </div>
            </div>
          </div>
        )}
      </div>

      {/* New group modal */}
      <Modal open={showNewGroup} onClose={() => setShowNewGroup(false)} title="สร้าง Group ใหม่" width={440}
        footer={<>
          <button onClick={() => setShowNewGroup(false)} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, background: 'var(--panel)', cursor: 'pointer' }}>ยกเลิก</button>
          <button onClick={createGroup} disabled={creating || !newGroup.name.trim() || !newGroup.group_id.trim()}
            style={{ padding: '7px 16px', background: newGroup.name.trim() ? 'var(--ink)' : 'var(--line)', color: newGroup.name.trim() ? '#fff' : 'var(--ink-3)', border: 'none', borderRadius: 3, fontSize: 12, fontWeight: 500, cursor: newGroup.name.trim() ? 'pointer' : 'default' }}>
            {creating ? 'กำลังสร้าง...' : 'สร้าง Group'}
          </button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div>
            <label style={{ display: 'block', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>ชื่อ Group</label>
            <input autoFocus value={newGroup.name} placeholder="เช่น Cloud Services"
              onChange={e => setNewGroup(g => ({ ...g, name: e.target.value, group_id: slugify(e.target.value) }))}
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 13, boxSizing: 'border-box' }} />
          </div>
          <div>
            <label style={{ display: 'block', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Group ID <span style={{ textTransform: 'none', fontWeight: 400 }}>(ใช้ภายใน ไม่สามารถเปลี่ยนภายหลัง)</span></label>
            <input value={newGroup.group_id} placeholder="เช่น cloud-services"
              onChange={e => setNewGroup(g => ({ ...g, group_id: e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '') }))}
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, fontFamily: 'IBM Plex Mono', boxSizing: 'border-box' }} />
          </div>
          <div>
            <label style={{ display: 'block', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Brand color</label>
            <div style={{ display: 'flex', gap: 7, alignItems: 'center', flexWrap: 'wrap' }}>
              {PRESET_COLORS.map(c => (
                <div key={c} onClick={() => setNewGroup(g => ({ ...g, color: c }))} title={c}
                  style={{ width: 24, height: 24, background: c, border: c === newGroup.color ? '3px solid var(--ink)' : '2px solid transparent', borderRadius: 3, cursor: 'pointer' }} />
              ))}
              <div style={{ width: 24, height: 24, background: newGroup.color, borderRadius: 3, border: '1px solid var(--line)', marginLeft: 4 }} />
            </div>
          </div>
        </div>
      </Modal>

      {/* Delete confirm */}
      <Modal open={!!confirmDelete} onClose={() => setConfirmDelete(null)}
        title={`ลบ Group "${confirmDelete?.name}"?`}
        subtitle={confirmDelete?.products?.length ? `Products ทั้ง ${confirmDelete.products.length} รายการใน group นี้จะถูก unassign` : 'Group นี้ยังไม่มี product'}
        width={420}
        footer={<>
          <button onClick={() => setConfirmDelete(null)} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, background: 'var(--panel)', cursor: 'pointer' }}>ยกเลิก</button>
          <button onClick={() => deleteGroup(confirmDelete)}
            style={{ padding: '7px 14px', background: '#c0392b', color: '#fff', border: 'none', borderRadius: 3, fontSize: 12, fontWeight: 500, cursor: 'pointer' }}>
            ลบ Group
          </button>
        </>}>
        <div style={{ fontSize: 13, color: 'var(--ink-2)' }}>การกระทำนี้ไม่สามารถยกเลิกได้</div>
      </Modal>
    </SettingsPageShell>
  );
};

// ============================================================
// Product Setting View — display name per product_sub_cat
// ============================================================
const EMPTY_NEW_PRODUCT = { product_cat: '', product_sub_cat: '', display_name: '', group_id: '' };

const ProductSettingView = () => {
  const [products, setProducts] = useState([]);
  const [groups, setGroups] = useState([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState({});
  // edits: { [productId]: { display_name?, group_id? } }
  const [edits, setEdits] = useState({});
  const [filterGroup, setFilterGroup] = useState('all');
  const [search, setSearch] = useState('');
  // Create
  const [showAdd, setShowAdd] = useState(false);
  const [newProd, setNewProd] = useState(EMPTY_NEW_PRODUCT);
  const [creating, setCreating] = useState(false);
  // Delete confirm: productId waiting confirmation
  const [confirmDelete, setConfirmDelete] = useState(null);
  const [deleting, setDeleting] = useState({});

  useEffect(() => {
    Promise.all([
      authFetch('/api/products').then(r => r.json()),
      authFetch('/api/groups/config').then(r => r.json()),
    ]).then(([pd, gd]) => {
      setProducts(pd.products || []);
      setGroups(gd.groups || []);
    }).catch(() => showToast('ไม่สามารถโหลดข้อมูลได้', { variant: 'error' }))
      .finally(() => setLoading(false));
  }, []);

  const getField = (id, field, fallback) =>
    (id in edits && field in edits[id]) ? edits[id][field] : fallback;

  const setField = (id, field, value) =>
    setEdits(ed => ({ ...ed, [id]: { ...ed[id], [field]: value } }));

  const isRowDirty = (p) => {
    if (!(p.id in edits)) return false;
    const e = edits[p.id];
    return ('display_name' in e && e.display_name !== p.display_name) ||
           ('group_id' in e && e.group_id !== (p.group_id ?? ''));
  };

  const dirtyProducts = products.filter(isRowDirty);

  const saveProduct = async (p) => {
    setSaving(s => ({ ...s, [p.id]: true }));
    try {
      const newName = getField(p.id, 'display_name', p.display_name);
      const newGroup = getField(p.id, 'group_id', p.group_id ?? '');
      const r = await authFetch(`/api/products/${p.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          display_name: newName,
          group_id: newGroup || null,
        }),
      });
      if (!r.ok) throw new Error((await r.json()).error);
      const grp = groups.find(g => g.group_id === newGroup);
      setProducts(ps => ps.map(x => x.id === p.id ? {
        ...x,
        display_name: newName,
        has_custom_name: newName !== x.product_sub_cat,
        group_id: newGroup || null,
        group_name: grp?.group_name || null,
        group_color: grp?.group_color || null,
      } : x));
      setEdits(e => { const n = { ...e }; delete n[p.id]; return n; });
      showToast('บันทึกแล้ว', { variant: 'success', detail: newName });
    } catch (e) {
      showToast('บันทึกไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally {
      setSaving(s => { const n = { ...s }; delete n[p.id]; return n; });
    }
  };

  const resetProduct = (p) =>
    setEdits(e => { const n = { ...e }; delete n[p.id]; return n; });

  const saveAll = async () => {
    for (const p of dirtyProducts) await saveProduct(p);
  };

  const createProduct = async () => {
    if (!newProd.product_cat.trim() || !newProd.product_sub_cat.trim()) {
      showToast('กรุณากรอก Category และ Product name', { variant: 'error' }); return;
    }
    setCreating(true);
    try {
      const r = await authFetch('/api/products', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newProd),
      });
      const data = await r.json();
      if (!r.ok) throw new Error(data.error);
      const grp = groups.find(g => g.group_id === newProd.group_id);
      setProducts(ps => [{
        id: data.id,
        product_cat: newProd.product_cat.trim(),
        product_sub_cat: newProd.product_sub_cat.trim(),
        display_name: newProd.display_name.trim() || newProd.product_sub_cat.trim(),
        has_custom_name: !!newProd.display_name.trim(),
        group_id: newProd.group_id || null,
        group_name: grp?.group_name || null,
        group_color: grp?.group_color || null,
      }, ...ps]);
      setNewProd(EMPTY_NEW_PRODUCT);
      setShowAdd(false);
      showToast('เพิ่ม product แล้ว', { variant: 'success', detail: newProd.product_sub_cat.trim() });
    } catch (e) {
      showToast('เพิ่มไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally {
      setCreating(false);
    }
  };

  const deleteProduct = async (p) => {
    setDeleting(d => ({ ...d, [p.id]: true }));
    try {
      const r = await authFetch(`/api/products/${p.id}`, { method: 'DELETE' });
      if (!r.ok) throw new Error((await r.json()).error);
      setProducts(ps => ps.filter(x => x.id !== p.id));
      setConfirmDelete(null);
      showToast('ลบ product แล้ว', { variant: 'success', detail: p.display_name || p.product_sub_cat });
    } catch (e) {
      showToast('ลบไม่สำเร็จ', { variant: 'error', detail: e.message });
    } finally {
      setDeleting(d => { const n = { ...d }; delete n[p.id]; return n; });
    }
  };

  const filtered = products.filter(p => {
    if (filterGroup === 'none' && p.group_id) return false;
    if (filterGroup !== 'all' && filterGroup !== 'none' && p.group_id !== filterGroup) return false;
    if (search) {
      const q = search.toLowerCase();
      return p.product_sub_cat.toLowerCase().includes(q) ||
             (p.display_name || '').toLowerCase().includes(q) ||
             (p.product_cat || '').toLowerCase().includes(q);
    }
    return true;
  });

  return (
    <SettingsPageShell
      title="Product setting"
      subtitle="ตั้งชื่อแสดงผลและ group สำหรับแต่ละ product (product_sub_cat)"
      actions={<>
        {dirtyProducts.length > 0 && (
          <button onClick={saveAll} style={{ padding: '6px 14px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, cursor: 'pointer', border: 'none' }}>
            บันทึก {dirtyProducts.length} รายการ
          </button>
        )}
        <button onClick={() => { setShowAdd(s => !s); setNewProd(EMPTY_NEW_PRODUCT); }}
          style={{ padding: '6px 14px', background: showAdd ? 'var(--line)' : 'var(--accent)', color: showAdd ? 'var(--ink)' : '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, cursor: 'pointer', border: 'none' }}>
          {showAdd ? '✕ ยกเลิก' : '+ เพิ่ม Product'}
        </button>
      </>}>

      {/* Filters */}
      <div style={{ display: 'flex', gap: 10, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', gap: 0, border: '1px solid var(--line)', borderRadius: 3, overflow: 'hidden' }}>
          <button onClick={() => setFilterGroup('all')} style={{ padding: '5px 12px', fontSize: 12, background: filterGroup === 'all' ? 'var(--ink)' : 'var(--panel)', color: filterGroup === 'all' ? '#fff' : 'var(--ink-2)', border: 'none', cursor: 'pointer' }}>
            ทั้งหมด ({products.length})
          </button>
          {groups.map(g => (
            <button key={g.group_id} onClick={() => setFilterGroup(g.group_id)}
              style={{ padding: '5px 12px', fontSize: 12, background: filterGroup === g.group_id ? g.group_color : 'var(--panel)', color: filterGroup === g.group_id ? '#fff' : 'var(--ink-2)', border: 'none', borderLeft: '1px solid var(--line)', cursor: 'pointer' }}>
              {g.group_name} ({products.filter(p => p.group_id === g.group_id).length})
            </button>
          ))}
          <button onClick={() => setFilterGroup('none')}
            style={{ padding: '5px 12px', fontSize: 12, background: filterGroup === 'none' ? '#666' : 'var(--panel)', color: filterGroup === 'none' ? '#fff' : 'var(--ink-2)', border: 'none', borderLeft: '1px solid var(--line)', cursor: 'pointer' }}>
            ไม่มี group ({products.filter(p => !p.group_id).length})
          </button>
        </div>
        <input type="text" placeholder="ค้นหา product…" value={search}
          onChange={e => setSearch(e.target.value)}
          style={{ padding: '5px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, width: 200 }}
        />
        <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{filtered.length} รายการ</span>
      </div>

      {/* Table */}
      {loading ? (
        <div style={{ padding: 40, color: 'var(--ink-3)', textAlign: 'center' }}>กำลังโหลด…</div>
      ) : (
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 4, overflow: 'hidden' }}>
          {/* Column headers */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 200px 110px', padding: '8px 16px', background: 'var(--bg)', borderBottom: '1px solid var(--line)', fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>
            <div>Product (product_sub_cat)</div>
            <div>ชื่อแสดงผล</div>
            <div>Group</div>
            <div></div>
          </div>

          {/* Inline create form */}
          {showAdd && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 200px 110px', padding: '10px 16px', borderBottom: '2px solid var(--accent)', alignItems: 'center', background: '#f0f4ff', gap: 0 }}>
              {/* product_cat + product_sub_cat */}
              <div style={{ paddingRight: 12, display: 'flex', flexDirection: 'column', gap: 4 }}>
                <input value={newProd.product_sub_cat} onChange={e => setNewProd(n => ({ ...n, product_sub_cat: e.target.value }))}
                  placeholder="Product name *" autoFocus
                  onKeyDown={e => { if (e.key === 'Enter') createProduct(); if (e.key === 'Escape') setShowAdd(false); }}
                  style={{ width: '100%', padding: '4px 8px', border: '1px solid var(--accent)', borderRadius: 3, fontSize: 12.5, boxSizing: 'border-box', outline: 'none' }} />
                <input value={newProd.product_cat} onChange={e => setNewProd(n => ({ ...n, product_cat: e.target.value }))}
                  placeholder="Category (product_cat) *"
                  onKeyDown={e => { if (e.key === 'Enter') createProduct(); if (e.key === 'Escape') setShowAdd(false); }}
                  style={{ width: '100%', padding: '3px 8px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 11, boxSizing: 'border-box', outline: 'none' }} />
              </div>
              {/* display_name */}
              <div style={{ paddingRight: 12 }}>
                <input value={newProd.display_name} onChange={e => setNewProd(n => ({ ...n, display_name: e.target.value }))}
                  placeholder="ชื่อแสดงผล (ถ้าไม่กรอก ใช้ Product name)"
                  onKeyDown={e => { if (e.key === 'Enter') createProduct(); if (e.key === 'Escape') setShowAdd(false); }}
                  style={{ width: '100%', padding: '4px 8px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12.5, boxSizing: 'border-box', outline: 'none' }} />
              </div>
              {/* group */}
              <div style={{ paddingRight: 8 }}>
                <select value={newProd.group_id} onChange={e => setNewProd(n => ({ ...n, group_id: e.target.value }))}
                  style={{ width: '100%', padding: '4px 6px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, outline: 'none', cursor: 'pointer' }}>
                  <option value="">— ไม่ระบุ group —</option>
                  {groups.map(g => <option key={g.group_id} value={g.group_id}>{g.group_name}</option>)}
                </select>
              </div>
              {/* confirm/cancel */}
              <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end' }}>
                <button onClick={createProduct} disabled={creating}
                  style={{ padding: '4px 10px', fontSize: 12, background: 'var(--accent)', color: '#fff', border: 'none', borderRadius: 3, cursor: 'pointer', fontWeight: 600 }}>
                  {creating ? '…' : 'เพิ่ม'}
                </button>
                <button onClick={() => setShowAdd(false)}
                  style={{ padding: '4px 8px', fontSize: 12, background: 'transparent', color: 'var(--ink-3)', border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer' }}>✕</button>
              </div>
            </div>
          )}

          {filtered.length === 0 && !showAdd && (
            <div style={{ padding: '32px 16px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 13 }}>ไม่พบข้อมูล</div>
          )}
          {filtered.map((p, i) => {
            const curName = getField(p.id, 'display_name', p.display_name);
            const curGroup = getField(p.id, 'group_id', p.group_id ?? '');
            const dirty = isRowDirty(p);
            const grpInfo = groups.find(g => g.group_id === curGroup);
            const isConfirming = confirmDelete === p.id;
            return (
              <div key={p.id} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 200px 110px', padding: '8px 16px', borderBottom: i < filtered.length - 1 ? '1px solid var(--line-2)' : 'none', alignItems: 'center', background: dirty ? '#fffbf0' : isConfirming ? '#fff5f5' : 'transparent' }}>
                {/* product_sub_cat — read-only */}
                <div style={{ fontSize: 12, color: 'var(--ink-2)', fontFamily: 'IBM Plex Mono', paddingRight: 12 }}>
                  <div>{p.product_sub_cat}</div>
                  <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{p.product_cat}</div>
                </div>
                {/* display_name — editable */}
                <div style={{ paddingRight: 12 }}>
                  <input
                    value={curName}
                    onChange={e => setField(p.id, 'display_name', e.target.value)}
                    onKeyDown={e => { if (e.key === 'Enter') saveProduct(p); if (e.key === 'Escape') resetProduct(p); }}
                    style={{ width: '100%', padding: '4px 8px', border: `1px solid ${'display_name' in (edits[p.id] || {}) && edits[p.id].display_name !== p.display_name ? 'var(--accent-2)' : 'var(--line)'}`, borderRadius: 3, fontSize: 12.5, background: 'transparent', outline: 'none', boxSizing: 'border-box' }}
                    placeholder={p.product_sub_cat}
                  />
                </div>
                {/* Group — editable dropdown */}
                <div style={{ paddingRight: 8 }}>
                  <select
                    value={curGroup}
                    onChange={e => setField(p.id, 'group_id', e.target.value)}
                    style={{ width: '100%', padding: '4px 6px', border: `1px solid ${'group_id' in (edits[p.id] || {}) && edits[p.id].group_id !== (p.group_id ?? '') ? 'var(--accent-2)' : 'var(--line)'}`, borderRadius: 3, fontSize: 12, background: grpInfo ? grpInfo.group_color + '18' : 'transparent', color: grpInfo?.group_color || 'var(--ink-3)', outline: 'none', cursor: 'pointer' }}>
                    <option value="">— ไม่ระบุ group —</option>
                    {groups.map(g => (
                      <option key={g.group_id} value={g.group_id}>{g.group_name}</option>
                    ))}
                  </select>
                </div>
                {/* Actions: save/reset if dirty, delete button always */}
                <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end', alignItems: 'center' }}>
                  {dirty && (
                    <>
                      <button onClick={() => saveProduct(p)} disabled={saving[p.id]}
                        style={{ padding: '3px 8px', fontSize: 11, background: 'var(--ink)', color: '#fff', border: 'none', borderRadius: 3, cursor: 'pointer' }}>
                        {saving[p.id] ? '…' : 'Save'}
                      </button>
                      <button onClick={() => resetProduct(p)}
                        style={{ padding: '3px 6px', fontSize: 11, background: 'transparent', color: 'var(--ink-3)', border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer' }}>✕</button>
                    </>
                  )}
                  {!dirty && !isConfirming && (
                    <button onClick={() => setConfirmDelete(p.id)} title="ลบ product นี้"
                      style={{ padding: '3px 7px', fontSize: 12, background: 'transparent', color: 'var(--ink-3)', border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer', lineHeight: 1 }}>🗑</button>
                  )}
                  {isConfirming && (
                    <>
                      <span style={{ fontSize: 11, color: '#c0392b', fontWeight: 600 }}>ลบ?</span>
                      <button onClick={() => deleteProduct(p)} disabled={deleting[p.id]}
                        style={{ padding: '3px 8px', fontSize: 11, background: '#c0392b', color: '#fff', border: 'none', borderRadius: 3, cursor: 'pointer', fontWeight: 600 }}>
                        {deleting[p.id] ? '…' : 'ยืนยัน'}
                      </button>
                      <button onClick={() => setConfirmDelete(null)}
                        style={{ padding: '3px 6px', fontSize: 11, background: 'transparent', color: 'var(--ink-3)', border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer' }}>✕</button>
                    </>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </SettingsPageShell>
  );
};

const TargetSettingView = () => {
  const [year, setYear] = useState('2026');
  const [groups, setGroups] = useState([]);
  // targets: { "PRODUCT_CAT|product_sub_cat": { Q1, Q2, Q3, Q4 } }
  const [targets, setTargets] = useState({});
  const [locked, setLocked] = useState({ Q1: false, Q2: false, Q3: false, Q4: false });
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [showGrowth, setShowGrowth] = useState(false);
  const [growthPct, setGrowthPct] = useState(12);
  const [savedAt, setSavedAt] = useState(null);
  const [importing, setImporting] = useState(false);
  const importRef = React.useRef(null);

  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      const r = await authFetch(`/api/targets?year=${year}`);
      const data = await r.json();
      setGroups(data.groups || []);
      setTargets(data.targets || {});
      setLocked(data.locked || { Q1: false, Q2: false, Q3: false, Q4: false });
    } catch (e) {
      showToast('Failed to load targets', { variant: 'error' });
    } finally {
      setLoading(false);
    }
  }, [year]);

  React.useEffect(() => { reload(); }, [reload]);

  const productKey = (p) => `${p.product_cat}|${p.product_sub_cat}`;

  const setCell = (key, q, val) => {
    setTargets(t => ({ ...t, [key]: { ...t[key], [q]: val } }));
  };

  const annualOfKey = (key) =>
    ['Q1','Q2','Q3','Q4'].reduce((s, q) => s + Number(targets[key]?.[q] || 0), 0);

  const allProducts = groups.flatMap(g => g.products.map(p => ({ ...p, group_id: g.group_id })));
  const annualTotal = allProducts.reduce((s, p) => s + annualOfKey(productKey(p)), 0);

  const handleSave = async () => {
    setSaving(true);
    try {
      // Build payload: include group/cat/sub metadata per key
      const payload = {};
      groups.forEach(g => {
        g.products.forEach(p => {
          const key = productKey(p);
          payload[key] = {
            product_group: g.group_id,
            product_cat: p.product_cat,
            product_sub_cat: p.product_sub_cat,
            ...(targets[key] || {}),
          };
        });
      });
      const r = await authFetch('/api/targets', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ targets: payload, locked, year: parseInt(year) }),
      });
      if (!r.ok) throw new Error((await r.json()).error);
      setSavedAt('Just now');
      showToast('Targets saved & published', {
        variant: 'success',
        detail: `${allProducts.length} products · 4 quarters · ฿${(annualTotal / 1000)}B annual`,
      });
    } catch (e) {
      showToast('Save failed: ' + e.message, { variant: 'error' });
    } finally {
      setSaving(false);
    }
  };

  const handleImport = async (file) => {
    if (!file) return;
    setImporting(true);
    try {
      const fd = new FormData();
      fd.append('file', file);
      const r = await authFetch('/api/targets/import', { method: 'POST', body: fd });
      const data = await r.json();
      if (!r.ok) throw new Error(data.error || 'Import failed');
      showToast('นำเข้าข้อมูลสำเร็จ', { variant: 'success', detail: `${data.upserted} rows updated · ${file.name}` });
      reload();
    } catch (e) {
      showToast('Import failed: ' + e.message, { variant: 'error' });
    } finally {
      setImporting(false);
      if (importRef.current) importRef.current.value = '';
    }
  };

  const applyGrowth = () => {
    const factor = 1 + growthPct / 100;
    setTargets(t => {
      const next = { ...t };
      allProducts.forEach(p => {
        const key = productKey(p);
        const updated = { ...t[key] };
        ['Q1','Q2','Q3','Q4'].forEach(q => {
          if (!locked[q]) updated[q] = (updated[q] || 0) * factor;
        });
        next[key] = updated;
      });
      return next;
    });
    setShowGrowth(false);
    showToast(`Applied ${growthPct >= 0 ? '+' : ''}${growthPct}% to unlocked quarters`, { variant: 'success' });
  };

  const inputStyle = (isLocked) => ({
    display: 'inline-block', textAlign: 'right',
    padding: '5px 8px',
    background: isLocked ? '#f1eee5' : 'var(--panel)',
    border: `1px solid ${isLocked ? 'var(--line-2)' : 'var(--line)'}`,
    borderRadius: 3, width: 100,
    color: isLocked ? 'var(--ink-3)' : 'var(--ink)',
    fontFamily: 'IBM Plex Mono', fontSize: 12,
    cursor: isLocked ? 'not-allowed' : 'text',
    outline: 'none',
  });

  return (
    <SettingsPageShell
      title="Target setting"
      subtitle={`กำหนดเป้าหมาย Revenue รายผลิตภัณฑ์ แยกตามไตรมาส · ปีงบประมาณ ${year}`}
      actions={<>
        <Segmented options={['2025', '2026', '2027']} value={year} onChange={(v) => setYear(v)} />
        <input ref={importRef} type="file" accept=".xlsx,.xls,.csv" style={{ display: 'none' }} onChange={e => handleImport(e.target.files[0])} />
        <a href="/api/upload/template/targets" download="targets_template.xlsx"
          style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)', textDecoration: 'none' }}>
          <Icon name="download" size={11} /> Download template
        </a>
        <button
          onClick={() => importRef.current?.click()}
          disabled={importing}
          style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)', opacity: importing ? 0.6 : 1 }}>
          <Icon name="arrowUp" size={11} /> {importing ? 'Importing…' : 'Load file'}
        </button>
        <button
          onClick={handleSave}
          disabled={saving}
          style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', background: 'var(--ink)', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, opacity: saving ? 0.6 : 1 }}>
          <Icon name="download" size={11} /> {saving ? 'Saving…' : 'Save & publish'}
        </button>
      </>}>

      {/* Summary cards */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1, background: 'var(--line)', border: '1px solid var(--line)', marginBottom: 24 }}>
        {[
          { l: 'Total annual target', v: loading ? '—' : `฿${(annualTotal / 1000)}B` },
          { l: 'Products with target', v: loading ? '—' : `${allProducts.filter(p => annualOfKey(productKey(p)) > 0).length} / ${allProducts.length}` },
          { l: 'Last saved', v: savedAt || '—', d: savedAt ? 'by Pim T.' : 'ยังไม่ได้บันทึก' },
        ].map((c, i) => (
          <div key={i} style={{ background: 'var(--panel)', padding: '16px 20px' }}>
            <div style={{ fontSize: 10.5, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 4 }}>{c.l}</div>
            <div className="num" style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.02em' }}>{c.v}</div>
            {c.d && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>{c.d}</div>}
          </div>
        ))}
      </div>

      {/* Editable grid */}
      <div style={{ background: 'var(--panel)', border: '1px solid var(--line)' }}>
        <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div>
            <div className="serif" style={{ fontSize: 16, fontWeight: 500 }}>Quarterly targets · {year} (THB millions)</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>คลิก cell เพื่อแก้ไข · คลิกหัวคอลัมน์เพื่อ lock/unlock</div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 11.5, color: 'var(--ink-2)' }}>
            <button onClick={() => setShowGrowth(true)} style={{ padding: '5px 10px', border: '1px solid var(--line)', borderRadius: 3 }}>Apply % growth</button>
          </div>
        </div>

        {loading ? (
          <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-3)', fontSize: 13 }}>Loading…</div>
        ) : (
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ background: '#fbfaf6', borderBottom: '2px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
                <th style={{ textAlign: 'left', padding: '12px 16px', fontWeight: 500, width: '32%' }}>Product</th>
                {['Q1', 'Q2', 'Q3', 'Q4'].map(q => (
                  <th key={q} style={{ textAlign: 'right', padding: '12px 12px', fontWeight: 500 }}>
                    <div>{q} {year}</div>
                    <button
                      onClick={() => { setLocked(l => ({ ...l, [q]: !l[q] })); showToast(`${q} ${locked[q] ? 'unlocked' : 'locked'}`, { variant: 'info' }); }}
                      style={{ fontWeight: 400, color: 'var(--ink-3)', marginTop: 2, textTransform: 'none', letterSpacing: 0, fontSize: 9, cursor: 'pointer', background: 'transparent', padding: '1px 4px', display: 'inline-flex', alignItems: 'center', gap: 3 }}>
                      {locked[q] ? '🔒 locked' : '○ unlocked'}
                    </button>
                  </th>
                ))}
                <th style={{ textAlign: 'right', padding: '12px 16px', fontWeight: 500 }}>Annual</th>
              </tr>
            </thead>
            <tbody>
              {groups.map(g => {
                if (g.products.length === 0) return null;
                const groupAnnual = g.products.reduce((s, p) => s + annualOfKey(productKey(p)), 0);
                return (
                  <React.Fragment key={g.group_id}>
                    {/* Group header row */}
                    <tr style={{ background: '#f7f5ef', borderTop: '2px solid var(--line)', borderBottom: '1px solid var(--line)' }}>
                      <td colSpan={6} style={{ padding: '8px 16px' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                          <div style={{ width: 4, height: 16, background: g.group_color, borderRadius: 2, flexShrink: 0 }} />
                          <span style={{ fontWeight: 600, fontSize: 12, color: 'var(--ink)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{g.group_name}</span>
                          <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{g.products.length} products</span>
                          <span style={{ marginLeft: 'auto', fontFamily: 'IBM Plex Mono', fontSize: 11.5, fontWeight: 600, color: 'var(--ink-2)' }}>
                            Annual: ฿{numFmt(groupAnnual,0)}M
                          </span>
                        </div>
                      </td>
                    </tr>
                    {/* Product rows */}
                    {g.products.map(p => {
                      const key = productKey(p);
                      const annual = annualOfKey(key);
                      return (
                        <tr key={key} style={{ borderBottom: '1px solid var(--line-2)' }}
                          onMouseEnter={e => e.currentTarget.style.background = '#fafaf8'}
                          onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                          <td style={{ padding: '9px 16px 9px 28px' }}>
                            <div style={{ fontWeight: 500, fontSize: 12.5 }}>{p.display_name}</div>
                            <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 1 }}>{p.product_cat} · {p.product_sub_cat}</div>
                          </td>
                          {['Q1','Q2','Q3','Q4'].map(q => {
                            const val = targets[key]?.[q] || 0;
                            const isLocked = locked[q];
                            return (
                              <td key={q} style={{ padding: '6px 8px', textAlign: 'right' }}>
                                <input
                                  type="number"
                                  value={val || ''}
                                  placeholder="0"
                                  step="any"
                                  disabled={isLocked}
                                  onChange={e => setCell(key, q, e.target.value === '' ? 0 : parseFloat(e.target.value))}
                                  style={inputStyle(isLocked)}
                                  onFocus={e => !isLocked && (e.target.style.borderColor = 'var(--ink)')}
                                  onBlur={e => e.target.style.borderColor = isLocked ? 'var(--line-2)' : 'var(--line)'}
                                />
                              </td>
                            );
                          })}
                          <td className="num" style={{ padding: '9px 16px', textAlign: 'right', fontWeight: 600, fontSize: 12.5 }}>
                            {annual > 0 ? numFmt(annual,0) : <span style={{ color: 'var(--ink-3)' }}>—</span>}
                          </td>
                        </tr>
                      );
                    })}
                  </React.Fragment>
                );
              })}
            </tbody>
            <tfoot>
              <tr style={{ background: '#fbfaf6', borderTop: '2px solid var(--line)' }}>
                <td style={{ padding: '12px 16px', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 600 }}>Grand Total</td>
                {['Q1','Q2','Q3','Q4'].map(q => (
                  <td key={q} className="num" style={{ padding: '12px 12px', textAlign: 'right', fontWeight: 700 }}>
                    ฿{allProducts.reduce((s, p) => s + Number(targets[productKey(p)]?.[q] || 0), 0, 0)}M
                  </td>
                ))}
                <td className="num" style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 700 }}>
                  ฿{numFmt(annualTotal,0)}M
                </td>
              </tr>
            </tfoot>
          </table>
        )}
      </div>

      <div style={{ marginTop: 16, padding: 14, background: '#fbf6ec', borderLeft: '3px solid var(--accent-2)', fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.5, display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <Icon name="info" size={14} />
        <div>
          <strong style={{ color: 'var(--ink)' }}>Note:</strong> เป้าหมายตั้งได้รายผลิตภัณฑ์ตาม Group setting · คลิกหัวคอลัมน์เพื่อ lock/unlock · ตัวเลข Annual ใช้ในการคำนวณ Pacing บนหน้า Overview
        </div>
      </div>

      {/* Apply growth modal */}
      <Modal
        open={showGrowth}
        onClose={() => setShowGrowth(false)}
        title="Apply percentage growth"
        subtitle="ปรับเป้าหมายของทุก quarter ที่ unlock โดยอัตรา % ที่กำหนด"
        width={420}
        footer={<>
          <button onClick={() => setShowGrowth(false)} style={{ padding: '8px 14px', border: '1px solid var(--line)', fontSize: 12, borderRadius: 3 }}>Cancel</button>
          <button onClick={applyGrowth} style={{ padding: '8px 14px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3 }}>Apply {growthPct >= 0 ? '+' : ''}{growthPct}%</button>
        </>}>
        <div>
          <label style={{ display: 'block', fontSize: 11, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 10 }}>Growth rate</label>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
            <input type="range" min={-20} max={50} value={growthPct} onChange={e => setGrowthPct(Number(e.target.value))} style={{ flex: 1, accentColor: 'var(--accent)' }} />
            <div className="num" style={{ fontSize: 28, fontWeight: 500, color: growthPct >= 0 ? 'var(--positive)' : 'var(--negative)', minWidth: 80, textAlign: 'right' }}>
              {growthPct > 0 ? '+' : ''}{growthPct}%
            </div>
          </div>
          <div style={{ marginTop: 16, padding: 12, background: '#fbfaf6', borderRadius: 3, fontSize: 11.5, color: 'var(--ink-2)' }}>
            ใช้กับ <strong>{Object.values(locked).filter(v => !v).length} quarters</strong> ที่ unlock
          </div>
        </div>
      </Modal>
    </SettingsPageShell>
  );
};

// ============================================================
// Root App
// ============================================================
// ============================================================
// Login Page
// ============================================================
const LoginPage = ({ onLogin }) => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [showPw, setShowPw] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!username || !password) { setError('กรุณากรอก Username และ Password'); return; }
    setLoading(true); setError('');
    try {
      const user = await window.API.login(username.trim(), password);
      onLogin(user);
    } catch (err) {
      setError(err.message || 'เข้าสู่ระบบไม่สำเร็จ');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ minHeight: '100vh', background: '#f5f3ee', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ width: 380 }}>
        {/* Logo */}
        <div style={{ textAlign: 'center', marginBottom: 36 }}>
          <img
            src="https://business.true.th/hubfs/raw_assets/public/TrueB2B2024/images/header-footer/logo-true-business.png"
            alt="True Business"
            style={{ height: 40, width: 'auto', marginBottom: 14 }}
          />
          <h1 className="serif" style={{ fontSize: 26, fontWeight: 700, color: 'var(--ink)', letterSpacing: '-0.02em', marginBottom: 4 }}>B2B Product</h1>
          <p style={{ fontSize: 13, color: 'var(--ink-3)' }}>Revenue Dashboard</p>
        </div>

        {/* Card */}
        <div style={{ background: 'var(--panel)', border: '1px solid var(--line)', padding: '32px 32px 28px', borderRadius: 4 }}>
          <form onSubmit={handleSubmit}>
            <div style={{ marginBottom: 18 }}>
              <label style={{ display: 'block', fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-2)', marginBottom: 6 }}>Username</label>
              <input
                type="text" autoComplete="username" autoFocus
                value={username} onChange={e => setUsername(e.target.value)}
                style={{ width: '100%', padding: '9px 12px', border: `1px solid ${error ? '#e05' : 'var(--line)'}`, borderRadius: 3, fontSize: 13.5, color: 'var(--ink)', background: 'var(--bg)', outline: 'none', boxSizing: 'border-box' }}
                onFocus={e => e.target.style.borderColor = '#2d3a8c'}
                onBlur={e => e.target.style.borderColor = error ? '#e05' : 'var(--line)'}
              />
            </div>
            <div style={{ marginBottom: 22 }}>
              <label style={{ display: 'block', fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-2)', marginBottom: 6 }}>Password</label>
              <div style={{ position: 'relative' }}>
                <input
                  type={showPw ? 'text' : 'password'} autoComplete="current-password"
                  value={password} onChange={e => setPassword(e.target.value)}
                  style={{ width: '100%', padding: '9px 38px 9px 12px', border: `1px solid ${error ? '#e05' : 'var(--line)'}`, borderRadius: 3, fontSize: 13.5, color: 'var(--ink)', background: 'var(--bg)', outline: 'none', boxSizing: 'border-box' }}
                  onFocus={e => e.target.style.borderColor = '#2d3a8c'}
                  onBlur={e => e.target.style.borderColor = error ? '#e05' : 'var(--line)'}
                />
                <button type="button" onClick={() => setShowPw(s => !s)}
                  style={{ position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: 'var(--ink-3)', padding: 2 }}>
                  {showPw ? '🙈' : '👁'}
                </button>
              </div>
            </div>

            {error && (
              <div style={{ marginBottom: 16, padding: '9px 12px', background: '#fff0f0', border: '1px solid #fcc', borderRadius: 3, fontSize: 12.5, color: '#c00' }}>
                {error}
              </div>
            )}

            <button type="submit" disabled={loading}
              style={{ width: '100%', padding: '10px', background: loading ? '#9aa' : '#2d3a8c', color: '#fff', border: 'none', borderRadius: 3, fontSize: 13.5, fontWeight: 600, cursor: loading ? 'not-allowed' : 'pointer', letterSpacing: '0.01em' }}>
              {loading ? 'กำลังเข้าสู่ระบบ…' : 'เข้าสู่ระบบ'}
            </button>
          </form>
        </div>

        <p style={{ textAlign: 'center', fontSize: 11.5, color: 'var(--ink-3)', marginTop: 20 }}>
          ติดต่อผู้ดูแลระบบหากลืม Password
        </p>
      </div>
    </div>
  );
};

// ============================================================
// User Settings View
// ============================================================
const ROLES = { admin: 'Admin', editor: 'Editor', viewer: 'Viewer' };
const ROLE_DESC = { admin: 'จัดการได้ทุกอย่างรวมถึง User', editor: 'แก้ไขข้อมูลและ Upload ได้', viewer: 'ดูข้อมูลได้อย่างเดียว' };

const UserModal = ({ user, onSave, onClose }) => {
  const isNew = !user;
  const [form, setForm] = useState({
    username: user?.username || '',
    display_name: user?.display_name || '',
    email: user?.email || '',
    role: user?.role || 'viewer',
    password: '',
    is_active: user?.is_active !== false,
  });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const handleSave = async () => {
    if (!form.display_name.trim()) { setError('กรุณากรอก Display Name'); return; }
    if (isNew && !form.username.trim()) { setError('กรุณากรอก Username'); return; }
    if (isNew && form.password.length < 6) { setError('Password ต้องมีอย่างน้อย 6 ตัวอักษร'); return; }
    setLoading(true); setError('');
    try {
      if (isNew) {
        await window.API.createUser(form);
      } else {
        await window.API.updateUser(user.id, { display_name: form.display_name, email: form.email, role: form.role, is_active: form.is_active });
      }
      onSave();
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  };

  const inputStyle = { width: '100%', padding: '8px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 13, color: 'var(--ink)', background: 'var(--bg)', boxSizing: 'border-box' };
  const labelStyle = { display: 'block', fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-3)', marginBottom: 5 };

  return (
    <Modal open onClose={onClose} title={isNew ? 'เพิ่ม User ใหม่' : `แก้ไข · ${user.display_name}`} width={440}
      footer={<>
        <button onClick={onClose} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12 }}>ยกเลิก</button>
        <button onClick={handleSave} disabled={loading} style={{ padding: '7px 16px', background: '#2d3a8c', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer' }}>
          {loading ? 'กำลังบันทึก…' : 'บันทึก'}
        </button>
      </>}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {error && <div style={{ padding: '8px 12px', background: '#fff0f0', border: '1px solid #fcc', borderRadius: 3, fontSize: 12.5, color: '#c00' }}>{error}</div>}

        {isNew && (
          <div>
            <label style={labelStyle}>Username</label>
            <input value={form.username} onChange={e => set('username', e.target.value)} style={inputStyle} placeholder="ตัวพิมพ์เล็ก ไม่มีช่องว่าง" />
          </div>
        )}
        <div>
          <label style={labelStyle}>Display Name</label>
          <input value={form.display_name} onChange={e => set('display_name', e.target.value)} style={inputStyle} placeholder="ชื่อที่แสดงในระบบ" />
        </div>
        <div>
          <label style={labelStyle}>Email (optional)</label>
          <input type="email" value={form.email} onChange={e => set('email', e.target.value)} style={inputStyle} placeholder="user@company.com" />
        </div>
        <div>
          <label style={labelStyle}>Role</label>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {Object.keys(ROLES).map(r => (
              <label key={r} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, cursor: 'pointer', padding: '9px 12px', border: `1px solid ${form.role === r ? '#2d3a8c' : 'var(--line)'}`, borderRadius: 3, background: form.role === r ? '#f0f2fa' : 'transparent' }}>
                <input type="radio" name="role" value={r} checked={form.role === r} onChange={() => set('role', r)} style={{ marginTop: 2 }} />
                <div>
                  <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)' }}>{ROLES[r]}</div>
                  <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 1 }}>{ROLE_DESC[r]}</div>
                </div>
              </label>
            ))}
          </div>
        </div>
        {isNew && (
          <div>
            <label style={labelStyle}>Password เริ่มต้น</label>
            <input type="password" value={form.password} onChange={e => set('password', e.target.value)} style={inputStyle} placeholder="อย่างน้อย 6 ตัวอักษร" />
          </div>
        )}
        {!isNew && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <input type="checkbox" id="ua-active" checked={form.is_active} onChange={e => set('is_active', e.target.checked)} style={{ width: 14, height: 14 }} />
            <label htmlFor="ua-active" style={{ fontSize: 13, color: 'var(--ink)', cursor: 'pointer' }}>Active (สามารถเข้าสู่ระบบได้)</label>
          </div>
        )}
      </div>
    </Modal>
  );
};

const ResetPasswordModal = ({ user, onClose }) => {
  const [pw, setPw] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [done, setDone] = useState(false);

  const handleReset = async () => {
    if (pw.length < 6) { setError('Password ต้องมีอย่างน้อย 6 ตัวอักษร'); return; }
    setLoading(true); setError('');
    try {
      await window.API.resetPassword(user.id, pw);
      setDone(true);
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  };

  return (
    <Modal open onClose={onClose} title={`Reset Password · ${user.display_name}`} width={380}
      footer={done ? <button onClick={onClose} style={{ padding: '7px 16px', background: '#2d3a8c', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none' }}>ปิด</button>
        : <>
          <button onClick={onClose} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12 }}>ยกเลิก</button>
          <button onClick={handleReset} disabled={loading} style={{ padding: '7px 16px', background: '#c00', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer' }}>
            {loading ? 'กำลังบันทึก…' : 'Reset Password'}
          </button>
        </>}>
      {done
        ? <div style={{ textAlign: 'center', padding: '12px 0', color: 'var(--positive)', fontSize: 14 }}>✓ Reset Password เรียบร้อยแล้ว</div>
        : <div>
          {error && <div style={{ marginBottom: 12, padding: '8px 12px', background: '#fff0f0', border: '1px solid #fcc', borderRadius: 3, fontSize: 12.5, color: '#c00' }}>{error}</div>}
          <label style={{ display: 'block', fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-3)', marginBottom: 6 }}>New Password</label>
          <input type="password" value={pw} onChange={e => setPw(e.target.value)} autoFocus
            style={{ width: '100%', padding: '8px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 13, color: 'var(--ink)', background: 'var(--bg)', boxSizing: 'border-box' }}
            placeholder="อย่างน้อย 6 ตัวอักษร" />
        </div>}
    </Modal>
  );
};

const UserSettingView = ({ currentUser }) => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editUser, setEditUser] = useState(null);   // null=closed, false=new, obj=edit
  const [resetUser, setResetUser] = useState(null);
  const [deleteConfirm, setDeleteConfirm] = useState(null);
  const [reactivateConfirm, setReactivateConfirm] = useState(null);

  const load = () => {
    setLoading(true);
    window.API.getUsers().then(u => { setUsers(u); setLoading(false); }).catch(() => setLoading(false));
  };
  useEffect(() => { load(); }, []);

  const handleDelete = async (u) => {
    try {
      await window.API.deleteUser(u.id);
      showToast(`Deactivated ${u.display_name}`, { variant: 'warn' });
      load();
    } catch (e) { showToast(e.message, { variant: 'error' }); }
    setDeleteConfirm(null);
  };

  const handleReactivate = async (u) => {
    try {
      await window.API.reactivateUser(u.id, { display_name: u.display_name, email: u.email, role: u.role });
      showToast(`Reactivated ${u.display_name}`, { variant: 'success' });
      load();
    } catch (e) { showToast(e.message, { variant: 'error' }); }
    setReactivateConfirm(null);
  };

  return (
    <div>
      <div style={{ marginBottom: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
        <div>
          <h1 className="serif" style={{ fontSize: 24, fontWeight: 500, letterSpacing: '-0.02em', marginBottom: 4 }}>User Management</h1>
          <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>จัดการบัญชีผู้ใช้งานระบบ · เฉพาะ Admin เท่านั้น</div>
        </div>
        <button onClick={() => setEditUser(false)} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px', background: '#2d3a8c', color: '#fff', border: 'none', borderRadius: 3, fontSize: 12.5, fontWeight: 500, cursor: 'pointer' }}>
          + เพิ่ม User ใหม่
        </button>
      </div>

      <div style={{ background: 'var(--panel)', border: '1px solid var(--line)' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
          <thead>
            <tr style={{ background: '#fbfaf6', borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-3)' }}>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>User</th>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>Username</th>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>Email</th>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>Role</th>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>Status</th>
              <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500 }}>Last Login</th>
              <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500 }}>Actions</th>
            </tr>
          </thead>
          <tbody>
            {loading ? (
              <tr><td colSpan={7} style={{ padding: 32, textAlign: 'center', color: 'var(--ink-3)' }}>กำลังโหลด…</td></tr>
            ) : users.map(u => {
              const isMe = u.id === currentUser?.id;
              const roleColor = u.role === 'admin' ? '#2d3a8c' : u.role === 'editor' ? '#b06000' : '#555';
              return (
                <tr key={u.id} style={{ borderBottom: '1px solid var(--line-2)' }}
                  onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                  <td style={{ padding: '12px 16px' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                      <div style={{ width: 32, height: 32, borderRadius: '50%', background: roleColor, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 13, fontWeight: 600, flexShrink: 0 }}>
                        {u.display_name[0].toUpperCase()}
                      </div>
                      <div>
                        <div style={{ fontWeight: 500 }}>{u.display_name}{isMe && <span style={{ fontSize: 10, marginLeft: 6, padding: '1px 6px', background: '#e8eaf3', color: '#2d3a8c', borderRadius: 8 }}>คุณ</span>}</div>
                      </div>
                    </div>
                  </td>
                  <td style={{ padding: '12px 16px', fontFamily: 'IBM Plex Mono', fontSize: 12, color: 'var(--ink-2)' }}>{u.username}</td>
                  <td style={{ padding: '12px 16px', color: 'var(--ink-3)', fontSize: 12 }}>{u.email || '—'}</td>
                  <td style={{ padding: '12px 16px' }}>
                    <span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 10, fontWeight: 500, background: u.role === 'admin' ? '#e8eaf3' : u.role === 'editor' ? '#fef3e2' : '#f1f1f1', color: roleColor }}>
                      {ROLES[u.role]}
                    </span>
                  </td>
                  <td style={{ padding: '12px 16px' }}>
                    <span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 10, background: u.is_active ? '#e3f1ea' : '#f5f5f5', color: u.is_active ? '#1a7a3a' : '#888' }}>
                      {u.is_active ? '● Active' : '○ Inactive'}
                    </span>
                  </td>
                  <td style={{ padding: '12px 16px', fontSize: 12, color: 'var(--ink-3)' }}>
                    {u.last_login ? new Date(u.last_login).toLocaleString('th-TH', { dateStyle: 'short', timeStyle: 'short' }) : '—'}
                  </td>
                  <td style={{ padding: '12px 16px', textAlign: 'right' }}>
                    <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
                      {u.is_active ? (
                        <>
                          <button onClick={() => setEditUser(u)} style={{ fontSize: 11.5, padding: '4px 10px', border: '1px solid var(--line)', borderRadius: 3, background: 'var(--panel)', cursor: 'pointer', color: 'var(--ink-2)' }}>แก้ไข</button>
                          <button onClick={() => setResetUser(u)} style={{ fontSize: 11.5, padding: '4px 10px', border: '1px solid var(--line)', borderRadius: 3, background: 'var(--panel)', cursor: 'pointer', color: 'var(--ink-2)' }}>Reset PW</button>
                          {!isMe && (
                            <button onClick={() => setDeleteConfirm(u)} style={{ fontSize: 11.5, padding: '4px 10px', border: '1px solid #fcc', borderRadius: 3, background: '#fff8f8', cursor: 'pointer', color: '#c00' }}>Deactivate</button>
                          )}
                        </>
                      ) : (
                        <button onClick={() => setReactivateConfirm(u)} style={{ fontSize: 11.5, padding: '4px 10px', border: '1px solid #b4d9b4', borderRadius: 3, background: '#f0faf0', cursor: 'pointer', color: '#1a7a3a', fontWeight: 500 }}>↺ Reactivate</button>
                      )}
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {editUser !== null && (
        <UserModal user={editUser || null} onClose={() => setEditUser(null)} onSave={() => { setEditUser(null); load(); showToast('บันทึกเรียบร้อย', { variant: 'success' }); }} />
      )}
      {resetUser && <ResetPasswordModal user={resetUser} onClose={() => setResetUser(null)} />}
      {deleteConfirm && (
        <Modal open onClose={() => setDeleteConfirm(null)} title="ยืนยันการ Deactivate" width={360}
          footer={<>
            <button onClick={() => setDeleteConfirm(null)} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12 }}>ยกเลิก</button>
            <button onClick={() => handleDelete(deleteConfirm)} style={{ padding: '7px 16px', background: '#c00', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer' }}>Deactivate</button>
          </>}>
          <p style={{ fontSize: 13.5, color: 'var(--ink)' }}>
            คุณแน่ใจหรือไม่ที่จะ Deactivate <strong>{deleteConfirm.display_name}</strong>?<br />
            <span style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4, display: 'block' }}>User จะไม่สามารถเข้าสู่ระบบได้จนกว่าจะ Activate ใหม่</span>
          </p>
        </Modal>
      )}
      {reactivateConfirm && (
        <Modal open onClose={() => setReactivateConfirm(null)} title="Reactivate User" width={360}
          footer={<>
            <button onClick={() => setReactivateConfirm(null)} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12 }}>ยกเลิก</button>
            <button onClick={() => handleReactivate(reactivateConfirm)} style={{ padding: '7px 16px', background: '#1a7a3a', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer' }}>↺ Reactivate</button>
          </>}>
          <p style={{ fontSize: 13.5, color: 'var(--ink)' }}>
            Reactivate <strong>{reactivateConfirm.display_name}</strong> (@{reactivateConfirm.username})?<br />
            <span style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4, display: 'block' }}>User จะสามารถ login ได้ด้วย password เดิม</span>
          </p>
        </Modal>
      )}
    </div>
  );
};

// ============================================================
// Change Password Modal
// ============================================================
const ChangePasswordModal = ({ onClose }) => {
  const [form, setForm] = useState({ current: '', next: '', confirm: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [done, setDone] = useState(false);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const handleSave = async () => {
    if (form.next.length < 6) { setError('Password ใหม่ต้องมีอย่างน้อย 6 ตัวอักษร'); return; }
    if (form.next !== form.confirm) { setError('Password ใหม่ไม่ตรงกัน'); return; }
    setLoading(true); setError('');
    try {
      await window.API.changePassword(form.current, form.next);
      setDone(true);
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  };

  const iStyle = { width: '100%', padding: '8px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 13, boxSizing: 'border-box', background: 'var(--bg)' };
  const lStyle = { display: 'block', fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-3)', marginBottom: 5 };

  return (
    <Modal open onClose={onClose} title="เปลี่ยน Password" width={380}
      footer={done ? <button onClick={onClose} style={{ padding: '7px 16px', background: '#2d3a8c', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none' }}>ปิด</button>
        : <>
          <button onClick={onClose} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12 }}>ยกเลิก</button>
          <button onClick={handleSave} disabled={loading} style={{ padding: '7px 16px', background: '#2d3a8c', color: '#fff', borderRadius: 3, fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer' }}>
            {loading ? 'กำลังบันทึก…' : 'เปลี่ยน Password'}
          </button>
        </>}>
      {done
        ? <div style={{ textAlign: 'center', padding: '16px 0', color: 'var(--positive)', fontSize: 14 }}>✓ เปลี่ยน Password เรียบร้อยแล้ว</div>
        : <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {error && <div style={{ padding: '8px 12px', background: '#fff0f0', border: '1px solid #fcc', borderRadius: 3, fontSize: 12.5, color: '#c00' }}>{error}</div>}
          <div><label style={lStyle}>Password ปัจจุบัน</label><input type="password" value={form.current} onChange={e => set('current', e.target.value)} style={iStyle} autoFocus /></div>
          <div><label style={lStyle}>Password ใหม่</label><input type="password" value={form.next} onChange={e => set('next', e.target.value)} style={iStyle} /></div>
          <div><label style={lStyle}>ยืนยัน Password ใหม่</label><input type="password" value={form.confirm} onChange={e => set('confirm', e.target.value)} style={iStyle} /></div>
        </div>}
    </Modal>
  );
};

// ============================================================
// Pipeline Dashboard View
// ============================================================
const DEFAULT_PIPELINE_SECTIONS = [
  { id: 'kpi',        label: 'KPI summary',                            visible: true },
  { id: 'funnel',     label: 'Stage Funnel & Forecast',                visible: true },
  { id: 'heatmap',    label: 'Pipeline by Product Group × Vertical',   visible: true },
  { id: 'groups',     label: 'Pipeline by Product Group (drill-down)', visible: true },
  { id: 'trend_opps', label: 'Trend by Segment & Top Opportunities',   visible: true },
];

const PIPELINE_STAGE_ORDER = ['Prospecting', 'Qualification', 'Proposal', 'POC / Demo', 'Negotiation', 'Closed Won'];
const PIPELINE_STAGE_COLORS = {
  'Prospecting':  '#3b4ba0',
  'Qualification':'#4a5cb8',
  'Proposal':     '#5e72c6',
  'POC / Demo':   '#d97b2e',
  'Negotiation':  '#c06010',
  'Closed Won':   '#1f7a4d',
};
const STAGE_PILLS = {
  'Prospecting':  { bg: '#eaf0fd', color: '#2d3a8c' },
  'Qualification':{ bg: '#eaf0fd', color: '#2d3a8c' },
  'Proposal':     { bg: '#e8f0fb', color: '#2d3a8c' },
  'POC / Demo':   { bg: '#f0fdf4', color: '#1f7a4d' },
  'Negotiation':  { bg: '#fff3e3', color: '#c06010' },
  'Closed Won':   { bg: '#e8f5ed', color: '#1f7a4d' },
  'Closed Lost':  { bg: '#fdecea', color: '#b8492f' },
};

// fmtMillion – input in raw baht (pipeline deal_value = actual THB)
const fmtMillion = (v) => {
  const n = parseFloat(v) || 0;
  const abs = Math.abs(n);
  if (abs >= 1e9) return `฿${numFmt(n / 1e9)}B`;
  if (abs >= 1e6) return `฿${numFmt(n / 1e6)}M`;
  if (abs >= 1e3) return `฿${numFmt(n / 1e3)}K`;
  return `฿${numFmt(n)}`;
};

const PIPELINE_GROUP_PALETTE = ['#2d3a8c','#d97b2e','#1f7a4d','#7c3aed','#0891b2','#c06010','#b8492f','#64748b'];

// ============================================================
// Lost Analysis View
// ============================================================
const LOST_CARDS_DEFAULT = [
  { id: 'total_lost',   label: 'Total Lost Deals',    visible: true },
  { id: 'lost_value',   label: 'Lost Value',           visible: true },
  { id: 'loss_rate',    label: 'Loss Rate',            visible: true },
  { id: 'avg_size',     label: 'Avg Deal Size (Lost)', visible: true },
  { id: 'avg_days',     label: 'Avg Days to Loss',     visible: true },
  { id: 'won_value',    label: 'Won Value',            visible: false },
];

const LOST_SECTIONS_DEFAULT = [
  { id: 'dept_product', label: 'Lost by Department & Product', visible: true },
  { id: 'trend',        label: 'Loss Trend (12 เดือน)',        visible: true },
  { id: 'heatmap',      label: 'Loss Heatmap',                 visible: true },
  { id: 'loss_pattern', label: 'Lost Reason Analysis',         visible: true },
  { id: 'recent',       label: 'Top 20 Recent Lost Deals',     visible: true },
];

const LostAnalysisView = ({ userId }) => {
  const lostCardsKey    = `lost_cards_${userId || 'default'}`;
  const lostSectionsKey = `lost_sections_${userId || 'default'}`;

  const [data, setData]         = useState(null);
  const [loading, setLoading]   = useState(true);
  const [error, setError]       = useState(null);
  const [metric, setMetric]     = useState('value');
  const [hmMetric, setHmMetric] = useState('count');
  const [hmSort, setHmSort]     = useState({ col: null, dir: 'desc' }); // col: null=dept-alpha | segment string
  const [showAddKPI, setShowAddKPI]       = useState(false);
  const [showCustomize, setShowCustomize] = useState(false);
  const [recentFilter, setRecentFilter]   = useState('all'); // 'all' | 'close_lost' | 'close_cancel'
  // Expose to global so the top-level Export handler can read the current filter
  React.useEffect(() => { window.__lostFilter = recentFilter; }, [recentFilter]);
  React.useEffect(() => { window.__lostFilter = 'all'; return () => { window.__lostFilter = null; }; }, []);

  // ── Card visibility state (persisted per user) ──
  const [cards, setCards] = useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(`lost_cards_${userId || 'default'}`) || 'null');
      if (saved) {
        const savedIds = new Set(saved.map(c => c.id));
        const newCards = LOST_CARDS_DEFAULT.filter(c => !savedIds.has(c.id));
        return [...saved, ...newCards];
      }
    } catch {}
    return LOST_CARDS_DEFAULT;
  });
  const [pendingCards, setPendingCards] = useState(LOST_CARDS_DEFAULT);
  const cardDragIdx     = useRef(null);
  const cardDragOverIdx = useRef(null);

  // ── Section order/visibility (persisted per user) ──
  const [lostSections, setLostSections] = useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(`lost_sections_${userId || 'default'}`) || 'null');
      if (saved) {
        const savedIds = new Set(saved.map(s => s.id));
        const newSects = LOST_SECTIONS_DEFAULT.filter(s => !savedIds.has(s.id));
        return [...saved, ...newSects];
      }
    } catch {}
    return LOST_SECTIONS_DEFAULT;
  });
  const [pendingSections, setPendingSections] = useState(LOST_SECTIONS_DEFAULT);
  const sectDragIdx     = useRef(null);
  const sectDragOverIdx = useRef(null);
  const sectVisible = (id) => lostSections.find(s => s.id === id)?.visible !== false;

  useEffect(() => {
    setLoading(true);
    authFetch('/api/pipeline/lost')
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 300, color: 'var(--ink-3)', fontSize: 13 }}>
      <span>กำลังโหลดข้อมูล…</span>
    </div>
  );
  if (error) return (
    <div style={{ padding: 24, color: '#b8492f', fontSize: 13 }}>Error: {error}</div>
  );
  if (!data) return null;

  const { kpis, trend, byDept, byProduct, heatmap, recentDeals,
          winLossBySegment = [], cycleTimeBuckets = [], dealSizeBuckets = [] } = data;

  const fmtM = (v) => {
    const n = parseFloat(v) || 0;
    if (n >= 1e9) return `฿${numFmt(n/1e9)}B`;
    if (n >= 1e6) return `฿${numFmt(n/1e6)}M`;
    if (n >= 1e3) return `฿${numFmt(n/1e3)}K`;
    return `฿${numFmt(n)}`;
  };

  // Map card id → rendered card
  const cardDefs = {
    total_lost: { label: 'Total Lost Deals',    value: kpis.lost_count.toLocaleString(), sub: 'Close Lost + Close Cancel', accent: '#b8492f' },
    lost_value: { label: 'Lost Value',           value: fmtM(kpis.lost_value),           sub: 'มูลค่า deal รวม' },
    loss_rate:  { label: 'Loss Rate',            value: `${kpis.lost_rate}%`,            sub: 'ต่อ closed deals ทั้งหมด' },
    avg_size:   { label: 'Avg Deal Size (Lost)', value: fmtM(kpis.avg_lost_value),       sub: 'เฉลี่ยต่อ deal' },
    avg_days:   { label: 'Avg Days to Loss',     value: `${kpis.avg_days} วัน`,          sub: 'นับจาก created date' },
    won_value:  { label: 'Won Value',            value: fmtM(kpis.won_value),            sub: 'มูลค่า deal ที่ชนะ', accent: '#1f7a4d' },
  };

  const KPICard = ({ id }) => {
    const d = cardDefs[id];
    if (!d) return null;
    return (
      <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: '18px 20px', borderTop: d.accent ? `3px solid ${d.accent}` : undefined }}>
        <div style={{ fontSize: 9.5, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--ink-3)', marginBottom: 8 }}>{d.label}</div>
        <div style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-0.5px', fontVariantNumeric: 'tabular-nums', color: d.accent || 'var(--ink)' }}>{d.value}</div>
        {d.sub && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 6 }}>{d.sub}</div>}
      </div>
    );
  };

  const visibleCards = cards.filter(c => c.visible);

  // ── Bar charts ──
  const maxDeptVal = Math.max(...byDept.map(d => metric === 'value' ? d.total.value : d.total.count), 1);
  const maxProdVal = Math.max(...byProduct.map(d => metric === 'value' ? d.total.value : d.total.count), 1);

  // Stacked bar: Close Lost (dark red) + Close Cancel (peach)
  const CL_COLOR = '#b8492f';
  const CC_COLOR = '#e8a87c';

  const StackedBarRow = ({ label, closeLost, closeCancel, total, maxVal, metricKey, suffix }) => {
    const clVal = closeLost[metricKey];
    const ccVal = closeCancel[metricKey];
    const totVal = total[metricKey];
    const clPct  = totVal > 0 ? (clVal / maxVal) * 100 : 0;
    const ccPct  = totVal > 0 ? (ccVal / maxVal) * 100 : 0;
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
        <div style={{ width: 180, fontSize: 11.5, color: 'var(--ink-2)', textAlign: 'right', flexShrink: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</div>
        <div style={{ flex: 1, height: 22, background: 'var(--line-2)', borderRadius: 2, overflow: 'hidden', display: 'flex' }}>
          {clPct > 0 && <div style={{ width: `${clPct}%`, height: '100%', background: CL_COLOR, flexShrink: 0 }} title={`Close Lost: ${metricKey === 'value' ? fmtM(clVal) : clVal + ' deals'}`} />}
          {ccPct > 0 && <div style={{ width: `${ccPct}%`, height: '100%', background: CC_COLOR, flexShrink: 0 }} title={`Close Cancel: ${metricKey === 'value' ? fmtM(ccVal) : ccVal + ' deals'}`} />}
        </div>
        <div style={{ width: 90, fontSize: 11.5, fontWeight: 500, textAlign: 'right', fontVariantNumeric: 'tabular-nums', flexShrink: 0 }}>{suffix}</div>
      </div>
    );
  };

  const StackLegend = () => (
    <div style={{ display: 'flex', gap: 14, marginBottom: 12 }}>
      {[[CL_COLOR,'Close Lost'],[CC_COLOR,'Close Cancel']].map(([c,l]) => (
        <div key={l} style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, color: 'var(--ink-2)' }}>
          <div style={{ width: 10, height: 10, borderRadius: 2, background: c, flexShrink: 0 }} />
          {l}
        </div>
      ))}
    </div>
  );

  // ── Heatmap ──
  const hmMax = Math.max(...(heatmap.data || []).flat().map(c => hmMetric === 'value' ? c.value : c.count), 1);
  const hmColor = (val) => {
    const pct = val / hmMax;
    const r = Math.round(184 + (60 - 184) * pct);
    const g = Math.round(73  + (30 - 73)  * pct);
    const b = Math.round(47  + (20 - 47)  * pct);
    return `rgba(${r},${g},${b},${0.12 + pct * 0.82})`;
  };

  // ── Trend bars ──
  const trendVals = trend[metric === 'value' ? 'lostValue' : 'lost'] || [];
  const trendMax  = Math.max(...trendVals, 1);

  // ── Section renderers (keyed by id) ──
  const SectionDeptProduct = () => {
    const mk = metric === 'value' ? 'value' : 'count';
    return (
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
        <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>
          <div style={{ marginBottom: 10 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Lost by Department</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>แยกตาม department / sector</div>
          </div>
          <StackLegend />
          {byDept.map((d, i) => (
            <StackedBarRow key={i} label={d.dept}
              closeLost={d.closeLost} closeCancel={d.closeCancel} total={d.total}
              maxVal={maxDeptVal} metricKey={mk}
              suffix={metric === 'value' ? fmtM(d.total.value) : `${d.total.count} deals`} />
          ))}
        </div>
        <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>
          <div style={{ marginBottom: 10 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Lost by Product</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>Top 10 product ที่แพ้บ่อย</div>
          </div>
          <StackLegend />
          {byProduct.map((d, i) => (
            <StackedBarRow key={i} label={d.prod}
              closeLost={d.closeLost} closeCancel={d.closeCancel} total={d.total}
              maxVal={maxProdVal} metricKey={mk}
              suffix={metric === 'value' ? fmtM(d.total.value) : `${d.total.count} deals`} />
          ))}
        </div>
      </div>
    );
  };

  const SectionTrend = () => (
    <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 16 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Loss Trend (12 เดือนล่าสุด)</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>แนวโน้ม deals ที่แพ้ · เรียงตาม close date</div>
        </div>
        <div style={{ fontSize: 11, color: 'var(--ink-3)', display: 'flex', gap: 16 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}><span style={{ width: 10, height: 10, borderRadius: 2, background: '#b8492f', display: 'inline-block' }} />Lost {metric === 'value' ? 'Value' : 'Count'}</span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}><span style={{ width: 10, height: 10, borderRadius: 2, background: '#d97b2e', display: 'inline-block' }} />Loss Rate %</span>
        </div>
      </div>
      <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end', height: 100 }}>
        {trend.labels.map((label, i) => {
          const v = trendVals[i] || 0;
          const h = trendMax > 0 ? Math.max((v / trendMax) * 90, v > 0 ? 4 : 0) : 0;
          const rate = trend.lostRate[i] || 0;
          return (
            <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }} title={`${label}: ${metric === 'value' ? fmtM(v) : v + ' deals'} · Loss rate ${rate}%`}>
              <div style={{ fontSize: 9, color: 'var(--ink-3)', marginBottom: 2 }}>{rate > 0 ? `${rate}%` : ''}</div>
              <div style={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', height: 72 }}>
                <div style={{ width: '80%', marginLeft: '10%', height: h, background: '#b8492f', borderRadius: '2px 2px 0 0', minHeight: v > 0 ? 2 : 0 }} />
              </div>
              <div style={{ fontSize: 9, color: 'var(--ink-3)', textAlign: 'center', marginTop: 2 }}>{label}</div>
            </div>
          );
        })}
      </div>
    </div>
  );

  const SectionHeatmap = () => {
    // Build sorted dept indices
    const sortedDeptIndices = heatmap.depts.map((_, i) => i).sort((a, b) => {
      const { col, dir } = hmSort;
      const mult = dir === 'asc' ? 1 : -1;
      if (col === null) {
        // Sort alphabetically by dept name
        return mult * heatmap.depts[a].localeCompare(heatmap.depts[b]);
      }
      const si = heatmap.segments.indexOf(col);
      if (si === -1) return 0;
      const cellA = heatmap.data[a]?.[si] || { count: 0, value: 0 };
      const cellB = heatmap.data[b]?.[si] || { count: 0, value: 0 };
      const vA = hmMetric === 'value' ? parseFloat(cellA.value) : parseInt(cellA.count);
      const vB = hmMetric === 'value' ? parseFloat(cellB.value) : parseInt(cellB.count);
      return mult * (vB - vA);
    });

    const handleSort = (col) => {
      setHmSort(prev => ({
        col,
        dir: prev.col === col && prev.dir === 'desc' ? 'asc' : 'desc',
      }));
    };

    const SortArrow = ({ col }) => {
      const active = hmSort.col === col;
      return (
        <span style={{ marginLeft: 4, opacity: active ? 1 : 0.3, fontSize: 9, verticalAlign: 'middle' }}>
          {active && hmSort.dir === 'asc' ? '▲' : '▼'}
        </span>
      );
    };

    const thStyle = (col) => ({
      padding: '8px 10px', textAlign: col === null ? 'left' : 'center',
      color: hmSort.col === col ? 'var(--ink)' : 'var(--ink-3)',
      fontWeight: hmSort.col === col ? 600 : 500,
      fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.06em',
      minWidth: col === null ? 200 : 120,
      cursor: 'pointer', userSelect: 'none',
      borderBottom: `2px solid ${hmSort.col === col ? '#b8492f' : 'var(--line)'}`,
      whiteSpace: 'nowrap',
    });

    return (
      <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14 }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Loss Heatmap · Department × Segment</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>คลิก header เพื่อ sort · เซลล์สีเข้ม = สูญเสียมาก</div>
          </div>
          <div style={{ display: 'flex', gap: 4 }}>
            {[['count','จำนวน'],['value','มูลค่า']].map(([v,l]) => (
              <button key={v} onClick={() => setHmMetric(v)}
                style={{ padding: '4px 10px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 11,
                  background: hmMetric === v ? '#b8492f' : '#fff', color: hmMetric === v ? '#fff' : 'var(--ink-2)',
                  cursor: 'pointer', fontFamily: 'inherit' }}>{l}</button>
            ))}
          </div>
        </div>
        {heatmap.depts.length === 0 ? <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>ไม่มีข้อมูล</div> : (
          <div style={{ overflowX: 'auto' }}>
            <table style={{ borderCollapse: 'collapse', fontSize: 11, width: '100%' }}>
              <thead>
                <tr>
                  <th onClick={() => handleSort(null)} style={thStyle(null)}>
                    Department <SortArrow col={null} />
                  </th>
                  {heatmap.segments.map(seg => (
                    <th key={seg} onClick={() => handleSort(seg)} style={thStyle(seg)}>
                      {seg} <SortArrow col={seg} />
                    </th>
                  ))}
                  <th style={{ padding: '8px 10px', fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--ink-3)', fontWeight: 500, textAlign: 'center', minWidth: 80, borderBottom: '2px solid var(--line)', whiteSpace: 'nowrap' }}>Total</th>
                </tr>
              </thead>
              <tbody>
                {sortedDeptIndices.map(di => {
                  const dept = heatmap.depts[di];
                  const rowTotal = heatmap.segments.reduce((sum, _, si) => {
                    const cell = heatmap.data[di]?.[si] || { count: 0, value: 0 };
                    return sum + (hmMetric === 'value' ? parseFloat(cell.value) : parseInt(cell.count));
                  }, 0);
                  return (
                    <tr key={dept} style={{ borderTop: '1px solid var(--line-2)' }}
                      onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
                      onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                      <td style={{ padding: '8px 12px', color: 'var(--ink-2)', fontWeight: 500, fontSize: 11.5 }}>{dept}</td>
                      {heatmap.segments.map((seg, si) => {
                        const cell = heatmap.data[di]?.[si] || { count: 0, value: 0 };
                        const cellVal = hmMetric === 'value' ? cell.value : cell.count;
                        return (
                          <td key={seg} style={{ padding: '8px 10px', textAlign: 'center', background: cellVal > 0 ? hmColor(cellVal) : 'transparent', borderRadius: 2, transition: 'background 120ms' }}
                            title={`${dept} × ${seg}: ${cell.count} deals · ${fmtM(cell.value)}`}>
                            {cellVal > 0 ? (hmMetric === 'value' ? fmtM(cell.value) : cell.count) : <span style={{ color: 'var(--line)' }}>—</span>}
                          </td>
                        );
                      })}
                      <td style={{ padding: '8px 10px', textAlign: 'center', fontWeight: 600, color: 'var(--ink-2)', fontSize: 11, background: '#f9f8f4' }}>
                        {hmMetric === 'value' ? fmtM(rowTotal) : rowTotal || <span style={{ color: 'var(--line)' }}>—</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>
    );
  };

  const SectionRecent = () => {
    const filterOpts = [
      { key: 'all',          label: 'ทั้งหมด',      count: recentDeals.length },
      { key: 'close_lost',   label: 'Close Lost',   count: recentDeals.filter(d => !d.opportunity_status?.toLowerCase().includes('cancel')).length },
      { key: 'close_cancel', label: 'Close Cancel', count: recentDeals.filter(d =>  d.opportunity_status?.toLowerCase().includes('cancel')).length },
    ];
    const filtered = recentDeals.filter(d => {
      if (recentFilter === 'all') return true;
      const isCancel = d.opportunity_status?.toLowerCase().includes('cancel');
      return recentFilter === 'close_cancel' ? isCancel : !isCancel;
    });
    const isCancel = s => s?.toLowerCase().includes('cancel');
    return (
      <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Top 20 Recent Lost Deals</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>
              เรียงตาม close date
              {recentFilter !== 'all' && <span style={{ marginLeft: 6, color: '#b8492f' }}>· แสดง {filtered.length} รายการ</span>}
            </div>
          </div>
          {/* Status filter tabs */}
          <div style={{ display: 'flex', gap: 4, background: 'var(--line-2)', borderRadius: 4, padding: 3 }}>
            {filterOpts.map(opt => {
              const active = recentFilter === opt.key;
              return (
                <button key={opt.key} onClick={() => setRecentFilter(opt.key)}
                  style={{ padding: '4px 12px', borderRadius: 3, border: 'none', cursor: 'pointer', fontSize: 11.5, fontFamily: 'inherit', fontWeight: active ? 600 : 400,
                    background: active ? '#fff' : 'transparent',
                    color: active ? (opt.key === 'close_lost' ? '#b8492f' : opt.key === 'close_cancel' ? '#a06000' : 'var(--ink-1)') : 'var(--ink-3)',
                    boxShadow: active ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
                    transition: 'all 0.15s',
                  }}>
                  {opt.label}
                  <span style={{ marginLeft: 5, fontSize: 10, opacity: 0.7 }}>({opt.count})</span>
                </button>
              );
            })}
          </div>
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
                {['Account','Product','Segment','Department','Deal Value','Status','Close Date','Cycle (วัน)','Owner'].map(h => (
                  <th key={h} style={{ padding: '8px 12px', textAlign: 'left', fontWeight: 500, whiteSpace: 'nowrap' }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {filtered.length === 0 && <tr><td colSpan={9} style={{ padding: '24px 12px', color: 'var(--ink-3)', textAlign: 'center' }}>ไม่มีข้อมูลสำหรับ filter นี้</td></tr>}
              {filtered.map((d, i) => (
                <tr key={i} style={{ borderBottom: '1px solid var(--line-2)' }}
                  onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                  <td style={{ padding: '8px 12px', fontWeight: 500, maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.account_name || '—'}</td>
                  <td style={{ padding: '8px 12px', color: 'var(--ink-2)', maxWidth: 140, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.product || '—'}</td>
                  <td style={{ padding: '8px 12px', color: 'var(--ink-2)' }}>{d.segment || '—'}</td>
                  <td style={{ padding: '8px 12px', color: 'var(--ink-2)', maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.department || '—'}</td>
                  <td style={{ padding: '8px 12px', fontVariantNumeric: 'tabular-nums', fontWeight: 500 }}>{fmtM(d.deal_value)}</td>
                  <td style={{ padding: '8px 12px' }}>
                    <span style={{ fontSize: 10.5, padding: '2px 8px', borderRadius: 10, fontWeight: 600,
                      background: isCancel(d.opportunity_status) ? '#fef3cd' : '#f9eeeb',
                      color:      isCancel(d.opportunity_status) ? '#a06000' : '#b8492f' }}>
                      {d.opportunity_status || 'Lost'}
                    </span>
                  </td>
                  <td style={{ padding: '8px 12px', color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>{d.close_date ? new Date(d.close_date).toLocaleDateString('th-TH', { year: '2-digit', month: 'short', day: 'numeric' }) : '—'}</td>
                  <td style={{ padding: '8px 12px', textAlign: 'center', color: 'var(--ink-2)' }}>{d.cycle_days != null ? d.cycle_days : '—'}</td>
                  <td style={{ padding: '8px 12px', color: 'var(--ink-2)', maxWidth: 120, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.owner || '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  };

  // ── Lost Reason Analysis section ───────────────────────────────────────
  const SectionLossPattern = () => {
    const totalLostDeals = kpis.lost_count || 1;

    // ── Derive reason categories from existing data ──
    const clCount  = byDept.reduce((s, d) => s + (d.closeLost?.count  || 0), 0);
    const ccCount  = byDept.reduce((s, d) => s + (d.closeCancel?.count || 0), 0);
    const clValue  = byDept.reduce((s, d) => s + (d.closeLost?.value  || 0), 0);
    const ccValue  = byDept.reduce((s, d) => s + (d.closeCancel?.value || 0), 0);
    const fastB    = cycleTimeBuckets.find(b => b.key === 'fast')   || { count: 0, value: 0 };
    const midB     = cycleTimeBuckets.find(b => b.key === 'medium') || { count: 0, value: 0 };
    const longB    = cycleTimeBuckets.find(b => b.key === 'long')   || { count: 0, value: 0 };
    const vLongB   = cycleTimeBuckets.find(b => b.key === 'very_long') || { count: 0, value: 0 };

    const reasonCards = [
      {
        key: 'close_lost',
        icon: '🏆',
        title: 'แพ้คู่แข่ง',
        subtitle: 'Close Lost',
        desc: 'Deal ที่แพ้ให้คู่แข่งหรือลูกค้าเลือก solution อื่น',
        count: clCount,
        value: clValue,
        pct: Math.round(clCount / totalLostDeals * 100),
        color: '#b8492f',
        bg: '#fdf0ec',
      },
      {
        key: 'close_cancel',
        icon: '🚫',
        title: 'ลูกค้ายกเลิก',
        subtitle: 'Close Cancel',
        desc: 'Deal ที่ถูกยกเลิก — อาจเกิดจาก budget freeze, priority เปลี่ยน',
        count: ccCount,
        value: ccValue,
        pct: Math.round(ccCount / totalLostDeals * 100),
        color: '#a06000',
        bg: '#fef8ec',
      },
      {
        key: 'budget_fit',
        icon: '⚡',
        title: 'Budget / Fit ไม่ตรง',
        subtitle: 'ปฏิเสธเร็ว < 30 วัน',
        desc: 'Deal ที่แพ้ภายใน 30 วัน — มักเป็นปัญหา budget หรือ product ไม่ตรงความต้องการ',
        count: fastB.count,
        value: fastB.value,
        pct: Math.round(fastB.count / totalLostDeals * 100),
        color: '#1565c0',
        bg: '#eef4fd',
      },
      {
        key: 'competitive',
        icon: '⚔️',
        title: 'สูญเสียเชิงแข่งขัน',
        subtitle: 'Cycle 30–90 วัน',
        desc: 'Deal ที่ผ่านการพิจารณาแต่แพ้ขั้นตัดสินใจ — มักเป็น competitive situation',
        count: midB.count,
        value: midB.value,
        pct: Math.round(midB.count / totalLostDeals * 100),
        color: '#6a1a6a',
        bg: '#f9eef9',
      },
      {
        key: 'complex_decision',
        icon: '🏛️',
        title: 'Decision Process ซับซ้อน',
        subtitle: 'Cycle > 90 วัน',
        desc: 'Deal ที่ใช้เวลานาน — มักติด committee, procurement หรือ multi-stakeholder',
        count: longB.count + vLongB.count,
        value: longB.value + vLongB.value,
        pct: Math.round((longB.count + vLongB.count) / totalLostDeals * 100),
        color: '#2e6b4f',
        bg: '#edf7f2',
      },
    ];

    // Win/Loss bar component
    const WinLossBar = ({ row }) => {
      const wonW  = row.total > 0 ? Math.round((row.wonCount  / row.total) * 100) : 0;
      const lostW = row.total > 0 ? Math.round((row.lostCount / row.total) * 100) : 0;
      return (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
          <div style={{ width: 140, fontSize: 11.5, color: 'var(--ink-2)', textAlign: 'right', flexShrink: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{row.seg}</div>
          <div style={{ flex: 1, height: 22, borderRadius: 3, overflow: 'hidden', display: 'flex' }}>
            <div style={{ width: `${wonW}%`,  height: '100%', background: '#2e7d52' }} title={`Won: ${row.wonCount.toLocaleString()} deals`} />
            <div style={{ width: `${lostW}%`, height: '100%', background: '#b8492f' }} title={`Lost: ${row.lostCount.toLocaleString()} deals`} />
            {wonW + lostW < 100 && <div style={{ flex: 1, height: '100%', background: 'var(--line-2)' }} />}
          </div>
          <div style={{ width: 130, fontSize: 11, textAlign: 'right', flexShrink: 0, fontVariantNumeric: 'tabular-nums', display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
            <span style={{ color: '#2e7d52', fontWeight: 600 }}>{wonW}% won</span>
            <span style={{ color: 'var(--ink-3)' }}>·</span>
            <span style={{ color: '#b8492f', fontWeight: 600 }}>{lostW}% lost</span>
          </div>
        </div>
      );
    };

    // Cycle time bar
    const maxCycle = Math.max(...cycleTimeBuckets.map(b => b.count), 1);
    const CycleBar = ({ b }) => {
      const pct = Math.round((b.count / maxCycle) * 100);
      const sharePct = Math.round(b.count / totalLostDeals * 100);
      return (
        <div style={{ marginBottom: 12 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
            <span style={{ fontSize: 12, fontWeight: 600 }}>{b.label}</span>
            <span style={{ fontSize: 11.5, fontVariantNumeric: 'tabular-nums', color: 'var(--ink-2)' }}>
              <strong>{b.count.toLocaleString()}</strong> deals
              <span style={{ color: 'var(--ink-3)', marginLeft: 4 }}>({sharePct}%)</span>
            </span>
          </div>
          <div style={{ height: 12, background: 'var(--line-2)', borderRadius: 2, overflow: 'hidden', marginBottom: 4 }}>
            <div style={{ width: `${pct}%`, height: '100%', background: '#b8492f', opacity: 0.55 + (pct / 100) * 0.45 }} />
          </div>
          <div style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{b.hint}</div>
        </div>
      );
    };

    // Deal size tiles
    const totalSizeDeals = dealSizeBuckets.reduce((s, b) => s + b.count, 0) || 1;
    const totalSizeValue = dealSizeBuckets.reduce((s, b) => s + b.value, 0) || 1;
    const sizeColors = { zero: '#aaa', small: '#e8a87c', medium: '#b8492f', large: '#8c3520', enterprise: '#5c1a0a' };
    const visibleBuckets = dealSizeBuckets.filter(b => b.key !== 'zero' || b.count > 0);

    return (
      <div style={{ background: '#fff', border: '1px solid var(--line)', borderRadius: 4, padding: 20 }}>

        {/* Header */}
        <div style={{ marginBottom: 20 }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Lost Reason Analysis</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>วิเคราะห์สาเหตุที่แพ้ deal — จำแนกตามประเภทและ pattern</div>
        </div>

        {/* ── Reason Category Cards ── */}
        <div style={{ marginBottom: 24 }}>
          <div style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 12, textTransform: 'uppercase', letterSpacing: '0.5px' }}>สาเหตุที่แพ้ (Reason Categories)</div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 10 }}>
            {reasonCards.map(r => (
              <div key={r.key} style={{ border: `1px solid ${r.color}30`, borderTop: `3px solid ${r.color}`, borderRadius: 4, padding: '12px 14px', background: r.bg }}>
                <div style={{ fontSize: 18, marginBottom: 6 }}>{r.icon}</div>
                <div style={{ fontSize: 12, fontWeight: 700, color: r.color, marginBottom: 2 }}>{r.title}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', marginBottom: 10 }}>{r.subtitle}</div>
                <div style={{ fontSize: 22, fontWeight: 800, color: 'var(--ink-1)', fontVariantNumeric: 'tabular-nums', lineHeight: 1 }}>{r.count.toLocaleString()}</div>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2, marginBottom: 6 }}>{r.pct}% ของ loss ทั้งหมด</div>
                <div style={{ height: 3, background: `${r.color}20`, borderRadius: 2, overflow: 'hidden', marginBottom: 6 }}>
                  <div style={{ width: `${r.pct}%`, height: '100%', background: r.color }} />
                </div>
                <div style={{ fontSize: 11, fontVariantNumeric: 'tabular-nums', fontWeight: 600, color: r.color }}>{fmtM(r.value)}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 4, lineHeight: 1.4 }}>{r.desc}</div>
              </div>
            ))}
          </div>
        </div>

        {/* ── Win Rate + Cycle Time ── */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, borderTop: '1px solid var(--line)', paddingTop: 18, marginBottom: 24 }}>
          <div>
            <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 12, display: 'flex', alignItems: 'center', gap: 10 }}>
              Win Rate by Segment
              <span style={{ display: 'flex', gap: 8 }}>
                {[['#2e7d52','Won'],['#b8492f','Lost']].map(([c,l]) => (
                  <span key={l} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 10.5, fontWeight: 400 }}>
                    <span style={{ width: 8, height: 8, borderRadius: 2, background: c, display: 'inline-block' }} />{l}
                  </span>
                ))}
              </span>
            </div>
            {winLossBySegment.map((row, i) => <WinLossBar key={i} row={row} />)}
            <div style={{ marginTop: 10, fontSize: 10.5, color: 'var(--ink-3)', lineHeight: 1.5 }}>
              * เปรียบเทียบเฉพาะ Closed Won vs Closed Lost (ไม่รวม Open deals)
            </div>
          </div>

          <div>
            <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 12 }}>
              Cycle Time to Loss
              <span style={{ fontSize: 10.5, fontWeight: 400, color: 'var(--ink-3)', marginLeft: 8 }}>ตั้งแต่ Create ถึง Close Lost</span>
            </div>
            {cycleTimeBuckets.map((b, i) => <CycleBar key={i} b={b} />)}
          </div>
        </div>

        {/* ── Deal Size ── */}
        <div style={{ borderTop: '1px solid var(--line)', paddingTop: 18 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 14 }}>
            Deal Size Distribution
            <span style={{ fontSize: 10.5, fontWeight: 400, color: 'var(--ink-3)', marginLeft: 8 }}>Deal ขนาดไหนที่แพ้บ่อย — volume vs value</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${visibleBuckets.length}, 1fr)`, gap: 10 }}>
            {visibleBuckets.map(b => (
              <div key={b.key} style={{ border: '1px solid var(--line)', borderRadius: 4, padding: '12px 14px', borderTop: `3px solid ${sizeColors[b.key]}` }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 2 }}>{b.label}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', marginBottom: 10 }}>{b.range}</div>
                <div style={{ fontSize: 20, fontWeight: 800, fontVariantNumeric: 'tabular-nums', marginBottom: 2 }}>{b.count.toLocaleString()}</div>
                <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginBottom: 6 }}>{Math.round(b.count / totalSizeDeals * 100)}% deals</div>
                <div style={{ height: 4, background: 'var(--line-2)', borderRadius: 2, marginBottom: 8, overflow: 'hidden' }}>
                  <div style={{ width: `${Math.round(b.count / totalSizeDeals * 100)}%`, height: '100%', background: sizeColors[b.key] }} />
                </div>
                <div style={{ fontSize: 11, fontWeight: 600, fontVariantNumeric: 'tabular-nums', color: sizeColors[b.key] }}>{fmtM(b.value)}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)' }}>{Math.round(b.value / totalSizeValue * 100)}% of value</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    );
  };

  const SECTION_MAP = { dept_product: SectionDeptProduct, trend: SectionTrend, heatmap: SectionHeatmap, loss_pattern: SectionLossPattern, recent: SectionRecent };

  const btnStyle = { display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)', cursor: 'pointer', fontFamily: 'inherit' };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {/* Page header */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
        <div>
          <h1 style={{ fontSize: 24, fontWeight: 700, letterSpacing: '-0.3px', marginBottom: 4 }}>Lost Analysis</h1>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <span>วิเคราะห์ deal ที่แพ้ (Close Lost + Close Cancel) · ข้อมูลจาก Pipeline</span>
            {data?.data_as_of && (() => {
              const d = new Date(data.data_as_of);
              const label = d.toLocaleString('en', { day: 'numeric', month: 'short', year: 'numeric' });
              return (
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: '#eef2ff', color: '#2d3a8c', border: '1px solid #c7d0f5', borderRadius: 4, padding: '2px 8px', fontSize: 11.5, fontWeight: 600, letterSpacing: '0.01em' }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#2d3a8c', flexShrink: 0 }} />
                  อัปเดตล่าสุด: {label}
                </span>
              );
            })()}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={() => { setPendingCards([...cards]); setShowAddKPI(true); }} style={btnStyle}>
            <Icon name="plus" size={11} /> Add KPI
          </button>
          <button onClick={() => { setPendingSections([...lostSections]); setShowCustomize(true); }} style={btnStyle}>
            <Icon name="drag" size={11} /> Customize
          </button>
        </div>
      </div>

      {/* KPI strip — only visible cards */}
      {visibleCards.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(visibleCards.length, 6)}, 1fr)`, gap: 12 }}>
          {visibleCards.map(c => <KPICard key={c.id} id={c.id} />)}
        </div>
      )}

      {/* Metric toggle */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>แสดงผลโดย:</span>
        {[['value','มูลค่า (฿)'],['count','จำนวน deals']].map(([v,l]) => (
          <button key={v} onClick={() => setMetric(v)}
            style={{ padding: '5px 12px', border: '1px solid var(--line)', borderRadius: 4, fontSize: 11.5,
              background: metric === v ? 'var(--ink)' : '#fff', color: metric === v ? '#fff' : 'var(--ink-2)',
              cursor: 'pointer', fontWeight: metric === v ? 500 : 400, fontFamily: 'inherit' }}>
            {l}
          </button>
        ))}
      </div>

      {/* Sections — rendered in user-defined order.
          Call render fn directly (not <Comp/>) so React doesn't treat new
          function references as different component types → no unmount/scroll. */}
      {lostSections.filter(s => s.visible).map(s => {
        const renderFn = SECTION_MAP[s.id];
        return renderFn ? <React.Fragment key={s.id}>{renderFn()}</React.Fragment> : null;
      })}

      {/* ── Add KPI Modal ── */}
      <Modal
        open={showAddKPI}
        onClose={() => setShowAddKPI(false)}
        title="Add KPI"
        subtitle="ลาก ⠿ เพื่อเรียงลำดับ · toggle เพื่อแสดง/ซ่อน card"
        width={440}
        footer={<>
          <button onClick={() => setShowAddKPI(false)} style={{ padding: '8px 14px', border: '1px solid var(--line)', fontSize: 12, borderRadius: 3, fontFamily: 'inherit', cursor: 'pointer' }}>Cancel</button>
          <button onClick={() => {
            setCards([...pendingCards]);
            try { localStorage.setItem(lostCardsKey, JSON.stringify(pendingCards)); } catch {}
            setShowAddKPI(false);
          }} style={{ padding: '8px 14px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3, fontFamily: 'inherit', cursor: 'pointer' }}>Apply</button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {pendingCards.map((c, idx) => (
            <div
              key={c.id}
              draggable
              onDragStart={() => { cardDragIdx.current = idx; }}
              onDragOver={e => {
                e.preventDefault();
                if (cardDragOverIdx.current !== idx) {
                  cardDragOverIdx.current = idx;
                  const arr = [...pendingCards];
                  const [moved] = arr.splice(cardDragIdx.current, 1);
                  arr.splice(idx, 0, moved);
                  cardDragIdx.current = idx;
                  setPendingCards(arr);
                }
              }}
              onDragEnd={() => { cardDragIdx.current = null; cardDragOverIdx.current = null; }}
              style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '11px 12px',
                border: '1px solid var(--line)', borderRadius: 3,
                background: c.visible ? 'var(--panel)' : '#f7f5f0',
                cursor: 'grab', userSelect: 'none',
                opacity: c.visible ? 1 : 0.55,
                transition: 'opacity 120ms, background 120ms',
              }}>
              <span style={{ color: 'var(--ink-3)', fontSize: 14, lineHeight: 1 }}>⠿</span>
              <span style={{ fontSize: 10, fontFamily: 'IBM Plex Mono', color: 'var(--ink-3)', minWidth: 16, textAlign: 'center' }}>
                {c.visible ? pendingCards.filter((x, xi) => x.visible && xi <= idx).length : '—'}
              </span>
              <span style={{ flex: 1, fontSize: 12.5, fontWeight: 500, color: 'var(--ink)' }}>{c.label}</span>
              <div
                onClick={() => setPendingCards(prev => prev.map((x, xi) => xi === idx ? { ...x, visible: !x.visible } : x))}
                style={{ position: 'relative', width: 32, height: 18, borderRadius: 9, cursor: 'pointer',
                  background: c.visible ? 'var(--accent)' : '#c8c5bc', transition: 'background 150ms', flexShrink: 0 }}>
                <div style={{ position: 'absolute', top: 3, left: c.visible ? 16 : 3,
                  width: 12, height: 12, borderRadius: '50%', background: '#fff',
                  transition: 'left 150ms', boxShadow: '0 1px 3px rgba(0,0,0,0.2)' }} />
              </div>
            </div>
          ))}
          <div style={{ marginTop: 6, fontSize: 11, color: 'var(--ink-3)' }}>
            {pendingCards.filter(c => c.visible).length} cards จะแสดงบน Lost Analysis · บันทึกแยกตาม user
          </div>
        </div>
      </Modal>

      {/* ── Customize Sections Modal ── */}
      <Modal
        open={showCustomize}
        onClose={() => setShowCustomize(false)}
        title="Customize layout"
        subtitle="ลาก ⠿ เพื่อเรียงลำดับ · toggle เพื่อแสดง/ซ่อน section"
        width={480}
        footer={<>
          <button onClick={() => setShowCustomize(false)} style={{ padding: '8px 14px', border: '1px solid var(--line)', fontSize: 12, borderRadius: 3, fontFamily: 'inherit', cursor: 'pointer' }}>Cancel</button>
          <button onClick={() => {
            setLostSections([...pendingSections]);
            try { localStorage.setItem(lostSectionsKey, JSON.stringify(pendingSections)); } catch {}
            setShowCustomize(false);
          }} style={{ padding: '8px 14px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3, fontFamily: 'inherit', cursor: 'pointer' }}>Apply</button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {pendingSections.map((s, idx) => (
            <div
              key={s.id}
              draggable
              onDragStart={() => { sectDragIdx.current = idx; }}
              onDragOver={e => {
                e.preventDefault();
                if (sectDragOverIdx.current !== idx) {
                  sectDragOverIdx.current = idx;
                  const arr = [...pendingSections];
                  const [moved] = arr.splice(sectDragIdx.current, 1);
                  arr.splice(idx, 0, moved);
                  sectDragIdx.current = idx;
                  setPendingSections(arr);
                }
              }}
              onDragEnd={() => { sectDragIdx.current = null; sectDragOverIdx.current = null; }}
              style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '11px 12px',
                border: '1px solid var(--line)', borderRadius: 3,
                background: s.visible ? 'var(--panel)' : '#f7f5f0',
                cursor: 'grab', userSelect: 'none',
                opacity: s.visible ? 1 : 0.55,
                transition: 'opacity 120ms, background 120ms',
              }}>
              <span style={{ color: 'var(--ink-3)', fontSize: 14, lineHeight: 1 }}>⠿</span>
              <span style={{ fontSize: 10, fontFamily: 'IBM Plex Mono', color: 'var(--ink-3)', minWidth: 16, textAlign: 'center' }}>
                {s.visible ? pendingSections.filter((x, xi) => x.visible && xi <= idx).length : '—'}
              </span>
              <span style={{ flex: 1, fontSize: 12.5, fontWeight: 500, color: 'var(--ink)' }}>{s.label}</span>
              <div
                onClick={() => setPendingSections(prev => prev.map((x, xi) => xi === idx ? { ...x, visible: !x.visible } : x))}
                style={{ position: 'relative', width: 32, height: 18, borderRadius: 9, cursor: 'pointer',
                  background: s.visible ? 'var(--accent)' : '#c8c5bc', transition: 'background 150ms', flexShrink: 0 }}>
                <div style={{ position: 'absolute', top: 3, left: s.visible ? 16 : 3,
                  width: 12, height: 12, borderRadius: '50%', background: '#fff',
                  transition: 'left 150ms', boxShadow: '0 1px 3px rgba(0,0,0,0.2)' }} />
              </div>
            </div>
          ))}
          <div style={{ marginTop: 6, fontSize: 11, color: 'var(--ink-3)' }}>
            {pendingSections.filter(s => s.visible).length} sections จะแสดงบน Lost Analysis · บันทึกแยกตาม user
          </div>
        </div>
      </Modal>
    </div>
  );
};

const PipelineDashboardView = ({ userId, stageFilter }) => {
  const [summary, setSummary]         = useState(null);
  const [opportunities, setOpps]      = useState([]);
  const [loading, setLoading]         = useState(true);
  const [oppsTotal, setOppsTotal]     = useState(0);
  const [uploading, setUploading]     = useState(false);
  const [uploadMsg, setUploadMsg]     = useState(null);
  const [showAllOpps, setShowAllOpps] = useState(false);
  const fileRef = useRef(null);

  // Group drill-down state
  const [groupData, setGroupData]         = useState([]);
  const [selectedGroup, setSelectedGroup] = useState(null);   // group_name string
  const [selectedProduct, setSelectedProduct] = useState(null); // product_name string
  const [groupDeals, setGroupDeals]       = useState([]);
  const [groupDealsTotal, setGroupDealsTotal] = useState(0);
  const [loadingDeals, setLoadingDeals]   = useState(false);
  const [groupMetric, setGroupMetric]     = useState('pipeline'); // 'pipeline'|'weighted'|'deals'
  const [dealsStageFilter, setDealsStageFilter] = useState(null); // null = all stages
  const [dealsSort, setDealsSort] = useState({ col: 'deal_value', dir: 'desc' });

  // Customize layout
  const plLayoutKey = `pipeline_layout_${userId || 'default'}`;
  const [plSections, setPlSections] = useState(() => {
    try {
      const saved = localStorage.getItem(`pipeline_layout_${userId || 'default'}`);
      if (saved) {
        let parsed = JSON.parse(saved);
        // Migrate: collapse old 'trend' + 'opps' into 'trend_opps'
        const hasTrend = parsed.some(s => s.id === 'trend');
        const hasOpps  = parsed.some(s => s.id === 'opps');
        if (hasTrend || hasOpps) {
          const trendVisible = parsed.find(s => s.id === 'trend')?.visible !== false;
          const oppsVisible  = parsed.find(s => s.id === 'opps')?.visible !== false;
          // Insert trend_opps where 'trend' was (or at end), remove both old entries
          const idx = parsed.findIndex(s => s.id === 'trend');
          const merged = { id: 'trend_opps', label: 'Trend by Segment & Top Opportunities', visible: trendVisible || oppsVisible };
          parsed = parsed.filter(s => s.id !== 'trend' && s.id !== 'opps');
          if (idx >= 0) parsed.splice(idx, 0, merged);
          else parsed.push(merged);
        }
        const savedIds = new Set(parsed.map(s => s.id));
        const newSects = DEFAULT_PIPELINE_SECTIONS.filter(s => !savedIds.has(s.id));
        return [...parsed, ...newSects];
      }
    } catch {}
    return DEFAULT_PIPELINE_SECTIONS;
  });
  const [pendingPlSections, setPendingPlSections] = useState(DEFAULT_PIPELINE_SECTIONS);
  const [showPlCustomize, setShowPlCustomize]     = useState(false);
  const plDragIdx     = useRef(null);
  const plDragOverIdx = useRef(null);
  const plVisible = (id) => plSections.find(s => s.id === id)?.visible !== false;

  // Created trend data
  const [createdTrend, setCreatedTrend] = useState(null);
  useEffect(() => {
    const sp = stageFilter ? `?stage=${encodeURIComponent(stageFilter)}` : '';
    authFetch(`/api/pipeline/created-trend${sp}`)
      .then(r => r.json())
      .then(d => setCreatedTrend(d))
      .catch(() => {});
  }, [stageFilter]);

  // Use group_color from API (Settings groups), fall back to palette if missing
  const groupColorMap = useMemo(() => {
    const m = {};
    groupData.forEach((g, i) => {
      m[g.group_name] = g.group_color || PIPELINE_GROUP_PALETTE[i % PIPELINE_GROUP_PALETTE.length];
    });
    return m;
  }, [groupData]);

  const loadData = async () => {
    setLoading(true);
    const sp = stageFilter ? `stage=${encodeURIComponent(stageFilter)}` : '';
    const sep = sp ? '&' : '';
    try {
      const [summRes, oppsRes, groupsRes] = await Promise.all([
        authFetch(`/api/pipeline${sp ? '?' + sp : ''}`).then(r => r.json()),
        authFetch(`/api/pipeline/opportunities?limit=8${sp ? sep + sp : ''}`).then(r => r.json()),
        authFetch(`/api/pipeline/groups${sp ? '?' + sp : ''}`).then(r => r.json()),
      ]);
      setSummary(summRes);
      setOpps(oppsRes.deals || []);
      setOppsTotal(oppsRes.total || 0);
      setGroupData(Array.isArray(groupsRes) ? groupsRes : []);
    } catch (e) {
      console.error('Pipeline load error:', e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { loadData(); }, [stageFilter]);

  // Fetch deals when product selected (or stage filter changes)
  useEffect(() => {
    if (!selectedProduct || !selectedGroup) { setGroupDeals([]); setGroupDealsTotal(0); return; }
    setDealsStageFilter(null);
    setDealsSort({ col: 'deal_value', dir: 'desc' });
    setLoadingDeals(true);
    const params = new URLSearchParams({ group: selectedGroup, product: selectedProduct, limit: 50, sort: 'deal_value' });
    if (stageFilter) params.set('stage', stageFilter);
    authFetch(`/api/pipeline/opportunities?${params}`)
      .then(r => r.json())
      .then(d => { setGroupDeals(d.deals || []); setGroupDealsTotal(d.total || 0); })
      .catch(() => { setGroupDeals([]); setGroupDealsTotal(0); })
      .finally(() => setLoadingDeals(false));
  }, [selectedGroup, selectedProduct, stageFilter]);

  const handleUpload = async (file) => {
    if (!file) return;
    setUploading(true);
    setUploadMsg(null);
    const fd = new FormData();
    fd.append('file', file);
    try {
      const r = await authFetch('/api/pipeline/upload', { method: 'POST', body: fd });
      const data = await r.json();
      if (!r.ok) throw new Error(data.error || 'Upload failed');
      setUploadMsg({ ok: true, text: `นำเข้า ${data.inserted} deals สำเร็จ (ข้าม ${data.skipped} แถว)` });
      await loadData();
    } catch (e) {
      setUploadMsg({ ok: false, text: e.message });
    } finally {
      setUploading(false);
      if (fileRef.current) fileRef.current.value = '';
    }
  };

  const kpi = summary?.kpi || {};
  const stages = summary?.stages || [];
  const heatmap = summary?.heatmap || { rows: [], departments: [], groups: [], divisions: [] };
  const forecast = summary?.forecast || [];

  // Compute max deal_value for funnel scaling
  const maxFunnelVal = Math.max(...stages.map(s => parseFloat(s.total_value) || 0), 1);
  const maxStageCount = Math.max(...stages.map(s => parseInt(s.deal_count) || 0), 1);

  // Heatmap division filter + sort state
  const [heatmapDivision, setHeatmapDivision] = React.useState(null);
  const [heatSort, setHeatSort] = React.useState({ col: null, dir: 'desc' }); // col: null=default, 'vertical', 'total', or group name

  const toggleHeatSort = (col) => setHeatSort(prev =>
    prev.col === col ? { col, dir: prev.dir === 'desc' ? 'asc' : 'desc' } : { col, dir: 'desc' }
  );

  // Heat intensity: filtered rows, max weighted value across all cells
  const heatFilteredRows = heatmapDivision
    ? heatmap.rows.filter(r => r.division === heatmapDivision)
    : heatmap.rows;
  const heatValues = heatFilteredRows.map(r => parseFloat(r.value) || 0);
  const maxHeat = Math.max(...heatValues, 1);

  // Forecast chart
  const maxForecast = Math.max(...forecast.map(f => Math.max(f.forecast, f.closed_won)), 1);
  const CHART_H = 110;

  const StagePill = ({ stage }) => {
    const style = STAGE_PILLS[stage] || { bg: '#f0eeea', color: '#4a4f5a' };
    return (
      <span style={{ display:'inline-flex', alignItems:'center', padding:'2px 8px', borderRadius:10, fontSize:11, fontWeight:500, background:style.bg, color:style.color, whiteSpace:'nowrap' }}>
        {stage}
      </span>
    );
  };

  const cardStyle = { background:'var(--panel)', border:'1px solid var(--line)', borderRadius:10, padding:'18px 20px' };

  if (loading) return (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height:300, color:'var(--ink-3)' }}>
      กำลังโหลดข้อมูล pipeline…
    </div>
  );

  const hasData = parseFloat(kpi.total_pipeline || 0) > 0 || stages.length > 0;

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:20 }}>

      {/* Page header */}
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-end' }}>
        <div>
          <h1 className="serif" style={{ fontSize:28, fontWeight:500, letterSpacing:'-0.02em', marginBottom:4 }}>Pipeline Dashboard</h1>
          <div style={{ fontSize:13, color:'var(--ink-3)', display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
            <span>Sales pipeline analysis · Active opportunities</span>
            {summary?.data_as_of && (() => {
              const d = new Date(summary.data_as_of);
              const label = d.toLocaleString('en', { day: 'numeric', month: 'short', year: 'numeric' });
              return (
                <span style={{ display:'inline-flex', alignItems:'center', gap:5, background:'#eef2ff', color:'#2d3a8c', border:'1px solid #c7d0f5', borderRadius:4, padding:'2px 8px', fontSize:11.5, fontWeight:600, letterSpacing:'0.01em' }}>
                  <span style={{ width:6, height:6, borderRadius:'50%', background:'#2d3a8c', flexShrink:0 }} />
                  อัปเดตล่าสุด: {label}
                </span>
              );
            })()}
          </div>
        </div>
        <div style={{ display:'flex', gap:8, alignItems:'center' }}>
          {uploadMsg && (
            <div style={{ fontSize:12, padding:'6px 12px', borderRadius:6, background: uploadMsg.ok ? '#e8f5ed' : '#fdecea', color: uploadMsg.ok ? '#1f7a4d' : '#b8492f', border:`1px solid ${uploadMsg.ok ? '#a3d9b8' : '#f5b8b0'}` }}>
              {uploadMsg.text}
            </div>
          )}
          <input ref={fileRef} type="file" accept=".xlsx,.xls,.csv" style={{ display:'none' }} onChange={e => handleUpload(e.target.files[0])} />
          <button
            onClick={() => { setPendingPlSections([...plSections]); setShowPlCustomize(true); }}
            style={{ display:'flex', alignItems:'center', gap:6, padding:'6px 12px', border:'1px solid var(--line)', borderRadius:6, fontSize:12, color:'var(--ink-2)', background:'var(--panel)', cursor:'pointer', fontFamily:'inherit' }}>
            <Icon name="drag" size={11} /> Customize
          </button>
        </div>
      </div>

      {/* Empty state */}
      {!hasData && (
        <div style={{ ...cardStyle, padding:48, textAlign:'center' }}>
          <div style={{ fontSize:32, marginBottom:12 }}>📋</div>
          <div style={{ fontSize:15, fontWeight:500, color:'var(--ink)', marginBottom:6 }}>ยังไม่มีข้อมูล Pipeline</div>
          <div style={{ fontSize:13, color:'var(--ink-3)', marginBottom:20 }}>กด "Load Excel" เพื่อนำเข้าไฟล์ pipeline deals</div>
          <div style={{ fontSize:12, color:'var(--ink-3)', background:'var(--bg)', border:'1px solid var(--line)', borderRadius:8, padding:'12px 20px', display:'inline-block', textAlign:'left' }}>
            <div style={{ fontWeight:500, marginBottom:6 }}>คอลัมน์ที่รองรับ (ชื่อยืดหยุ่น):</div>
            <div>Deal Name · Account · Segment · Vertical · Product</div>
            <div>Stage · Value (THB) · Probability % · Close Date · Owner</div>
          </div>
        </div>
      )}

      {hasData && (() => {
        const plBlocks = {
          kpi: () => (
            <>
            {/* KPI Cards */}
          <div style={{ display:'grid', gridTemplateColumns:'repeat(5,1fr)', gap:12 }}>
            {[
              { label:'Total Pipeline',     value: fmtMillion(kpi.total_pipeline),    sub:'active deals',           badge: null },
              { label:'Weighted Forecast',  value: fmtMillion(kpi.weighted_forecast), sub:'probability-weighted',   badge: null },
              { label:'Win Rate',           value: `${parseFloat(kpi.win_rate||0).toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})}%`, sub:'Closed Won / total closed', badge: null },
              { label:'Avg Deal Size',      value: fmtMillion(kpi.avg_deal_size),     sub:'per opportunity',        badge: null },
              { label:'Total Deals',        value: parseInt(kpi.total_deals||0).toLocaleString('en-US'), sub:'active opportunities', badge: null },
            ].map(c => (
              <div key={c.label} style={cardStyle}>
                <div style={{ fontSize:10.5, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:8 }}>{c.label}</div>
                <div className="num" style={{ fontSize:22, fontWeight:600, color:'var(--ink)', letterSpacing:'-0.02em' }}>{c.value}</div>
                <div style={{ fontSize:11, color:'var(--ink-3)', marginTop:4 }}>{c.sub}</div>
              </div>
            ))}
          </div>
            </>
          ),
          funnel: () => (
            <>
            {/* Stage Funnel + Forecast vs Target */}
          <div style={{ display:'grid', gridTemplateColumns:'2fr 3fr', gap:14 }}>

            {/* Stage Funnel */}
            <div style={cardStyle}>
              <div style={{ fontSize:13, fontWeight:600, marginBottom:2 }}>Stage Funnel</div>
              <div style={{ fontSize:11, color:'var(--ink-3)', marginBottom:16 }}>Pipeline by sales stage</div>
              <div style={{ display:'flex', flexDirection:'column', gap:9 }}>
                {PIPELINE_STAGE_ORDER.map(stageName => {
                  const row = stages.find(s => {
                    const n = (s.stage || '').toLowerCase();
                    const sn = stageName.toLowerCase();
                    return n === sn || (sn === 'poc / demo' && (n === 'poc' || n === 'demo' || n === 'poc / demo'));
                  });
                  if (!row && stages.length > 0) return null;
                  const val   = parseFloat(row?.total_value || 0);
                  const cnt   = parseInt(row?.deal_count || 0);
                  const pct   = maxFunnelVal > 0 ? (val / maxFunnelVal) * 100 : 0;
                  const color = PIPELINE_STAGE_COLORS[stageName] || '#888';
                  return (
                    <div key={stageName} style={{ display:'flex', alignItems:'center', gap:10 }}>
                      <div style={{ width:90, fontSize:11.5, color:'var(--ink-2)', flexShrink:0 }}>{stageName}</div>
                      <div style={{ flex:1, background:'var(--line-2)', borderRadius:4, height:24, overflow:'hidden' }}>
                        <div style={{ width:`${Math.max(pct,2)}%`, height:'100%', background:color, borderRadius:4, display:'flex', alignItems:'center', paddingLeft:8, transition:'width 0.4s ease', minWidth: val > 0 ? 60 : 0 }}>
                          {val > 0 && <span className="num" style={{ fontSize:10.5, color:'#fff', fontWeight:500, whiteSpace:'nowrap' }}>{fmtMillion(val)}</span>}
                        </div>
                      </div>
                      <div className="num" style={{ fontSize:11, color:'var(--ink-3)', width:28, textAlign:'right', flexShrink:0 }}>{cnt}</div>
                    </div>
                  );
                })}
                {/* Any unlisted stages */}
                {stages.filter(s => !PIPELINE_STAGE_ORDER.some(o => o.toLowerCase() === (s.stage||'').toLowerCase() || (s.stage||'').toLowerCase().startsWith('poc'))).map(row => (
                  <div key={row.stage} style={{ display:'flex', alignItems:'center', gap:10 }}>
                    <div style={{ width:90, fontSize:11.5, color:'var(--ink-2)', flexShrink:0 }}>{row.stage}</div>
                    <div style={{ flex:1, background:'var(--line-2)', borderRadius:4, height:24, overflow:'hidden' }}>
                      <div style={{ width:`${(parseFloat(row.total_value)/maxFunnelVal)*100}%`, height:'100%', background:'#888', borderRadius:4, display:'flex', alignItems:'center', paddingLeft:8 }}>
                        <span className="num" style={{ fontSize:10.5, color:'#fff', whiteSpace:'nowrap' }}>{fmtMillion(row.total_value)}</span>
                      </div>
                    </div>
                    <div className="num" style={{ fontSize:11, color:'var(--ink-3)', width:28, textAlign:'right' }}>{row.deal_count}</div>
                  </div>
                ))}
              </div>
            </div>

            {/* Monthly Forecast */}
            <div style={cardStyle}>
              <div style={{ fontSize:13, fontWeight:600, marginBottom:2 }}>Monthly Forecast</div>
              <div style={{ fontSize:11, color:'var(--ink-3)', marginBottom:12 }}>Weighted forecast vs Closed Won (฿M)</div>

              {/* Legend */}
              <div style={{ display:'flex', gap:16, marginBottom:14 }}>
                {[['#2d3a8c','Weighted Forecast'],['#1f7a4d','Closed Won']].map(([c,l]) => (
                  <div key={l} style={{ display:'flex', alignItems:'center', gap:5, fontSize:11, color:'var(--ink-3)' }}>
                    <div style={{ width:10, height:10, borderRadius:2, background:c }} />
                    {l}
                  </div>
                ))}
              </div>

              {forecast.length === 0 ? (
                <div style={{ height:CHART_H, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--ink-3)', fontSize:12 }}>ไม่มีข้อมูล close date</div>
              ) : (
                <div style={{ position:'relative', overflow:'hidden' }}>
                  <svg width="100%" height={CHART_H + 28} style={{ display:'block', overflow:'visible' }}>
                    {[0, 0.33, 0.66, 1].map(t => (
                      <line key={t} x1="0" y1={CHART_H * (1 - t)} x2="100%" y2={CHART_H * (1 - t)}
                        stroke="var(--line-2)" strokeWidth="1" strokeDasharray={t === 0 ? 'none' : '3,3'} />
                    ))}
                    {forecast.map((f, i) => {
                      const total = forecast.length;
                      const barW  = Math.max(Math.floor((100 / total) * 0.7), 3);
                      const slotW = 100 / total;
                      const cx    = slotW * i + slotW / 2;
                      const fH    = CHART_H * (f.forecast / maxForecast);
                      const wH    = CHART_H * (f.closed_won / maxForecast);
                      const isNow = f.month === new Date().toISOString().slice(0, 7);
                      return (
                        <g key={f.month}>
                          <rect x={`${cx - barW * 0.9}%`} y={CHART_H - fH} width={`${barW * 1.4}%`} height={fH} fill="#2d3a8c" opacity={isNow ? 0.95 : 0.75} rx="2" />
                          {f.closed_won > 0 && <rect x={`${cx - barW * 0.4}%`} y={CHART_H - wH} width={`${barW * 0.8}%`} height={wH} fill="#1f7a4d" opacity="0.9" rx="2" />}
                          <text x={`${cx}%`} y={CHART_H + 18} textAnchor="middle" fontSize="10" fill="var(--ink-3)" fontFamily="IBM Plex Mono, monospace">{f.month.slice(5)}</text>
                          {isNow && <text x={`${cx}%`} y={CHART_H + 28} textAnchor="middle" fontSize="8" fill="var(--accent)" fontFamily="Kanit, sans-serif">NOW</text>}
                        </g>
                      );
                    })}
                  </svg>
                </div>
              )}

              {forecast.length > 0 && (() => {
                const totalForecast = forecast.reduce((s, f) => s + f.forecast, 0);
                const totalWon      = forecast.reduce((s, f) => s + f.closed_won, 0);
                return (
                  <div style={{ display:'flex', gap:24, marginTop:14, paddingTop:14, borderTop:'1px solid var(--line-2)' }}>
                    {[['Total Forecast', fmtMillion(totalForecast)], ['Closed Won', fmtMillion(totalWon)]].map(([label, val]) => (
                      <div key={label}>
                        <div style={{ fontSize:10, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.06em' }}>{label}</div>
                        <div className="num" style={{ fontSize:17, fontWeight:600, color:'var(--ink)', marginTop:2 }}>{val}</div>
                      </div>
                    ))}
                  </div>
                );
              })()}
            </div>
          </div>
            </>
          ),
          heatmap: () => (
            <>
            {/* Heatmap: Product Group × Department – full width */}
          <div>

            {/* Heatmap: Product Group × Department */}
            <div style={cardStyle}>
              <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', marginBottom:2 }}>
                <div style={{ fontSize:13, fontWeight:600 }}>Pipeline by Product Group × Vertical</div>
                <div style={{ fontSize:11, color:'var(--ink-3)' }}>Weighted (฿M)</div>
              </div>

              {/* Division filter chips */}
              {heatmap.divisions.length > 0 && (
                <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginBottom:12, marginTop:8 }}>
                  <button
                    onClick={() => setHeatmapDivision(null)}
                    style={{ padding:'3px 10px', borderRadius:12, fontSize:11, fontWeight:500, border:'1px solid', cursor:'pointer', fontFamily:'inherit',
                      background: heatmapDivision === null ? 'var(--accent)' : 'transparent',
                      color:      heatmapDivision === null ? '#fff' : 'var(--ink-3)',
                      borderColor:heatmapDivision === null ? 'var(--accent)' : 'var(--line)' }}>
                    All
                  </button>
                  {heatmap.divisions.map(div => (
                    <button key={div}
                      onClick={() => setHeatmapDivision(div === heatmapDivision ? null : div)}
                      style={{ padding:'3px 10px', borderRadius:12, fontSize:11, fontWeight:500, border:'1px solid', cursor:'pointer', fontFamily:'inherit',
                        background: heatmapDivision === div ? 'var(--accent)' : 'transparent',
                        color:      heatmapDivision === div ? '#fff' : 'var(--ink-3)',
                        borderColor:heatmapDivision === div ? 'var(--accent)' : 'var(--line)' }}>
                      {div}
                    </button>
                  ))}
                </div>
              )}

              {heatmap.departments.length === 0 ? (
                <div style={{ color:'var(--ink-3)', fontSize:12 }}>ไม่มีข้อมูล department</div>
              ) : (() => {
                // Build row data with totals for sorting
                const heatRows = heatmap.departments
                  .map(dept => {
                    const deptRows = heatFilteredRows.filter(r => r.department === dept);
                    const rowTotal = deptRows.reduce((s, r) => s + parseFloat(r.value || 0), 0);
                    const groupVals = {};
                    heatmap.groups.forEach(grp => {
                      const cell = deptRows.find(r => r.group_name === grp);
                      groupVals[grp] = parseFloat(cell?.value || 0);
                    });
                    return { dept, rowTotal, groupVals };
                  })
                  .filter(r => r.rowTotal > 0);

                // Sort rows
                const sortedHeatRows = [...heatRows].sort((a, b) => {
                  let av, bv;
                  if (!heatSort.col || heatSort.col === 'total') {
                    av = a.rowTotal; bv = b.rowTotal;
                  } else if (heatSort.col === 'vertical') {
                    av = a.dept.toLowerCase(); bv = b.dept.toLowerCase();
                    return heatSort.dir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av);
                  } else {
                    av = a.groupVals[heatSort.col] || 0;
                    bv = b.groupVals[heatSort.col] || 0;
                  }
                  return heatSort.dir === 'desc' ? bv - av : av - bv;
                });

                const sortIcon = (col) => {
                  if (heatSort.col !== col) return <span style={{ opacity:0.3, fontSize:9, marginLeft:3 }}>↕</span>;
                  return <span style={{ fontSize:9, marginLeft:3, color:'var(--accent)' }}>{heatSort.dir === 'desc' ? '▼' : '▲'}</span>;
                };
                const thBase = { padding:'6px 8px', fontSize:10.5, color:'var(--ink-3)', fontWeight:500, borderBottom:'1px solid var(--line)', background:'var(--bg)', whiteSpace:'nowrap', cursor:'pointer', userSelect:'none' };

                return (
                  <div style={{ overflowX:'auto' }}>
                    <table style={{ borderCollapse:'collapse', width:'100%', fontSize:11.5 }}>
                      <thead>
                        <tr>
                          <th onClick={() => toggleHeatSort('vertical')}
                            style={{ ...thBase, textAlign:'left', minWidth:160 }}>
                            Vertical (Department){sortIcon('vertical')}
                          </th>
                          {heatmap.groups.map(g => (
                            <th key={g} onClick={() => toggleHeatSort(g)}
                              style={{ ...thBase, textAlign:'right' }}>
                              {g}{sortIcon(g)}
                            </th>
                          ))}
                          <th onClick={() => toggleHeatSort('total')}
                            style={{ ...thBase, textAlign:'right', borderLeft:'1px solid var(--line-2)', color: heatSort.col === 'total' ? 'var(--accent)' : 'var(--ink-3)' }}>
                            Total{sortIcon('total')}
                          </th>
                        </tr>
                      </thead>
                      <tbody>
                        {sortedHeatRows.map(({ dept, rowTotal, groupVals }) => (
                          <tr key={dept}>
                            <td style={{ padding:'6px 10px', fontWeight:500, color:'var(--ink-2)', borderBottom:'1px solid var(--line-2)', fontSize:11.5, whiteSpace:'nowrap' }}>{dept}</td>
                            {heatmap.groups.map(grp => {
                              const val = groupVals[grp] || 0;
                              const intensity = maxHeat > 0 ? val / maxHeat : 0;
                              const bg = `rgba(45,58,140,${(intensity * 0.5 + 0.05).toFixed(2)})`;
                              const textColor = intensity > 0.45 ? '#fff' : 'var(--ink)';
                              return (
                                <td key={grp} style={{ padding:'5px 8px', textAlign:'right', borderBottom:'1px solid var(--line-2)' }}>
                                  {val > 0 ? (
                                    <span className="num" style={{ display:'inline-block', padding:'2px 7px', borderRadius:4, background:bg, color:textColor, fontSize:11, whiteSpace:'nowrap' }}>
                                      {numFmt(val/1e6,0)}M
                                    </span>
                                  ) : (
                                    <span style={{ color:'var(--line)', fontSize:10 }}>—</span>
                                  )}
                                </td>
                              );
                            })}
                            <td style={{ padding:'5px 8px', textAlign:'right', borderBottom:'1px solid var(--line-2)', borderLeft:'1px solid var(--line-2)', fontWeight:600, fontSize:11, color:'var(--ink-2)' }}>
                              {numFmt(rowTotal/1e6,0)}M
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                );
              })()}
            </div>

          </div>
            </>
          ),
          groups: () => (
            <>
            {/* ── Pipeline by Product Group ─────────────────────────── */}
          {groupData.length > 0 && (() => {
            const totalMetric = groupData.reduce((s, g) => s + (groupMetric === 'deals' ? g.deals : g[groupMetric]), 0) || 1;
            const maxMetric   = Math.max(...groupData.map(g => groupMetric === 'deals' ? g.deals : g[groupMetric]), 1);
            const activeGroup = groupData.find(g => g.group_name === selectedGroup);
            const activeProducts = activeGroup?.products || [];
            const maxProductMetric = activeProducts.length
              ? Math.max(...activeProducts.map(p => groupMetric === 'deals' ? p.deals : p[groupMetric]), 1) : 1;
            // Display name for selected product (look up from products list via product_key)
            const selectedProductDisplay = activeProducts.find(p => p.product_key === selectedProduct)?.product_name || selectedProduct;
            // Stage filter helpers
            const stageCounts = groupDeals.reduce((acc, d) => { acc[d.stage] = (acc[d.stage]||0)+1; return acc; }, {});
            const availableStages = PIPELINE_STAGE_ORDER.filter(s => stageCounts[s]);
            const filteredDeals = (() => {
              const base = dealsStageFilter ? groupDeals.filter(d => d.stage === dealsStageFilter) : groupDeals;
              const { col, dir } = dealsSort;
              return [...base].sort((a, b) => {
                let av = a[col], bv = b[col];
                if (col === 'deal_value' || col === 'probability') { av = parseFloat(av)||0; bv = parseFloat(bv)||0; }
                else if (col === 'close_date') { av = av||''; bv = bv||''; }
                else { av = (av||'').toString().toLowerCase(); bv = (bv||'').toString().toLowerCase(); }
                if (av < bv) return dir === 'asc' ? -1 : 1;
                if (av > bv) return dir === 'asc' ? 1 : -1;
                return 0;
              });
            })();
            const toggleSort = (col) => setDealsSort(prev =>
              prev.col === col ? { col, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { col, dir: col === 'deal_value' || col === 'probability' ? 'desc' : 'asc' }
            );
            const sortIcon = (col) => {
              if (dealsSort.col !== col) return <span style={{ opacity:.25, marginLeft:2 }}>↕</span>;
              return <span style={{ marginLeft:2, color:'var(--accent)' }}>{dealsSort.dir === 'asc' ? '↑' : '↓'}</span>;
            };

            const MetricBtn = ({ id, label }) => (
              <button
                onClick={() => setGroupMetric(id)}
                style={{ padding:'4px 11px', fontSize:11.5, border:'1px solid var(--line)', borderRadius:5, background: groupMetric === id ? 'var(--ink)' : 'var(--panel)', color: groupMetric === id ? '#fff' : 'var(--ink-2)', cursor:'pointer', fontFamily:'inherit' }}>
                {label}
              </button>
            );

            const fmtMetric = (g) => {
              if (groupMetric === 'deals')    return `${g.deals} deals`;
              if (groupMetric === 'weighted') return fmtMillion(g.weighted);
              return fmtMillion(g.pipeline);
            };
            const metricVal = (g) => groupMetric === 'deals' ? g.deals : g[groupMetric];

            const pillStyle = (stage) => {
              const m = { Negotiation:{bg:'#fff3e3',c:'#c06010'}, Proposal:{bg:'#e8f0fb',c:'#2d3a8c'}, 'POC / Demo':{bg:'#f0fdf4',c:'#1f7a4d'}, Qualification:{bg:'#eaf0fd',c:'#3a5cb8'}, Prospecting:{bg:'#f4f4f8',c:'#6b6f78'}, 'Closed Won':{bg:'#e8f5ed',c:'#1f7a4d'}, 'Closed Lost':{bg:'#fdecea',c:'#b8492f'} };
              return m[stage] || { bg:'#f0eeea', c:'var(--ink-3)' };
            };

            return (
              <div style={{ background:'var(--panel)', border:'1px solid var(--line)', borderRadius:10, overflow:'hidden', marginBottom:0 }}>
                {/* Header */}
                <div style={{ padding:'16px 20px 12px', borderBottom:'1px solid var(--line-2)', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
                  <div>
                    <div style={{ fontSize:13, fontWeight:600, color:'var(--ink)' }}>Pipeline by Product Group</div>
                    <div style={{ fontSize:11, color:'var(--ink-3)', marginTop:2 }}>คลิก Group → Product เพื่อดูรายการ deals</div>
                  </div>
                  <div style={{ display:'flex', gap:6 }}>
                    <MetricBtn id="pipeline" label="Pipeline" />
                    <MetricBtn id="weighted" label="Weighted" />
                    <MetricBtn id="deals"    label="Deals" />
                  </div>
                </div>

                {/* 3-column drill-down */}
                <div style={{ display:'grid', gridTemplateColumns:'220px 1fr 1fr', height:480, overflow:'hidden' }}>

                  {/* Col 1: Groups */}
                  <div style={{ borderRight:'1px solid var(--line-2)', padding:'8px 4px 12px', overflowY:'auto', height:'100%', boxSizing:'border-box' }}>
                    <div style={{ padding:'4px 12px 8px', fontSize:10, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'.07em' }}>Product Groups</div>
                    {groupData.map(g => {
                      const pct   = (metricVal(g) / maxMetric * 100).toFixed(0);
                      const share = (metricVal(g) / totalMetric * 100).toFixed(1);
                      const color = groupColorMap[g.group_name] || '#888';
                      const isSel = g.group_name === selectedGroup;
                      return (
                        <div key={g.group_name}
                          onClick={() => { if (isSel) { setSelectedGroup(null); setSelectedProduct(null); } else { setSelectedGroup(g.group_name); setSelectedProduct(null); } }}
                          style={{ display:'flex', alignItems:'center', gap:9, padding:'9px 12px', cursor:'pointer', background: isSel ? '#f0f2fa' : 'transparent', borderLeft: isSel ? `3px solid ${color}` : '3px solid transparent', marginLeft:0, transition:'background 120ms' }}
                          onMouseEnter={e => !isSel && (e.currentTarget.style.background='var(--bg)')}
                          onMouseLeave={e => !isSel && (e.currentTarget.style.background='transparent')}>
                          <div style={{ width:9, height:9, borderRadius:2, background:color, flexShrink:0 }} />
                          <div style={{ flex:1, minWidth:0 }}>
                            <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:4 }}>
                              <span style={{ fontSize:12.5, fontWeight: isSel ? 600 : 400, color: isSel ? 'var(--ink)' : 'var(--ink-2)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis', maxWidth:90 }}>{g.group_name}</span>
                              <span className="num" style={{ fontSize:11, color:'var(--ink-3)', flexShrink:0, marginLeft:4 }}>{fmtMetric(g)}</span>
                            </div>
                            <div style={{ background:'var(--line-2)', borderRadius:3, height:5, overflow:'hidden' }}>
                              <div style={{ width:`${pct}%`, height:'100%', background:color, opacity: isSel ? 1 : 0.6, borderRadius:3, transition:'width .35s' }} />
                            </div>
                            <div style={{ fontSize:10, color:'var(--ink-3)', marginTop:3 }}>{share}% of total</div>
                          </div>
                          {isSel && <span style={{ color, fontSize:13, flexShrink:0 }}>›</span>}
                        </div>
                      );
                    })}
                  </div>

                  {/* Col 2: Products */}
                  <div style={{ borderRight:'1px solid var(--line-2)', display:'flex', flexDirection:'column', height:'100%', overflow:'hidden' }}>
                    <div style={{ padding:'10px 16px 8px', borderBottom:'1px solid var(--line-2)', minHeight:44, display:'flex', alignItems:'center', gap:10, flexShrink:0 }}>
                      {activeGroup ? (
                        <>
                          <div style={{ width:10, height:10, borderRadius:2, background: groupColorMap[selectedGroup], flexShrink:0 }} />
                          <span style={{ fontSize:13, fontWeight:600, color:'var(--ink)' }}>{activeGroup.group_name}</span>
                          <span style={{ fontSize:11, color:'var(--ink-3)' }}>· {activeGroup.products.length} products</span>
                        </>
                      ) : (
                        <span style={{ fontSize:12, color:'var(--ink-3)' }}>← เลือก Group</span>
                      )}
                    </div>
                    <div style={{ overflowY:'auto', flex:1 }}>
                      {activeProducts.map(p => {
                        const pct   = (metricVal(p) / maxProductMetric * 100).toFixed(0);
                        const color = groupColorMap[selectedGroup] || '#888';
                        const isSel = (p.product_key || p.product_name) === selectedProduct;
                        const fmtP  = groupMetric === 'deals' ? `${p.deals}` : fmtMillion(groupMetric === 'weighted' ? p.weighted : p.pipeline);
                        return (
                          <div key={p.product_key || p.product_name}
                            onClick={() => { if (isSel) setSelectedProduct(null); else setSelectedProduct(p.product_key || p.product_name); }}
                            style={{ display:'flex', alignItems:'center', gap:9, padding:'9px 16px', cursor:'pointer', background: isSel ? '#f0f2fa' : 'transparent', borderLeft: isSel ? `3px solid ${color}` : '3px solid transparent', transition:'background 120ms' }}
                            onMouseEnter={e => !isSel && (e.currentTarget.style.background='var(--bg)')}
                            onMouseLeave={e => !isSel && (e.currentTarget.style.background='transparent')}>
                            <div style={{ width:6, height:6, borderRadius:'50%', background:color, opacity: isSel ? 1 : 0.5, flexShrink:0 }} />
                            <div style={{ flex:1, minWidth:0 }}>
                              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:4 }}>
                                <span style={{ fontSize:12.5, fontWeight: isSel ? 600 : 400, color: isSel ? 'var(--ink)' : 'var(--ink-2)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis', maxWidth:120 }}>{p.product_name}</span>
                                <span className="num" style={{ fontSize:11, color:'var(--ink-3)', flexShrink:0, marginLeft:4 }}>{fmtP}</span>
                              </div>
                              <div style={{ background:'var(--line-2)', borderRadius:3, height:5, overflow:'hidden' }}>
                                <div style={{ width:`${pct}%`, height:'100%', background:color, opacity: isSel ? 1 : 0.55, borderRadius:3, transition:'width .35s' }} />
                              </div>
                              <div style={{ fontSize:10, color:'var(--ink-3)', marginTop:3 }}>{p.deals} deals</div>
                            </div>
                            {isSel && <span style={{ color, fontSize:13, flexShrink:0 }}>›</span>}
                          </div>
                        );
                      })}
                      {!activeGroup && (
                        <div style={{ padding:'32px 16px', textAlign:'center', color:'var(--ink-3)', fontSize:12 }}>เลือก Group ด้านซ้ายเพื่อดู Products</div>
                      )}
                    </div>
                  </div>

                  {/* Col 3: Deals */}
                  <div style={{ display:'flex', flexDirection:'column', height:'100%', overflow:'hidden' }}>
                    <div style={{ borderBottom:'1px solid var(--line-2)', flexShrink:0 }}>
                      {/* Title row */}
                      <div style={{ padding:'10px 16px 6px', display:'flex', alignItems:'center', justifyContent:'space-between', minHeight:40 }}>
                        {selectedProduct ? (
                          <>
                            <div>
                              <span style={{ fontSize:13, fontWeight:600, color:'var(--ink)' }}>{selectedProductDisplay}</span>
                              <span style={{ fontSize:11, color:'var(--ink-3)', marginLeft:8 }}>
                                · {dealsStageFilter ? `${filteredDeals.length} / ${groupDealsTotal}` : groupDealsTotal} deals
                              </span>
                            </div>
                            {loadingDeals && <span style={{ fontSize:11, color:'var(--ink-3)' }}>กำลังโหลด…</span>}
                          </>
                        ) : (
                          <span style={{ fontSize:12, color:'var(--ink-3)' }}>← เลือก Product</span>
                        )}
                      </div>
                      {/* Stage filter chips */}
                      {availableStages.length > 0 && (
                        <div style={{ padding:'0 12px 8px', display:'flex', gap:4, flexWrap:'wrap' }}>
                          <button
                            onClick={() => setDealsStageFilter(null)}
                            style={{ padding:'2px 8px', fontSize:10.5, borderRadius:10, border:'1px solid var(--line)', cursor:'pointer',
                              background: !dealsStageFilter ? 'var(--accent)' : 'transparent',
                              color: !dealsStageFilter ? '#fff' : 'var(--ink-3)', fontWeight: !dealsStageFilter ? 600 : 400 }}>
                            All
                          </button>
                          {availableStages.map(s => {
                            const ps  = pillStyle(s);
                            const cnt = stageCounts[s] || 0;
                            const sel = dealsStageFilter === s;
                            return (
                              <button key={s}
                                onClick={() => setDealsStageFilter(sel ? null : s)}
                                style={{ padding:'2px 8px', fontSize:10.5, borderRadius:10, cursor:'pointer',
                                  border: `1px solid ${sel ? ps.c : 'var(--line)'}`,
                                  background: sel ? ps.bg : 'transparent',
                                  color: sel ? ps.c : 'var(--ink-3)',
                                  fontWeight: sel ? 600 : 400 }}>
                                {s} <span style={{ opacity:.7 }}>{cnt}</span>
                              </button>
                            );
                          })}
                        </div>
                      )}
                    </div>
                    <div style={{ overflowY:'auto', flex:1 }}>
                      {filteredDeals.length > 0 ? (
                        <table style={{ width:'100%', borderCollapse:'collapse' }}>
                          <thead>
                            <tr style={{ borderBottom:'1px solid var(--line-2)', background:'#fafaf7' }}>
                              {[
                                { label:'Account', col:'account_name', align:'left'  },
                                { label:'Stage',   col:'stage',        align:'left'  },
                                { label:'Value',   col:'deal_value',   align:'right' },
                                { label:'Prob.',   col:'probability',  align:'right' },
                                { label:'Close',   col:'close_date',   align:'right' },
                              ].map(({ label, col, align }) => (
                                <th key={col}
                                  onClick={() => toggleSort(col)}
                                  style={{ fontSize:10.5, color: dealsSort.col===col ? 'var(--accent)' : 'var(--ink-3)', fontWeight:500, textTransform:'uppercase', letterSpacing:'.05em', padding:'7px 10px', textAlign:align, cursor:'pointer', userSelect:'none', whiteSpace:'nowrap' }}>
                                  {label}{sortIcon(col)}
                                </th>
                              ))}
                            </tr>
                          </thead>
                          <tbody>
                            {filteredDeals.map((deal, idx) => {
                              const ps = pillStyle(deal.stage);
                              return (
                                <tr key={deal.id || idx}
                                  style={{ borderBottom:'1px solid var(--line-2)' }}
                                  onMouseEnter={e => e.currentTarget.style.background='var(--bg)'}
                                  onMouseLeave={e => e.currentTarget.style.background='transparent'}>
                                  <td style={{ padding:'8px 10px', maxWidth:160 }}>
                                    <div style={{ fontWeight:500, fontSize:12, color:'var(--ink)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }}>{deal.account_name || deal.deal_name || '—'}</div>
                                    {deal.segment && <div style={{ fontSize:10.5, color:'var(--ink-3)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }}>{deal.segment}</div>}
                                    {deal.product && <div style={{ fontSize:10.5, color:'var(--accent)', fontWeight:500, overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }}>{deal.product}</div>}
                                  </td>
                                  <td style={{ padding:'8px 10px' }}>
                                    <span style={{ display:'inline-flex', alignItems:'center', padding:'2px 7px', borderRadius:9, fontSize:10.5, fontWeight:500, background:ps.bg, color:ps.c, whiteSpace:'nowrap' }}>{deal.stage || '—'}</span>
                                  </td>
                                  <td className="num" style={{ padding:'8px 10px', textAlign:'right', fontWeight:600, color:'var(--ink)', fontSize:12, whiteSpace:'nowrap' }}>{fmtMillion(deal.deal_value)}</td>
                                  <td className="num" style={{ padding:'8px 10px', textAlign:'right', fontSize:12, color: deal.probability >= 70 ? 'var(--positive)' : 'var(--ink-2)' }}>{deal.probability}%</td>
                                  <td className="num" style={{ padding:'8px 10px', textAlign:'right', fontSize:11, color:'var(--ink-3)', whiteSpace:'nowrap' }}>{deal.close_date ? deal.close_date.slice(0,7) : '—'}</td>
                                </tr>
                              );
                            })}
                          </tbody>
                          <tfoot>
                            <tr style={{ borderTop:'1px solid var(--line)', background:'#fafaf7' }}>
                              <td style={{ padding:'7px 10px', fontSize:10.5, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'.05em' }} colSpan={2}>
                                {dealsStageFilter ? `${dealsStageFilter} · ` : ''}{filteredDeals.length} deals
                              </td>
                              <td className="num" style={{ padding:'7px 10px', textAlign:'right', fontWeight:600, fontSize:12 }}>{fmtMillion(filteredDeals.reduce((s, d) => s + parseFloat(d.deal_value || 0), 0))}</td>
                              <td className="num" style={{ padding:'7px 10px', textAlign:'right', fontSize:12, color:'var(--ink-3)' }}>
                                {filteredDeals.length ? Math.round(filteredDeals.reduce((s, d) => s + (d.probability || 0), 0) / filteredDeals.length) : 0}%
                              </td>
                              <td />
                            </tr>
                          </tfoot>
                        </table>
                      ) : selectedProduct && !loadingDeals ? (
                        <div style={{ padding:'32px 16px', textAlign:'center', color:'var(--ink-3)', fontSize:12 }}>ไม่มีข้อมูล deals{dealsStageFilter ? ` ใน ${dealsStageFilter}` : ''}</div>
                      ) : !selectedProduct ? (
                        <div style={{ padding:'32px 16px', textAlign:'center', color:'var(--ink-3)', fontSize:12 }}>เลือก Product ด้านซ้ายเพื่อดู Deals</div>
                      ) : null}
                    </div>
                  </div>
                </div>

                {/* Share bar */}
                <div style={{ padding:'10px 20px', borderTop:'1px solid var(--line-2)', display:'flex', gap:14, alignItems:'center' }}>
                  <div style={{ fontSize:11, color:'var(--ink-3)', flexShrink:0 }}>Group share:</div>
                  <div style={{ flex:1, display:'flex', height:10, borderRadius:5, overflow:'hidden', gap:1 }}>
                    {groupData.map(g => (
                      <div key={g.group_name}
                        onClick={() => setSelectedGroup(g.group_name === selectedGroup ? null : g.group_name)}
                        title={`${g.group_name}: ${numFmt(metricVal(g)/totalMetric*100)}%`}
                        style={{ flex: metricVal(g), background: groupColorMap[g.group_name], transition:'flex .4s', cursor:'pointer', opacity: selectedGroup && g.group_name !== selectedGroup ? 0.4 : 1 }} />
                    ))}
                  </div>
                  <div style={{ display:'flex', gap:12, flexWrap:'wrap' }}>
                    {groupData.map(g => (
                      <div key={g.group_name} style={{ display:'flex', alignItems:'center', gap:5, fontSize:11, color:'var(--ink-3)' }}>
                        <div style={{ width:8, height:8, borderRadius:2, background: groupColorMap[g.group_name] }} />
                        {g.group_name}
                        <span className="num">{numFmt(metricVal(g)/totalMetric*100)}%</span>
                      </div>
                    ))}
                  </div>
                </div>
              </div>
            );
          })()}
            </>
          ),
          trend_opps: () => {
            const TREND_COLORS = [
              '#2d3a8c','#d97b2e','#1f7a4d','#9b59b6','#e74c3c',
              '#16a085','#f39c12','#2980b9','#8e44ad','#c0392b',
              '#27ae60','#d35400',
            ];
            const { labels, series: trendSeries } = createdTrend || {};
            const chartSeries = (trendSeries || []).map(s => ({
              label: `${s.segment} (${numFmt(s.total, 0)})`,
              data: s.data,
            }));
            const rangeLabel = labels && labels.length >= 2 ? `${labels[0]} – ${labels[labels.length - 1]}` : 'Last 12 months';
            return (
              <div key="trend_opps" style={{ display:'grid', gridTemplateColumns:'3fr 2fr', gap:14, alignItems:'start' }}>

                {/* Left: Created Trend */}
                <div style={{ background:'var(--panel)', border:'1px solid var(--line)', padding:20, minWidth:0 }}>
                  <SectionHead title="Pipeline Created Trend by Segment" subtitle={`Deals created per month · ${rangeLabel}`} />
                  {!createdTrend ? (
                    <div style={{ height:240, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--ink-3)', fontSize:12 }}>กำลังโหลด…</div>
                  ) : (
                    <div style={{ overflow:'hidden' }}><LineChart
                      series={chartSeries}
                      labels={labels || []}
                      height={240}
                      colors={TREND_COLORS}
                      showLegend={true}
                      yFormat={v => v >= 1000 ? `${numFmt(v/1000)}K` : String(Math.round(v))}
                    /></div>
                  )}
                </div>

                {/* Right: Top Opportunities */}
                <div style={{ ...cardStyle, display:'flex', flexDirection:'column', minWidth:0 }}>
                  <div style={{ fontSize:13, fontWeight:600, marginBottom:2 }}>Top Opportunities</div>
                  <div style={{ fontSize:11, color:'var(--ink-3)', marginBottom:14 }}>Ranked by weighted deal value</div>
                  {opportunities.length === 0 ? (
                    <div style={{ color:'var(--ink-3)', fontSize:12 }}>ไม่มีข้อมูล opportunities</div>
                  ) : (
                    <>
                      <div style={{ overflowX:'auto' }}>
                        <table style={{ width:'100%', borderCollapse:'collapse' }}>
                          <thead>
                            <tr style={{ borderBottom:'2px solid var(--line)' }}>
                              {['Account','Stage','Value','Prob.'].map(h => (
                                <th key={h} style={{ fontSize:10.5, color:'var(--ink-3)', fontWeight:500, textTransform:'uppercase', letterSpacing:'0.05em', padding:'6px 8px', textAlign: h === 'Account' || h === 'Stage' ? 'left' : 'right', whiteSpace:'nowrap' }}>{h}</th>
                              ))}
                            </tr>
                          </thead>
                          <tbody>
                            {opportunities.slice(0, showAllOpps ? undefined : 8).map((deal, idx) => (
                              <tr key={deal.id || idx}
                                style={{ borderBottom:'1px solid var(--line-2)' }}
                                onMouseEnter={e => e.currentTarget.style.background = 'var(--bg)'}
                                onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                                <td style={{ padding:'8px 8px', maxWidth:160 }}>
                                  <div style={{ fontWeight:500, fontSize:12, color:'var(--ink)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }}>{deal.account_name || deal.deal_name || '—'}</div>
                                  {deal.product && <div style={{ fontSize:10.5, color:'var(--accent)', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis' }}>{deal.product}</div>}
                                  {deal.segment && <div style={{ fontSize:10.5, color:'var(--ink-3)' }}>{deal.segment}</div>}
                                </td>
                                <td style={{ padding:'8px 8px' }}><StagePill stage={deal.stage || '—'} /></td>
                                <td className="num" style={{ padding:'8px 8px', textAlign:'right', fontWeight:600, color:'var(--ink)', fontSize:12, whiteSpace:'nowrap' }}>{fmtMillion(deal.deal_value)}</td>
                                <td className="num" style={{ padding:'8px 8px', textAlign:'right', fontSize:12, color: deal.probability >= 70 ? 'var(--positive)' : 'var(--ink-2)' }}>{deal.probability}%</td>
                              </tr>
                            ))}
                          </tbody>
                        </table>
                      </div>
                      <div style={{ marginTop:10, paddingTop:10, borderTop:'1px solid var(--line-2)', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
                        <div style={{ fontSize:11, color:'var(--ink-3)' }}>แสดง {Math.min(showAllOpps ? opportunities.length : 8, opportunities.length)} จาก {oppsTotal}</div>
                        {opportunities.length > 8 && (
                          <button onClick={() => setShowAllOpps(s => !s)} style={{ fontSize:12, color:'var(--accent)', background:'none', border:'none', cursor:'pointer', fontWeight:500, fontFamily:'inherit' }}>
                            {showAllOpps ? 'ย่อลง' : `ดูทั้งหมด →`}
                          </button>
                        )}
                      </div>
                    </>
                  )}
                </div>

              </div>
            );
          },
        };
        return (
          <>
            {plSections.filter(s => s.visible).map(s =>
              plBlocks[s.id] ? <React.Fragment key={s.id}>{plBlocks[s.id]()}</React.Fragment> : null
            )}
          </>
        );
      })()}

      {/* ── Pipeline Customize Modal ─────────────────────────── */}
      <Modal
        open={showPlCustomize}
        onClose={() => setShowPlCustomize(false)}
        title="Customize Pipeline layout"
        subtitle="ลาก ⠿ เพื่อเรียงลำดับ · toggle เพื่อแสดง/ซ่อน section"
        width={480}
        footer={<>
          <button onClick={() => setShowPlCustomize(false)} style={{ padding:'8px 14px', border:'1px solid var(--line)', fontSize:12, borderRadius:3, fontFamily:'inherit', cursor:'pointer' }}>Cancel</button>
          <button onClick={() => {
            const next = [...pendingPlSections];
            setPlSections(next);
            try { localStorage.setItem(plLayoutKey, JSON.stringify(next)); } catch {}
            setShowPlCustomize(false);
          }} style={{ padding:'8px 14px', background:'var(--ink)', color:'#fff', fontSize:12, fontWeight:500, borderRadius:3, fontFamily:'inherit', cursor:'pointer' }}>Apply</button>
        </>}>
        <div style={{ display:'flex', flexDirection:'column', gap:6 }}>
          {pendingPlSections.map((s, idx) => (
            <div
              key={s.id}
              draggable
              onDragStart={() => { plDragIdx.current = idx; }}
              onDragOver={e => {
                e.preventDefault();
                if (plDragOverIdx.current !== idx) {
                  plDragOverIdx.current = idx;
                  const arr = [...pendingPlSections];
                  const [moved] = arr.splice(plDragIdx.current, 1);
                  arr.splice(idx, 0, moved);
                  plDragIdx.current = idx;
                  setPendingPlSections(arr);
                }
              }}
              onDragEnd={() => { plDragIdx.current = null; plDragOverIdx.current = null; }}
              style={{
                display:'flex', alignItems:'center', gap:12,
                padding:'11px 12px',
                border:'1px solid var(--line)', borderRadius:3,
                background: s.visible ? 'var(--panel)' : '#f7f5f0',
                cursor:'grab', userSelect:'none',
                opacity: s.visible ? 1 : 0.55,
                transition:'opacity 120ms, background 120ms',
              }}>
              <span style={{ color:'var(--ink-3)', fontSize:14, lineHeight:1, cursor:'grab' }}>⠿</span>
              <span style={{ fontSize:10, fontFamily:'IBM Plex Mono', color:'var(--ink-3)', minWidth:16, textAlign:'center' }}>
                {s.visible ? pendingPlSections.filter((x, xi) => x.visible && xi <= idx).length : '—'}
              </span>
              <span style={{ flex:1, fontSize:12.5, fontWeight:500, color:'var(--ink)' }}>{s.label}</span>
              <div
                onClick={() => setPendingPlSections(prev => prev.map((x, xi) => xi === idx ? { ...x, visible: !x.visible } : x))}
                style={{ position:'relative', width:32, height:18, borderRadius:9, cursor:'pointer',
                  background: s.visible ? 'var(--accent)' : '#c8c5bc', transition:'background 150ms', flexShrink:0 }}>
                <div style={{ position:'absolute', top:3, left: s.visible ? 16 : 3,
                  width:12, height:12, borderRadius:'50%', background:'#fff',
                  transition:'left 150ms', boxShadow:'0 1px 3px rgba(0,0,0,0.2)' }} />
              </div>
            </div>
          ))}
          <div style={{ marginTop:6, fontSize:11, color:'var(--ink-3)' }}>
            {pendingPlSections.filter(s => s.visible).length} sections จะแสดงบน Pipeline Dashboard
          </div>
        </div>
      </Modal>

    </div>
  );
};

const App = () => {
  const [authUser, setAuthUser] = useState(null);       // null = not checked yet
  const [authReady, setAuthReady] = useState(false);    // whether auth check is done
  const [showChangePw, setShowChangePw] = useState(false);

  // Check existing token on mount
  useEffect(() => {
    window.API?.me().then(user => {
      setAuthUser(user);
      setAuthReady(true);
    }).catch(() => setAuthReady(true));

    const onLogout = () => setAuthUser(null);
    window.addEventListener('auth:logout', onLogout);
    return () => window.removeEventListener('auth:logout', onLogout);
  }, []);

  if (!authReady) {
    return (
      <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f5f3ee' }}>
        <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>กำลังโหลด…</div>
      </div>
    );
  }

  if (!authUser) return <LoginPage onLogin={user => setAuthUser(user)} />;

  return <AppShell authUser={authUser} onLogout={() => { window.API.logout(); setAuthUser(null); }} onChangePw={() => setShowChangePw(true)}
    showChangePw={showChangePw} onCloseChangePw={() => setShowChangePw(false)} />;
};

const AppShell = ({ authUser, onLogout, onChangePw, showChangePw, onCloseChangePw }) => {
  const [view, setView] = useState('overview');
  const [drill, setDrill] = useState(null); // group id or null
  const [productSel, setProductSel] = useState(null);
  const [period, setPeriod] = useState('YTD');
  const [compare, setCompare] = useState(true);
  const [sidebarMode, setSidebarMode] = useState(() => localStorage.getItem('sidebarMode') || 'expanded');
  // Persist sidebar mode
  useEffect(() => { localStorage.setItem('sidebarMode', sidebarMode); }, [sidebarMode]);

  // Live data from API
  const [liveGroups, setLiveGroups] = useState(GROUPS);
  const [liveAccounts, setLiveAccounts] = useState([]);
  const [liveAlerts, setLiveAlerts]   = useState(ALERTS);
  const [drillProducts, setDrillProducts] = useState([]);
  const [dataLoading, setDataLoading] = useState(false);
  const [lastRefresh, setLastRefresh] = useState(null);

  const loadLiveData = async (p = period) => {
    if (!window.API) return;
    setDataLoading(true);
    try {
      const [groupsRes, accountsRes, alertsRes] = await Promise.allSettled([
        window.API.getGroups(p),
        window.API.getAccounts(p),
        window.API.getAlerts(),
      ]);
      const groups   = groupsRes.status   === 'fulfilled' ? groupsRes.value   : [];
      const accounts = accountsRes.status === 'fulfilled' ? accountsRes.value : [];
      const alerts   = alertsRes.status   === 'fulfilled' ? alertsRes.value   : [];
      if (accountsRes.status === 'rejected') console.warn('getAccounts failed:', accountsRes.reason);
      const resolvedGroups = groups.length > 0 ? groups : GROUPS;
      resolvedGroups.forEach(g => {
        if (g.color) GROUP_COLORS[g.id] = g.color;
        if (g.name)  GROUP_NAMES[g.id]  = g.name;
      });
      setLiveGroups(resolvedGroups);
      setLiveAccounts(accounts);
      setLiveAlerts(alerts.length > 0 ? alerts : ALERTS);
      setLastRefresh(new Date());
    } catch (e) {
      console.error('loadLiveData failed:', e);
    } finally {
      setDataLoading(false);
    }
  };

  useEffect(() => { loadLiveData(); }, []);
  useEffect(() => { loadLiveData(period); }, [period]);

  // Load real products from DB when drilling into a group
  useEffect(() => {
    if (!drill) { setDrillProducts([]); return; }
    window.API?.getGroupDetail(drill, period).then(products => {
      setDrillProducts(products || []);
    }).catch(() => setDrillProducts([]));
  }, [drill, period]);

  const currentGroup = drill ? liveGroups.find(g => g.id === drill) : null;
  const currentGroupWithProducts = currentGroup
    ? { ...currentGroup, products: drillProducts.length > 0 ? drillProducts : (currentGroup.products || []) }
    : null;
  const currentProduct = currentGroupWithProducts && productSel
    ? currentGroupWithProducts.products.find(p => p.id === productSel)
    : null;
  const isPipelineView = ['pipeline', 'lost-analysis'].includes(view);
  const isSettings = !['overview', 'pipeline', 'lost-analysis'].includes(view);

  const crumbs = view === 'pipeline'
    ? ['Pipeline', 'Dashboard']
    : view === 'lost-analysis'
      ? ['Pipeline', 'Lost Analysis']
      : isSettings
        ? ['Settings', view === 'upload' ? 'Upload file' : view === 'group-setting' ? 'Group setting' : view === 'product-setting' ? 'Product setting' : view === 'target-setting' ? 'Target setting' : 'User setting']
        : drill
          ? ['Revenue', 'Product Groups', currentGroup?.name ?? '…']
          : ['Revenue', 'Product Groups'];

  const SETTINGS_VIEWS = ['upload', 'group-setting', 'product-setting', 'target-setting', 'user-setting'];
  const handleNavigate = (v) => {
    if (SETTINGS_VIEWS.includes(v) && authUser?.role === 'viewer') return;
    if (v === 'pipeline') { setDrill(null); setProductSel(null); setView('pipeline'); return; }
    if (v === 'lost-analysis') { setDrill(null); setProductSel(null); setView('lost-analysis'); return; }
    setView(v);
    setDrill(null);
    setProductSel(null);
  };

  const [pipelineStage, setPipelineStage] = useState(null);
  const [showAllAccounts, setShowAllAccounts] = useState(false);
  const [showAddKPI, setShowAddKPI] = useState(false);
  const [showCustomize, setShowCustomize] = useState(false);
  const kpiStorageKey = `kpi_items_${authUser?.id || 'default'}`;
  const [kpiItems, setKpiItems] = useState(() => {
    try {
      const saved = localStorage.getItem(`kpi_items_${authUser?.id || 'default'}`);
      if (saved) {
        const parsed = JSON.parse(saved);
        // Merge: append any new KPI ids added since last save
        const savedIds = new Set(parsed.map(k => k.id));
        const newItems = KPI_ITEMS_DEFAULT.filter(k => !savedIds.has(k.id));
        return [...parsed, ...newItems];
      }
    } catch {}
    return KPI_ITEMS_DEFAULT;
  });
  const [pendingKPIItems, setPendingKPIItems] = useState(kpiItems);
  const kpiDragIdx = useRef(null);
  const kpiDragOverIdx = useRef(null);
  const layoutStorageKey = `layout_sections_${authUser?.id || 'default'}`;
  const [sections, setSections] = useState(() => {
    try {
      const saved = localStorage.getItem(`layout_sections_${authUser?.id || 'default'}`);
      if (saved) {
        const parsed = JSON.parse(saved);
        // Merge with DEFAULT_SECTIONS to pick up any new sections added in future updates
        const savedIds = new Set(parsed.map(s => s.id));
        const newSections = DEFAULT_SECTIONS.filter(s => !savedIds.has(s.id));
        return [...parsed, ...newSections];
      }
    } catch {}
    return DEFAULT_SECTIONS;
  });
  const [pendingSections, setPendingSections] = useState(DEFAULT_SECTIONS);
  const dragIdx = useRef(null);
  const dragOverIdx = useRef(null);

  const handleSearchSelect = (r) => {
    if (r.kind === 'Group' && r.groupId) {
      setView('overview');
      setDrill(r.groupId);
      showToast(`Opened group: ${r.label}`, { variant: 'info' });
    } else if (r.kind === 'Product' && r.groupId) {
      setView('overview');
      setDrill(r.groupId);
      showToast(`Opened product: ${r.label}`, { variant: 'info' });
    } else if (r.kind === 'Account') {
      setShowAllAccounts(true);
      showToast(`Showing account: ${r.label}`, { variant: 'info' });
    }
  };

  const handleExport = (kind) => {
    // ── PDF via browser print dialog ──────────────────────────────
    if (kind === 'pdf') {
      const styleId = '__print_style';
      let el = document.getElementById(styleId);
      if (!el) {
        el = document.createElement('style');
        el.id = styleId;
        document.head.appendChild(el);
      }
      el.textContent = `
        @media print {
          aside { display: none !important; }
          body  { overflow: visible !important; background: #fff !important; }
          * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
        }
      `;
      const cleanup = () => { el.textContent = ''; window.removeEventListener('afterprint', cleanup); };
      window.addEventListener('afterprint', cleanup);
      showToast('กำลังเปิดหน้าต่าง Print', { variant: 'info', detail: 'เลือก "Save as PDF" ใน print dialog เพื่อบันทึก PDF' });
      setTimeout(() => window.print(), 200);
      return;
    }

    // ── CSV / XLSX download ────────────────────────────────────────
    const downloadCSV = (headers, rows, filename) => {
      const csv = '﻿' + [headers, ...rows]
        .map(r => r.map(c => `"${String(c ?? '').replace(/"/g, '""')}"`).join(','))
        .join('\n');
      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.download = `${filename}.csv`;
      document.body.appendChild(a);
      a.click();
      setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 300);
      showToast(`Exported ${filename}.csv`, { variant: 'success', detail: `${rows.length} rows` });
    };

    // Pipeline CSV export
    if (view === 'lost-analysis') {
      showToast('กำลังดึงข้อมูล…', { variant: 'info' });
      // recentFilter is set inside LostAnalysisView; read from a shared ref or
      // simply pass no status filter and export everything (all lost deals).
      // We expose the current filter via window.__lostFilter set by LostAnalysisView.
      const statusParam = window.__lostFilter && window.__lostFilter !== 'all'
        ? `?status=${window.__lostFilter}` : '';
      authFetch(`/api/pipeline/lost/export${statusParam}`)
        .then(r => r.json())
        .then(data => {
          const deals = data.deals || [];
          if (!deals.length) { showToast('ไม่มีข้อมูลที่จะ export', { variant: 'info' }); return; }
          const headers = ['Account','Deal Name','Product','Segment','Department',
                           'Deal Value (THB)','Status','Close Date','Created Date','Cycle (Days)','Owner'];
          const rows = deals.map(d => [
            d.account_name || '',
            d.deal_name    || '',
            d.product      || '',
            d.segment      || '',
            d.department   || '',
            d.deal_value   || 0,
            d.opportunity_status || '',
            d.close_date   ? d.close_date.slice(0,10) : '',
            d.created_date ? d.created_date.slice(0,10) : '',
            d.cycle_days   != null ? d.cycle_days : '',
            d.owner        || '',
          ]);
          const suffix = window.__lostFilter && window.__lostFilter !== 'all'
            ? `_${window.__lostFilter}` : '';
          downloadCSV(headers, rows, `lost_deals${suffix}_${new Date().toISOString().slice(0,10)}`);
          showToast(`Exported ${rows.length} lost deals`, { variant: 'success' });
        })
        .catch(() => showToast('Export ไม่สำเร็จ', { variant: 'error' }));
      return;
    }
    if (view === 'pipeline') {
      showToast('กำลังดึงข้อมูล…', { variant: 'info' });
      const sp = pipelineStage ? `&stage=${encodeURIComponent(pipelineStage)}` : '';
      authFetch(`/api/pipeline/opportunities?limit=9999${sp}`)
        .then(r => r.json())
        .then(data => {
          const deals = data.deals || [];
          if (!deals.length) { showToast('ไม่มีข้อมูลที่จะ export', { variant: 'info' }); return; }
          const headers = ['Deal Name','Account','Segment','Vertical','Product','Product Group','Stage','Deal Value (THB)','Probability %','Weighted Value (THB)','Close Date','Owner'];
          const rows = deals.map(d => [
            d.deal_name || '',
            d.account_name || '',
            d.segment || '',
            d.vertical || '',
            d.product || '',
            d.settings_group || d.product_group || '',
            d.stage || '',
            d.deal_value || 0,
            d.probability || 0,
            d.weighted_value || 0,
            d.close_date || '',
            d.owner || '',
          ]);
          const stageSuffix = pipelineStage ? `_${pipelineStage.replace(/[^a-z0-9]/gi,'_')}` : '';
          downloadCSV(headers, rows, `pipeline_deals${stageSuffix}_${new Date().toISOString().slice(0,10)}`);
        })
        .catch(() => showToast('Export ไม่สำเร็จ', { variant: 'error' }));
      return;
    }

    // Settings views — no tabular data
    if (!['overview'].includes(view)) {
      showToast('No data to export', { variant: 'info', detail: 'Export is available from the Overview page' });
      return;
    }

    // Product detail drill-down
    if (drill && productSel && currentProduct) {
      const headers = ['Product', 'Group', 'Revenue', 'MoM %', 'YoY %', 'Customers', 'ARPU'];
      const rows = [[
        currentProduct.name || currentProduct.id,
        currentGroup?.name || drill,
        currentProduct.revenue ?? '',
        currentProduct.growth ?? '',
        currentProduct.yoy ?? '',
        currentProduct.customers ?? '',
        currentProduct.arpu ?? '',
      ]];
      downloadCSV(headers, rows, `product_${(currentProduct.name || productSel).replace(/\s+/g,'_').toLowerCase()}`);
      return;
    }

    // Group drill-down — products of this group
    if (drill && currentGroupWithProducts) {
      const products = currentGroupWithProducts.products || [];
      const headers = ['Product', 'SKU', 'Revenue', 'MoM %', 'YoY %', 'Customers', 'ARPU', 'Margin %'];
      const rows = products.length > 0
        ? products.map(p => [
            p.name || p.id,
            p.sku || '',
            p.revenue ?? '',
            p.growth ?? '',
            p.yoy ?? '',
            p.customers ?? '',
            p.arpu ?? '',
            p.margin ?? '',
          ])
        : [['No product data available', '', '', '', '', '', '', '']];
      const gName = (currentGroup?.name || drill).replace(/\s+/g,'_').toLowerCase();
      downloadCSV(headers, rows, `products_${gName}_${period.toLowerCase()}`);
      return;
    }

    // Overview — all groups summary
    const headers = ['Product Group', 'Revenue', 'MoM %', 'YoY %', 'Target', 'Pacing %', 'Customers', 'ARPU'];
    const rows = liveGroups.map(g => [
      g.name,
      g.revenue ?? '',
      g.growth ?? '',
      g.yoy ?? '',
      g.target ?? '',
      (g.target > 0 && g.revenue != null) ? ((g.revenue / g.target) * 100).toFixed(1) : '',
      g.customers ?? '',
      g.arpu ?? '',
    ]);
    downloadCSV(headers, rows, `revenue_overview_${period.toLowerCase()}`);
  };

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') {
        if (productSel) setProductSel(null);
        else if (drill) setDrill(null);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [drill, productSel]);

  const isMobile = useIsMobile();
  const [mobileNavOpen, setMobileNavOpen] = useState(false);

  return (
    <div style={{ display: 'flex', height: isMobile ? 'auto' : '100vh', minHeight: '100vh', background: 'var(--bg)' }}>
      {showChangePw && <ChangePasswordModal onClose={onCloseChangePw} />}
      <Sidebar active={view} onNavigate={(v) => { handleNavigate(v); setMobileNavOpen(false); }} sidebarMode={sidebarMode} setSidebarMode={setSidebarMode} authUser={authUser} onLogout={onLogout} onChangePw={onChangePw} mobileOpen={mobileNavOpen} onMobileClose={() => setMobileNavOpen(false)} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        <TopBar
          crumbs={crumbs}
          onCrumbClick={(i) => {
            if (isSettings && i === 0) return;
            if (i === 0 || i === 1) { setDrill(null); setProductSel(null); }
          }}
          period={period} setPeriod={setPeriod}
          compare={compare} setCompare={setCompare}
          onSearchSelect={handleSearchSelect}
          onExport={handleExport}
          isPipeline={isPipelineView}
          pipelineStage={pipelineStage}
          setPipelineStage={setPipelineStage}
          onHamburger={() => setMobileNavOpen(true)}
        />
        <main style={{ flex: 1, overflowY: isMobile ? 'visible' : 'auto', padding: isMobile ? '16px 14px 60px' : '24px 28px 40px' }}>
          {view === 'pipeline' && <PipelineDashboardView userId={authUser?.id} stageFilter={pipelineStage} />}
          {view === 'lost-analysis' && <LostAnalysisView userId={authUser?.id} />}
          {view === 'upload' && authUser?.role !== 'viewer' && <UploadFileView />}
          {view === 'group-setting' && authUser?.role !== 'viewer' && <GroupSettingView />}
          {view === 'product-setting' && authUser?.role !== 'viewer' && <ProductSettingView />}
          {view === 'target-setting' && authUser?.role !== 'viewer' && <TargetSettingView />}
          {view === 'user-setting' && authUser?.role !== 'viewer' && <UserSettingView currentUser={authUser} />}
          {view === 'overview' && !drill && (
            <>
              <div style={{ marginBottom: 24, display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', alignItems: isMobile ? 'flex-start' : 'flex-end', gap: isMobile ? 12 : 0 }}>
                <div>
                  <h1 className="serif" style={{ fontSize: isMobile ? 22 : 28, fontWeight: 500, letterSpacing: '-0.02em', marginBottom: 4 }}>Revenue overview</h1>
                  <div style={{ fontSize: 13, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                    <span>{period === 'MTD' ? 'May 2026 month-to-date' : period === 'QTD' ? 'Q2 2026 quarter-to-date' : period === 'YTD' ? 'Year 2026, Jan–May' : 'Trailing 12 months'} · {dataLoading ? 'กำลังโหลด…' : lastRefresh ? `refreshed ${lastRefresh.toLocaleTimeString('th-TH', {hour:'2-digit',minute:'2-digit'})}` : 'synthetic data'}</span>
                    {(() => {
                      const iso = (window.__trendMonths || []).slice(-1)[0];
                      if (!iso) return null;
                      const label = new Date(iso + '-02').toLocaleString('en', { month: 'short', year: 'numeric' });
                      return (
                        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: '#eef2ff', color: '#2d3a8c', border: '1px solid #c7d0f5', borderRadius: 4, padding: '2px 8px', fontSize: 11.5, fontWeight: 600, letterSpacing: '0.01em' }}>
                          <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#2d3a8c', flexShrink: 0 }} />
                          ข้อมูลล่าสุด: {label}
                        </span>
                      );
                    })()}
                  </div>
                </div>
                {!isMobile && <div style={{ display: 'flex', gap: 8 }}>
                  <button onClick={() => { setPendingKPIItems([...kpiItems]); setShowAddKPI(true); }} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)' }}>
                    <Icon name="plus" size={11} /> Add KPI
                  </button>
                  <button onClick={() => { setPendingSections([...sections]); setShowCustomize(true); }} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', border: '1px solid var(--line)', borderRadius: 3, fontSize: 12, color: 'var(--ink-2)', background: 'var(--panel)' }}>
                    <Icon name="drag" size={11} /> Customize
                  </button>
                </div>}
              </div>
              <OverviewView groups={liveGroups} onDrill={(id) => setDrill(id)} period={period} compare={compare} onViewAllAccounts={() => setShowAllAccounts(true)} accounts={liveAccounts} alerts={liveAlerts} sections={sections} kpiItems={kpiItems} />
            </>
          )}
          {view === 'overview' && drill && (
            <>
              <div style={{ marginBottom: 20, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <button onClick={() => { setDrill(null); setProductSel(null); }} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--ink-2)' }}>
                  <span style={{ transform: 'rotate(180deg)', display: 'inline-flex' }}><Icon name="chevron" size={11} /></span>
                  Back to all product groups
                </button>
                <div style={{ display: 'flex', gap: 8 }}>
                  {liveGroups.map(g => (
                    <button key={g.id} onClick={() => { setDrill(g.id); setProductSel(null); }}
                      style={{
                        padding: '5px 10px',
                        fontSize: 11.5,
                        borderRadius: 3,
                        background: drill === g.id ? GROUP_COLORS[g.id] : 'transparent',
                        color: drill === g.id ? '#fff' : 'var(--ink-2)',
                        border: '1px solid ' + (drill === g.id ? GROUP_COLORS[g.id] : 'var(--line)'),
                        fontWeight: drill === g.id ? 500 : 400,
                      }}>
                      {g.name}
                    </button>
                  ))}
                </div>
              </div>
              <GroupDetail
                group={currentGroupWithProducts}
                totalGroups={liveGroups}
                onBack={() => setDrill(null)}
                onProductClick={(id) => setProductSel(id === productSel ? null : id)}
                selectedProduct={productSel}
              />
            </>
          )}
        </main>
      </div>

      {currentProduct && <ProductPanel product={currentProduct} group={currentGroupWithProducts} onClose={() => setProductSel(null)} period={period} />}

      {/* Add KPI modal */}
      <Modal
        open={showAddKPI}
        onClose={() => setShowAddKPI(false)}
        title="Add KPI to dashboard"
        subtitle="ลาก ⠿ เพื่อเรียงลำดับ · toggle เพื่อแสดง/ซ่อน · สูงสุด 4 ตัว"
        width={480}
        footer={<>
          <button onClick={() => setShowAddKPI(false)} style={{ padding: '8px 14px', border: '1px solid var(--line)', fontSize: 12, borderRadius: 3 }}>Cancel</button>
          <button
            onClick={() => {
              const next = [...pendingKPIItems];
              setKpiItems(next);
              try { localStorage.setItem(kpiStorageKey, JSON.stringify(next)); } catch {}
              setShowAddKPI(false);
              showToast('KPI saved', { variant: 'success' });
            }}
            disabled={pendingKPIItems.filter(k => k.visible).length === 0}
            style={{ padding: '8px 14px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3, opacity: pendingKPIItems.filter(k => k.visible).length === 0 ? 0.4 : 1 }}>
            Save
          </button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {pendingKPIItems.map((k, idx) => {
            const selectedCount = pendingKPIItems.filter(x => x.visible).length;
            const atMax = selectedCount >= 4;
            const disableToggle = !k.visible && atMax;
            return (
              <div
                key={k.id}
                draggable
                onDragStart={() => { kpiDragIdx.current = idx; }}
                onDragOver={e => {
                  e.preventDefault();
                  if (kpiDragOverIdx.current !== idx) {
                    kpiDragOverIdx.current = idx;
                    const arr = [...pendingKPIItems];
                    const [moved] = arr.splice(kpiDragIdx.current, 1);
                    arr.splice(idx, 0, moved);
                    kpiDragIdx.current = idx;
                    setPendingKPIItems(arr);
                  }
                }}
                onDragEnd={() => { kpiDragIdx.current = null; kpiDragOverIdx.current = null; }}
                style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  padding: '10px 12px',
                  border: '1px solid var(--line)', borderRadius: 3,
                  background: k.visible ? 'var(--panel)' : '#f7f5f0',
                  cursor: 'grab', userSelect: 'none',
                  opacity: k.visible ? 1 : 0.55,
                  transition: 'opacity 120ms, background 120ms',
                }}>
                <span style={{ color: 'var(--ink-3)', fontSize: 14, lineHeight: 1, cursor: 'grab' }}>⠿</span>
                <span style={{ fontSize: 10, fontFamily: 'IBM Plex Mono', color: 'var(--ink-3)', minWidth: 16, textAlign: 'center' }}>
                  {k.visible ? pendingKPIItems.filter((x, xi) => x.visible && xi <= idx).length : '—'}
                </span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--ink)' }}>{k.label}</div>
                  <div style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{k.desc}</div>
                </div>
                {disableToggle && (
                  <span style={{ fontSize: 9, color: 'var(--accent-2)', padding: '2px 6px', background: '#fef3e2', borderRadius: 2, whiteSpace: 'nowrap' }}>max 4</span>
                )}
                <div
                  onClick={() => { if (disableToggle) return; setPendingKPIItems(prev => prev.map((x, xi) => xi === idx ? { ...x, visible: !x.visible } : x)); }}
                  style={{
                    position: 'relative', width: 32, height: 18, borderRadius: 9,
                    cursor: disableToggle ? 'not-allowed' : 'pointer',
                    background: k.visible ? 'var(--accent)' : '#c8c5bc',
                    transition: 'background 150ms', flexShrink: 0,
                    opacity: disableToggle ? 0.4 : 1,
                  }}>
                  <div style={{
                    position: 'absolute', top: 3, left: k.visible ? 16 : 3,
                    width: 12, height: 12, borderRadius: '50%', background: '#fff',
                    transition: 'left 150ms', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
                  }} />
                </div>
              </div>
            );
          })}
          <div style={{ marginTop: 4, fontSize: 11, color: pendingKPIItems.filter(k => k.visible).length >= 4 ? 'var(--accent-2)' : 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 6 }}>
            {pendingKPIItems.filter(k => k.visible).length >= 4 && <span>⚠</span>}
            {pendingKPIItems.filter(k => k.visible).length} of 8 selected · max 4
            {pendingKPIItems.filter(k => k.visible).length >= 4 ? ' — ถึงจำนวนสูงสุดแล้ว' : ''}
          </div>
        </div>
      </Modal>

      {/* Customize modal — real drag-and-drop */}
      <Modal
        open={showCustomize}
        onClose={() => setShowCustomize(false)}
        title="Customize layout"
        subtitle="ลาก ⠿ เพื่อเรียงลำดับ · toggle เพื่อแสดง/ซ่อน section"
        width={480}
        footer={<>
          <button onClick={() => setShowCustomize(false)} style={{ padding: '8px 14px', border: '1px solid var(--line)', fontSize: 12, borderRadius: 3 }}>Cancel</button>
          <button onClick={() => {
            const next = [...pendingSections];
            setSections(next);
            try { localStorage.setItem(layoutStorageKey, JSON.stringify(next)); } catch {}
            setShowCustomize(false);
            showToast('Layout saved', { variant: 'success' });
          }} style={{ padding: '8px 14px', background: 'var(--ink)', color: '#fff', fontSize: 12, fontWeight: 500, borderRadius: 3 }}>Apply</button>
        </>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {pendingSections.map((s, idx) => (
            <div
              key={s.id}
              draggable
              onDragStart={() => { dragIdx.current = idx; }}
              onDragOver={e => {
                e.preventDefault();
                if (dragOverIdx.current !== idx) {
                  dragOverIdx.current = idx;
                  const arr = [...pendingSections];
                  const [moved] = arr.splice(dragIdx.current, 1);
                  arr.splice(idx, 0, moved);
                  dragIdx.current = idx;
                  setPendingSections(arr);
                }
              }}
              onDragEnd={() => { dragIdx.current = null; dragOverIdx.current = null; }}
              style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '11px 12px',
                border: '1px solid var(--line)', borderRadius: 3,
                background: s.visible ? 'var(--panel)' : '#f7f5f0',
                cursor: 'grab', userSelect: 'none',
                opacity: s.visible ? 1 : 0.55,
                transition: 'opacity 120ms, background 120ms',
              }}>
              {/* drag handle */}
              <span style={{ color: 'var(--ink-3)', fontSize: 14, lineHeight: 1, cursor: 'grab' }}>⠿</span>
              {/* order badge */}
              <span style={{ fontSize: 10, fontFamily: 'IBM Plex Mono', color: 'var(--ink-3)', minWidth: 16, textAlign: 'center' }}>
                {s.visible ? pendingSections.filter((x, xi) => x.visible && xi <= idx).length : '—'}
              </span>
              <span style={{ flex: 1, fontSize: 12.5, fontWeight: 500, color: 'var(--ink)' }}>{s.label}</span>
              {/* toggle switch */}
              <div
                onClick={() => setPendingSections(prev => prev.map((x, xi) => xi === idx ? { ...x, visible: !x.visible } : x))}
                style={{
                  position: 'relative', width: 32, height: 18, borderRadius: 9, cursor: 'pointer',
                  background: s.visible ? 'var(--accent)' : '#c8c5bc',
                  transition: 'background 150ms', flexShrink: 0,
                }}>
                <div style={{
                  position: 'absolute', top: 3, left: s.visible ? 16 : 3,
                  width: 12, height: 12, borderRadius: '50%', background: '#fff',
                  transition: 'left 150ms', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
                }} />
              </div>
            </div>
          ))}
          <div style={{ marginTop: 6, fontSize: 11, color: 'var(--ink-3)' }}>
            {pendingSections.filter(s => s.visible).length} sections จะแสดงบน overview
          </div>
        </div>
      </Modal>

      {/* All accounts modal */}
      <Modal
        open={showAllAccounts}
        onClose={() => setShowAllAccounts(false)}
        title="All accounts"
        subtitle={`${liveAccounts.length} active accounts · sortable by any column`}
        width={760}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead>
            <tr style={{ borderBottom: '1px solid var(--line)', fontSize: 10, textTransform: 'uppercase', color: 'var(--ink-3)', letterSpacing: '0.06em' }}>
              {['Account', 'Sector', 'Products', 'Revenue', 'YoY'].map(h => (
                <th key={h} style={{ padding: '10px 12px', textAlign: 'left', fontWeight: 500 }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {[...liveAccounts].sort((a, b) => b.revenue - a.revenue).map(a => (
              <tr key={a.id} style={{ borderBottom: '1px solid var(--line-2)', cursor: 'pointer' }}
                onClick={() => { setShowAllAccounts(false); showToast(`Opening ${a.name}`, { variant: 'info', detail: `${a.sector} · ฿${a.revenue}M YTD` }); }}
                onMouseEnter={e => e.currentTarget.style.background = '#fbfaf6'}
                onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                <td style={{ padding: '10px 12px', fontWeight: 500 }}>{a.name}</td>
                <td style={{ padding: '10px 12px', color: 'var(--ink-3)' }}>{a.sector}</td>
                <td style={{ padding: '10px 12px' }}>
                  <div style={{ display: 'flex', gap: 4 }}>
                    {a.groups.map(g => <GroupDot key={g} groupId={g} />)}
                  </div>
                </td>
                <td className="num" style={{ padding: '10px 12px', fontWeight: 500 }}>฿{numFmt(a.revenue)}M</td>
                <td style={{ padding: '10px 12px' }}><Delta value={a.growth} /></td>
              </tr>
            ))}
          </tbody>
        </table>
      </Modal>

      {/* footer / data freshness */}
      <div style={{
        position: 'fixed', bottom: 12, left: 212,
        fontSize: 10.5, color: 'var(--ink-3)',
        fontFamily: 'IBM Plex Mono',
        display: 'flex', gap: 14, alignItems: 'center',
      }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: dataLoading ? 'var(--accent-2)' : 'var(--positive)' }} />
          {dataLoading ? 'LOADING…' : 'DB · LIVE'}
        </span>
        <span>revenue_dashboard · PostgreSQL</span>
        <span>{lastRefresh ? lastRefresh.toLocaleTimeString('th-TH') : 'synthetic'}</span>
      </div>
    </div>
  );
};

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