const { useState: useStateLive, useMemo: useMemoLive, useEffect: useEffectLive, useRef: useRefLive } = React;

const STATUS_BUTTONS = [
  { id: 'Open', label: 'Open' },
  { id: 'Working', label: 'Bezig' },
  { id: 'Completed', label: 'Klaar' },
];

const TEMPLATE_PRIORITY_OPTIONS_LIVE = ['Low', 'Medium', 'High', 'Urgent'];

const statusButtonStyle = (active) => ({
  padding: '5px 9px',
  fontSize: 11,
  fontWeight: 600,
  borderRadius: 6,
  cursor: 'pointer',
  border: active ? '1px solid var(--ink-1)' : '1px solid var(--line)',
  background: active ? 'var(--ink-1)' : 'var(--paper)',
  color: active ? 'var(--paper)' : 'var(--ink-2)',
});

const inputBaseStyle = {
  width: '100%',
  padding: '8px 10px',
  fontSize: 13,
  border: '1px solid var(--line)',
  borderRadius: 7,
  background: 'var(--paper)',
  color: 'var(--ink-1)',
  fontFamily: 'inherit',
  outline: 'none',
  boxSizing: 'border-box',
};

const compactDateInputStyle = {
  ...inputBaseStyle,
  width: 150,
  padding: '6px 8px',
  fontSize: 11.5,
};

const emptyAddress = () => ({
  address_line1: '',
  address_line2: '',
  city: '',
  county: '',
  state: '',
  pincode: '',
  country: 'Netherlands',
});

const withAddressDefaults = (address) => ({
  ...emptyAddress(),
  ...(address || {}),
});

const emptyContactDraft = () => ({
  name: '',
  email: '',
  phone: '',
  role: '',
});

const emptyUserDraft = () => ({
  name: '',
  email: '',
  phone: '',
  mobile: '',
  roleProfile: '',
});

const makeDraftId = () => `draft-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;

const emptyTemplateStepDraft = (taskType = '') => ({
  localId: makeDraftId(),
  taskId: '',
  taskType,
  name: taskType || '',
  durationDays: 1,
  expectedHours: '',
  priority: 'Medium',
  milestone: false,
  description: '',
});

const parseNumericInput = (value, fallback = 0) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
};

const formatHoursLabel = (value) => {
  const amount = parseNumericInput(value, 0);
  if (!amount) return '0 uur';
  const rounded = Math.round(amount * 10) / 10;
  return `${String(rounded).replace('.0', '')} uur`;
};

const formatAddressPreview = (address) => {
  const normalized = withAddressDefaults(address);
  return [
    normalized.address_line1,
    normalized.address_line2,
    [normalized.pincode, normalized.city].filter(Boolean).join(' ').trim(),
    normalized.country,
  ].filter(Boolean);
};

const displayTeamMemberName = (user) => {
  const cleaned = String(user?.naam || '').replace(/^Pilot\s+/i, '').trim();
  return cleaned || user?.naam || user?.id || 'Onbekend';
};

const emptyWorkloadStats = () => ({
  open: 0,
  overdue: 0,
  deze_week: 0,
  vandaag: 0,
});

const isWorkdayLive = (date) => {
  const weekday = date.getDay();
  return weekday >= 1 && weekday <= 5;
};

const shiftToWorkdayLive = (date, direction = 'next') => {
  if (!date) return null;
  const cursor = new Date(date);
  cursor.setHours(0, 0, 0, 0);
  const step = direction === 'previous' ? -1 : 1;
  while (!isWorkdayLive(cursor)) {
    cursor.setDate(cursor.getDate() + step);
  }
  return cursor;
};

const addWorkdaysLive = (date, amount) => {
  const cursor = shiftToWorkdayLive(date, 'next');
  let remaining = Math.max(parseNumericInput(amount, 0), 0);
  while (remaining > 0) {
    cursor.setDate(cursor.getDate() + 1);
    if (isWorkdayLive(cursor)) remaining -= 1;
  }
  return cursor;
};

const buildTemplateSchedulePreview = (template, startDateValue) => {
  if (!template || !(template.steps || []).length) return null;
  const baseStart = shiftToWorkdayLive(startDateValue || window.todayDate || new Date(), 'next');
  let currentStart = new Date(baseStart);
  let lastEnd = new Date(baseStart);

  const stepSchedule = template.steps.map((step) => {
    const durationDays = Math.max(parseNumericInput(step.durationDays, 1), 1);
    const start = new Date(currentStart);
    const end = addWorkdaysLive(start, durationDays - 1);
    lastEnd = new Date(end);
    currentStart = addWorkdaysLive(end, 1);
    return {
      ...step,
      start: window.iso(start),
      end: window.iso(end),
      durationDays,
    };
  });

  return {
    start: window.iso(baseStart),
    end: window.iso(lastEnd),
    stepCount: stepSchedule.length,
    durationDays: stepSchedule.reduce((sum, step) => sum + step.durationDays, 0),
    expectedHours: stepSchedule.reduce((sum, step) => sum + parseNumericInput(step.expectedHours, 0), 0),
    steps: stepSchedule,
  };
};

function getMyDayAgendaSnapshotLive(userId) {
  const today = String(window.today || window.iso(new Date()));
  const tasks = window.getMyTasks(userId);
  const prepared = tasks.map((task) => {
    const start = task.start || task.deadline || '';
    const deadline = task.deadline || task.start || '';
    const overdue = Boolean(deadline && window.daysUntil(deadline) < 0);
    const activeToday = Boolean(start && deadline && start <= today && deadline >= today);
    const anchorDate = activeToday ? today : (start || deadline || '');
    return {
      ...task,
      start,
      deadline,
      overdue,
      anchorDate,
    };
  });
  const overdueTasks = prepared.filter((task) => task.overdue);
  const plannedTasks = prepared.filter((task) => !task.overdue);
  const timelineKeys = [
    ...new Set(
      plannedTasks
        .filter((task) => task.anchorDate)
        .map((task) => task.anchorDate)
        .sort()
    ),
  ].slice(0, 5);
  const visiblePlannedTasks = plannedTasks.filter((task) => timelineKeys.includes(task.anchorDate));

  return {
    allCount: tasks.length,
    visibleCount: overdueTasks.length + visiblePlannedTasks.length,
  };
}

function buildSidebarSections() {
  const myDaySnapshot = getMyDayAgendaSnapshotLive(window.CURRENT_USER_ID);
  return [
    {
      title: null,
      items: [
        { id: 'kanban', label: 'Kanban', icon: 'kanban', count: window.PROJECTEN.length },
        { id: 'mijndag', label: 'Mijn dag', icon: 'today', count: myDaySnapshot.visibleCount, hot: myDaySnapshot.visibleCount > 0 },
        { id: 'perklant', label: 'Per klant', icon: 'klant' },
        { id: 'lijst', label: 'Alle projecten', icon: 'lijst' },
        { id: 'team', label: 'Team workload', icon: 'team' },
      ],
    },
    {
      title: 'Configuratie',
      items: [
        { id: 'klanten', label: 'Klanten', icon: 'klant', count: window.KLANTEN.length },
        { id: 'gebruikers', label: 'Gebruikers', icon: 'team', count: window.GEBRUIKERS.length },
        { id: 'templates', label: 'Templates', icon: 'settings', count: window.PROJECT_TEMPLATES.filter((template) => !template.disabled).length },
      ],
    },
  ];
}

const SidebarLive = ({ view, setView, onNewProject }) => {
  const currentUser = window.medewerkerById(window.CURRENT_USER_ID);
  const currentConfigUser = window.GEBRUIKERS.find((user) => user.id === window.CURRENT_USER_ID) || null;
  const [switchingUser, setSwitchingUser] = useStateLive(false);
  const [switchError, setSwitchError] = useStateLive('');
  const [userMenuOpen, setUserMenuOpen] = useStateLive(false);
  const userMenuRef = useRefLive(null);
  const switchableUsers = useMemoLive(() => (
    [...window.GEBRUIKERS].sort((left, right) => (
      String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
    ))
  ), [window.GEBRUIKERS]);

  useEffectLive(() => {
    if (!userMenuOpen) return undefined;
    const handleOutsideClick = (event) => {
      if (userMenuRef.current && !userMenuRef.current.contains(event.target)) {
        setUserMenuOpen(false);
      }
    };
    document.addEventListener('mousedown', handleOutsideClick);
    return () => document.removeEventListener('mousedown', handleOutsideClick);
  }, [userMenuOpen]);

  const handleUserSwitch = async (nextUserId) => {
    if (!nextUserId) return;
    if (nextUserId === window.CURRENT_USER_ID) {
      setUserMenuOpen(false);
      return;
    }
    setSwitchingUser(true);
    setSwitchError('');
    try {
      await window.dashboardApi.switchCurrentUser(nextUserId);
      setUserMenuOpen(false);
    } catch (error) {
      setSwitchError(error.message || 'Wisselen van gebruiker mislukt.');
    } finally {
      setSwitchingUser(false);
    }
  };

  return (
    <aside style={{
      width: 232, flexShrink: 0, background: 'var(--paper)',
      borderRight: '1px solid var(--line)', display: 'flex', flexDirection: 'column',
      padding: '18px 14px 14px',
    }}>
      <div style={{ padding: '0 6px 18px' }}><window.Logo /></div>

      <button className="primary-btn" onClick={onNewProject} style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        gap: 8, padding: '10px 12px', borderRadius: 9,
        background: 'var(--ink-1)', color: 'var(--paper)', border: 'none',
        fontSize: 13, fontWeight: 600, cursor: 'pointer', marginBottom: 14,
        boxShadow: '0 1px 0 rgba(255,255,255,0.1) inset, 0 1px 2px rgba(0,0,0,0.06)',
      }}>
        <window.Icon name="plus" size={14} /> Nieuwe aanvraag
      </button>

      <nav style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        {buildSidebarSections().map((section, sectionIndex) => (
          <div key={section.title || `section-${sectionIndex}`}>
            {section.title && (
              <div style={{
                padding: '0 10px', fontSize: 10, fontWeight: 700, color: 'var(--ink-3)',
                letterSpacing: 0.6, marginBottom: 6, textTransform: 'uppercase',
              }}>
                {section.title}
              </div>
            )}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
              {section.items.map((item) => (
                <button key={item.id} onClick={() => setView(item.id)} style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '8px 10px', borderRadius: 7,
                  border: 'none', background: view === item.id ? 'var(--accent-bg)' : 'transparent',
                  color: view === item.id ? 'var(--accent-fg)' : 'var(--ink-2)',
                  fontSize: 13, fontWeight: view === item.id ? 600 : 500,
                  cursor: 'pointer', textAlign: 'left',
                }}>
                  <window.Icon name={item.icon} size={16} />
                  <span style={{ flex: 1 }}>{item.label}</span>
                  {item.count != null && (
                    <span style={{
                      fontSize: 11, fontWeight: 600,
                      color: item.hot ? 'oklch(0.98 0 0)' : 'var(--ink-3)',
                      background: item.hot ? 'oklch(0.55 0.18 28)' : 'transparent',
                      padding: item.hot ? '1px 6px' : '0',
                      borderRadius: 10,
                    }}>{item.count}</span>
                  )}
                </button>
              ))}
            </div>
          </div>
        ))}
      </nav>

      <div style={{ flex: 1 }} />

      {currentUser && (
        <div ref={userMenuRef} style={{
          position: 'relative',
          padding: '10px', background: 'var(--surface-1)', borderRadius: 8,
          fontSize: 11, color: 'var(--ink-2)', lineHeight: 1.45,
        }}>
          <button
            onClick={() => {
              setSwitchError('');
              setUserMenuOpen((open) => !open);
            }}
            style={{
              width: '100%',
              display: 'flex',
              alignItems: 'center',
              gap: 8,
              padding: 0,
              border: 'none',
              background: 'transparent',
              cursor: 'pointer',
              textAlign: 'left',
            }}
          >
            <window.Avatar m={currentUser} size={24} />
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-1)' }}>{currentUser.naam}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-3)' }}>
                {currentConfigUser?.roleLabel || currentUser.rol}
              </div>
            </div>
            <span style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              width: 24,
              height: 24,
              borderRadius: 6,
              background: userMenuOpen ? 'var(--paper)' : 'transparent',
              color: 'var(--ink-3)',
              transform: userMenuOpen ? 'rotate(90deg)' : 'none',
              transition: 'transform 140ms ease',
            }}>
              <window.Icon name="chevron" size={13} />
            </span>
          </button>

          {userMenuOpen && (
            <div style={{
              position: 'absolute',
              left: 0,
              right: 0,
              bottom: 'calc(100% + 8px)',
              zIndex: 20,
              padding: '10px',
              background: 'var(--paper)',
              border: '1px solid var(--line)',
              borderRadius: 10,
              boxShadow: '0 12px 28px rgba(0,0,0,0.10)',
              display: 'flex',
              flexDirection: 'column',
              gap: 6,
              maxHeight: 'min(320px, calc(100vh - 140px))',
              overflowY: 'auto',
            }}>
              <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.2 }}>
                Wissel gebruiker
              </div>
              {switchableUsers.map((user) => {
                const active = user.id === window.CURRENT_USER_ID;
                return (
                  <button
                    key={user.id}
                    onClick={() => handleUserSwitch(user.id)}
                    disabled={switchingUser}
                    style={{
                      display: 'flex',
                      alignItems: 'center',
                      gap: 8,
                      width: '100%',
                      padding: '7px 8px',
                      borderRadius: 8,
                      border: active ? '1px solid var(--line)' : '1px solid transparent',
                      background: active ? 'var(--paper)' : 'transparent',
                      cursor: switchingUser ? 'not-allowed' : 'pointer',
                      textAlign: 'left',
                      opacity: switchingUser ? 0.7 : 1,
                    }}
                  >
                    <window.Avatar m={user} size={22} />
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink-1)' }}>{user.naam}</div>
                      <div style={{ fontSize: 10, color: 'var(--ink-3)' }}>{user.roleLabel || user.rol}</div>
                    </div>
                    {active && (
                      <span style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600 }}>
                        actief
                      </span>
                    )}
                  </button>
                );
              })}
              {switchError && (
                <div style={{ marginTop: 2, fontSize: 10.5, color: 'oklch(0.42 0.16 25)' }}>
                  {switchError}
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </aside>
  );
};

const TopbarLive = ({ view, search, setSearch }) => {
  const myDaySnapshot = getMyDayAgendaSnapshotLive(window.CURRENT_USER_ID);
  const titles = {
    kanban: 'NPD Ontwikkeling',
    mijndag: 'Mijn dag',
    perklant: 'Per klant',
    klanten: 'Klanten',
    gebruikers: 'Gebruikers',
    templates: 'Projecttemplates',
    lijst: 'Alle projecten',
    team: 'Team workload',
  };

  const subtitles = {
    kanban: `${window.PROJECTEN.length} lopende projecten · ${window.BUCKETS.length} buckets`,
    mijndag: `${myDaySnapshot.visibleCount} taken in beeld · ${window.todayHuman}`,
    perklant: `${window.KLANTEN.length} klanten met lopende of potentiële dashboardprojecten`,
    klanten: '',
    gebruikers: '',
    templates: `${window.PROJECT_TEMPLATES.length} templates voor automatische stapplanning`,
    lijst: 'Zoek, filter en demo vanuit live ERPNext-data',
    team: `${window.ALL_TASKS.length} openstaande stappen`,
  };

  return (
    <header style={{
      display: 'flex', alignItems: 'center', gap: 14,
      padding: '14px 24px', borderBottom: '1px solid var(--line)',
      background: 'var(--paper)',
    }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <h1 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0 }}>{titles[view]}</h1>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>{subtitles[view]}</div>
      </div>

      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '7px 10px', background: 'var(--surface-1)',
        border: '1px solid var(--line)', borderRadius: 8, width: 260,
        color: 'var(--ink-3)',
      }}>
        <window.Icon name="search" size={14} />
        <input
          value={search}
          onChange={(event) => setSearch(event.target.value)}
          placeholder="Zoek project, klant, saus…"
          style={{
            border: 'none', outline: 'none', background: 'transparent',
            fontSize: 13, flex: 1, color: 'var(--ink-1)', fontFamily: 'inherit',
          }}
        />
        <span style={{ fontSize: 10, color: 'var(--ink-3)', padding: '1px 5px', background: 'var(--paper)', borderRadius: 4, border: '1px solid var(--line)' }}>⌘K</span>
      </div>

      <button style={{
        display: 'flex', alignItems: 'center', gap: 6,
        padding: '7px 12px', border: '1px solid var(--line)',
        borderRadius: 8, background: 'var(--paper)', cursor: 'default',
        fontSize: 13, fontWeight: 500, color: 'var(--ink-2)',
      }}>
        <window.Icon name="filter" size={14} /> Live data
      </button>
    </header>
  );
};

const NewProjectModalLive = ({ onClose, onCreated }) => {
  const customers = window.KLANTEN;
  const activeTemplates = useMemoLive(() => (
    [...window.PROJECT_TEMPLATES]
      .filter((template) => !template.disabled)
      .sort((left, right) => String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl'))
  ), [window.PROJECT_TEMPLATES]);
  const [klantId, setKlantId] = useStateLive(customers[0]?.id || '');
  const [naam, setNaam] = useStateLive('');
  const [prio, setPrio] = useStateLive('gemiddeld');
  const [deadline, setDeadline] = useStateLive(() => window.iso(window.addDays(window.todayDate || new Date(), 21)));
  const [sauzen, setSauzen] = useStateLive(['']);
  const [templateId, setTemplateId] = useStateLive('');
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');
  const selectedTemplate = templateId ? window.templateById(templateId) : null;
  const templatePreview = selectedTemplate ? buildTemplateSchedulePreview(selectedTemplate, window.todayDate || new Date()) : null;
  const templateFinishAfterDeadline = Boolean(
    templatePreview
    && deadline
    && new Date(`${templatePreview.end}T00:00:00`).getTime() > new Date(`${deadline}T00:00:00`).getTime()
  );

  const saveProject = async () => {
    setError('');
    if (!naam.trim()) {
      setError('Vul een projectnaam in.');
      return;
    }
    if (!klantId) {
      setError('Selecteer een klant.');
      return;
    }
    if (!deadline) {
      setError('Vul een einddatum in voor het project.');
      return;
    }

    const cleanedSauces = sauzen.map((item) => item.trim()).filter(Boolean);
    if (cleanedSauces.length === 0) {
      setError('Voeg minimaal één saus toe.');
      return;
    }

    setBusy(true);
    try {
      const result = await window.dashboardApi.createProject({
        customer: klantId,
        name: naam.trim(),
        prio,
        deadline,
        sauces: cleanedSauces,
        projectTemplate: templateId || null,
      });
      if (onCreated) onCreated(result.projectId);
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 560, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>NIEUWE AANVRAAG</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>Project aanmaken</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Projectnummer">
            <div style={{
              ...inputBaseStyle,
              fontFamily: 'var(--mono)',
              fontWeight: 700,
              color: 'var(--ink-1)',
              background: 'var(--surface-1)',
              cursor: 'default',
            }}>
              {window.NEXT_PROJECT_ID_PREVIEW || 'Projectnummer wordt geladen'}
            </div>
          </FieldLive>

          <FieldLive label="Klant">
            {customers.length > 0 ? (
              <select value={klantId} onChange={(event) => setKlantId(event.target.value)} style={inputBaseStyle}>
                {customers.map((customer) => (
                  <option key={customer.id} value={customer.id}>{customer.naam}</option>
                ))}
              </select>
            ) : (
              <div style={{
                padding: '10px 12px', border: '1px solid var(--line)', borderRadius: 8,
                background: 'var(--surface-1)', fontSize: 12, color: 'var(--ink-2)',
              }}>
                Voeg eerst een klant toe in de tab <strong>Klanten</strong>.
              </div>
            )}
          </FieldLive>

          <FieldLive label="Projectnaam">
            <input value={naam} onChange={(event) => setNaam(event.target.value)} placeholder="bijv. Appelmoes lijn 2026" style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Template" hint="Optioneel, wordt per saus toegepast">
            <div style={{ display: 'grid', gap: 10 }}>
              <select value={templateId} onChange={(event) => setTemplateId(event.target.value)} style={inputBaseStyle}>
                <option value="">Geen template</option>
                {activeTemplates.map((template) => (
                  <option key={template.id} value={template.id}>
                    {template.naam} · {template.stepCount} stappen · {template.durationDays} werkdagen
                  </option>
                ))}
              </select>

              {selectedTemplate && templatePreview && (
                <div style={{
                  padding: '12px 14px',
                  borderRadius: 10,
                  border: '1px solid var(--line)',
                  background: 'var(--surface-1)',
                  display: 'grid',
                  gap: 10,
                }}>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))', gap: 8 }}>
                    <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--paper)', border: '1px solid var(--line)' }}>
                      <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.3 }}>PER SAUS</div>
                      <div style={{ marginTop: 4, fontSize: 13.5, fontWeight: 650, color: 'var(--ink-1)' }}>{templatePreview.stepCount} stappen</div>
                    </div>
                    <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--paper)', border: '1px solid var(--line)' }}>
                      <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.3 }}>PLANNING</div>
                      <div style={{ marginTop: 4, fontSize: 13.5, fontWeight: 650, color: 'var(--ink-1)' }}>{templatePreview.durationDays} werkdagen</div>
                    </div>
                    <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--paper)', border: '1px solid var(--line)' }}>
                      <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.3 }}>UREN</div>
                      <div style={{ marginTop: 4, fontSize: 13.5, fontWeight: 650, color: 'var(--ink-1)' }}>{formatHoursLabel(templatePreview.expectedHours)}</div>
                    </div>
                    <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--paper)', border: '1px solid var(--line)' }}>
                      <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.3 }}>VERWACHTE EINDDATUM</div>
                      <div style={{ marginTop: 4, fontSize: 13.5, fontWeight: 650, color: 'var(--ink-1)' }}>{window.fmtDateLong(templatePreview.end)}</div>
                    </div>
                  </div>

                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {templatePreview.steps.map((step, index) => (
                      <div key={step.taskId || `${step.name}-${index}`} style={{
                        display: 'grid',
                        gridTemplateColumns: 'minmax(0, 1fr) auto auto',
                        gap: 10,
                        alignItems: 'center',
                        padding: '9px 12px',
                        background: 'var(--paper)',
                        borderRadius: 8,
                        border: '1px solid var(--line)',
                      }}>
                        <div style={{ minWidth: 0 }}>
                          <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-1)' }}>{step.name}</div>
                          <div style={{ marginTop: 3, fontSize: 11, color: 'var(--ink-3)' }}>
                            {step.taskType || 'Geen taaktype'} · {step.durationDays} werkdag{step.durationDays === 1 ? '' : 'en'}
                            {parseNumericInput(step.expectedHours, 0) > 0 && ` · ${formatHoursLabel(step.expectedHours)}`}
                          </div>
                        </div>
                        {step.milestone && (
                          <window.Pill bg="oklch(0.96 0.03 55)" fg="oklch(0.42 0.10 52)" border="1px solid oklch(0.84 0.05 55)">
                            Milestone
                          </window.Pill>
                        )}
                        <div style={{ fontSize: 11.5, color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>
                          {window.fmtDate(step.start)} - {window.fmtDate(step.end)}
                        </div>
                      </div>
                    ))}
                  </div>

                  {templateFinishAfterDeadline && (
                    <div style={{
                      padding: '10px 12px',
                      borderRadius: 8,
                      border: '1px solid oklch(0.82 0.08 25)',
                      background: 'oklch(0.97 0.03 25)',
                      color: 'oklch(0.40 0.16 25)',
                      fontSize: 12.5,
                    }}>
                      Deze template loopt door tot {window.fmtDateLong(templatePreview.end)} en valt daarmee na de ingevulde klantdeadline.
                    </div>
                  )}
                </div>
              )}
            </div>
          </FieldLive>

          <FieldLive label="Einddatum">
            <input
              type="date"
              value={deadline}
              onChange={(event) => setDeadline(event.target.value)}
              style={inputBaseStyle}
            />
          </FieldLive>

          <FieldLive label="Prioriteit">
            <div style={{ display: 'flex', gap: 6 }}>
              {Object.entries(window.PRIORITY).map(([id, item]) => (
                <button key={id} onClick={() => setPrio(id)} style={{
                  flex: 1, padding: '8px 10px', fontSize: 12, fontWeight: 600,
                  background: prio === id ? item.bg : 'var(--paper)',
                  color: prio === id ? item.fg : 'var(--ink-2)',
                  border: prio === id ? `1px solid ${item.fg}` : '1px solid var(--line)',
                  borderRadius: 7, cursor: 'pointer',
                }}>{item.label}</button>
              ))}
            </div>
          </FieldLive>

          <FieldLive label="Sauzen in dit project">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {sauzen.map((saus, index) => (
                <div key={index} style={{ display: 'flex', gap: 6 }}>
                  <input value={saus} onChange={(event) => {
                    const next = [...sauzen];
                    next[index] = event.target.value;
                    setSauzen(next);
                  }} placeholder={`Saus ${index + 1}`} style={inputBaseStyle} />
                  {sauzen.length > 1 && (
                    <button onClick={() => setSauzen(sauzen.filter((_, itemIndex) => itemIndex !== index))} style={{
                      width: 36, border: '1px solid var(--line)', background: 'var(--paper)',
                      borderRadius: 7, color: 'var(--ink-3)', cursor: 'pointer',
                    }}>×</button>
                  )}
                </div>
              ))}
              <button onClick={() => setSauzen([...sauzen, ''])} style={{
                padding: '7px 10px', background: 'transparent', border: '1px dashed var(--line)',
                borderRadius: 7, color: 'var(--ink-3)', fontSize: 12, cursor: 'pointer',
              }}>+ Saus toevoegen</button>
            </div>
          </FieldLive>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveProject} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Project aanmaken'}</button>
        </div>
      </div>
    </>
  );
};

const NewCustomerModalLive = ({ onClose, onCreated }) => {
  const [naam, setNaam] = useStateLive('');
  const [address, setAddress] = useStateLive(() => emptyAddress());
  const [contacts, setContacts] = useStateLive([emptyContactDraft()]);
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const updateContactRow = (index, patch) => {
    const next = [...contacts];
    next[index] = { ...next[index], ...patch };
    setContacts(next);
  };

  const addContactRow = () => setContacts([...contacts, emptyContactDraft()]);

  const removeContactRow = (index) => {
    if (contacts.length === 1) {
      setContacts([emptyContactDraft()]);
      return;
    }
    setContacts(contacts.filter((_, itemIndex) => itemIndex !== index));
  };

  const saveCustomer = async () => {
    if (!naam.trim()) {
      setError('Vul een klantnaam in.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      const result = await window.dashboardApi.createCustomer({
        name: naam.trim(),
        address,
        contacts,
      });
      if (onCreated) onCreated(result.customerId);
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 620, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>NIEUWE KLANT</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>Klant aanmaken</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Klantnaam">
            <input value={naam} onChange={(event) => setNaam(event.target.value)} placeholder="bijv. Sauce Retail BV" style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Adres">
            <div style={{ display: 'grid', gap: 8 }}>
              <input value={address.address_line1} onChange={(event) => setAddress({ ...address, address_line1: event.target.value })} placeholder="Straat en nummer" style={inputBaseStyle} />
              <input value={address.address_line2} onChange={(event) => setAddress({ ...address, address_line2: event.target.value })} placeholder="Extra adresregel" style={inputBaseStyle} />
              <div style={{ display: 'grid', gridTemplateColumns: 'minmax(120px, 160px) 1fr', gap: 8 }}>
                <input value={address.pincode} onChange={(event) => setAddress({ ...address, pincode: event.target.value })} placeholder="Postcode" style={inputBaseStyle} />
                <input value={address.city} onChange={(event) => setAddress({ ...address, city: event.target.value })} placeholder="Plaats" style={inputBaseStyle} />
              </div>
              <input value={address.country} onChange={(event) => setAddress({ ...address, country: event.target.value })} placeholder="Land" style={inputBaseStyle} />
            </div>
          </FieldLive>

          <FieldLive label="Contactpersonen" hint="Naam, mail, telefoon en functie">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {contacts.map((contact, index) => (
                <div key={index} style={{
                  padding: 10, border: '1px solid var(--line)', borderRadius: 10, background: 'var(--surface-1)',
                }}>
                  <div style={{ display: 'grid', gap: 8 }}>
                    <input value={contact.name} onChange={(event) => updateContactRow(index, { name: event.target.value })} placeholder="Naam" style={inputBaseStyle} />
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 8 }}>
                      <input value={contact.email} onChange={(event) => updateContactRow(index, { email: event.target.value })} placeholder="Mail" style={inputBaseStyle} />
                      <input value={contact.phone} onChange={(event) => updateContactRow(index, { phone: event.target.value })} placeholder="Telefoon" style={inputBaseStyle} />
                    </div>
                    <div style={{ display: 'flex', gap: 8 }}>
                      <input value={contact.role} onChange={(event) => updateContactRow(index, { role: event.target.value })} placeholder="Functie" style={inputBaseStyle} />
                      <button onClick={() => removeContactRow(index)} style={{
                        width: 40, borderRadius: 8, border: '1px solid var(--line)', background: 'var(--paper)',
                        color: 'var(--ink-3)', cursor: 'pointer',
                      }}>×</button>
                    </div>
                  </div>
                </div>
              ))}
              <button onClick={addContactRow} style={{
                padding: '8px 10px', borderRadius: 8, border: '1px dashed var(--line)',
                background: 'transparent', color: 'var(--ink-2)', fontSize: 12, cursor: 'pointer',
              }}>+ Contactpersoon toevoegen</button>
            </div>
          </FieldLive>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveCustomer} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Klant opslaan'}</button>
        </div>
      </div>
    </>
  );
};

const EditCustomerModalLive = ({ customer, projectCount, onClose, onSaved }) => {
  const [naam, setNaam] = useStateLive(customer.naam || '');
  const [address, setAddress] = useStateLive(() => withAddressDefaults(customer.address));
  const [contacts, setContacts] = useStateLive(() => (
    (customer.contacts || []).map((contact) => ({
      id: contact.id,
      name: contact.naam || '',
      email: contact.email || '',
      phone: contact.phone || '',
      role: contact.role || '',
    }))
  ));
  const [removedContactIds, setRemovedContactIds] = useStateLive([]);
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const updateContactRow = (index, patch) => {
    setContacts((current) => current.map((contact, itemIndex) => (
      itemIndex === index ? { ...contact, ...patch } : contact
    )));
  };

  const addContactRow = () => {
    setContacts((current) => [...current, emptyContactDraft()]);
  };

  const removeContactRow = (index) => {
    setContacts((current) => {
      const target = current[index];
      if (target?.id) {
        setRemovedContactIds((existing) => [...existing, target.id]);
      }
      return current.filter((_, itemIndex) => itemIndex !== index);
    });
  };

  const saveCustomer = async () => {
    if (!naam.trim()) {
      setError('Vul een klantnaam in.');
      return;
    }

    const filledContacts = contacts
      .map((contact) => ({
        id: contact.id,
        name: String(contact.name || '').trim(),
        email: String(contact.email || '').trim(),
        phone: String(contact.phone || '').trim(),
        role: String(contact.role || '').trim(),
      }))
      .filter((contact) => contact.name || contact.email || contact.phone || contact.role);

    if (filledContacts.some((contact) => !contact.name)) {
      setError('Elke contactpersoon moet een naam hebben.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateCustomer(customer.id, {
        name: naam.trim(),
        address,
      });
      for (const contactId of removedContactIds) {
        await window.dashboardApi.deleteContact(contactId);
      }
      for (const contact of filledContacts) {
        const payload = {
          name: contact.name,
          email: contact.email,
          phone: contact.phone,
          role: contact.role,
        };
        if (contact.id) {
          await window.dashboardApi.updateContact(contact.id, payload);
        } else {
          await window.dashboardApi.addCustomerContact(customer.id, payload);
        }
      }
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  const deleteCustomer = async () => {
    if (projectCount > 0) {
      setError('Verwijder eerst de gekoppelde projecten of kies een klant zonder projecten.');
      return;
    }
    if (!window.confirm(`Klant "${customer.naam}" verwijderen?`)) return;

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.deleteCustomer(customer.id);
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 620, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>KLANT BEWERKEN</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{customer.naam}</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Klantnaam">
            <input value={naam} onChange={(event) => setNaam(event.target.value)} placeholder="Klantnaam" style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Adres">
            <div style={{ display: 'grid', gap: 8 }}>
              <input value={address.address_line1} onChange={(event) => setAddress({ ...address, address_line1: event.target.value })} placeholder="Straat en nummer" style={inputBaseStyle} />
              <input value={address.address_line2} onChange={(event) => setAddress({ ...address, address_line2: event.target.value })} placeholder="Extra adresregel" style={inputBaseStyle} />
              <div style={{ display: 'grid', gridTemplateColumns: 'minmax(120px, 160px) 1fr', gap: 8 }}>
                <input value={address.pincode} onChange={(event) => setAddress({ ...address, pincode: event.target.value })} placeholder="Postcode" style={inputBaseStyle} />
                <input value={address.city} onChange={(event) => setAddress({ ...address, city: event.target.value })} placeholder="Plaats" style={inputBaseStyle} />
              </div>
              <input value={address.country} onChange={(event) => setAddress({ ...address, country: event.target.value })} placeholder="Land" style={inputBaseStyle} />
            </div>
          </FieldLive>

          <FieldLive label="Contactpersonen" hint="Toevoegen, aanpassen en verwijderen binnen dit scherm">
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {contacts.length === 0 && (
                <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
                  Nog geen contactpersonen toegevoegd.
                </div>
              )}
              {contacts.map((contact, index) => (
                <div key={contact.id || `new-${index}`} style={{
                  padding: 10, border: '1px solid var(--line)', borderRadius: 10, background: 'var(--surface-1)',
                }}>
                  <div style={{ display: 'grid', gap: 8 }}>
                    <input value={contact.name} onChange={(event) => updateContactRow(index, { name: event.target.value })} placeholder="Naam" style={inputBaseStyle} />
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 8 }}>
                      <input value={contact.email} onChange={(event) => updateContactRow(index, { email: event.target.value })} placeholder="Mail" style={inputBaseStyle} />
                      <input value={contact.phone} onChange={(event) => updateContactRow(index, { phone: event.target.value })} placeholder="Telefoon" style={inputBaseStyle} />
                    </div>
                    <div style={{ display: 'flex', gap: 8 }}>
                      <input value={contact.role} onChange={(event) => updateContactRow(index, { role: event.target.value })} placeholder="Functie" style={inputBaseStyle} />
                      <button onClick={() => removeContactRow(index)} disabled={busy} style={{
                        minWidth: 110, borderRadius: 8,
                        border: '1px solid oklch(0.82 0.07 25)', background: 'oklch(0.97 0.03 25)',
                        color: 'oklch(0.42 0.16 25)', cursor: busy ? 'not-allowed' : 'pointer',
                        fontSize: 12, fontWeight: 600,
                      }}>
                        Verwijder
                      </button>
                    </div>
                  </div>
                </div>
              ))}
              <button onClick={addContactRow} disabled={busy} style={{
                padding: '8px 10px', borderRadius: 8, border: '1px dashed var(--line)',
                background: 'transparent', color: 'var(--ink-2)', fontSize: 12, cursor: busy ? 'not-allowed' : 'pointer',
              }}>
                + Contactpersoon toevoegen
              </button>
            </div>
          </FieldLive>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'space-between', background: 'var(--surface-1)' }}>
          <button
            onClick={busy ? undefined : deleteCustomer}
            disabled={busy || projectCount > 0}
            title={projectCount > 0 ? 'Kan niet verwijderen zolang er projecten aan hangen.' : 'Verwijder klant'}
            style={{
              padding: '9px 16px', borderRadius: 8,
              border: projectCount > 0 ? '1px solid var(--line)' : '1px solid oklch(0.78 0.09 25)',
              background: projectCount > 0 ? 'var(--paper)' : 'oklch(0.97 0.03 25)',
              color: projectCount > 0 ? 'var(--ink-3)' : 'oklch(0.42 0.16 25)',
              fontSize: 12.5, fontWeight: 600, cursor: busy || projectCount > 0 ? 'not-allowed' : 'pointer',
            }}
          >
            Verwijder klant
          </button>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveCustomer} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Opslaan'}</button>
          </div>
        </div>
      </div>
    </>
  );
};

const ContactModalLive = ({ customer, contact, onClose, onSaved, onDeleted }) => {
  const [draft, setDraft] = useStateLive(() => ({
    name: contact?.naam || '',
    email: contact?.email || '',
    phone: contact?.phone || '',
    role: contact?.role || '',
  }));
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const saveContact = async () => {
    if (!draft.name.trim()) {
      setError('Vul een naam in voor de contactpersoon.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      if (contact) {
        await window.dashboardApi.updateContact(contact.id, draft);
      } else {
        await window.dashboardApi.addCustomerContact(customer.id, draft);
      }
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  const deleteContact = async () => {
    if (!contact) return;
    if (!window.confirm(`Contactpersoon "${contact.naam}" verwijderen?`)) return;

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.deleteContact(contact.id);
      if (onDeleted) onDeleted();
      else if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 560, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>
              {contact ? 'CONTACTPERSOON BEWERKEN' : 'CONTACTPERSOON TOEVOEGEN'}
            </div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{customer.naam}</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Naam">
            <input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} placeholder="Naam" style={inputBaseStyle} />
          </FieldLive>
          <FieldLive label="Mail">
            <input value={draft.email} onChange={(event) => setDraft({ ...draft, email: event.target.value })} placeholder="Mail" style={inputBaseStyle} />
          </FieldLive>
          <FieldLive label="Telefoon">
            <input value={draft.phone} onChange={(event) => setDraft({ ...draft, phone: event.target.value })} placeholder="Telefoon" style={inputBaseStyle} />
          </FieldLive>
          <FieldLive label="Functie">
            <input value={draft.role} onChange={(event) => setDraft({ ...draft, role: event.target.value })} placeholder="Functie" style={inputBaseStyle} />
          </FieldLive>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'space-between', background: 'var(--surface-1)' }}>
          <div>
            {contact ? (
              <button onClick={busy ? undefined : deleteContact} style={{
                padding: '9px 16px', borderRadius: 8,
                border: '1px solid oklch(0.78 0.09 25)', background: 'oklch(0.97 0.03 25)',
                color: 'oklch(0.42 0.16 25)', fontSize: 12.5, fontWeight: 600, cursor: busy ? 'not-allowed' : 'pointer',
                opacity: busy ? 0.7 : 1,
              }}>
                Verwijder contactpersoon
              </button>
            ) : <span />}
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveContact} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Opslaan'}</button>
          </div>
        </div>
      </div>
    </>
  );
};

const UserModalLive = ({ user, onClose, onSaved }) => {
  const defaultRoleProfile = user?.roleProfile || window.USER_ROLE_PROFILES.find((item) => item.id.startsWith('NPD Pilot - '))?.id || window.USER_ROLE_PROFILES[0]?.id || '';
  const [draft, setDraft] = useStateLive(() => ({
    name: user?.naam || '',
    email: user?.email || '',
    phone: user?.phone || '',
    mobile: user?.mobile || '',
    roleProfile: defaultRoleProfile,
  }));
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const saveUser = async () => {
    if (!draft.name.trim()) {
      setError('Vul een naam in voor de gebruiker.');
      return;
    }
    if (!draft.email.trim()) {
      setError('Vul een mailadres in voor de gebruiker.');
      return;
    }
    if (!draft.roleProfile) {
      setError('Selecteer een rol voor de gebruiker.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      const payload = {
        name: draft.name.trim(),
        email: draft.email.trim(),
        phone: draft.phone.trim(),
        mobile: draft.mobile.trim(),
        roleProfile: draft.roleProfile,
      };
      if (user) {
        await window.dashboardApi.updateUser(user.id, payload);
      } else {
        await window.dashboardApi.createUser(payload);
      }
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 620, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>
              {user ? 'GEBRUIKER BEWERKEN' : 'NIEUWE GEBRUIKER'}
            </div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>
              {user ? user.naam : 'Gebruiker aanmaken'}
            </h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Naam">
            <input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} placeholder="Naam" style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Mailadres">
            <input value={draft.email} onChange={(event) => setDraft({ ...draft, email: event.target.value })} placeholder="mail@voorbeeld.nl" style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Rol">
            <select value={draft.roleProfile} onChange={(event) => setDraft({ ...draft, roleProfile: event.target.value })} style={inputBaseStyle}>
              <option value="">Selecteer een rol</option>
              {window.USER_ROLE_PROFILES.map((roleProfile) => (
                <option key={roleProfile.id} value={roleProfile.id}>{roleProfile.label}</option>
              ))}
            </select>
          </FieldLive>

          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
            <FieldLive label="Telefoon">
              <input value={draft.phone} onChange={(event) => setDraft({ ...draft, phone: event.target.value })} placeholder="Telefoon" style={inputBaseStyle} />
            </FieldLive>
            <FieldLive label="Mobiel">
              <input value={draft.mobile} onChange={(event) => setDraft({ ...draft, mobile: event.target.value })} placeholder="Mobiel" style={inputBaseStyle} />
            </FieldLive>
          </div>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveUser} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Opslaan'}</button>
        </div>
      </div>
    </>
  );
};

const FieldLive = ({ label, hint, children }) => (
  <div style={{ marginBottom: 14 }}>
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 5 }}>
      <label style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-2)', letterSpacing: 0.2 }}>{label.toUpperCase()}</label>
      {hint && <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{hint}</span>}
    </div>
    {children}
  </div>
);

const StepControls = ({ step }) => {
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');
  const stepUsers = useMemoLive(() => (
    [...window.GEBRUIKERS].sort((left, right) => (
      String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
    ))
  ), [window.GEBRUIKERS]);

  const updateStatus = async (status) => {
    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateTask(step.taskName, { status });
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  const updateWaitingReason = async (event) => {
    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateTask(step.taskName, { waitingReason: event.target.value });
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  const updateDateField = async (field, value) => {
    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateTask(step.taskName, { [field]: value || null });
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  const updateOwner = async (event) => {
    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateTask(step.taskName, { owner: event.target.value || null });
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{ marginTop: 8 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        {STATUS_BUTTONS.map((item) => (
          <button key={item.id} disabled={busy} onClick={() => updateStatus(item.id)} style={statusButtonStyle(step.status === item.id)}>
            {item.label}
          </button>
        ))}
        <select disabled={busy || step.done} value={step.waitingReason || 'none'} onChange={updateWaitingReason} style={{
          ...inputBaseStyle,
          width: 170,
          padding: '6px 8px',
          fontSize: 11.5,
        }}>
          {window.WAITING_REASONS.map((reason) => (
            <option key={reason} value={reason}>{window.titleCaseWaiting(reason)}</option>
          ))}
        </select>
      </div>
      <div style={{ marginTop: 8 }}>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 10.5, fontWeight: 600, color: 'var(--ink-3)', letterSpacing: 0.3 }}>
          VERANTWOORDELIJKE
          <select
            disabled={busy}
            value={step.owner || ''}
            onChange={updateOwner}
            style={{
              ...inputBaseStyle,
              width: 240,
              maxWidth: '100%',
              padding: '6px 8px',
              fontSize: 11.5,
            }}
          >
            <option value="">Niet toegewezen</option>
            {stepUsers.map((user) => (
              <option key={user.id} value={user.id}>
                {user.naam}{user.roleLabel ? ` — ${user.roleLabel}` : ''}
              </option>
            ))}
          </select>
        </label>
      </div>
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 8 }}>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 10.5, fontWeight: 600, color: 'var(--ink-3)', letterSpacing: 0.3 }}>
          START
          <input
            type="date"
            disabled={busy}
            value={step.start || ''}
            onChange={(event) => updateDateField('startDate', event.target.value)}
            style={compactDateInputStyle}
          />
        </label>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 10.5, fontWeight: 600, color: 'var(--ink-3)', letterSpacing: 0.3 }}>
          DEADLINE
          <input
            type="date"
            disabled={busy}
            value={step.deadline || ''}
            onChange={(event) => updateDateField('deadline', event.target.value)}
            style={compactDateInputStyle}
          />
        </label>
      </div>
      {error && <div style={{ marginTop: 6, fontSize: 11.5, color: 'oklch(0.45 0.18 25)' }}>{error}</div>}
    </div>
  );
};

const StepQuickEditModalLive = ({ variant, step, onClose }) => {
  if (!variant || !step) return null;

  return (
    <>
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.36)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 560, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{
          padding: '18px 22px 14px',
          borderBottom: '1px solid var(--line)',
          display: 'flex',
          alignItems: 'center',
          gap: 12,
        }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>STAP BEWERKEN</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{step.name}</h3>
            <div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <window.Pill bg="var(--surface-1)" fg="var(--ink-2)">{variant.naam}</window.Pill>
              <window.TeamChip team={step.team} compact />
              {step.deadline && <window.DeadlineChip deadline={step.deadline} done={step.done} />}
            </div>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px 20px' }}>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.5 }}>
            Pas hier direct de status, verantwoordelijke en planning aan zonder te hoeven slepen in de Gantt.
          </div>
          <div style={{ marginTop: 14, padding: '14px 16px', borderRadius: 10, border: '1px solid var(--line)', background: 'var(--surface-1)' }}>
            <StepControls step={step} />
          </div>
        </div>
      </div>
    </>
  );
};

const AddVariantStepModalLive = ({ p, variant, onClose, onSaved }) => {
  const stepOptions = useMemoLive(() => (
    [...window.BUCKETS]
      .filter((bucket) => bucket.id !== '—')
      .sort((left, right) => (
        (left.sequence || 0) - (right.sequence || 0)
        || String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
      ))
  ), []);
  const [bucketId, setBucketId] = useStateLive(stepOptions[0]?.id || '');
  const [startDate, setStartDate] = useStateLive('');
  const [deadline, setDeadline] = useStateLive(variant.deadline || p.einde || '');
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const saveStep = async () => {
    const selectedBucket = stepOptions.find((bucket) => bucket.id === bucketId);
    if (!selectedBucket) {
      setError('Selecteer een stap.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.addVariantStep(variant.taskName, {
        taskType: selectedBucket.naam,
        startDate: startDate || null,
        deadline: deadline || null,
      });
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 560, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>STAP TOEVOEGEN</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{variant.naam}</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Stap">
            <select value={bucketId} onChange={(event) => setBucketId(event.target.value)} style={inputBaseStyle}>
              {stepOptions.map((bucket) => (
                <option key={bucket.id} value={bucket.id}>{bucket.naam}</option>
              ))}
            </select>
          </FieldLive>

          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
            <FieldLive label="Startdatum">
              <input type="date" value={startDate} onChange={(event) => setStartDate(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
            <FieldLive label="Deadline">
              <input type="date" value={deadline} onChange={(event) => setDeadline(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
          </div>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveStep} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Stap toevoegen'}</button>
        </div>
      </div>
    </>
  );
};

const VariantDeadlineControlLive = ({ variant }) => {
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const updateDeadline = async (event) => {
    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.updateTask(variant.taskName, { deadline: event.target.value || null });
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <label style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 10.5, fontWeight: 600, color: 'var(--ink-3)', letterSpacing: 0.3 }}>
        SAUS DEADLINE
        <input
          type="date"
          disabled={busy}
          value={variant.deadline || ''}
          onChange={updateDeadline}
          style={{
            ...inputBaseStyle,
            width: 170,
            maxWidth: '100%',
            padding: '6px 8px',
            fontSize: 11.5,
            background: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}
        />
      </label>
      {error && <div style={{ marginTop: 6, fontSize: 11, color: 'oklch(0.45 0.18 25)' }}>{error}</div>}
    </div>
  );
};

const VariantPlanCardLive = ({ p, variant }) => {
  const [addStepOpen, setAddStepOpen] = useStateLive(false);
  const currentBucket = window.bucketById(variant.bucketId);
  const hasSteps = variant.steps.length > 0;

  return (
    <div style={{
      marginBottom: 18, border: '1px solid var(--line)', borderRadius: 10,
      overflow: 'hidden', background: 'var(--paper)',
    }}>
      <div style={{
        padding: '12px 14px', borderBottom: '1px solid var(--line)',
        display: 'flex', alignItems: 'center', gap: 10, background: 'var(--surface-1)',
      }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 14, fontWeight: 650, color: 'var(--ink-1)' }}>{variant.naam}</div>
          <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>
            Huidige bucket: {hasSteps ? currentBucket.naam : 'Nog geen stappen'}
            {variant.deadline && <> · deadline {window.fmtDate(variant.deadline)}</>}
          </div>
        </div>
        {hasSteps ? <window.TeamChip team={currentBucket.team} compact /> : <window.Pill bg="var(--paper)" fg="var(--ink-2)">Nog leeg</window.Pill>}
        <span style={{ fontSize: 11, color: 'var(--ink-3)', fontFamily: 'var(--mono)' }}>
          {Math.round((variant.progress || 0) * 100)}%
        </span>
        <button onClick={() => setAddStepOpen(true)} style={{
          padding: '7px 10px', borderRadius: 8, border: '1px solid var(--line)',
          background: 'var(--paper)', color: 'var(--ink-2)', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
        }}>
          Stap toevoegen
        </button>
      </div>

      {variant.blocker && (
        <div style={{
          margin: '12px 14px 0', padding: '10px 12px',
          borderRadius: 8, border: '1px solid oklch(0.86 0.05 70)',
          background: 'oklch(0.97 0.02 80)', fontSize: 12, color: 'var(--ink-2)',
        }}>
          <strong style={{ color: 'var(--ink-1)' }}>Blokkade:</strong> {variant.blocker}
        </div>
      )}

      <div style={{ padding: '14px' }}>
        <div style={{ marginBottom: 12 }}>
          <VariantDeadlineControlLive variant={variant} />
        </div>
        {hasSteps ? variant.steps.map((step) => {
          const teamColors = window.TEAM_COLORS[step.team] || window.TEAM_COLORS['—'];
          const owner = step.owner ? window.medewerkerById(step.owner) : null;
          const isCurrent = step.id === variant.bucketId && !step.done;
          const overdue = !step.done && window.daysUntil(step.deadline) < 0;

          return (
            <div key={step.taskName} style={{ position: 'relative', paddingLeft: 30, marginBottom: 12 }}>
              <div style={{
                position: 'absolute', left: 2, top: 8,
                width: 16, height: 16, borderRadius: '50%',
                background: step.done ? 'oklch(0.58 0.12 150)' : isCurrent ? teamColors.dot : 'var(--paper)',
                border: step.done ? 'none' : isCurrent ? 'none' : `2px solid var(--line)`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: 'white', boxShadow: isCurrent ? `0 0 0 4px ${teamColors.bg}` : 'none',
              }}>
                {step.done && <window.Icon name="check" size={10} />}
              </div>
              <div style={{
                background: isCurrent ? teamColors.bg : overdue ? 'oklch(0.96 0.04 25)' : 'var(--surface-1)',
                border: overdue ? '1px solid oklch(0.80 0.12 25)' : '1px solid var(--line)',
                borderRadius: 8, padding: '10px 12px',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2 }}>
                  <window.TeamChip team={step.team} compact />
                  <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-1)', flex: 1 }}>{step.name}</span>
                  <window.DeadlineChip deadline={step.deadline} done={step.done} />
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, color: 'var(--ink-2)', marginTop: 4 }}>
                  {owner && <window.Avatar m={owner} size={16} />}
                  <span>{owner ? owner.naam : 'Niet toegewezen'}</span>
                  <span style={{ flex: 1 }} />
                  <span style={{ color: 'var(--ink-3)' }}>{step.waitingReason !== 'none' ? `wacht op ${step.waitingReason}` : 'actief'}</span>
                </div>
                <StepControls step={step} />
              </div>
            </div>
          );
        }) : (
          <div style={{
            padding: '14px 16px', borderRadius: 8, border: '1px dashed var(--line)',
            background: 'var(--surface-1)', fontSize: 12, color: 'var(--ink-2)', lineHeight: 1.5,
          }}>
            Nog geen stappen toegevoegd voor deze saus. Voeg de eerste stap handmatig toe zodra deze variant klaar is om verder te gaan.
          </div>
        )}
      </div>

      {addStepOpen && (
        <AddVariantStepModalLive
          p={p}
          variant={variant}
          onClose={() => setAddStepOpen(false)}
          onSaved={() => setAddStepOpen(false)}
        />
      )}
    </div>
  );
};

const GANTT_VISIBLE_WEEKS = 4;
const GANTT_WORKDAYS_PER_WEEK = 5;
const GANTT_VISIBLE_DAYS = GANTT_VISIBLE_WEEKS * GANTT_WORKDAYS_PER_WEEK;
const GANTT_FORWARD_WEEKS = 26;

const GANTT_VARIANT_TONES = [
  {
    soft: 'oklch(0.96 0.03 45)',
    border: 'oklch(0.82 0.08 45)',
    accent: 'oklch(0.58 0.16 42)',
    fill: 'oklch(0.72 0.11 44)',
    ink: 'oklch(0.44 0.10 40)',
  },
  {
    soft: 'oklch(0.96 0.03 250)',
    border: 'oklch(0.82 0.07 245)',
    accent: 'oklch(0.60 0.14 250)',
    fill: 'oklch(0.74 0.10 248)',
    ink: 'oklch(0.42 0.09 248)',
  },
  {
    soft: 'oklch(0.96 0.03 155)',
    border: 'oklch(0.82 0.07 155)',
    accent: 'oklch(0.56 0.13 155)',
    fill: 'oklch(0.72 0.09 155)',
    ink: 'oklch(0.40 0.08 155)',
  },
  {
    soft: 'oklch(0.96 0.03 320)',
    border: 'oklch(0.82 0.07 318)',
    accent: 'oklch(0.57 0.13 318)',
    fill: 'oklch(0.72 0.09 318)',
    ink: 'oklch(0.41 0.08 318)',
  },
];

const parsePlanningDate = (value) => {
  if (!value) return null;
  const parsed = new Date(`${String(value).slice(0, 10)}T00:00:00`);
  return Number.isNaN(parsed.getTime()) ? null : parsed;
};

const addPlanningDays = (date, amount) => {
  const next = new Date(date);
  next.setDate(next.getDate() + amount);
  return next;
};

const diffPlanningDays = (left, right) => Math.round((right.getTime() - left.getTime()) / 86400000);

const planningDateKey = (date) => {
  if (!date) return '';
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
};

const isPlanningWorkday = (date) => {
  const weekday = date.getDay();
  return weekday >= 1 && weekday <= 5;
};

const shiftToPlanningWorkday = (date, direction = 'next') => {
  if (!date) return null;
  const cursor = new Date(date);
  cursor.setHours(0, 0, 0, 0);
  const step = direction === 'previous' ? -1 : 1;
  while (!isPlanningWorkday(cursor)) {
    cursor.setDate(cursor.getDate() + step);
  }
  return cursor;
};

const startOfPlanningWeek = (date) => {
  const cursor = new Date(date);
  cursor.setHours(0, 0, 0, 0);
  const weekday = cursor.getDay() === 0 ? 7 : cursor.getDay();
  cursor.setDate(cursor.getDate() - weekday + 1);
  return cursor;
};

const endOfPlanningWeek = (date) => addPlanningDays(startOfPlanningWeek(date), 4);

const buildPlanningDates = (startDate, endDate) => {
  const dates = [];
  let cursor = new Date(startDate);
  while (cursor <= endDate) {
    if (isPlanningWorkday(cursor)) {
      dates.push(new Date(cursor));
    }
    cursor = addPlanningDays(cursor, 1);
  }
  return dates;
};

const getVariantTone = (index) => GANTT_VARIANT_TONES[index % GANTT_VARIANT_TONES.length];

const formatPlanningWeekday = (date) => (
  date.toLocaleDateString('nl-NL', { weekday: 'short' }).replace('.', '')
);

const formatPlanningMonth = (date) => (
  date.toLocaleDateString('nl-NL', { month: 'short' }).toUpperCase()
);

const buildProjectPlanningRange = (project, variants) => {
  const dates = [
    parsePlanningDate(project.start),
    parsePlanningDate(project.einde),
    parsePlanningDate(window.today),
    ...variants.flatMap((variant) => [
      parsePlanningDate(variant.deadline),
      ...variant.steps.flatMap((step) => [
        parsePlanningDate(step.start),
        parsePlanningDate(step.deadline),
      ]),
    ]),
  ].filter(Boolean).sort((left, right) => left - right);

  const fallback = shiftToPlanningWorkday(parsePlanningDate(window.today) || new Date(), 'next');
  const earliest = dates[0] ? shiftToPlanningWorkday(dates[0], 'previous') : fallback;
  const latest = dates[dates.length - 1] ? shiftToPlanningWorkday(dates[dates.length - 1], 'next') : fallback;
  const today = fallback;
  const prefersToday = today >= earliest && today <= latest;
  const preferred = prefersToday ? today : earliest;

  let start = startOfPlanningWeek(earliest);
  let end = endOfPlanningWeek(latest);
  const preferredWindowEnd = endOfPlanningWeek(addPlanningDays(startOfPlanningWeek(preferred), ((GANTT_VISIBLE_WEEKS - 1) * 7)));
  const minimumWindowEnd = endOfPlanningWeek(addPlanningDays(start, ((GANTT_VISIBLE_WEEKS - 1) * 7)));
  const forwardWindowEnd = endOfPlanningWeek(addPlanningDays(startOfPlanningWeek(preferred), ((GANTT_FORWARD_WEEKS - 1) * 7)));

  if (end < preferredWindowEnd) end = preferredWindowEnd;
  if (end < minimumWindowEnd) end = minimumWindowEnd;
  if (end < forwardWindowEnd) end = forwardWindowEnd;

  const planningDates = buildPlanningDates(start, end);
  const maxWeekShift = Math.max((planningDates.length / GANTT_WORKDAYS_PER_WEEK) - GANTT_VISIBLE_WEEKS, 0);
  const initialWeekShift = Math.min(
    Math.max(Math.round(diffPlanningDays(start, startOfPlanningWeek(preferred)) / 7), 0),
    maxWeekShift,
  );

  return {
    start,
    end,
    dates: planningDates,
    indexByDate: new Map(planningDates.map((date, index) => [planningDateKey(date), index])),
    maxWeekShift,
    initialWeekShift,
  };
};

const clampPlanningIndex = (value, maxIndex) => Math.max(0, Math.min(value, maxIndex));

const planningIndexFromClientX = (clientX, timelineLeft, dayWidth, maxIndex) => (
  clampPlanningIndex(Math.floor((clientX - timelineLeft) / dayWidth), maxIndex)
);

const findPlanningDateIndex = (planningRange, target, direction = 'next') => {
  if (!planningRange.dates.length) return 0;
  const normalized = shiftToPlanningWorkday(target, direction);
  if (!normalized) return 0;
  const key = planningDateKey(normalized);
  if (planningRange.indexByDate.has(key)) {
    return planningRange.indexByDate.get(key);
  }
  if (normalized < planningRange.dates[0]) return 0;
  return planningRange.dates.length - 1;
};

const ProjectDetailStatLive = ({ label, value, tone = 'neutral' }) => {
  const color = tone === 'success'
    ? 'oklch(0.47 0.11 155)'
    : tone === 'alert'
      ? 'oklch(0.48 0.16 25)'
      : 'var(--ink-1)';

  return (
    <div style={{ minWidth: 64, textAlign: 'center' }}>
      <div style={{ fontSize: 30, fontWeight: 650, letterSpacing: -0.8, color, lineHeight: 1 }}>{value}</div>
      <div style={{ marginTop: 4, fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 1, textTransform: 'uppercase' }}>
        {label}
      </div>
    </div>
  );
};

const ProjectAddStepModalLive = ({ p, initialVariantId, onClose, onSaved }) => {
  const stepOptions = useMemoLive(() => (
    [...window.BUCKETS]
      .filter((bucket) => bucket.id !== '—')
      .sort((left, right) => (
        (left.sequence || 0) - (right.sequence || 0)
        || String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
      ))
  ), []);
  const [variantId, setVariantId] = useStateLive(initialVariantId || p.variants[0]?.id || '');
  const [bucketId, setBucketId] = useStateLive(stepOptions[0]?.id || '');
  const [startDate, setStartDate] = useStateLive('');
  const [deadline, setDeadline] = useStateLive('');
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const selectedVariant = p.variants.find((variant) => variant.id === variantId) || p.variants[0] || null;

  useEffectLive(() => {
    setDeadline(selectedVariant?.deadline || p.einde || '');
  }, [selectedVariant?.id, selectedVariant?.deadline, p.einde]);

  const saveStep = async () => {
    const selectedBucket = stepOptions.find((bucket) => bucket.id === bucketId);
    if (!selectedVariant) {
      setError('Selecteer eerst een saus.');
      return;
    }
    if (!selectedBucket) {
      setError('Selecteer een stap.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.addVariantStep(selectedVariant.taskName, {
        taskType: selectedBucket.naam,
        startDate: startDate || null,
        deadline: deadline || null,
      });
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 580, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>NIEUWE STAP</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{p.naam}</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Saus">
            <select value={variantId} onChange={(event) => setVariantId(event.target.value)} style={inputBaseStyle}>
              {p.variants.map((variant) => (
                <option key={variant.id} value={variant.id}>{variant.naam}</option>
              ))}
            </select>
          </FieldLive>

          <FieldLive label="Stap">
            <select value={bucketId} onChange={(event) => setBucketId(event.target.value)} style={inputBaseStyle}>
              {stepOptions.map((bucket) => (
                <option key={bucket.id} value={bucket.id}>{bucket.naam}</option>
              ))}
            </select>
          </FieldLive>

          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
            <FieldLive label="Startdatum">
              <input type="date" value={startDate} onChange={(event) => setStartDate(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
            <FieldLive label="Deadline">
              <input type="date" value={deadline} onChange={(event) => setDeadline(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
          </div>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveStep} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Stap toevoegen'}</button>
        </div>
      </div>
    </>
  );
};

const DetailPlanTab = ({ p }) => {
  const [weekShift, setWeekShift] = useStateLive(0);
  const [activeVariantId, setActiveVariantId] = useStateLive('all');
  const [detailsOpen, setDetailsOpen] = useStateLive(true);
  const [dragState, setDragState] = useStateLive(null);
  const [selectedStepRef, setSelectedStepRef] = useStateLive(null);
  const [interactionBusy, setInteractionBusy] = useStateLive(false);
  const [interactionError, setInteractionError] = useStateLive('');
  const ganttViewportRef = useRefLive(null);
  const dragStateRef = useRefLive(null);
  const suppressStepModalUntilRef = useRefLive(0);
  const [ganttViewportWidth, setGanttViewportWidth] = useStateLive(0);

  const syncDragState = (nextOrUpdater) => {
    const current = dragStateRef.current;
    const next = typeof nextOrUpdater === 'function' ? nextOrUpdater(current) : nextOrUpdater;
    dragStateRef.current = next;
    setDragState(next);
  };

  useEffectLive(() => {
    setWeekShift(0);
    setActiveVariantId('all');
    setDetailsOpen(true);
    setSelectedStepRef(null);
    syncDragState(null);
    setInteractionBusy(false);
    setInteractionError('');
  }, [p.id]);

  useEffectLive(() => {
    const element = ganttViewportRef.current;
    if (!element) return undefined;

    const updateWidth = () => {
      setGanttViewportWidth(element.getBoundingClientRect().width);
    };

    updateWidth();

    if (typeof ResizeObserver !== 'undefined') {
      const observer = new ResizeObserver(() => updateWidth());
      observer.observe(element);
      return () => observer.disconnect();
    }

    window.addEventListener('resize', updateWidth);
    return () => window.removeEventListener('resize', updateWidth);
  }, []);

  const variantToneMap = useMemoLive(() => (
    Object.fromEntries(p.variants.map((variant, index) => [variant.id, getVariantTone(index)]))
  ), [p.variants]);

  const visibleVariants = useMemoLive(() => (
    activeVariantId === 'all'
      ? p.variants
      : p.variants.filter((variant) => variant.id === activeVariantId)
  ), [activeVariantId, p.variants]);
  const orderedPlanningBuckets = useMemoLive(() => (
    [...window.BUCKETS]
      .filter((bucket) => bucket.id !== '—')
      .sort((left, right) => (
        (left.sequence || 0) - (right.sequence || 0)
        || String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
      ))
  ), [window.BUCKETS]);

  const planningContext = useMemoLive(() => buildProjectPlanningRange(p, p.variants), [p]);
  useEffectLive(() => {
    setWeekShift(planningContext.initialWeekShift);
  }, [p.id, planningContext.initialWeekShift]);
  useEffectLive(() => {
    setWeekShift((current) => Math.max(0, Math.min(current, planningContext.maxWeekShift)));
  }, [planningContext.maxWeekShift]);

  const visibleStartIndex = Math.min(weekShift * GANTT_WORKDAYS_PER_WEEK, Math.max(planningContext.dates.length - GANTT_VISIBLE_DAYS, 0));
  const planningRange = useMemoLive(() => {
    const dates = planningContext.dates.slice(visibleStartIndex, visibleStartIndex + GANTT_VISIBLE_DAYS);
    return {
      startIndex: visibleStartIndex,
      endIndex: visibleStartIndex + Math.max(dates.length - 1, 0),
      dates,
    };
  }, [planningContext, visibleStartIndex]);

  const labelWidth = 248;
  const availableTimelineWidth = Math.max((ganttViewportWidth || 0) - labelWidth - 2, 560);
  const dayWidth = availableTimelineWidth / Math.max(planningRange.dates.length || GANTT_VISIBLE_DAYS, 1);
  const timelineWidth = availableTimelineWidth;
  const compactMonthHeader = dayWidth < 30;
  const maxPlanningIndex = Math.max(planningRange.dates.length - 1, 0);
  const canShiftBack = weekShift > 0;
  const canShiftForward = weekShift < planningContext.maxWeekShift;
  const todayPlanningKey = planningDateKey(shiftToPlanningWorkday(parsePlanningDate(window.today), 'next'));
  const planningWindowLabel = planningRange.dates.length
    ? `${planningRange.dates[0].toLocaleDateString('nl-NL', { day: 'numeric', month: 'short' })} - ${planningRange.dates[planningRange.dates.length - 1].toLocaleDateString('nl-NL', { day: 'numeric', month: 'short' })}`
    : '';
  const selectedVariant = selectedStepRef
    ? p.variants.find((variant) => variant.id === selectedStepRef.variantId) || null
    : null;
  const selectedStep = selectedStepRef && selectedVariant
    ? selectedVariant.steps.find((step) => step.taskName === selectedStepRef.taskName) || null
    : null;
  const renderTimelineBackground = (height) => (
    <>
      {planningRange.dates.map((date, index) => (
        <div
          key={`${date.toISOString()}-${height}`}
          style={{
            position: 'absolute',
            left: index * dayWidth,
            top: 0,
            width: dayWidth,
            height,
            borderRight: (index + 1) % GANTT_WORKDAYS_PER_WEEK === 0 ? '1px solid oklch(0.83 0.01 80)' : '1px solid var(--line)',
            background: planningDateKey(date) === todayPlanningKey ? 'oklch(0.96 0.003 260)' : 'transparent',
          }}
        />
      ))}
    </>
  );

  const beginInteraction = (event, config) => {
    if (interactionBusy || event.button !== 0) return;
    event.preventDefault();
    event.stopPropagation();
    setInteractionError('');
    syncDragState({
      ...config,
      initialPointerX: event.clientX,
      previewStartIndex: config.baseStartIndex ?? config.anchorIndex ?? 0,
      previewEndIndex: config.baseEndIndex ?? config.anchorIndex ?? 0,
    });
  };

  useEffectLive(() => {
    if (!dragState) return undefined;

    const previousUserSelect = document.body.style.userSelect;
    const previousCursor = document.body.style.cursor;
    document.body.style.userSelect = 'none';
    document.body.style.cursor =
      dragState.mode === 'create'
        ? 'crosshair'
        : dragState.mode === 'move'
          ? 'grabbing'
          : 'ew-resize';

    const handleMouseMove = (event) => {
      const current = dragStateRef.current;
      if (!current) return;

      if (current.mode === 'create') {
        const hoveredIndex = planningIndexFromClientX(
          event.clientX,
          current.timelineLeft,
          current.dayWidth,
          current.maxIndex,
        );
        syncDragState((state) => {
          if (!state) return state;
          return {
            ...state,
            previewStartIndex: Math.min(state.anchorIndex, hoveredIndex),
            previewEndIndex: Math.max(state.anchorIndex, hoveredIndex),
          };
        });
        return;
      }

      const deltaDays = Math.round((event.clientX - current.initialPointerX) / current.dayWidth);
      if (current.mode === 'move') {
        const span = current.baseEndIndex - current.baseStartIndex;
        const nextStartIndex = clampPlanningIndex(current.baseStartIndex + deltaDays, current.maxIndex - span);
        syncDragState((state) => {
          if (!state) return state;
          return {
            ...state,
            previewStartIndex: nextStartIndex,
            previewEndIndex: nextStartIndex + span,
          };
        });
        return;
      }

      if (current.mode === 'resize-start') {
        const nextStartIndex = clampPlanningIndex(current.baseStartIndex + deltaDays, current.baseEndIndex);
        syncDragState((state) => {
          if (!state) return state;
          return {
            ...state,
            previewStartIndex: Math.min(nextStartIndex, state.previewEndIndex),
            previewEndIndex: state.previewEndIndex,
          };
        });
        return;
      }

      if (current.mode === 'resize-end') {
        const nextEndIndex = clampPlanningIndex(current.baseEndIndex + deltaDays, current.maxIndex);
        syncDragState((state) => {
          if (!state) return state;
          return {
            ...state,
            previewStartIndex: state.previewStartIndex,
            previewEndIndex: Math.max(state.previewStartIndex, nextEndIndex),
          };
        });
      }
    };

    const handleMouseUp = () => {
      const current = dragStateRef.current;
      syncDragState(null);
      document.body.style.userSelect = previousUserSelect;
      document.body.style.cursor = previousCursor;
      if (!current) return;

      const startDateValue = planningRange.dates[current.previewStartIndex];
      const deadlineValue = planningRange.dates[current.previewEndIndex];
      const unchanged = current.mode !== 'create'
        && current.previewStartIndex === current.baseStartIndex
        && current.previewEndIndex === current.baseEndIndex;
      if (unchanged || !startDateValue || !deadlineValue) return;
      suppressStepModalUntilRef.current = Date.now() + 250;

      const startDate = window.iso(startDateValue);
      const deadline = window.iso(deadlineValue);

      setInteractionBusy(true);
      setInteractionError('');
      (async () => {
        try {
          if (current.mode === 'create') {
            await window.dashboardApi.addVariantStep(current.variantTaskName, {
              taskType: current.bucketName,
              startDate,
              deadline,
            });
          } else {
            await window.dashboardApi.updateTask(current.taskName, {
              startDate,
              deadline,
            });
          }
        } catch (error) {
          setInteractionError(error.message || 'Plannen in de Gantt is mislukt.');
        } finally {
          setInteractionBusy(false);
        }
      })();
    };

    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);
    return () => {
      document.body.style.userSelect = previousUserSelect;
      document.body.style.cursor = previousCursor;
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, [Boolean(dragState), planningRange.dates]);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
          <button
            onClick={() => setActiveVariantId('all')}
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '7px 12px', borderRadius: 8,
              border: activeVariantId === 'all' ? '1px solid var(--ink-1)' : '1px solid var(--line)',
              background: activeVariantId === 'all' ? 'var(--ink-1)' : 'var(--paper)',
              color: activeVariantId === 'all' ? 'var(--paper)' : 'var(--ink-2)',
              fontSize: 12, fontWeight: 600, cursor: 'pointer',
            }}
          >
            Alle sauzen
          </button>
          {p.variants.map((variant) => {
            const tone = variantToneMap[variant.id];
            const selected = activeVariantId === variant.id;
            return (
              <button
                key={variant.id}
                onClick={() => setActiveVariantId(variant.id)}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                  padding: '7px 10px', borderRadius: 8,
                  border: `1px solid ${selected ? tone.border : 'var(--line)'}`,
                  background: selected ? tone.soft : 'var(--paper)',
                  color: selected ? tone.ink : 'var(--ink-2)',
                  fontSize: 12, fontWeight: 600, cursor: 'pointer',
                }}
              >
                <span style={{ width: 7, height: 7, borderRadius: '50%', background: tone.accent }} />
                {variant.naam}
              </button>
            );
          })}
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginLeft: 'auto' }}>
          <button
            onClick={() => setWeekShift((current) => Math.max(0, current - 1))}
            disabled={!canShiftBack}
            style={{
              padding: '8px 12px',
              borderRadius: 8,
              border: '1px solid var(--line)',
              background: 'var(--paper)',
              color: 'var(--ink-2)',
              fontSize: 12,
              fontWeight: 600,
              cursor: canShiftBack ? 'pointer' : 'default',
              opacity: canShiftBack ? 1 : 0.45,
            }}
          >
            Vorige week
          </button>

          <div style={{ minWidth: 164, textAlign: 'center' }}>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.8, textTransform: 'uppercase' }}>
              4 weken
            </div>
            <div style={{ marginTop: 3, fontSize: 13, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.1 }}>
              {planningWindowLabel}
            </div>
          </div>

          <button
            onClick={() => setWeekShift((current) => Math.min(planningContext.maxWeekShift, current + 1))}
            disabled={!canShiftForward}
            style={{
              padding: '8px 12px',
              borderRadius: 8,
              border: '1px solid var(--line)',
              background: 'var(--paper)',
              color: 'var(--ink-2)',
              fontSize: 12,
              fontWeight: 600,
              cursor: canShiftForward ? 'pointer' : 'default',
              opacity: canShiftForward ? 1 : 0.45,
            }}
          >
            Volgende week
          </button>
        </div>
      </div>

      {interactionError && (
        <div style={{
          padding: '10px 12px',
          borderRadius: 10,
          border: '1px solid oklch(0.80 0.12 25)',
          background: 'oklch(0.96 0.04 25)',
          color: 'oklch(0.38 0.18 25)',
          fontSize: 12.5,
        }}
        >
          {interactionError}
        </div>
      )}

      <div
        ref={ganttViewportRef}
        style={{
        border: '1px solid var(--line)',
        borderRadius: 14,
        background: 'var(--paper)',
        overflowX: 'hidden',
        overflowY: 'auto',
        boxShadow: '0 10px 30px rgba(0,0,0,0.03)',
      }}
      >
        <div style={{ minWidth: labelWidth + timelineWidth }}>
          <div style={{ display: 'flex', position: 'sticky', top: 0, zIndex: 10 }}>
            <div style={{
              width: labelWidth,
              flexShrink: 0,
              position: 'sticky',
              left: 0,
              zIndex: 11,
              background: 'var(--paper)',
              borderRight: '1px solid var(--line)',
              borderBottom: '1px solid var(--line)',
              padding: '14px 16px',
            }}>
              <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.8, textTransform: 'uppercase' }}>
                Stap / saus
              </div>
            </div>

            <div style={{
              position: 'relative',
              width: timelineWidth,
              height: 58,
              background: 'var(--paper)',
              borderBottom: '1px solid var(--line)',
            }}>
              {renderTimelineBackground(58)}
              {planningRange.dates.map((date, index) => {
                return (
                  <div key={date.toISOString()} style={{
                    position: 'absolute',
                    left: index * dayWidth,
                    top: 0,
                    width: dayWidth,
                    height: 58,
                    paddingTop: 7,
                    textAlign: 'center',
                    zIndex: 1,
                  }}>
                    <div style={{
                      fontSize: compactMonthHeader ? 8 : 9,
                      color: 'var(--ink-3)',
                      fontWeight: 700,
                      letterSpacing: compactMonthHeader ? 0.35 : 0.6,
                    }}
                    >
                      {formatPlanningMonth(date)}
                    </div>
                    <div style={{
                      marginTop: compactMonthHeader ? 2 : 3,
                      fontSize: compactMonthHeader ? 15 : 17,
                      fontWeight: 700,
                      letterSpacing: compactMonthHeader ? -0.2 : -0.35,
                      color: 'var(--ink-1)',
                    }}
                    >
                      {date.getDate()}
                    </div>
                    <div style={{
                      marginTop: 2,
                      fontSize: compactMonthHeader ? 8 : 9.5,
                      color: 'var(--ink-3)',
                      fontWeight: 600,
                    }}
                    >
                      {formatPlanningWeekday(date)}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>

          {visibleVariants.map((variant) => {
            const tone = variantToneMap[variant.id];
            return (
              <div key={variant.id}>
                <div style={{ display: 'flex', borderBottom: '1px solid var(--line)' }}>
                  <div style={{
                    width: labelWidth,
                    flexShrink: 0,
                    position: 'sticky',
                    left: 0,
                    zIndex: 8,
                    background: 'var(--surface-1)',
                    borderRight: '1px solid var(--line)',
                    padding: '7px 14px',
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <span style={{ width: 6, height: 6, borderRadius: '50%', background: tone.accent, flexShrink: 0 }} />
                      <span style={{ fontSize: 12.5, fontWeight: 700, letterSpacing: 0.22, color: tone.ink, textTransform: 'uppercase' }}>{variant.naam}</span>
                    </div>
                  </div>

                  <div style={{
                    position: 'relative',
                    width: timelineWidth,
                    height: 32,
                    background: 'var(--surface-1)',
                  }}>
                    {renderTimelineBackground(32)}
                  </div>
                </div>

                {orderedPlanningBuckets.map((bucketMeta) => {
                  const step = variant.steps.find((item) => item.id === bucketMeta.id) || null;
                  const rowInteraction = dragState && dragState.variantId === variant.id && dragState.bucketId === bucketMeta.id
                    ? dragState
                    : null;
                  const rawStartDate = step ? parsePlanningDate(step.start || step.deadline) : null;
                  const rawEndDate = step ? parsePlanningDate(step.deadline || step.start) : null;
                  const fullStepStartIndex = step
                    ? findPlanningDateIndex(planningContext, rawStartDate || rawEndDate, 'next')
                    : 0;
                  const fullStepEndIndexBase = step
                    ? findPlanningDateIndex(planningContext, rawEndDate || rawStartDate, 'previous')
                    : 0;
                  const fullStepEndIndex = Math.max(fullStepStartIndex, fullStepEndIndexBase);
                  const stepVisible = Boolean(
                    step
                    && fullStepEndIndex >= planningRange.startIndex
                    && fullStepStartIndex <= planningRange.endIndex,
                  );
                  const stepFullyVisible = Boolean(
                    stepVisible
                    && fullStepStartIndex >= planningRange.startIndex
                    && fullStepEndIndex <= planningRange.endIndex,
                  );
                  const stepStartIndex = stepVisible
                    ? Math.max(fullStepStartIndex - planningRange.startIndex, 0)
                    : 0;
                  const stepEndIndex = stepVisible
                    ? Math.min(fullStepEndIndex - planningRange.startIndex, planningRange.dates.length - 1)
                    : 0;
                  const overdue = Boolean(step && !step.done && step.deadline && window.daysUntil(step.deadline) < 0);
                  const statusDot = !step
                    ? 'var(--line)'
                    : step.done
                      ? 'oklch(0.58 0.12 150)'
                      : overdue
                        ? 'oklch(0.58 0.14 25)'
                        : tone.accent;
                  const barBackground = step?.done
                    ? 'oklch(0.58 0.12 150)'
                    : overdue
                      ? 'oklch(0.66 0.15 28)'
                      : tone.accent;
                  const visibleStartIndex = rowInteraction ? rowInteraction.previewStartIndex : stepStartIndex;
                  const visibleEndIndex = rowInteraction ? rowInteraction.previewEndIndex : stepEndIndex;
                  const visibleWidth = ((step && stepVisible) || rowInteraction)
                    ? Math.max(((visibleEndIndex - visibleStartIndex) + 1) * dayWidth, 18)
                    : 0;
                  const canCreate = !step && !interactionBusy;
                  const showResizeHandles = stepFullyVisible && !interactionBusy && visibleWidth >= 28;
                  const handleWidth = visibleWidth >= 42 ? 8 : 6;
                  const moveInset = showResizeHandles ? handleWidth : 0;

                  return (
                    <div key={`${variant.id}-${bucketMeta.id}`} style={{ display: 'flex', borderBottom: '1px solid var(--line)' }}>
                      <div style={{
                        width: labelWidth,
                        flexShrink: 0,
                        position: 'sticky',
                        left: 0,
                        zIndex: 7,
                        background: 'var(--paper)',
                        borderRight: '1px solid var(--line)',
                        padding: '11px 16px',
                      }}>
                        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
                          <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink-1)' }}>{bucketMeta.naam}</span>
                          <span style={{
                            width: 7,
                            height: 7,
                            borderRadius: '50%',
                            background: statusDot,
                            flexShrink: 0,
                          }} />
                        </div>
                      </div>

                      <div style={{
                        position: 'relative',
                        width: timelineWidth,
                        height: 44,
                        background: 'var(--paper)',
                        cursor: canCreate ? 'crosshair' : 'default',
                      }}>
                        {renderTimelineBackground(44)}
                        <div
                          onMouseDown={canCreate ? (event) => {
                            const rect = event.currentTarget.getBoundingClientRect();
                            const clickedIndex = planningIndexFromClientX(event.clientX, rect.left, dayWidth, maxPlanningIndex);
                            beginInteraction(event, {
                              mode: 'create',
                              variantId: variant.id,
                              variantTaskName: variant.taskName,
                              bucketId: bucketMeta.id,
                              bucketName: bucketMeta.naam,
                              anchorIndex: clickedIndex,
                              timelineLeft: rect.left,
                              dayWidth,
                              maxIndex: maxPlanningIndex,
                            });
                          } : undefined}
                          style={{ position: 'absolute', inset: 0, zIndex: 1 }}
                        />
                        {((step && stepVisible) || rowInteraction) && (
                          <div style={{
                            position: 'absolute',
                            left: (visibleStartIndex * dayWidth) + 4,
                            top: 13,
                            width: Math.max(visibleWidth - 8, 14),
                            height: 18,
                            borderRadius: 6,
                            background: barBackground,
                            zIndex: 3,
                            opacity: rowInteraction ? 0.88 : 1,
                            cursor: 'default',
                          }}
                          onClick={step ? () => {
                            if (Date.now() < suppressStepModalUntilRef.current) return;
                            setSelectedStepRef({ variantId: variant.id, taskName: step.taskName });
                          } : undefined}
                          >
                            {stepFullyVisible && !interactionBusy && (
                              <div
                                onMouseDown={(event) => beginInteraction(event, {
                                  mode: 'move',
                                  variantId: variant.id,
                                  bucketId: bucketMeta.id,
                                  taskName: step.taskName,
                                  baseStartIndex: stepStartIndex,
                                  baseEndIndex: stepEndIndex,
                                  timelineLeft: event.currentTarget.parentElement.parentElement.getBoundingClientRect().left,
                                  dayWidth,
                                  maxIndex: maxPlanningIndex,
                                })}
                                style={{
                                  position: 'absolute',
                                  left: moveInset,
                                  right: moveInset,
                                  top: 0,
                                  bottom: 0,
                                  minWidth: 10,
                                  cursor: 'grab',
                                }}
                              />
                            )}
                            {showResizeHandles && (
                              <>
                                <div
                                  onMouseDown={(event) => beginInteraction(event, {
                                    mode: 'resize-start',
                                    variantId: variant.id,
                                    bucketId: bucketMeta.id,
                                    taskName: step.taskName,
                                    baseStartIndex: stepStartIndex,
                                    baseEndIndex: stepEndIndex,
                                    timelineLeft: event.currentTarget.parentElement.parentElement.getBoundingClientRect().left,
                                    dayWidth,
                                    maxIndex: maxPlanningIndex,
                                  })}
                                  style={{
                                    position: 'absolute',
                                    left: 0,
                                    top: 0,
                                    bottom: 0,
                                    width: handleWidth,
                                    cursor: 'ew-resize',
                                    background: 'transparent',
                                    borderTopLeftRadius: 6,
                                    borderBottomLeftRadius: 6,
                                  }}
                                  onClick={(event) => event.stopPropagation()}
                                />
                                <div
                                  onMouseDown={(event) => beginInteraction(event, {
                                    mode: 'resize-end',
                                    variantId: variant.id,
                                    bucketId: bucketMeta.id,
                                    taskName: step.taskName,
                                    baseStartIndex: stepStartIndex,
                                    baseEndIndex: stepEndIndex,
                                    timelineLeft: event.currentTarget.parentElement.parentElement.getBoundingClientRect().left,
                                    dayWidth,
                                    maxIndex: maxPlanningIndex,
                                  })}
                                  style={{
                                    position: 'absolute',
                                    right: 0,
                                    top: 0,
                                    bottom: 0,
                                    width: handleWidth,
                                    cursor: 'ew-resize',
                                    background: 'transparent',
                                    borderTopRightRadius: 6,
                                    borderBottomRightRadius: 6,
                                  }}
                                  onClick={(event) => event.stopPropagation()}
                                />
                              </>
                            )}
                          </div>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            );
          })}
        </div>
      </div>

      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap',
      }}>
        <div>
          <div style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.2, color: 'var(--ink-1)' }}>Stapdetails en bewerken</div>
          <div style={{ marginTop: 3, fontSize: 12, color: 'var(--ink-3)' }}>
            Pas onderliggende stappen, verantwoordelijken en deadlines aan zonder de planning uit het oog te verliezen.
          </div>
        </div>
        <button onClick={() => setDetailsOpen((open) => !open)} style={{
          padding: '8px 12px', borderRadius: 8, border: '1px solid var(--line)',
          background: 'var(--paper)', color: 'var(--ink-2)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
        }}>
          {detailsOpen ? 'Verberg stapdetails' : 'Toon stapdetails'}
        </button>
      </div>

      {detailsOpen && (
        <div>
          {visibleVariants.map((variant) => (
            <VariantPlanCardLive key={variant.id} p={p} variant={variant} />
          ))}
        </div>
      )}

      {selectedVariant && selectedStep && (
        <StepQuickEditModalLive
          variant={selectedVariant}
          step={selectedStep}
          onClose={() => setSelectedStepRef(null)}
        />
      )}
    </div>
  );
};

const DetailSauzenTab = ({ p }) => (
  <div>
    <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 12 }}>
      Dit project bestaat uit {p.sauzen.length} saus{p.sauzen.length > 1 ? 'en' : ''}. Elke saus heeft een eigen Group Task in ERPNext; stappen voeg je daarna per saus toe.
    </div>
    {p.variants.map((variant) => {
      const bucket = window.bucketById(variant.bucketId);
      return (
        <div key={variant.id} style={{
          padding: 12, border: '1px solid var(--line)', borderRadius: 8,
          marginBottom: 8, background: 'var(--paper)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-1)', flex: 1 }}>{variant.naam}</span>
            <window.TeamChip team={bucket.team} compact />
            <span style={{ fontSize: 11, color: 'var(--ink-2)' }}>{bucket.naam}</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div style={{ flex: 1, height: 4, background: 'var(--surface-2)', borderRadius: 2, overflow: 'hidden' }}>
              <div style={{ height: '100%', width: `${Math.max((variant.progress || 0) * 100, 4)}%`, background: window.klantById(p.klant).kleur }} />
            </div>
            <span style={{ fontSize: 10.5, color: 'var(--ink-3)', fontWeight: 600, fontFamily: 'var(--mono)' }}>
              {Math.round((variant.progress || 0) * 100)}%
            </span>
          </div>
          <div style={{ marginTop: 10, marginBottom: 6 }}>
            <VariantDeadlineControlLive variant={variant} />
          </div>
          {variant.blocker && (
            <div style={{ marginTop: 8, fontSize: 11.5, color: 'var(--ink-2)' }}>
              <strong style={{ color: 'var(--ink-1)' }}>Blokkade:</strong> {variant.blocker}
            </div>
          )}
        </div>
      );
    })}
  </div>
);

const DetailCommentsTab = ({ p }) => {
  const [content, setContent] = useStateLive('');
  const [target, setTarget] = useStateLive(`Project:${p.id}`);
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const options = [
    { value: `Project:${p.id}`, label: 'Project' },
    ...p.variants.map((variant) => ({ value: `Task:${variant.taskName}`, label: variant.naam })),
  ];

  const submit = async () => {
    if (!content.trim()) return;
    setBusy(true);
    setError('');
    try {
      const [referenceDoctype, referenceName] = target.split(':');
      await window.dashboardApi.addComment(referenceDoctype, referenceName, content.trim());
      setContent('');
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <div style={{ marginBottom: 14 }}>
        {p.comments.length === 0 && (
          <div style={{ padding: '12px 0', color: 'var(--ink-3)', fontSize: 12 }}>Nog geen opmerkingen geplaatst.</div>
        )}
        {p.comments.map((comment) => {
          const person = window.medewerkerById(comment.who) || { naam: comment.authorName || comment.who, initials: '??', kleur: 'oklch(0.62 0.14 30)' };
          return (
            <div key={comment.id} style={{ display: 'flex', gap: 10, marginBottom: 14 }}>
              <window.Avatar m={person} size={28} />
              <div style={{ flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3, flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-1)' }}>{person.naam}</span>
                  <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{window.fmtDateTime(comment.time)}</span>
                  <window.Pill bg="var(--surface-1)" fg="var(--ink-2)">{comment.referenceLabel}</window.Pill>
                </div>
                <div style={{ fontSize: 12.5, color: 'var(--ink-1)', lineHeight: 1.5 }}>{comment.text}</div>
              </div>
            </div>
          );
        })}
      </div>

      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <select value={target} onChange={(event) => setTarget(event.target.value)} style={{ ...inputBaseStyle, width: 180 }}>
          {options.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
        </select>
      </div>
      <div style={{
        border: '1px solid var(--line)', borderRadius: 8, padding: 10,
        display: 'flex', gap: 8, alignItems: 'center',
      }}>
        <input value={content} onChange={(event) => setContent(event.target.value)} placeholder="Opmerking toevoegen…" style={{
          flex: 1, border: 'none', outline: 'none', background: 'transparent',
          fontSize: 12.5, color: 'var(--ink-1)', fontFamily: 'inherit',
        }} />
        <button onClick={submit} disabled={busy} className="primary-btn" style={{
          padding: '5px 12px', background: 'var(--ink-1)', color: 'var(--paper)',
          border: 'none', borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: 'pointer',
          opacity: busy ? 0.7 : 1,
        }}>Plaats</button>
      </div>
      {error && <div style={{ marginTop: 8, fontSize: 12, color: 'oklch(0.45 0.18 25)' }}>{error}</div>}
    </div>
  );
};

const DetailFilesTab = ({ p }) => (
  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
    {p.files.length === 0 && (
      <div style={{ padding: '12px 0', color: 'var(--ink-3)', fontSize: 12 }}>Nog geen bijlagen op project- of taakniveau.</div>
    )}
    {p.files.map((file) => {
      const person = window.medewerkerById(file.wie) || { naam: file.wie || 'Onbekend', initials: '??', kleur: 'oklch(0.62 0.14 30)' };
      return (
        <a key={file.id} href={file.downloadUrl} target="_blank" rel="noreferrer" style={{
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '9px 12px', border: '1px solid var(--line)', borderRadius: 8,
          textDecoration: 'none',
        }}>
          <div style={{
            width: 32, height: 32, borderRadius: 6, background: 'var(--surface-1)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 9, fontWeight: 700, color: 'var(--ink-2)', fontFamily: 'var(--mono)',
          }}>{String(file.type || 'file').toUpperCase().slice(0, 4)}</div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-1)' }}>{file.naam}</div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 2, fontSize: 11, color: 'var(--ink-3)', flexWrap: 'wrap' }}>
              <span>{file.grootte}</span>
              <span>·</span>
              <span>{window.fmtDateTime(file.wanneer)}</span>
              <span>·</span>
              <span>{person.naam}</span>
              <window.Pill bg="var(--surface-1)" fg="var(--ink-2)">{file.referenceLabel}</window.Pill>
            </div>
          </div>
        </a>
      );
    })}
  </div>
);

const OverviewFieldLive = ({ label, children }) => (
  <div style={{
    padding: '12px 14px', borderRadius: 10, background: 'var(--surface-1)', border: '1px solid var(--line)',
  }}>
    <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4, marginBottom: 5 }}>{label}</div>
    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-1)', lineHeight: 1.35 }}>{children}</div>
  </div>
);

const DetailOverviewTab = ({ p }) => {
  const klant = window.klantById(p.klant);
  const bucket = window.bucketById(p.huidigeBucket);
  const template = p.projectTemplate ? window.templateById(p.projectTemplate) : null;
  const allSteps = p.variants.flatMap((variant) => variant.steps);
  const openSteps = allSteps.filter((step) => !step.done).length;
  const doneSteps = allSteps.filter((step) => step.done).length;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12 }}>
        <OverviewFieldLive label="Klant">{klant.naam}</OverviewFieldLive>
        <OverviewFieldLive label="Projectnummer">{p.id}</OverviewFieldLive>
        <OverviewFieldLive label="Prioriteit">
          <window.PrioPill prio={p.prio} />
        </OverviewFieldLive>
        <OverviewFieldLive label="Huidige stap">
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            <window.TeamChip team={bucket.team} compact />
            <span>{bucket.naam}</span>
          </div>
        </OverviewFieldLive>
        <OverviewFieldLive label="Template">{template?.naam || p.projectTemplate || 'Geen template gekoppeld'}</OverviewFieldLive>
        <OverviewFieldLive label="Projectstart">{window.fmtDateLong(p.start)}</OverviewFieldLive>
        <OverviewFieldLive label="Klantdeadline">{window.fmtDateLong(p.einde)}</OverviewFieldLive>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.4fr) minmax(280px, 0.8fr)', gap: 12 }}>
        <div style={{
          padding: '14px 16px', borderRadius: 12, background: 'var(--surface-1)', border: '1px solid var(--line)',
        }}>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.4, marginBottom: 10 }}>SAUZEN</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {p.variants.map((variant) => {
              const variantBucket = window.bucketById(variant.bucketId);
              return (
                <div key={variant.id} style={{
                  display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto auto', gap: 10,
                  alignItems: 'center', padding: '10px 12px', background: 'var(--paper)',
                  border: '1px solid var(--line)', borderRadius: 10,
                }}>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-1)' }}>{variant.naam}</div>
                    <div style={{ marginTop: 3, fontSize: 11, color: 'var(--ink-3)' }}>
                      {variant.steps.length} stap{variant.steps.length === 1 ? '' : 'pen'}
                    </div>
                  </div>
                  <window.TeamChip team={variantBucket.team} compact />
                  <span style={{ fontSize: 11.5, color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>{window.fmtDate(variant.deadline)}</span>
                </div>
              );
            })}
          </div>
        </div>

        <div style={{
          padding: '14px 16px', borderRadius: 12, background: 'var(--surface-1)', border: '1px solid var(--line)',
        }}>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.4, marginBottom: 10 }}>STATUS</div>
          <div style={{ display: 'grid', gap: 10 }}>
            <OverviewFieldLive label="Sauzen">{p.sauzen.length}</OverviewFieldLive>
            <OverviewFieldLive label="Open stappen">{openSteps}</OverviewFieldLive>
            <OverviewFieldLive label="Afgeronde stappen">{doneSteps}</OverviewFieldLive>
            <OverviewFieldLive label="Betrokken teamleden">
              <window.AvatarStack ids={p.toegewezen} size={24} />
            </OverviewFieldLive>
          </div>
        </div>
      </div>
    </div>
  );
};

const EditProjectModalLive = ({ p, onClose, onSaved }) => {
  const customers = window.KLANTEN;
  const activeTemplates = useMemoLive(() => (
    [...window.PROJECT_TEMPLATES]
      .filter((template) => !template.disabled)
      .sort((left, right) => String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl'))
  ), [window.PROJECT_TEMPLATES]);
  const allStepsCount = p.variants.reduce((sum, variant) => sum + variant.steps.length, 0);
  const [klantId, setKlantId] = useStateLive(p.klant || customers[0]?.id || '');
  const [naam, setNaam] = useStateLive(p.naam || '');
  const [prio, setPrio] = useStateLive(p.prio || 'gemiddeld');
  const [startDate, setStartDate] = useStateLive(p.start || '');
  const [deadline, setDeadline] = useStateLive(p.einde || '');
  const [projectTemplate, setProjectTemplate] = useStateLive(p.projectTemplate || '');
  const [error, setError] = useStateLive('');
  const [busy, setBusy] = useStateLive(false);
  const selectedTemplate = projectTemplate ? window.templateById(projectTemplate) : null;
  const templatePreview = selectedTemplate
    ? buildTemplateSchedulePreview(selectedTemplate, startDate ? new Date(`${startDate}T00:00:00`) : window.todayDate || new Date())
    : null;

  const saveProject = async () => {
    if (!naam.trim()) {
      setError('Vul een projectnaam in.');
      return;
    }
    if (!klantId) {
      setError('Selecteer een klant.');
      return;
    }

    setError('');
    setBusy(true);
    try {
      await window.dashboardApi.updateProject(p.id, {
        projectName: naam.trim(),
        customerId: klantId,
        prio,
        startDate: startDate || null,
        deadline: deadline || null,
        projectTemplate: projectTemplate || null,
      });
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 560, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>PROJECT BEWERKEN</div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>{p.id}</h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px' }}>
          <FieldLive label="Klant">
            <select value={klantId} onChange={(event) => setKlantId(event.target.value)} style={inputBaseStyle}>
              {customers.map((customer) => (
                <option key={customer.id} value={customer.id}>{customer.naam}</option>
              ))}
            </select>
          </FieldLive>

          <FieldLive label="Projectnaam">
            <input value={naam} onChange={(event) => setNaam(event.target.value)} style={inputBaseStyle} />
          </FieldLive>

          <FieldLive label="Prioriteit">
            <div style={{ display: 'flex', gap: 6 }}>
              {Object.entries(window.PRIORITY).map(([id, item]) => (
                <button key={id} onClick={() => setPrio(id)} style={{
                  flex: 1, padding: '8px 10px', fontSize: 12, fontWeight: 600,
                  background: prio === id ? item.bg : 'var(--paper)',
                  color: prio === id ? item.fg : 'var(--ink-2)',
                  border: prio === id ? `1px solid ${item.fg}` : '1px solid var(--line)',
                  borderRadius: 7, cursor: 'pointer',
                }}>{item.label}</button>
              ))}
            </div>
          </FieldLive>

          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 8 }}>
            <FieldLive label="Projectstart">
              <input type="date" value={startDate} onChange={(event) => setStartDate(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
            <FieldLive label="Klantdeadline">
              <input type="date" value={deadline} onChange={(event) => setDeadline(event.target.value)} style={inputBaseStyle} />
            </FieldLive>
          </div>

          <FieldLive
            label="Template"
            hint={allStepsCount > 0 ? 'Alleen leesbaar zodra er al stappen bestaan' : 'Optioneel, wordt op alle sauzen toegepast'}
          >
            {allStepsCount > 0 ? (
              <div style={{
                ...inputBaseStyle,
                background: 'var(--surface-1)',
                color: 'var(--ink-2)',
                cursor: 'default',
              }}>
                {selectedTemplate?.naam || p.projectTemplate || 'Geen template gekoppeld'}
              </div>
            ) : (
              <div style={{ display: 'grid', gap: 10 }}>
                <select value={projectTemplate} onChange={(event) => setProjectTemplate(event.target.value)} style={inputBaseStyle}>
                  <option value="">Geen template</option>
                  {activeTemplates.map((template) => (
                    <option key={template.id} value={template.id}>
                      {template.naam} · {template.stepCount} stappen · {template.durationDays} werkdagen
                    </option>
                  ))}
                </select>
                {selectedTemplate && templatePreview && (
                  <div style={{
                    padding: '10px 12px',
                    borderRadius: 9,
                    border: '1px solid var(--line)',
                    background: 'var(--surface-1)',
                    fontSize: 12,
                    color: 'var(--ink-2)',
                  }}>
                    {templatePreview.stepCount} stappen per saus · {templatePreview.durationDays} werkdagen · verwachte afronding {window.fmtDateLong(templatePreview.end)}
                  </div>
                )}
              </div>
            )}
          </FieldLive>

          {error && (
            <div style={{
              marginTop: 12, padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end', background: 'var(--surface-1)' }}>
          <button onClick={busy ? undefined : onClose} style={{
            padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
          }}>Annuleren</button>
          <button onClick={saveProject} disabled={busy} style={{
            padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
            borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
            opacity: busy ? 0.7 : 1,
          }}>{busy ? 'Bezig…' : 'Project opslaan'}</button>
        </div>
      </div>
    </>
  );
};

const ProjectDetailLive = ({ p, onClose }) => {
  const [tab, setTab] = useStateLive('plan');
  const [editOpen, setEditOpen] = useStateLive(false);
  const [projectAddStepOpen, setProjectAddStepOpen] = useStateLive(false);
  useEffectLive(() => {
    setTab('plan');
    setEditOpen(false);
    setProjectAddStepOpen(false);
  }, [p?.id]);
  if (!p) return null;

  const klant = window.klantById(p.klant);
  const bucket = window.bucketById(p.huidigeBucket);
  const template = p.projectTemplate ? window.templateById(p.projectTemplate) : null;
  const allSteps = p.variants.flatMap((variant) => variant.steps);
  const openSteps = allSteps.filter((step) => !step.done).length;
  const doneSteps = allSteps.filter((step) => step.done).length;
  const overdueSteps = allSteps.filter((step) => !step.done && step.deadline && window.daysUntil(step.deadline) < 0).length;
  const progressPct = Math.round((p.progress || 0) * 100);
  const primaryVariantId = p.variants[0]?.id || '';

  return (
    <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', background: 'var(--canvas)' }}>
      <div style={{ maxWidth: 1260, margin: '0 auto', padding: '20px 24px 32px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
          <button onClick={onClose} style={{
            display: 'flex', alignItems: 'center', gap: 6,
            padding: '8px 12px', border: '1px solid var(--line)', background: 'var(--paper)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)', fontSize: 12.5, fontWeight: 600,
          }}>
            <span style={{ display: 'inline-flex', transform: 'rotate(180deg)' }}>
              <window.Icon name="chevron" size={13} />
            </span>
            Terug
          </button>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.5, textTransform: 'uppercase' }}>
            Projectoverzicht
          </div>
        </div>

        <section style={{
          background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 18,
          overflow: 'hidden', boxShadow: '0 12px 34px rgba(0,0,0,0.04)', marginBottom: 18,
        }}>
          <div style={{
            padding: '24px 26px 22px', borderTop: `4px solid ${klant.kleur}`,
          }}>
            <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 24, alignItems: 'start' }}>
              <div>
                <div style={{ fontSize: 12, fontFamily: 'var(--mono)', fontWeight: 600, color: 'var(--ink-3)', letterSpacing: 0.4 }}>
                  {p.id}
                </div>
                <h2 style={{ fontSize: 39, fontWeight: 650, letterSpacing: -1.1, margin: '4px 0 0', lineHeight: 1.05 }}>{p.naam}</h2>

                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
                  <window.Pill bg="var(--paper)" fg="var(--ink-1)" border="1px solid var(--line)" style={{ padding: '5px 10px', fontSize: 12, fontWeight: 600 }}>
                    <span style={{ width: 8, height: 8, borderRadius: '50%', background: klant.kleur }} />
                    {klant.naam}
                  </window.Pill>
                  <window.PrioPill prio={p.prio} />
                  <window.Pill bg="var(--paper)" fg="var(--ink-2)" border="1px solid var(--line)" style={{ padding: '5px 10px', fontSize: 12, fontWeight: 600 }}>
                    {bucket.naam}
                  </window.Pill>
                  <window.Pill bg="var(--paper)" fg="var(--ink-2)" border="1px solid var(--line)" style={{ padding: '5px 10px', fontSize: 12, fontWeight: 600 }}>
                    {p.sauzen.length} sauzen
                  </window.Pill>
                  {(template || p.projectTemplate) && (
                    <window.Pill bg="var(--paper)" fg="var(--ink-2)" border="1px solid var(--line)" style={{ padding: '5px 10px', fontSize: 12, fontWeight: 600 }}>
                      Template: {template?.naam || p.projectTemplate}
                    </window.Pill>
                  )}
                  <window.Pill bg="var(--paper)" fg="var(--ink-2)" border="1px solid var(--line)" style={{ padding: '5px 10px', fontSize: 12, fontWeight: 600 }}>
                    Deadline: {p.einde ? window.fmtDateLong(p.einde) : 'Nog niet gezet'}
                  </window.Pill>
                </div>

                <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 14, fontSize: 12, flexWrap: 'wrap' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span style={{ color: 'var(--ink-3)' }}>Opgevolgd door</span>
                    <window.AvatarStack ids={p.toegewezen} size={24} />
                  </div>
                  <span style={{ width: 1, height: 14, background: 'var(--line)' }} />
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    <window.TeamChip team={bucket.team} compact />
                    <span style={{ color: 'var(--ink-2)', fontWeight: 500 }}>{bucket.naam}</span>
                  </div>
                </div>

                <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 22 }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ height: 6, background: 'var(--surface-2)', borderRadius: 999, overflow: 'hidden' }}>
                      <div style={{
                        height: '100%',
                        width: `${Math.max(progressPct, allSteps.length ? 4 : 0)}%`,
                        borderRadius: 999,
                        background: 'linear-gradient(90deg, oklch(0.68 0.09 155), oklch(0.48 0.12 155))',
                      }} />
                    </div>
                  </div>
                  <div style={{ minWidth: 120, fontSize: 12, color: 'var(--ink-2)', fontFamily: 'var(--mono)', textAlign: 'right' }}>
                    {progressPct}% afgerond
                  </div>
                </div>
              </div>

              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 14 }}>
                <div style={{ display: 'flex', gap: 18, justifyContent: 'flex-end' }}>
                  <ProjectDetailStatLive label="Open stappen" value={openSteps} />
                  <ProjectDetailStatLive label="Afgerond" value={doneSteps} tone="success" />
                  <ProjectDetailStatLive label="Te laat" value={overdueSteps} tone="alert" />
                </div>

                <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: 166 }}>
                  <button onClick={() => setEditOpen(true)} style={{
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7,
                    padding: '11px 14px', border: 'none', background: 'var(--ink-1)',
                    borderRadius: 10, cursor: 'pointer', color: 'var(--paper)', fontSize: 12.5, fontWeight: 650,
                    boxShadow: '0 12px 24px rgba(0,0,0,0.08)',
                  }}>
                    <window.Icon name="edit" size={14} />
                    Bewerk project
                  </button>

                  <button onClick={() => setProjectAddStepOpen(true)} style={{
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7,
                    padding: '11px 14px', border: '1px solid var(--line)', background: 'var(--paper)',
                    borderRadius: 10, cursor: 'pointer', color: 'var(--ink-2)', fontSize: 12.5, fontWeight: 650,
                  }}>
                    <window.Icon name="plus" size={14} />
                    Nieuwe stap
                  </button>
                </div>
              </div>
            </div>
          </div>
        </section>

        <section style={{
          background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 16,
          overflow: 'hidden', boxShadow: '0 10px 28px rgba(0,0,0,0.03)',
        }}>
          <div style={{ display: 'flex', gap: 6, padding: '0 18px', borderBottom: '1px solid var(--line)', overflowX: 'auto' }}>
            {[
              { id: 'overview', label: 'Overzicht' },
              { id: 'plan', label: 'Planning', count: allSteps.length },
              { id: 'sauzen', label: 'Sauzen', count: p.sauzen.length },
              { id: 'chat', label: 'Opmerkingen', count: p.opmerkingen },
              { id: 'files', label: 'Bijlagen', count: p.bijlagen },
            ].map((item) => (
              <button key={item.id} onClick={() => setTab(item.id)} style={{
                padding: '14px 8px 13px', border: 'none', background: 'transparent',
                fontSize: 13, fontWeight: tab === item.id ? 650 : 550, cursor: 'pointer', whiteSpace: 'nowrap',
                color: tab === item.id ? 'var(--accent-fg)' : 'var(--ink-3)',
                borderBottom: tab === item.id ? '2px solid var(--accent-fg)' : '2px solid transparent',
                marginBottom: -1,
              }}>
                {item.label}
                {item.count != null && <span style={{ color: 'var(--ink-3)', marginLeft: 3 }}>{item.count}</span>}
              </button>
            ))}
          </div>

          <div style={{ padding: '22px 24px' }}>
            {tab === 'overview' && <DetailOverviewTab p={p} />}
            {tab === 'plan' && <DetailPlanTab p={p} />}
            {tab === 'sauzen' && <DetailSauzenTab p={p} />}
            {tab === 'chat' && <DetailCommentsTab p={p} />}
            {tab === 'files' && <DetailFilesTab p={p} />}
          </div>
        </section>
      </div>

      {editOpen && (
        <EditProjectModalLive
          p={p}
          onClose={() => setEditOpen(false)}
          onSaved={() => setEditOpen(false)}
        />
      )}
      {projectAddStepOpen && (
        <ProjectAddStepModalLive
          p={p}
          initialVariantId={primaryVariantId}
          onClose={() => setProjectAddStepOpen(false)}
          onSaved={() => setProjectAddStepOpen(false)}
        />
      )}
    </div>
  );
};

const MijnDagLive = ({ onPick }) => {
  const tasks = window.getMyTasks(window.CURRENT_USER_ID);
  const todayDate = parsePlanningDate(window.today) || new Date();
  const today = planningDateKey(todayDate);
  const currentUser = window.medewerkerById(window.CURRENT_USER_ID);
  const datedTasks = tasks.map((task) => {
    const start = task.start || task.deadline || '';
    const deadline = task.deadline || task.start || '';
    const activeToday = Boolean(start && deadline && start <= today && deadline >= today);
    const anchorDate = activeToday ? today : (start || deadline || '');
    return {
      ...task,
      start,
      deadline,
      activeToday,
      anchorDate,
    };
  });

  const overdue = datedTasks.filter((task) => task.deadline && window.daysUntil(task.deadline) < 0).sort(sortMyDayTasksLive);
  const overdueIds = new Set(overdue.map((task) => task.id));
  const plannedTasks = datedTasks.filter((task) => !overdueIds.has(task.id));
  const todayTasks = plannedTasks.filter((task) => task.anchorDate === today).sort(sortMyDayTasksLive);
  const timelineKeys = [
    ...new Set(
      plannedTasks
        .filter((task) => task.anchorDate)
        .map((task) => task.anchorDate)
        .sort()
    ),
  ].slice(0, 5);
  const agendaDays = timelineKeys.map((dayKey) => {
    const dayTasks = plannedTasks.filter((task) => task.anchorDate === dayKey).sort(sortMyDayTasksLive);
    return {
      key: dayKey,
      date: parsePlanningDate(dayKey) || todayDate,
      tasks: dayTasks,
      isToday: dayKey === today,
    };
  });
  const handoffReadyTasks = datedTasks.filter((task) => task.previousStep && task.readyForHandoff).sort(sortMyDayTasksLive);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '22px 28px 40px' }}>
      <div style={{ maxWidth: 1220, margin: '0 auto' }}>
        <div style={{
          background: 'linear-gradient(135deg, oklch(0.96 0.03 60), oklch(0.97 0.02 40))',
          border: '1px solid oklch(0.88 0.04 60)',
          borderRadius: 16, padding: '20px 22px',
          display: 'flex', alignItems: 'center', gap: 16, marginBottom: 20,
          flexWrap: 'wrap',
        }}>
          {currentUser && <window.Avatar m={currentUser} size={48} />}
          <div style={{ flex: 1, minWidth: 240 }}>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>
              MIJN DAG
            </div>
            <div style={{ fontSize: 22, fontWeight: 650, letterSpacing: -0.4, marginTop: 2 }}>
              {`${(currentUser?.naam || 'team').split(' ')[0]}, hier staat het belangrijkste.`}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            <StatTileLive label="Vandaag" value={todayTasks.length} tone={todayTasks.length ? 'hot' : 'neutral'} />
            <StatTileLive label="Klaar voor jou" value={handoffReadyTasks.length} tone={handoffReadyTasks.length ? 'success' : 'neutral'} />
            {overdue.length > 0 && <StatTileLive label="Te laat" value={overdue.length} tone="alert" />}
          </div>
        </div>

        {(handoffReadyTasks.length > 0 || overdue.length > 0) && (
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
            gap: 14,
            marginBottom: 18,
          }}>
            {handoffReadyTasks.length > 0 && (
              <AgendaSupportPanelLive
                title="Klaar voor jou"
                subtitle="Direct oppakbaar"
                tasks={handoffReadyTasks}
                onPick={onPick}
                tone="success"
                emptyLabel="Nog geen stappen doorgekregen."
              />
            )}
            {overdue.length > 0 && (
              <AgendaSupportPanelLive
                title="Te laat"
                subtitle="Eerst oplossen"
                tasks={overdue}
                onPick={onPick}
                tone="alert"
                emptyLabel="Geen taken te laat."
              />
            )}
          </div>
        )}

        <div style={{
          background: 'var(--paper)',
          border: '1px solid var(--line)',
          borderRadius: 16,
          padding: 18,
        }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginBottom: 10 }}>
            <div>
              <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--ink-1)', letterSpacing: -0.2 }}>Eerstvolgende planning</div>
              <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 3 }}>
                Je eerstvolgende taken, ook als ze pas volgende week starten.
              </div>
            </div>
          </div>

          {agendaDays.length > 0 ? (
            <div style={{
              marginTop: 16,
              display: 'flex',
              flexDirection: 'column',
            }}>
              {agendaDays.map((day, index) => (
                <AgendaDaySectionLive key={day.key} day={day} onPick={onPick} first={index === 0} />
              ))}
            </div>
          ) : (
            <div style={{
              marginTop: 12,
              padding: '18px 16px',
              borderRadius: 12,
              border: '1px dashed var(--line)',
              background: 'var(--surface-1)',
              color: 'var(--ink-3)',
              fontSize: 12.5,
            }}>
              Er staan nog geen taken met een datum op jouw planning.
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

const StatTileLive = ({ label, value, tone }) => {
  const color = tone === 'hot'
    ? 'oklch(0.55 0.18 30)'
    : tone === 'alert'
      ? 'oklch(0.48 0.18 25)'
      : tone === 'success'
        ? 'oklch(0.40 0.12 150)'
        : tone === 'warning'
          ? 'oklch(0.44 0.12 60)'
      : 'var(--ink-1)';
  const valueText = String(value);
  const valueFontSize = valueText.length > 6 ? 16 : valueText.length > 4 ? 18 : 22;

  return (
    <div style={{
      background: 'var(--paper)', border: '1px solid var(--line)',
      borderRadius: 9, padding: '8px 14px', minWidth: 70, textAlign: 'center',
    }}>
      <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>{label.toUpperCase()}</div>
      <div style={{ fontSize: valueFontSize, fontWeight: 650, color, letterSpacing: -0.5, marginTop: 2, whiteSpace: 'nowrap' }}>{value}</div>
    </div>
  );
};

const MY_DAY_PRIORITY_WEIGHT_LIVE = {
  dringend: 4,
  belangrijk: 3,
  gemiddeld: 2,
  laag: 1,
};

const MY_DAY_WAITING_REASON_LABELS = {
  customer: 'klant',
  internal: 'intern',
  supplier: 'leverancier',
  review: 'review',
  production: 'productie',
};

const formatMyDayWaitingReasonLive = (reason) => {
  if (!reason || reason === 'none') return '';
  return MY_DAY_WAITING_REASON_LABELS[reason] || String(reason).toLowerCase();
};

const formatMyDayStepOwnerLive = (ownerId) => {
  if (!ownerId) return '';
  const owner = window.medewerkerById(ownerId);
  return owner ? displayTeamMemberName(owner) : ownerId;
};

const formatMyDayPreviousStepStateLive = (step) => {
  if (!step) return '';
  if (step.done) {
    return step.completedOn ? `Klaar op ${window.fmtDateTime(step.completedOn)}` : 'Klaar';
  }
  if (step.waitingReason && step.waitingReason !== 'none') {
    return `Wacht op ${formatMyDayWaitingReasonLive(step.waitingReason)}`;
  }
  if (step.status === 'Working') return 'Nog bezig';
  return 'Nog niet klaar';
};

const getMyDayHandoffMetaLive = (task) => {
  const previousStep = task.previousStep || null;

  if (!previousStep) {
    return {
      badge: 'Startstap',
      bg: 'oklch(0.97 0.01 80)',
      border: '1px solid var(--line)',
      pillBg: 'var(--paper)',
      pillFg: 'var(--ink-2)',
      pillBorder: '1px solid var(--line)',
    };
  }

  if (task.readyForHandoff) {
    return {
      badge: 'Klaar voor jou',
      bg: 'oklch(0.97 0.03 150)',
      border: '1px solid oklch(0.86 0.05 150)',
      pillBg: 'oklch(0.93 0.05 150)',
      pillFg: 'oklch(0.36 0.10 150)',
      pillBorder: '1px solid oklch(0.84 0.05 150)',
    };
  }

  return {
    badge: 'Wacht op voorganger',
    bg: 'oklch(0.98 0.02 60)',
    border: '1px solid oklch(0.87 0.04 60)',
    pillBg: 'oklch(0.95 0.04 60)',
    pillFg: 'oklch(0.40 0.09 60)',
    pillBorder: '1px solid oklch(0.84 0.05 60)',
  };
};

const sortMyDayTasksLive = (left, right) => {
  const leftPriority = MY_DAY_PRIORITY_WEIGHT_LIVE[left.prio] || 0;
  const rightPriority = MY_DAY_PRIORITY_WEIGHT_LIVE[right.prio] || 0;
  const leftDeadline = left.deadline || '9999-12-31';
  const rightDeadline = right.deadline || '9999-12-31';
  const leftAnchor = left.anchorDate || leftDeadline;
  const rightAnchor = right.anchorDate || rightDeadline;
  const leftOverdue = left.deadline && window.daysUntil(left.deadline) < 0 ? 1 : 0;
  const rightOverdue = right.deadline && window.daysUntil(right.deadline) < 0 ? 1 : 0;
  if (leftOverdue !== rightOverdue) return rightOverdue - leftOverdue;
  if (leftAnchor !== rightAnchor) return leftAnchor.localeCompare(rightAnchor);
  if (leftDeadline !== rightDeadline) return leftDeadline.localeCompare(rightDeadline);
  const leftBlocked = left.previousStep && !left.readyForHandoff ? 1 : 0;
  const rightBlocked = right.previousStep && !right.readyForHandoff ? 1 : 0;
  if (leftBlocked !== rightBlocked) return leftBlocked - rightBlocked;
  if (leftPriority !== rightPriority) return rightPriority - leftPriority;
  return String(left.projectNaam || '').localeCompare(String(right.projectNaam || ''), 'nl');
};

const AgendaDaySectionLive = ({ day, onPick, first = false }) => (
  <section style={{
    display: 'grid',
    gridTemplateColumns: 'minmax(140px, 180px) minmax(0, 1fr)',
    gap: 16,
    padding: '18px 0',
    borderTop: first ? 'none' : '1px solid var(--line)',
  }}>
    <div style={{
      padding: '2px 0',
      display: 'flex',
      flexDirection: 'column',
      gap: 8,
    }}>
      <div>
        <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-3)', letterSpacing: 0.55, textTransform: 'uppercase' }}>
          {formatPlanningWeekday(day.date)}
        </div>
        <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--ink-1)', letterSpacing: -0.4, marginTop: 2 }}>
          {window.fmtDate(day.key)}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {day.isToday && (
          <window.Pill bg="oklch(0.95 0.04 60)" fg="oklch(0.41 0.12 55)" border="1px solid oklch(0.84 0.05 60)">
            Vandaag
          </window.Pill>
        )}
        <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
          {day.tasks.length} taak{day.tasks.length === 1 ? '' : 'en'}
        </div>
      </div>
    </div>

    {day.tasks.length > 0 ? (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {day.tasks.map((task) => <AgendaTaskRowLive key={task.id} t={task} onPick={onPick} />)}
      </div>
    ) : (
      <div style={{
        display: 'flex',
        alignItems: 'center',
        minHeight: 98,
        padding: '16px 18px',
        borderRadius: 14,
        border: '1px dashed var(--line)',
        background: 'var(--surface-1)',
        color: 'var(--ink-3)',
        fontSize: 12.5,
      }}>
        Geen taken gepland op deze dag.
      </div>
    )}
  </section>
);

const AgendaSupportPanelLive = ({ title, subtitle, tasks, onPick, tone, emptyLabel }) => {
  const alertTone = tone === 'alert';
  const successTone = tone === 'success';
  const warningTone = tone === 'warning';
  const background = alertTone
    ? 'oklch(0.98 0.02 25)'
    : successTone
      ? 'oklch(0.98 0.02 150)'
      : warningTone
        ? 'oklch(0.98 0.015 60)'
        : 'var(--paper)';
  const border = alertTone
    ? 'oklch(0.86 0.06 25)'
    : successTone
      ? 'oklch(0.86 0.05 150)'
      : warningTone
        ? 'oklch(0.86 0.04 60)'
        : 'var(--line)';
  const titleColor = alertTone
    ? 'oklch(0.42 0.14 25)'
    : successTone
      ? 'oklch(0.36 0.10 150)'
      : warningTone
        ? 'oklch(0.40 0.09 60)'
        : 'var(--ink-1)';
  const pillBg = alertTone
    ? 'oklch(0.95 0.05 25)'
    : successTone
      ? 'oklch(0.94 0.05 150)'
      : warningTone
        ? 'oklch(0.95 0.04 60)'
        : 'var(--surface-1)';
  const pillFg = alertTone
    ? 'oklch(0.42 0.14 25)'
    : successTone
      ? 'oklch(0.36 0.10 150)'
      : warningTone
        ? 'oklch(0.40 0.09 60)'
        : 'var(--ink-2)';
  return (
    <section style={{
      background,
      border: `1px solid ${border}`,
      borderRadius: 16,
      padding: 16,
    }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginBottom: 12 }}>
        <div>
          <div style={{ fontSize: 15, fontWeight: 700, color: titleColor }}>{title}</div>
          {subtitle && <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 3 }}>{subtitle}</div>}
        </div>
        <window.Pill bg={pillBg} fg={pillFg} border={`1px solid ${border}`}>
          {tasks.length} taak{tasks.length === 1 ? '' : 'en'}
        </window.Pill>
      </div>

      {tasks.length > 0 ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {tasks.slice(0, 4).map((task) => <AgendaTaskRowLive key={task.id} t={task} onPick={onPick} compact />)}
          {tasks.length > 4 && (
            <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>
              + {tasks.length - 4} extra taak{tasks.length - 4 === 1 ? '' : 'en'}
            </div>
          )}
        </div>
      ) : (
        <div style={{
          padding: '18px 14px',
          borderRadius: 12,
          border: '1px dashed var(--line)',
          background: 'var(--surface-1)',
          color: 'var(--ink-3)',
          fontSize: 12,
        }}>
          {emptyLabel || 'Geen taken.'}
        </div>
      )}
    </section>
  );
};

const AgendaTaskRowLive = ({ t, onPick, compact = false }) => {
  const project = window.projectById(t.projectId);
  const bucket = window.bucketById(t.bucketId);
  const customer = window.klantById(t.klant);
  const overdue = Boolean(t.deadline && window.daysUntil(t.deadline) < 0);
  const clickable = Boolean(project && onPick);
  const handoffMeta = getMyDayHandoffMetaLive(t);

  return (
    <div onClick={() => clickable && onPick(project)} style={{
      display: 'flex',
      flexDirection: 'column',
      padding: compact ? '11px 12px' : '12px 14px',
      background: 'var(--paper)',
      border: `1px solid ${overdue ? 'oklch(0.86 0.06 25)' : 'var(--line)'}`,
      borderLeft: `4px solid ${overdue ? 'oklch(0.65 0.18 25)' : customer.kleur}`,
      borderRadius: 12,
      cursor: clickable ? 'pointer' : 'default',
      boxShadow: compact ? 'none' : '0 4px 14px rgba(15, 23, 42, 0.04)',
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <input type="checkbox" style={{ width: 16, height: 16, marginTop: 2, accentColor: 'oklch(0.58 0.12 150)' }} onClick={(event) => event.stopPropagation()} onChange={() => window.dashboardApi.updateTask(t.id, { status: 'Completed' })} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <window.TeamChip team={bucket.team} compact />
            {t.variantNaam && <window.Pill bg="var(--surface-1)" fg="var(--ink-2)" border="1px solid var(--line)">{t.variantNaam}</window.Pill>}
          </div>
          <div style={{ fontSize: compact ? 12.5 : 13.5, fontWeight: 700, color: 'var(--ink-1)', marginTop: 7, lineHeight: 1.35 }}>
            {bucket.naam}
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4, display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
            <span>{t.projectNaam}</span>
            <span>·</span>
            <span style={{ color: customer.kleur, fontWeight: 700 }}>{customer.naam}</span>
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 6, flexWrap: 'wrap' }}>
          {t.deadline && <window.DeadlineChip deadline={t.deadline} />}
          {handoffMeta && (
            <window.Pill bg={handoffMeta.pillBg} fg={handoffMeta.pillFg} border={handoffMeta.pillBorder}>
              {handoffMeta.badge}
            </window.Pill>
          )}
        </div>
      </div>
    </div>
  );
};

function useCustomerRowsLive() {
  const customerRows = useMemoLive(() => {
    const map = new Map(window.KLANTEN.map((customer) => [customer.id, { customer, projects: [] }]));
    window.PROJECTEN.forEach((project) => {
      if (!map.has(project.klant)) {
        map.set(project.klant, { customer: window.klantById(project.klant), projects: [] });
      }
      map.get(project.klant).projects.push(project);
    });

    return Array.from(map.values())
      .map(({ customer, projects }) => ({
        customer,
        projects: projects.sort((left, right) => left.naam.localeCompare(right.naam)),
        projectCount: projects.length,
        sauceCount: projects.reduce((sum, project) => sum + project.sauzen.length, 0),
        urgentCount: projects.filter((project) => project.prio === 'dringend').length,
      }))
      .sort((left, right) => left.customer.naam.localeCompare(right.customer.naam));
  }, [window.KLANTEN, window.PROJECTEN]);
  return customerRows;
}

const PerKlantOverviewLive = ({ onPick }) => {
  const customerRows = useCustomerRowsLive();

  const [expanded, setExpanded] = useStateLive(() => new Set(customerRows.filter((row) => row.projectCount > 0).slice(0, 2).map((row) => row.customer.id)));

  useEffectLive(() => {
    setExpanded((current) => {
      const validIds = new Set(customerRows.map((row) => row.customer.id));
      const next = new Set([...current].filter((id) => validIds.has(id)));
      customerRows
        .filter((row) => row.projectCount > 0)
        .slice(0, 2)
        .forEach((row) => next.add(row.customer.id));
      return next;
    });
  }, [customerRows]);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '20px 28px 40px' }}>
      <div style={{ maxWidth: 1200, margin: '0 auto' }}>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.6, marginBottom: 16 }}>
          Dit overzicht bundelt lopende projecten per klant, terwijl de brondata zelf standaard in ERPNext blijft staan.
        </div>

        {customerRows.map((row) => {
          const customer = row.customer;
          const projects = row.projects;
          const customerId = customer.id;
          const isOpen = expanded.has(customerId);
          const toggle = () => {
            const next = new Set(expanded);
            next.has(customerId) ? next.delete(customerId) : next.add(customerId);
            setExpanded(next);
          };

          return (
            <div key={customerId} style={{
              background: 'var(--paper)', border: '1px solid var(--line)',
              borderRadius: 10, marginBottom: 12, overflow: 'hidden',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '14px 18px' }}>
                <button onClick={toggle} style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  flex: 1, padding: 0, background: 'transparent',
                  border: 'none', cursor: 'pointer', textAlign: 'left',
                }}>
                  <div style={{
                    width: 36, height: 36, borderRadius: 9,
                    background: customer.kleur, color: 'white',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontWeight: 700, fontSize: 13, letterSpacing: -0.2,
                  }}>{customer.naam.substring(0, 2).toUpperCase()}</div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 15, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.2 }}>{customer.naam}</div>
                    <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>
                      {row.projectCount} aanvragen · {row.sauceCount} sauzen
                    </div>
                  </div>
                  {row.urgentCount > 0 && <window.Pill bg="oklch(0.94 0.06 25)" fg="oklch(0.45 0.18 25)">{row.urgentCount} dringend</window.Pill>}
                  <span style={{
                    color: 'var(--ink-3)',
                    transform: isOpen ? 'rotate(90deg)' : 'none',
                    transition: 'transform 140ms',
                  }}>
                    <window.Icon name="chevron" size={16} />
                  </span>
                </button>
              </div>
              {isOpen && (
                <div style={{ padding: '0 18px 16px', borderTop: '1px solid var(--line)' }}>
                  {projects.length > 0 ? (
                    <KlantProjectTableLive projects={projects} onPick={onPick} />
                  ) : (
                    <div style={{ paddingTop: 12, fontSize: 12, color: 'var(--ink-3)' }}>
                      Nog geen projecten gekoppeld aan deze klant.
                    </div>
                  )}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

const CustomerConfigCardLive = ({ row, isLast }) => {
  const customer = row.customer;
  const [customerEditOpen, setCustomerEditOpen] = useStateLive(false);
  const contactCount = customer.contacts.length;
  const contactLabel = `${contactCount} ${contactCount === 1 ? 'contactpersoon' : 'contactpersonen'}`;
  const customerAvatar = {
    naam: customer.naam,
    kleur: customer.kleur,
    initials: customer.naam.substring(0, 2).toUpperCase(),
  };

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(260px, 1.2fr) minmax(300px, 1fr) auto',
      alignItems: 'center',
      gap: 18,
      padding: '18px',
      minHeight: 92,
      borderBottom: isLast ? 'none' : '1px solid var(--line)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 0 }}>
        <window.Avatar m={customerAvatar} size={30} />
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.2 }}>{customer.naam}</div>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>
            {row.projectCount} projecten · {row.sauceCount} sauzen · {contactLabel}
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        <window.Pill bg="var(--surface-1)" fg="var(--ink-2)">
          {contactLabel}
        </window.Pill>
        <window.Pill bg="oklch(0.96 0.02 80)" fg="var(--ink-2)">
          {row.projectCount} projecten
        </window.Pill>
        {row.urgentCount > 0 && (
          <window.Pill bg="oklch(0.94 0.06 25)" fg="oklch(0.45 0.18 25)">
            {row.urgentCount} dringend
          </window.Pill>
        )}
      </div>

      <div style={{ display: 'flex', justifyContent: 'flex-end', minWidth: 130 }}>
        <button
          onClick={() => setCustomerEditOpen(true)}
          style={{
            padding: '8px 10px', borderRadius: 8, border: '1px solid var(--line)',
            background: 'var(--surface-1)', color: 'var(--ink-2)', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
          }}
        >
          Bewerk klant
        </button>
      </div>
      {customerEditOpen && (
        <EditCustomerModalLive
          customer={customer}
          projectCount={row.projectCount}
          onClose={() => setCustomerEditOpen(false)}
          onSaved={() => setCustomerEditOpen(false)}
        />
      )}
    </div>
  );
};

const KlantenConfigLive = ({ search = '' }) => {
  const customerRows = useCustomerRowsLive();
  const [newOpen, setNewOpen] = useStateLive(false);
  const filteredRows = useMemoLive(() => {
    const term = String(search || '').trim().toLowerCase();
    if (!term) return customerRows;

    return customerRows.filter((row) => {
      const customer = row.customer || {};
      const contacts = customer.contacts || [];
      const address = customer.address || {};
      const projects = row.projects || [];

      const haystack = [
        customer.id,
        customer.naam,
        address.address_line1,
        address.address_line2,
        address.city,
        address.pincode,
        address.country,
        ...contacts.flatMap((contact) => [
          contact.naam,
          contact.email,
          contact.phone,
          contact.role,
        ]),
        ...projects.flatMap((project) => [
          project.id,
          project.naam,
        ]),
      ]
        .filter(Boolean)
        .join(' ')
        .toLowerCase();

      return haystack.includes(term);
    });
  }, [customerRows, search]);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '20px 28px 40px' }}>
      <div style={{ maxWidth: 1100, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div style={{
          display: 'flex', justifyContent: 'flex-end', padding: '4px 0 2px',
        }}>
          <button onClick={() => setNewOpen(true)} style={{
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            padding: '10px 14px', borderRadius: 9, border: 'none', background: 'var(--ink-1)',
            color: 'var(--paper)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
          }}>
            <window.Icon name="plus" size={14} /> Voeg klant toe
          </button>
        </div>

        <div style={{ background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden' }}>
          {filteredRows.length === 0 ? (
            <div style={{ padding: '18px', fontSize: 12.5, color: 'var(--ink-3)' }}>
              {search
                ? `Geen klanten gevonden voor "${search}".`
                : 'Nog geen klanten beschikbaar.'}
            </div>
          ) : filteredRows.map((row, index) => (
            <CustomerConfigCardLive
              key={row.customer.id}
              row={row}
              isLast={index === filteredRows.length - 1}
            />
          ))}
        </div>
      </div>
      {newOpen && <NewCustomerModalLive onClose={() => setNewOpen(false)} onCreated={() => setNewOpen(false)} />}
    </div>
  );
};

const UserConfigCardLive = ({ user, isLast }) => {
  const [userEditOpen, setUserEditOpen] = useStateLive(false);
  const avatar = {
    naam: user.naam,
    kleur: user.kleur,
    initials: user.initials,
    image: user.image,
  };
  const roleLabel = user.roleLabel || user.rol || 'Geen rol';

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(280px, 1.2fr) minmax(320px, 1fr) auto',
      alignItems: 'center',
      gap: 18,
      padding: '18px',
      minHeight: 92,
      borderBottom: isLast ? 'none' : '1px solid var(--line)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 0 }}>
        <window.Avatar m={avatar} size={30} />
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.2 }}>
            {user.naam}
          </div>
          <div style={{ marginTop: 6 }}>
            <window.Pill
              bg="var(--surface-1)"
              fg="var(--ink-2)"
              border="1px solid var(--line)"
              style={{ padding: '2px 8px', fontSize: 10.5, fontWeight: 600, letterSpacing: 0.1 }}
            >
              {roleLabel}
            </window.Pill>
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        {user.phone && (
          <window.Pill bg="var(--surface-1)" fg="var(--ink-2)" border="1px solid var(--line)">
            Tel {user.phone}
          </window.Pill>
        )}
        {user.mobile && (
          <window.Pill bg="var(--surface-1)" fg="var(--ink-2)" border="1px solid var(--line)">
            Mobiel {user.mobile}
          </window.Pill>
        )}
      </div>

      <div style={{ display: 'flex', justifyContent: 'flex-end', minWidth: 150 }}>
        <button
          onClick={() => setUserEditOpen(true)}
          style={{
            padding: '8px 10px', borderRadius: 8, border: '1px solid var(--line)',
            background: 'var(--surface-1)', color: 'var(--ink-2)', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
          }}
        >
          Bewerk gebruiker
        </button>
      </div>

      {userEditOpen && (
        <UserModalLive
          user={user}
          onClose={() => setUserEditOpen(false)}
          onSaved={() => setUserEditOpen(false)}
        />
      )}
    </div>
  );
};

const GebruikersConfigLive = ({ search = '' }) => {
  const [newOpen, setNewOpen] = useStateLive(false);
  const users = useMemoLive(() => (
    [...window.GEBRUIKERS].sort((left, right) => (
      String(left.naam || left.email || '').localeCompare(String(right.naam || right.email || ''), 'nl')
    ))
  ), [window.GEBRUIKERS]);

  const filteredUsers = useMemoLive(() => {
    const term = String(search || '').trim().toLowerCase();
    if (!term) return users;

    return users.filter((user) => (
      [
        user.id,
        user.naam,
        user.email,
        user.phone,
        user.mobile,
        user.roleLabel,
      ]
        .filter(Boolean)
        .join(' ')
        .toLowerCase()
        .includes(term)
    ));
  }, [users, search]);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '20px 28px 40px' }}>
      <div style={{ maxWidth: 1100, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div style={{
          display: 'flex', justifyContent: 'flex-end', padding: '4px 0 2px',
        }}>
          <button onClick={() => setNewOpen(true)} style={{
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            padding: '10px 14px', borderRadius: 9, border: 'none', background: 'var(--ink-1)',
            color: 'var(--paper)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
          }}>
            <window.Icon name="plus" size={14} /> Voeg gebruiker toe
          </button>
        </div>

        <div style={{ background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden' }}>
          {filteredUsers.length === 0 ? (
            <div style={{ padding: '18px', fontSize: 12.5, color: 'var(--ink-3)' }}>
              {search
                ? `Geen gebruikers gevonden voor "${search}".`
                : 'Nog geen gebruikers beschikbaar.'}
            </div>
          ) : filteredUsers.map((user, index) => (
            <UserConfigCardLive
              key={user.id}
              user={user}
              isLast={index === filteredUsers.length - 1}
            />
          ))}
        </div>
      </div>
      {newOpen && <UserModalLive onClose={() => setNewOpen(false)} onSaved={() => setNewOpen(false)} />}
    </div>
  );
};

const ProjectTemplateModalLive = ({ template, onClose, onSaved }) => {
  const taskTypeOptions = useMemoLive(() => (
    [...window.BUCKETS]
      .filter((bucket) => bucket.id !== '—')
      .sort((left, right) => (
        (left.sequence || 0) - (right.sequence || 0)
        || String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
      ))
  ), [window.BUCKETS]);
  const defaultTaskType = taskTypeOptions[0]?.naam || '';
  const [name, setName] = useStateLive(template?.naam || '');
  const [disabled, setDisabled] = useStateLive(Boolean(template?.disabled));
  const [steps, setSteps] = useStateLive(() => {
    if (template?.steps?.length) {
      return template.steps.map((step) => ({
        localId: makeDraftId(),
        taskId: step.taskId || step.taskName || '',
        taskType: step.taskType || '',
        name: step.name || '',
        durationDays: step.durationDays || 1,
        expectedHours: step.expectedHours || '',
        priority: step.priority || 'Medium',
        milestone: Boolean(step.milestone),
        description: step.description || '',
      }));
    }
    return [emptyTemplateStepDraft(defaultTaskType)];
  });
  const [busy, setBusy] = useStateLive(false);
  const [error, setError] = useStateLive('');

  const preview = useMemoLive(() => (
    buildTemplateSchedulePreview(
      {
        steps: steps.map((step) => ({
          ...step,
          durationDays: Math.max(parseNumericInput(step.durationDays, 1), 1),
          expectedHours: Math.max(parseNumericInput(step.expectedHours, 0), 0),
          name: String(step.name || step.taskType || '').trim(),
        })),
      },
      window.todayDate || new Date(),
    )
  ), [steps]);

  const updateStep = (index, patch) => {
    setSteps((current) => current.map((step, itemIndex) => {
      if (itemIndex !== index) return step;
      const next = { ...step, ...patch };
      if (patch.taskType && (!String(step.name || '').trim() || step.name === step.taskType)) {
        next.name = patch.taskType;
      }
      return next;
    }));
  };

  const addStep = () => {
    setSteps((current) => [...current, emptyTemplateStepDraft(defaultTaskType)]);
  };

  const moveStep = (index, direction) => {
    setSteps((current) => {
      const targetIndex = index + direction;
      if (targetIndex < 0 || targetIndex >= current.length) return current;
      const next = [...current];
      const [item] = next.splice(index, 1);
      next.splice(targetIndex, 0, item);
      return next;
    });
  };

  const removeStep = (index) => {
    setSteps((current) => {
      if (current.length === 1) return [emptyTemplateStepDraft(defaultTaskType)];
      return current.filter((_, itemIndex) => itemIndex !== index);
    });
  };

  const saveTemplate = async () => {
    if (!name.trim()) {
      setError('Vul een naam in voor de template.');
      return;
    }

    const normalizedSteps = steps.map((step) => ({
      taskId: step.taskId || '',
      taskType: String(step.taskType || '').trim(),
      name: String(step.name || '').trim(),
      durationDays: Math.max(Math.round(parseNumericInput(step.durationDays, 1)), 1),
      expectedHours: Math.max(parseNumericInput(step.expectedHours, 0), 0),
      priority: step.priority || 'Medium',
      milestone: Boolean(step.milestone),
      description: String(step.description || '').trim(),
    }));

    if (normalizedSteps.some((step) => !step.taskType)) {
      setError('Selecteer voor elke stap een taaktype.');
      return;
    }
    if (normalizedSteps.some((step) => !step.name)) {
      setError('Vul voor elke stap een naam in.');
      return;
    }

    setBusy(true);
    setError('');
    try {
      const payload = {
        name: name.trim(),
        disabled,
        steps: normalizedSteps,
      };
      if (template) {
        await window.dashboardApi.updateProjectTemplate(template.id, payload);
      } else {
        await window.dashboardApi.createProjectTemplate(payload);
      }
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message || 'Template opslaan mislukt.');
    } finally {
      setBusy(false);
    }
  };

  const deleteTemplate = async () => {
    if (!template) return;
    if (!window.confirm(`Template "${template.naam}" verwijderen?`)) return;

    setBusy(true);
    setError('');
    try {
      await window.dashboardApi.deleteProjectTemplate(template.id);
      if (onSaved) onSaved();
    } catch (exc) {
      setError(exc.message || 'Template verwijderen mislukt.');
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <div onClick={busy ? undefined : onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(20,14,10,0.4)', zIndex: 50,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: 860, maxWidth: 'calc(100vw - 24px)', maxHeight: '88vh', background: 'var(--paper)',
        border: '1px solid var(--line)', borderRadius: 14, zIndex: 51,
        display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: 0.4 }}>
              {template ? 'TEMPLATE BEWERKEN' : 'NIEUWE TEMPLATE'}
            </div>
            <h3 style={{ fontSize: 18, fontWeight: 650, letterSpacing: -0.3, margin: 0, marginTop: 2 }}>
              {template ? template.naam : 'Projecttemplate aanmaken'}
            </h3>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={busy ? undefined : onClose} style={{
            width: 32, height: 32, border: 'none', background: 'var(--surface-1)',
            borderRadius: 8, cursor: 'pointer', color: 'var(--ink-2)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><window.Icon name="x" size={16} /></button>
        </div>

        <div style={{ overflowY: 'auto', padding: '18px 22px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 18 }}>
          <div>
            <FieldLive label="Templatenaam">
              <input value={name} onChange={(event) => setName(event.target.value)} placeholder="bijv. Retail standaard" style={inputBaseStyle} />
            </FieldLive>

            <FieldLive label="Status">
              <label style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '10px 12px', borderRadius: 8, border: '1px solid var(--line)', background: 'var(--surface-1)',
                fontSize: 12.5, color: 'var(--ink-2)',
              }}>
                <input type="checkbox" checked={!disabled} onChange={(event) => setDisabled(!event.target.checked)} />
                Template actief voor nieuwe projecten
              </label>
            </FieldLive>

            <FieldLive label="Stappen" hint="Volgorde bepaalt de automatische planning">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {steps.map((step, index) => (
                  <div key={step.localId} style={{
                    padding: '12px',
                    border: '1px solid var(--line)',
                    borderRadius: 10,
                    background: 'var(--surface-1)',
                    display: 'grid',
                    gap: 10,
                  }}>
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))', gap: 8 }}>
                      <select
                        value={step.taskType}
                        onChange={(event) => updateStep(index, { taskType: event.target.value })}
                        style={inputBaseStyle}
                      >
                        <option value="">Selecteer stap</option>
                        {taskTypeOptions.map((taskType) => (
                          <option key={taskType.naam} value={taskType.naam}>
                            {taskType.naam} · {taskType.team}
                          </option>
                        ))}
                      </select>
                      <input
                        value={step.name}
                        onChange={(event) => updateStep(index, { name: event.target.value })}
                        placeholder="Naam van de stap"
                        style={inputBaseStyle}
                      />
                      <input
                        type="number"
                        min="1"
                        step="1"
                        value={step.durationDays}
                        onChange={(event) => updateStep(index, { durationDays: event.target.value })}
                        placeholder="Dagen"
                        style={inputBaseStyle}
                      />
                      <input
                        type="number"
                        min="0"
                        step="0.5"
                        value={step.expectedHours}
                        onChange={(event) => updateStep(index, { expectedHours: event.target.value })}
                        placeholder="Uren"
                        style={inputBaseStyle}
                      />
                    </div>

                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))', gap: 8, alignItems: 'center' }}>
                      <select
                        value={step.priority}
                        onChange={(event) => updateStep(index, { priority: event.target.value })}
                        style={inputBaseStyle}
                      >
                        {TEMPLATE_PRIORITY_OPTIONS_LIVE.map((option) => (
                          <option key={option} value={option}>{option}</option>
                        ))}
                      </select>
                      <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--ink-2)' }}>
                        <input
                          type="checkbox"
                          checked={step.milestone}
                          onChange={(event) => updateStep(index, { milestone: event.target.checked })}
                        />
                        Milestone
                      </label>
                      <input
                        value={step.description}
                        onChange={(event) => updateStep(index, { description: event.target.value })}
                        placeholder="Korte omschrijving voor deze stap"
                        style={inputBaseStyle}
                      />
                      <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
                        <button
                          onClick={() => moveStep(index, -1)}
                          disabled={index === 0}
                          style={{
                            padding: '7px 10px', borderRadius: 8, border: '1px solid var(--line)',
                            background: 'var(--paper)', color: 'var(--ink-2)', fontSize: 11.5, cursor: index === 0 ? 'default' : 'pointer',
                            opacity: index === 0 ? 0.45 : 1,
                          }}
                        >
                          Omhoog
                        </button>
                        <button
                          onClick={() => moveStep(index, 1)}
                          disabled={index === steps.length - 1}
                          style={{
                            padding: '7px 10px', borderRadius: 8, border: '1px solid var(--line)',
                            background: 'var(--paper)', color: 'var(--ink-2)', fontSize: 11.5, cursor: index === steps.length - 1 ? 'default' : 'pointer',
                            opacity: index === steps.length - 1 ? 0.45 : 1,
                          }}
                        >
                          Omlaag
                        </button>
                        <button
                          onClick={() => removeStep(index)}
                          style={{
                            padding: '7px 10px', borderRadius: 8, border: '1px solid oklch(0.82 0.07 25)',
                            background: 'oklch(0.97 0.03 25)', color: 'oklch(0.42 0.16 25)', fontSize: 11.5, cursor: 'pointer',
                          }}
                        >
                          Verwijder
                        </button>
                      </div>
                    </div>
                  </div>
                ))}

                <button onClick={addStep} style={{
                  padding: '9px 12px', borderRadius: 8, border: '1px dashed var(--line)',
                  background: 'transparent', color: 'var(--ink-2)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
                }}>
                  + Stap toevoegen
                </button>
              </div>
            </FieldLive>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{
              padding: '14px 16px',
              borderRadius: 12,
              border: '1px solid var(--line)',
              background: 'var(--surface-1)',
            }}>
              <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.4, marginBottom: 10 }}>
                TEMPLATE PREVIEW
              </div>
              <div style={{ display: 'grid', gap: 8 }}>
                <OverviewFieldLive label="Stappen per saus">{preview?.stepCount || 0}</OverviewFieldLive>
                <OverviewFieldLive label="Totale duur">{preview ? `${preview.durationDays} werkdagen` : '—'}</OverviewFieldLive>
                <OverviewFieldLive label="Verwachte uren">{preview ? formatHoursLabel(preview.expectedHours) : '—'}</OverviewFieldLive>
                <OverviewFieldLive label="Verwachte einddatum">{preview ? window.fmtDateLong(preview.end) : '—'}</OverviewFieldLive>
              </div>
            </div>

            <div style={{
              padding: '14px 16px',
              borderRadius: 12,
              border: '1px solid var(--line)',
              background: 'var(--surface-1)',
            }}>
              <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: 0.4, marginBottom: 10 }}>
                AUTO-INVULLING
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {(preview?.steps || []).map((step, index) => (
                  <div key={step.taskId || `${step.name}-${index}`} style={{
                    padding: '10px 12px',
                    background: 'var(--paper)',
                    borderRadius: 9,
                    border: '1px solid var(--line)',
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                      <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--ink-1)' }}>{step.name}</div>
                      <window.Pill bg="var(--surface-1)" fg="var(--ink-2)" border="1px solid var(--line)">
                        {step.taskType || 'Geen taaktype'}
                      </window.Pill>
                      {step.milestone && (
                        <window.Pill bg="oklch(0.96 0.03 55)" fg="oklch(0.42 0.10 52)" border="1px solid oklch(0.84 0.05 55)">
                          Milestone
                        </window.Pill>
                      )}
                    </div>
                    <div style={{ marginTop: 4, fontSize: 11.5, color: 'var(--ink-3)' }}>
                      {window.fmtDate(step.start)} - {window.fmtDate(step.end)} · {step.durationDays} werkdag{step.durationDays === 1 ? '' : 'en'}
                      {parseNumericInput(step.expectedHours, 0) > 0 && ` · ${formatHoursLabel(step.expectedHours)}`}
                    </div>
                    {step.description && (
                      <div style={{ marginTop: 6, fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.45 }}>
                        {step.description}
                      </div>
                    )}
                  </div>
                ))}
              </div>
            </div>
          </div>

          {error && (
            <div style={{
              gridColumn: '1 / -1',
              padding: '10px 12px', borderRadius: 8,
              border: '1px solid oklch(0.80 0.12 25)', background: 'oklch(0.96 0.04 25)',
              color: 'oklch(0.38 0.18 25)', fontSize: 12.5,
            }}>{error}</div>
          )}
        </div>

        <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'space-between', background: 'var(--surface-1)' }}>
          <div>
            {template ? (
              <button
                onClick={busy ? undefined : deleteTemplate}
                disabled={busy}
                style={{
                  padding: '9px 16px', borderRadius: 8,
                  border: '1px solid oklch(0.78 0.09 25)', background: 'oklch(0.97 0.03 25)',
                  color: 'oklch(0.42 0.16 25)', fontSize: 12.5, fontWeight: 600, cursor: busy ? 'not-allowed' : 'pointer',
                  opacity: busy ? 0.7 : 1,
                }}
              >
                Verwijder template
              </button>
            ) : <span />}
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
            <button onClick={busy ? undefined : onClose} style={{
              padding: '9px 16px', background: 'var(--paper)', border: '1px solid var(--line)',
              borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--ink-2)',
            }}>Annuleren</button>
            <button onClick={saveTemplate} disabled={busy} style={{
              padding: '9px 16px', background: 'var(--ink-1)', border: 'none',
              borderRadius: 8, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', color: 'var(--paper)',
              opacity: busy ? 0.7 : 1,
            }}>{busy ? 'Bezig…' : 'Template opslaan'}</button>
          </div>
        </div>
      </div>
    </>
  );
};

const ProjectTemplateConfigCardLive = ({ template, isLast }) => {
  const [editOpen, setEditOpen] = useStateLive(false);
  const linkedProjects = window.PROJECTEN.filter((project) => project.projectTemplate === template.id).length;

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(260px, 1.2fr) minmax(320px, 1fr) auto',
      alignItems: 'center',
      gap: 18,
      padding: '18px',
      minHeight: 98,
      borderBottom: isLast ? 'none' : '1px solid var(--line)',
    }}>
      <div style={{ minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
          <div style={{ fontSize: 15, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.2 }}>{template.naam}</div>
          {!template.disabled ? (
            <window.Pill bg="oklch(0.95 0.04 150)" fg="oklch(0.40 0.10 150)" border="1px solid oklch(0.86 0.05 150)">
              Actief
            </window.Pill>
          ) : (
            <window.Pill bg="oklch(0.95 0.01 80)" fg="var(--ink-3)" border="1px solid var(--line)">
              Uitgeschakeld
            </window.Pill>
          )}
        </div>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4 }}>
          {template.stepCount} stappen per saus · {template.durationDays} werkdagen · {formatHoursLabel(template.expectedHours)} · {linkedProjects} gekoppelde projecten
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        {template.steps.slice(0, 4).map((step) => (
          <window.Pill key={step.taskId || step.name} bg="var(--surface-1)" fg="var(--ink-2)" border="1px solid var(--line)">
            {step.name}
          </window.Pill>
        ))}
        {template.steps.length > 4 && (
          <window.Pill bg="var(--surface-1)" fg="var(--ink-3)" border="1px solid var(--line)">
            +{template.steps.length - 4} meer
          </window.Pill>
        )}
      </div>

      <div style={{ display: 'flex', justifyContent: 'flex-end', minWidth: 150 }}>
        <button
          onClick={() => setEditOpen(true)}
          style={{
            padding: '8px 10px', borderRadius: 8, border: '1px solid var(--line)',
            background: 'var(--surface-1)', color: 'var(--ink-2)', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
          }}
        >
          Bewerk template
        </button>
      </div>

      {editOpen && (
        <ProjectTemplateModalLive
          template={template}
          onClose={() => setEditOpen(false)}
          onSaved={() => setEditOpen(false)}
        />
      )}
    </div>
  );
};

const ProjectTemplatesConfigLive = ({ search = '' }) => {
  const [newOpen, setNewOpen] = useStateLive(false);
  const templates = useMemoLive(() => (
    [...window.PROJECT_TEMPLATES].sort((left, right) => (
      Number(Boolean(left.disabled)) - Number(Boolean(right.disabled))
      || String(left.naam || left.id).localeCompare(String(right.naam || right.id), 'nl')
    ))
  ), [window.PROJECT_TEMPLATES]);

  const filteredTemplates = useMemoLive(() => {
    const term = String(search || '').trim().toLowerCase();
    if (!term) return templates;

    return templates.filter((template) => (
      [
        template.id,
        template.naam,
        ...template.steps.flatMap((step) => [
          step.name,
          step.taskType,
          step.description,
        ]),
      ]
        .filter(Boolean)
        .join(' ')
        .toLowerCase()
        .includes(term)
    ));
  }, [templates, search]);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '20px 28px 40px' }}>
      <div style={{ maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 14, flexWrap: 'wrap',
        }}>
          <div style={{ fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.6 }}>
            Templates vullen bij nieuwe projecten automatisch alle stappen, duur en verwachte uren per saus in.
          </div>
          <button onClick={() => setNewOpen(true)} style={{
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            padding: '10px 14px', borderRadius: 9, border: 'none', background: 'var(--ink-1)',
            color: 'var(--paper)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
          }}>
            <window.Icon name="plus" size={14} /> Voeg template toe
          </button>
        </div>

        <div style={{ background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden' }}>
          {filteredTemplates.length === 0 ? (
            <div style={{ padding: '18px', fontSize: 12.5, color: 'var(--ink-3)' }}>
              {search
                ? `Geen templates gevonden voor "${search}".`
                : 'Nog geen templates beschikbaar.'}
            </div>
          ) : filteredTemplates.map((template, index) => (
            <ProjectTemplateConfigCardLive
              key={template.id}
              template={template}
              isLast={index === filteredTemplates.length - 1}
            />
          ))}
        </div>
      </div>

      {newOpen && (
        <ProjectTemplateModalLive
          onClose={() => setNewOpen(false)}
          onSaved={() => setNewOpen(false)}
        />
      )}
    </div>
  );
};

const KlantProjectTableLive = ({ projects, onPick }) => (
  <div style={{ marginTop: 8 }}>
    {projects.map((project) => (
      <div key={project.id} onClick={() => onPick(project)} style={{
        padding: '12px 4px', borderBottom: '1px solid var(--line)',
        cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 8,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ fontSize: 11, fontFamily: 'var(--mono)', color: 'var(--ink-3)', fontWeight: 600 }}>{project.id}</span>
          <span style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--ink-1)' }}>{project.naam}</span>
          <span style={{ flex: 1 }} />
          <window.PrioPill prio={project.prio} />
          <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>deadline {window.fmtDate(project.einde)}</span>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(230px, 1fr))', gap: 6 }}>
          {project.variants.map((variant) => {
            const bucket = window.bucketById(variant.bucketId);
            return (
              <div key={variant.id} style={{
                padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6,
                background: 'var(--surface-1)',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                  <span style={{ fontSize: 11.5, fontWeight: 550, color: 'var(--ink-1)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{variant.naam}</span>
                  <window.TeamChip team={bucket.team} compact />
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <div style={{ flex: 1, height: 3, background: 'var(--surface-2)', borderRadius: 2, overflow: 'hidden' }}>
                    <div style={{ height: '100%', width: `${Math.max((variant.progress || 0) * 100, 4)}%`, background: window.klantById(project.klant).kleur }} />
                  </div>
                  <span style={{ fontSize: 10, color: 'var(--ink-3)', fontFamily: 'var(--mono)' }}>{bucket.naam}</span>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    ))}
  </div>
);

const LijstLive = ({ onPick }) => {
  const [sort, setSort] = useStateLive('deadline');
  const projects = useMemoLive(() => {
    const items = [...window.PROJECTEN];
    items.sort((left, right) => {
      if (sort === 'deadline') return (left.einde || '9999-12-31').localeCompare(right.einde || '9999-12-31');
      if (sort === 'klant') return window.klantById(left.klant).naam.localeCompare(window.klantById(right.klant).naam);
      if (sort === 'prio') {
        const order = { dringend: 0, belangrijk: 1, gemiddeld: 2, laag: 3 };
        return order[left.prio] - order[right.prio];
      }
      return 0;
    });
    return items;
  }, [sort, window.PROJECTEN]);

  return (
    <div style={{ flex: 1, overflow: 'auto', background: 'var(--canvas)', padding: '18px 28px 40px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, fontSize: 12, color: 'var(--ink-3)' }}>
        Sorteer:
        {['deadline', 'klant', 'prio'].map((item) => (
          <button key={item} onClick={() => setSort(item)} style={{
            padding: '3px 8px', fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
            background: sort === item ? 'var(--ink-1)' : 'transparent',
            color: sort === item ? 'var(--paper)' : 'var(--ink-2)',
            border: sort === item ? 'none' : '1px solid var(--line)', borderRadius: 5,
          }}>{item}</button>
        ))}
      </div>
      <div style={{ background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 10, overflow: 'hidden' }}>
        <div style={{
          display: 'grid',
          gridTemplateColumns: '98px 1.6fr 1fr 180px 110px 90px 120px',
          padding: '10px 16px', fontSize: 10.5, fontWeight: 650,
          color: 'var(--ink-3)', letterSpacing: 0.5, background: 'var(--surface-1)',
          borderBottom: '1px solid var(--line)',
        }}>
          <span>PROJECT ID</span>
          <span>NAAM</span>
          <span>KLANT</span>
          <span>HUIDIGE STAP</span>
          <span>PRIO</span>
          <span>TEAM</span>
          <span>DEADLINE</span>
        </div>
        {projects.map((project) => {
          const customer = window.klantById(project.klant);
          const bucket = window.bucketById(project.huidigeBucket);
          return (
            <div key={project.id} onClick={() => onPick(project)} style={{
              display: 'grid',
              gridTemplateColumns: '98px 1.6fr 1fr 180px 110px 90px 120px',
              padding: '10px 16px', fontSize: 12.5,
              borderBottom: '1px solid var(--line)', cursor: 'pointer',
              alignItems: 'center',
            }}
            onMouseEnter={(event) => { event.currentTarget.style.background = 'var(--surface-1)'; }}
            onMouseLeave={(event) => { event.currentTarget.style.background = 'var(--paper)'; }}>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ink-2)', fontWeight: 550 }}>{project.id}</span>
              <span style={{ fontWeight: 600, color: 'var(--ink-1)' }}>{project.naam}</span>
              <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: customer.kleur }} />
                {customer.naam}
              </span>
              <span><window.TeamChip team={bucket.team} compact /> <span style={{ fontSize: 11.5, color: 'var(--ink-2)', marginLeft: 3 }}>{bucket.naam}</span></span>
              <span><window.PrioPill prio={project.prio} /></span>
              <span><window.AvatarStack ids={project.toegewezen} size={20} max={3} /></span>
              <span style={{ fontSize: 11.5 }}><window.DeadlineChip deadline={project.einde} /></span>
            </div>
          );
        })}
      </div>
    </div>
  );
};

const TeamLive = () => {
  const workload = useMemoLive(() => {
    const map = {};
    window.MEDEWERKERS.forEach((user) => {
      map[user.id] = emptyWorkloadStats();
    });
    window.ALL_TASKS.forEach((task) => {
      if (!task.owner || !map[task.owner]) return;
      const days = window.daysUntil(task.deadline);
      map[task.owner].open += 1;
      if (days < 0) map[task.owner].overdue += 1;
      if (days === 0) map[task.owner].vandaag += 1;
      if (days >= 0 && days <= 7) map[task.owner].deze_week += 1;
    });
    return map;
  }, [window.ALL_TASKS, window.MEDEWERKERS]);

  const users = useMemoLive(() => {
    return window.MEDEWERKERS
      .map((user) => ({
        ...user,
        displayName: displayTeamMemberName(user),
        workload: workload[user.id] || emptyWorkloadStats(),
      }))
      .sort((left, right) => (
        right.workload.open - left.workload.open
        || left.displayName.localeCompare(right.displayName, 'nl')
      ));
  }, [workload, window.MEDEWERKERS]);

  const maxOpen = Math.max(...users.map((item) => item.workload.open), 1);

  const renderWorkloadBar = (stats, maxValue) => {
    const remaining = Math.max(stats.open - stats.overdue - stats.vandaag, 0);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
        <div style={{ display: 'flex', height: 18, borderRadius: 4, overflow: 'hidden', background: 'var(--surface-2)' }}>
          {stats.overdue > 0 && <div style={{ width: `${(stats.overdue / maxValue) * 100}%`, background: 'oklch(0.60 0.18 25)' }} />}
          {stats.vandaag > 0 && <div style={{ width: `${(stats.vandaag / maxValue) * 100}%`, background: 'oklch(0.68 0.15 55)' }} />}
          {remaining > 0 && <div style={{ width: `${(remaining / maxValue) * 100}%`, background: 'var(--ink-2)', opacity: 0.3 }} />}
        </div>
      </div>
    );
  };

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'var(--canvas)', padding: '22px 28px 40px' }}>
      <div style={{ maxWidth: 1100, margin: '0 auto' }}>
        <div style={{ background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden' }}>
          {users.map((user, index) => (
            <div key={user.id} style={{
              display: 'grid',
              gridTemplateColumns: '240px 1fr 80px 90px 90px',
              alignItems: 'center',
              gap: 12,
              padding: '14px 18px',
              borderBottom: index === users.length - 1 ? 'none' : '1px solid var(--line)',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
                <window.Avatar m={{ ...user, naam: user.displayName }} size={30} />
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--ink-1)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {user.displayName}
                  </div>
                  <div style={{ marginTop: 3 }}>
                    <window.Pill
                      bg="var(--surface-1)"
                      fg="var(--ink-2)"
                      border="1px solid var(--line)"
                      style={{ padding: '2px 8px', fontSize: 10.5, fontWeight: 600, letterSpacing: 0.1 }}
                    >
                      {user.roleLabel || user.rol || 'Geen rol'}
                    </window.Pill>
                  </div>
                </div>
              </div>
              {renderWorkloadBar(user.workload, maxOpen)}
              <div style={{ textAlign: 'center' }}>
                <div style={{ fontSize: 17, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.3 }}>{user.workload.open}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: 0.3 }}>OPEN</div>
              </div>
              <div style={{ textAlign: 'center' }}>
                <div style={{ fontSize: 17, fontWeight: 650, color: user.workload.overdue ? 'oklch(0.48 0.18 25)' : 'var(--ink-3)', letterSpacing: -0.3 }}>
                  {user.workload.overdue}
                </div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: 0.3 }}>TE LAAT</div>
              </div>
              <div style={{ textAlign: 'center' }}>
                <div style={{ fontSize: 17, fontWeight: 650, color: 'var(--ink-1)', letterSpacing: -0.3 }}>{user.workload.deze_week}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: 0.3 }}>DEZE WEEK</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

window.Sidebar = SidebarLive;
window.Topbar = TopbarLive;
window.NewProjectModal = NewProjectModalLive;
window.ProjectDetail = ProjectDetailLive;
window.MijnDag = MijnDagLive;
window.PerKlantOverview = PerKlantOverviewLive;
window.KlantenConfig = KlantenConfigLive;
window.GebruikersConfig = GebruikersConfigLive;
window.ProjectTemplatesConfig = ProjectTemplatesConfigLive;
window.Lijst = LijstLive;
window.Team = TeamLive;
