/* ============================================================================
   ENERVECO UI SHELL — gedeeld design-system voor alle portaalpagina's
   ----------------------------------------------------------------------------
   Laden vóór de paginascript:
     <script type="text/babel" src="/ui/shell.jsx"></script>
   Alles is bereikbaar via window.EUI  (géén andere globals).
   ============================================================================ */
(() => {
const { useState, useEffect, useMemo, useCallback, useRef } = React;

/* ─── Design tokens ─────────────────────────────────────────────────────── */
const C = {
  primary: '#2b4f26',        // diep teal — merkkleur
  primaryDark: '#1a3117',
  primaryLight: '#e9f0e7',   // lichte tint (achtergrond voor selecties)
  primaryStrong: '#3d6d35',
  accent: '#e8a838',         // amber
  accentDark: '#b57e1a',
  bg: '#f2f4f3',
  card: '#ffffff',
  border: '#e3e7e5',
  borderSoft: '#eef1ef',
  text: '#16211f',
  textMuted: '#5f6f6c',
  textLight: '#93a19d',
  success: '#1e8a6e',
  warning: '#d69e2e',
  danger: '#c53030',
  info: '#2b6cb0',
  sidebar: '#1a3117',
  sidebarText: '#c3d2ce',
  sidebarActive: '#e8a838',
  headerBg: '#fbfcfb',
};

/* ─── Statusmodel (één bron van waarheid, slug → label + kleur) ─────────── */
const STATUS_META = {
  nieuw:                        { label: 'Nieuw',                         color: '#8a98a5' },
  in_behandeling:               { label: 'In behandeling',                color: '#4299e1' },
  offerte_verzonden:            { label: 'Offerte verzonden',             color: '#d69e2e' },
  goedgekeurd:                  { label: 'Goedgekeurd',                   color: '#38a169' },
  opgestart_outsourcing:        { label: 'Opgestart — outsourcing',       color: '#f59e0b' },
  opgestart_enerveco:           { label: 'Opgestart — Enerveco',          color: '#14b8a6' },
  advies_gestuurd:              { label: 'Advies gestuurd',               color: '#319795' },
  startverklaring_ingediend:    { label: 'Startverklaring ingediend',     color: '#3182ce' },
  stavingsstukken_opgevraagd:   { label: 'Stavingsstukken opgevraagd',    color: '#dd6b20' },
  voorbereiding_aangifte:       { label: 'Voorbereiding aangifte',        color: '#805ad5' },
  voorlopige_aangifte_verzonden:{ label: 'Voorlopige aangifte verzonden', color: '#6b46c1' },
  definitief_ingediend:         { label: 'Definitief ingediend',          color: '#2f855a' },
  afgewezen:                    { label: 'Afgewezen',                     color: '#c53030' },
  vervallen:                    { label: 'Vervallen',                     color: '#718096' },
};
const STATUS_ORDER = Object.keys(STATUS_META);
const normStatus = (s) => {
  if (!s) return 'nieuw';
  const k = String(s).toLowerCase().replace(/[\s-]+/g, '_');
  return STATUS_META[k] ? k : (s === 'Nieuw' ? 'nieuw' : k);
};
const statusLabel = (s) => (STATUS_META[normStatus(s)] || {}).label || s || '—';
const statusColor = (s) => (STATUS_META[normStatus(s)] || {}).color || '#8a98a5';

const INVOICE_STATUS_COLORS = {
  Verzonden: '#4299e1', Betaald: '#1e8a6e', Vervallen: '#c53030', Creditnota: '#8a98a5',
};

const URGENCY_META = {
  overdue:     { label: 'Verstreken',        color: '#c53030', bg: '#fde8e8' },
  critical:    { label: 'Kritiek (<30 d)',   color: '#c2410c', bg: '#ffedd5' },
  imminent:    { label: 'Binnen 90 d',       color: '#b45309', bg: '#fef3c7' },
  approaching: { label: 'Binnen 180 d',      color: '#1d4ed8', bg: '#dbeafe' },
  ok:          { label: 'Op schema',         color: '#166534', bg: '#dcfce7' },
  completed:   { label: 'Ingediend',         color: '#374151', bg: '#e5e7eb' },
  unknown:     { label: 'Geen datums',       color: '#6b7280', bg: '#f3f4f6' },
};

/* ─── API-client (cookie-sessie) ────────────────────────────────────────── */
const api = {
  async call(method, url, body) {
    const opts = { method, credentials: 'include', headers: {} };
    if (body !== undefined) {
      opts.headers['Content-Type'] = 'application/json';
      opts.body = JSON.stringify(body);
    }
    const r = await fetch(url, opts);
    if (r.status === 401) {
      if (!url.includes('/api/me')) window.location.href = '/';
      return null;
    }
    try { return await r.json(); } catch { return null; }
  },
  get:   (url)       => api.call('GET', url),
  post:  (url, body) => api.call('POST', url, body),
  put:   (url, body) => api.call('PUT', url, body),
  patch: (url, body) => api.call('PATCH', url, body),
  del:   (url)       => api.call('DELETE', url),
};

/* ─── Formatters ────────────────────────────────────────────────────────── */
const fmtEuro = (n, { dash = true } = {}) => {
  const v = Number(n);
  if (!isFinite(v) || (dash && v === 0)) return '—';
  return '€ ' + v.toLocaleString('nl-BE', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
};
const fmtDate = (d) => {
  if (!d) return '—';
  const t = new Date(String(d).replace(' ', 'T'));
  return isNaN(t) ? '—' : t.toLocaleDateString('nl-BE', { day: '2-digit', month: '2-digit', year: 'numeric' });
};
const fmtDateShort = (d) => {
  if (!d) return '—';
  const t = new Date(String(d).replace(' ', 'T'));
  return isNaN(t) ? '—' : t.toLocaleDateString('nl-BE', { day: '2-digit', month: '2-digit', year: '2-digit' });
};
const relTime = (d) => {
  if (!d) return '';
  const t = new Date(String(d).replace(' ', 'T'));
  if (isNaN(t)) return '';
  const s = (Date.now() - t.getTime()) / 1000;
  if (s < 90) return 'zonet';
  if (s < 3600) return Math.round(s / 60) + ' min geleden';
  if (s < 86400 * 2) return Math.round(s / 3600) + ' uur geleden';
  return Math.round(s / 86400) + ' dagen geleden';
};

/* ─── Viewport hook ─────────────────────────────────────────────────────── */
const MOBILE_BP = 768, TABLET_BP = 1080;
function useViewport() {
  const [w, setW] = useState(window.innerWidth);
  useEffect(() => {
    let raf = null;
    const onR = () => { if (!raf) raf = requestAnimationFrame(() => { raf = null; setW(window.innerWidth); }); };
    window.addEventListener('resize', onR);
    return () => window.removeEventListener('resize', onR);
  }, []);
  return { width: w, isMobile: w < MOBILE_BP, isTablet: w >= MOBILE_BP && w < TABLET_BP, isDesktop: w >= TABLET_BP };
}

/* ─── Iconen (inline SVG, stroke-based) ─────────────────────────────────── */
const ICON_PATHS = {
  dashboard: <><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></>,
  inbox_tray: <><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></>,
  mail: <><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></>,
  folder: <><path d="M4 4h5l2 3h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/></>,
  euro: <><path d="M18.5 5.5a7.5 7.5 0 1 0 0 13"/><path d="M3 10h10M3 14h10"/></>,
  doc: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h5"/></>,
  settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
  logout: <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/></>,
  search: <><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></>,
  plus: <><path d="M12 5v14M5 12h14"/></>,
  refresh: <><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></>,
  upload: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m17 8-5-5-5 5"/><path d="M12 3v12"/></>,
  download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/></>,
  calendar: <><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></>,
  bell: <><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></>,
  check: <><path d="M20 6 9 17l-5-5"/></>,
  x: <><path d="M18 6 6 18M6 6l12 12"/></>,
  chevron_left: <><path d="m15 18-6-6 6-6"/></>,
  chevron_right: <><path d="m9 18 6-6-6-6"/></>,
  external: <><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></>,
  map: <><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15M15 6v15"/></>,
  table: <><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18"/></>,
  archive: <><rect x="2" y="3" width="20" height="5" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></>,
  building: <><path d="M3 21h18"/><path d="M5 21V7l7-4 7 4v14"/><path d="M9 10h1M9 14h1M14 10h1M14 14h1"/></>,
  user: <><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></>,
  clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
  warning: <><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/></>,
  filter: <><path d="M22 3H2l8 9.46V19l4 2v-8.54z"/></>,
  paperclip: <><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></>,
  send: <><path d="m22 2-7 20-4-9-9-4z"/><path d="M22 2 11 13"/></>,
  home: <><path d="M3 9.5 12 3l9 6.5V20a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22v-8h6v8"/></>,
  eye: <><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></>,
  lock: <><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></>,
  phone: <><rect x="6" y="2" width="12" height="20" rx="2.5"/><path d="M11 18h2"/></>,
  edit: <><path d="M17 3a2.83 2.83 0 0 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></>,
};

function Icon({ name, size = 18, style = {}, strokeWidth = 2 }) {
  const paths = ICON_PATHS[name];
  if (!paths) return null;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"
         style={{ flexShrink: 0, verticalAlign: '-3px', ...style }} aria-hidden="true">
      {paths}
    </svg>
  );
}

/* ─── Basiscomponenten ──────────────────────────────────────────────────── */
function Btn({ children, onClick, variant, small, disabled, style = {}, title, type = 'button', icon }) {
  const base = {
    padding: small ? '6px 12px' : '9px 16px',
    borderRadius: 8, border: 'none', cursor: disabled ? 'not-allowed' : 'pointer',
    fontSize: small ? 12.5 : 13.5, fontWeight: 600, fontFamily: 'inherit',
    display: 'inline-flex', alignItems: 'center', gap: 7,
    transition: 'background .15s, color .15s, box-shadow .15s',
    opacity: disabled ? 0.55 : 1, whiteSpace: 'nowrap', lineHeight: 1.35,
  };
  const variants = {
    primary: { background: C.primary, color: '#fff' },
    accent:  { background: C.accent, color: C.primaryDark },
    danger:  { background: '#fde8e8', color: C.danger },
    ghost:   { background: 'transparent', color: C.textMuted, border: `1.5px solid ${C.border}` },
    default: { background: '#ebf1e9', color: C.primary, border: `1.5px solid #d5dfd1` },
  };
  return (
    <button type={type} title={title} disabled={disabled} onClick={onClick}
            style={{ ...base, ...(variants[variant] || variants.default), ...style }}>
      {icon && <Icon name={icon} size={small ? 14 : 16} />}{children}
    </button>
  );
}

function Badge({ color = '#8a98a5', children, style = {} }) {
  return (
    <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
                   background: color + '22', color, whiteSpace: 'nowrap', ...style }}>
      {children}
    </span>
  );
}

function StatusBadge({ status, small, style = {} }) {
  const meta = STATUS_META[normStatus(status)] || { label: status || '—', color: '#8a98a5' };
  return (
    <span style={{ padding: small ? '2px 8px' : '3px 10px', borderRadius: 999,
                   fontSize: small ? 10.5 : 11, fontWeight: 600,
                   background: meta.color + '1f', color: meta.color, whiteSpace: 'nowrap', ...style }}>
      {meta.label}
    </span>
  );
}

function Card({ title, subtitle, action, children, style = {}, bodyStyle = {} }) {
  return (
    <div style={{ background: C.card, borderRadius: 14, border: `1px solid ${C.border}`,
                  boxShadow: '0 1px 2px rgba(26,49,23,.04)', ...style }}>
      {(title || action) && (
        <div style={{ padding: '14px 20px', borderBottom: `1px solid ${C.borderSoft}`,
                      display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
          <div>
            <div style={{ fontWeight: 700, fontSize: 14.5, color: C.text }}>{title}</div>
            {subtitle && <div style={{ fontSize: 12, color: C.textMuted, marginTop: 2 }}>{subtitle}</div>}
          </div>
          {action}
        </div>
      )}
      <div style={{ padding: '16px 20px', ...bodyStyle }}>{children}</div>
    </div>
  );
}

const inputBase = {
  padding: '8px 11px', border: `1.5px solid ${C.border}`, borderRadius: 8,
  background: '#fbfcfb', fontSize: 13.5, fontFamily: 'inherit', color: C.text,
  outline: 'none', width: '100%', boxSizing: 'border-box',
};
function Input(props) { return <input {...props} style={{ ...inputBase, ...(props.style || {}) }} />; }
function Select({ children, ...props }) {
  return <select {...props} style={{ ...inputBase, cursor: 'pointer', ...(props.style || {}) }}>{children}</select>;
}
function Field({ label, children, style = {} }) {
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0, ...style }}>
      <span style={{ fontSize: 10.5, fontWeight: 700, textTransform: 'uppercase',
                     letterSpacing: '.5px', color: C.textMuted }}>{label}</span>
      {children}
    </label>
  );
}

function Spinner({ label = 'Laden…' }) {
  return (
    <div style={{ padding: 48, textAlign: 'center', color: C.textMuted, fontSize: 14 }}>
      <div className="eui-spin" style={{ width: 26, height: 26, border: `3px solid ${C.border}`,
        borderTopColor: C.primary, borderRadius: '50%', margin: '0 auto 12px' }} />
      {label}
    </div>
  );
}

function EmptyState({ icon = 'folder', title, hint }) {
  return (
    <div style={{ padding: '44px 20px', textAlign: 'center', color: C.textMuted }}>
      <div style={{ width: 52, height: 52, borderRadius: 14, background: C.bg, display: 'flex',
                    alignItems: 'center', justifyContent: 'center', margin: '0 auto 14px', color: C.textLight }}>
        <Icon name={icon} size={24} />
      </div>
      <div style={{ fontWeight: 700, fontSize: 14, color: C.text }}>{title}</div>
      {hint && <div style={{ fontSize: 12.5, marginTop: 5 }}>{hint}</div>}
    </div>
  );
}

/* ─── Navigatie ─────────────────────────────────────────────────────────── */
const NAV_ITEMS = [
  { id: 'dashboard',    icon: 'dashboard',  label: 'Dashboard' },
  { id: 'aanvragen',    icon: 'inbox_tray', label: 'Aanvragen', href: '/aanvragen.html' },
  { id: 'projects',     icon: 'folder',     label: 'Projecten' },
  { id: 'overzicht',    icon: 'table',      label: 'Overzicht' },
  { id: 'invoices',     icon: 'euro',       label: 'Facturatie' },
  { id: 'offerte',      icon: 'doc',        label: 'Offerte Generator', href: '/offerte.html', newTab: true },
  { id: 'inbox',        icon: 'mail',       label: 'Inbox' },
  { id: 'instellingen', icon: 'settings',   label: 'Instellingen' },
  { id: 'snel',         icon: 'phone',      label: 'Mobile', href: '/app.html' },
];

/** Navigeer naar een pagina van de hoofdapp (index.html). */
function goPage(id) {
  const item = NAV_ITEMS.find(m => m.id === id);
  if (item && item.href) {
    if (item.newTab) window.open(item.href, '_blank'); else window.location.href = item.href;
    return;
  }
  window.location.href = '/?page=' + id;
}

/* ─── Layout (sidebar + topbar) ─────────────────────────────────────────── */
function Layout({ children, currentUser, active, page, setPage, badges = {}, fullBleed = false, topExtra }) {
  const { isMobile } = useViewport();
  const [collapsed, setCollapsed] = useState(() => localStorage.getItem('sidebar_collapsed') === '1');
  const [drawerOpen, setDrawerOpen] = useState(false);
  const activeId = active || page;

  useEffect(() => { localStorage.setItem('sidebar_collapsed', collapsed ? '1' : '0'); }, [collapsed]);
  useEffect(() => { if (!isMobile) setDrawerOpen(false); }, [isMobile]);

  const nav = (m) => {
    setDrawerOpen(false);
    if (m.href) { if (m.newTab) window.open(m.href, '_blank'); else window.location.href = m.href; return; }
    if (setPage) {
      setPage(m.id);
      try { history.replaceState(null, '', '/?page=' + m.id); } catch {}
    } else {
      window.location.href = '/?page=' + m.id;
    }
  };

  const doLogout = async () => {
    await api.post('/api/logout');
    window.location.href = '/';
  };

  const sidebarWidth = collapsed ? 64 : 224;
  const showSidebar = !isMobile || drawerOpen;

  const sidebar = (
    <div style={{
      width: sidebarWidth, background: C.sidebar, color: C.sidebarText,
      display: 'flex', flexDirection: 'column', flexShrink: 0,
      transition: 'width .18s ease', overflow: 'hidden',
      ...(isMobile ? { position: 'fixed', inset: '0 auto 0 0', zIndex: 2500, width: 240,
                       boxShadow: drawerOpen ? '8px 0 40px rgba(0,0,0,.35)' : 'none' } : {}),
    }}>
      {/* brand — het échte Enerveco-logo (lichte variant voor donkere achtergrond) */}
      <div style={{ padding: collapsed && !isMobile ? '16px 6px' : '16px 20px', display: 'flex',
                    flexDirection: 'column', alignItems: collapsed && !isMobile ? 'center' : 'flex-start', gap: 6,
                    borderBottom: '1px solid rgba(245,241,234,.08)' }}>
        <img src="/ui/logo-light.png" srcSet="/ui/logo-light.png 1x, /ui/logo-light@2x.png 2x" alt="Enerveco"
             style={{ height: collapsed && !isMobile ? 34 : 56, width: 'auto', display: 'block' }} />
        {(!collapsed || isMobile) && (
          <div style={{ fontSize: 10, letterSpacing: '.14em', textTransform: 'uppercase', color: 'rgba(195,210,206,.65)' }}>Projectbeheer</div>
        )}
      </div>

      {/* nav */}
      <nav style={{ flex: 1, padding: '12px 10px', display: 'flex', flexDirection: 'column', gap: 3, overflowY: 'auto' }}>
        {NAV_ITEMS.map(m => {
          const isActive = activeId === m.id;
          const badge = badges[m.id];
          return (
            <button key={m.id} onClick={() => nav(m)} title={m.label}
              style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: collapsed && !isMobile ? '11px 0' : '10px 12px',
                justifyContent: collapsed && !isMobile ? 'center' : 'flex-start',
                borderRadius: 9, border: 'none', cursor: 'pointer', width: '100%',
                background: isActive ? 'rgba(232,168,56,.14)' : 'transparent',
                color: isActive ? '#f0c268' : C.sidebarText,
                fontSize: 13.5, fontWeight: isActive ? 700 : 500, fontFamily: 'inherit',
                position: 'relative', transition: 'background .12s, color .12s',
              }}
              onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'rgba(245,241,234,.06)'; }}
              onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'transparent'; }}
            >
              {isActive && <span style={{ position: 'absolute', left: 0, top: 8, bottom: 8, width: 3,
                                          borderRadius: 3, background: C.sidebarActive }} />}
              <Icon name={m.icon} size={18} />
              {(!collapsed || isMobile) && <span style={{ flex: 1, textAlign: 'left', whiteSpace: 'nowrap' }}>{m.label}</span>}
              {(!collapsed || isMobile) && badge ? (
                <span style={{ background: C.danger, color: '#fff', fontSize: 10.5, fontWeight: 700,
                               borderRadius: 999, padding: '1px 7px' }}>{badge}</span>
              ) : null}
              {m.newTab && (!collapsed || isMobile) && <Icon name="external" size={12} style={{ opacity: .5 }} />}
            </button>
          );
        })}
      </nav>

      {/* footer */}
      <div style={{ padding: '12px 10px', borderTop: '1px solid rgba(245,241,234,.08)' }}>
        {(!collapsed || isMobile) && currentUser && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 12px 10px' }}>
            <div style={{ width: 30, height: 30, borderRadius: '50%', background: 'rgba(232,168,56,.18)',
                          color: '#f0c268', display: 'flex', alignItems: 'center', justifyContent: 'center',
                          fontSize: 12, fontWeight: 700, flexShrink: 0 }}>
              {(currentUser.name || currentUser.username || '?').slice(0, 1).toUpperCase()}
            </div>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: '#f5f1ea', overflow: 'hidden',
                            textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{currentUser.name || currentUser.username}</div>
              <div style={{ fontSize: 10.5, color: 'rgba(195,210,206,.6)' }}>{currentUser.role === 'admin' ? 'Beheerder' : 'Gebruiker'}</div>
            </div>
          </div>
        )}
        <button onClick={doLogout}
          style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%',
                   padding: collapsed && !isMobile ? '10px 0' : '9px 12px',
                   justifyContent: collapsed && !isMobile ? 'center' : 'flex-start',
                   borderRadius: 9, border: 'none', cursor: 'pointer',
                   background: 'transparent', color: '#e59898', fontSize: 13, fontWeight: 600, fontFamily: 'inherit' }}
          onMouseEnter={e => e.currentTarget.style.background = 'rgba(197,48,48,.15)'}
          onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
          <Icon name="logout" size={17} />
          {(!collapsed || isMobile) && 'Uitloggen'}
        </button>
        {!isMobile && (
          <button onClick={() => setCollapsed(!collapsed)}
            style={{ marginTop: 4, display: 'flex', alignItems: 'center', justifyContent: 'center',
                     width: '100%', padding: '7px 0', borderRadius: 9, border: 'none', cursor: 'pointer',
                     background: 'transparent', color: 'rgba(195,210,206,.5)', fontFamily: 'inherit' }}
            title={collapsed ? 'Menu uitklappen' : 'Menu inklappen'}>
            <Icon name={collapsed ? 'chevron_right' : 'chevron_left'} size={16} />
          </button>
        )}
      </div>
    </div>
  );

  return (
    <div style={{ display: 'flex', height: '100vh', overflow: 'hidden', background: C.bg }}>
      {showSidebar && sidebar}
      {isMobile && drawerOpen && (
        <div onClick={() => setDrawerOpen(false)}
             style={{ position: 'fixed', inset: 0, background: 'rgba(26,49,23,.5)', zIndex: 2400 }} />
      )}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        {/* topbar — weggelaten op desktop-fullBleed zonder extra inhoud */}
        {(isMobile || topExtra) && (
        <div style={{ background: C.headerBg, borderBottom: `1px solid ${C.border}`,
                      padding: isMobile ? '10px 14px' : '10px 26px',
                      display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0 }}>
          {isMobile && (
            <button onClick={() => setDrawerOpen(true)}
              style={{ background: 'none', border: 'none', cursor: 'pointer', color: C.text, padding: 4 }}
              aria-label="Menu openen">
              <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
            </button>
          )}
          <div style={{ flex: 1, minWidth: 0 }} />
          {topExtra}
        </div>
        )}
        {/* content */}
        <div style={{ flex: 1, overflow: fullBleed ? 'hidden' : 'auto', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
          {fullBleed ? children : (
            <div style={{ padding: isMobile ? '18px 14px' : '26px 32px', maxWidth: 1520, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
              {children}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function PageHeader({ title, subtitle, actions }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between',
                  gap: 16, flexWrap: 'wrap', marginBottom: 20 }}>
      <div>
        <h1 style={{ margin: 0, fontSize: 23, fontWeight: 800, color: C.text, letterSpacing: '-.01em' }}>{title}</h1>
        {subtitle && <div style={{ fontSize: 13, color: C.textMuted, marginTop: 3 }}>{subtitle}</div>}
      </div>
      {actions && <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>{actions}</div>}
    </div>
  );
}

/* ─── Login screen ──────────────────────────────────────────────────────── */
function LoginScreen({ onLogin }) {
  const [user, setUser] = useState('');
  const [pass, setPass] = useState('');
  const [showPass, setShowPass] = useState(false);
  const [err, setErr] = useState('');
  const [loading, setLoading] = useState(false);
  const submit = async (e) => {
    e.preventDefault(); setLoading(true); setErr('');
    const r = await api.post('/api/login', { username: user, password: pass });
    setLoading(false);
    if (r && r.token) onLogin(r.user);
    else setErr((r && r.error) || 'Inloggen mislukt');
  };
  return (
    <div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
                  background: `linear-gradient(135deg, #1a3117 0%, #2b4f26 55%, #3d6d35 100%)`, padding: 16 }}>
      <form onSubmit={submit} style={{ background: '#fff', borderRadius: 18, padding: '44px 38px',
                                       width: 390, maxWidth: '100%', boxShadow: '0 24px 70px rgba(0,0,0,.35)' }}>
        <div style={{ textAlign: 'center', marginBottom: 30 }}>
          <img src="/ui/logo.png" srcSet="/ui/logo.png 1x, /ui/logo@2x.png 2x" alt="Enerveco"
               style={{ height: 86, width: 'auto', display: 'inline-block', marginBottom: 10 }} />
          <div style={{ fontSize: 12.5, color: C.textMuted, letterSpacing: '.06em', textTransform: 'uppercase' }}>Projectbeheer</div>
        </div>
        <Field label="Gebruikersnaam" style={{ marginBottom: 14 }}>
          <Input value={user} onChange={e => setUser(e.target.value)} autoFocus autoComplete="username" />
        </Field>
        <Field label="Wachtwoord" style={{ marginBottom: 20 }}>
          <div style={{ position: 'relative' }}>
            <Input type={showPass ? 'text' : 'password'} value={pass} onChange={e => setPass(e.target.value)}
                   autoComplete="current-password" style={{ paddingRight: 40 }} />
            <button type="button" onClick={() => setShowPass(!showPass)}
                    aria-label={showPass ? 'Wachtwoord verbergen' : 'Wachtwoord tonen'}
                    style={{ position: 'absolute', right: 8, top: '50%', transform: 'translateY(-50%)',
                             background: 'none', border: 'none', cursor: 'pointer', color: C.textLight, padding: 4 }}>
              <Icon name="eye" size={16} />
            </button>
          </div>
        </Field>
        {err && <div style={{ background: '#fde8e8', color: C.danger, borderRadius: 8, padding: '9px 12px',
                              fontSize: 12.5, marginBottom: 14, fontWeight: 600 }}>{err}</div>}
        <Btn type="submit" variant="primary" disabled={loading}
             style={{ width: '100%', justifyContent: 'center', padding: '11px 0', fontSize: 14 }}>
          {loading ? 'Bezig…' : 'Inloggen'}
        </Btn>
      </form>
    </div>
  );
}

/* ─── Export ────────────────────────────────────────────────────────────── */
window.EUI = {
  C, STATUS_META, STATUS_ORDER, normStatus, statusLabel, statusColor,
  INVOICE_STATUS_COLORS, URGENCY_META,
  api, fmtEuro, fmtDate, fmtDateShort, relTime,
  useViewport, MOBILE_BP, TABLET_BP,
  Icon, Btn, Badge, StatusBadge, Card, Input, Select, Field, Spinner, EmptyState,
  NAV_ITEMS, goPage, Layout, PageHeader, LoginScreen,
};
})();
