import React, { useState } from "react";
import { SIA_PHASES, PROJECT_TYPES, PROTOKOLL_TYPES } from "../constants.js";

const NON_BILLING_CATEGORIES = ["Wettbewerb"];
import { calcSIAHours, calcManualHours, deriveQuoteBudget, migrateLinkedQuotes, generateId, formatCHF, formatDate, formatHours, applyProjectNumberFormat, parseSeqFromNumber, nextProtoSeq, applyProtoNumberFormat } from "../utils.js";
import { Header, Modal, FormField, StatusBadge, useConfirm , DateInput } from "../components/UI.jsx";

function ProjectEditForm({ form, setForm, data }) {
  const [newPhaseLabel, setNewPhaseLabel] = useState("");
  const togglePhase = (phaseId) => {
    setForm(prev => {
      const phases = prev.enabledPhases || [];
      return { ...prev, enabledPhases: phases.includes(phaseId) ? phases.filter(p => p !== phaseId) : [...phases, phaseId] };
    });
  };
  return (
    <>
      <div className="form-row">
        <FormField label="Projektnummer">
          <input value={form.number} onChange={e => setForm({ ...form, number: e.target.value })} placeholder={`z.B. ${new Date().getFullYear()}/01`} style={{ maxWidth: 140 }} />
        </FormField>
        <FormField label="Projektname *" style={{ flex: 3 }}>
          <input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} placeholder="z.B. Wohnhaus Müller" autoFocus style={!form.name?.trim() ? { borderColor: "#b5621e" } : {}} />
        </FormField>
      </div>
      <div className="form-row">
        <FormField label="Kategorie">
          <select value={form.category} onChange={e => setForm({ ...form, category: e.target.value })}>
            {PROJECT_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
          </select>
        </FormField>
        <FormField label="Auftraggeber">
          <select value={form.clientId} onChange={e => setForm({ ...form, clientId: e.target.value })}>
            <option value="">— kein Auftraggeber —</option>
            {(data.persons||[]).filter(p=>p.isAuftraggeber).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
        </FormField>
        <FormField label="Status">
          <select value={form.status} onChange={e => setForm({ ...form, status: e.target.value })}>
            {["aktiv", "pausiert", "abgeschlossen"].map(s => <option key={s}>{s}</option>)}
          </select>
        </FormField>
      </div>
      <div className="form-row">
        {NON_BILLING_CATEGORIES.includes(form.category) ? (
          <div style={{ flex: 1, display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", background: "#faf8f5", borderRadius: 6, border: "1px solid #e0dbd4", fontSize: 12, color: "#888" }}>
            <span style={{ fontSize: 16 }}>○</span>
            <span><strong style={{ color: "#1a1a18" }}>Nicht verrechenbar</strong> — Stunden werden erfasst, aber nicht fakturiert.</span>
          </div>
        ) : (
          <>
            <FormField label="Abrechnungstyp">
              <select value={form.billingType} onChange={e => setForm({ ...form, billingType: e.target.value })}>
                <option value="stundensatz">Stundensatz</option>
                <option value="pauschal">Pauschal</option>
              </select>
            </FormField>
            {form.billingType === "stundensatz"
              ? <FormField label="Stundensatz (CHF)"><input type="number" value={form.hourlyRate} onChange={e => setForm({ ...form, hourlyRate: +e.target.value })} /></FormField>
              : <FormField label="Pauschalpreis (CHF)"><input type="number" value={form.budget} onChange={e => setForm({ ...form, budget: +e.target.value })} /></FormField>}
          </>
        )}
        <FormField label="Startdatum"><DateInput value={form.startDate} onChange={e => setForm({ ...form, startDate: e.target.value })} /></FormField>
      </div>
      <div className="form-row">
        <FormField label="Stundenbudget (Soll-Stunden)">
          <input type="number" step="0.5" min={0} value={form.budgetHours || 0}
            onChange={e => setForm({ ...form, budgetHours: +e.target.value, linkedQuotes: [], phasesBudget: [] })}
            placeholder="0 = aus Offerten berechnet" />
        </FormField>
      </div>
      {(() => {
        const linked = form.linkedQuotes || [];
        const ROLES = ["Hauptofferte", "Nachtrag", "Referenz"];

        const buildPositions = (newLinked, existingPositions) => {
          const manual = (existingPositions || []).filter(p => !p.quoteId);
          const nachtraege = newLinked.filter(lq => lq.role === "Nachtrag").map((lq, idx) => {
            const existing = (existingPositions || []).find(p => p.quoteId === lq.quoteId);
            const q = (data.quotes || []).find(x => x.id === lq.quoteId);
            const sd = deriveQuoteBudget([lq], data.quotes || [], data.settings.roles || []);
            return { code: existing?.code || `N${idx + 1}`, label: existing !== undefined ? existing.label : (q?.projectName || q?.number || ""), enabledPhases: existing?.enabledPhases || sd.enabledPhases, quoteId: lq.quoteId };
          });
          return [...manual, ...nachtraege];
        };

        const addQuote = (quoteId) => {
          if (!quoteId || linked.some(lq => lq.quoteId === quoteId)) return;
          const isFirst = linked.length === 0;
          const newLinked = [...linked, { quoteId, role: isFirst ? "Hauptofferte" : "Nachtrag" }];
          const d = deriveQuoteBudget(newLinked, data.quotes || [], data.settings.roles || []);
          const q = (data.quotes || []).find(x => x.id === quoteId);
          setForm(f => ({
            ...f, linkedQuotes: newLinked, budgetHours: d.budgetHours, budgetAmount: d.budgetAmount, phasesBudget: d.phasesBudget,
            enabledPhases: isFirst ? [...new Set([...(f.enabledPhases || []), ...d.enabledPhases])] : (f.enabledPhases || []),
            billingType: isFirst ? (q?.mode === "manual" ? "stundensatz" : "pauschal") : f.billingType,
            positions: buildPositions(newLinked, f.positions || []),
          }));
        };

        const removeQuote = (quoteId) => {
          const newLinked = linked.filter(lq => lq.quoteId !== quoteId);
          const d = newLinked.length > 0 ? deriveQuoteBudget(newLinked, data.quotes || [], data.settings.roles || []) : { budgetHours: 0, budgetAmount: 0, phasesBudget: [] };
          setForm(f => ({ ...f, linkedQuotes: newLinked, budgetHours: d.budgetHours, budgetAmount: d.budgetAmount, phasesBudget: d.phasesBudget, positions: buildPositions(newLinked, (f.positions || []).filter(p => p.quoteId !== quoteId)) }));
        };

        const changeRole = (quoteId, role) => setForm(f => {
          const newLinked = f.linkedQuotes.map(lq => lq.quoteId === quoteId ? { ...lq, role } : lq);
          return { ...f, linkedQuotes: newLinked, positions: buildPositions(newLinked, f.positions || []) };
        });

        const updateNachtragPos = (quoteId, updates) => setForm(f => ({ ...f, positions: (f.positions || []).map(p => p.quoteId === quoteId ? { ...p, ...updates } : p) }));
        const toggleHOPhase = (phId) => setForm(f => { const phases = f.enabledPhases || []; return { ...f, enabledPhases: phases.includes(phId) ? phases.filter(p => p !== phId) : [...phases, phId] }; });
        const toggleNTPhase = (quoteId, phId) => setForm(f => ({ ...f, positions: (f.positions || []).map(p => { if (p.quoteId !== quoteId) return p; const phases = p.enabledPhases || []; return { ...p, enabledPhases: phases.includes(phId) ? phases.filter(x => x !== phId) : [...phases, phId] }; }) }));

        const alreadyInOtherProjects = new Set((data.projects || []).filter(p => p.id !== form.id).flatMap(p => migrateLinkedQuotes(p).map(lq => lq.quoteId)));
        const unlinkedQuotes = (data.quotes || []).filter(q => !linked.some(lq => lq.quoteId === q.id) && !alreadyInOtherProjects.has(q.id));
        const sortedLinked = [...linked.filter(lq => lq.role === "Hauptofferte"), ...linked.filter(lq => lq.role !== "Hauptofferte")];

        return (
          <div style={{ marginBottom: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 10 }}>VERKNÜPFTE HONORAROFFERTEN</div>
            {sortedLinked.map(lq => {
              const q = (data.quotes || []).find(x => x.id === lq.quoteId);
              if (!q) return null;
              const isNT = lq.role === "Nachtrag";
              const pos = isNT ? (form.positions || []).find(p => p.quoteId === lq.quoteId) : null;
              const currentPhases = isNT ? (pos?.enabledPhases || []) : (form.enabledPhases || []);
              const qRoles = q.quoteRoles || data.settings.roles || [];
              const qH = q.mode === "sia" ? (calcSIAHours(q.sia?.baukosten, q.sia?.schwierigkeit, q.sia?.phases || []).total || 0) : q.mode === "manual" ? (calcManualHours(q.manualPhases || [], qRoles).totalHours || 0) : 0;
              const bgColor = isNT ? "#f0f5fd" : "#faf8f5";
              const borderColor = isNT ? "#c8d8ee" : "#ddd8d0";
              const accentColor = isNT ? "#1a4e8a" : "#2d6a4f";
              return (
                <div key={lq.quoteId} style={{ marginBottom: 10, border: `1px solid ${borderColor}`, borderRadius: 8, overflow: "hidden" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", background: bgColor, flexWrap: "wrap" }}>
                    {isNT && pos ? (
                      <>
                        <input value={pos.code || ""} onChange={e => updateNachtragPos(lq.quoteId, { code: e.target.value.toUpperCase().replace(/\s/g, "").slice(0, 6) })} placeholder="N1" style={{ width: 52, fontWeight: 700, fontSize: 12, height: 26, padding: "0 6px" }} />
                        <input value={pos.label || ""} onChange={e => updateNachtragPos(lq.quoteId, { label: e.target.value })} placeholder="z.B. Nachtrag Fassade" style={{ flex: 1, minWidth: 100, fontSize: 12, height: 26, padding: "0 6px" }} />
                      </>
                    ) : (
                      <span style={{ fontSize: 10, fontWeight: 700, background: "#e8f5ee", color: accentColor, padding: "2px 8px", borderRadius: 3 }}>⬡ HO</span>
                    )}
                    <span style={{ fontWeight: 600, color: "#b07848", fontSize: 12 }}>{q.number}</span>
                    <span style={{ color: "#888", fontSize: 11 }}>{q.mode === "sia" ? "SIA 102" : q.mode === "manual" ? "Aufwand" : "Pauschal"}</span>
                    {qH > 0 && <span style={{ color: "#555", fontSize: 11 }}>{qH.toFixed(1)}h</span>}
                    <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
                      <select value={lq.role} onChange={e => changeRole(lq.quoteId, e.target.value)} style={{ fontSize: 11, height: 26, padding: "0 6px" }}>
                        {ROLES.map(r => <option key={r}>{r}</option>)}
                      </select>
                      <button className="btn btn-danger" style={{ padding: "0 6px", height: 24, fontSize: 10 }} onClick={() => removeQuote(lq.quoteId)}><span className="material-icons" style={{ fontSize: 16 }}>close</span></button>
                    </div>
                  </div>
                  <div style={{ padding: "10px 12px", background: "#fff" }}>
                    <div style={{ fontSize: 10, color: "#888", marginBottom: 6, letterSpacing: "0.07em" }}>AKTIVIERTE SIA-PHASEN FÜR ZEITERFASSUNG</div>
                    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 3 }}>
                      {SIA_PHASES.map(ph => (
                        <label key={ph.id} style={{ display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontSize: 11, textTransform: "none", color: "#1a1a18" }}>
                          <input type="checkbox" checked={currentPhases.includes(ph.id)} onChange={() => isNT ? toggleNTPhase(lq.quoteId, ph.id) : toggleHOPhase(ph.id)} style={{ width: "auto" }} />
                          {ph.label}
                        </label>
                      ))}
                    </div>
                  </div>
                </div>
              );
            })}
            {linked.length === 0 && <div style={{ fontSize: 11, color: "#aaa", marginBottom: 8 }}>Noch keine Offerte verknüpft.</div>}
            {unlinkedQuotes.length > 0 && (
              <select defaultValue="" onChange={e => { addQuote(e.target.value); e.target.value = ""; }} style={{ fontSize: 12, width: "100%" }}>
                <option value="">{linked.length === 0 ? "Offerte verknüpfen…" : "+ Nachtrag / weitere Offerte hinzufügen…"}</option>
                {unlinkedQuotes.map(q => <option key={q.id} value={q.id}>{q.number} — {q.mode === "sia" ? "SIA 102" : q.mode === "manual" ? "Aufwand" : "Pauschal"} · {formatCHF(q.total)}</option>)}
              </select>
            )}
          </div>
        );
      })()}
      <FormField label="Beschreibung"><textarea rows={2} value={form.description} onChange={e => setForm({ ...form, description: e.target.value })} style={{ resize: "vertical" }} /></FormField>
      <div className="form-group" style={{ marginTop: 6 }}>
        <label>Eigene Phasen <span style={{ fontWeight: 400, fontSize: 11, color: "#aaa" }}>(z.B. für Wettbewerbe)</span></label>
        <div style={{ padding: "8px 10px", background: "#faf8f5", borderRadius: 6, border: "1px solid #ece8e2" }}>
          {(form.customPhases || []).map(cp => (
            <div key={cp.id} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "3px 0", borderBottom: "1px solid #ece8e2" }}>
              <span style={{ fontSize: 12 }}>{cp.label}</span>
              <button onClick={() => setForm(f => ({ ...f, customPhases: (f.customPhases || []).filter(c => c.id !== cp.id) }))} style={{ background: "none", border: "none", color: "#bbb", cursor: "pointer", fontSize: 15, padding: "0 2px", lineHeight: 1 }}>×</button>
            </div>
          ))}
          <div style={{ display: "flex", gap: 6, alignItems: "center", marginTop: (form.customPhases || []).length > 0 ? 7 : 0 }}>
            <input value={newPhaseLabel} onChange={e => setNewPhaseLabel(e.target.value)} placeholder="z.B. Visualisierung, Modell, Präsentation…" style={{ flex: 1, height: 28, fontSize: 12 }}
              onKeyDown={e => { if (e.key === "Enter" && newPhaseLabel.trim()) { setForm(f => ({ ...f, customPhases: [...(f.customPhases || []), { id: "cp_" + generateId(), label: newPhaseLabel.trim() }] })); setNewPhaseLabel(""); }}} />
            <button className="btn btn-ghost" style={{ height: 28, padding: "0 10px", fontSize: 11, whiteSpace: "nowrap" }}
              onClick={() => { if (!newPhaseLabel.trim()) return; setForm(f => ({ ...f, customPhases: [...(f.customPhases || []), { id: "cp_" + generateId(), label: newPhaseLabel.trim() }] })); setNewPhaseLabel(""); }}>+ Hinzufügen</button>
          </div>
        </div>
      </div>
      {(form.linkedQuotes || []).length === 0 && (
        <div className="form-group" style={{ marginTop: 6 }}>
          <label>Relevante SIA-Phasen (nur angewählte sind bei der Zeiterfassung verfügbar)</label>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6, marginTop: 6, padding: 12, background: "#faf8f5", borderRadius: 6, border: "1px solid #ece8e2" }}>
            {SIA_PHASES.map(ph => (
              <label key={ph.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 12, textTransform: "none", color: "#1a1a18", padding: "4px 0" }}>
                <input type="checkbox" checked={(form.enabledPhases || []).includes(ph.id)} onChange={() => togglePhase(ph.id)} style={{ width: "auto" }} />
                {ph.label}
              </label>
            ))}
          </div>
          <div style={{ fontSize: 10, color: "#aaa", marginTop: 4 }}>Leer lassen, wenn keine Phasen-Unterteilung gewünscht.</div>
        </div>
      )}
    </>
  );
}

export
function Projects({ data, update, saveAll, modal, setModal, onSelect, setPrintContent, currentUser }) {
  const canSeeQuotes   = !currentUser || currentUser.role === "admin" || !currentUser.permissions || currentUser.permissions.includes("quotes");
  const canSeeInvoices = !currentUser || currentUser.role === "admin" || !currentUser.permissions || currentUser.permissions.includes("invoices");
  const clients = (data.persons || []).filter(p => p.isAuftraggeber);
  const emptyForm = { number: "", name: "", clientId: "", category: "Direktauftrag", billingType: "stundensatz", hourlyRate: data.settings.defaultHourlyRate, budget: 0, status: "aktiv", description: "", startDate: "", enabledPhases: [], budgetHours: 0, linkedQuotes: [], positions: [], customPhases: [], projectContacts: [], internalMembers: [] };
  const [form, setForm] = useState(emptyForm);
  const [filter, setFilter] = useState(() => { const cid = window.__navClientId || ""; window.__navClientId = null; return { category: "", status: "", search: "", clientId: cid }; });
  const [groupBy, setGroupBy] = useState("status");
  const [compact, setCompact] = useState(true);
  const { askConfirm, ConfirmModalEl } = useConfirm();
  const [showAddBeteiligter, setShowAddBeteiligter] = useState(false);
  const [editingContactId, setEditingContactId] = useState(null);
  const [addContactId, setAddContactId] = useState("");
  const [addPersonIds, setAddPersonIds] = useState([]);

  const nextProjectNumber = () => {
    const fmt = data.settings.projectNumberFormat || "YYYY/NN";
    const currentYear = new Date().getFullYear();
    const lastSeq = data.settings.lastProjectYear === currentYear ? (data.settings.lastProjectSeq || 0) : 0;
    return applyProjectNumberFormat(fmt, lastSeq >= 99 ? 1 : lastSeq + 1);
  };

  const saveProject = () => {
    if (!form.name?.trim()) { alert("Bitte einen Projektnamen eingeben."); return; }
    const isNew = !modal?.id;
    const projects = isNew
      ? [...data.projects, { ...form, id: generateId(), createdAt: new Date().toISOString() }]
      : data.projects.map(p => p.id === modal.id ? { ...p, ...form } : p);
    let settings = data.settings;
    if (isNew) {
      const seq = parseSeqFromNumber(form.number, data.settings.projectNumberFormat || "YYYY/NN");
      if (seq !== null) settings = { ...data.settings, lastProjectSeq: seq, lastProjectYear: new Date().getFullYear() };
    }
    saveAll({ ...data, projects, settings });
    setModal(null);
  };

  const resetBeteiligter = () => { setShowAddBeteiligter(false); setEditingContactId(null); setAddContactId(""); setAddPersonIds([]); };
  const openEdit = (p, e) => { e?.stopPropagation(); setForm({ ...emptyForm, ...p, enabledPhases: p.enabledPhases || [], projectContacts: p.projectContacts || [] }); resetBeteiligter(); setModal({ type: "project", id: p.id }); };
  const openNew = () => { setForm({ ...emptyForm, number: nextProjectNumber(), startDate: new Date().toISOString().slice(0, 10) }); resetBeteiligter(); setModal({ type: "project" }); };
  const del = async (id, e) => { e?.stopPropagation(); if (await askConfirm("Projekt wirklich löschen?")) update("projects", data.projects.filter(p => p.id !== id)); };

  // Projekt aus Offerte erstellen
  const createFromQuote = (quote) => {
    const linkedQuotes = [{ quoteId: quote.id, role: "Hauptofferte" }];
    const derived = deriveQuoteBudget(linkedQuotes, data.quotes || [], data.settings.roles || []);
    const newProj = {
      ...emptyForm,
      id: generateId(),
      number: nextProjectNumber(),
      name: (() => { const proj = data.projects.find(p => p.id === quote.projectId); return proj?.name || "Projekt aus Offerte " + quote.number; })(),
      clientId: quote.clientId || "",
      startDate: new Date().toISOString().slice(0, 10),
      billingType: "pauschal",
      hourlyRate: quote.mode === "sia" ? (quote.sia?.stundenansatz || data.settings.defaultHourlyRate) : data.settings.defaultHourlyRate,
      linkedQuotes,
      budgetHours: derived.budgetHours,
      phasesBudget: derived.phasesBudget,
      enabledPhases: derived.enabledPhases,
      createdAt: new Date().toISOString(),
    };
    const seq = parseSeqFromNumber(newProj.number, data.settings.projectNumberFormat || "YYYY/NN");
    const newSettings = seq !== null ? { ...data.settings, lastProjectSeq: seq, lastProjectYear: new Date().getFullYear() } : data.settings;
    saveAll({ ...data, projects: [...data.projects, newProj], settings: newSettings });
    return newProj.id;
  };
  const projectMinutes = (id) => data.timeEntries.filter(e => e.projectId === id).reduce((s, e) => s + (e.minutes || 0), 0);

  const [sort, setSort] = useState({ col: "number", dir: 1 });
  const toggleSort = (col) => setSort(s => ({ col, dir: s.col === col ? -s.dir : 1 }));
  const SortTh = ({ col, children, style }) => (
    <th onClick={() => toggleSort(col)} style={{ cursor: "pointer", userSelect: "none", whiteSpace: "nowrap", ...style }}>
      {children} <span style={{ color: sort.col === col ? "#b07848" : "#ccc", fontSize: 10 }}>{sort.col === col ? (sort.dir === 1 ? "▲" : "▼") : "⇅"}</span>
    </th>
  );

  const filtered = data.projects.filter(p => {
    if (filter.clientId && p.clientId !== filter.clientId) return false;
    if (filter.category && p.category !== filter.category) return false;
    if (filter.status && p.status !== filter.status) return false;
    if (filter.search) {
      const q = filter.search.toLowerCase();
      const client = clients.find(c => c.id === p.clientId);
      return (p.name || "").toLowerCase().includes(q) || (p.number || "").toLowerCase().includes(q) || (client?.name || "").toLowerCase().includes(q) || (p.description || "").toLowerCase().includes(q);
    }
    return true;
  });
  const sorted = [...filtered].sort((a, b) => {
    let va, vb;
    if (sort.col === "number") { va = a.number || ""; vb = b.number || ""; }
    else if (sort.col === "name") { va = a.name || ""; vb = b.name || ""; }
    else if (sort.col === "category") { va = a.category || ""; vb = b.category || ""; }
    else if (sort.col === "client") { va = (clients.find(c => c.id === a.clientId)?.name || ""); vb = (clients.find(c => c.id === b.clientId)?.name || ""); }
    else if (sort.col === "hours") { va = projectMinutes(a.id); vb = projectMinutes(b.id); }
    else if (sort.col === "status") { va = a.status || ""; vb = b.status || ""; }
    else { va = ""; vb = ""; }
    return typeof va === "number" ? (va - vb) * sort.dir : va.localeCompare(vb) * sort.dir;
  });

  // Gruppieren
  const groupedProjects = (() => {
    if (groupBy === "status") {
      const groups = {};
      sorted.forEach(p => { const key = p.status || "unbekannt"; if (!groups[key]) groups[key] = []; groups[key].push(p); });
      const order = ["aktiv", "pausiert", "abgeschlossen", "unbekannt"];
      return order.filter(k => groups[k]).map(key => ({ key, label: key.charAt(0).toUpperCase() + key.slice(1), items: groups[key] }));
    }
    if (groupBy === "category") {
      const groups = {};
      sorted.forEach(p => { const key = p.category || "—"; if (!groups[key]) groups[key] = []; groups[key].push(p); });
      return Object.entries(groups).sort((a, b) => a[0].localeCompare(b[0])).map(([key, items]) => ({ key, label: key, items }));
    }
    if (groupBy === "client") {
      const groups = {};
      sorted.forEach(p => { const key = p.clientId || "__none__"; const label = clients.find(c => c.id === p.clientId)?.name || "Kein Auftraggeber"; if (!groups[key]) groups[key] = { label, items: [] }; groups[key].items.push(p); });
      return Object.entries(groups).sort((a, b) => a[1].label.localeCompare(b[1].label)).map(([key, val]) => ({ key, label: val.label, items: val.items }));
    }
    if (groupBy === "year") {
      const groups = {};
      sorted.forEach(p => { const key = (p.startDate || p.createdAt || "").slice(0, 4) || "—"; if (!groups[key]) groups[key] = []; groups[key].push(p); });
      return Object.entries(groups).sort((a, b) => b[0].localeCompare(a[0])).map(([key, items]) => ({ key, label: key, items }));
    }
    return [{ key: "all", label: "", items: sorted }];
  })();

  return (
    <div>
      {ConfirmModalEl}
      <Header title="Projekte" action={
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn btn-ghost" onClick={() => setPrintContent({ type: "projectsOverview", projects: data.projects, data })}>Gesamt-PDF</button>
          <button className="btn btn-primary" onClick={openNew}>+ Neues Projekt</button>
        </div>
      } />

      <div className="filter-bar">
        <input className="pill" value={filter.search} onChange={e => setFilter({ ...filter, search: e.target.value })} placeholder="Suchen…" style={{ minWidth: 180 }} />
        <select className="pill" value={filter.clientId} onChange={e => setFilter({ ...filter, clientId: e.target.value })}>
          <option value="">Alle Auftraggeber</option>
          {clients.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
        </select>
        <select className="pill" value={filter.category} onChange={e => setFilter({ ...filter, category: e.target.value })}>
          <option value="">Alle Kategorien</option>
          {PROJECT_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
        </select>
        <select className="pill" value={filter.status} onChange={e => setFilter({ ...filter, status: e.target.value })}>
          <option value="">Alle Status</option>
          {["aktiv", "pausiert", "abgeschlossen"].map(s => <option key={s} value={s}>{s}</option>)}
        </select>
      </div>

      <div className="filter-bar">
        <span className="filter-label">GRUPPIEREN:</span>
        {[{ id: "none", label: "Keine" }, { id: "status", label: "Status" }, { id: "category", label: "Kategorie" }, { id: "client", label: "Auftraggeber" }, { id: "year", label: "Jahr" }].map(g => (
          <button key={g.id} className={`pill${groupBy === g.id ? " active" : ""}`} onClick={() => setGroupBy(g.id)}>{g.label}</button>
        ))}
      </div>

      <div className="card" style={{ padding: 0 }}>
        <table>
          <thead><tr>
            <SortTh col="number" style={{ width: 90 }}>Nr.</SortTh>
            <SortTh col="name">Projekt</SortTh>
            <SortTh col="category">Kategorie</SortTh>
            <SortTh col="client">Kunde</SortTh>
            <SortTh col="hours">Stunden</SortTh>
            <SortTh col="status">Status</SortTh>
            <th></th>
          </tr></thead>
          <tbody>
            {sorted.length === 0 && <tr><td colSpan={7} style={{ textAlign: "center", color: "#aaa", padding: 32, fontSize: 13 }}>{data.projects.length === 0 ? "Noch keine Projekte" : "Keine Treffer"}</td></tr>}
            {groupedProjects.map(group => (
              <React.Fragment key={group.key}>
                {groupBy !== "none" && group.label && (
                  <tr>
                    <td colSpan={7} style={{ background: "#f5f0e8", padding: "6px 12px", fontSize: 10, letterSpacing: "0.1em", color: "#888", fontWeight: 600, textTransform: "uppercase", borderTop: "1px solid #e0dbd4" }}>
                      {group.label}
                      <span style={{ float: "right", fontWeight: 400, letterSpacing: 0, textTransform: "none", fontSize: 11, color: "#aaa" }}>
                        {group.items.length} Projekt{group.items.length !== 1 ? "e" : ""}
                      </span>
                    </td>
                  </tr>
                )}
                {group.items.map(p => {
                  const client = clients.find(c => c.id === p.clientId);
                  const usedMins = projectMinutes(p.id);
                  const usedHours = usedMins / 60;
                  const budget = p.budgetHours || 0;
                  const pct = budget > 0 ? (usedHours / budget) * 100 : 0;
                  const barColor = pct >= 100 ? "#8a1a1a" : pct >= 80 ? "#b5621e" : "#2d6a4f";
                  return (
                    <tr key={p.id} onClick={() => onSelect(p.id)} style={{ cursor: "pointer" }}>
                      <td style={{ fontSize: 11, color: "#888", fontWeight: 500 }}>{p.number || "—"}</td>
                      <td><strong>{p.name}</strong>{p.description && <div style={{ fontSize: 11, color: "#aaa", marginTop: 2 }}>{p.description.slice(0, 60)}</div>}</td>
                      <td style={{ fontSize: 12 }}>{p.category || "—"}</td>
                      <td>{client?.name || "—"}</td>
                      <td>
                        <div style={{ fontSize: 12, fontWeight: 500, color: budget > 0 ? barColor : "#1a1a18" }}>
                          {formatHours(usedMins)}{budget > 0 && ` / ${budget}h`}
                        </div>
                        {budget > 0 && (
                          <div style={{ marginTop: 4, height: 4, background: "#ece8e2", borderRadius: 2, width: 80, overflow: "hidden" }}>
                            <div style={{ width: `${Math.min(pct, 100)}%`, height: "100%", background: barColor, borderRadius: 2, transition: "width 0.3s" }} />
                          </div>
                        )}
                      </td>
                      <td><StatusBadge status={p.status} /></td>
                      <td style={{ textAlign: "right", whiteSpace: "nowrap" }}>
                        <button className="btn btn-ghost" style={{ marginRight: 6, padding: "5px 12px", fontSize: 12 }} onClick={(e) => openEdit(p, e)}><span className="material-icons" style={{ fontSize: 16 }}>edit</span></button>
                        <button className="btn btn-danger" style={{ padding: "5px 12px", fontSize: 12 }} onClick={(e) => del(p.id, e)}><span className="material-icons" style={{ fontSize: 16 }}>close</span></button>
                      </td>
                    </tr>
                  );
                })}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>

      {modal?.type === "project" && (() => {
        const projectContacts = form.projectContacts || [];
        const allContacts = (data.persons || []).filter(p => p.isPartner);
        const alreadyAdded = new Set(projectContacts.map(pc => pc.contactId));
        const selectedFirm = allContacts.find(c => c.id === addContactId);

        const addBeteiligte = () => {
          if (!addContactId) return;
          setForm(f => ({ ...f, projectContacts: [...(f.projectContacts || []), { contactId: addContactId, personIds: addPersonIds }] }));
          resetBeteiligter();
        };
        const saveEditedBeteiligte = (contactId) => {
          setForm(f => ({ ...f, projectContacts: (f.projectContacts || []).map(pc => pc.contactId === contactId ? { ...pc, personIds: addPersonIds } : pc) }));
          setEditingContactId(null); setAddPersonIds([]);
        };
        const removeBeteiligte = (contactId) => {
          setForm(f => ({ ...f, projectContacts: (f.projectContacts || []).filter(pc => pc.contactId !== contactId) }));
        };
        const togglePerson = (personId) => setAddPersonIds(prev =>
          prev.includes(personId) ? prev.filter(id => id !== personId) : [...prev, personId]);

        return (
          <Modal title={modal.id ? "Projekt bearbeiten" : "Neues Projekt"} onClose={() => { setModal(null); resetBeteiligter(); }} onSave={saveProject} wide>
            <ProjectEditForm form={form} setForm={setForm} data={data} />

            <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid #ece8e2" }}>
              <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 12 }}>INTERNE PROJEKTBETEILIGUNG</div>
              {(data.employees || []).length === 0
                ? <div style={{ fontSize: 12, color: "#aaa", marginBottom: 10 }}>Noch keine Mitarbeitenden erfasst.</div>
                : <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 4 }}>
                    {(data.employees || []).map(emp => {
                      const checked = (form.internalMembers || []).includes(emp.id);
                      return (
                        <label key={emp.id} style={{ display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontSize: 13, background: checked ? "#f0ede8" : "#faf9f7", border: `1px solid ${checked ? "#c8b89a" : "#ece8e2"}`, borderRadius: 8, padding: "4px 10px" }}>
                          <input type="checkbox" checked={checked} onChange={() => setForm(f => ({ ...f, internalMembers: checked ? (f.internalMembers || []).filter(id => id !== emp.id) : [...(f.internalMembers || []), emp.id] }))} style={{ margin: 0 }} />
                          <span>{emp.name}</span>
                          {emp.role && <span style={{ fontSize: 10, color: "#aaa" }}>{emp.role}</span>}
                        </label>
                      );
                    })}
                  </div>
              }
            </div>

            <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid #ece8e2" }}>
              <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 12 }}>PROJEKTBETEILIGTE</div>

              {projectContacts.map(pc => {
                const firm = allContacts.find(c => c.id === pc.contactId);
                if (!firm) return null;
                const selectedPersons = (firm.contacts || []).filter(p => pc.personIds.includes(p.id));
                const isEditing = editingContactId === pc.contactId;
                return (
                  <div key={pc.contactId} style={{ padding: "10px 0", borderBottom: "1px solid #f5f2ec" }}>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
                      <div>
                        {firm.type && <div style={{ fontSize: 10, color: "#aaa", marginBottom: 2, letterSpacing: "0.06em" }}>{firm.type.toUpperCase()}</div>}
                        <div style={{ fontWeight: 600, fontSize: 13 }}>{firm.name}</div>
                        {!isEditing && (selectedPersons.length > 0
                          ? <div style={{ marginTop: 4, display: "flex", flexWrap: "wrap", gap: 6 }}>
                              {selectedPersons.map(p => <span key={p.id} style={{ fontSize: 11, background: "#f5f2ec", color: "#555", padding: "2px 8px", borderRadius: 10 }}>{p.name}{p.position ? " · " + p.position : ""}</span>)}
                            </div>
                          : <div style={{ fontSize: 11, color: "#aaa", marginTop: 3 }}>Alle Ansprechpartner</div>
                        )}
                      </div>
                      <div style={{ display: "flex", gap: 4 }}>
                        {(firm.contacts || []).length > 0 && !isEditing && (
                          <button className="btn btn-ghost" style={{ fontSize: 11, padding: "3px 8px" }}
                            onClick={() => { setEditingContactId(pc.contactId); setAddPersonIds(pc.personIds || []); setShowAddBeteiligter(false); }}><span className="material-icons" style={{ fontSize: 16 }}>edit</span></button>
                        )}
                        <button className="btn btn-ghost" style={{ fontSize: 11, padding: "3px 8px", color: "#888" }} onClick={() => removeBeteiligte(pc.contactId)}><span className="material-icons" style={{ fontSize: 16 }}>close</span></button>
                      </div>
                    </div>
                    {isEditing && (
                      <div style={{ marginTop: 8 }}>
                        <div style={{ fontSize: 11, color: "#888", marginBottom: 6 }}>Personen (leer = alle):</div>
                        {(firm.contacts || []).map(p => (
                          <label key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, marginBottom: 4 }}>
                            <input type="checkbox" checked={addPersonIds.includes(p.id)} onChange={() => togglePerson(p.id)} />
                            <span>{p.name}</span>
                            {p.position && <span style={{ fontSize: 11, color: "#888" }}>{p.position}</span>}
                          </label>
                        ))}
                        <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
                          <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={() => saveEditedBeteiligte(pc.contactId)}>Speichern</button>
                          <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => { setEditingContactId(null); setAddPersonIds([]); }}>Abbrechen</button>
                        </div>
                      </div>
                    )}
                  </div>
                );
              })}

              {projectContacts.length === 0 && !showAddBeteiligter && allContacts.length === 0 && (
                <div style={{ fontSize: 12, color: "#aaa", marginBottom: 10 }}>Keine Partner erfasst. Zuerst unter «Personen» Partner hinzufügen.</div>
              )}

              {showAddBeteiligter ? (
                <div style={{ marginTop: 12 }}>
                  <select value={addContactId} onChange={e => { setAddContactId(e.target.value); setAddPersonIds([]); }} style={{ width: "100%", height: 34, marginBottom: 10 }}>
                    <option value="">— Firma wählen —</option>
                    {allContacts.filter(c => !alreadyAdded.has(c.id)).sort((a, b) => a.name.localeCompare(b.name)).map(c => (
                      <option key={c.id} value={c.id}>{c.type ? "[" + c.type + "] " : ""}{c.name}</option>
                    ))}
                  </select>
                  {selectedFirm && (selectedFirm.contacts || []).length > 0 && (
                    <div style={{ marginBottom: 10 }}>
                      <div style={{ fontSize: 11, color: "#888", marginBottom: 6 }}>Personen (leer = alle):</div>
                      {(selectedFirm.contacts || []).map(p => (
                        <label key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, marginBottom: 4 }}>
                          <input type="checkbox" checked={addPersonIds.includes(p.id)} onChange={() => togglePerson(p.id)} />
                          <span>{p.name}</span>
                          {p.position && <span style={{ fontSize: 11, color: "#888" }}>{p.position}</span>}
                        </label>
                      ))}
                    </div>
                  )}
                  <div style={{ display: "flex", gap: 8 }}>
                    <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={addBeteiligte} disabled={!addContactId}>Hinzufügen</button>
                    <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={resetBeteiligter}>Abbrechen</button>
                  </div>
                </div>
              ) : allContacts.length > 0 && (
                <button className="btn btn-ghost" style={{ fontSize: 12, marginTop: 8 }} onClick={() => setShowAddBeteiligter(true)}>+ Beteiligten hinzufügen</button>
              )}
            </div>
          </Modal>
        );
      })()}
    </div>
  );
}

export
function ProjectDetail({ data, update, saveAll, projectId, onBack, setPrintContent, modal, setModal, currentUser }) {
  const isAdmin        = !currentUser || currentUser.role === "admin" || !currentUser.permissions;
  const canSeeQuotes   = isAdmin || currentUser.permissions.includes("quotes");
  const canSeeInvoices = isAdmin || currentUser.permissions.includes("invoices");
  const canSeeMitarbeiter = isAdmin || currentUser.permissions.includes("mitarbeiter");
  const project = data.projects.find(p => p.id === projectId);
  const emptySettingsForm = { number: "", name: "", clientId: "", category: "Direktauftrag", billingType: "stundensatz", hourlyRate: data.settings.defaultHourlyRate, budget: 0, status: "aktiv", description: "", startDate: "", enabledPhases: [], budgetHours: 0, linkedQuotes: [], positions: [], customPhases: [], internalMembers: [] };
  const [budgetModal, setBudgetModal] = useState(false);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [budgetChartView, setBudgetChartView] = useState("einzeln");
  const [phaseChartView, setPhaseChartView] = useState("einzeln");
  const [settingsForm, setSettingsForm] = useState(() => project ? { ...emptySettingsForm, ...project, enabledPhases: project.enabledPhases || [] } : emptySettingsForm);
  const [showAddBeteiligter, setShowAddBeteiligter] = useState(false);
  const [editingContactId, setEditingContactId] = useState(null);
  const [addContactId, setAddContactId] = useState("");
  const [addPersonIds, setAddPersonIds] = useState([]);

  const openSettings = () => {
    setSettingsForm({ ...emptySettingsForm, ...project, enabledPhases: project.enabledPhases || [], projectContacts: project.projectContacts || [], internalMembers: project.internalMembers || [] });
    setShowAddBeteiligter(false);
    setEditingContactId(null);
    setAddContactId("");
    setAddPersonIds([]);
    setSettingsOpen(true);
  };
  const saveSettings = () => {
    if (!settingsForm.name?.trim()) { alert("Bitte einen Projektnamen eingeben."); return; }
    update("projects", data.projects.map(p => p.id === projectId ? { ...p, ...settingsForm } : p));
    setSettingsOpen(false);
  };

  const buildNachtragPositions = (linked, existingPos) => {
    const manual = (existingPos || []).filter(p => !p.quoteId);
    const nachtrag = linked.filter(lq => lq.role === "Nachtrag").map((lq, idx) => {
      const existing = (existingPos || []).find(p => p.quoteId === lq.quoteId);
      const q = (data.quotes || []).find(x => x.id === lq.quoteId);
      const sd = deriveQuoteBudget([lq], data.quotes || [], data.settings.roles || []);
      return { code: existing?.code || `N${idx + 1}`, label: existing !== undefined ? existing.label : (q?.projectName || q?.number || ""), enabledPhases: existing?.enabledPhases || sd.enabledPhases, quoteId: lq.quoteId };
    });
    return [...manual, ...nachtrag];
  };

  const [budgetForm, setBudgetForm] = useState(() => ({
    budgetHours: project?.budgetHours || 0,
    budgetAmount: project?.budgetAmount || 0,
    linkedQuotes: project ? migrateLinkedQuotes(project) : [],
    phasesBudget: project?.phasesBudget || [],
    enabledPhases: project?.enabledPhases || [],
    billingType: project?.billingType || "stundensatz",
    positions: project?.positions || [],
  }));

  if (!project) return <div style={{ padding: 32 }}>Projekt nicht gefunden. <button className="btn btn-ghost" onClick={onBack}>Zurück</button></div>;

  const openBudgetModal = () => {
    const linked = migrateLinkedQuotes(project);
    setBudgetForm({
      budgetHours: project.budgetHours || 0,
      budgetAmount: project.budgetAmount || 0,
      linkedQuotes: linked,
      phasesBudget: project.phasesBudget || [],
      enabledPhases: project.enabledPhases || [],
      billingType: project.billingType || "stundensatz",
      positions: buildNachtragPositions(linked, project.positions || []),
    });
  };

  const addLinkedQuote = (quoteId) => {
    if (!quoteId || budgetForm.linkedQuotes.some(lq => lq.quoteId === quoteId)) return;
    const isFirst = budgetForm.linkedQuotes.length === 0;
    const newLq = { quoteId, role: isFirst ? "Hauptofferte" : "Nachtrag" };
    const newLinked = [...budgetForm.linkedQuotes, newLq];
    const d = deriveQuoteBudget(newLinked, data.quotes || [], data.settings.roles || []);
    const q = (data.quotes || []).find(x => x.id === quoteId);
    setBudgetForm(f => {
      const positions = isFirst ? (f.positions || []) : buildNachtragPositions(newLinked, f.positions || []);
      return {
        ...f,
        linkedQuotes: newLinked,
        budgetHours: d.budgetHours,
        budgetAmount: d.budgetAmount,
        phasesBudget: d.phasesBudget,
        enabledPhases: isFirst ? [...new Set([...(f.enabledPhases || []), ...d.enabledPhases])] : (f.enabledPhases || []),
        billingType: isFirst ? (q?.mode === "manual" ? "stundensatz" : "pauschal") : f.billingType,
        positions,
      };
    });
  };
  const removeLinkedQuote = (quoteId) => {
    const newLinked = budgetForm.linkedQuotes.filter(lq => lq.quoteId !== quoteId);
    const d = newLinked.length > 0 ? deriveQuoteBudget(newLinked, data.quotes || [], data.settings.roles || []) : { budgetHours: 0, budgetAmount: 0, phasesBudget: [] };
    setBudgetForm(f => ({
      ...f, linkedQuotes: newLinked, budgetHours: d.budgetHours, budgetAmount: d.budgetAmount, phasesBudget: d.phasesBudget,
      positions: buildNachtragPositions(newLinked, f.positions || []),
    }));
  };
  const changeLinkedRole = (quoteId, role) => setBudgetForm(f => {
    const newLinked = f.linkedQuotes.map(lq => lq.quoteId === quoteId ? { ...lq, role } : lq);
    return { ...f, linkedQuotes: newLinked, positions: buildNachtragPositions(newLinked, f.positions || []) };
  });

  const saveBudget = () => {
    update("projects", data.projects.map(p => p.id === projectId ? { ...p, ...budgetForm } : p));
    setBudgetModal(false);
  };

  const clients = (data.persons || []).filter(p => p.isAuftraggeber);
  const client = clients.find(c => c.id === project.clientId);
  const entries = data.timeEntries.filter(e => e.projectId === projectId).sort((a, b) => (b.date || "").localeCompare(a.date || ""));
  const totalMinutes = entries.reduce((s, e) => s + (e.minutes || 0), 0);
  const totalHours = totalMinutes / 60;
  const billingType = project.billingType || project.type || "stundensatz";
  const isNonBilling = NON_BILLING_CATEGORIES.includes(project.category);
  const isCostControl = billingType !== "stundensatz" || isNonBilling;
  const totalAmount = billingType === "stundensatz" ? (totalMinutes / 60) * project.hourlyRate : project.budget;
  const budget = project.budgetHours || 0;
  const budgetPct = budget > 0 ? (totalHours / budget) * 100 : 0;
  const budgetColor = budgetPct >= 100 ? "#8a1a1a" : budgetPct >= 80 ? "#b5621e" : "#2d6a4f";
  const invoicedMinutes = entries.filter(e => e.invoiceId).reduce((s, e) => s + (e.minutes || 0), 0);
  const openMinutes = totalMinutes - invoicedMinutes;

  // Phasen-Auswertung (nur aktivierte Phasen; aufgeteilt nach HO + Positionen)
  const positions = project.positions || [];
  const phaseStats = (project.enabledPhases || []).map(phaseId => {
    const phase = SIA_PHASES.find(p => p.id === phaseId);
    const phaseEntries = entries.filter(e => e.phaseId === phaseId);
    const hoMins = phaseEntries.filter(e => !e.positionId).reduce((s, e) => s + (e.minutes || 0), 0);
    const totalPhaseMins = phaseEntries.reduce((s, e) => s + (e.minutes || 0), 0);
    const phaseBudget = (project.phasesBudget || []).find(pb => pb.id === phaseId)?.hours || 0;
    const byPosition = positions.map(pos => ({
      code: pos.code, label: pos.label,
      mins: phaseEntries.filter(e => e.positionId === pos.code).reduce((s, e) => s + (e.minutes || 0), 0),
    })).filter(p => p.mins > 0);
    return { ...phase, minutes: totalPhaseMins, hoMins, hours: totalPhaseMins / 60, phaseBudget, percent: totalMinutes > 0 ? (totalPhaseMins / totalMinutes) * 100 : 0, entries: phaseEntries.length, byPosition };
  });
  const unassignedMins = entries.filter(e => !e.phaseId).reduce((s, e) => s + (e.minutes || 0), 0);
  const customPhaseStats = (project.customPhases || []).map(cp => {
    const mins = entries.filter(e => e.phaseId === cp.id).reduce((s, e) => s + (e.minutes || 0), 0);
    return { id: cp.id, label: cp.label, mins };
  });
  const customMinsTotal = customPhaseStats.reduce((s, cp) => s + cp.mins, 0);

  // Rechnungen zum Projekt
  const projectInvoices = data.invoices.filter(i => i.projectId === projectId);

  // Offen-Anzeige: nur bei Stundensatz-Projekten oder wenn ein Nachtrag stundensatz-basiert ist
  const hasStundensatzNachtrag = positions.some(pos => {
    if (!pos.quoteId) return false;
    const q = (data.quotes || []).find(q => q.id === pos.quoteId);
    return q?.mode === "manual";
  });
  const showOffen = billingType === "stundensatz" || hasStundensatzNachtrag;
  const paidInvoices = projectInvoices.filter(i => i.status === "bezahlt");
  const openInvoices = projectInvoices.filter(i => i.status === "gesendet" || i.status === "überfällig");
  const paidTotal = paidInvoices.reduce((s, i) => s + (i.total || 0), 0);
  const openTotal = openInvoices.reduce((s, i) => s + (i.total || 0), 0);

  return (
    <div>
      <button className="btn btn-ghost" onClick={onBack} style={{ marginBottom: 18, padding: "6px 14px", fontSize: 12 }}>← Alle Projekte</button>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 24 }}>
        <div>
          <div style={{ fontSize: 11, letterSpacing: "0.1em", color: "#888", marginBottom: 6 }}>{project.number && <span style={{ color: "#b07848", marginRight: 8 }}>{project.number}</span>}{(project.category || "—").toUpperCase()} · {client?.name || "Kein Auftraggeber"}</div>
          <h1 style={{ fontFamily: "'Playfair Display', serif", fontSize: 34, fontWeight: 400, letterSpacing: "-0.01em" }}>{project.name}</h1>
          {project.description && <div style={{ fontSize: 13, color: "#666", marginTop: 6, maxWidth: 600 }}>{project.description}</div>}
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          {isAdmin && <button className="btn btn-ghost" onClick={openSettings}>Einstellungen</button>}
          {canSeeQuotes && <button className="btn btn-ghost" onClick={() => { openBudgetModal(); setBudgetModal(true); }}>Budget / Offerten</button>}
          <button className="btn btn-ghost" onClick={() => setPrintContent({ type: "projectDetail", project, client, entries, phaseStats, unassignedMins, totalMinutes, totalAmount, billingType, invoices: projectInvoices, data })}>PDF</button>
        </div>
      </div>

      <div className="responsive-grid-4" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16, marginBottom: 28 }}>
        {(() => {
          const internalRate = project.hourlyRate || data.settings.defaultHourlyRate || 0;
          const internalCost = (totalMinutes / 60) * internalRate;
          if (isCostControl) return [
            { label: "STUNDEN TOTAL", value: formatHours(totalMinutes), sub: budget > 0 ? `von ${budget}h Soll` : `${entries.length} Einträge`, color: "#b07848" },
            isNonBilling
              ? { label: "NICHT VERRECHENBAR", value: project.category, sub: "Stunden fliessen ins Studio-Budget", color: "#aaa" }
              : { label: "INTERNER AUFWAND", value: internalRate > 0 ? formatCHF(internalCost) : "—", sub: internalRate > 0 ? `bei ${formatCHF(internalRate)}/h · Kostenkontrolle` : "Kein Stundensatz hinterlegt", color: "#888" },
            { label: "BUDGET GENUTZT", value: budget > 0 ? `${Math.min(Math.round(budgetPct), 999)}%` : "—", sub: budget > 0 ? `${totalHours.toFixed(1)}h / ${budget}h` : "Kein Stundenbudget", color: budget > 0 ? budgetColor : "#aaa" },
            { label: "STATUS", value: <StatusBadge status={project.status} />, color: "#b07848" },
          ];
          return [
            { label: "STUNDEN TOTAL", value: formatHours(totalMinutes), sub: budget > 0 ? `von ${budget}h Soll` : `${entries.length} Einträge`, color: "#b07848" },
            { label: "VERRECHNET", value: formatHours(invoicedMinutes), sub: formatCHF(invoicedMinutes / 60 * project.hourlyRate), color: "#2d6a4f" },
            { label: "OFFEN", value: formatHours(openMinutes), sub: formatCHF(openMinutes / 60 * project.hourlyRate), color: openMinutes > 0 ? "#b5621e" : "#aaa" },
            { label: "STATUS", value: <StatusBadge status={project.status} />, color: "#b07848" },
          ];
        })().map(c => (
          <div key={c.label} className="card" style={{ borderTop: `3px solid ${c.color}` }}>
            <div style={{ fontSize: 10, color: "#888", letterSpacing: "0.12em", marginBottom: 10 }}>{c.label}</div>
            <div style={{ fontSize: 22, fontFamily: "'Playfair Display', serif", fontWeight: 700 }}>{c.value}</div>
            {c.sub && <div style={{ fontSize: 11, color: "#aaa", marginTop: 4 }}>{c.sub}</div>}
          </div>
        ))}
      </div>

      {/* Projektbeteiligte */}
      {(project.projectContacts || []).length > 0 && (
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center", marginBottom: 20 }}>
          <span style={{ fontSize: 10, color: "#aaa", letterSpacing: "0.1em", fontWeight: 600, flexShrink: 0 }}>BETEILIGTE</span>
          {(project.projectContacts || []).map(pc => {
            const firm = (data.persons || []).find(p => p.id === pc.contactId);
            if (!firm) return null;
            const selectedPersons = (firm.contacts || []).filter(p => (pc.personIds || []).includes(p.id));
            return (
              <div key={pc.contactId} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--surface2)", border: "1px solid var(--border)", borderRadius: 4, padding: "4px 10px" }}>
                <span style={{ fontSize: 12, fontWeight: 600, color: "var(--text2)" }}>{firm.name}</span>
                {firm.type && <span style={{ fontSize: 10, color: "#aaa" }}>{firm.type}</span>}
                {selectedPersons.length > 0 && (
                  <span style={{ fontSize: 11, color: "var(--text3)", borderLeft: "1px solid var(--border)", paddingLeft: 6 }}>
                    {selectedPersons.map(p => p.name).join(", ")}
                  </span>
                )}
              </div>
            );
          })}
        </div>
      )}

      {/* Stundenbudget-Anzeige */}
      {(() => {
        const linked = migrateLinkedQuotes(project);
        const derived = linked.length > 0 ? deriveQuoteBudget(linked, data.quotes || [], data.settings.roles || []) : null;
        const amountBudget = project.budgetAmount || derived?.budgetAmount || 0;
        const invoicedAmount = projectInvoices.reduce((s, i) => s + (i.sub || 0), 0);

        if (budget === 0 && amountBudget === 0) return (
          <div className="card" style={{ marginBottom: 20, borderLeft: "4px solid #ddd", padding: "12px 20px" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <span style={{ fontSize: 12, color: "#aaa" }}>Kein Budget gesetzt</span>
              <button className="btn btn-ghost" style={{ fontSize: 11, padding: "4px 12px" }}
                onClick={() => { openBudgetModal(); setBudgetModal(true); }}>
                + Budget / Offerte verknüpfen
              </button>
            </div>
          </div>
        );

        // Pro Offerte einzeln berechnen
        const quoteRows = linked.map(lq => {
          const q = (data.quotes || []).find(x => x.id === lq.quoteId);
          if (!q) return null;
          const roles = q.quoteRoles || data.settings.roles || [];
          let qHours = 0, qAmount = 0;
          if (q.mode === "sia") {
            const calc = calcSIAHours(q.sia?.baukosten, q.sia?.schwierigkeit, q.sia?.phases || []);
            qHours = calc.total || 0;
            qAmount = qHours * (q.sia?.stundenansatz || 0);
          } else if (q.mode === "manual") {
            const calc = calcManualHours(q.manualPhases || [], roles);
            qHours = calc.totalHours || 0;
            qAmount = calc.totalAmount || 0;
          } else if (q.mode === "free") {
            qAmount = (q.freeItems || []).reduce((s, it) => s + (it.qty * it.price), 0);
          }
          return { lq, q, qHours: Math.round(qHours * 10) / 10, qAmount: Math.round(qAmount) };
        }).filter(Boolean);

        const isNachtrag = (role) => role && role !== "Hauptofferte";
        const nachtragRows = quoteRows.filter(r => isNachtrag(r.lq.role));
        const sortedQuoteRows = [
          ...quoteRows.filter(r => !isNachtrag(r.lq.role)),
          ...nachtragRows,
        ];
        const hauptTotal = quoteRows.filter(r => !isNachtrag(r.lq.role)).reduce((s, r) => s + r.qHours, 0);
        const nachtragTotal = nachtragRows.reduce((s, r) => s + r.qHours, 0);

        // resolve per-row posCode from project.positions (custom codes)
        const rowsWithPos = sortedQuoteRows.map(({ lq, q, qHours, qAmount }) => {
          const isNT = isNachtrag(lq.role);
          const pos = isNT ? positions.find(p => p.quoteId === lq.quoteId) : null;
          const ntIdx = isNT ? nachtragRows.findIndex(r => r.lq.quoteId === lq.quoteId) : -1;
          const posCode = pos?.code || (isNT ? `N${ntIdx + 1}` : null);
          const usedMins = isNT
            ? entries.filter(e => posCode && e.positionId === posCode).reduce((s, e) => s + (e.minutes || 0), 0)
            : entries.filter(e => !e.positionId).reduce((s, e) => s + (e.minutes || 0), 0);
          return { lq, q, qHours, qAmount, isNT, posCode, usedH: usedMins / 60 };
        });
        const totalBudgetH = rowsWithPos.filter(r => r.qHours > 0).reduce((s, r) => s + r.qHours, 0);
        const remaining = Math.max(0, budget - totalHours);

        return (
          <div className="card" style={{ marginBottom: 20, borderLeft: `4px solid ${budgetColor}`, padding: "16px 20px" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <div style={{ fontSize: 11, letterSpacing: "0.1em", color: "#888" }}>HONORAR- UND STUNDENBUDGET</div>
                {quoteRows.length > 1 && (
                  <div style={{ display: "flex", gap: 1, background: "#ece8e2", borderRadius: 4, padding: 2 }}>
                    {[["einzeln", "Einzeln"], ["gesamt", "Gesamt"]].map(([v, l]) => (
                      <button key={v} onClick={() => setBudgetChartView(v)} style={{ fontSize: 10, padding: "2px 9px", borderRadius: 3, border: "none", cursor: "pointer", background: budgetChartView === v ? "#fff" : "transparent", color: budgetChartView === v ? "#1a1a18" : "#888", fontWeight: budgetChartView === v ? 600 : 400 }}>{l}</button>
                    ))}
                  </div>
                )}
              </div>
              {canSeeInvoices && <div style={{ fontSize: 11, color: "#888" }}>
                Total verrechnet: <strong style={{ color: "#1a1a18" }}>{formatCHF(invoicedAmount)}</strong>
                {amountBudget > 0 && <span> / {formatCHF(amountBudget)}</span>}
              </div>}
            </div>

            {budgetChartView === "gesamt" && quoteRows.length > 1 ? (
              <div>
                {/* Segmentierter Balken */}
                <div style={{ display: "flex", height: 28, borderRadius: 4, overflow: "hidden", marginBottom: 10, background: "#ece8e2" }}>
                  {rowsWithPos.filter(r => r.qHours > 0).map((r, idx, arr) => {
                    const segW = (r.qHours / totalBudgetH) * 100;
                    const fillW = Math.min((r.usedH / r.qHours) * 100, 100);
                    const isOver = r.usedH > r.qHours;
                    const segBg = r.isNT ? "#c8d8ee" : "#b8dcc8";
                    const fillColor = isOver ? "#8a1a1a" : (r.isNT ? "#1a4e8a" : "#2d6a4f");
                    const label = r.posCode || (r.isNT ? "NT" : "HO");
                    return (
                      <div key={r.lq.quoteId} style={{ width: `${segW}%`, position: "relative", background: segBg, borderRight: idx < arr.length - 1 ? "2px solid #fff" : "none", overflow: "hidden", flexShrink: 0 }}>
                        <div style={{ width: `${fillW}%`, height: "100%", background: fillColor, transition: "width 0.4s" }} />
                        {segW > 6 && <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: "#fff", pointerEvents: "none", textShadow: "0 1px 2px rgba(0,0,0,0.4)" }}>{label}</div>}
                      </div>
                    );
                  })}
                </div>

                {/* Legende */}
                <div style={{ display: "flex", gap: 14, flexWrap: "wrap", marginBottom: 12 }}>
                  {rowsWithPos.map(r => {
                    const color = r.isNT ? "#1a4e8a" : "#2d6a4f";
                    const label = r.posCode || (r.isNT ? "NT" : "HO");
                    const isOver = r.usedH > r.qHours && r.qHours > 0;
                    return (
                      <div key={r.lq.quoteId} style={{ display: "flex", alignItems: "center", gap: 5, fontSize: 11 }}>
                        <div style={{ width: 10, height: 10, borderRadius: 2, background: isOver ? "#8a1a1a" : color, flexShrink: 0 }} />
                        <span style={{ color: "#888" }}>{label}:</span>
                        <span style={{ fontWeight: 600, color: isOver ? "#8a1a1a" : color }}>{r.usedH.toFixed(1)}h</span>
                        {r.qHours > 0 && <span style={{ color: "#aaa", fontSize: 10 }}>/ {r.qHours}h</span>}
                        {r.qAmount > 0 && <span style={{ color: "#bbb", fontSize: 10 }}> · {formatCHF(r.qAmount)}</span>}
                      </div>
                    );
                  })}
                </div>

                {/* Gesamt-Summe */}
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 12, paddingTop: 10, borderTop: "1px solid var(--border2)" }}>
                  <div style={{ color: "#888", display: "flex", gap: 16 }}>
                    <span>Verbraucht: <strong style={{ color: budgetColor }}>{totalHours.toFixed(1)}h</strong></span>
                    {budget > 0 && <span>Verfügbar: <strong style={{ color: remaining === 0 ? "#8a1a1a" : "#2d6a4f" }}>{remaining.toFixed(1)}h</strong></span>}
                  </div>
                  <span style={{ fontWeight: 700, color: budgetColor }}>
                    {budget > 0 ? `${totalHours.toFixed(1)}h / ${budget}h — ${budgetPct.toFixed(0)}%` : `${totalHours.toFixed(1)}h total`}
                    {budgetPct >= 100 && <span style={{ marginLeft: 6, fontSize: 11, color: "#8a1a1a" }}>⚠ überschritten</span>}
                  </span>
                </div>
              </div>
            ) : (
              <>
                {/* Einzeln-Ansicht */}
                {rowsWithPos.map(({ lq, q, qHours, qAmount, isNT, usedH }) => {
                  const barPct = qHours > 0 ? Math.min((usedH / qHours) * 100, 100) : 0;
                  const barColor = barPct >= 100 ? "#8a1a1a" : barPct >= 80 ? "#b5621e" : isNT ? "#1a4e8a" : "#2d6a4f";
                  return (
                    <div key={lq.quoteId} style={{ marginBottom: 14, paddingBottom: 14, borderBottom: "1px solid var(--border2)" }}>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                          <span style={{ fontSize: 10, fontWeight: 600, padding: "2px 7px", borderRadius: 3, letterSpacing: "0.04em", background: isNT ? "#e8f0fa" : "#f0f8f4", color: isNT ? "#1a4e8a" : "#2d6a4f", border: `1px solid ${isNT ? "#b0c8e8" : "#b8dcc8"}` }}>
                            {isNT ? "↪ " : "⬡ "}{lq.role}
                          </span>
                          <span style={{ fontSize: 12, color: "#555" }}>
                            {q.number && <span style={{ color: "#b07848", marginRight: 6, fontWeight: 600 }}>{q.number}</span>}
                            {q.projectName || "—"}
                          </span>
                        </div>
                        <div style={{ fontSize: 12, textAlign: "right" }}>
                          {qHours > 0 && <span style={{ fontWeight: 600, color: barColor }}>{usedH.toFixed(1)}h / {qHours}h<span style={{ fontWeight: 400, color: "#888", marginLeft: 6 }}>({barPct.toFixed(0)}%)</span></span>}
                          {qAmount > 0 && <span style={{ color: "#888", fontSize: 11, marginLeft: 10 }}>{formatCHF(qAmount)}</span>}
                        </div>
                      </div>
                      {qHours > 0 && (
                        <>
                          <div style={{ height: 7, background: "var(--border)", borderRadius: 4, overflow: "hidden" }}>
                            <div style={{ width: `${barPct}%`, height: "100%", background: barColor, borderRadius: 4, transition: "width 0.4s" }} />
                          </div>
                          {barPct >= 80 && <div style={{ fontSize: 10, color: barColor, marginTop: 3 }}>{barPct >= 100 ? "⚠ Budget überschritten" : "⚠ Budget fast ausgeschöpft"}</div>}
                        </>
                      )}
                    </div>
                  );
                })}

                {quoteRows.length > 1 && (
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, paddingTop: 4 }}>
                    <span style={{ color: "#888" }}>
                      {hauptTotal > 0 && <span>Hauptofferte {hauptTotal.toFixed(1)}h</span>}
                      {nachtragTotal > 0 && <span style={{ marginLeft: 8, color: "#1a4e8a" }}>+ Nachträge {nachtragTotal.toFixed(1)}h</span>}
                    </span>
                    <span style={{ fontWeight: 700, color: budgetColor }}>
                      {totalHours.toFixed(1)}h / {budget}h — {budgetPct.toFixed(0)}%
                      {budgetPct >= 100 && <span style={{ marginLeft: 6, fontSize: 11, color: "#8a1a1a" }}>⚠ überschritten</span>}
                    </span>
                  </div>
                )}
              </>
            )}

            {/* Fallback wenn kein linked quote aber manuelles Budget */}
            {quoteRows.length === 0 && budget > 0 && (
              <div>
                <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 4 }}>
                  <span style={{ color: "#888" }}>Stunden</span>
                  <span style={{ fontWeight: 700, color: budgetColor }}>{totalHours.toFixed(1)}h / {budget}h — {budgetPct.toFixed(0)}%</span>
                </div>
                <div style={{ height: 8, background: "#ece8e2", borderRadius: 4, overflow: "hidden" }}>
                  <div style={{ width: `${Math.min(budgetPct, 100)}%`, height: "100%", background: budgetColor, borderRadius: 4 }} />
                </div>
              </div>
            )}
          </div>
        );
      })()}

      {/* Aufwand pro Phase — Hauptofferte + Nachträge in einer Karte */}
      {((project.enabledPhases || []).length > 0 || positions.some(pos => entries.some(e => e.positionId === pos.code)) || customPhaseStats.length > 0) && (() => {
        const linked = migrateLinkedQuotes(project);
        const hauptLq = linked.find(lq => lq.role === "Hauptofferte");
        const hauptQ = hauptLq ? (data.quotes || []).find(q => q.id === hauptLq.quoteId) : null;
        const hoMinsTotal = entries.filter(e => !e.positionId).reduce((s, e) => s + (e.minutes || 0), 0);
        const hasPositions = positions.some(pos => entries.some(e => e.positionId === pos.code));
        return (
          <div className="card" style={{ marginBottom: 20 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16 }}>
              <div style={{ fontSize: 11, letterSpacing: "0.1em", color: "#888" }}>AUFWAND PRO PHASE</div>
              {hasPositions && (project.enabledPhases || []).length > 0 && (
                <div style={{ display: "flex", gap: 1, background: "#ece8e2", borderRadius: 4, padding: 2 }}>
                  {[["einzeln", "Einzeln"], ["gesamt", "Gesamt"]].map(([v, l]) => (
                    <button key={v} onClick={() => setPhaseChartView(v)} style={{ fontSize: 10, padding: "2px 9px", borderRadius: 3, border: "none", cursor: "pointer", background: phaseChartView === v ? "#fff" : "transparent", color: phaseChartView === v ? "#1a1a18" : "#888", fontWeight: phaseChartView === v ? 600 : 400 }}>{l}</button>
                  ))}
                </div>
              )}
            </div>

            {/* ─ Eigene Phasen (immer einzeln) ─ */}
            {customPhaseStats.length > 0 && (
              <div style={{ marginBottom: (project.enabledPhases || []).length > 0 || hasPositions ? 20 : 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
                  <span style={{ fontSize: 10, fontWeight: 700, background: "#fdf0e8", color: "#b5621e", padding: "2px 8px", borderRadius: 3 }}>EIGENE</span>
                  <span style={{ marginLeft: "auto", fontSize: 12, color: "#888" }}>{formatHours(customMinsTotal)}</span>
                </div>
                {customPhaseStats.map(cp => {
                  const pct = customMinsTotal > 0 ? (cp.mins / customMinsTotal) * 100 : 0;
                  return (
                    <div key={cp.id} style={{ marginBottom: 10 }}>
                      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 4 }}>
                        <span style={{ fontWeight: 500 }}>{cp.label}</span>
                        <span style={{ color: cp.mins > 0 ? "#b5621e" : "#bbb", fontWeight: 500 }}>{formatHours(cp.mins)}</span>
                      </div>
                      <div style={{ height: 6, background: "#fdf0e8", borderRadius: 3, overflow: "hidden" }}>
                        <div style={{ width: `${pct}%`, height: "100%", background: cp.mins > 0 ? "#b5621e" : "transparent" }} />
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            {phaseChartView === "gesamt" && hasPositions && (project.enabledPhases || []).length > 0 ? (() => {
              const allPhaseIds = [...new Set([
                ...(project.enabledPhases || []),
                ...positions.flatMap(pos => pos.enabledPhases || []),
                ...entries.map(e => e.phaseId).filter(id => id && SIA_PHASES.some(p => p.id === id)),
              ])];

              const segments = [
                { code: null, label: "HO", color: "#2d6a4f" },
                ...positions.map(pos => ({ code: pos.code, label: pos.code || "NT", color: "#1a4e8a" })),
              ];

              // Phasenbudgets der Nachträge vorberechnen
              const posPhasesBudget = positions.map(pos => {
                const linkedQ = pos.quoteId ? (data.quotes || []).find(q => q.id === pos.quoteId) : null;
                if (!linkedQ) return { code: pos.code, phases: [] };
                const roles = linkedQ.quoteRoles || data.settings.roles || [];
                let phases = [];
                if (linkedQ.mode === "sia") {
                  const calc = calcSIAHours(linkedQ.sia?.baukosten, linkedQ.sia?.schwierigkeit, linkedQ.sia?.phases || []);
                  phases = (calc.phases || []).filter(p => p.hours > 0).map(p => ({ id: p.id, hours: p.hours }));
                } else if (linkedQ.mode === "manual") {
                  phases = (linkedQ.manualPhases || []).filter(ph => ph.enabled).map(ph => {
                    const h = roles.reduce((s, r) => s + (ph.hoursByRole?.[r.id] || 0), 0);
                    return { id: ph.id, hours: Math.round(h * 10) / 10 };
                  }).filter(p => p.hours > 0);
                }
                return { code: pos.code, phases };
              });

              const phaseRows = allPhaseIds.map(phId => {
                const ph = SIA_PHASES.find(p => p.id === phId);
                if (!ph) return null;
                const phEntries = entries.filter(e => e.phaseId === phId);
                const segMins = segments.map(seg => ({
                  ...seg,
                  mins: seg.code === null
                    ? phEntries.filter(e => !e.positionId).reduce((s, e) => s + (e.minutes || 0), 0)
                    : phEntries.filter(e => e.positionId === seg.code).reduce((s, e) => s + (e.minutes || 0), 0),
                }));
                const totalMins = segMins.reduce((s, s2) => s + s2.mins, 0);
                const hoBudget = phaseStats.find(ps => ps.id === phId)?.phaseBudget || 0;
                const ntBudget = posPhasesBudget.reduce((s, p) => s + (p.phases.find(p2 => p2.id === phId)?.hours || 0), 0);
                const totalPhaseBudget = hoBudget + ntBudget;
                const remainingH = totalPhaseBudget > 0 ? Math.max(0, totalPhaseBudget - totalMins / 60) : 0;
                return { ph, segMins, totalMins, totalPhaseBudget, remainingH };
              }).filter(Boolean).filter(r => r.totalPhaseBudget > 0 || r.totalMins > 0);

              const activeSeg = segments.filter(seg =>
                phaseRows.some(r => r.segMins.find(s => s.code === seg.code)?.mins > 0)
              );

              return (
                <div>
                  <div style={{ display: "flex", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
                    {activeSeg.map(seg => (
                      <div key={seg.label} style={{ display: "flex", alignItems: "center", gap: 5, fontSize: 11 }}>
                        <div style={{ width: 10, height: 10, borderRadius: 2, background: seg.color }} />
                        <span style={{ color: "#888" }}>{seg.label}</span>
                      </div>
                    ))}
                    {phaseRows.some(r => r.remainingH > 0) && (
                      <div style={{ display: "flex", alignItems: "center", gap: 5, fontSize: 11 }}>
                        <div style={{ width: 10, height: 10, borderRadius: 2, background: "#d0ccc8" }} />
                        <span style={{ color: "#888" }}>Verfügbar</span>
                      </div>
                    )}
                  </div>
                  {phaseRows.map(({ ph, segMins, totalMins, totalPhaseBudget, remainingH }) => {
                    const totalUsedH = totalMins / 60;
                    const isOver = totalPhaseBudget > 0 && totalUsedH > totalPhaseBudget;
                    const budgetPctPh = totalPhaseBudget > 0 ? (totalUsedH / totalPhaseBudget) * 100 : 0;
                    const labelColor = isOver ? "#8a1a1a" : budgetPctPh >= 80 ? "#b5621e" : "#1a1a18";
                    // Balken-Basis: bei Budget = Budget*60min, sonst = verwendete Minuten
                    const barBaseMins = totalPhaseBudget > 0 ? totalPhaseBudget * 60 : totalMins;
                    const remainingBarMins = totalPhaseBudget > 0 ? Math.max(0, totalPhaseBudget * 60 - totalMins) : 0;
                    return (
                      <div key={ph.id} style={{ marginBottom: 14 }}>
                        <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 5 }}>
                          <span style={{ fontWeight: 500 }}>{ph.label}</span>
                          <span style={{ color: labelColor, fontWeight: 500 }}>
                            {formatHours(totalMins)}
                            {totalPhaseBudget > 0 && (
                              <span style={{ fontWeight: 400, color: "#888", marginLeft: 6 }}>
                                / {totalPhaseBudget}h
                                {remainingH > 0
                                  ? <span style={{ color: "#2d6a4f" }}> · {remainingH.toFixed(1)}h offen</span>
                                  : isOver
                                    ? <span style={{ color: "#8a1a1a" }}> · überschritten</span>
                                    : null}
                              </span>
                            )}
                          </span>
                        </div>
                        <div style={{ display: "flex", height: 8, borderRadius: 3, overflow: "hidden", background: "#ece8e2" }}>
                          {barBaseMins > 0 && segMins.filter(s => s.mins > 0).map((seg, idx, arr) => (
                            <div key={seg.label} style={{ width: `${(seg.mins / barBaseMins) * 100}%`, height: "100%", background: isOver ? "#8a1a1a" : seg.color, borderRight: idx < arr.length - 1 ? "1px solid rgba(255,255,255,0.4)" : "none", flexShrink: 0 }} />
                          ))}
                          {remainingBarMins > 0 && (
                            <div style={{ width: `${(remainingBarMins / barBaseMins) * 100}%`, height: "100%", background: "#d0ccc8", flexShrink: 0 }} />
                          )}
                        </div>
                      </div>
                    );
                  })}
                  {unassignedMins > 0 && <div style={{ fontSize: 11, color: "#b5621e", marginTop: 4 }}>⚠ {formatHours(unassignedMins)} ohne Phasen-Zuordnung</div>}
                </div>
              );
            })() : (
              <>
                {/* ─ Einzeln: Hauptauftrag ─ */}
                {(project.enabledPhases || []).length > 0 && (
                  <div style={{ marginBottom: hasPositions ? 20 : 0 }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
                      <span style={{ fontSize: 10, fontWeight: 700, background: "#e8f5ee", color: "#2d6a4f", padding: "2px 8px", borderRadius: 3 }}>⬡ HO</span>
                      {hauptQ?.number && <span style={{ fontWeight: 600, color: "#b07848", fontSize: 12 }}>{hauptQ.number}</span>}
                      <span style={{ fontSize: 12, color: "#555" }}>Hauptauftrag</span>
                      <span style={{ marginLeft: "auto", fontSize: 12, color: "#888" }}>{formatHours(hoMinsTotal)}</span>
                    </div>
                    {phaseStats.map(ps => {
                      const hoMins = ps.hoMins;
                      const hoHours = hoMins / 60;
                      const phasePct = ps.phaseBudget > 0 ? (hoHours / ps.phaseBudget) * 100 : (hoMinsTotal > 0 ? (hoMins / hoMinsTotal) * 100 : 0);
                      const barColor = ps.phaseBudget > 0 ? (phasePct >= 100 ? "#8a1a1a" : phasePct >= 80 ? "#b5621e" : "#2d6a4f") : "#2d6a4f";
                      return (
                        <div key={ps.id} style={{ marginBottom: 12 }}>
                          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 4 }}>
                            <span style={{ fontWeight: 500 }}>{ps.label}</span>
                            <span style={{ color: barColor, fontWeight: 500 }}>
                              {formatHours(hoMins)}
                              {ps.phaseBudget > 0 && ` / ${ps.phaseBudget}h (${phasePct.toFixed(0)}%)`}
                            </span>
                          </div>
                          <div style={{ height: 6, background: "#ece8e2", borderRadius: 3, overflow: "hidden" }}>
                            <div style={{ width: `${Math.min(phasePct, 100)}%`, height: "100%", background: barColor }} />
                          </div>
                        </div>
                      );
                    })}
                    {unassignedMins > 0 && <div style={{ fontSize: 11, color: "#b5621e", marginTop: 4 }}>⚠ {formatHours(unassignedMins)} ohne Phasen-Zuordnung</div>}
                  </div>
                )}

                {/* ─ Einzeln: Nachträge / Positionen ─ */}
                {positions.map(pos => {
                  const posEntries = entries.filter(e => e.positionId === pos.code);
                  const posMins = posEntries.reduce((s, e) => s + (e.minutes || 0), 0);
                  const enabledPhaseIds = pos.enabledPhases || [];
                  const extraPhaseIds = [...new Set(posEntries.map(e => e.phaseId).filter(Boolean).filter(id => !enabledPhaseIds.includes(id)))];
                  const allPhaseIds = [...enabledPhaseIds, ...extraPhaseIds];
                  if (allPhaseIds.length === 0 && posMins === 0) return null;
                  const linkedQ = pos.quoteId ? (data.quotes || []).find(q => q.id === pos.quoteId) : null;
                  let nachtragPhasesBudget = [];
                  if (linkedQ) {
                    const roles = linkedQ.quoteRoles || data.settings.roles || [];
                    if (linkedQ.mode === "sia") {
                      const calc = calcSIAHours(linkedQ.sia?.baukosten, linkedQ.sia?.schwierigkeit, linkedQ.sia?.phases || []);
                      nachtragPhasesBudget = (calc.phases || []).filter(p => p.hours > 0).map(p => ({ id: p.id, hours: p.hours }));
                    } else if (linkedQ.mode === "manual") {
                      nachtragPhasesBudget = (linkedQ.manualPhases || []).filter(ph => ph.enabled).map(ph => {
                        const h = roles.reduce((s, r) => s + (ph.hoursByRole?.[r.id] || 0), 0);
                        return { id: ph.id, hours: Math.round(h * 10) / 10 };
                      }).filter(p => p.hours > 0);
                    }
                  }
                  return (
                    <div key={pos.code} style={{ paddingTop: 20, borderTop: "1px solid #ece8e2" }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
                        <span style={{ fontSize: 10, fontWeight: 700, background: "#e8f0fa", color: "#1a4e8a", padding: "2px 8px", borderRadius: 3 }}>↪ {pos.code}</span>
                        {linkedQ?.number && <span style={{ fontWeight: 600, color: "#b07848", fontSize: 12 }}>{linkedQ.number}</span>}
                        <span style={{ fontSize: 12, color: "#555" }}>{pos.label || "Nachtrag"}</span>
                        <span style={{ marginLeft: "auto", fontSize: 12, color: "#1a4e8a", fontWeight: 600 }}>{formatHours(posMins)}</span>
                      </div>
                      {allPhaseIds.length > 0 ? allPhaseIds.map(phId => {
                        const ph = SIA_PHASES.find(p => p.id === phId);
                        if (!ph) return null;
                        const phMins = posEntries.filter(e => e.phaseId === phId).reduce((s, e) => s + (e.minutes || 0), 0);
                        const phaseBudgetHours = nachtragPhasesBudget.find(p => p.id === phId)?.hours || 0;
                        const phHours = phMins / 60;
                        const phPct = phaseBudgetHours > 0 ? (phHours / phaseBudgetHours) * 100 : (posMins > 0 ? (phMins / posMins) * 100 : 0);
                        const barColor = phaseBudgetHours > 0 ? (phPct >= 100 ? "#8a1a1a" : phPct >= 80 ? "#b5621e" : "#1a4e8a") : (phMins > 0 ? "#1a4e8a" : "#ccc");
                        return (
                          <div key={phId} style={{ marginBottom: 12 }}>
                            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 4 }}>
                              <span style={{ fontWeight: 500 }}>{ph.label}</span>
                              <span style={{ color: barColor, fontWeight: 500 }}>
                                {formatHours(phMins)}
                                {phaseBudgetHours > 0 && ` / ${phaseBudgetHours}h (${phPct.toFixed(0)}%)`}
                              </span>
                            </div>
                            <div style={{ height: 6, background: "#e8f0fa", borderRadius: 3, overflow: "hidden" }}>
                              <div style={{ width: `${Math.min(phPct, 100)}%`, height: "100%", background: barColor }} />
                            </div>
                          </div>
                        );
                      }) : (
                        <div style={{ fontSize: 12, color: "#aaa" }}>Noch keine Stunden erfasst</div>
                      )}
                    </div>
                  );
                })}
              </>
            )}
          </div>
        );
      })()}

      {/* Mitarbeiter-Auswertung */}
      {canSeeMitarbeiter && (() => {
        const employees = data.employees || [];
        // Gruppe nach Mitarbeiter
        const byEmp = {};
        entries.forEach(e => {
          const empId = e.employeeId || "__none__";
          if (!byEmp[empId]) byEmp[empId] = { mins: 0, entries: [] };
          byEmp[empId].mins += e.minutes || 0;
          byEmp[empId].entries.push(e);
        });
        const empRows = Object.entries(byEmp).map(([empId, d]) => {
          const emp = empId === "__none__" ? null : employees.find(e => e.id === empId);
          // Aufschlüsselung nach Phase
          const byPhase = {};
          d.entries.forEach(e => {
            const k = e.phaseId || "__none__";
            byPhase[k] = (byPhase[k] || 0) + (e.minutes || 0);
          });
          const amount = billingType === "stundensatz" ? (d.mins / 60) * project.hourlyRate : null;
          return { empId, emp, mins: d.mins, amount, byPhase, count: d.entries.length };
        }).sort((a, b) => b.mins - a.mins);

        if (empRows.length === 0) return null;

        const totalMins = empRows.reduce((s, r) => s + r.mins, 0);

        return (
          <div className="card" style={{ marginBottom: 20 }}>
            <div style={{ fontSize: 11, letterSpacing: "0.1em", color: "#888", marginBottom: 14 }}>MITARBEITER-AUSWERTUNG</div>
            <table>
              <thead>
                <tr>
                  <th>Mitarbeiter</th>
                  <th style={{ textAlign: "right" }}>Stunden</th>
                  <th style={{ textAlign: "right" }}>Anteil</th>
                  {billingType === "stundensatz" && <th style={{ textAlign: "right" }}>Betrag</th>}
                  <th>Einträge</th>
                  <th>Phasen</th>
                </tr>
              </thead>
              <tbody>
                {empRows.map(row => {
                  const pct = totalMins > 0 ? Math.round(row.mins / totalMins * 100) : 0;
                  const phases = Object.entries(row.byPhase).map(([phId, mins]) => {
                    const ph = SIA_PHASES.find(p => p.id === phId);
                    return ph ? `${ph.id} (${formatHours(mins)})` : `— (${formatHours(mins)})`;
                  }).join(", ");
                  return (
                    <tr key={row.empId}>
                      <td>
                        <strong>{row.emp?.name || "Ohne Mitarbeiter"}</strong>
                        {row.emp?.role && <div style={{ fontSize: 11, color: "#888" }}>{row.emp.role}</div>}
                      </td>
                      <td style={{ textAlign: "right", fontWeight: 600 }}>{formatHours(row.mins)}</td>
                      <td style={{ textAlign: "right" }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 6, justifyContent: "flex-end" }}>
                          <div style={{ width: 60, height: 6, background: "#ece8e2", borderRadius: 3, overflow: "hidden" }}>
                            <div style={{ width: `${pct}%`, height: "100%", background: "#b07848", borderRadius: 3 }} />
                          </div>
                          <span style={{ fontSize: 12, color: "#888", minWidth: 32 }}>{pct}%</span>
                        </div>
                      </td>
                      {billingType === "stundensatz" && <td style={{ textAlign: "right" }}>{row.amount !== null ? formatCHF(row.amount) : "—"}</td>}
                      <td style={{ color: "#888", fontSize: 12 }}>{row.count} Einträge</td>
                      <td style={{ fontSize: 11, color: "#888" }}>{phases || "—"}</td>
                    </tr>
                  );
                })}
              </tbody>
              <tfoot>
                <tr style={{ borderTop: "1.5px solid #1a1a18" }}>
                  <td><strong>Total</strong></td>
                  <td style={{ textAlign: "right", fontWeight: 700 }}>{formatHours(totalMins)}</td>
                  <td style={{ textAlign: "right", fontSize: 12, color: "#888" }}>100%</td>
                  {billingType === "stundensatz" && <td style={{ textAlign: "right", fontWeight: 700 }}>{formatCHF(empRows.reduce((s, r) => s + (r.amount || 0), 0))}</td>}
                  <td style={{ color: "#888", fontSize: 12 }}>{entries.length} Einträge</td>
                  <td></td>
                </tr>
              </tfoot>
            </table>
          </div>
        );
      })()}

      <div className="card" style={{ padding: 0, marginBottom: 20 }}>
        <div style={{ padding: "16px 20px", fontSize: 11, letterSpacing: "0.1em", color: "#888", borderBottom: "1px solid #ece8e2" }}>ZEITEINTRÄGE</div>
        <table style={{ tableLayout: "fixed" }}>
          <thead><tr>
            <th style={{ width: "12%" }}>Datum</th>
            {canSeeMitarbeiter && <th style={{ width: "14%" }}>Mitarbeiter</th>}
            <th style={{ width: "15%" }}>Phase / Position</th>
            <th>Tätigkeit</th>
            <th style={{ width: "10%" }}>Dauer</th>
            {canSeeInvoices && <th style={{ width: "11%" }}>Betrag</th>}
            {canSeeInvoices && <th style={{ width: "12%" }}>Rechnung</th>}
          </tr></thead>
          <tbody>
            {entries.length === 0 && <tr><td colSpan={canSeeMitarbeiter && canSeeInvoices ? 7 : 4} style={{ textAlign: "center", color: "#aaa", padding: 24 }}>Noch keine Zeiteinträge</td></tr>}
            {entries.map(e => {
              const phase = SIA_PHASES.find(p => p.id === e.phaseId);
              const pos = positions.find(p => p.code === e.positionId);
              const emp = e.employeeId ? (data.employees || []).find(em => em.id === e.employeeId) : null;
              const amount = billingType === "stundensatz" ? (e.minutes / 60) * project.hourlyRate : null;
              const invoice = e.invoiceId ? data.invoices.find(i => i.id === e.invoiceId) : null;
              const posQuote = pos?.quoteId ? (data.quotes || []).find(q => q.id === pos.quoteId) : null;
              const isStundensatzEntry = e.positionId
                ? posQuote?.mode === "manual"
                : billingType === "stundensatz";
              return (
                <tr key={e.id} style={{ opacity: invoice && canSeeInvoices ? 0.75 : 1 }}>
                  <td>{formatDate(e.date)}</td>
                  {canSeeMitarbeiter && <td style={{ fontSize: 11, color: "#666" }}>{emp?.name || <span style={{ color: "#ccc" }}>—</span>}</td>}
                  <td style={{ fontSize: 12 }}>
                    {isAdmin ? (
                      <select
                        value={e.phaseId ? (e.positionId ? e.phaseId + "|" + e.positionId : e.phaseId) : ""}
                        onChange={ev => {
                          const val = ev.target.value;
                          if (!val) { update("timeEntries", data.timeEntries.map(te => te.id === e.id ? { ...te, phaseId: "", positionId: "" } : te)); return; }
                          const [ph, p] = val.split("|");
                          update("timeEntries", data.timeEntries.map(te => te.id === e.id ? { ...te, phaseId: ph, positionId: p || "" } : te));
                        }}
                        disabled={!!e.invoiceId}
                        style={{ border: "1px solid #e0dbd4", height: 26, fontSize: 11, maxWidth: "100%", opacity: e.invoiceId ? 0.6 : 1 }}
                      >
                        <option value="">—</option>
                        {(project.customPhases || []).map(cp => <option key={cp.id} value={cp.id}>{cp.label}</option>)}
                        {positions.length === 0 && (project.enabledPhases || []).map(phId => { const ph = SIA_PHASES.find(p => p.id === phId); return ph ? <option key={ph.id} value={ph.id}>{ph.id}</option> : null; }).filter(Boolean)}
                        {positions.length > 0 && (project.enabledPhases || []).map(phId => { const ph = SIA_PHASES.find(p => p.id === phId); return ph ? <option key={ph.id} value={ph.id}>{ph.id} HO</option> : null; }).filter(Boolean)}
                        {positions.flatMap(pos => (pos.enabledPhases || []).map(phId => { const ph = SIA_PHASES.find(p => p.id === phId); return ph ? <option key={ph.id + "|" + pos.code} value={ph.id + "|" + pos.code}>{pos.code}·{ph.id}</option> : null; }).filter(Boolean))}
                      </select>
                    ) : phase ? (
                      <span>
                        <span style={{ color: "#666" }}>{phase.label.split(" ")[0]}</span>
                        {pos && <span style={{ fontWeight: 600, color: "#7a6a00", background: "#fffbe8", padding: "1px 5px", borderRadius: 2, marginLeft: 5, fontSize: 11 }}>{pos.code}</span>}
                      </span>
                    ) : <span style={{ color: "#ccc" }}>—</span>}
                  </td>
                  <td style={{ color: "#666" }}>{e.description || "—"}</td>
                  <td>{formatHours(e.minutes)}</td>
                  {canSeeInvoices && <td>{amount !== null ? formatCHF(amount) : "—"}</td>}
                  {canSeeInvoices && <td style={{ fontSize: 11 }}>
                    {invoice
                      ? <span className="tag" style={{ background: "#2d6a4f", fontSize: 10 }}>{invoice.number}</span>
                      : isStundensatzEntry
                        ? <span style={{ color: "#aaa" }}>offen</span>
                        : null}
                  </td>}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {canSeeQuotes && (() => {
        const projectQuotes = (data.quotes || []).filter(q => q.projectId === projectId);
        if (projectQuotes.length === 0) return null;
        return (
          <div className="card" style={{ padding: 0, marginBottom: 20 }}>
            <div style={{ padding: "16px 20px", fontSize: 11, letterSpacing: "0.1em", color: "#888", borderBottom: "1px solid #ece8e2" }}>OFFERTEN</div>
            <table style={{ tableLayout: "fixed" }}>
              <thead><tr><th style={{ width: "18%" }}>Nr.</th><th style={{ width: "14%" }}>Datum</th><th>Modus</th><th style={{ width: "14%" }}>Status</th><th style={{ width: "16%" }}>Honorar</th><th style={{ width: "7%" }}></th></tr></thead>
              <tbody>
                {projectQuotes.map(q => (
                  <tr key={q.id}>
                    <td><strong>{q.number}</strong></td>
                    <td>{formatDate(q.date)}</td>
                    <td style={{ fontSize: 11, color: "#888" }}>{q.mode === "sia" ? "SIA 102" : "Aufwand"}</td>
                    <td><StatusBadge status={q.status} /></td>
                    <td><strong>{formatCHF(q.total)}</strong></td>
                    <td style={{ textAlign: "right" }}>
                      <button className="btn btn-ghost" style={{ padding: "5px 10px", fontSize: 12 }} onClick={() => setPrintContent({ type: "quote", quote: q, client, settings: data.settings })}>PDF</button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        );
      })()}

      {canSeeInvoices && projectInvoices.length > 0 && (
        <div className="card" style={{ padding: 0 }}>
          <div style={{ padding: "16px 20px", fontSize: 11, letterSpacing: "0.1em", color: "#888", borderBottom: "1px solid #ece8e2" }}>RECHNUNGEN</div>
          <table style={{ tableLayout: "fixed" }}>
            <thead><tr><th style={{ width: "18%" }}>Nr.</th><th style={{ width: "14%" }}>Datum</th><th>Art</th><th style={{ width: "14%" }}>Status</th><th style={{ width: "16%" }}>Betrag</th><th style={{ width: "7%" }}></th></tr></thead>
            <tbody>
              {projectInvoices.map(inv => (
                <tr key={inv.id}>
                  <td><strong>{inv.number}</strong></td>
                  <td>{formatDate(inv.date)}</td>
                  <td style={{ fontSize: 11, color: "#888" }}>{inv.invoiceKind === "akonto" ? "Akonto" : (inv.invoiceKind === "schluss" ? "Schluss" : "—")}</td>
                  <td><StatusBadge status={inv.status} /></td>
                  <td><strong>{formatCHF(inv.total)}</strong></td>
                  <td style={{ textAlign: "right" }}>
                    <button className="btn btn-ghost" style={{ padding: "5px 10px", fontSize: 12 }} onClick={() => setPrintContent({ type: "invoice", inv, client })}>PDF</button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          {showOffen && (paidTotal > 0 || openTotal > 0) && (
            <div style={{ padding: "10px 20px", borderTop: "1px solid #ece8e2", display: "flex", gap: 24, fontSize: 12 }}>
              <span style={{ color: "#4a7c59" }}>Bezahlt: <strong>{formatCHF(paidTotal)}</strong></span>
              {openTotal > 0 && (
                <span style={{ color: "#b5621e" }}>Noch offen: <strong>{formatCHF(openTotal)}</strong></span>
              )}
              {openTotal === 0 && paidTotal > 0 && (
                <span style={{ color: "#888", fontSize: 11 }}>Alles bezahlt</span>
              )}
            </div>
          )}
        </div>
      )}

      {/* Protokolle zum Projekt */}
      {(() => {
        const projProtokolle = (data.protocols || [])
          .filter(p => p.projectId === projectId)
          .sort((a, b) => (b.date || "").localeCompare(a.date || ""));
        return (
          <div className="card" style={{ padding: 0, marginTop: 20 }}>
            <div style={{ padding: "16px 20px", display: "flex", justifyContent: "space-between", alignItems: "center", borderBottom: projProtokolle.length > 0 ? "1px solid #ece8e2" : "none" }}>
              <div style={{ fontSize: 11, letterSpacing: "0.1em", color: "#888" }}>PROTOKOLLE ({projProtokolle.length})</div>
              <button className="btn btn-ghost" style={{ fontSize: 11, padding: "4px 12px" }} onClick={() => {
                const p = {
                  id: generateId(),
                  title: "",
                  type: PROTOKOLL_TYPES[0],
                  date: new Date().toISOString().slice(0, 10),
                  time: "10:00",
                  endTime: "",
                  location: "",
                  projectId,
                  projectManual: "",
                  nummer: (() => {
                    const all = data.protocols || [];
                    const abbr = data.settings.protokollTypeAbbreviations || {};
                    const typKuerzel = abbr[PROTOKOLL_TYPES[0]] || "SO";
                    const proj = data.projects.find(x => x.id === projectId);
                    const seq = nextProtoSeq(all);
                    return applyProtoNumberFormat(data.settings.protokollNumberFormat || "PP-TT-NN", {
                      date: new Date().toISOString().slice(0, 10),
                      projectNumber: proj?.number || "",
                      seq,
                      typKuerzel,
                    });
                  })(),
                  participants: [],
                  traktanden: [{ id: generateId(), nr: "1", title: "", items: [] }],
                  nextDate: "",
                  verteiler: "",
                  createdAt: new Date().toISOString(),
                };
                saveAll({ ...data, protocols: [...(data.protocols || []), p] });
                // navigate to protokolle view – signal via a custom event
                window.__openProtokoll = p.id;
                window.dispatchEvent(new CustomEvent("openProtokoll", { detail: { id: p.id } }));
              }}>+ Neues Protokoll</button>
            </div>
            {projProtokolle.length > 0 && (
              <table>
                <thead>
                  <tr>
                    <th style={{ width: 110 }}>Nr.</th>
                    <th style={{ width: 100 }}>Datum</th>
                    <th style={{ width: 160 }}>Typ</th>
                    <th>Titel</th>
                    <th style={{ width: 60, textAlign: "center" }}>TN</th>
                    <th style={{ width: 50, textAlign: "center" }}>📌</th>
                    <th style={{ width: 80 }}></th>
                  </tr>
                </thead>
                <tbody>
                  {projProtokolle.map(proto => {
                    const anwesend = (proto.participants || []).filter(x => x.status === "anwesend").length;
                    const total = (proto.participants || []).length;
                    const offene = (proto.traktanden || []).flatMap(t => (t.items || []).filter(it => it.type === "aufgabe" && it.status !== "erledigt")).length;
                    return (
                      <tr key={proto.id}>
                        <td><strong style={{ color: "#b07848" }}>{proto.nummer}</strong></td>
                        <td>{formatDate(proto.date)}</td>
                        <td style={{ fontSize: 11, color: "#888" }}>{proto.type}</td>
                        <td>{proto.title || <span style={{ color: "#aaa" }}>Kein Titel</span>}</td>
                        <td style={{ textAlign: "center", fontSize: 12, color: "#888" }}>{total > 0 ? `${anwesend}/${total}` : "—"}</td>
                        <td style={{ textAlign: "center" }}>{offene > 0 && <span style={{ fontSize: 11, color: "#b5621e", fontWeight: 600 }}>{offene}</span>}</td>
                        <td style={{ textAlign: "right" }}>
                          <button className="btn btn-ghost" style={{ fontSize: 11, padding: "4px 10px" }}
                            onClick={() => { window.__openProtokoll = proto.id; window.dispatchEvent(new CustomEvent("openProtokoll", { detail: { id: proto.id } })); }}>
                            Öffnen
                          </button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>
        );
      })()}

      {/* Einstellungen Modal */}
      {settingsOpen && (() => {
        const projectContacts = settingsForm.projectContacts || [];
        const allContacts = (data.persons || []).filter(p => p.isPartner);
        const alreadyAdded = new Set(projectContacts.map(pc => pc.contactId));
        const selectedFirm = allContacts.find(c => c.id === addContactId);

        const addBeteiligte = () => {
          if (!addContactId) return;
          setSettingsForm(f => ({ ...f, projectContacts: [...(f.projectContacts || []), { contactId: addContactId, personIds: addPersonIds }] }));
          setShowAddBeteiligter(false); setAddContactId(""); setAddPersonIds([]);
        };
        const saveEditedBeteiligte = (contactId) => {
          setSettingsForm(f => ({ ...f, projectContacts: (f.projectContacts || []).map(pc => pc.contactId === contactId ? { ...pc, personIds: addPersonIds } : pc) }));
          setEditingContactId(null); setAddPersonIds([]);
        };
        const removeBeteiligte = (contactId) => {
          setSettingsForm(f => ({ ...f, projectContacts: (f.projectContacts || []).filter(pc => pc.contactId !== contactId) }));
        };
        const togglePerson = (personId) => setAddPersonIds(prev =>
          prev.includes(personId) ? prev.filter(id => id !== personId) : [...prev, personId]);

        return (
          <Modal title="Projekteinstellungen" onClose={() => setSettingsOpen(false)} onSave={saveSettings} wide>
            <ProjectEditForm form={settingsForm} setForm={setSettingsForm} data={data} />

            <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid #ece8e2" }}>
              <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 12 }}>INTERNE PROJEKTBETEILIGUNG</div>
              {(data.employees || []).length === 0
                ? <div style={{ fontSize: 12, color: "#aaa", marginBottom: 10 }}>Noch keine Mitarbeitenden erfasst.</div>
                : <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 4 }}>
                    {(data.employees || []).map(emp => {
                      const checked = (settingsForm.internalMembers || []).includes(emp.id);
                      return (
                        <label key={emp.id} style={{ display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontSize: 13, background: checked ? "#f0ede8" : "#faf9f7", border: `1px solid ${checked ? "#c8b89a" : "#ece8e2"}`, borderRadius: 8, padding: "4px 10px" }}>
                          <input type="checkbox" checked={checked} onChange={() => setSettingsForm(f => ({ ...f, internalMembers: checked ? (f.internalMembers || []).filter(id => id !== emp.id) : [...(f.internalMembers || []), emp.id] }))} style={{ margin: 0 }} />
                          <span>{emp.name}</span>
                          {emp.role && <span style={{ fontSize: 10, color: "#aaa" }}>{emp.role}</span>}
                        </label>
                      );
                    })}
                  </div>
              }
            </div>

            <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid #ece8e2" }}>
              <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 12 }}>PROJEKTBETEILIGTE</div>

              {projectContacts.map(pc => {
                const firm = allContacts.find(c => c.id === pc.contactId);
                if (!firm) return null;
                const selectedPersons = (firm.contacts || []).filter(p => pc.personIds.includes(p.id));
                const isEditing = editingContactId === pc.contactId;
                return (
                  <div key={pc.contactId} style={{ padding: "10px 0", borderBottom: "1px solid #f5f2ec" }}>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
                      <div>
                        {firm.type && <div style={{ fontSize: 10, color: "#aaa", marginBottom: 2, letterSpacing: "0.06em" }}>{firm.type.toUpperCase()}</div>}
                        <div style={{ fontWeight: 600, fontSize: 13 }}>{firm.name}</div>
                        {!isEditing && (selectedPersons.length > 0
                          ? <div style={{ marginTop: 4, display: "flex", flexWrap: "wrap", gap: 6 }}>
                              {selectedPersons.map(p => <span key={p.id} style={{ fontSize: 11, background: "#f5f2ec", color: "#555", padding: "2px 8px", borderRadius: 10 }}>{p.name}{p.position ? " · " + p.position : ""}</span>)}
                            </div>
                          : <div style={{ fontSize: 11, color: "#aaa", marginTop: 3 }}>Alle Ansprechpartner</div>
                        )}
                      </div>
                      <div style={{ display: "flex", gap: 4 }}>
                        {(firm.contacts || []).length > 0 && !isEditing && (
                          <button className="btn btn-ghost" style={{ fontSize: 11, padding: "3px 8px" }}
                            onClick={() => { setEditingContactId(pc.contactId); setAddPersonIds(pc.personIds || []); setShowAddBeteiligter(false); }}><span className="material-icons" style={{ fontSize: 16 }}>edit</span></button>
                        )}
                        <button className="btn btn-ghost" style={{ fontSize: 11, padding: "3px 8px", color: "#888" }} onClick={() => removeBeteiligte(pc.contactId)}><span className="material-icons" style={{ fontSize: 16 }}>close</span></button>
                      </div>
                    </div>
                    {isEditing && (
                      <div style={{ marginTop: 8 }}>
                        <div style={{ fontSize: 11, color: "#888", marginBottom: 6 }}>Personen (leer = alle):</div>
                        {(firm.contacts || []).map(p => (
                          <label key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, marginBottom: 4 }}>
                            <input type="checkbox" checked={addPersonIds.includes(p.id)} onChange={() => togglePerson(p.id)} />
                            <span>{p.name}</span>
                            {p.position && <span style={{ fontSize: 11, color: "#888" }}>{p.position}</span>}
                          </label>
                        ))}
                        <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
                          <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={() => saveEditedBeteiligte(pc.contactId)}>Speichern</button>
                          <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => { setEditingContactId(null); setAddPersonIds([]); }}>Abbrechen</button>
                        </div>
                      </div>
                    )}
                  </div>
                );
              })}

              {projectContacts.length === 0 && !showAddBeteiligter && (
                <div style={{ fontSize: 12, color: "#aaa", marginBottom: 10 }}>Noch keine Beteiligten erfasst.</div>
              )}

              {showAddBeteiligter ? (
                <div style={{ marginTop: 12 }}>
                  <select value={addContactId} onChange={e => { setAddContactId(e.target.value); setAddPersonIds([]); }} style={{ width: "100%", height: 34, marginBottom: 10 }}>
                    <option value="">— Firma wählen —</option>
                    {allContacts.filter(c => !alreadyAdded.has(c.id)).sort((a, b) => a.name.localeCompare(b.name)).map(c => (
                      <option key={c.id} value={c.id}>{c.type ? "[" + c.type + "] " : ""}{c.name}</option>
                    ))}
                  </select>
                  {selectedFirm && (selectedFirm.contacts || []).length > 0 && (
                    <div style={{ marginBottom: 10 }}>
                      <div style={{ fontSize: 11, color: "#888", marginBottom: 6 }}>Personen (leer = alle):</div>
                      {(selectedFirm.contacts || []).map(p => (
                        <label key={p.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, marginBottom: 4 }}>
                          <input type="checkbox" checked={addPersonIds.includes(p.id)} onChange={() => togglePerson(p.id)} />
                          <span>{p.name}</span>
                          {p.position && <span style={{ fontSize: 11, color: "#888" }}>{p.position}</span>}
                        </label>
                      ))}
                    </div>
                  )}
                  <div style={{ display: "flex", gap: 8 }}>
                    <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={addBeteiligte} disabled={!addContactId}>Hinzufügen</button>
                    <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => { setShowAddBeteiligter(false); setAddContactId(""); setAddPersonIds([]); }}>Abbrechen</button>
                  </div>
                </div>
              ) : (
                <button className="btn btn-ghost" style={{ fontSize: 12, marginTop: 10 }} onClick={() => setShowAddBeteiligter(true)}>+ Beteiligten hinzufügen</button>
              )}
            </div>
          </Modal>
        );
      })()}

            {/* Budget / Offerten-Modal */}
      {budgetModal && (() => {
        const ROLES = ["Hauptofferte", "Nachtrag", "Referenz"];
        const linked = budgetForm.linkedQuotes || [];
        const nachtragLinked = linked.filter(lq => lq.role !== "Hauptofferte" && lq.role !== "Referenz");
        const alreadyInOtherProjects = new Set((data.projects || []).filter(p => p.id !== projectId).flatMap(p => migrateLinkedQuotes(p).map(lq => lq.quoteId)));
        const unlinked = (data.quotes || []).filter(q => !linked.some(lq => lq.quoteId === q.id) && !alreadyInOtherProjects.has(q.id));
        const sortedLinked = [
          ...linked.filter(lq => lq.role === "Hauptofferte"),
          ...linked.filter(lq => lq.role !== "Hauptofferte"),
        ];
        return (
          <Modal title="Budget & Honorarofferten" onClose={() => setBudgetModal(false)} onSave={saveBudget} wide>
            <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.08em", color: "#888", marginBottom: 10 }}>VERKNÜPFTE OFFERTEN</div>

            {sortedLinked.map((lq, lqIdx) => {
              const q = (data.quotes || []).find(x => x.id === lq.quoteId);
              if (!q) return null;
              const isNT = lq.role !== "Hauptofferte";
              const ntIdx = isNT ? nachtragLinked.findIndex(x => x.quoteId === lq.quoteId) : -1;
              const posCode = isNT ? `N${ntIdx + 1}` : null;
              const pos = isNT
                ? ((budgetForm.positions || []).find(p => p.quoteId === lq.quoteId) || (posCode ? (budgetForm.positions || []).find(p => p.code === posCode) : null))
                : null;
              const currentPhases = isNT ? (pos?.enabledPhases || []) : (budgetForm.enabledPhases || []);
              const togglePhase = (phId) => {
                if (isNT) {
                  setBudgetForm(f => ({
                    ...f,
                    positions: f.positions.map(p =>
                      (p.quoteId ? p.quoteId === lq.quoteId : p.code === posCode)
                        ? { ...p, enabledPhases: (p.enabledPhases || []).includes(phId) ? (p.enabledPhases || []).filter(x => x !== phId) : [...(p.enabledPhases || []), phId] }
                        : p
                    ),
                  }));
                } else {
                  setBudgetForm(f => {
                    const phases = f.enabledPhases || [];
                    return { ...f, enabledPhases: phases.includes(phId) ? phases.filter(p => p !== phId) : [...phases, phId] };
                  });
                }
              };
              const qRoles = q.quoteRoles || data.settings.roles || [];
              const qH = q.mode === "sia" ? (calcSIAHours(q.sia?.baukosten, q.sia?.schwierigkeit, q.sia?.phases || []).total || 0)
                : q.mode === "manual" ? (calcManualHours(q.manualPhases || [], qRoles).totalHours || 0) : 0;
              const bgColor = isNT ? "#f0f5fd" : "#faf8f5";
              const borderColor = isNT ? "#c8d8ee" : "#ddd8d0";
              const accentColor = isNT ? "#1a4e8a" : "#2d6a4f";
              return (
                <div key={lq.quoteId} style={{ marginBottom: 12, border: `1px solid ${borderColor}`, borderRadius: 8, overflow: "hidden" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: bgColor, flexWrap: "wrap" }}>
                    {isNT && pos ? (
                      <>
                        <input value={pos.code || ""} onChange={e => setBudgetForm(f => ({ ...f, positions: f.positions.map(p => p.quoteId === lq.quoteId ? { ...p, code: e.target.value.toUpperCase().replace(/\s/g, "").slice(0, 6) } : p) }))} placeholder="N1" style={{ width: 52, fontWeight: 700, fontSize: 12, height: 26, padding: "0 6px" }} />
                        <input value={pos.label || ""} onChange={e => setBudgetForm(f => ({ ...f, positions: f.positions.map(p => p.quoteId === lq.quoteId ? { ...p, label: e.target.value } : p) }))} placeholder="z.B. Nachtrag Fassade" style={{ flex: 1, minWidth: 80, fontSize: 12, height: 26, padding: "0 6px" }} />
                      </>
                    ) : (
                      <span style={{ fontSize: 10, fontWeight: 700, background: "#e8f5ee", color: accentColor, padding: "2px 8px", borderRadius: 3 }}>⬡ HO</span>
                    )}
                    <span style={{ fontWeight: 600, color: "#b07848" }}>{q.number}</span>
                    <span style={{ color: "#888", fontSize: 11 }}>{q.mode === "sia" ? "SIA 102" : q.mode === "manual" ? "Aufwand" : "Pauschal"}</span>
                    {qH > 0 && <span style={{ color: "#555", fontSize: 11 }}>{qH.toFixed(1)}h · {formatCHF(q.sub || q.total || 0)}</span>}
                    <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
                      <select value={lq.role} onChange={e => changeLinkedRole(lq.quoteId, e.target.value)} style={{ fontSize: 11, height: 26, padding: "0 6px" }}>
                        {ROLES.map(r => <option key={r}>{r}</option>)}
                      </select>
                      <button className="btn btn-danger" style={{ padding: "0 6px", height: 24, fontSize: 10 }} onClick={() => removeLinkedQuote(lq.quoteId)}><span className="material-icons" style={{ fontSize: 16 }}>close</span></button>
                    </div>
                  </div>
                  <div style={{ padding: "10px 14px", background: "#fff" }}>
                    <div style={{ fontSize: 10, color: "#888", marginBottom: 6, letterSpacing: "0.07em" }}>AKTIVIERTE SIA-PHASEN FÜR ZEITERFASSUNG</div>
                    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }}>
                      {SIA_PHASES.map(ph => (
                        <label key={ph.id} style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 12, textTransform: "none", color: "#1a1a18", padding: "3px 0" }}>
                          <input type="checkbox" checked={currentPhases.includes(ph.id)} onChange={() => togglePhase(ph.id)} style={{ width: "auto" }} />
                          {ph.label}
                        </label>
                      ))}
                    </div>
                  </div>
                </div>
              );
            })}

            {linked.length === 0 && (
              <div style={{ fontSize: 12, color: "#aaa", marginBottom: 10 }}>Noch keine Offerte verknüpft. SIA, Aufwand und Pauschal-Offerten können verknüpft werden.</div>
            )}
            {unlinked.length > 0 && (
              <select defaultValue="" onChange={e => { if (e.target.value) { addLinkedQuote(e.target.value); e.target.value = ""; } }} style={{ fontSize: 12, width: "100%", marginBottom: 16 }}>
                <option value="">{linked.length === 0 ? "Offerte verknüpfen…" : "+ Nachtrag / weitere Offerte hinzufügen…"}</option>
                {unlinked.map(q => <option key={q.id} value={q.id}>{q.number} — {q.mode === "sia" ? "SIA 102" : q.mode === "manual" ? "Aufwand" : "Pauschal"} · {formatCHF(q.total)}</option>)}
              </select>
            )}

            <FormField label="Stundenbudget manuell überschreiben (leert Offerten-Verknüpfung)">
              <input type="number" step="0.5" min={0} value={budgetForm.budgetHours || 0}
                onChange={e => setBudgetForm(f => ({ ...f, budgetHours: +e.target.value, linkedQuotes: [], phasesBudget: [] }))}
                placeholder="0 = aus Offerten berechnet" />
            </FormField>
          </Modal>
        );
      })()}
    </div>
  );
}
