Files
projektuebersicht/public/Test.html
T

1662 lines
94 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<!--
HAUPTSEITE der Projektübersicht (wird vom Express-Server unter "/" ausgeliefert).
Sie verwaltet eine einzige, firmenweit globale wöchentliche Sitzung (Sitzungswahl,
-anlage, -duplikat und -löschung im seitlichen Menü) und enthält ALLE Bereiche der
Anwendung. Die Seitenleiste links schaltet über showTab() (js/tabs-auth.js)
zwischen ihnen um: Dashboard, Projektboard, Wochenbericht, Auslastung, Projekte,
Projektbeteiligte — und dem PROJEKTBEREICH eines einzelnen Projekts (#plan).
Der Projektbereich war bis zum Umbau ein eigenes Dokument (public/project-plan.html),
das openProjectPlan() als neue Seite öffnete; die Seitenleiste dort war nur eine
abgespeckte Nachbildung. Jetzt ist er ein Bereich wie jeder andere: dieselbe
Seitenleiste, derselbe Wechsel-Weg wie vom Dashboard ins Projektboard, kein
Neuladen. Adressierbar bleibt er über "/Projektplan/<Projektname>?weekLabel=…
&projNr=…" (applyUrlRoute()/updateRouteUrl() in js/ui.js); seine fünf
Unterbereiche stehen im URL-Fragment (…#aufgaben).
Geladen werden das gemeinsame Stylesheet styles.css und plan-styles.css (die
Stile des Projektbereichs, früher ein <style>-Block in project-plan.html) sowie
der Reihe nach die Module js/globals.js, js/utils.js, js/icons.js, js/ui.js,
js/date-picker.js, js/server.js, js/auth.js, js/store.js, js/weeks.js, danach die
bereichsspezifischen Module js/board/board.js, js/wochenbericht/wochenbericht.js,
js/people/people.js, js/projects/projects.js, js/load/load.js,
js/dashboard/dashboard.js, dann js/gantt.js mit den darauf aufbauenden
js/plan/*-Modulen des Projektbereichs und zuletzt js/showmode.js,
js/tabs-auth.js, js/pdf.js, js/backup.js, js/import.js und
js/init.js; pdfmake kommt von einem CDN. Der Zugriff auf die gesamte Seite ist
über eine echte serverseitige Session abgesichert (js/auth.js,
checkAuthAndInit(): Login-Overlay bis /whoami eine gültige Session meldet).
-->
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projektübersicht</title>
<link rel="stylesheet" href="/styles.css?v=47">
<link rel="stylesheet" href="/plan-styles.css?v=6">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/pdfmake.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js"></script>
</head>
<body>
<div class="app-shell">
<header class="app-topbar">
<button class="mobile-nav-toggle menu-corner-btn" onclick="toggleHeader()" title="Menü öffnen/schließen" aria-expanded="false" aria-controls="headerMenu">
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg></span>
</button>
<div class="topbar-brand">
<img src="/dein_bild.ico" alt="Logo" class="topbar-logo">
<h1 id="pageTitle" class="topbar-title">Projektübersicht</h1>
</div>
<!-- Die Sitzungsauswahl stand hier früher als Dropdown. Sitzungen sind
reine Speicherung + Verlauf; die Oberfläche zeigt immer nur die
eine offene Sitzung, ohne das im Menüband zu betonen. Eine
gesperrte Sitzung lässt sich weiterhin ansehen und für eine
Korrektur entsperren — dafür jetzt Admin → Termine & Sitzungen
(renderAdminSitzungen(), js/admin/admin.js). -->
<div class="app-topbar-actions">
<button class="theme-toggle-circle" id="themeToggle" onclick="toggleTheme()" title="Hell/Dunkel umschalten">
<span id="themeIcon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 14.5A8.5 8.5 0 1 1 9.5 3.5a7 7 0 0 0 11 11Z"/></svg></span></span>
</button>
<div class="topbar-user">
<button class="theme-toggle-circle avatar-btn" id="userMenuToggle" onclick="toggleUserMenu()" title="Konto" aria-expanded="false" aria-controls="userMenuDropdown">
<span class="icon" id="userAvatarIcon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8.5" r="3.5"/><path d="M4.5 20c0-4 3.5-6.5 7.5-6.5s7.5 2.5 7.5 6.5"/></svg></span>
</button>
<div class="user-menu-dropdown" id="userMenuDropdown">
<div class="user-menu-info" id="userBarInfo"></div>
<button class="menu-item" onclick="logout()">
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5.5A1.5 1.5 0 0 1 4 19.5v-15A1.5 1.5 0 0 1 5.5 3H9"/><path d="M16 17l5-5-5-5"/><line x1="21" y1="12" x2="9" y2="12"/></svg></span>
Abmelden
</button>
</div>
</div>
</div>
</header>
<div class="app-body">
<div id="headerMenu" class="side-menu" aria-hidden="true">
<div class="side-menu-mobile-header">
<button class="header-toggle secondary" onclick="toggleHeader()" title="Menü schließen">
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
</button>
</div>
<nav class="sidebar-nav">
<button class="nav-item tab active" data-tab="dashboard" onclick="showTab('dashboard')" title="Dashboard">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.5"/><path d="m4.5 7.5 1.3 1.3L8.5 6"/><line x1="13" y1="6" x2="21" y2="6"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="m4.5 17.5 1.3 1.3 2.7-2.8"/><line x1="13" y1="16" x2="21" y2="16"/></svg></span></span><span class="nav-label">Dashboard</span>
</button>
<hr class="sidebar-nav-divider">
<div class="sidebar-section-label">Navigation</div>
<!-- Die eine Projektliste. Der frühere zweite Eintrag "Projekte" —
dieselben Projekte, dieselben Angaben, eine zweite Bearbeitungs-
maske — ist hier aufgegangen: Reihenfolge, Gruppenwechsel und
Löschen stehen in dieser Liste, die Projektangaben im
Projektbereich, die wöchentliche Erfassung im Wochenbericht. -->
<button class="nav-item tab" data-tab="board" onclick="showTab('board')" title="Projekte">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2.5"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg></span></span><span class="nav-label">Projekte</span>
</button>
<button class="nav-item tab" data-tab="wochenbericht" onclick="showTab('wochenbericht')" title="Wochenbericht">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3.5h8a1.5 1.5 0 0 1 1.5 1.5v14a1.5 1.5 0 0 1-1.5 1.5H8A1.5 1.5 0 0 1 6.5 19V5A1.5 1.5 0 0 1 8 3.5Z"/><path d="M9.5 3.5h5v2.2a.8.8 0 0 1-.8.8h-3.4a.8.8 0 0 1-.8-.8V3.5Z"/><path d="m9.3 12.2 1.5 1.5 3.4-3.4"/><line x1="9.3" y1="16.3" x2="14.7" y2="16.3"/></svg></span></span><span class="nav-label">Wochenbericht</span>
</button>
<button class="nav-item tab" data-tab="load" onclick="showTab('load')" title="Auslastung">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="21" x2="5" y2="10"/><line x1="12" y1="21" x2="12" y2="5"/><line x1="19" y1="21" x2="19" y2="14"/><line x1="3" y1="21" x2="21" y2="21"/></svg></span></span><span class="nav-label">Auslastung</span>
</button>
<!-- "Projektbeteiligte" stand hier bis zur Entfernung der Abteilungs-
trennung (migrateRemoveDepartmentPartition(), CLAUDE.md
"Departments were retired"): eine rein lesende Liste, deren
eigentlicher Zweck — Beteiligte gezielt aus einer ANDEREN Abteilung
in die eigene Sitzung zu holen — mit der Zusammenlegung zu EINEM
gemeinsamen Personenkreis entfiel. Übrig blieb eine Dopplung der
Rollen-/Avatar-Chips, die in Wochenbericht/Board/Auslastung ohnehin
schon sichtbar sind. Auf Eriks Wunsch aus der Seitenleiste entfernt;
der Tab selbst (`#people`, `js/people/people.js`) und sein Slug
`Projektbeteiligte` in VALID_TAB_SLUGS bestehen unverändert fort —
ein alter Link/Deep-Link funktioniert also weiterhin, nur der
direkte Weg über die Navigation fehlt. -->
<!-- Projektbereich: der einzige Navigationspunkt, der nicht immer da
ist. Er erscheint, sobald ein Projekt geöffnet wurde, und trägt
dessen Bezeichnung (syncPlanNavItem() in js/tabs-auth.js). Ohne
ihn wäre im Projektbereich gar kein Punkt hervorgehoben und die
Seitenleiste zeigte nicht mehr, wo man gerade ist. -->
<button class="nav-item tab" data-tab="plan" id="planNavItem" style="display:none" onclick="showTab('plan')" title="Projektbereich">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="14" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="7" y1="13" x2="13" y2="13"/><line x1="7" y1="16" x2="16" y2="16"/></svg></span></span><span class="nav-label" id="planNavItemLabel">Projektbereich</span>
</button>
<!-- Planung: eigener, zweiter Abschnitt (nicht Teil von "Navigation"),
wie gewünscht — eine Sandbox für Terminpläne unabhängig von einem
echten Projekt (aus Honorar errechnet oder frei angelegt), siehe
CLAUDE.md "Planung — projektunabhängige Terminplan-Sandbox". -->
<div class="sidebar-section-label">Planung</div>
<button class="nav-item tab" data-tab="planung" onclick="showTab('planung')" title="Planung">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="17" rx="2.5"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="8" y1="2.5" x2="8" y2="6.5"/><line x1="16" y1="2.5" x2="16" y2="6.5"/><line x1="7" y1="14" x2="12" y2="14"/><line x1="7" y1="17.5" x2="15" y2="17.5"/></svg></span></span><span class="nav-label">Planung</span>
</button>
<hr class="sidebar-nav-divider">
<!-- Einstellungen: seit die firmenweiten Masken in den Admin-Bereich
gewandert sind, steht hier nur noch das eigene Konto — ein
Aufklapp-Menue fuer einen einzigen Punkt waere Ballast. -->
<button class="nav-item tab" data-tab="settings" onclick="showTab('settings')" title="Einstellungen">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M12 3.5v2.4M12 18.1v2.4M20.5 12h-2.4M5.9 12H3.5M17.7 6.3l-1.7 1.7M8 15.7l-1.7 1.7M17.7 17.7 16 16M8 8 6.3 6.3"/></svg></span></span><span class="nav-label">Einstellungen</span>
</button>
<button class="nav-item nav-group-toggle" id="toolsAccordionToggle" onclick="toggleSidebarAccordion('tools')" title="Werkzeuge" aria-expanded="false" aria-controls="toolsSubnav">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 6.2a3.6 3.6 0 0 1 4.8 4.4l-8.1 8.1a2 2 0 0 1-2.8-2.8l8.1-8.1"/><path d="M6.5 3.5 9 6l-3 3-2.5-2.5a3.5 3.5 0 0 1 3-3Z"/></svg></span></span><span class="nav-label">Werkzeuge</span>
<span class="nav-chevron"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
<div class="sidebar-subnav" id="toolsSubnav">
<!-- "+ Neues Projekt" stand bis zum Aufraeumen unter "Admin". Ein
Projekt anzulegen ist aber alltaegliche Arbeit: Es geht ueber den
normalen Wochen-Speicherweg (POST /save-week, requireAuth), der
Server verlangt dafuer keine Administratorrolle. Die Sperre war
rein optisch und hielt Mitglieder von gewoehnlicher Arbeit ab,
ohne irgendetwas zu schuetzen. -->
<!-- "Neue Sitzung"/"Sitzung duplizieren" standen hier bis zum
automatischen Sitzungswechsel (ensureCurrentWeekRollover(),
server.js): eine neue Sitzung entsteht jetzt serverseitig von
selbst, sobald der Kalender in eine neue ISO-Woche wechselt —
unabhängig davon, ob der Wochenbericht überhaupt geöffnet wird,
siehe CLAUDE.md. -->
<!-- "PDF Export" (die gewerkeübergreifende A3-Sitzungsübersicht,
exportPDF()) stand hier bis auf Eriks Wunsch: PDF-Export soll
nur noch im Wochenbericht (exportWochenberichtPDF()) und in den
Terminplanungen (exportSandboxPlanPDF()) stehen — beide siehe
js/pdf.js. Damit fällt die einzige gewerkeübergreifende
Sitzungsübersicht als PDF weg; die einzelnen Gewerke-Berichte
im Wochenbericht bleiben die Ablage. -->
<button class="menu-item sidebar-subitem" onclick="openNewProjWizard()">+ Neues Projekt</button>
<button class="menu-item sidebar-subitem" onclick="startShowMode()">Show Mode</button>
</div>
<!-- Admin: aufklappbare Gruppe ueber die Abschnitte von #admin. Wie
bei "Einstellungen" zuvor ist das KEINE Aktionsliste, sondern
Navigation innerhalb eines Bereichs — deshalb fuehrt schon der
Kopf-Eintrag dorthin (toggleAdminNav()) und die Unterpunkte
schalten nur den sichtbaren Abschnitt um (showAdminSection() in
js/admin/admin.js). Die gesamte Gruppe blendet renderUserBar()
(js/auth.js) fuer Nicht-Administratoren aus; die eigentliche
Sperre sitzt serverseitig auf den Routen. -->
<div id="navAdminSection" style="display:none">
<button class="nav-item nav-group-toggle" id="adminAccordionToggle" onclick="toggleAdminNav()" title="Admin" aria-expanded="false" aria-controls="adminSubnav">
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5 5.5 6v5.3c0 4.4 2.9 7.4 6.5 8.7 3.6-1.3 6.5-4.3 6.5-8.7V6Z"/><path d="m9 12 2.1 2.1L15.5 9.5"/></svg></span></span><span class="nav-label">Admin</span>
<span class="nav-chevron"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
<div class="sidebar-subnav" id="adminSubnav">
<button class="menu-item sidebar-subitem" data-admin-section="benutzer" onclick="showAdminSection('benutzer')">Benutzerverwaltung</button>
<button class="menu-item sidebar-subitem" data-admin-section="honorar" onclick="showAdminSection('honorar')">Honorar</button>
<button class="menu-item sidebar-subitem" data-admin-section="auslastung" onclick="showAdminSection('auslastung')">Auslastung</button>
<button class="menu-item sidebar-subitem" data-admin-section="termine" onclick="showAdminSection('termine')">Termine &amp; Sitzungen</button>
<button class="menu-item sidebar-subitem" data-admin-section="feiertage" onclick="showAdminSection('feiertage')">Feiertage</button>
<button class="menu-item sidebar-subitem" data-admin-section="vorlagen" onclick="showAdminSection('vorlagen')">Aufgaben-Vorlagen</button>
<button class="menu-item sidebar-subitem" data-admin-section="betrieb" onclick="showAdminSection('betrieb')">Betrieb &amp; Sicherheit</button>
<button class="menu-item sidebar-subitem" data-admin-section="datenbank" onclick="showAdminSection('datenbank')">Datenbank &amp; Sitzungen</button>
</div>
</div>
</nav>
<button class="sidebar-collapse-bar" id="sidebarCollapseToggle" onclick="toggleSidebarCollapse()" title="Seitenleiste ein-/ausklappen" aria-pressed="false">
<span id="sidebarCollapseIcon" class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 5 8 12 15 19"/></svg></span>
</button>
</div>
<div id="sideMenuBackdrop" class="side-menu-backdrop" onclick="toggleHeader()"></div>
<div class="app-main">
<!-- Show Mode Overlay -->
<div id="showModeOverlay" class="show-mode-overlay" style="display:none;" onclick="pauseShowMode()">
<div class="show-mode-header">
<span id="showModeTitle">Show Mode</span>
<button class="secondary" onclick="stopShowMode()">Beenden</button>
</div>
<div id="showModeContent" class="show-mode-content"></div>
<div id="showModePaused" class="show-mode-paused" style="display:none;">Pausiert – berühre zum Fortsetzen</div>
</div>
<div class="container">
<!-- BOARD -->
<!-- display:none wie bei allen anderen Panels: showTab() blendet den
richtigen Bereich erst nach der Anmeldeprüfung ein. Ohne das stand
hinter dem Login-Fenster das Projektboard, während in der Navigation
"Dashboard" hervorgehoben war. -->
<div id="board" style="display:none">
<!-- Titelzeile mit "+ Projekt": Der Knopf stand bisher nur in der
Seitenleiste unter "Sitzung". Anlegen gehört dorthin, wo die Projekte
stehen — in dieser Liste, die seit der Zusammenlegung auch Reihenfolge,
Gruppe und Löschen verantwortet. In der Seitenleiste bleibt er
zusätzlich stehen, weil er dort dieselbe Maske öffnet. -->
<div class="bericht-titelzeile">
<div class="section-title" style="margin:0">Projekte</div>
<button type="button" class="secondary small" onclick="openNewProjWizard()">+ Projekt</button>
</div>
<!-- Die Farbe der Kreise richtet sich nach der ROLLE (siehe
.board-avatar.role-* in styles.css), die Füllung nach der Art der
Zuordnung. Die frühere Legende erklärte nur Grün und behauptete damit
implizit, Projektleitung (rot) und Bauüberwachung (blau) gäbe es nicht. -->
<div class="legend">
<span><span class="swatch" style="background:var(--green)"></span> Planung</span>
<span><span class="swatch" style="background:var(--red)"></span> Projektleitung</span>
<span><span class="swatch" style="background:var(--accent)"></span> Bauüberwachung</span>
<span class="legend-sep">·</span>
<span><span class="swatch" style="background:var(--text-subtle)"></span> gefüllt = hauptverantwortlich</span>
<span><span class="swatch" style="background:transparent;box-shadow:inset 0 0 0 2px var(--text-subtle)"></span> umrandet = unterstützend</span>
</div>
<div id="boardTable"></div>
</div>
<!-- WOCHENBERICHT -->
<div id="wochenbericht" style="display:none">
<div class="bericht-titelzeile">
<div class="section-title" style="margin:0">Wochenbericht</div>
<button type="button" class="secondary small" onclick="openBerichtProtokoll()">Protokoll…</button>
<!-- Anderer Export als "Protokoll": Das Protokoll ist eine Kurzfassung
(was hat sich geändert + neue Aufgaben) zum Kopieren/Mailen. Dieser
Knopf legt die ganze Tagesordnung als PDF ab — eine Karte je
Projekt mit vollständigem Kommentar, Fortschritt und Beteiligten,
siehe exportWochenberichtPDF() in js/pdf.js. -->
<button type="button" class="secondary small" onclick="exportWochenberichtPDF()">Als PDF ablegen</button>
</div>
<div id="wochenberichtContent"></div>
<div id="weekAttendance"></div>
</div>
<!-- LOAD -->
<div id="load" style="display:none">
<div class="section-title">Auslastung der Projektbeteiligten</div>
<div class="toolbar" style="margin-bottom:12px">
<label>Auswertung:</label>
<select id="loadScope" onchange="renderLoad()">
<option value="current">Aktuelle Sitzung</option>
<option value="all">Alle Sitzungen (Historie)</option>
</select>
<span class="hint">Auswertung über den rollenspezifischen Gewichtungsfaktor je Leistungsphase.</span>
</div>
<div class="load-grid" id="loadGrid"></div>
</div>
<!-- PEOPLE -->
<div id="people" style="display:none">
<div class="section-title">Projektbeteiligte</div>
<div id="peopleList"></div>
</div>
<!-- PLANUNG (js/planung/planung.js) — Terminplan-Sandbox, unabhängig von
einem echten Projekt. Bearbeitet wird ein einzelner Plan im ganz
normalen Projektbereich (#plan) — siehe openSandboxPlan(),
js/plan/plan-state.js. -->
<div id="planung" style="display:none">
<div class="bericht-titelzeile">
<div class="section-title" style="margin:0">Planung</div>
<button type="button" class="secondary small" onclick="oeffneNeuerTerminplanDialog()">+ Neuer Terminplan</button>
</div>
<p class="hint" style="margin:0 0 16px">Terminpläne unabhängig von einem laufenden Projekt — zum Ausprobieren, für Angebote, um Kapazität grob abzuschätzen.</p>
<div id="planungList"></div>
</div>
<!-- DASHBOARD (Begrüßung/Uhr, Meine Aufgaben, Freigaben, Kollegen-Abwesenheiten) -->
<div id="dashboard" style="display:none">
<div id="dashboardContent"></div>
</div>
<!-- EINSTELLUNGEN (js/settings/settings.js)
Nur noch das eigene Konto: Stammdaten zur Ansicht und der
Passwortwechsel. Die firmenweiten Masken standen hier zunaechst
daneben, gehoeren aber zum Bereich "Admin" weiter unten — sie
verschieben die Zahlen ALLER Nutzer und stehen nur Administratoren zu,
waehrend dieser Bereich jedem angemeldeten Konto gehoert. -->
<div id="settings" style="display:none">
<div class="section-title">Einstellungen</div>
<div class="settings-grid">
<section class="settings-card" id="settingsAccountCard" data-settings-section="konto">
<h3 class="settings-card-title">Mein Konto</h3>
<div id="settingsAccountInfo" class="settings-kv"></div>
<p class="hint">Anzeigename, Rolle und Personen-Zuordnung pflegt ein
Administrator in der Benutzerverwaltung. Die E-Mail-Adresse und das
Passwort gehören dem Konto selbst.</p>
<div class="toolbar">
<button class="secondary" onclick="openPasswordModal(false)">Passwort ändern</button>
</div>
</section>
<!-- KI-Zugangsdaten: persönlich je Konto, nicht firmenweit — jeder
Nutzer hinterlegt seinen eigenen Schlüssel (+ optional eine
abweichende API-URL für einen lokalen Server wie LM Studio/
Ollama oder einen Firmen-Proxy) für die KI-gestützte
Punkt-Erkennung beim Protokoll-Import. Die Schlüssel werden
geschrieben, aber nie zurückgelesen (wie ein Passwort), siehe
readUserAiKey()/writeUserAiKey() in server.js und CLAUDE.md
"### Protokolle". Je Anbieter ein eigener, umrandeter Block mit
Cloud/Eigener-Server-Umschalter statt eines eingeklappten
"API-URL"-Links — die eigene Anbindung (z. B. LM Studio) ist ein
Kernfall, kein Nebendetail, siehe CLAUDE.md. Dazu ein
"Neu verbinden"-Knopf (echter Testaufruf über
POST /my-ai-settings/test, nicht nur Anzeige von "configured")
mit Fehlertext, der bis zum nächsten Testergebnis stehen bleibt
(Design-Entwürfe mit Erik abgestimmt: Artifacts d7ab2945,
01fe1346). -->
<section class="settings-card" id="settingsAiCard">
<h3 class="settings-card-title">KI-Zugangsdaten</h3>
<p class="hint">Für die automatische Punkt-Erkennung beim Protokoll-Import
(Projektbereich → Protokolle → „Protokoll importieren"). Ohne
hinterlegten Schlüssel wird automatisch die Textmuster-Erkennung
verwendet — ein Schlüssel ist also optional, nicht Voraussetzung.</p>
<div class="ai-provider" id="aiProviderChatgpt">
<div class="ai-provider-head">
<div class="ai-provider-mark chatgpt">Ai</div>
<div class="ai-provider-info">
<div class="ai-provider-name">ChatGPT</div>
<div class="ai-provider-sub">OpenAI</div>
</div>
<span class="ai-status-chip" id="aiOpenaiStatus"><span class="dot"></span>–</span>
<button class="ai-reconnect-btn" id="aiOpenaiReconnectBtn" onclick="testMyAiConnection('chatgpt')" title="Neu verbinden">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
</button>
</div>
<div class="ai-provider-row">
<input type="password" id="setAiOpenaiKey" placeholder="API-Schlüssel einfügen…" autocomplete="off">
<button class="ai-icon-btn" onclick="saveMyAiKey('chatgpt')" title="Speichern">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
</button>
<button class="ai-text-btn" id="aiOpenaiClearBtn" onclick="clearMyAiKey('chatgpt')" title="Schlüssel entfernen">Entfernen</button>
</div>
<div class="ai-test-error" id="aiOpenaiTestError"></div>
<div class="ai-mode-toggle" id="aiOpenaiModeToggle">
<button type="button" class="is-active" onclick="setAiMode('chatgpt','cloud')">Cloud</button>
<button type="button" onclick="setAiMode('chatgpt','lokal')">Eigener Server</button>
</div>
<div class="ai-url-panel" id="aiOpenaiUrlPanel">
<label>Adresse des eigenen Servers</label>
<div class="ai-provider-row">
<input type="text" id="setAiOpenaiUrl" placeholder="z. B. http://localhost:1234 für LM Studio">
<button class="ai-icon-btn" onclick="saveMyAiUrl('chatgpt')" title="URL speichern">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
</button>
</div>
</div>
</div>
<div class="ai-provider" id="aiProviderClaude">
<div class="ai-provider-head">
<div class="ai-provider-mark claude">Cl</div>
<div class="ai-provider-info">
<div class="ai-provider-name">Claude</div>
<div class="ai-provider-sub">Anthropic</div>
</div>
<span class="ai-status-chip" id="aiClaudeStatus"><span class="dot"></span>–</span>
<button class="ai-reconnect-btn" id="aiClaudeReconnectBtn" onclick="testMyAiConnection('claude')" title="Neu verbinden">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
</button>
</div>
<div class="ai-provider-row">
<input type="password" id="setAiClaudeKey" placeholder="API-Schlüssel einfügen…" autocomplete="off">
<button class="ai-icon-btn" onclick="saveMyAiKey('claude')" title="Speichern">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
</button>
<button class="ai-text-btn" id="aiClaudeClearBtn" onclick="clearMyAiKey('claude')" title="Schlüssel entfernen">Entfernen</button>
</div>
<div class="ai-test-error" id="aiClaudeTestError"></div>
<div class="ai-mode-toggle" id="aiClaudeModeToggle">
<button type="button" class="is-active" onclick="setAiMode('claude','cloud')">Cloud</button>
<button type="button" onclick="setAiMode('claude','lokal')">Eigener Server</button>
</div>
<div class="ai-url-panel" id="aiClaudeUrlPanel">
<label>Adresse des eigenen Servers</label>
<div class="ai-provider-row">
<input type="text" id="setAiClaudeUrl" placeholder="z. B. http://localhost:11434 für Ollama">
<button class="ai-icon-btn" onclick="saveMyAiUrl('claude')" title="URL speichern">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
</button>
</div>
</div>
</div>
<span class="field-error" id="settingsAiError" hidden></span>
</section>
</div>
<div id="settingsNote" class="settings-note"></div>
</div>
<!-- ===================== ADMIN (js/admin/admin.js) =====================
Alles, was nur Administratoren zusteht, an einer Stelle. Die
firmenweiten Masken standen zuvor im Bereich „Einstellungen" neben
„Mein Konto" — das vermischte die eigenen Kontoangaben mit Vorgaben,
die die Zahlen ALLER verschieben. Die drei Datenbank-/Sitzungs-
Aktionen waren nackte Menüpunkte in der Seitenleiste, wo ein
Fehlklick genügte; sie stehen jetzt im Abschnitt „Datenbank &
Sitzungen" mit dem Hinweis, was sie anrichten.
Sichtbar ist immer genau ein Abschnitt (showAdminSection()); der
gewählte steht im URL-Fragment (…/Admin#auslastung). -->
<div id="admin" style="display:none">
<div class="section-title" id="adminSectionTitle">Admin</div>
<div class="settings-grid">
<section class="settings-card" id="adminHonorarCard" data-admin-section="honorar" style="display:none">
<h3 class="settings-card-title">Honorar <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Grundlage der Terminplan-Berechnung aus dem Honorar („+ Terminplan" im
Projektbereich): Honorar × Anteil der Leistungsphase ÷ Stundensatz = Stunden.</p>
<div id="honorarAdminFields" class="honorar-anteil-grid"></div>
<div class="honorar-admin-foot">
<div id="honorarAdminSum"></div>
<label class="honorar-satz-field">
<span>Stundensatz</span>
<input type="number" id="honorarStundensatz" min="1" step="1" value="135"> €
</label>
<button class="secondary small" onclick="saveHonorarConfig()">Speichern</button>
</div>
</section>
<section class="settings-card" id="adminAuslastungCard" data-admin-section="auslastung" style="display:none">
<h3 class="settings-card-title">Auslastung <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Die Auslastung wird in <strong>Wochenstunden</strong> gerechnet: offener
Honorarwert der amtierenden Leistungsphase geteilt durch den Stundensatz, verteilt auf
die Rollen, die zugeordneten Personen und die verbleibenden Wochen. Die Ampel misst
jede Person an IHRER Stelle — 1,0 heißt voll ausgelastet; die vertraglichen
Wochenstunden stehen am Konto (Benutzerverwaltung).</p>
<div class="settings-field-grid">
<label class="settings-field"><span>Wochenstunden einer vollen Stelle</span>
<input type="number" id="setVollzeitStunden" min="1" max="80" step="0.5"></label>
<label class="settings-field"><span>Ampel gelb ab</span>
<input type="number" id="setSchwelleGelb" min="0.05" max="5" step="0.05"></label>
<label class="settings-field"><span>Ampel rot ab</span>
<input type="number" id="setSchwelleRot" min="0.05" max="5" step="0.05"></label>
<label class="settings-field"><span>Referenzhonorar für die Projektgröße (€)</span>
<input type="number" id="setReferenzHonorar" min="1000" max="100000000" step="1000"></label>
<label class="settings-field"><span>Verteilzeitraum ohne Terminplan (Wochen)</span>
<input type="number" id="setVerteilWochen" min="1" max="260" step="1"></label>
</div>
<p class="hint">Projekte ohne hinterlegtes Honorar tragen statt dessen eine
<strong>Projektgröße von 0 bis 1</strong> (Bereich „Einstellungen" des Projekts);
1,0 entspricht dem Referenzhonorar. Der Verteilzeitraum gilt, solange ein Projekt
keinen Terminplan hat, aus dem sich ein Ende ergibt — ohne Horizont wäre die
Wochenlast nicht bestimmt.</p>
<h4 class="settings-subtitle">Aufwand je Leistungsphase und Rolle</h4>
<p class="hint">Gilt für jedes Projekt, das in seiner eigenen Aufwandsmaske keinen Wert
trägt — also für die meisten. 0 = zählt nicht zur Auslastung, 1 = voller Aufwand.</p>
<div id="setAufwandMatrix" class="settings-matrix-wrap"></div>
<div class="toolbar">
<button class="secondary small" onclick="saveAuslastungSettings()">Speichern</button>
</div>
</section>
<section class="settings-card" id="adminTerminCard" data-admin-section="termine" style="display:none">
<h3 class="settings-card-title">Termine &amp; Sitzungen <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<div class="settings-field-grid">
<label class="settings-field"><span>Arbeitsstunden je Person und Werktag</span>
<input type="number" id="setStundenProTag" min="1" max="24" step="0.5"></label>
</div>
<label class="settings-check">
<input type="checkbox" id="setAutoLock">
<span>Ältere Sitzungen automatisch sperren</span>
</label>
<p class="hint">Ist das aus, bleibt jede Sitzung bearbeitbar. Bereits gesperrte Sitzungen
bleiben gesperrt — entsperrt wird weiterhin bewusst über „Sitzung entsperren".</p>
<div class="toolbar">
<button class="secondary small" onclick="saveTerminSettings()">Speichern</button>
</div>
<!-- Die Oberfläche zeigt sonst nirgends mehr, welche Sitzung gerade
offen ist — dafür genügt eine Zeile, da es ohnehin immer nur
eine gibt. Diese Liste ist die einzige Stelle, die alle
Sitzungen nebeneinander zeigt: zur Kontrolle und um eine
gesperrte für eine nachträgliche Korrektur zu entsperren. -->
<h4 class="settings-subtitle">Sitzungen</h4>
<p class="hint">Jede Sitzung ist ein Wochenstand für Speicherung und Verlauf — bearbeitet
wird immer nur die eine offene. Eine gesperrte Sitzung lässt sich hier entsperren und
öffnen, etwa um einen Kommentar nachträglich zu korrigieren; beim nächsten Laden sperrt
die Automatik sie wieder, sofern sie aktiv ist.</p>
<div class="template-admin-list" id="adminSessionsList"></div>
</section>
<section class="settings-card" id="adminHolidayCard" data-admin-section="feiertage" style="display:none">
<h3 class="settings-card-title">Feiertage <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Arbeitsfreie Tage zählen bei der Terminplan-Berechnung aus dem Honorar und
bei der Ressourcen-Auslastung nicht als Arbeitstag. Vorher kannte die Anwendung nur Wochenenden.</p>
<div id="holidayYearHint" class="settings-warn" style="display:none;margin-bottom:10px"></div>
<h4 class="settings-subtitle">Gesetzliche Feiertage übernehmen</h4>
<p class="hint">Werden aus dem Kalender berechnet (feste Termine plus alles, was am
Osterdatum hängt) — kein Netzdienst, kein Internetzugang nötig. Vorgeschlagen, nicht
automatisch eingetragen: Feiertage verschieben jeden gerechneten Terminplan.</p>
<div class="settings-inline-field">
<span>Jahr</span>
<input type="number" id="holidayYear" min="1970" max="2200" step="1" style="width:90px">
<span>Bundesland</span>
<select id="holidayState"></select>
<button class="secondary small" onclick="suggestHolidays()">Vorschlagen</button>
</div>
<div id="holidaySuggestion"></div>
<h4 class="settings-subtitle">Hinterlegte Tage</h4>
<div id="adminHolidayList" class="settings-holiday-list"></div>
<div class="template-add-row">
<input type="date" id="holidayNewDate">
<input type="text" id="holidayNewName" placeholder="Bezeichnung, z. B. „Betriebsruhe“">
<button class="secondary small" onclick="addHoliday()">+ Einzelner Tag</button>
</div>
</section>
<section class="settings-card" id="adminBetriebCard" data-admin-section="betrieb" style="display:none">
<h3 class="settings-card-title">Betrieb &amp; Sicherheit <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<div class="settings-field-grid">
<label class="settings-field"><span>Aufbewahrte automatische Backups</span>
<input type="number" id="setMaxBackups" min="1" max="500" step="1"></label>
<label class="settings-field"><span>Gültigkeit einer Anmeldung (Tage)</span>
<input type="number" id="setSessionTage" min="1" max="365" step="1"></label>
<label class="settings-field"><span>Fehlversuche bis zur Anmeldesperre</span>
<input type="number" id="setLoginVersuche" min="1" max="100" step="1"></label>
<label class="settings-field"><span>Dauer der Anmeldesperre (Minuten)</span>
<input type="number" id="setLoginSperre" min="1" max="1440" step="1"></label>
</div>
<p class="hint">Die Gültigkeit wirkt auf neue Anmeldungen; bestehende behalten ihre Laufzeit.</p>
<div class="toolbar">
<button class="secondary small" onclick="saveBetriebSettings()">Speichern</button>
</div>
</section>
<section class="settings-card settings-card-wide" id="adminTemplateCard" data-admin-section="vorlagen" style="display:none">
<h3 class="settings-card-title">Aufgaben-Vorlagen <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Wiederkehrende Schritte einer Leistungsphase. Im Projektbereich fügt
„+ Vorlage" sie als Aufgaben ein; danach sind es ganz normale Aufgaben — eine später
geänderte Vorlage wirkt NICHT rückwirkend.</p>
<label class="settings-inline-field">
<span>Leistungsphase</span>
<select id="adminTemplateLph" onchange="renderAdminTemplates()"></select>
</label>
<div id="templateAdminList" class="template-admin-list"></div>
<div class="template-add-row">
<input type="text" id="templateNewTitle" placeholder="Neuer Schritt, z. B. „LV erstellen“">
<input type="number" id="templateNewOffset" value="0" min="0" title="Start in Tagen nach dem Startdatum">
<input type="number" id="templateNewDuration" value="1" min="1" title="Dauer in Tagen">
<button class="secondary small" onclick="addTemplateEntry()">+ Schritt</button>
</div>
</section>
<section class="settings-card settings-card-wide" id="adminUsersCard" data-admin-section="benutzer" style="display:none">
<h3 class="settings-card-title">Benutzerverwaltung <span class="settings-scope">nur Administratoren</span></h3>
<p class="hint">Konten anlegen, bearbeiten, deaktivieren, Passwoerter zuruecksetzen und
Abwesenheiten pflegen. Die <strong>Wochenstunden</strong> hier sind die Bezugsgroesse der
Auslastungs-Ampel; die <strong>Person-ID</strong> verbindet das Konto mit den
Aufgaben-Zuordnungen und fuellt damit „Meine Aufgaben".</p>
<div id="generatedPasswordBox"></div>
<div class="toolbar admin-toolbar">
<button onclick="openNewUserModal()">+ Neuer Benutzer</button>
</div>
<div class="table-wrap">
<table id="usersTable">
<thead>
<tr>
<th>Benutzername</th>
<th>Anzeigename</th>
<th>E-Mail</th>
<th>Rolle</th>
<th>Abteilung</th>
<th>Kürzel</th>
<th>Std./Woche</th>
<th>Person-ID</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
</table>
</div>
</section>
<section class="settings-card" id="adminDatenbankCard" data-admin-section="datenbank" style="display:none">
<h3 class="settings-card-title">Datenbank &amp; Sitzungen <span class="settings-scope">nur Administratoren</span></h3>
<h4 class="settings-subtitle">Sicherung</h4>
<p class="hint">Der Server legt bei jedem Start selbst eine Sicherung an (Anzahl siehe
„Betrieb &amp; Sicherheit"). „DB Backup" lädt zusätzlich den aktuellen Stand herunter.
<strong>Die Datei enthält die komplette Datenbank samt Sitzungs-Token</strong> — sie
gehört nicht in fremde Hände.</p>
<div class="toolbar">
<button class="secondary small" onclick="downloadBackup()">DB Backup herunterladen</button>
</div>
<h4 class="settings-subtitle">Wiederherstellen</h4>
<p class="hint">Spielt eine mit „DB Backup" erzeugte Datei ein und <strong>ersetzt damit
alle Sitzungsdaten</strong>. Konten, Rollen, Abwesenheiten und Datei-Freigaben bleiben
unberührt. Unmittelbar vorher sichert der Server den aktuellen Stand, ein Einspielen
lässt sich also rückgängig machen.</p>
<div class="toolbar">
<button class="secondary small" onclick="importDb()">DB Import…</button>
</div>
<!-- Das Löschen einzelner Sitzungen ist zur Sitzungsliste unter
"Termine & Sitzungen" gewandert (renderAdminSitzungen()) — dort
steht ohnehin schon, welche Sitzung welche ist; ein zweiter,
von "der im Menüband gewählten" abhängiger Knopf hier wäre seit
dem Wegfall der Menüband-Auswahl gegenstandslos gewesen. -->
</section>
</div>
<div id="adminNote" class="settings-note"></div>
</div>
</div>
<!-- ===================== PROJEKTBEREICH (#plan) =====================
Der eigene Bereich EINES Projekts — frueher die eigenstaendige Seite
public/project-plan.html, jetzt ein ganz normaler Bereich dieser Seite.
Der Wechsel dorthin laeuft ueber showTab('plan') (js/tabs-auth.js) wie
jeder andere Navigationswechsel, sodass Menueband und Seitenleiste
stehen bleiben; geoeffnet wird er ueber openProjectPlan() (js/gantt.js).
Er steht bewusst AUSSERHALB von .container, weil er seine eigene
Kopfzeile und eigene Innenabstaende mitbringt (siehe plan-styles.css).
Die fuenf Unterbereiche (Dashboard, Terminplan, Aufgaben, Dateien,
Einstellungen) schaltet .plan-nav um (showPlanSection() in
js/plan/plan-sections.js); alle liegen gleichzeitig im DOM. -->
<div id="plan" style="display:none">
<div class="plan-page">
<div class="plan-header">
<span class="plan-id" id="planIdBadge"></span>
<h1 id="planTitle">Projektplan</h1>
<div class="tag-chip-list" id="planTagsList"></div>
<div class="plan-header-spacer"></div>
<div class="subtitle" id="planSubtitle"></div>
<div class="plan-avatar-stack" id="planPeopleList"></div>
<div class="plan-header-actions">
<button class="theme-toggle-circle" onclick="todoMail()" title="E-Mail erstellen">
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 6.5 8 6.5 8-6.5"/></svg></span>
</button>
<button class="theme-toggle-circle" onclick="closePlanPage()" title="Schließen">
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
</button>
</div>
</div>
<!-- Bereichs-Navigation des Projektbereichs: schaltet zwischen den fünf
Bereichen um (showPlanSection() in js/plan/plan-sections.js). Die
Bereiche liegen alle gleichzeitig im DOM und werden nur ein- bzw.
ausgeblendet, damit die vorhandenen Render-Funktionen ihre Elemente
unabhängig von der Sichtbarkeit befüllen können. -->
<nav class="plan-nav" id="planNav">
<button class="plan-nav-item" data-section="dashboard" onclick="showPlanSection('dashboard')">Dashboard</button>
<button class="plan-nav-item" data-section="terminplan" onclick="showPlanSection('terminplan')">Terminplan</button>
<button class="plan-nav-item" data-section="aufgaben" onclick="showPlanSection('aufgaben')">Aufgaben</button>
<button class="plan-nav-item" data-section="leistung" onclick="showPlanSection('leistung')">Leistung</button>
<button class="plan-nav-item" data-section="dateien" onclick="showPlanSection('dateien')">Dateien</button>
<button class="plan-nav-item" data-section="protokolle" onclick="showPlanSection('protokolle')">Protokolle</button>
<button class="plan-nav-item" data-section="einstellungen" onclick="showPlanSection('einstellungen')">Einstellungen</button>
</nav>
<div class="plan-body">
<div class="plan-main">
<div class="plan-main-content">
<!-- ===================== Bereich: Dashboard ===================== -->
<section class="plan-section" id="planSection-dashboard" data-section="dashboard">
<div class="plan-kpi-row" id="planKpiRow"></div>
<div class="plan-panel-grid">
<div class="plan-panel">
<h3 class="plan-panel-title">Kommentar der Sitzung</h3>
<!-- Nur zu lesen: Der Kommentar entsteht in der wöchentlichen
Besprechung und wird deshalb allein im Wochenbericht
erfasst. Die frühere Werkzeugleiste (Fett/Farben) ist mit
dem Bearbeiten hier entfallen. -->
<div class="status" id="planStatusWrap">
<div class="status-edit" id="planStatusEdit"></div>
</div>
<div class="plan-verweis-row" id="planStatusVerweis"></div>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Leistungsphasen</h3>
<div class="board-lph-list" id="planLphList"></div>
<div class="plan-verweis-row" id="planLphVerweis"></div>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Nächste Termine</h3>
<div id="planDashUpcoming"></div>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Team</h3>
<div id="planDashTeam"></div>
</div>
</div>
<div class="plan-card-row">
<div class="plan-mini-card" onclick="openResourceModal()">
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Ressourcen-Auslastung</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
<div id="planResourceSummary"></div>
</div>
<div class="plan-mini-card" onclick="openFilesPanel()">
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Dateien &amp; Freigaben</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
<div id="planFilesSummary"></div>
</div>
<div class="plan-mini-card" onclick="openProtokollePanel()">
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Protokolle</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
<div id="planProtokolleSummary"></div>
</div>
<div class="plan-mini-card" onclick="openHistoryModal()">
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Verlauf</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
<div id="planHistorySummary"></div>
</div>
</div>
</section>
<!-- ===================== Bereich: Terminplan ==================== -->
<section class="plan-section" id="planSection-terminplan" data-section="terminplan">
<div class="plan-main-header">
<label>Zeitraum:</label>
<input type="text" id="planGanttRange" class="range-picker-display" placeholder="Zeitraum wählen…" readonly
onclick="openRangePicker({ anchor: this, startInputId: 'planGanttStart', endInputId: 'planGanttEnd', displayId: 'planGanttRange', onApply: renderPlanGantt })">
<input type="hidden" id="planGanttStart">
<input type="hidden" id="planGanttEnd">
<button class="secondary" onclick="fitPlanGanttRange()">Zeitraum anpassen</button>
<label><input type="checkbox" id="planShowVacation" checked onchange="renderPlanGantt()"> Urlaub anzeigen</label>
<label title="Termine der vorherigen Sitzung als gestrichelten Umriss einblenden"><input type="checkbox" id="planShowGhost" checked onchange="renderPlanGantt()"> Vorwoche</label>
<button class="secondary" onclick="zoomPlanGantt(-1)" title="Verkleinern">−</button>
<button class="secondary" onclick="zoomPlanGantt(1)" title="Vergrößern">+</button>
<button onclick="addPlanMilestone()" id="addMilestoneBtn">+ Meilenstein</button>
<button onclick="addTodo()" id="addTodoBtn">+ TODO</button>
<button onclick="openTemplateModal()" id="templateBtn" title="Standard-Aufgaben einer Leistungsphase einfügen">+ Vorlage</button>
<button onclick="openHonorarModal()" id="honorarBtn" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">+ Terminplan</button>
<button onclick="autoScheduleTodos()" id="autoScheduleBtn" title="Aufgaben nach Abhängigkeiten automatisch anordnen">Auto-Schedule</button>
<button onclick="toggleCriticalPath()" id="criticalPathBtn" title="Kritischen Pfad ein-/ausblenden">Kritischer Pfad</button>
<!-- Nur für Sandbox-Pläne der Planung sichtbar (renderPlanPage(),
js/plan/plan-state.js) — ein echtes Projekt hat keinen
vergleichbaren Gantt-PDF-Export. -->
<button class="secondary" onclick="exportSandboxPlanPDF(PlanApp.project.id)" id="sandboxPdfBtn" style="display:none">Als PDF exportieren</button>
</div>
<div id="planWarnings"></div>
<div id="planGanttContainer" class="gantt-container"></div>
<div class="plan-panel" style="margin-top:18px">
<h3 class="plan-panel-title">Ressourcen-Auslastung</h3>
<div id="resourceLoadContainer"></div>
</div>
</section>
<!-- ====================== Bereich: Aufgaben ===================== -->
<section class="plan-section" id="planSection-aufgaben" data-section="aufgaben">
<div class="plan-main-header">
<button onclick="addTodo()" id="addTodoBtnList">+ Aufgabe</button>
<button onclick="addPlanMilestone()" id="addMilestoneBtnList">+ Meilenstein</button>
<button onclick="openTemplateModal()" id="templateBtnList" title="Standard-Aufgaben einer Leistungsphase einfügen">+ Vorlage</button>
<button onclick="openHonorarModal()" id="honorarBtnList" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">+ Terminplan</button>
<button class="secondary" onclick="autoScheduleTodos()" id="autoScheduleBtnList" title="Aufgaben nach Abhängigkeiten automatisch anordnen">Auto-Schedule</button>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Aufgaben <span class="plan-panel-count" id="planTodoCount"></span></h3>
<div id="planTodoList" class="plan-task-list"></div>
</div>
<div class="plan-panel" style="margin-top:18px">
<h3 class="plan-panel-title">Meilensteine <span class="plan-panel-count" id="planMilestoneCount"></span></h3>
<div id="planMilestoneList" class="plan-task-list"></div>
</div>
</section>
<!-- ===================== Bereich: Leistung ====================== -->
<!-- Leistungsstand & Abrechnung: der erbrachte Leistungswert aus
Honorar × HOAI-Anteil je Phase × Fortschritt der Phase
(renderPlanLeistung(), js/plan/plan-leistung.js).
Ein eigener Bereich und keine Dashboard-Karte: Die Aufschlüsselung
hat eine Zeile je beauftragter Leistungsphase mit fünf Spalten und
stünde im Raster des Dashboards entweder gedrängt oder ein zweites
Mal — genau die Doppelung, die überall sonst gerade beseitigt
wurde. Die Zahlen beruhen auf Honorar und beauftragten Phasen aus
dem Bereich "Einstellungen" und auf dem Fortschritt, der im
Wochenbericht erfasst wird; hier wird nur gerechnet, nichts
eingegeben. -->
<section class="plan-section" id="planSection-leistung" data-section="leistung">
<div class="plan-panel plan-panel-wide">
<h3 class="plan-panel-title">Leistungsstand &amp; Abrechnung</h3>
<div id="planLeistungWrap" class="plan-leistung-wrap"></div>
</div>
</section>
<!-- ====================== Bereich: Dateien ====================== -->
<section class="plan-section" id="planSection-dateien" data-section="dateien">
<div class="plan-panel">
<h3 class="plan-panel-title">Datei hochladen</h3>
<div id="filesNoProjectHint" class="hint" style="display:none">Dieses Projekt hat keine Projektnummer und kann daher keine Dateien erhalten.</div>
<div id="filesUploadSection" class="plan-files-upload">
<div class="form-row">
<label>Datei</label>
<input type="file" id="fileUploadInput">
</div>
<div class="form-row">
<label>Prüfer (mehrfach möglich)</label>
<div id="fileUploadReviewers" class="todo-depends-list"></div>
</div>
<div class="toolbar">
<button onclick="submitFileUpload()">Hochladen</button>
</div>
</div>
</div>
<div class="plan-panel" style="margin-top:18px">
<h3 class="plan-panel-title">Dateien &amp; Freigaben</h3>
<div id="filesListContainer" class="files-list"></div>
</div>
</section>
<!-- ===================== Bereich: Protokolle ===================== -->
<section class="plan-section" id="planSection-protokolle" data-section="protokolle">
<div id="protokolleNoProjectHint" class="hint" style="display:none">Dieses Projekt hat keine Projektnummer und kann daher keine Protokolle erhalten.</div>
<div id="protokolleContainer"></div>
</section>
<!-- =================== Bereich: Einstellungen =================== -->
<section class="plan-section" id="planSection-einstellungen" data-section="einstellungen">
<div id="planSettingsLockHint" class="hint" style="display:none">Diese Sitzung ist gesperrt — Einstellungen können nicht geändert werden.</div>
<div class="plan-panel-grid">
<div class="plan-panel">
<h3 class="plan-panel-title">Projektangaben</h3>
<!-- Projektnummer, Bezeichnung und Schlagwörter waren zuvor
zusätzlich im eigenen Tab "Projekte" bearbeitbar, der
dieselben Projekte ein zweites Mal auflistete. Er ist in die
Projektliste aufgegangen; gepflegt werden sie jetzt hier. -->
<div class="form-row">
<label>Projektnummer</label>
<input type="text" id="settingsProjNr" placeholder="z. B. 26-4071"
onchange="setPlanProjNr(this.value)"
title="Kennzeichen des Projekts. Hochgeladene Dateien hängen an dieser Nummer — eine Änderung wird deshalb noch einmal abgefragt.">
</div>
<div class="form-row">
<label>Projektname</label>
<input type="text" id="settingsProjName" placeholder="Bezeichnung"
onchange="setPlanProjName(this.value)">
</div>
<!-- Auftragsstatus: Anfrage → Angebot → beauftragt →
abgeschlossen. Der Sprung auf "beauftragt" öffnet den
Beauftragungs-Dialog (js/auftrag.js), weil dazu Honorar und
beauftragte Leistungsphasen gehören. -->
<div class="form-row">
<label>Auftragsstatus</label>
<div id="settingsAuftragsstatus"></div>
</div>
<div class="form-row">
<label>Gruppe</label>
<select id="planGroupSelect" onchange="setPlanGroup(this.value)">
<option>Aktive Projekte</option>
<option>Kleine Projekte</option>
<option>In Anbahnung</option>
<option>Allgemein</option>
</select>
</div>
<div class="form-row">
<label>Amtierende Leistungsphase</label>
<select id="planActiveLpSelect" onchange="setPlanActiveLp(this.value)"></select>
</div>
<div class="form-row">
<label>Honorar (netto)</label>
<input type="number" id="settingsHonorar" min="0" step="100" placeholder="0"
onchange="setHonorarBetrag(this.value)"
title="Vereinbartes Netto-Honorar. Grundlage der Terminplan-Berechnung und dauerhaft am Projekt gespeichert.">
</div>
<!-- Projektgröße: Ersatz für ein fehlendes Honorar. Die
Auslastung rechnet in Stunden und braucht dafür einen
Geldwert; ist keiner hinterlegt, tritt diese Zahl mal dem
Referenzhonorar (Admin → Auslastung) an seine Stelle. -->
<div class="form-row">
<label>Projektgröße</label>
<!-- Zahlenfeld statt Regler: Ein Schieber legt eine
Feinabstufung nahe, die eine grobe Schätzung nicht hat,
und lässt sich schlechter treffen als eine getippte Zahl. -->
<input type="number" id="settingsGroesse" min="0" max="1" step="0.05" value="0"
placeholder="z. B. 0,40" style="max-width:160px"
onchange="setPlanGroesse(this.value)">
<span class="hint" id="settingsGroesseHinweis" style="margin:4px 0 0"></span>
</div>
<div class="form-row">
<label>Schlagwörter</label>
<div class="tag-chip-list" id="settingsProjTags"></div>
</div>
<div class="form-row">
<label>Gewerke</label>
<div id="settingsGewerke" class="beauftragte-lp-grid"></div>
<span class="hint" style="margin:4px 0 0">
Bestimmt, in welcher Gewerke-Besprechung des Wochenberichts dieses Projekt erscheint.
</span>
</div>
<div class="form-row">
<label>Aufwand je Phase</label>
<button type="button" class="secondary small" id="settingsAufwandBtn" onclick="planOpenAufwand()"
title="Gewichtung des Aufwands je Leistungsphase und Rolle — Grundlage der Auslastungsrechnung.">Gewichtung bearbeiten</button>
</div>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Beauftragte Leistungsphasen</h3>
<p class="hint" style="margin-top:0">Welche Leistungsphasen für dieses Projekt beauftragt sind. Grundlage der Terminplan-Berechnung aus dem Honorar; der Wochenbericht zeigt ebenfalls nur diese Phasen.</p>
<div id="settingsBeauftragteLp" class="beauftragte-lp-grid"></div>
</div>
<div class="plan-panel plan-panel-wide">
<h3 class="plan-panel-title">Beteiligte</h3>
<p class="hint" style="margin-top:0">Wer diesem Projekt zugeordnet ist — kräftig die Hauptverantwortung, blass die Unterstützung. Geändert wird die Zuordnung im Wochenbericht.</p>
<div class="board-role-mini" id="planAssignMatrix"></div>
<div class="plan-verweis-row" id="planAssignVerweis"></div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Aufwand je Leistungsphase Modal -->
<div class="modal-bg" id="lpModal" style="display:none">
<div class="modal">
<h2>Aufwand je Leistungsphase</h2>
<p style="color:#888;margin-bottom:6px" id="lpModalProject"></p>
<p class="hint" style="margin-bottom:10px">
Rollenbezogene Gewichtungsfaktoren je Leistungsphase (Standardwerte laut
Vorlage im Code). Die amtierende Leistungsphase wird pro Projekt im Tab
„Projekte" gewählt.
</p>
<div id="lpModalRows"></div>
<div class="toolbar" style="margin-top:12px">
<button onclick="saveLpModal()">Speichern</button>
<button class="secondary" onclick="closeLpModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Neues Projekt Modal (inkl. LP-Aufwand) -->
<!-- Assistent "Neues Projekt" (js/projekt-wizard.js). Er ersetzt den
früheren Dialog #newProjModal, der alle Felder auf einmal zeigte — samt
einer 27-Felder-Matrix für den Aufwand je Phase und Rolle. Der Rumpf
wird vollständig aus JavaScript gefüllt; hier steht nur die Hülle. -->
<div class="modal-bg" id="wizardModal" style="display:none">
<div class="modal wizard-modal">
<div class="wiz-kopf">
<h2>Neues Projekt</h2>
<button type="button" class="wiz-schliessen" onclick="closeNewProjWizard()" title="Abbrechen">×</button>
</div>
<div id="wizardBody"></div>
</div>
</div>
<!-- Beauftragung: der Übergang vom Angebot zum beauftragten Projekt
(js/auftrag.js). Erfasst genau die Angaben, die zur Beauftragung gehören
und die der Terminplan aus dem Honorar braucht (js/plan/plan-honorar.js):
vereinbartes Netto-Honorar und beauftragte Leistungsphasen. -->
<div class="modal-bg" id="beauftragungModal" style="display:none">
<div class="modal">
<h2>Beauftragung</h2>
<p style="color:#888;margin-bottom:2px" id="beauftragungProjekt"></p>
<p class="hint" style="margin:0 0 10px" id="beauftragungStatusHinweis"></p>
<div class="form-row">
<label>Honorar (netto)</label>
<!-- type="number" wie im Honorar-Dialog (#honorarBetrag): Der Browser
liefert den Wert dann immer mit Punkt als Dezimaltrenner, und ein
Tausenderpunkt ("48.500") kann gar nicht erst als 48,5 durchgehen. -->
<input type="number" min="0" step="100" id="beauftragungHonorar" placeholder="z. B. 48500" style="width:100%"
title="Vereinbartes Netto-Honorar in Euro. Grundlage des Terminplans aus dem Honorar.">
</div>
<div class="form-row">
<label>Beauftragte Leistungsphasen</label>
<div id="beauftragungBeauftragteLp" class="beauftragte-lp-grid"></div>
<span class="hint" style="margin:4px 0 0">
Das Honorar wird auf genau diese Phasen verteilt.
</span>
</div>
<!-- Nur sichtbar, solange das Projekt in „In Anbahnung" liegt: Diese
Gruppe wird von der Auslastung nicht eingerechnet. -->
<div class="form-row" id="beauftragungGruppeWrap" style="display:none">
<label class="person-chip-label">
<input type="checkbox" id="beauftragungGruppeCheck">
Projekt aus „In Anbahnung" nach „Aktive Projekte" verschieben
</label>
<span class="hint" style="margin:4px 0 0">
Projekte in „In Anbahnung" zählen in der Auslastung nicht mit.
</span>
</div>
<div class="toolbar" style="margin-top:12px">
<button onclick="saveBeauftragung(false)">Als beauftragt kennzeichnen</button>
<button onclick="saveBeauftragung(true)" title="Speichert und öffnet den Projektbereich, dort „+ Terminplan“">Beauftragen und zum Terminplan</button>
<button class="secondary" onclick="closeBeauftragungModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Neuer Terminplan (js/planung/planung.js) — schlanker Anlege-Dialog der
Planung-Sandbox: nur Name + Art, bewusst kein mehrstufiger Assistent wie
"Neues Projekt" (der verlangt Gewerke/Team/Auftragsstatus, die hier nicht
zutreffen), siehe CLAUDE.md "Planung — projektunabhängige Terminplan-
Sandbox". -->
<div class="modal-bg" id="neuerTerminplanModal" style="display:none">
<div class="modal">
<h2>Neuer Terminplan</h2>
<p class="hint">Unabhängig von einem echten Projekt — für Angebote, Kapazitätstests oder eigene Vorhaben.</p>
<div class="form-row">
<label>Name</label>
<input type="text" id="planungName" placeholder="z. B. Angebot Sporthalle Menden" style="width:100%">
</div>
<div class="form-row">
<label style="display:block;margin-bottom:10px">Wie soll's losgehen?</label>
<div class="planung-choice-cards">
<div class="choice-card" data-art="honorar" onclick="waehlePlanungArt('honorar')">
<span class="choice-card-icon">€</span>
<div class="choice-card-title">Aus Honorar berechnen</div>
<div class="choice-card-desc">Honorar und Leistungsphasen angeben, der vorhandene Terminplan-Rechner erstellt die Aufgaben.</div>
</div>
<div class="choice-card" data-art="leer" onclick="waehlePlanungArt('leer')">
<span class="choice-card-icon">✎</span>
<div class="choice-card-title">Leer beginnen</div>
<div class="choice-card-desc">Direkt in den leeren Terminplan, Aufgaben und Meilensteine von Hand anlegen.</div>
</div>
</div>
</div>
<div class="toolbar" style="margin-top:12px">
<button onclick="erstellePlanungTerminplan()">Anlegen</button>
<button class="secondary" onclick="schliesseNeuerTerminplanDialog()">Abbrechen</button>
</div>
</div>
</div>
<!-- ===================== Dialoge des Projektbereichs =====================
Gehoeren zum Bereich #plan (frueher project-plan.html) und liegen wie alle
anderen Dialoge dieser Seite auf oberster Ebene, damit .modal-bg den
ganzen Bildschirm ueberdeckt. Befuellt werden sie von den Modulen unter
public/js/plan/ (plan-modals.js, plan-templates.js, plan-honorar.js,
plan-history.js). -->
<!-- Verlaufs-Modal: Kommentar-Historie des Projekts über alle Sitzungen -->
<div class="modal-bg" id="planHistoryModal" style="display:none">
<div class="modal modal-wide">
<h2>Verlauf</h2>
<p class="hint" style="margin-top:-4px">Status-Kommentare dieses Projekts aus allen Sitzungen, neueste zuerst. Aufgeführt sind nur Sitzungen, in denen der Text tatsächlich geändert wurde.</p>
<div id="planHistoryList" class="history-list"></div>
<div class="toolbar" style="margin-top:16px">
<button class="secondary" onclick="closeHistoryModal()">Schließen</button>
</div>
</div>
</div>
<!-- Protokoll-Import: externes Protokoll (PDF/Word) hochladen, Punkte werden
serverseitig heuristisch erkannt (POST /project-protokolle/import,
js/plan/plan-protokoll.js) — Ergebnis landet als ganz normales,
editierbares Protokoll, keine eigene Vorschau-Oberfläche nötig. -->
<div class="modal-bg" id="protokollImportModal" style="display:none">
<div class="modal">
<h2>Protokoll importieren</h2>
<p class="hint">Ein extern erstelltes Protokoll (PDF oder Word) hochladen — die
App versucht, die Punkte automatisch zu erkennen. Das Ergebnis ist ein
Entwurf: bitte vor dem Speichern prüfen und bei Bedarf anpassen.</p>
<div class="form-row">
<label>Datei</label>
<input type="file" id="protokollImportFile" accept=".pdf,.docx">
</div>
<div class="form-row">
<label>Erkennung</label>
<select id="protokollImportErkennung"></select>
</div>
<div class="form-row">
<label>Datum</label>
<input type="text" id="protokollImportDatumDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
onclick="openDatePicker({ anchor: this, inputId: 'protokollImportDatum', displayId: 'protokollImportDatumDisplay' })">
<input type="hidden" id="protokollImportDatum">
</div>
<div class="form-row">
<label>Betreff</label>
<input type="text" id="protokollImportBetreff" placeholder="z. B. Baubesprechung TGA">
</div>
<div class="form-row">
<label>Ort</label>
<input type="text" id="protokollImportOrt">
</div>
<span class="field-error" id="protokollImportError" hidden></span>
<!-- Läuft nur bei der KI-Erkennung an, kann dort je nach Anbieter/
eigenem Server (KI_TIMEOUT_MS_LOKAL, server.js) bis zu 30 Minuten
dauern — ohne diesen Hinweis wirkte der Dialog in der Zeit wie
hängengeblieben. -->
<div class="protokoll-import-loading" id="protokollImportLoading" hidden>
<span class="protokoll-import-spinner" aria-hidden="true"></span>
<span>Importiere … bei KI-Erkennung über einen eigenen Server kann dies
bis zu 30 Minuten dauern.</span>
</div>
<div class="toolbar" style="margin-top:12px">
<button onclick="importiereProtokoll()">Importieren</button>
<button class="secondary" onclick="schliesseProtokollImportDialog()">Abbrechen</button>
</div>
</div>
</div>
<!-- Vorlagen-Dialog: Standard-Aufgaben einer Leistungsphase einfügen.
Eigenständiger Dialog — der frühere gemeinsame Dialog mit Modus-Umschalter
("Aus Vorlage" / "Aus Honorar") wurde in zwei getrennte Dialoge aufgeteilt,
die je eine eigene Schaltfläche in der GANTT-Toolbar haben. Damit steht auf
dem Knopf, was dahinter passiert, und keine der beiden Masken trägt die
Eingaben der jeweils anderen mit. -->
<div class="modal-bg" id="lphTemplateModal" style="display:none">
<div class="modal modal-wide">
<h2>Aufgaben aus Vorlage</h2>
<div class="template-head-grid">
<div class="form-row">
<label>Leistungsphase</label>
<select id="templateLphSelect" onchange="renderTemplateList()"></select>
</div>
<div class="form-row">
<label>Startdatum</label>
<input type="text" id="templateStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
onclick="openDatePicker({ anchor: this, inputId: 'templateStart', displayId: 'templateStartDisplay', onApply: renderTemplateList })">
<input type="hidden" id="templateStart">
</div>
</div>
<label class="form-row" style="margin-bottom:4px"><span>Schritte</span></label>
<div id="templateTaskList" class="todo-depends-list"></div>
<!-- Gepflegt werden die Vorlagen im Bereich „Einstellungen"; hier werden
sie nur noch eingefügt. Der Verweis erscheint nur für Administratoren
(renderTemplateHint() in js/plan/plan-templates.js). -->
<p class="hint" id="templateAdminHint" style="display:none">
Vorlagen pflegen: <a href="/Einstellungen" onclick="closeTemplateModal();showTab('settings');return false;">Einstellungen → Aufgaben-Vorlagen</a>
</p>
<div class="toolbar" style="margin-top:16px">
<button onclick="applyTemplate()">Aufgaben einfügen</button>
<button class="secondary" onclick="closeTemplateModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Honorar-Dialog: kompletten Terminplan aus dem Honorar errechnen
(js/plan/plan-honorar.js). Zweiter der beiden getrennten Dialoge, siehe
Kommentar beim Vorlagen-Dialog oben. -->
<div class="modal-bg" id="honorarPlanModal" style="display:none">
<div class="modal modal-wide">
<h2>Terminplan aus Honorar</h2>
<div class="template-head-grid">
<div class="form-row">
<label>Honorar (netto)</label>
<input type="number" id="honorarBetrag" min="0" step="100" placeholder="0"
onchange="setHonorarBetrag(this.value)"
title="Vereinbartes Netto-Honorar. Wird am Projekt gespeichert und bleibt erhalten.">
</div>
<div class="form-row">
<label>Beginn der ersten Leistungsphase</label>
<input type="text" id="honorarStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
onclick="openDatePicker({ anchor: this, inputId: 'honorarStart', displayId: 'honorarStartDisplay', onApply: renderHonorarPlan })">
<input type="hidden" id="honorarStart">
</div>
</div>
<div class="form-row">
<label>Beauftragte Leistungsphasen</label>
<div id="honorarBeauftragteLp" class="beauftragte-lp-grid"></div>
</div>
<label class="honorar-check" title="Sind nur einzelne Phasen beauftragt, ist das vereinbarte Honorar das Honorar für genau diese Phasen. Ohne Normierung bliebe ein Teil des Honorars unverplant.">
<input type="checkbox" id="honorarNormieren" checked onchange="renderHonorarPlan()">
Honorar auf die beauftragten Phasen normieren
</label>
<div id="honorarPlanTable" class="honorar-plan-wrap"></div>
<!-- Anteile und Stundensatz werden im Bereich „Einstellungen" gepflegt;
hier wird damit nur gerechnet. Der Verweis erscheint nur für
Administratoren (renderHonorarHint() in js/plan/plan-honorar.js). -->
<p class="hint" id="honorarAdminHint" style="display:none">
Anteile und Stundensatz pflegen: <a href="/Einstellungen" onclick="closeHonorarModal();showTab('settings');return false;">Einstellungen → Honorar</a>
</p>
<div class="toolbar" style="margin-top:16px">
<button onclick="applyHonorarPlan()">Terminplan einfügen</button>
<button class="secondary" onclick="closeHonorarModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Meilenstein-Modal -->
<div class="modal-bg" id="milestoneModal" style="display:none">
<div class="modal">
<h2>Meilenstein</h2>
<div class="form-row">
<label>Name</label>
<input type="text" id="milestoneEditName" placeholder="Name">
</div>
<div class="form-row">
<label>Datum</label>
<!-- Getrennte Felder wie bei allen anderen Datumseingaben der Seite:
sichtbares Anzeigefeld (deutsches Format) + verstecktes ISO-Feld.
Vorher war beides dasselbe Feld, weshalb dort "2026-03-15" stand. -->
<input type="text" id="milestoneEditDateDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
onclick="openDatePicker({ anchor: this, inputId: 'milestoneEditDate', displayId: 'milestoneEditDateDisplay' })">
<input type="hidden" id="milestoneEditDate">
</div>
<div class="form-row">
<label>Position</label>
<select id="milestoneEditPosition">
<option value="">Oben in der Projektzeile</option>
</select>
</div>
<div class="form-row">
<label>Vorgänger (mehrfach möglich)</label>
<div id="milestoneEditDependsOn" class="todo-depends-list"></div>
</div>
<div class="toolbar">
<button onclick="saveMilestoneEdit()">Speichern</button>
<button class="danger" onclick="deleteMilestoneEdit()">Löschen</button>
<button class="secondary" onclick="closeMilestoneModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- TODO-Modal (Stufe B: Status-Segment statt Erledigt+Fortschritt, Zuständige
als Personen-Chips, Abhängigkeiten eingeklappt mit Zähler/Suche,
"Speichern & Neu", Fehler am Feld statt alert(), Esc/Strg+Enter). Befüllt
und ausgewertet von openTodoModal()/saveTodoEdit() in js/plan/plan-modals.js. -->
<div class="modal-bg" id="todoModal" style="display:none">
<div class="modal">
<div class="modal-head">
<h2 id="todoModalTitle">Aufgabe bearbeiten</h2>
<span class="modal-kbd" id="todoModalKbd">Esc · Strg+↵</span>
</div>
<div class="form-row">
<label for="todoEditTitle">Titel</label>
<input type="text" id="todoEditTitle" placeholder="Titel">
<span class="field-error" id="todoEditTitleError" hidden></span>
</div>
<div class="form-row">
<label>Zeitraum</label>
<div class="field-inline">
<input type="text" id="todoEditRange" class="range-picker-display" placeholder="Start – Ende wählen…" readonly
onclick="openRangePicker({ anchor: this, startInputId: 'todoEditStart', endInputId: 'todoEditEnd', displayId: 'todoEditRange', onApply: refreshTodoDuration })">
<span class="field-badge" id="todoEditDuration" hidden></span>
</div>
<input type="hidden" id="todoEditStart">
<input type="hidden" id="todoEditEnd">
<span class="field-error" id="todoEditRangeError" hidden></span>
<p class="hint">Ohne Zeitraum erscheint die Aufgabe nur unter „Aufgaben“, nicht im Terminplan.</p>
</div>
<div class="form-row">
<label>Zuständige</label>
<div id="todoEditAssignees" class="person-chips"></div>
</div>
<div class="form-row">
<label>Status</label>
<div class="segmented" id="todoEditStatus">
<button type="button" data-status="offen" aria-pressed="false" onclick="setTodoEditStatus('offen')"><span class="segmented-dot"></span>Offen</button>
<button type="button" data-status="arbeit" aria-pressed="false" onclick="setTodoEditStatus('arbeit')"><span class="segmented-dot"></span>In Arbeit</button>
<button type="button" data-status="fertig" aria-pressed="false" onclick="setTodoEditStatus('fertig')"><span class="segmented-dot"></span>Erledigt</button>
</div>
<div class="progress-row" id="todoEditProgressRow">
<input type="range" id="todoEditProgress" min="0" max="100" step="5" value="0" aria-label="Fortschritt"
oninput="onTodoProgressInput(this)">
<output id="todoEditProgressVal">0 %</output>
</div>
</div>
<div class="form-row">
<details class="fold" id="todoEditDependsFold">
<summary>
<span class="fold-caret">▶</span>
<span>Abhängigkeiten</span>
<span class="fold-count zero" id="todoEditDependsCount">0</span>
</summary>
<div class="fold-body">
<div class="fold-search">
<span class="fold-search-icon">⌕</span>
<input type="text" id="todoEditDependsSearch" placeholder="Aufgabe oder Meilenstein suchen…" oninput="filterTodoDepends()">
</div>
<div id="todoEditDependsOn" class="todo-depends-list flat" onchange="refreshTodoDependsCount()"></div>
</div>
</details>
</div>
<div class="modal-footer">
<div class="toolbar">
<button onclick="saveTodoEdit()">Speichern</button>
<button class="secondary" id="todoEditSaveNew" onclick="saveTodoEdit(true)">Speichern &amp; Neu</button>
</div>
<div class="toolbar">
<button class="danger" id="todoEditDelete" onclick="deleteTodoEdit()">Löschen</button>
<button class="secondary" onclick="closeTodoModal()">Abbrechen</button>
</div>
</div>
</div>
</div>
<!-- Abwesenheits-Modal (Urlaube der Projektbeteiligten) -->
<div class="modal-bg" id="absenceModal" style="display:none">
<div class="modal">
<h2 id="absenceModalTitle">Abwesenheiten</h2>
<div class="form-row">
<div id="absenceModalList"></div>
</div>
<div class="absence-form" id="absenceModalForm">
<input type="text" id="absenceEditRange" class="range-picker-display" placeholder="Zeitraum wählen…" readonly
onclick="openRangePicker({ anchor: this, startInputId: 'absenceEditStart', endInputId: 'absenceEditEnd', displayId: 'absenceEditRange' })">
<input type="hidden" id="absenceEditStart">
<input type="hidden" id="absenceEditEnd">
<select id="absenceEditType">
<option>Urlaub</option>
<option>Krank</option>
<option>Fortbildung</option>
<option>Sonstiges</option>
</select>
<input type="text" id="absenceEditNote" placeholder="Notiz (optional)" style="width:140px">
<button class="secondary small" onclick="addPlanAbsence()">+ Abwesenheit</button>
</div>
<div class="toolbar" style="margin-top:12px">
<button class="secondary" onclick="closeAbsenceModal()">Schließen</button>
</div>
</div>
</div>
<!-- ===================== Dialoge der Benutzerverwaltung =====================
Gehoeren zum Abschnitt „Benutzerverwaltung" des Bereichs #admin (frueher
die eigenstaendige Seite admin-users.html) und liegen wie alle anderen
Dialoge auf oberster Ebene, damit .modal-bg den ganzen Bildschirm
ueberdeckt. Befuellt werden sie von js/admin-users.js. -->
<!-- Neuer-Benutzer-Modal -->
<div class="modal-bg" id="newUserModal" style="display:none">
<div class="modal">
<h2>Neuer Benutzer</h2>
<div class="form-row">
<label>Benutzername</label>
<input type="text" id="newUserUsername" placeholder="z. B. mmustermann" autocomplete="off">
</div>
<div class="form-row">
<label>Anzeigename</label>
<input type="text" id="newUserDisplayName" placeholder="z. B. Max Mustermann" oninput="onNewUserDisplayNameInput()">
</div>
<div class="form-row">
<label>E-Mail</label>
<input type="email" id="newUserEmail" placeholder="max.mustermann@beispiel.de">
</div>
<div class="form-row">
<label>Rolle</label>
<select id="newUserRole">
<option value="member">Mitglied</option>
<option value="admin">Administrator</option>
</select>
</div>
<div class="form-row">
<label>Abteilung</label>
<select id="newUserDepartment" required></select>
</div>
<div class="form-row">
<label>Kürzel</label>
<input type="text" id="newUserInitials" placeholder="wird aus dem Anzeigenamen vorgeschlagen" maxlength="4">
<p class="hint">Kurzform fürs Projektboard (2-4 Zeichen), wird vorgeschlagen und kann angepasst werden.</p>
</div>
<div class="form-row">
<label>Wochenstunden</label>
<input type="number" id="newUserCapacity" min="0" max="80" step="0.5" placeholder="z. B. 40">
<p class="hint">Vertragliche Arbeitszeit. Die Auslastung misst die Person daran: Eine halbe Stelle gilt schon bei halber Last als voll ausgelastet. Leer = volle Stelle.</p>
</div>
<div class="form-row">
<label>Person-ID</label>
<input type="text" id="newUserPersonId" placeholder="wird aus dem Anzeigenamen vorgeschlagen">
<p class="hint">Verknüpft den Account mit einem Eintrag in der Projektbeteiligten-Liste (personIdForName). Bei Kollisionen anpassen.</p>
</div>
<div class="form-row">
<label>Fachrolle(n)</label>
<div class="role-checkboxes" id="newUserRoleCheckboxes"></div>
</div>
<div class="form-row">
<label>Passwort</label>
<div class="role-checkboxes">
<label><input type="radio" name="newUserPwMode" value="generate" checked onchange="onNewUserPwModeChange()"> Zufällig generieren</label>
<label><input type="radio" name="newUserPwMode" value="manual" onchange="onNewUserPwModeChange()"> Manuell festlegen</label>
</div>
<input type="password" id="newUserPassword" placeholder="mind. 8 Zeichen" style="display:none;margin-top:8px" autocomplete="new-password">
</div>
<div id="newUserError" style="color:#c00;"></div>
<div class="toolbar" style="margin-top:12px">
<button onclick="saveNewUser()">Anlegen</button>
<button class="secondary" onclick="closeNewUserModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Benutzer-bearbeiten-Modal -->
<div class="modal-bg" id="editUserModal" style="display:none">
<div class="modal">
<h2>Benutzer bearbeiten</h2>
<p class="hint" id="editUserUsernameLabel" style="margin-bottom:10px"></p>
<div class="form-row">
<label>Anzeigename</label>
<input type="text" id="editUserDisplayName">
</div>
<div class="form-row">
<label>E-Mail</label>
<input type="email" id="editUserEmail">
</div>
<div class="form-row">
<label>Rolle</label>
<select id="editUserRole">
<option value="member">Mitglied</option>
<option value="admin">Administrator</option>
</select>
</div>
<div class="form-row">
<label>Abteilung</label>
<select id="editUserDepartment" required></select>
</div>
<div class="form-row">
<label>Kürzel</label>
<input type="text" id="editUserInitials" maxlength="4">
</div>
<div class="form-row">
<label>Wochenstunden</label>
<input type="number" id="editUserCapacity" min="0" max="80" step="0.5" placeholder="leer = volle Stelle">
<p class="hint">Vertragliche Arbeitszeit; Grundlage der Auslastungs-Ampel.</p>
</div>
<div class="form-row">
<label>Person-ID</label>
<input type="text" id="editUserPersonId">
</div>
<div class="form-row">
<label>Fachrolle(n)</label>
<div class="role-checkboxes" id="editUserRoleCheckboxes"></div>
</div>
<div class="form-row">
<label>Abwesenheiten</label>
<div id="editUserAbsenceList" class="absence-list"></div>
<div class="absence-form">
<input type="date" id="editUserAbsenceStart">
<input type="date" id="editUserAbsenceEnd">
<select id="editUserAbsenceType">
<option>Urlaub</option>
<option>Krank</option>
<option>Fortbildung</option>
<option>Sonstiges</option>
</select>
<input type="text" id="editUserAbsenceNote" placeholder="Notiz (optional)" style="width:140px">
<button class="secondary small" onclick="addEditUserAbsence()">+ Abwesenheit</button>
</div>
<div id="editUserAbsenceError" style="color:#c00;"></div>
</div>
<div id="editUserError" style="color:#c00;"></div>
<div class="toolbar" style="margin-top:12px">
<button onclick="saveEditUser()">Speichern</button>
<button class="secondary" onclick="closeEditUserModal()">Abbrechen</button>
</div>
</div>
</div>
<!-- Aufgabe aus dem Kommentar (js/wochenbericht/wochenbericht.js).
Macht aus einem im Meeting gefassten Beschluss eine echte Aufgabe im Plan
des Projekts — bis dahin blieben solche Absprachen Fließtext im Kommentar
und tauchten im Terminplan nie auf. -->
<div class="modal-bg" id="todoKommentarModal" style="display:none">
<div class="modal">
<h2>Aufgabe anlegen</h2>
<p class="hint" id="todoKommentarProjekt" style="margin-top:-4px"></p>
<div class="form-row">
<label for="todoKommentarTitel">Was ist zu tun?</label>
<input type="text" id="todoKommentarTitel" placeholder="z. B. „LV an T.W. zur Prüfung">
<span class="hint" style="margin:4px 0 0">Vorgeschlagen ist die erste Zeile des Kommentars.</span>
</div>
<div class="form-row">
<label>Zuständige</label>
<div id="todoKommentarPersonen" class="person-chips"></div>
</div>
<div class="form-row">
<label for="todoKommentarEnde">Fällig am</label>
<input type="date" id="todoKommentarEnde">
<span class="hint" style="margin:4px 0 0">Vorgeschlagen ist die kommende Woche.</span>
</div>
<div class="field-error" id="todoKommentarFehler" hidden></div>
<div class="toolbar">
<button type="button" onclick="saveTodoAusKommentar()">Aufgabe anlegen</button>
<button type="button" class="secondary" onclick="closeTodoAusKommentar()">Abbrechen</button>
</div>
</div>
</div>
<!-- Protokoll der Sitzung (js/wochenbericht/wochenbericht.js). Stellt
zusammen, was sich gegenüber der vorherigen Sitzung geändert hat, samt
der heute angelegten Aufgaben. -->
<div class="modal-bg" id="protokollModal" style="display:none">
<div class="modal modal-wide">
<h2>Protokoll der Sitzung</h2>
<p class="hint" style="margin-top:-4px">
<span id="protokollAnzahl"></span> — unveränderte Projekte stehen nicht drin.
</p>
<textarea id="protokollText" rows="16" class="protokoll-text" spellcheck="false"></textarea>
<div class="toolbar" style="margin-top:12px">
<button type="button" onclick="kopiereProtokoll()">In die Zwischenablage</button>
<button type="button" class="secondary" onclick="protokollAlsMail()">Als Mail öffnen</button>
<span class="hint" id="protokollHinweis" style="margin:0"></span>
<span class="comment-foot-spacer"></span>
<button type="button" class="secondary" onclick="closeBerichtProtokoll()">Schließen</button>
</div>
</div>
</div>
<!-- Passwort ändern (js/auth.js). Zwei Betriebsarten: freiwillig aus dem
Bereich „Einstellungen" heraus und ERZWUNGEN, wenn das Konto
must_change_password trägt — dann ohne Abbrechen. Der Server verlangt in
beiden Fällen das aktuelle Passwort. -->
<div class="modal-bg" id="passwordModal" style="display:none">
<div class="modal">
<h2>Passwort ändern</h2>
<p class="hint" id="passwordForcedHint" hidden>
Für dieses Konto ist ein Passwortwechsel erforderlich — das Startpasswort
wurde von einem Administrator vergeben. Erst danach geht es weiter.
</p>
<form id="passwordForm" onsubmit="submitPasswordChange(event)">
<div class="form-row">
<label for="passwordCurrent">Aktuelles Passwort</label>
<input type="password" id="passwordCurrent" autocomplete="current-password" required>
</div>
<div class="form-row">
<label for="passwordNew">Neues Passwort</label>
<input type="password" id="passwordNew" autocomplete="new-password" minlength="8" required>
<span class="hint" style="margin:4px 0 0">Mindestens 8 Zeichen.</span>
</div>
<div class="form-row">
<label for="passwordRepeat">Neues Passwort wiederholen</label>
<input type="password" id="passwordRepeat" autocomplete="new-password" minlength="8" required>
</div>
<div class="field-error" id="passwordError" hidden></div>
<div class="toolbar">
<button type="submit">Passwort ändern</button>
<button type="button" class="secondary" id="passwordCancelBtn" onclick="closePasswordModal()">Abbrechen</button>
</div>
</form>
</div>
</div>
<!-- Login-Overlay: blockiert die gesamte App ohne gültige Session (siehe js/auth.js) -->
<div id="loginOverlay" style="display:none;" class="modal-bg">
<div class="modal">
<h2>Anmelden</h2>
<form id="loginForm">
<div class="form-row"><label>Benutzername</label><input type="text" id="loginUsername" autocomplete="username" required></div>
<div class="form-row"><label>Passwort</label><input type="password" id="loginPassword" autocomplete="current-password" required></div>
<div id="loginError" style="color:#c00;"></div>
<div class="toolbar"><button type="submit">Anmelden</button></div>
</form>
</div>
</div>
<script src="/js/globals.js?v=17"></script>
<script src="/js/utils.js?v=10"></script>
<script src="/js/icons.js?v=2"></script>
<script src="/js/ui.js?v=9"></script>
<script src="/js/date-picker.js?v=5"></script>
<script src="/js/server.js?v=5"></script>
<script src="/js/auth.js?v=3"></script>
<script src="/js/store.js?v=9"></script>
<script src="/js/weeks.js?v=11"></script>
<script src="/js/board/board.js?v=23"></script>
<script src="/js/wochenbericht/wochenbericht.js?v=16"></script>
<script src="/js/people/people.js?v=7"></script>
<script src="/js/planung/planung.js?v=1"></script>
<script src="/js/projects/projects.js?v=7"></script>
<script src="/js/load/load.js?v=16"></script>
<!-- auftrag.js: Auftragsstatus und Dialog "Beauftragung". Nach load.js, weil
der Neues-Projekt-Dialog dort die Statusauswahl mit AUFTRAGSSTATUS füllt. -->
<script src="/js/auftrag.js?v=1"></script>
<!-- Assistent "Neues Projekt": braucht auftrag.js (Auftragsstatus) und
load.js (faktorFormat), steht deshalb dahinter. -->
<script src="/js/projekt-wizard.js?v=1"></script>
<script src="/js/dashboard/dashboard.js?v=5"></script>
<script src="/js/settings/settings.js?v=9"></script>
<script src="/js/admin/admin.js?v=11"></script>
<script src="/js/admin-users.js?v=3"></script>
<script src="/js/gantt.js?v=6"></script>
<!-- Module des Projektbereichs (#plan). Sie liefen frueher nur in
project-plan.html; seit der Projektbereich ein Bereich dieser Seite ist,
gehoeren sie hierher. Reihenfolge wie gehabt: plan-state.js legt
window.PlanApp an, plan-history.js muss VOR plan-build-gantt.js stehen
(das seine Vorwochen-Helfer nutzt), und alle zusammen muessen nach
js/gantt.js kommen, dessen buildGanttHtml() plan-build-gantt.js
umhuellt. -->
<script src="/js/plan/plan-state.js?v=12"></script>
<script src="/js/plan/plan-sections.js?v=5"></script>
<script src="/js/plan/plan-info.js?v=7"></script>
<script src="/js/plan/plan-history.js?v=3"></script>
<script src="/js/plan/plan-build-gantt.js?v=7"></script>
<script src="/js/plan/plan-gantt.js?v=9"></script>
<script src="/js/plan/plan-drag.js?v=6"></script>
<script src="/js/plan/plan-modals.js?v=6"></script>
<script src="/js/plan/plan-tasks.js?v=2"></script>
<script src="/js/plan/plan-files.js?v=3"></script>
<script src="/js/plan/plan-protokoll.js?v=8"></script>
<script src="/js/plan/plan-templates.js?v=4"></script>
<script src="/js/plan/plan-honorar.js?v=6"></script>
<!-- plan-leistung.js muss NACH plan-honorar.js stehen: es umhuellt dessen
invalidateHonorarConfig(), damit eine Aenderung der Anteile auch seinen
Zwischenspeicher verwirft. -->
<script src="/js/plan/plan-leistung.js?v=1"></script>
<script src="/js/plan/plan-settings.js?v=7"></script>
<script src="/js/plan/plan-schedule.js?v=5"></script>
<script src="/js/showmode.js?v=5"></script>
<script src="/js/tabs-auth.js?v=13"></script>
<script src="/js/pdf.js?v=17"></script>
<script src="/js/backup.js?v=1"></script>
<script src="/js/import.js?v=1"></script>
<script src="/js/init.js?v=11"></script>
</body>
</html>