Files
projektuebersicht/public/App.html
T
erikandClaude Opus 5.5 7d65ff813f Notizen je Projekt statt Reiter Leistungsphasen
- Neuer Reiter „Notizen“ im Projektbereich: Liste mit Suche und
  angehefteten Notizen, Editor mit Formatierung, automatisches Speichern,
  Mail per Drag & Drop oder Strg+V als neue Notiz, live bei allen
- Reiter „Leistungsphasen“ entfernt (Phasen weiter im Terminplan)
- Behoben: Umlaute in .eml-Dateien ohne Transfer-Kodierung gingen
  verloren (betraf auch „Termin aus Mail“)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:00:20 +01:00

2056 lines
124 KiB
HTML
Raw Permalink 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, Wandtafel — 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, js/wand/wand.js (Wandtafel, braucht load.js/
auftrag.js/dashboard.js), dann js/gantt.js mit den darauf aufbauenden
js/plan/*-Modulen des Projektbereichs und zuletzt 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="icon" type="image/svg+xml" href="/logo.svg">
<link rel="icon" type="image/png" href="/logo.png">
<link rel="alternate icon" href="/logo.ico">
<link rel="apple-touch-icon" href="/logo.png">
<link rel="stylesheet" href="/styles.css?v=111">
<link rel="stylesheet" href="/plan-styles.css?v=51">
<!-- Glas-Darstellung (public/glas.css, als letztes Stylesheet): greift nur mit
der Klasse html.glas. Gesetzt wird sie hier, vor dem ersten Zeichnen,
damit die Seite nicht erst ohne Glas aufblitzt; abschaltbar je Browser
unter Einstellungen → Mein Konto (localStorage projuebersicht-glas). -->
<link rel="stylesheet" href="/glas.css?v=14">
<script>
try { if(localStorage.getItem('projuebersicht-glas') !== 'aus') document.documentElement.classList.add('glas'); }
catch(e){ document.documentElement.classList.add('glas'); }
// Hell/Dunkel ebenfalls vor dem ersten Zeichnen setzen (wie loadTheme() in
// js/ui.js) — sonst blitzt die Seite bis zum Ende des Ladens hell auf.
try {
var thema = localStorage.getItem('projuebersicht-theme');
if(thema === 'dark' || thema === 'light') document.documentElement.setAttribute('data-theme', thema);
} catch(e){}
</script>
<!-- Outfit: die Kalenderwoche der Login-Seite und die Wandtafel (Ziffern,
Überschriften). Fällt ohne Internetzugang automatisch auf die System-
Schrift zurück (font-family-Stack in styles.css), bricht also nichts
offline. Fredoka/Nunito Sans der früheren Wandtafel sind entfallen. -->
<!-- Externe Dateien blockieren den Seitenaufbau nicht mehr (Ladezeit auf dem
Pi): die Schrift wird nachgeladen, pdfmake (rund 2 MB, nur für den
PDF-Export) erst nach dem Laden der Seite, vfs_fonts nach pdfmake. Die
Exporte prüfen window.pdfMake ohnehin. -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&display=swap" onload="this.onload=null;this.rel='stylesheet'">
<script>
window.addEventListener('load', function(){
var lade = function(src, danach){ var s = document.createElement('script'); s.src = src; if(danach) s.onload = danach; document.head.appendChild(s); };
lade('https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/pdfmake.min.js', function(){
lade('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="/logo.svg" 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">
<!-- Glocke: Benachrichtigungen über neu zugewiesene Aufgaben
(js/benachrichtigungen.js). -->
<div class="glocke">
<button class="theme-toggle-circle" id="glockeBtn" onclick="toggleGlocke()" title="Benachrichtigungen" aria-expanded="false" aria-controls="glockeMenue" hidden>
<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="M6 9.5a6 6 0 0 1 12 0c0 5 2 6.5 2 6.5H4s2-1.5 2-6.5Z"/><path d="M10 19.5a2 2 0 0 0 4 0"/></svg></span>
<span class="glocke-zahl" id="glockeZahl" hidden>0</span>
</button>
<div class="glocke-menue" id="glockeMenue" role="dialog" aria-label="Benachrichtigungen" hidden></div>
</div>
<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>
<!-- Abwesenheiten (js/abwesenheit/abwesenheit.js): sichtbar mit einem der
vier Abwesenheits-Rechte (data-recht mit "|" = eines davon genügt). -->
<button class="nav-item tab nur-recht" data-recht="team.abw|team.abwProjekte|team.abwGewerk|team.abwAlle" data-tab="abwesenheit" onclick="showTab('abwesenheit')" title="Abwesenheiten">
<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="15.5" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="8" y1="3" x2="8" y2="6.5"/><line x1="16" y1="3" x2="16" y2="6.5"/><line x1="8.5" y1="13" x2="15.5" y2="17.5"/><line x1="15.5" y1="13" x2="8.5" y2="17.5"/></svg></span></span><span class="nav-label">Abwesenheiten</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 nur-recht" data-recht="wz.planung">Planung</div>
<button class="nav-item tab nur-recht" data-recht="wz.planung" data-tab="planung" onclick="showTab('planung')" title="Terminplan">
<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">Terminplan</span>
</button>
<!-- Studenten: eigener, dritter Abschnitt (wie "Planung" nicht Teil
von "Navigation") — bislang nur ein Werkzeug, der Plan Checker:
lädt einen Bauplan als PDF hoch und lässt ihn von einer KI gegen
eine Liste von Merkmalen prüfen (Maßketten, Plankopf, ...),
siehe js/planchecker/planchecker.js. -->
<div class="sidebar-section-label nur-recht" data-recht="wz.checker">Studenten</div>
<button class="nav-item tab nur-recht" data-recht="wz.checker" data-tab="planchecker" onclick="showTab('planchecker')" title="Plan Checker">
<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="4" y="3" width="16" height="18" rx="2"/><path d="m9 12 2 2 4-4"/></svg></span></span><span class="nav-label">Plan Checker</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>
<!-- Der Bereich „Werkzeuge“ ist entfallen (Eriks Wunsch): „+ Neues
Projekt“ steht in der Projektliste („+ Projekt“), die Wandtafel
braucht nur das Wandtafel-Konto (landet dort automatisch, sonst
über /Wandtafel erreichbar). -->
<!-- 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="rollen" onclick="showAdminSection('rollen')">Rollen &amp; Rechte</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="planchecker" onclick="showAdminSection('planchecker')">Plan-Checker-Merkmale</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="teams" onclick="showAdminSection('teams')">Teams</button>
<button class="menu-item sidebar-subitem" data-admin-section="papierkorb" onclick="showAdminSection('papierkorb')">Papierkorb</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">
<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 nur-projektleitung" 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 -->
<!-- ABWESENHEITEN (js/abwesenheit/abwesenheit.js) -->
<div id="abwesenheit" style="display:none"></div>
<div id="load" style="display:none">
<div class="section-title">Auslastung der Projektbeteiligten</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">Terminplan</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>
<!-- PLAN CHECKER (js/planchecker/planchecker.js) — Studenten-Werkzeug: lädt
einen Bauplan als PDF hoch und lässt eine KI ihn gegen die unter
Admin → Plan-Checker-Merkmale gepflegten Merkmale prüfen. Weder die
Datei noch das Ergebnis werden gespeichert — ein Ad-hoc-Werkzeug, kein
Dokumentenarchiv (siehe POST /plan-checker-pruefen, server.js). -->
<div id="planchecker" style="display:none">
<div class="section-title">Plan Checker</div>
<p class="hint">Lädt einen Bauplan als PDF hoch und prüft ihn mit einer KI gegen die hinterlegten Merkmale.</p>
<div class="pc-layout">
<div class="pc-panel">
<label class="pc-drop" id="planCheckerDrop" for="planCheckerFile"
ondragover="planCheckerDragOver(event)" ondragleave="planCheckerDragLeave(event)" ondrop="planCheckerDrop(event)">
<span id="planCheckerDropText">PDF hierher ziehen oder klicken</span>
<div class="pc-drop-name" id="planCheckerDateiname" hidden></div>
</label>
<input type="file" id="planCheckerFile" accept="application/pdf" hidden onchange="planCheckerDateiGewaehlt(this.files[0])">
<div class="pc-section">
<label class="pc-label">Geprüfte Merkmale</label>
<div id="planCheckerMerkmaleListe"><span class="hint" style="margin:0">Lade Merkmale…</span></div>
<div class="pc-zusatz">
<input type="text" class="pc-input" id="planCheckerZusatz" placeholder="Weiteres Merkmal für diese Prüfung…">
</div>
</div>
<div class="pc-section">
<label class="pc-label">Projekt (optional)</label>
<select id="planCheckerProjekt" class="pc-select" style="width:100%">
<option value="">Kein Projekt wählen</option>
</select>
<p class="hint" style="margin:4px 0 0">Prüft zusätzlich, ob der Plankopf zu diesem Projekt passt (Nummer/Bezeichnung).</p>
</div>
<div class="pc-section">
<label class="pc-label">KI-Anbieter</label>
<select id="planCheckerAnbieter" class="pc-select" style="width:100%"></select>
</div>
<div class="pc-section">
<button type="button" class="pc-btn-primary pc-btn" id="planCheckerPruefenBtn" onclick="planCheckerPruefen()" style="width:100%">Plan prüfen</button>
<p class="hint pc-error" id="planCheckerError" hidden></p>
</div>
</div>
<div class="pc-panel pc-results">
<div class="pc-results-empty" id="planCheckerLeer">
<span class="big">Noch kein Plan geprüft</span>
PDF auswählen und auf „Plan prüfen" klicken.
</div>
<div id="planCheckerErgebnis"></div>
</div>
</div>
</div>
<!-- Plan Checker: Planausschnitt-Vorschau (js/planchecker/planchecker.js,
planCheckerZeigeScreenshot()) — zeigt den Screenshot vergrößert, den
die KI für ein Merkmal als Beleg gewählt hat. Ein einfaches In-Page-
Modal statt eines <a target="_blank"> auf die data:-URL: Chrome
blockiert das Öffnen einer data:-URL als Top-Level-Navigation in einem
neuen Tab, ein Link blieb deshalb wirkungslos. -->
<div class="modal-bg" id="pcShotModal" style="display:none">
<div class="modal modal-wide">
<h2 id="pcShotModalTitel">Planausschnitt</h2>
<img id="pcShotModalImg" src="" alt="" style="width:100%;height:auto;border-radius:var(--r-md);display:block">
<div class="toolbar" style="margin-top:16px">
<button class="secondary" onclick="planCheckerSchliesseScreenshot()">Schließen</button>
</div>
</div>
</div>
<!-- DASHBOARD (Begrüßung/Uhr, Meine Aufgaben, Freigaben, Kollegen-Abwesenheiten) -->
<div id="dashboard" style="display:none">
<div id="dashboardContent"></div>
</div>
<!-- WANDTAFEL (js/wand/wand.js) — gewerke- und nutzerübergreifendes
Kachel-Dashboard für ein dauerhaft laufendes Touch-Display. Ersetzt
den früheren Show Mode, der nur die gerade angemeldete Sitzung
großflächig klonte und keine echte Zusammenfassung über Gewerke/
Nutzer hinweg bot. showTab() blendet beim Betreten zusätzlich
Seitenleiste und Kopfzeile aus (body.wandtafel-aktiv, styles.css) —
das Kiosk-Konto hat echte Schreibrechte, eine zufällige Berührung der
Navigation soll dort nicht versehentlich in einen bearbeitbaren
Bereich führen. Der kleine Rücksprung-Link dient einer Vorschau durch
reguläres Personal; das Kiosk-Konto selbst braucht ihn nie. -->
<div id="wand" style="display:none">
<div class="wand-kopfzeile">
<button type="button" class="wand-zurueck" onclick="showTab((typeof previousTab==='string' && previousTab && previousTab!=='wand') ? previousTab : DEFAULT_TAB)">← Zur Übersicht</button>
<span id="wandIdleCountdown" class="wand-idle-countdown"></span>
</div>
<div id="wandContent" class="wand-grid-wrap"></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>
<button class="secondary" onclick="einfuehrungStarten()">Rundgang starten</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). -->
<!-- Kalender-Abo für Outlook (renderSettingsKalender(), js/settings/settings.js;
Server: /mein-kalender, /kalender/<token>.ics) -->
<section class="settings-card" id="settingsKalenderCard">
<h3 class="settings-card-title">Kalender in Outlook</h3>
<p class="hint">Ihre offenen Aufgaben, die Meilensteine Ihrer Projekte und Ihre
Abwesenheiten als Kalender in Outlook — nur zum Ansehen, geändert wird weiter hier.
Outlook aktualisiert ihn selbst (je nach Outlook stündlich bis alle paar Stunden).</p>
<div id="settingsKalender"></div>
</section>
<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>
<!-- Modellname: nur nötig, wenn der eigene Server/Anbieter einen
bestimmten Namen erwartet (z. B. ein eigenständiger Anbieter
wie Mammouth.ai mit eigenem Modell-Katalog, bei dem der feste
OpenAI-Name "gpt-4o"/"gpt-4o-mini" nicht existiert) - leer
bedeutet weiterhin die hier hinterlegte Vorgabe. -->
<label>Modellname (optional)</label>
<div class="ai-provider-row">
<input type="text" id="setAiOpenaiModel" placeholder="leer = Vorgabe (gpt-4o-mini / gpt-4o)">
<button class="ai-icon-btn" onclick="saveMyAiModell('chatgpt')" title="Modellname 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>
<label>Modellname (optional)</label>
<div class="ai-provider-row">
<input type="text" id="setAiClaudeModel" placeholder="leer = Vorgabe (claude-haiku-4-5 / claude-sonnet-5)">
<button class="ai-icon-btn" onclick="saveMyAiModell('claude')" title="Modellname 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 entsteht allein aus <strong>Aufgaben mit Aufwand</strong>:
Die Stunden einer Aufgabe verteilen sich zu gleichen Teilen auf ihre Zuständigen
und auf die Arbeitstage der Aufgabe. 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>
</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>
<!-- Teams-Anbindung (loadAdminTeams(), js/admin/admin.js; Server: /admin/teams*) -->
<section class="settings-card" id="adminTeamsCard" data-admin-section="teams" style="display:none">
<h3 class="settings-card-title">Teams <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Bekommt jemand eine Aufgabe zugewiesen, erhält genau diese Person in Teams
eine persönliche Nachricht (Chat mit dem Flow-Bot) mit Link direkt auf die Aufgabe.
Gefunden wird sie über die E-Mail-Adresse ihres Kontos — die muss ihrem Teams-Anmeldenamen
entsprechen. Wer kein Konto oder keine E-Mail hat, bekommt nichts.</p>
<div id="adminTeamsInhalt"></div>
<details class="kalender-anleitung">
<summary>So richten Sie den Workflow in Teams ein</summary>
<ol>
<li>In Teams die App <i>Workflows</i> öffnen (oder im Browser make.powerautomate.com) und
einen <b>leeren</b> Workflow anlegen.</li>
<li><b>Auslöser:</b> „Bei Empfang einer Teams-Webhookanforderung“, bei
<i>Wer kann den Flow auslösen</i> „Jeder“ wählen.</li>
<li><b>Aktion:</b> „Karte in einem Chat oder Kanal veröffentlichen“ —
<i>Posten als</i>: Flow-Bot, <i>Posten in</i>: Chat mit Flow-Bot,
<i>Empfänger</i>: als Ausdruck <code>triggerBody()?['empfaenger']</code>,
<i>Adaptive Karte</i>: als Ausdruck <code>string(triggerBody()?['karte'])</code>.</li>
<li>Speichern. Beim Auslöser steht jetzt eine lange Adresse (https://…) — kopieren,
hier einfügen und speichern.</li>
<li>„Testnachricht senden“ — die Nachricht kommt an Sie selbst.</li>
</ol>
<p class="hint">Der Workflow läuft unter dem Konto, das ihn anlegt. Am besten legt ihn ein
Sammelkonto oder die IT an — sonst hört er auf, wenn dieses Konto gesperrt wird.
Der Server braucht Internetzugang nach außen; der Link in der Nachricht funktioniert
nur im Büronetz (bzw. per VPN). Die Bezeichnungen in Teams können je nach Version
leicht abweichen.</p>
</details>
</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</span></h3>
<p class="hint">Wiederkehrende Schritte einer Leistungsphase, getrennt je Gewerk. 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. Änderungen an
Bezeichnung, Anteil, Start und Dauer werden beim Verlassen des Feldes gespeichert.
<strong>Anteil %</strong> (vorbelegt mit den Mittelwerten der Siemon-Tabellen zur HOAI 2013):
Beim Einfügen bekommt jeder Schritt diesen Anteil an der Dauer der Phase — die Schritte
laufen nacheinander durch den Phasenbalken. „in a)“ heißt: in Schritt a) enthalten, läuft
zeitgleich mit ihm. Ohne Anteil gelten Start und Dauer; ohne Dauer wird die Aufgabe ohne Termin
eingefügt (nur in „Aufgaben“, nicht im Terminplan). Die Schritte
sind mit den Grundleistungen der HOAI vorbelegt (ARCH: Anlage 10.1, ELT/HLS: Anlage 15.1,
Buchstabe in Klammern).</p>
<div class="vorlagen-wahl">
<div class="vorlagen-gewerke" id="adminTemplateGewerk" role="group" aria-label="Gewerk"></div>
<label class="settings-inline-field">
<span>Leistungsphase</span>
<select id="adminTemplateLph" onchange="renderAdminTemplates()"></select>
</label>
</div>
<div class="vorlagen-kopf"><span>Schritt</span><span title="Anteil an der Phase, z. B. nach Siemon-Tabelle">Anteil %</span><span>Start (Tag +)</span><span title="leer = ohne Termin">Dauer (Tage)</span><span></span></div>
<div id="templateAdminList" class="template-admin-list"></div>
<div class="template-add-row vorlagen-neu">
<input type="text" id="templateNewTitle" placeholder="Neuer Schritt, z. B. „LV erstellen“" onkeydown="if(event.key==='Enter') addTemplateEntry()">
<input type="text" id="templateNewAnteil" inputmode="decimal" placeholder="Anteil %" title="Anteil an der Phase in Prozent, z. B. nach Siemon-Tabelle">
<input type="number" id="templateNewOffset" value="0" min="0" title="Start in Tagen nach dem Startdatum">
<input type="number" id="templateNewDuration" value="" min="0" placeholder="ohne" title="Dauer in Tagen, leer = ohne Termin">
<button class="secondary small" onclick="addTemplateEntry()">+ Schritt</button>
</div>
</section>
<section class="settings-card settings-card-wide" id="adminPlanCheckerCard" data-admin-section="planchecker" style="display:none">
<h3 class="settings-card-title">Plan-Checker-Merkmale <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
<p class="hint">Die Grundliste, gegen die der Plan Checker (Bereich „Studenten") jeden hochgeladenen Plan prüft.
„Prüfanweisung" geht wortwörtlich an die KI — „Titel" ist zugleich die Anzeige-Bezeichnung.</p>
<div id="planCheckerMerkmaleAdminListe" class="template-admin-list"></div>
<div class="template-add-row">
<input type="text" id="merkmalNeuTitel" placeholder="Titel, z. B. „Maßketten vorhanden“">
<input type="text" id="merkmalNeuPrompt" placeholder="Prüfanweisung für die KI" style="flex:2">
<button class="secondary small" onclick="addPlanCheckerMerkmal()">+ Merkmal</button>
</div>
</section>
<section class="settings-card settings-card-wide" id="adminRollenCard" data-admin-section="rollen" style="display:none">
<h3 class="settings-card-title">Rollen &amp; Rechte <span class="settings-scope">firmenweit</span></h3>
<p class="hint">Jedes Konto hat genau eine Rolle (zugewiesen in der Benutzerverwaltung). Hier legst
du fest, was eine Rolle darf. Änderungen gelten sofort, für angemeldete Konten nach dem
nächsten Neuladen der Seite. Administrator hat immer alle Rechte. Die Fachrollen
(Planung, Projektleitung, Bauüberwachung) sind davon unabhängig.</p>
<div id="rollenRechte" class="rr"></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, Passwörter zurücksetzen und
Abwesenheiten pflegen. Die <strong>Wochenstunden</strong> hier sind die Bezugsgröße der
Auslastungs-Ampel; die <strong>Person-ID</strong> verbindet das Konto mit den
Aufgaben-Zuordnungen und füllt 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>Name</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 settings-card-wide" id="adminPapierkorbCard" data-admin-section="papierkorb" style="display:none">
<h3 class="settings-card-title">Papierkorb <span class="settings-scope">nur Administratoren</span></h3>
<p class="hint">Aus der Projektliste gelöschte Projekte — samt Terminplan, Beteiligten und
Leistungsphasen. Sie bleiben 30 Tage hier und lassen sich in die aktuelle Sitzung
zurückholen; danach werden sie endgültig entfernt. Dateien und Protokolle hängen an der
Projektnummer und sind nach dem Wiederherstellen ebenfalls wieder da.</p>
<div id="adminPapierkorbList"><p class="hint">Wird geladen…</p></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 nur-recht" data-recht="a.backup">
<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 nur-recht" data-recht="a.import">
<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">
<!-- .plan-kopf fasst Kopfzeile und Bereichs-Navigation zu EINER Fläche
zusammen — unter Glas eine schwebende Karte wie die Seitenleiste
(glas.css, "Alles schwebt"); ohne Glas wirkungslos. -->
<div class="plan-kopf">
<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="notizen" onclick="showPlanSection('notizen')">Notizen</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>
<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">
<!-- Anordnung "Lagebild" (Eriks Wahl aus drei Entwürfen): oben ein
schmales Band mit laufender Phase und drei Kennzahlen
(renderPlanKpis(), plan-gantt.js), darunter zwei Spalten, die
sich unabhängig füllen — links Kommentar und Termine, rechts
Leistungsphasen, Team und Unterlagen. Vorher stand ein Raster
aus vier gleichen Karten, in dem "Team" allein in einer zweiten
Reihe hing, und vier halbleere Kurzkarten darunter. -->
<div class="plan-lage-band" id="planKpiRow"></div>
<div class="plan-lage-raster">
<div class="plan-lage-spalte">
<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 allein im Wochenbericht erfasst. -->
<div class="plan-kommentar-hinweis" id="planStatusHinweis" hidden></div>
<div class="status" id="planStatusWrap">
<div class="status-edit" id="planStatusEdit"></div>
</div>
<div class="plan-kommentar-fuss">
<span class="plan-kommentar-autor" id="planStatusAutor"></span>
<span class="plan-verweis-row" id="planStatusVerweis"></span>
</div>
<div class="plan-kommentar-verlauf" id="planStatusVerlauf" hidden></div>
</div>
<div class="plan-panel">
<h3 class="plan-panel-title">Termine und Aufgaben
<button type="button" class="plan-panel-link" onclick="showPlanSection('aufgaben')">Alle Aufgaben</button></h3>
<div id="planDashUpcoming"></div>
</div>
<!-- Protokolle: letztes Protokoll mit seinen offenen Punkten und
die früheren als Kurzliste (renderProtokolleSummary(),
plan-protokoll.js). Ohne Projektnummer ausgeblendet. -->
<div class="plan-panel" id="planDashProtokollePanel">
<h3 class="plan-panel-title">Protokolle
<button type="button" class="plan-panel-link" onclick="openProtokollePanel()">Alle Protokolle</button></h3>
<div id="planDashProtokolle"></div>
</div>
</div>
<div class="plan-lage-spalte">
<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">Team
<button type="button" class="plan-panel-link" onclick="showPlanSection('einstellungen')">Bearbeiten</button></h3>
<div id="planDashTeam"></div>
</div>
<!-- Unterlagen: die früheren Kurzkarten als eine Liste; die
Inhalte füllen weiterhin renderResourceLoad(),
renderProjectFiles(). Protokolle haben eine eigene Kachel
(links), daher hier nicht mehr.
Der Eintrag "Verlauf" ist entfallen — der Kommentar-
Verlauf steht jetzt in der Kachel "Kommentar der Sitzung". -->
<div class="plan-panel">
<h3 class="plan-panel-title">Unterlagen</h3>
<div class="plan-unterlagen">
<div class="plan-unterlage" onclick="openFilesPanel()">
<div class="plan-unterlage-kopf"><b>Dateien und Freigaben</b><span class="plan-panel-link">Öffnen</span></div>
<div id="planFilesSummary"></div>
</div>
<div class="plan-unterlage" onclick="openResourceModal()">
<div class="plan-unterlage-kopf"><b>Auslastung des Teams</b><span class="plan-panel-link">Öffnen</span></div>
<div id="planResourceSummary"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===================== Bereich: Terminplan ==================== -->
<section class="plan-section" id="planSection-terminplan" data-section="terminplan">
<!-- Ruhige Leiste (Eriks Wahl "A"): Maßstab, Heute, Zeitraum, Filter,
dahinter die Menüs "Ansicht" und "+ Neu". Alle Knöpfe behalten ids und Funktionen
(renderPlanPage() blendet sie in gesperrten Sitzungen aus). -->
<div class="plan-main-header tp-leiste">
<span class="segmented tp-seg" id="tpMassstab" role="group" aria-label="Maßstab">
<button type="button" data-stufe="fit" aria-pressed="true" onclick="tpMassstab('fit')">Übersicht</button>
<button type="button" data-stufe="woche" aria-pressed="false" onclick="tpMassstab('woche')">Wochen</button>
<button type="button" data-stufe="tag" aria-pressed="false" onclick="tpMassstab('tag')">Tage</button>
</span>
<button type="button" class="secondary" onclick="tpHeute()">Heute</button>
<input type="text" id="planGanttRange" class="range-picker-display tp-zeitraum" placeholder="Zeitraum wählen…" readonly aria-label="Zeitraum"
onclick="openRangePicker({ anchor: this, startInputId: 'planGanttStart', endInputId: 'planGanttEnd', displayId: 'planGanttRange', onApply: renderPlanGantt })">
<input type="hidden" id="planGanttStart">
<input type="hidden" id="planGanttEnd">
<span class="tp-luecke"></span>
<span class="segmented tp-seg" id="tpFilter" role="group" aria-label="Filter">
<button type="button" data-filter="alle" aria-pressed="true" onclick="tpFilter('alle')">Alle</button>
<button type="button" data-filter="meine" aria-pressed="false" onclick="tpFilter('meine')">Meine</button>
<button type="button" data-filter="ueberfaellig" aria-pressed="false" onclick="tpFilter('ueberfaellig')">Überfällig</button>
</span>
<details class="tp-menue">
<summary class="secondary">Ansicht ▾</summary>
<div class="tp-menue-liste">
<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()"> Stand der Vorwoche zeigen</label>
<label><input type="checkbox" id="planShowPfeile" checked onchange="renderPlanGantt()"> Abhängigkeiten als Linien</label>
<!-- Nur für Sandbox-Pläne der Planung sichtbar (renderPlanPage()). -->
<button type="button" onclick="exportSandboxPlanPDF(PlanApp.project.id); tpMenueZu()" id="sandboxPdfBtn">Als PDF exportieren</button>
</div>
</details>
<details class="tp-menue tp-neu">
<summary>+ Neu ▾</summary>
<div class="tp-menue-liste">
<button type="button" onclick="tpMenueZu(); addTodo()" id="addTodoBtn"><span class="tp-sym-aufgabe"></span>Aufgabe</button>
<button type="button" onclick="tpMenueZu(); addPlanMilestone()" id="addMilestoneBtn"><span class="tp-sym-ms"></span>Meilenstein</button>
<button type="button" onclick="tpMenueZu(); addPlanPhase()" id="addPhaseBtn" title="Leistungsphase als Balken anlegen; Aufgaben in ihrem Zeitraum stehen darunter"><span class="tp-sym-phase"></span>Leistungsphase</button>
<hr>
<button type="button" onclick="tpMenueZu(); openTemplateModal()" id="templateBtn" title="Standard-Aufgaben einer Leistungsphase einfügen">Aus Vorlage</button>
<button type="button" onclick="tpMenueZu(); openHonorarModal()" id="honorarBtn" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">Terminplan aus Honorar</button>
<button type="button" onclick="tpMenueZu(); openProtokollTodosModal()" id="protokollTodosBtn" title="Externes Besprechungsprotokoll (PDF) per KI einlesen und daraus Aufgaben ableiten">Aufgaben aus Protokoll</button>
</div>
</details>
</div>
<div id="planWarnings"></div>
<div id="planGanttContainer" class="gantt-container"></div>
<button type="button" class="tp-ohne" id="planOhneTermin" hidden onclick="showPlanSection('aufgaben')"></button>
<div class="plan-panel" style="margin-top:18px">
<h3 class="plan-panel-title">Ressourcen-Auslastung</h3>
<div id="resourceLoadContainer"></div>
</div>
</section>
<!-- ====================== Bereich: Aufgaben ===================== -->
<!-- ======================= Bereich: Notizen ======================= -->
<!-- Eriks Wahl „A – Liste + Editor“: gemeinsame Notizen je Projekt
(renderPlanNotizen(), js/plan/plan-notizen.js; Server:
/projekt-notizen*). Ersetzt den früheren Reiter „Leistungsphasen“. -->
<section class="plan-section" id="planSection-notizen" data-section="notizen">
<div id="planNotizenWrap"></div>
</section>
<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="openProtokollTodosModal()" id="protokollTodosBtnList" title="Externes Besprechungsprotokoll (PDF) per KI einlesen und daraus Aufgaben ableiten">+ TODOs aus Protokoll</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="planTodoFilter" class="plan-task-filter"></div>
<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>
<!-- Einstellungen mit Unternavigation (Eriks Wahl "C"): links vier
Abschnitte mit Kurzfassung, rechts immer genau einer
(zeigeEinstTeil(), plan-settings.js). Alle Felder behalten ihre
ids und Schreibwege; die Sandbox blendet über .form-row aus. -->
<div class="plan-einst">
<nav class="plan-einst-nav" aria-label="Abschnitte der Einstellungen">
<button type="button" class="plan-einst-reiter" data-teil="allgemein" onclick="zeigeEinstTeil('allgemein')"><b>Allgemein</b><span id="einstSumAllgemein"></span></button>
<button type="button" class="plan-einst-reiter" data-teil="auftrag" onclick="zeigeEinstTeil('auftrag')"><b>Auftrag &amp; Honorar</b><span id="einstSumAuftrag"></span></button>
<button type="button" class="plan-einst-reiter" data-teil="phasen" onclick="zeigeEinstTeil('phasen')"><b>Leistungsphasen</b><span id="einstSumPhasen"></span></button>
<button type="button" class="plan-einst-reiter" data-teil="team" onclick="zeigeEinstTeil('team')"><b>Beteiligte</b><span id="einstSumTeam"></span></button>
</nav>
<div class="plan-panel plan-einst-inhalt">
<div class="plan-einst-teil" data-teil="allgemein">
<h2 class="plan-einst-titel">Allgemein</h2>
<div class="plan-einst-paar">
<div class="form-row">
<label for="settingsProjNr">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 for="settingsProjName">Projektname</label>
<input type="text" id="settingsProjName" placeholder="Bezeichnung"
onchange="setPlanProjName(this.value)">
</div>
</div>
<p class="hint plan-einst-hint" id="settingsProjNrHinweis">Dateien und Protokolle hängen an der Projektnummer — eine Änderung wird deshalb noch einmal abgefragt.</p>
<div class="form-row">
<label>Gewerke</label>
<div id="settingsGewerke" class="plan-einst-chips"></div>
<span class="hint plan-einst-hint">Bestimmt, in welcher Gewerke-Besprechung des Wochenberichts dieses Projekt erscheint.</span>
</div>
<div class="form-row">
<label for="planGroupSelect">Gruppe</label>
<select id="planGroupSelect" class="plan-einst-schmal" 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 for="planActiveLpSelect">Amtierende Leistungsphase</label>
<select id="planActiveLpSelect" class="plan-einst-schmal" onchange="setPlanActiveLp(this.value)"></select>
<span class="hint plan-einst-hint">Die Phase, in der das Projekt gerade steht. Im Bereich „Leistungsphasen“ mit „läuft“ markiert.</span>
</div>
<div class="form-row">
<label>Schlagwörter</label>
<div class="tag-chip-list" id="settingsProjTags"></div>
</div>
</div>
<div class="plan-einst-teil" data-teil="auftrag">
<h2 class="plan-einst-titel">Auftrag &amp; Honorar</h2>
<!-- Auftragsstatus: Anfrage → Angebot → beauftragt →
abgeschlossen. Der Sprung auf "beauftragt" öffnet den
Beauftragungs-Dialog (js/auftrag.js). -->
<div class="form-row">
<label>Auftragsstatus</label>
<div id="settingsAuftragsstatus" class="plan-einst-schmal"></div>
</div>
<div class="plan-einst-paar">
<div class="form-row">
<label for="settingsHonorar">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>
</div>
</div>
<div class="plan-einst-teil" data-teil="phasen">
<h2 class="plan-einst-titel">Leistungsphasen</h2>
<p class="hint plan-einst-hint">Beauftragte Phasen bestimmen den Terminplan aus dem Honorar, den Leistungsstand und die Phasen im Wochenbericht.</p>
<div id="settingsBeauftragteLp" class="plan-einst-phasen"></div>
</div>
<!-- Beteiligte als Personenliste (Eriks Wahl "B"): Besetzungsleiste,
Suchfeld, Zeilen je Person nach Gewerk mit Rollen-Kapseln,
Entfernen mit "Rückgängig" (plan-settings.js). -->
<div class="plan-einst-teil" data-teil="team">
<div class="plan-einst-team-kopfzeile">
<div>
<h2 class="plan-einst-titel">Beteiligte</h2>
<p class="hint plan-einst-hint">Eine Zeile je Person. Rolle antippen: Haupt, dann Unterstützung, dann aus.</p>
</div>
<div class="plan-einst-legende" aria-hidden="true">
<span><i class="ist-haupt"></i>Haupt</span>
<span><i class="ist-neben"></i>Unterstützung</span>
</div>
</div>
<div id="planEinstSperre" class="plan-einst-sperre" hidden>Diese Sitzung ist gesperrt. Die Zuordnung ist nur zu sehen, nicht zu ändern.</div>
<div id="planEinstBesetzung" class="plan-einst-besetzung"></div>
<div class="plan-einst-suche" id="planEinstSucheWrap">
<label for="planEinstSuche">Person hinzufügen</label>
<input type="text" id="planEinstSuche" autocomplete="off" placeholder="Name eingeben, z. B. Körner"
oninput="einstSucheZeigen()" onfocus="einstSucheZeigen()" onblur="einstSucheSchliessen()"
onkeydown="einstSucheTaste(event)">
<div id="planEinstVorschlaege" class="plan-einst-vorschlaege" hidden></div>
</div>
<div id="planAssignMatrix" class="plan-einst-team"></div>
<div id="planEinstToast" class="plan-einst-toast" hidden></div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</div>
</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). -->
<!-- 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>
<!-- "TODOs aus Protokoll" (js/plan/plan-protokoll-todos.js): liest ein
EXTERNES Besprechungsprotokoll (PDF) per KI ein und leitet daraus
Aufgaben für dieses Projekt ab. Bewusst unabhängig vom Protokoll-Import
oben: nichts davon wird gespeichert, kein Bezug zu project_protokolle —
ein reines Ad-hoc-Werkzeug wie der Plan Checker. Drei Schritte
(Hochladen → Punkte wählen → Aufgaben bestätigen) in EINEM Dialog mit
sichtbarem Fortschritt (#protokollTodosSteps) und fester Zurück/
Abbrechen/Weiter-Fußzeile — Titel, Fortschritt und der Inhalt von
#protokollTodosBody werden je Schritt neu gerendert, der Rahmen bleibt
stehen. Entwurfsrichtung "A — Schritt-Dialog" aus dem Entwurfsvergleich
(Taste-Skill), von Erik gewählt. -->
<div class="modal-bg" id="protokollTodosModal" style="display:none">
<div class="modal modal-wide">
<h2 id="protokollTodosTitle">Externes Protokoll einlesen</h2>
<p class="hint" id="protokollTodosSubtitle" style="margin-top:-4px"></p>
<div class="protokoll-todos-steps" id="protokollTodosSteps">
<div class="seg" data-i="0"></div><div class="seg" data-i="1"></div><div class="seg" data-i="2"></div>
</div>
<div class="protokoll-ki-fallback-banner" id="protokollTodosFallback" hidden></div>
<div id="protokollTodosBody"></div>
<span class="field-error" id="protokollTodosError" hidden></span>
<div class="protokoll-import-loading" id="protokollTodosLoading" hidden>
<span class="protokoll-import-spinner" aria-hidden="true"></span>
<span id="protokollTodosLoadingText">Wird verarbeitet …</span>
</div>
<div class="toolbar" style="margin-top:14px; justify-content:space-between">
<button type="button" class="secondary" id="protokollTodosBackBtn" onclick="protokollTodosZurueck()" disabled>Zurück</button>
<div style="display:flex; gap:8px">
<button type="button" class="secondary" onclick="closeProtokollTodosModal()">Abbrechen</button>
<button type="button" id="protokollTodosPrimaryBtn" onclick="protokollTodosWeiter()">Einlesen</button>
</div>
</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>Gewerk</label>
<select id="templateGewerkSelect" onchange="renderTemplateList()"></select>
</div>
<div class="form-row">
<label>Leistungsphase</label>
<select id="templateLphSelect" onchange="templatePhaseVorbelegen(); 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 class="form-row">
<label for="templatePhasenTage">Dauer der Phase (Arbeitstage)</label>
<input type="number" id="templatePhasenTage" min="1" placeholder="z. B. 40" oninput="renderTemplateList()">
<span class="hint" id="templatePhasenHinweis" style="margin:2px 0 0"></span>
</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 Admin-Bereich; 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="/Admin#vorlagen" onclick="closeTemplateModal();showAdminSection('vorlagen');return false;">Admin → 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 und Aufgabe als SEITENPANEL rechts (Eriks Wahl "B"): der
Terminplan bleibt links sichtbar. Beide Panels gleich gebaut: Kopf mit
Art und Titel, Abschnitte Wann / Wo bzw. Wer / Stand / Hängt ab von,
feste Fußleiste. Alle Feld-ids wie zuvor; befüllt und ausgewertet von
js/plan/plan-modals.js. Stile: Block "Seitenpanel" in plan-styles.css. -->
<div class="modal-bg als-panel" id="milestoneModal" style="display:none">
<div class="modal pd">
<div class="pd-kopf">
<div class="pd-art"><span class="pd-raute" aria-hidden="true"></span><span id="milestoneModalTitle">Meilenstein</span>
<span class="modal-kbd">Esc schließt</span>
<button type="button" class="pd-zu" onclick="closeMilestoneModal()" aria-label="Schließen">×</button></div>
<input type="text" id="milestoneEditName" class="pd-titel" placeholder="z. B. Abgabe Genehmigungsplanung" aria-label="Name des Meilensteins">
</div>
<div class="pd-inhalt">
<section class="pd-abs">
<h3>Wann</h3>
<!-- sichtbares Anzeigefeld (deutsches Format) + verstecktes ISO-Feld -->
<input type="text" id="milestoneEditDateDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly aria-label="Datum"
onclick="openDatePicker({ anchor: this, inputId: 'milestoneEditDate', displayId: 'milestoneEditDateDisplay' })">
<input type="hidden" id="milestoneEditDate">
</section>
<section class="pd-abs">
<h3>Wo im Terminplan</h3>
<div class="segmented pd-seg" id="milestoneEditLage">
<button type="button" data-lage="zeile" aria-pressed="false" onclick="msLageSetzen('zeile')">Oben, eigene Zeile</button>
<button type="button" data-lage="aufgabe" aria-pressed="false" onclick="msLageSetzen('aufgabe')">Bei einer Aufgabe</button>
</div>
<select id="milestoneEditPosition" aria-label="Position bei einer Aufgabe" onchange="msLageSync()">
<option value="">Oben in der Projektzeile</option>
</select>
</section>
<section class="pd-abs">
<h3>Hängt ab von</h3>
<div id="milestoneEditDependsOn" class="todo-depends-list pd-liste"></div>
</section>
<section class="pd-abs">
<h3>Notiz</h3>
<p class="hint pd-hint pd-mail-hinweis" id="milestoneEditMailHinweis" hidden></p>
<textarea id="milestoneEditNotiz" class="pd-notiz" rows="4" placeholder="Optional, z. B. Hintergrund oder die Mail, aus der der Meilenstein stammt" aria-label="Notiz"></textarea>
</section>
</div>
<div class="pd-fuss">
<button type="button" class="pd-loeschen" id="milestoneEditDelete" onclick="deleteMilestoneEdit()">Löschen</button>
<button type="button" class="pd-speichern" onclick="saveMilestoneEdit()">Speichern</button>
</div>
</div>
</div>
<div class="modal-bg als-panel" id="todoModal" style="display:none">
<div class="modal pd">
<div class="pd-kopf">
<div class="pd-art"><span class="pd-kasten" aria-hidden="true"></span><span id="todoModalTitle">Aufgabe bearbeiten</span>
<span class="modal-kbd" id="todoModalKbd">Esc · Strg+↵</span>
<button type="button" class="pd-zu" onclick="closeTodoModal()" aria-label="Schließen">×</button></div>
<input type="text" id="todoEditTitle" class="pd-titel" placeholder="Titel der Aufgabe" aria-label="Titel">
<span class="field-error" id="todoEditTitleError" hidden></span>
</div>
<div class="pd-inhalt">
<!-- Nur im Phasen-Modus ("+ Neu" → "Leistungsphase" oder Bearbeiten
eines Phasenbalkens): Welche Leistungsphase dieser Balken ist. Nur
darüber wird ein Balken zum Phasenkopf, nicht mehr über den Titel. -->
<section class="pd-abs" id="todoEditPhaseAbs" hidden>
<h3>Leistungsphase</h3>
<select id="todoEditLph" aria-label="Leistungsphase" onchange="todoPhaseGewechselt()"></select>
<p class="hint pd-hint">Aufgaben, die im Zeitraum dieser Phase beginnen, stehen im Terminplan darunter.</p>
</section>
<section class="pd-abs">
<h3>Wann</h3>
<div class="field-inline">
<input type="text" id="todoEditRange" class="range-picker-display" placeholder="Start – Ende wählen…" readonly aria-label="Zeitraum"
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>
<label class="pd-haken"><input type="checkbox" id="todoEditShowInGantt" checked> Im Terminplan als Balken zeigen</label>
<p class="hint pd-hint">Ohne Zeitraum erscheint die Aufgabe nur unter „Aufgaben“, nicht im Terminplan.</p>
</section>
<section class="pd-abs" id="todoEditWerAbs">
<h3>Wer</h3>
<div id="todoEditAssignees" class="person-chips"></div>
<!-- Externe Personen: Kollegen mit Konto, die dem Projekt NICHT
zugeordnet sind (Eriks Wunsch). todoExternHinzufuegen()/
todoExternEntfernen(), js/plan/plan-modals.js -->
<div class="pd-externe">
<div class="pd-externe-kopf">Externe Personen <span class="hint">nicht im Projekt</span></div>
<div id="todoEditExterne" class="person-chips"></div>
<select id="todoEditExterneWahl" aria-label="Externe Person hinzufügen" onchange="todoExternHinzufuegen(this.value)"></select>
</div>
<!-- Aufwand: −/+, Schnellwahl, Umrechnung in Arbeitstage
(todoAufwandAendern()/todoAufwandSetzen()/todoAufwandInfo(), plan-modals.js) -->
<div class="pd-aufwand">
<div class="pd-aufwand-kopf">
<label for="todoEditAufwand">Aufwand</label>
<span>optional · zählt zur Auslastung der Zuständigen</span>
</div>
<div class="pd-aufwand-zeile">
<div class="pd-stepper">
<button type="button" class="pd-stepper-knopf" onclick="todoAufwandAendern(-1)" aria-label="Eine Stunde weniger">−</button>
<input type="number" id="todoEditAufwand" min="0" step="0.5" placeholder="0" inputmode="decimal" oninput="todoAufwandInfo()">
<span class="pd-einheit">Std.</span>
<button type="button" class="pd-stepper-knopf" onclick="todoAufwandAendern(1)" aria-label="Eine Stunde mehr">+</button>
</div>
<div class="pd-aufwand-schnell" id="todoAufwandSchnell" role="group" aria-label="Schnellwahl">
<button type="button" data-h="2" onclick="todoAufwandSetzen(2)">2 h</button>
<button type="button" data-h="4" onclick="todoAufwandSetzen(4)">½ Tag</button>
<button type="button" data-tage="1" onclick="todoAufwandSetzen('tag', 1)">1 Tag</button>
<button type="button" data-tage="2" onclick="todoAufwandSetzen('tag', 2)">2 Tage</button>
<button type="button" data-tage="5" onclick="todoAufwandSetzen('tag', 5)">1 Woche</button>
</div>
</div>
<span class="pd-aufwand-info" id="todoAufwandInfoText"></span>
</div>
</section>
<section class="pd-abs">
<h3>Stand</h3>
<div class="segmented pd-seg" 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>
</section>
<section class="pd-abs">
<h3>Notiz</h3>
<p class="hint pd-hint pd-mail-hinweis" id="todoEditMailHinweis" hidden></p>
<textarea id="todoEditNotiz" class="pd-notiz" rows="4" placeholder="Optional, z. B. Hintergrund oder die Mail, aus der die Aufgabe stammt" aria-label="Notiz"></textarea>
</section>
<section class="pd-abs">
<details class="fold" id="todoEditDependsFold">
<summary>
<span class="fold-caret">▶</span>
<span>Hängt ab von</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>
</section>
</div>
<div class="pd-fuss">
<button type="button" class="pd-loeschen" id="todoEditDelete" onclick="deleteTodoEdit()">Löschen</button>
<button type="button" class="secondary" id="todoEditSaveNew" onclick="saveTodoEdit(true)">Speichern &amp; neu</button>
<button type="button" class="pd-speichern" onclick="saveTodoEdit()">Speichern</button>
</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-pair">
<div class="form-row">
<label for="newUserFirstName">Vorname</label>
<input type="text" id="newUserFirstName" placeholder="z. B. Max" autocomplete="off" oninput="onNewUserNameInput()">
</div>
<div class="form-row">
<label for="newUserLastName">Nachname</label>
<input type="text" id="newUserLastName" placeholder="z. B. Mustermann" autocomplete="off" oninput="onNewUserNameInput()">
</div>
</div>
<div class="form-row">
<label for="newUserEmail">E-Mail</label>
<input type="email" id="newUserEmail" placeholder="v.nachname@rauschenberg-ing.de">
<span class="hint" style="margin:4px 0 0">Der Anfang der Adresse ist zugleich der Benutzername für die Anmeldung.</span>
</div>
<div class="form-row">
<label for="newUserUsername">Benutzername</label>
<input type="text" id="newUserUsername" placeholder="Anfang der E-Mail, z. B. m.mustermann" autocomplete="off">
</div>
<div class="form-row">
<label>Rolle</label>
<select id="newUserRole" onchange="onUserRoleChange('new')">
<!-- Optionen aus „Rollen & Rechte“ (ladeSystemRollen(), js/admin-users.js) -->
</select>
<p class="hint" id="newUserRoleHint" style="display:none">Diese Rolle sieht nur die Wandtafel und hat keine Fachrolle.</p>
</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 Namen 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 Kürzel gebildet, z. B. p_mm">
<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-pair">
<div class="form-row">
<label for="editUserFirstName">Vorname</label>
<input type="text" id="editUserFirstName" oninput="onEditUserNameInput()">
</div>
<div class="form-row">
<label for="editUserLastName">Nachname</label>
<input type="text" id="editUserLastName" oninput="onEditUserNameInput()">
</div>
</div>
<div class="form-row">
<label>E-Mail</label>
<input type="email" id="editUserEmail" placeholder="v.nachname@rauschenberg-ing.de">
</div>
<div class="form-row">
<label>Rolle</label>
<select id="editUserRole" onchange="onUserRoleChange('edit')">
<!-- Optionen aus „Rollen & Rechte“ (ladeSystemRollen(), js/admin-users.js) -->
</select>
<p class="hint" id="editUserRoleHint" style="display:none">Diese Rolle sieht nur die Wandtafel und hat keine Fachrolle.</p>
</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-Seite: blockiert die gesamte App ohne gültige Session (siehe
js/auth.js). Design "Kalenderwoche" (Eriks Wahl B aus dem Vergleich der
Login-Entwürfe): links Logo und Formular, rechts die laufende
Kalenderwoche als große Zahl mit ihren Arbeitstagen — die App denkt in
Sitzungen je KW. Gefüllt von renderLoginWoche(). Das Logo liegt als
blasses Wasserzeichen über die ganze Seite (Variante E aus dem
Logo-Vergleich), ein kleines Logo gibt es bewusst nicht. -->
<div id="loginOverlay" class="login-seite" style="display:none;">
<div class="login-wz-rahmen" aria-hidden="true"><img class="login-wasserzeichen" src="/logo.svg" alt=""></div>
<main class="login-raster" aria-label="Anmeldung bei der Projektübersicht">
<section class="login-links">
<h1 class="login-titel">Projektübersicht</h1>
<p class="login-unter">Melde dich mit dem Anfang deiner E-Mail-Adresse an.</p>
<form id="loginForm" class="login-form" novalidate>
<div class="login-feld">
<label for="loginUsername">Benutzername</label>
<input type="text" id="loginUsername" name="username" autocomplete="username" placeholder="z. B. m.mustermann" spellcheck="false" autocapitalize="off">
</div>
<div class="login-feld">
<label for="loginPassword">Passwort</label>
<div class="login-pw">
<input type="password" id="loginPassword" name="password" autocomplete="current-password">
<button type="button" id="loginPwToggle" class="login-pw-toggle" aria-pressed="false" aria-controls="loginPassword">Anzeigen</button>
</div>
<p id="loginCaps" class="login-caps" hidden>Die Feststelltaste ist aktiv.</p>
</div>
<p id="loginError" class="login-fehler" role="alert" hidden></p>
<button type="submit" id="loginSubmit" class="login-btn"><span class="login-spinner" aria-hidden="true"></span><span class="login-btn-text">Anmelden</span></button>
</form>
<p class="login-hilfe">Passwort vergessen? Ein Administrator setzt es dir zurück.</p>
</section>
<section class="login-woche" aria-label="Laufende Kalenderwoche">
<span class="login-kw-label">Kalenderwoche</span>
<span class="login-kw-zahl" id="loginKwZahl"></span>
<span class="login-kw-jahr" id="loginKwJahr"></span>
<ol class="login-tage" id="loginTage"></ol>
<p class="login-datum"><span id="loginDatum"></span>. Die Sitzung dieser Woche ist geöffnet.</p>
</section>
</main>
</div>
<script src="/js/globals.js?v=9"></script>
<script src="/js/utils.js?v=31"></script>
<script src="/js/icons.js?v=2"></script>
<script src="/js/ui.js?v=9"></script>
<script src="/js/date-picker.js?v=6"></script>
<script src="/js/server.js?v=7"></script>
<script src="/js/auth.js?v=11"></script>
<script src="/js/store.js?v=16"></script>
<script src="/js/weeks.js?v=12"></script>
<script src="/js/board/board.js?v=41"></script>
<script src="/js/wochenbericht/wochenbericht.js?v=29"></script>
<script src="/js/people/people.js?v=8"></script>
<script src="/js/planung/planung.js?v=3"></script>
<script src="/js/planchecker/planchecker.js?v=1"></script>
<script src="/js/abwesenheit/abwesenheit.js?v=10"></script>
<script src="/js/projects/projects.js?v=10"></script>
<script src="/js/load/load.js?v=23"></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=2"></script>
<!-- Assistent "Neues Projekt": braucht auftrag.js (Auftragsstatus) und
load.js (faktorFormat), steht deshalb dahinter. -->
<script src="/js/projekt-wizard.js?v=6"></script>
<script src="/js/dashboard/dashboard.js?v=10"></script>
<script src="/js/benachrichtigungen.js?v=5"></script>
<!-- wand.js braucht berechneAuslastung()/loadAmpelColor()/kapazitaetsFaktor()/
stundenFormat() (load.js), classifyProjectChange()/findPreviousWeek()
(wochenbericht.js), statusPreviewText() (board.js) und
relativerTag()/tageBis()/initialsFor() (dashboard.js) — alle laden
bereits vorher, die Reihenfolge hier ist also nur Zuordnung zum
Tab-Ordner, keine neue Abhängigkeit nach vorn. -->
<script src="/js/wand/wand.js?v=21"></script>
<script src="/js/settings/settings.js?v=14"></script>
<script src="/js/admin/admin.js?v=23"></script>
<script src="/js/admin-users.js?v=10"></script>
<script src="/js/admin/rollen.js?v=1"></script>
<script src="/js/einfuehrung.js?v=2"></script>
<script src="/js/gantt.js?v=10"></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=25"></script>
<script src="/js/plan/plan-sections.js?v=12"></script>
<script src="/js/plan/plan-info.js?v=15"></script>
<script src="/js/plan/plan-history.js?v=4"></script>
<script src="/js/plan/plan-build-gantt.js?v=11"></script>
<script src="/js/plan/plan-gantt.js?v=22"></script>
<script src="/js/plan/plan-drag.js?v=8"></script>
<script src="/js/plan/plan-modals.js?v=18"></script>
<script src="/js/plan/plan-tasks.js?v=4"></script>
<script src="/js/plan/plan-phasen.js?v=3"></script>
<script src="/js/plan/plan-files.js?v=4"></script>
<script src="/js/plan/plan-protokoll.js?v=20"></script>
<script src="/js/plan/plan-templates.js?v=11"></script>
<script src="/js/plan/plan-protokoll-todos.js?v=2"></script>
<script src="/js/plan/plan-mail-eintrag.js?v=4"></script>
<script src="/js/plan/plan-notizen.js?v=1"></script>
<script src="/js/plan/plan-honorar.js?v=9"></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=14"></script>
<script src="/js/plan/plan-schedule.js?v=6"></script>
<script src="/js/tabs-auth.js?v=17"></script>
<script src="/js/pdf.js?v=31"></script>
<script src="/js/backup.js?v=2"></script>
<script src="/js/import.js?v=1"></script>
<script src="/js/init.js?v=14"></script>
</body>
</html>