Beteiligten-Zuordnung verlagert: p.assign ist jetzt ein Stammdatum und nur noch in den Projekteinstellungen editierbar (planCycleAssign()). Wochenbericht und Projektboard zeigen sie nur noch an, mit Verweis dorthin (springZuEinstellungen()). cycleAssign() ist damit entfallen. Neu "TODOs aus Protokoll": ein externes Besprechungsprotokoll als PDF wird per KI eingelesen und daraus in einem zweiten KI-Schritt Aufgaben abgeleitet. Eigener Bereich in der Aufgaben-/GANTT-Leiste, bewusst unabhaengig von den gespeicherten Protokollen - nichts davon wird abgelegt. Drei Schritte in einem Dialog mit fester Fusszeile; die Vorschlagsliste als kompakte, aufklappbare Zeilen, weil ein echter Import leicht 20 Vorschlaege liefert. Sichtbarkeit je Aufgabe im GANTT: neue Spalte plan_todos.show_in_gantt (Vorgabe 1, idempotente Migration) und eine Checkbox im Aufgaben-Dialog. Eine terminierte Aufgabe kann damit gezielt aus dem Terminplan herausgehalten werden, bleibt sonst aber eine vollwertige Aufgabe. plan-build-gantt.js und plan-drag.js pruefen dieselbe Bedingung, damit Zeilenzahl und Umsortierung nicht auseinanderlaufen. KI-Timeout dritte Stufe: ein abweichender, aber entfernter Endpunkt (Drittanbieter-Proxy) bekommt 2 Minuten statt der 45 Sekunden der echten Anbieter-API. Gemessen: die Punkte-Erkennung eines realen Protokolls brauchte dort 39 Sekunden, lag also stets knapp unter dem alten Limit und brach bei jeder Schwankung ab. Bewegung: Hover- und Klick-Rueckmeldung als Token-Satz in :root, angewandt auf die anklickbaren Zeilen, Schalter und Dialoge. Die Akzentleiste ist ein inset-Schatten statt eines positionierten Elements, damit Drag-and-Drop unberuehrt bleibt; prefers-reduced-motion war bisher gar nicht vorhanden und kippt jetzt einfach die Tokens. Plan Checker: optionaler Projektabgleich des Plankopfs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1831 lines
105 KiB
HTML
1831 lines
105 KiB
HTML
<!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="stylesheet" href="/styles.css?v=64">
|
||
<link rel="stylesheet" href="/plan-styles.css?v=19">
|
||
<!-- Fredoka/Nunito Sans: nur für die Wandtafel (js/wand/wand.js), siehe ihren
|
||
Dateikopf — fällt ohne Internetzugang automatisch auf die System-Schrift
|
||
zurück (font-family-Stack in styles.css), bricht also nichts offline. -->
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600;700&family=Nunito+Sans:wght@400;600;700;800&display=swap">
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/pdfmake.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js"></script>
|
||
</head>
|
||
<body>
|
||
<div class="app-shell">
|
||
<header class="app-topbar">
|
||
<button class="mobile-nav-toggle menu-corner-btn" onclick="toggleHeader()" title="Menü öffnen/schließen" aria-expanded="false" aria-controls="headerMenu">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg></span>
|
||
</button>
|
||
|
||
<div class="topbar-brand">
|
||
<img src="/logo.ico" alt="Logo" class="topbar-logo">
|
||
<h1 id="pageTitle" class="topbar-title">Projektübersicht</h1>
|
||
</div>
|
||
|
||
<!-- Die Sitzungsauswahl stand hier früher als Dropdown. Sitzungen sind
|
||
reine Speicherung + Verlauf; die Oberfläche zeigt immer nur die
|
||
eine offene Sitzung, ohne das im Menüband zu betonen. Eine
|
||
gesperrte Sitzung lässt sich weiterhin ansehen und für eine
|
||
Korrektur entsperren — dafür jetzt Admin → Termine & Sitzungen
|
||
(renderAdminSitzungen(), js/admin/admin.js). -->
|
||
|
||
<div class="app-topbar-actions">
|
||
<button class="theme-toggle-circle" id="themeToggle" onclick="toggleTheme()" title="Hell/Dunkel umschalten">
|
||
<span id="themeIcon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 14.5A8.5 8.5 0 1 1 9.5 3.5a7 7 0 0 0 11 11Z"/></svg></span></span>
|
||
</button>
|
||
|
||
<div class="topbar-user">
|
||
<button class="theme-toggle-circle avatar-btn" id="userMenuToggle" onclick="toggleUserMenu()" title="Konto" aria-expanded="false" aria-controls="userMenuDropdown">
|
||
<span class="icon" id="userAvatarIcon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8.5" r="3.5"/><path d="M4.5 20c0-4 3.5-6.5 7.5-6.5s7.5 2.5 7.5 6.5"/></svg></span>
|
||
</button>
|
||
<div class="user-menu-dropdown" id="userMenuDropdown">
|
||
<div class="user-menu-info" id="userBarInfo"></div>
|
||
<button class="menu-item" onclick="logout()">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5.5A1.5 1.5 0 0 1 4 19.5v-15A1.5 1.5 0 0 1 5.5 3H9"/><path d="M16 17l5-5-5-5"/><line x1="21" y1="12" x2="9" y2="12"/></svg></span>
|
||
Abmelden
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="app-body">
|
||
<div id="headerMenu" class="side-menu" aria-hidden="true">
|
||
<div class="side-menu-mobile-header">
|
||
<button class="header-toggle secondary" onclick="toggleHeader()" title="Menü schließen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
|
||
</button>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav">
|
||
<button class="nav-item tab active" data-tab="dashboard" onclick="showTab('dashboard')" title="Dashboard">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.5"/><path d="m4.5 7.5 1.3 1.3L8.5 6"/><line x1="13" y1="6" x2="21" y2="6"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="m4.5 17.5 1.3 1.3 2.7-2.8"/><line x1="13" y1="16" x2="21" y2="16"/></svg></span></span><span class="nav-label">Dashboard</span>
|
||
</button>
|
||
|
||
<hr class="sidebar-nav-divider">
|
||
|
||
<div class="sidebar-section-label">Navigation</div>
|
||
<!-- Die eine Projektliste. Der frühere zweite Eintrag "Projekte" —
|
||
dieselben Projekte, dieselben Angaben, eine zweite Bearbeitungs-
|
||
maske — ist hier aufgegangen: Reihenfolge, Gruppenwechsel und
|
||
Löschen stehen in dieser Liste, die Projektangaben im
|
||
Projektbereich, die wöchentliche Erfassung im Wochenbericht. -->
|
||
<button class="nav-item tab" data-tab="board" onclick="showTab('board')" title="Projekte">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2.5"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg></span></span><span class="nav-label">Projekte</span>
|
||
</button>
|
||
<button class="nav-item tab" data-tab="wochenbericht" onclick="showTab('wochenbericht')" title="Wochenbericht">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3.5h8a1.5 1.5 0 0 1 1.5 1.5v14a1.5 1.5 0 0 1-1.5 1.5H8A1.5 1.5 0 0 1 6.5 19V5A1.5 1.5 0 0 1 8 3.5Z"/><path d="M9.5 3.5h5v2.2a.8.8 0 0 1-.8.8h-3.4a.8.8 0 0 1-.8-.8V3.5Z"/><path d="m9.3 12.2 1.5 1.5 3.4-3.4"/><line x1="9.3" y1="16.3" x2="14.7" y2="16.3"/></svg></span></span><span class="nav-label">Wochenbericht</span>
|
||
</button>
|
||
<button class="nav-item tab" data-tab="load" onclick="showTab('load')" title="Auslastung">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="21" x2="5" y2="10"/><line x1="12" y1="21" x2="12" y2="5"/><line x1="19" y1="21" x2="19" y2="14"/><line x1="3" y1="21" x2="21" y2="21"/></svg></span></span><span class="nav-label">Auslastung</span>
|
||
</button>
|
||
<!-- "Projektbeteiligte" stand hier bis zur Entfernung der Abteilungs-
|
||
trennung (migrateRemoveDepartmentPartition(), CLAUDE.md
|
||
"Departments were retired"): eine rein lesende Liste, deren
|
||
eigentlicher Zweck — Beteiligte gezielt aus einer ANDEREN Abteilung
|
||
in die eigene Sitzung zu holen — mit der Zusammenlegung zu EINEM
|
||
gemeinsamen Personenkreis entfiel. Übrig blieb eine Dopplung der
|
||
Rollen-/Avatar-Chips, die in Wochenbericht/Board/Auslastung ohnehin
|
||
schon sichtbar sind. Auf Eriks Wunsch aus der Seitenleiste entfernt;
|
||
der Tab selbst (`#people`, `js/people/people.js`) und sein Slug
|
||
`Projektbeteiligte` in VALID_TAB_SLUGS bestehen unverändert fort —
|
||
ein alter Link/Deep-Link funktioniert also weiterhin, nur der
|
||
direkte Weg über die Navigation fehlt. -->
|
||
|
||
<!-- Projektbereich: der einzige Navigationspunkt, der nicht immer da
|
||
ist. Er erscheint, sobald ein Projekt geöffnet wurde, und trägt
|
||
dessen Bezeichnung (syncPlanNavItem() in js/tabs-auth.js). Ohne
|
||
ihn wäre im Projektbereich gar kein Punkt hervorgehoben und die
|
||
Seitenleiste zeigte nicht mehr, wo man gerade ist. -->
|
||
<button class="nav-item tab" data-tab="plan" id="planNavItem" style="display:none" onclick="showTab('plan')" title="Projektbereich">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="14" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="7" y1="13" x2="13" y2="13"/><line x1="7" y1="16" x2="16" y2="16"/></svg></span></span><span class="nav-label" id="planNavItemLabel">Projektbereich</span>
|
||
</button>
|
||
|
||
<!-- Planung: eigener, zweiter Abschnitt (nicht Teil von "Navigation"),
|
||
wie gewünscht — eine Sandbox für Terminpläne unabhängig von einem
|
||
echten Projekt (aus Honorar errechnet oder frei angelegt), siehe
|
||
CLAUDE.md "Planung — projektunabhängige Terminplan-Sandbox". -->
|
||
<div class="sidebar-section-label">Planung</div>
|
||
<button class="nav-item tab" data-tab="planung" onclick="showTab('planung')" title="Planung">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="17" rx="2.5"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="8" y1="2.5" x2="8" y2="6.5"/><line x1="16" y1="2.5" x2="16" y2="6.5"/><line x1="7" y1="14" x2="12" y2="14"/><line x1="7" y1="17.5" x2="15" y2="17.5"/></svg></span></span><span class="nav-label">Planung</span>
|
||
</button>
|
||
|
||
<!-- 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">Studenten</div>
|
||
<button class="nav-item tab" 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>
|
||
|
||
<button class="nav-item nav-group-toggle" id="toolsAccordionToggle" onclick="toggleSidebarAccordion('tools')" title="Werkzeuge" aria-expanded="false" aria-controls="toolsSubnav">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 6.2a3.6 3.6 0 0 1 4.8 4.4l-8.1 8.1a2 2 0 0 1-2.8-2.8l8.1-8.1"/><path d="M6.5 3.5 9 6l-3 3-2.5-2.5a3.5 3.5 0 0 1 3-3Z"/></svg></span></span><span class="nav-label">Werkzeuge</span>
|
||
<span class="nav-chevron"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
|
||
</button>
|
||
<div class="sidebar-subnav" id="toolsSubnav">
|
||
<!-- "+ Neues Projekt" stand bis zum Aufraeumen unter "Admin". Ein
|
||
Projekt anzulegen ist aber alltaegliche Arbeit: Es geht ueber den
|
||
normalen Wochen-Speicherweg (POST /save-week, requireAuth), der
|
||
Server verlangt dafuer keine Administratorrolle. Die Sperre war
|
||
rein optisch und hielt Mitglieder von gewoehnlicher Arbeit ab,
|
||
ohne irgendetwas zu schuetzen. -->
|
||
<!-- "Neue Sitzung"/"Sitzung duplizieren" standen hier bis zum
|
||
automatischen Sitzungswechsel (ensureCurrentWeekRollover(),
|
||
server.js): eine neue Sitzung entsteht jetzt serverseitig von
|
||
selbst, sobald der Kalender in eine neue ISO-Woche wechselt —
|
||
unabhängig davon, ob der Wochenbericht überhaupt geöffnet wird,
|
||
siehe CLAUDE.md. -->
|
||
<!-- "PDF Export" (die gewerkeübergreifende A3-Sitzungsübersicht,
|
||
exportPDF()) stand hier bis auf Eriks Wunsch: PDF-Export soll
|
||
nur noch im Wochenbericht (exportWochenberichtPDF()) und in den
|
||
Terminplanungen (exportSandboxPlanPDF()) stehen — beide siehe
|
||
js/pdf.js. Damit fällt die einzige gewerkeübergreifende
|
||
Sitzungsübersicht als PDF weg; die einzelnen Gewerke-Berichte
|
||
im Wochenbericht bleiben die Ablage. -->
|
||
<button class="menu-item sidebar-subitem" onclick="openNewProjWizard()">+ Neues Projekt</button>
|
||
<button class="menu-item sidebar-subitem" onclick="showTab('wand')">Wandtafel</button>
|
||
</div>
|
||
|
||
<!-- Admin: aufklappbare Gruppe ueber die Abschnitte von #admin. Wie
|
||
bei "Einstellungen" zuvor ist das KEINE Aktionsliste, sondern
|
||
Navigation innerhalb eines Bereichs — deshalb fuehrt schon der
|
||
Kopf-Eintrag dorthin (toggleAdminNav()) und die Unterpunkte
|
||
schalten nur den sichtbaren Abschnitt um (showAdminSection() in
|
||
js/admin/admin.js). Die gesamte Gruppe blendet renderUserBar()
|
||
(js/auth.js) fuer Nicht-Administratoren aus; die eigentliche
|
||
Sperre sitzt serverseitig auf den Routen. -->
|
||
<div id="navAdminSection" style="display:none">
|
||
<button class="nav-item nav-group-toggle" id="adminAccordionToggle" onclick="toggleAdminNav()" title="Admin" aria-expanded="false" aria-controls="adminSubnav">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5 5.5 6v5.3c0 4.4 2.9 7.4 6.5 8.7 3.6-1.3 6.5-4.3 6.5-8.7V6Z"/><path d="m9 12 2.1 2.1L15.5 9.5"/></svg></span></span><span class="nav-label">Admin</span>
|
||
<span class="nav-chevron"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
|
||
</button>
|
||
<div class="sidebar-subnav" id="adminSubnav">
|
||
<button class="menu-item sidebar-subitem" data-admin-section="benutzer" onclick="showAdminSection('benutzer')">Benutzerverwaltung</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="honorar" onclick="showAdminSection('honorar')">Honorar</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="auslastung" onclick="showAdminSection('auslastung')">Auslastung</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="termine" onclick="showAdminSection('termine')">Termine & 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 & Sicherheit</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="datenbank" onclick="showAdminSection('datenbank')">Datenbank & 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" onclick="openNewProjWizard()">+ Projekt</button>
|
||
</div>
|
||
<!-- Die Farbe der Kreise richtet sich nach der ROLLE (siehe
|
||
.board-avatar.role-* in styles.css), die Füllung nach der Art der
|
||
Zuordnung. Die frühere Legende erklärte nur Grün und behauptete damit
|
||
implizit, Projektleitung (rot) und Bauüberwachung (blau) gäbe es nicht. -->
|
||
<div class="legend">
|
||
<span><span class="swatch" style="background:var(--green)"></span> Planung</span>
|
||
<span><span class="swatch" style="background:var(--red)"></span> Projektleitung</span>
|
||
<span><span class="swatch" style="background:var(--accent)"></span> Bauüberwachung</span>
|
||
<span class="legend-sep">·</span>
|
||
<span><span class="swatch" style="background:var(--text-subtle)"></span> gefüllt = hauptverantwortlich</span>
|
||
<span><span class="swatch" style="background:transparent;box-shadow:inset 0 0 0 2px var(--text-subtle)"></span> umrandet = unterstützend</span>
|
||
</div>
|
||
<div id="boardTable"></div>
|
||
</div>
|
||
|
||
<!-- WOCHENBERICHT -->
|
||
<div id="wochenbericht" style="display:none">
|
||
<div class="bericht-titelzeile">
|
||
<div class="section-title" style="margin:0">Wochenbericht</div>
|
||
<button type="button" class="secondary small" onclick="openBerichtProtokoll()">Protokoll…</button>
|
||
<!-- Anderer Export als "Protokoll": Das Protokoll ist eine Kurzfassung
|
||
(was hat sich geändert + neue Aufgaben) zum Kopieren/Mailen. Dieser
|
||
Knopf legt die ganze Tagesordnung als PDF ab — eine Karte je
|
||
Projekt mit vollständigem Kommentar, Fortschritt und Beteiligten,
|
||
siehe exportWochenberichtPDF() in js/pdf.js. -->
|
||
<button type="button" class="secondary small" onclick="exportWochenberichtPDF()">Als PDF ablegen</button>
|
||
</div>
|
||
<div id="wochenberichtContent"></div>
|
||
<div id="weekAttendance"></div>
|
||
</div>
|
||
|
||
<!-- LOAD -->
|
||
<div id="load" style="display:none">
|
||
<div class="section-title">Auslastung der Projektbeteiligten</div>
|
||
<div class="load-grid" id="loadGrid"></div>
|
||
</div>
|
||
|
||
<!-- PEOPLE -->
|
||
<div id="people" style="display:none">
|
||
<div class="section-title">Projektbeteiligte</div>
|
||
<div id="peopleList"></div>
|
||
</div>
|
||
|
||
<!-- PLANUNG (js/planung/planung.js) — Terminplan-Sandbox, unabhängig von
|
||
einem echten Projekt. Bearbeitet wird ein einzelner Plan im ganz
|
||
normalen Projektbereich (#plan) — siehe openSandboxPlan(),
|
||
js/plan/plan-state.js. -->
|
||
<div id="planung" style="display:none">
|
||
<div class="bericht-titelzeile">
|
||
<div class="section-title" style="margin:0">Planung</div>
|
||
<button type="button" class="secondary small" onclick="oeffneNeuerTerminplanDialog()">+ Neuer Terminplan</button>
|
||
</div>
|
||
<p class="hint" style="margin:0 0 16px">Terminpläne unabhängig von einem laufenden Projekt — zum Ausprobieren, für Angebote, um Kapazität grob abzuschätzen.</p>
|
||
<div id="planungList"></div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- KI-Zugangsdaten: persönlich je Konto, nicht firmenweit — jeder
|
||
Nutzer hinterlegt seinen eigenen Schlüssel (+ optional eine
|
||
abweichende API-URL für einen lokalen Server wie LM Studio/
|
||
Ollama oder einen Firmen-Proxy) für die KI-gestützte
|
||
Punkt-Erkennung beim Protokoll-Import. Die Schlüssel werden
|
||
geschrieben, aber nie zurückgelesen (wie ein Passwort), siehe
|
||
readUserAiKey()/writeUserAiKey() in server.js und CLAUDE.md
|
||
"### Protokolle". Je Anbieter ein eigener, umrandeter Block mit
|
||
Cloud/Eigener-Server-Umschalter statt eines eingeklappten
|
||
"API-URL"-Links — die eigene Anbindung (z. B. LM Studio) ist ein
|
||
Kernfall, kein Nebendetail, siehe CLAUDE.md. Dazu ein
|
||
"Neu verbinden"-Knopf (echter Testaufruf über
|
||
POST /my-ai-settings/test, nicht nur Anzeige von "configured")
|
||
mit Fehlertext, der bis zum nächsten Testergebnis stehen bleibt
|
||
(Design-Entwürfe mit Erik abgestimmt: Artifacts d7ab2945,
|
||
01fe1346). -->
|
||
<section class="settings-card" id="settingsAiCard">
|
||
<h3 class="settings-card-title">KI-Zugangsdaten</h3>
|
||
<p class="hint">Für die automatische Punkt-Erkennung beim Protokoll-Import
|
||
(Projektbereich → Protokolle → „Protokoll importieren"). Ohne
|
||
hinterlegten Schlüssel wird automatisch die Textmuster-Erkennung
|
||
verwendet — ein Schlüssel ist also optional, nicht Voraussetzung.</p>
|
||
|
||
<div class="ai-provider" id="aiProviderChatgpt">
|
||
<div class="ai-provider-head">
|
||
<div class="ai-provider-mark chatgpt">Ai</div>
|
||
<div class="ai-provider-info">
|
||
<div class="ai-provider-name">ChatGPT</div>
|
||
<div class="ai-provider-sub">OpenAI</div>
|
||
</div>
|
||
<span class="ai-status-chip" id="aiOpenaiStatus"><span class="dot"></span>–</span>
|
||
<button class="ai-reconnect-btn" id="aiOpenaiReconnectBtn" onclick="testMyAiConnection('chatgpt')" title="Neu verbinden">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="ai-provider-row">
|
||
<input type="password" id="setAiOpenaiKey" placeholder="API-Schlüssel einfügen…" autocomplete="off">
|
||
<button class="ai-icon-btn" onclick="saveMyAiKey('chatgpt')" title="Speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
<button class="ai-text-btn" id="aiOpenaiClearBtn" onclick="clearMyAiKey('chatgpt')" title="Schlüssel entfernen">Entfernen</button>
|
||
</div>
|
||
<div class="ai-test-error" id="aiOpenaiTestError"></div>
|
||
<div class="ai-mode-toggle" id="aiOpenaiModeToggle">
|
||
<button type="button" class="is-active" onclick="setAiMode('chatgpt','cloud')">Cloud</button>
|
||
<button type="button" onclick="setAiMode('chatgpt','lokal')">Eigener Server</button>
|
||
</div>
|
||
<div class="ai-url-panel" id="aiOpenaiUrlPanel">
|
||
<label>Adresse des eigenen Servers</label>
|
||
<div class="ai-provider-row">
|
||
<input type="text" id="setAiOpenaiUrl" placeholder="z. B. http://localhost:1234 für LM Studio">
|
||
<button class="ai-icon-btn" onclick="saveMyAiUrl('chatgpt')" title="URL speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
</div>
|
||
<!-- 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 wird in <strong>Wochenstunden</strong> gerechnet: offener
|
||
Honorarwert der amtierenden Leistungsphase geteilt durch den Stundensatz, verteilt auf
|
||
die Rollen, die zugeordneten Personen und die verbleibenden Wochen. Die Ampel misst
|
||
jede Person an IHRER Stelle — 1,0 heißt voll ausgelastet; die vertraglichen
|
||
Wochenstunden stehen am Konto (Benutzerverwaltung).</p>
|
||
<div class="settings-field-grid">
|
||
<label class="settings-field"><span>Wochenstunden einer vollen Stelle</span>
|
||
<input type="number" id="setVollzeitStunden" min="1" max="80" step="0.5"></label>
|
||
<label class="settings-field"><span>Ampel gelb ab</span>
|
||
<input type="number" id="setSchwelleGelb" min="0.05" max="5" step="0.05"></label>
|
||
<label class="settings-field"><span>Ampel rot ab</span>
|
||
<input type="number" id="setSchwelleRot" min="0.05" max="5" step="0.05"></label>
|
||
<label class="settings-field"><span>Referenzhonorar für die Projektgröße (€)</span>
|
||
<input type="number" id="setReferenzHonorar" min="1000" max="100000000" step="1000"></label>
|
||
<label class="settings-field"><span>Verteilzeitraum ohne Terminplan (Wochen)</span>
|
||
<input type="number" id="setVerteilWochen" min="1" max="260" step="1"></label>
|
||
</div>
|
||
<p class="hint">Projekte ohne hinterlegtes Honorar tragen statt dessen eine
|
||
<strong>Projektgröße von 0 bis 1</strong> (Bereich „Einstellungen" des Projekts);
|
||
1,0 entspricht dem Referenzhonorar. Der Verteilzeitraum gilt, solange ein Projekt
|
||
keinen Terminplan hat, aus dem sich ein Ende ergibt — ohne Horizont wäre die
|
||
Wochenlast nicht bestimmt.</p>
|
||
|
||
<h4 class="settings-subtitle">Aufwand je Leistungsphase und Rolle</h4>
|
||
<p class="hint">Gilt für jedes Projekt, das in seiner eigenen Aufwandsmaske keinen Wert
|
||
trägt — also für die meisten. 0 = zählt nicht zur Auslastung, 1 = voller Aufwand.</p>
|
||
<div id="setAufwandMatrix" class="settings-matrix-wrap"></div>
|
||
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="saveAuslastungSettings()">Speichern</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminTerminCard" data-admin-section="termine" style="display:none">
|
||
<h3 class="settings-card-title">Termine & Sitzungen <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<div class="settings-field-grid">
|
||
<label class="settings-field"><span>Arbeitsstunden je Person und Werktag</span>
|
||
<input type="number" id="setStundenProTag" min="1" max="24" step="0.5"></label>
|
||
</div>
|
||
<label class="settings-check">
|
||
<input type="checkbox" id="setAutoLock">
|
||
<span>Ältere Sitzungen automatisch sperren</span>
|
||
</label>
|
||
<p class="hint">Ist das aus, bleibt jede Sitzung bearbeitbar. Bereits gesperrte Sitzungen
|
||
bleiben gesperrt — entsperrt wird weiterhin bewusst über „Sitzung entsperren".</p>
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="saveTerminSettings()">Speichern</button>
|
||
</div>
|
||
|
||
<!-- Die Oberfläche zeigt sonst nirgends mehr, welche Sitzung gerade
|
||
offen ist — dafür genügt eine Zeile, da es ohnehin immer nur
|
||
eine gibt. Diese Liste ist die einzige Stelle, die alle
|
||
Sitzungen nebeneinander zeigt: zur Kontrolle und um eine
|
||
gesperrte für eine nachträgliche Korrektur zu entsperren. -->
|
||
<h4 class="settings-subtitle">Sitzungen</h4>
|
||
<p class="hint">Jede Sitzung ist ein Wochenstand für Speicherung und Verlauf — bearbeitet
|
||
wird immer nur die eine offene. Eine gesperrte Sitzung lässt sich hier entsperren und
|
||
öffnen, etwa um einen Kommentar nachträglich zu korrigieren; beim nächsten Laden sperrt
|
||
die Automatik sie wieder, sofern sie aktiv ist.</p>
|
||
<div class="template-admin-list" id="adminSessionsList"></div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminHolidayCard" data-admin-section="feiertage" style="display:none">
|
||
<h3 class="settings-card-title">Feiertage <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Arbeitsfreie Tage zählen bei der Terminplan-Berechnung aus dem Honorar und
|
||
bei der Ressourcen-Auslastung nicht als Arbeitstag. Vorher kannte die Anwendung nur Wochenenden.</p>
|
||
<div id="holidayYearHint" class="settings-warn" style="display:none;margin-bottom:10px"></div>
|
||
|
||
<h4 class="settings-subtitle">Gesetzliche Feiertage übernehmen</h4>
|
||
<p class="hint">Werden aus dem Kalender berechnet (feste Termine plus alles, was am
|
||
Osterdatum hängt) — kein Netzdienst, kein Internetzugang nötig. Vorgeschlagen, nicht
|
||
automatisch eingetragen: Feiertage verschieben jeden gerechneten Terminplan.</p>
|
||
<div class="settings-inline-field">
|
||
<span>Jahr</span>
|
||
<input type="number" id="holidayYear" min="1970" max="2200" step="1" style="width:90px">
|
||
<span>Bundesland</span>
|
||
<select id="holidayState"></select>
|
||
<button class="secondary small" onclick="suggestHolidays()">Vorschlagen</button>
|
||
</div>
|
||
<div id="holidaySuggestion"></div>
|
||
|
||
<h4 class="settings-subtitle">Hinterlegte Tage</h4>
|
||
<div id="adminHolidayList" class="settings-holiday-list"></div>
|
||
<div class="template-add-row">
|
||
<input type="date" id="holidayNewDate">
|
||
<input type="text" id="holidayNewName" placeholder="Bezeichnung, z. B. „Betriebsruhe“">
|
||
<button class="secondary small" onclick="addHoliday()">+ Einzelner Tag</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminBetriebCard" data-admin-section="betrieb" style="display:none">
|
||
<h3 class="settings-card-title">Betrieb & Sicherheit <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<div class="settings-field-grid">
|
||
<label class="settings-field"><span>Aufbewahrte automatische Backups</span>
|
||
<input type="number" id="setMaxBackups" min="1" max="500" step="1"></label>
|
||
<label class="settings-field"><span>Gültigkeit einer Anmeldung (Tage)</span>
|
||
<input type="number" id="setSessionTage" min="1" max="365" step="1"></label>
|
||
<label class="settings-field"><span>Fehlversuche bis zur Anmeldesperre</span>
|
||
<input type="number" id="setLoginVersuche" min="1" max="100" step="1"></label>
|
||
<label class="settings-field"><span>Dauer der Anmeldesperre (Minuten)</span>
|
||
<input type="number" id="setLoginSperre" min="1" max="1440" step="1"></label>
|
||
</div>
|
||
<p class="hint">Die Gültigkeit wirkt auf neue Anmeldungen; bestehende behalten ihre Laufzeit.</p>
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="saveBetriebSettings()">Speichern</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card settings-card-wide" id="adminTemplateCard" data-admin-section="vorlagen" style="display:none">
|
||
<h3 class="settings-card-title">Aufgaben-Vorlagen <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Wiederkehrende Schritte einer Leistungsphase. Im Projektbereich fügt
|
||
„+ Vorlage" sie als Aufgaben ein; danach sind es ganz normale Aufgaben — eine später
|
||
geänderte Vorlage wirkt NICHT rückwirkend.</p>
|
||
<label class="settings-inline-field">
|
||
<span>Leistungsphase</span>
|
||
<select id="adminTemplateLph" onchange="renderAdminTemplates()"></select>
|
||
</label>
|
||
<div id="templateAdminList" class="template-admin-list"></div>
|
||
<div class="template-add-row">
|
||
<input type="text" id="templateNewTitle" placeholder="Neuer Schritt, z. B. „LV erstellen“">
|
||
<input type="number" id="templateNewOffset" value="0" min="0" title="Start in Tagen nach dem Startdatum">
|
||
<input type="number" id="templateNewDuration" value="1" min="1" title="Dauer in Tagen">
|
||
<button class="secondary small" onclick="addTemplateEntry()">+ Schritt</button>
|
||
</div>
|
||
</section>
|
||
<section class="settings-card settings-card-wide" id="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="adminUsersCard" data-admin-section="benutzer" style="display:none">
|
||
<h3 class="settings-card-title">Benutzerverwaltung <span class="settings-scope">nur Administratoren</span></h3>
|
||
<p class="hint">Konten anlegen, bearbeiten, deaktivieren, Passwoerter zuruecksetzen und
|
||
Abwesenheiten pflegen. Die <strong>Wochenstunden</strong> hier sind die Bezugsgroesse der
|
||
Auslastungs-Ampel; die <strong>Person-ID</strong> verbindet das Konto mit den
|
||
Aufgaben-Zuordnungen und fuellt damit „Meine Aufgaben".</p>
|
||
<div id="generatedPasswordBox"></div>
|
||
|
||
<div class="toolbar admin-toolbar">
|
||
<button onclick="openNewUserModal()">+ Neuer Benutzer</button>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table id="usersTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzername</th>
|
||
<th>Anzeigename</th>
|
||
<th>E-Mail</th>
|
||
<th>Rolle</th>
|
||
<th>Abteilung</th>
|
||
<th>Kürzel</th>
|
||
<th>Std./Woche</th>
|
||
<th>Person-ID</th>
|
||
<th>Status</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="usersTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminDatenbankCard" data-admin-section="datenbank" style="display:none">
|
||
<h3 class="settings-card-title">Datenbank & 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 & Sicherheit"). „DB Backup" lädt zusätzlich den aktuellen Stand herunter.
|
||
<strong>Die Datei enthält die komplette Datenbank samt Sitzungs-Token</strong> — sie
|
||
gehört nicht in fremde Hände.</p>
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="downloadBackup()">DB Backup herunterladen</button>
|
||
</div>
|
||
|
||
<h4 class="settings-subtitle">Wiederherstellen</h4>
|
||
<p class="hint">Spielt eine mit „DB Backup" erzeugte Datei ein und <strong>ersetzt damit
|
||
alle Sitzungsdaten</strong>. Konten, Rollen, Abwesenheiten und Datei-Freigaben bleiben
|
||
unberührt. Unmittelbar vorher sichert der Server den aktuellen Stand, ein Einspielen
|
||
lässt sich also rückgängig machen.</p>
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="importDb()">DB Import…</button>
|
||
</div>
|
||
|
||
<!-- Das Löschen einzelner Sitzungen ist zur Sitzungsliste unter
|
||
"Termine & Sitzungen" gewandert (renderAdminSitzungen()) — dort
|
||
steht ohnehin schon, welche Sitzung welche ist; ein zweiter,
|
||
von "der im Menüband gewählten" abhängiger Knopf hier wäre seit
|
||
dem Wegfall der Menüband-Auswahl gegenstandslos gewesen. -->
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div id="adminNote" class="settings-note"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ===================== PROJEKTBEREICH (#plan) =====================
|
||
Der eigene Bereich EINES Projekts — frueher die eigenstaendige Seite
|
||
public/project-plan.html, jetzt ein ganz normaler Bereich dieser Seite.
|
||
Der Wechsel dorthin laeuft ueber showTab('plan') (js/tabs-auth.js) wie
|
||
jeder andere Navigationswechsel, sodass Menueband und Seitenleiste
|
||
stehen bleiben; geoeffnet wird er ueber openProjectPlan() (js/gantt.js).
|
||
Er steht bewusst AUSSERHALB von .container, weil er seine eigene
|
||
Kopfzeile und eigene Innenabstaende mitbringt (siehe plan-styles.css).
|
||
Die fuenf Unterbereiche (Dashboard, Terminplan, Aufgaben, Dateien,
|
||
Einstellungen) schaltet .plan-nav um (showPlanSection() in
|
||
js/plan/plan-sections.js); alle liegen gleichzeitig im DOM. -->
|
||
<div id="plan" style="display:none">
|
||
<div class="plan-page">
|
||
<div class="plan-header">
|
||
<span class="plan-id" id="planIdBadge"></span>
|
||
<h1 id="planTitle">Projektplan</h1>
|
||
<div class="tag-chip-list" id="planTagsList"></div>
|
||
<div class="plan-header-spacer"></div>
|
||
<div class="subtitle" id="planSubtitle"></div>
|
||
<div class="plan-avatar-stack" id="planPeopleList"></div>
|
||
<div class="plan-header-actions">
|
||
<button class="theme-toggle-circle" onclick="todoMail()" title="E-Mail erstellen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 6.5 8 6.5 8-6.5"/></svg></span>
|
||
</button>
|
||
<button class="theme-toggle-circle" onclick="closePlanPage()" title="Schließen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bereichs-Navigation des Projektbereichs: schaltet zwischen den fünf
|
||
Bereichen um (showPlanSection() in js/plan/plan-sections.js). Die
|
||
Bereiche liegen alle gleichzeitig im DOM und werden nur ein- bzw.
|
||
ausgeblendet, damit die vorhandenen Render-Funktionen ihre Elemente
|
||
unabhängig von der Sichtbarkeit befüllen können. -->
|
||
<nav class="plan-nav" id="planNav">
|
||
<button class="plan-nav-item" data-section="dashboard" onclick="showPlanSection('dashboard')">Dashboard</button>
|
||
<button class="plan-nav-item" data-section="terminplan" onclick="showPlanSection('terminplan')">Terminplan</button>
|
||
<button class="plan-nav-item" data-section="aufgaben" onclick="showPlanSection('aufgaben')">Aufgaben</button>
|
||
<button class="plan-nav-item" data-section="leistung" onclick="showPlanSection('leistung')">Leistung</button>
|
||
<button class="plan-nav-item" data-section="dateien" onclick="showPlanSection('dateien')">Dateien</button>
|
||
<button class="plan-nav-item" data-section="protokolle" onclick="showPlanSection('protokolle')">Protokolle</button>
|
||
<button class="plan-nav-item" data-section="einstellungen" onclick="showPlanSection('einstellungen')">Einstellungen</button>
|
||
</nav>
|
||
|
||
<div class="plan-body">
|
||
<div class="plan-main">
|
||
<div class="plan-main-content">
|
||
|
||
<!-- ===================== Bereich: Dashboard ===================== -->
|
||
<section class="plan-section" id="planSection-dashboard" data-section="dashboard">
|
||
<div class="plan-kpi-row" id="planKpiRow"></div>
|
||
|
||
<div class="plan-panel-grid">
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Kommentar der Sitzung</h3>
|
||
<!-- Nur zu lesen: Der Kommentar entsteht in der wöchentlichen
|
||
Besprechung und wird deshalb allein im Wochenbericht
|
||
erfasst. Die frühere Werkzeugleiste (Fett/Farben) ist mit
|
||
dem Bearbeiten hier entfallen. -->
|
||
<div class="status" id="planStatusWrap">
|
||
<div class="status-edit" id="planStatusEdit"></div>
|
||
</div>
|
||
<div class="plan-verweis-row" id="planStatusVerweis"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Leistungsphasen</h3>
|
||
<div class="board-lph-list" id="planLphList"></div>
|
||
<div class="plan-verweis-row" id="planLphVerweis"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Nächste Termine</h3>
|
||
<div id="planDashUpcoming"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Team</h3>
|
||
<div id="planDashTeam"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="plan-card-row">
|
||
<div class="plan-mini-card" onclick="openResourceModal()">
|
||
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Ressourcen-Auslastung</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
|
||
<div id="planResourceSummary"></div>
|
||
</div>
|
||
<div class="plan-mini-card" onclick="openFilesPanel()">
|
||
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Dateien & Freigaben</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
|
||
<div id="planFilesSummary"></div>
|
||
</div>
|
||
<div class="plan-mini-card" onclick="openProtokollePanel()">
|
||
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Protokolle</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
|
||
<div id="planProtokolleSummary"></div>
|
||
</div>
|
||
<div class="plan-mini-card" onclick="openHistoryModal()">
|
||
<div class="plan-mini-card-head"><span class="plan-mini-card-title">Verlauf</span><span class="plan-mini-card-expand">Öffnen ↗</span></div>
|
||
<div id="planHistorySummary"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== Bereich: Terminplan ==================== -->
|
||
<section class="plan-section" id="planSection-terminplan" data-section="terminplan">
|
||
<div class="plan-main-header">
|
||
<label>Zeitraum:</label>
|
||
<input type="text" id="planGanttRange" class="range-picker-display" placeholder="Zeitraum wählen…" readonly
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'planGanttStart', endInputId: 'planGanttEnd', displayId: 'planGanttRange', onApply: renderPlanGantt })">
|
||
<input type="hidden" id="planGanttStart">
|
||
<input type="hidden" id="planGanttEnd">
|
||
<button class="secondary" onclick="fitPlanGanttRange()">Zeitraum anpassen</button>
|
||
<label><input type="checkbox" id="planShowVacation" checked onchange="renderPlanGantt()"> Urlaub anzeigen</label>
|
||
<label title="Termine der vorherigen Sitzung als gestrichelten Umriss einblenden"><input type="checkbox" id="planShowGhost" checked onchange="renderPlanGantt()"> Vorwoche</label>
|
||
<button class="secondary" onclick="zoomPlanGantt(-1)" title="Verkleinern">−</button>
|
||
<button class="secondary" onclick="zoomPlanGantt(1)" title="Vergrößern">+</button>
|
||
<button onclick="addPlanMilestone()" id="addMilestoneBtn">+ Meilenstein</button>
|
||
<button onclick="addTodo()" id="addTodoBtn">+ TODO</button>
|
||
<button onclick="openTemplateModal()" id="templateBtn" title="Standard-Aufgaben einer Leistungsphase einfügen">+ Vorlage</button>
|
||
<button onclick="openHonorarModal()" id="honorarBtn" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">+ Terminplan</button>
|
||
<button onclick="openProtokollTodosModal()" id="protokollTodosBtn" title="Externes Besprechungsprotokoll (PDF) per KI einlesen und daraus Aufgaben ableiten">+ TODOs aus Protokoll</button>
|
||
<button onclick="autoScheduleTodos()" id="autoScheduleBtn" title="Aufgaben nach Abhängigkeiten automatisch anordnen">Auto-Schedule</button>
|
||
<button onclick="toggleCriticalPath()" id="criticalPathBtn" title="Kritischen Pfad ein-/ausblenden">Kritischer Pfad</button>
|
||
<!-- Nur für Sandbox-Pläne der Planung sichtbar (renderPlanPage(),
|
||
js/plan/plan-state.js) — ein echtes Projekt hat keinen
|
||
vergleichbaren Gantt-PDF-Export. -->
|
||
<button class="secondary" onclick="exportSandboxPlanPDF(PlanApp.project.id)" id="sandboxPdfBtn" style="display:none">Als PDF exportieren</button>
|
||
</div>
|
||
<div id="planWarnings"></div>
|
||
<div id="planGanttContainer" class="gantt-container"></div>
|
||
|
||
<div class="plan-panel" style="margin-top:18px">
|
||
<h3 class="plan-panel-title">Ressourcen-Auslastung</h3>
|
||
<div id="resourceLoadContainer"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ====================== Bereich: Aufgaben ===================== -->
|
||
<section class="plan-section" id="planSection-aufgaben" data-section="aufgaben">
|
||
<div class="plan-main-header">
|
||
<button onclick="addTodo()" id="addTodoBtnList">+ Aufgabe</button>
|
||
<button onclick="addPlanMilestone()" id="addMilestoneBtnList">+ Meilenstein</button>
|
||
<button onclick="openTemplateModal()" id="templateBtnList" title="Standard-Aufgaben einer Leistungsphase einfügen">+ Vorlage</button>
|
||
<button onclick="openHonorarModal()" id="honorarBtnList" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">+ Terminplan</button>
|
||
<button 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="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 & 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 & Freigaben</h3>
|
||
<div id="filesListContainer" class="files-list"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== Bereich: Protokolle ===================== -->
|
||
<section class="plan-section" id="planSection-protokolle" data-section="protokolle">
|
||
<div id="protokolleNoProjectHint" class="hint" style="display:none">Dieses Projekt hat keine Projektnummer und kann daher keine Protokolle erhalten.</div>
|
||
<div id="protokolleContainer"></div>
|
||
</section>
|
||
|
||
<!-- =================== Bereich: Einstellungen =================== -->
|
||
<section class="plan-section" id="planSection-einstellungen" data-section="einstellungen">
|
||
<div id="planSettingsLockHint" class="hint" style="display:none">Diese Sitzung ist gesperrt — Einstellungen können nicht geändert werden.</div>
|
||
|
||
<div class="plan-panel-grid">
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Projektangaben</h3>
|
||
<!-- Projektnummer, Bezeichnung und Schlagwörter waren zuvor
|
||
zusätzlich im eigenen Tab "Projekte" bearbeitbar, der
|
||
dieselben Projekte ein zweites Mal auflistete. Er ist in die
|
||
Projektliste aufgegangen; gepflegt werden sie jetzt hier. -->
|
||
<div class="form-row">
|
||
<label>Projektnummer</label>
|
||
<input type="text" id="settingsProjNr" placeholder="z. B. 26-4071"
|
||
onchange="setPlanProjNr(this.value)"
|
||
title="Kennzeichen des Projekts. Hochgeladene Dateien hängen an dieser Nummer — eine Änderung wird deshalb noch einmal abgefragt.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Projektname</label>
|
||
<input type="text" id="settingsProjName" placeholder="Bezeichnung"
|
||
onchange="setPlanProjName(this.value)">
|
||
</div>
|
||
<!-- Auftragsstatus: Anfrage → Angebot → beauftragt →
|
||
abgeschlossen. Der Sprung auf "beauftragt" öffnet den
|
||
Beauftragungs-Dialog (js/auftrag.js), weil dazu Honorar und
|
||
beauftragte Leistungsphasen gehören. -->
|
||
<div class="form-row">
|
||
<label>Auftragsstatus</label>
|
||
<div id="settingsAuftragsstatus"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Gruppe</label>
|
||
<select id="planGroupSelect" onchange="setPlanGroup(this.value)">
|
||
<option>Aktive Projekte</option>
|
||
<option>Kleine Projekte</option>
|
||
<option>In Anbahnung</option>
|
||
<option>Allgemein</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Amtierende Leistungsphase</label>
|
||
<select id="planActiveLpSelect" onchange="setPlanActiveLp(this.value)"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Honorar (netto)</label>
|
||
<input type="number" id="settingsHonorar" min="0" step="100" placeholder="0"
|
||
onchange="setHonorarBetrag(this.value)"
|
||
title="Vereinbartes Netto-Honorar. Grundlage der Terminplan-Berechnung und dauerhaft am Projekt gespeichert.">
|
||
</div>
|
||
<!-- Projektgröße: Ersatz für ein fehlendes Honorar. Die
|
||
Auslastung rechnet in Stunden und braucht dafür einen
|
||
Geldwert; ist keiner hinterlegt, tritt diese Zahl mal dem
|
||
Referenzhonorar (Admin → Auslastung) an seine Stelle. -->
|
||
<div class="form-row">
|
||
<label>Projektgröße</label>
|
||
<!-- Zahlenfeld statt Regler: Ein Schieber legt eine
|
||
Feinabstufung nahe, die eine grobe Schätzung nicht hat,
|
||
und lässt sich schlechter treffen als eine getippte Zahl. -->
|
||
<input type="number" id="settingsGroesse" min="0" max="1" step="0.05" value="0"
|
||
placeholder="z. B. 0,40" style="max-width:160px"
|
||
onchange="setPlanGroesse(this.value)">
|
||
<span class="hint" id="settingsGroesseHinweis" style="margin:4px 0 0"></span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Schlagwörter</label>
|
||
<div class="tag-chip-list" id="settingsProjTags"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Gewerke</label>
|
||
<div id="settingsGewerke" class="beauftragte-lp-grid"></div>
|
||
<span class="hint" style="margin:4px 0 0">
|
||
Bestimmt, in welcher Gewerke-Besprechung des Wochenberichts dieses Projekt erscheint.
|
||
</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Aufwand je Phase</label>
|
||
<button type="button" class="secondary small" id="settingsAufwandBtn" onclick="planOpenAufwand()"
|
||
title="Gewichtung des Aufwands je Leistungsphase und Rolle — Grundlage der Auslastungsrechnung.">Gewichtung bearbeiten</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Beauftragte Leistungsphasen</h3>
|
||
<p class="hint" style="margin-top:0">Welche Leistungsphasen für dieses Projekt beauftragt sind. Grundlage der Terminplan-Berechnung aus dem Honorar; der Wochenbericht zeigt ebenfalls nur diese Phasen.</p>
|
||
<div id="settingsBeauftragteLp" class="beauftragte-lp-grid"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel plan-panel-wide">
|
||
<h3 class="plan-panel-title">Beteiligte</h3>
|
||
<p class="hint" style="margin-top:0">Wer diesem Projekt zugeordnet ist — kräftig die Hauptverantwortung, blass die Unterstützung. Zum Ändern anklicken.</p>
|
||
<div class="board-role-mini" id="planAssignMatrix"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Aufwand je Leistungsphase Modal -->
|
||
<div class="modal-bg" id="lpModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Aufwand je Leistungsphase</h2>
|
||
<p style="color:#888;margin-bottom:6px" id="lpModalProject"></p>
|
||
<p class="hint" style="margin-bottom:10px">
|
||
Rollenbezogene Gewichtungsfaktoren je Leistungsphase (Standardwerte laut
|
||
Vorlage im Code). Die amtierende Leistungsphase wird pro Projekt im Tab
|
||
„Projekte" gewählt.
|
||
</p>
|
||
<div id="lpModalRows"></div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveLpModal()">Speichern</button>
|
||
<button class="secondary" onclick="closeLpModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Neues Projekt Modal (inkl. LP-Aufwand) -->
|
||
<!-- Assistent "Neues Projekt" (js/projekt-wizard.js). Er ersetzt den
|
||
früheren Dialog #newProjModal, der alle Felder auf einmal zeigte — samt
|
||
einer 27-Felder-Matrix für den Aufwand je Phase und Rolle. Der Rumpf
|
||
wird vollständig aus JavaScript gefüllt; hier steht nur die Hülle. -->
|
||
<div class="modal-bg" id="wizardModal" style="display:none">
|
||
<div class="modal wizard-modal">
|
||
<div class="wiz-kopf">
|
||
<h2>Neues Projekt</h2>
|
||
<button type="button" class="wiz-schliessen" onclick="closeNewProjWizard()" title="Abbrechen">×</button>
|
||
</div>
|
||
<div id="wizardBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Beauftragung: der Übergang vom Angebot zum beauftragten Projekt
|
||
(js/auftrag.js). Erfasst genau die Angaben, die zur Beauftragung gehören
|
||
und die der Terminplan aus dem Honorar braucht (js/plan/plan-honorar.js):
|
||
vereinbartes Netto-Honorar und beauftragte Leistungsphasen. -->
|
||
<div class="modal-bg" id="beauftragungModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Beauftragung</h2>
|
||
<p style="color:#888;margin-bottom:2px" id="beauftragungProjekt"></p>
|
||
<p class="hint" style="margin:0 0 10px" id="beauftragungStatusHinweis"></p>
|
||
<div class="form-row">
|
||
<label>Honorar (netto)</label>
|
||
<!-- type="number" wie im Honorar-Dialog (#honorarBetrag): Der Browser
|
||
liefert den Wert dann immer mit Punkt als Dezimaltrenner, und ein
|
||
Tausenderpunkt ("48.500") kann gar nicht erst als 48,5 durchgehen. -->
|
||
<input type="number" min="0" step="100" id="beauftragungHonorar" placeholder="z. B. 48500" style="width:100%"
|
||
title="Vereinbartes Netto-Honorar in Euro. Grundlage des Terminplans aus dem Honorar.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Beauftragte Leistungsphasen</label>
|
||
<div id="beauftragungBeauftragteLp" class="beauftragte-lp-grid"></div>
|
||
<span class="hint" style="margin:4px 0 0">
|
||
Das Honorar wird auf genau diese Phasen verteilt.
|
||
</span>
|
||
</div>
|
||
<!-- Nur sichtbar, solange das Projekt in „In Anbahnung" liegt: Diese
|
||
Gruppe wird von der Auslastung nicht eingerechnet. -->
|
||
<div class="form-row" id="beauftragungGruppeWrap" style="display:none">
|
||
<label class="person-chip-label">
|
||
<input type="checkbox" id="beauftragungGruppeCheck">
|
||
Projekt aus „In Anbahnung" nach „Aktive Projekte" verschieben
|
||
</label>
|
||
<span class="hint" style="margin:4px 0 0">
|
||
Projekte in „In Anbahnung" zählen in der Auslastung nicht mit.
|
||
</span>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveBeauftragung(false)">Als beauftragt kennzeichnen</button>
|
||
<button onclick="saveBeauftragung(true)" title="Speichert und öffnet den Projektbereich, dort „+ Terminplan“">Beauftragen und zum Terminplan</button>
|
||
<button class="secondary" onclick="closeBeauftragungModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Neuer Terminplan (js/planung/planung.js) — schlanker Anlege-Dialog der
|
||
Planung-Sandbox: nur Name + Art, bewusst kein mehrstufiger Assistent wie
|
||
"Neues Projekt" (der verlangt Gewerke/Team/Auftragsstatus, die hier nicht
|
||
zutreffen), siehe CLAUDE.md "Planung — projektunabhängige Terminplan-
|
||
Sandbox". -->
|
||
<div class="modal-bg" id="neuerTerminplanModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Neuer Terminplan</h2>
|
||
<p class="hint">Unabhängig von einem echten Projekt — für Angebote, Kapazitätstests oder eigene Vorhaben.</p>
|
||
<div class="form-row">
|
||
<label>Name</label>
|
||
<input type="text" id="planungName" placeholder="z. B. Angebot Sporthalle Menden" style="width:100%">
|
||
</div>
|
||
<div class="form-row">
|
||
<label style="display:block;margin-bottom:10px">Wie soll's losgehen?</label>
|
||
<div class="planung-choice-cards">
|
||
<div class="choice-card" data-art="honorar" onclick="waehlePlanungArt('honorar')">
|
||
<span class="choice-card-icon">€</span>
|
||
<div class="choice-card-title">Aus Honorar berechnen</div>
|
||
<div class="choice-card-desc">Honorar und Leistungsphasen angeben, der vorhandene Terminplan-Rechner erstellt die Aufgaben.</div>
|
||
</div>
|
||
<div class="choice-card" data-art="leer" onclick="waehlePlanungArt('leer')">
|
||
<span class="choice-card-icon">✎</span>
|
||
<div class="choice-card-title">Leer beginnen</div>
|
||
<div class="choice-card-desc">Direkt in den leeren Terminplan, Aufgaben und Meilensteine von Hand anlegen.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="erstellePlanungTerminplan()">Anlegen</button>
|
||
<button class="secondary" onclick="schliesseNeuerTerminplanDialog()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== Dialoge des Projektbereichs =====================
|
||
Gehoeren zum Bereich #plan (frueher project-plan.html) und liegen wie alle
|
||
anderen Dialoge dieser Seite auf oberster Ebene, damit .modal-bg den
|
||
ganzen Bildschirm ueberdeckt. Befuellt werden sie von den Modulen unter
|
||
public/js/plan/ (plan-modals.js, plan-templates.js, plan-honorar.js,
|
||
plan-history.js). -->
|
||
<!-- Verlaufs-Modal: Kommentar-Historie des Projekts über alle Sitzungen -->
|
||
<div class="modal-bg" id="planHistoryModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2>Verlauf</h2>
|
||
<p class="hint" style="margin-top:-4px">Status-Kommentare dieses Projekts aus allen Sitzungen, neueste zuerst. Aufgeführt sind nur Sitzungen, in denen der Text tatsächlich geändert wurde.</p>
|
||
<div id="planHistoryList" class="history-list"></div>
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button class="secondary" onclick="closeHistoryModal()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Protokoll-Import: externes Protokoll (PDF/Word) hochladen, Punkte werden
|
||
serverseitig heuristisch erkannt (POST /project-protokolle/import,
|
||
js/plan/plan-protokoll.js) — Ergebnis landet als ganz normales,
|
||
editierbares Protokoll, keine eigene Vorschau-Oberfläche nötig. -->
|
||
<div class="modal-bg" id="protokollImportModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Protokoll importieren</h2>
|
||
<p class="hint">Ein extern erstelltes Protokoll (PDF oder Word) hochladen — die
|
||
App versucht, die Punkte automatisch zu erkennen. Das Ergebnis ist ein
|
||
Entwurf: bitte vor dem Speichern prüfen und bei Bedarf anpassen.</p>
|
||
<div class="form-row">
|
||
<label>Datei</label>
|
||
<input type="file" id="protokollImportFile" accept=".pdf,.docx">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Erkennung</label>
|
||
<select id="protokollImportErkennung"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Datum</label>
|
||
<input type="text" id="protokollImportDatumDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'protokollImportDatum', displayId: 'protokollImportDatumDisplay' })">
|
||
<input type="hidden" id="protokollImportDatum">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Betreff</label>
|
||
<input type="text" id="protokollImportBetreff" placeholder="z. B. Baubesprechung TGA">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Ort</label>
|
||
<input type="text" id="protokollImportOrt">
|
||
</div>
|
||
<span class="field-error" id="protokollImportError" hidden></span>
|
||
<!-- Läuft nur bei der KI-Erkennung an, kann dort je nach Anbieter/
|
||
eigenem Server (KI_TIMEOUT_MS_LOKAL, server.js) bis zu 30 Minuten
|
||
dauern — ohne diesen Hinweis wirkte der Dialog in der Zeit wie
|
||
hängengeblieben. -->
|
||
<div class="protokoll-import-loading" id="protokollImportLoading" hidden>
|
||
<span class="protokoll-import-spinner" aria-hidden="true"></span>
|
||
<span>Importiere … bei KI-Erkennung über einen eigenen Server kann dies
|
||
bis zu 30 Minuten dauern.</span>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="importiereProtokoll()">Importieren</button>
|
||
<button class="secondary" onclick="schliesseProtokollImportDialog()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- "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>Leistungsphase</label>
|
||
<select id="templateLphSelect" onchange="renderTemplateList()"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Startdatum</label>
|
||
<input type="text" id="templateStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'templateStart', displayId: 'templateStartDisplay', onApply: renderTemplateList })">
|
||
<input type="hidden" id="templateStart">
|
||
</div>
|
||
</div>
|
||
|
||
<label class="form-row" style="margin-bottom:4px"><span>Schritte</span></label>
|
||
<div id="templateTaskList" class="todo-depends-list"></div>
|
||
|
||
<!-- Gepflegt werden die Vorlagen im Bereich „Einstellungen"; hier werden
|
||
sie nur noch eingefügt. Der Verweis erscheint nur für Administratoren
|
||
(renderTemplateHint() in js/plan/plan-templates.js). -->
|
||
<p class="hint" id="templateAdminHint" style="display:none">
|
||
Vorlagen pflegen: <a href="/Einstellungen" onclick="closeTemplateModal();showTab('settings');return false;">Einstellungen → Aufgaben-Vorlagen</a>
|
||
</p>
|
||
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button onclick="applyTemplate()">Aufgaben einfügen</button>
|
||
<button class="secondary" onclick="closeTemplateModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Honorar-Dialog: kompletten Terminplan aus dem Honorar errechnen
|
||
(js/plan/plan-honorar.js). Zweiter der beiden getrennten Dialoge, siehe
|
||
Kommentar beim Vorlagen-Dialog oben. -->
|
||
<div class="modal-bg" id="honorarPlanModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2>Terminplan aus Honorar</h2>
|
||
|
||
<div class="template-head-grid">
|
||
<div class="form-row">
|
||
<label>Honorar (netto)</label>
|
||
<input type="number" id="honorarBetrag" min="0" step="100" placeholder="0"
|
||
onchange="setHonorarBetrag(this.value)"
|
||
title="Vereinbartes Netto-Honorar. Wird am Projekt gespeichert und bleibt erhalten.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Beginn der ersten Leistungsphase</label>
|
||
<input type="text" id="honorarStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'honorarStart', displayId: 'honorarStartDisplay', onApply: renderHonorarPlan })">
|
||
<input type="hidden" id="honorarStart">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Beauftragte Leistungsphasen</label>
|
||
<div id="honorarBeauftragteLp" class="beauftragte-lp-grid"></div>
|
||
</div>
|
||
|
||
<label class="honorar-check" title="Sind nur einzelne Phasen beauftragt, ist das vereinbarte Honorar das Honorar für genau diese Phasen. Ohne Normierung bliebe ein Teil des Honorars unverplant.">
|
||
<input type="checkbox" id="honorarNormieren" checked onchange="renderHonorarPlan()">
|
||
Honorar auf die beauftragten Phasen normieren
|
||
</label>
|
||
|
||
<div id="honorarPlanTable" class="honorar-plan-wrap"></div>
|
||
|
||
<!-- Anteile und Stundensatz werden im Bereich „Einstellungen" gepflegt;
|
||
hier wird damit nur gerechnet. Der Verweis erscheint nur für
|
||
Administratoren (renderHonorarHint() in js/plan/plan-honorar.js). -->
|
||
<p class="hint" id="honorarAdminHint" style="display:none">
|
||
Anteile und Stundensatz pflegen: <a href="/Einstellungen" onclick="closeHonorarModal();showTab('settings');return false;">Einstellungen → Honorar</a>
|
||
</p>
|
||
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button onclick="applyHonorarPlan()">Terminplan einfügen</button>
|
||
<button class="secondary" onclick="closeHonorarModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Meilenstein-Modal -->
|
||
<div class="modal-bg" id="milestoneModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Meilenstein</h2>
|
||
<div class="form-row">
|
||
<label>Name</label>
|
||
<input type="text" id="milestoneEditName" placeholder="Name">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Datum</label>
|
||
<!-- Getrennte Felder wie bei allen anderen Datumseingaben der Seite:
|
||
sichtbares Anzeigefeld (deutsches Format) + verstecktes ISO-Feld.
|
||
Vorher war beides dasselbe Feld, weshalb dort "2026-03-15" stand. -->
|
||
<input type="text" id="milestoneEditDateDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'milestoneEditDate', displayId: 'milestoneEditDateDisplay' })">
|
||
<input type="hidden" id="milestoneEditDate">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Position</label>
|
||
<select id="milestoneEditPosition">
|
||
<option value="">Oben in der Projektzeile</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Vorgänger (mehrfach möglich)</label>
|
||
<div id="milestoneEditDependsOn" class="todo-depends-list"></div>
|
||
</div>
|
||
<div class="toolbar">
|
||
<button onclick="saveMilestoneEdit()">Speichern</button>
|
||
<button class="danger" onclick="deleteMilestoneEdit()">Löschen</button>
|
||
<button class="secondary" onclick="closeMilestoneModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TODO-Modal (Stufe B: Status-Segment statt Erledigt+Fortschritt, Zuständige
|
||
als Personen-Chips, Abhängigkeiten eingeklappt mit Zähler/Suche,
|
||
"Speichern & Neu", Fehler am Feld statt alert(), Esc/Strg+Enter). Befüllt
|
||
und ausgewertet von openTodoModal()/saveTodoEdit() in js/plan/plan-modals.js. -->
|
||
<div class="modal-bg" id="todoModal" style="display:none">
|
||
<div class="modal">
|
||
<div class="modal-head">
|
||
<h2 id="todoModalTitle">Aufgabe bearbeiten</h2>
|
||
<span class="modal-kbd" id="todoModalKbd">Esc · Strg+↵</span>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label for="todoEditTitle">Titel</label>
|
||
<input type="text" id="todoEditTitle" placeholder="Titel">
|
||
<span class="field-error" id="todoEditTitleError" hidden></span>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Zeitraum</label>
|
||
<div class="field-inline">
|
||
<input type="text" id="todoEditRange" class="range-picker-display" placeholder="Start – Ende wählen…" readonly
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'todoEditStart', endInputId: 'todoEditEnd', displayId: 'todoEditRange', onApply: refreshTodoDuration })">
|
||
<span class="field-badge" id="todoEditDuration" hidden></span>
|
||
</div>
|
||
<input type="hidden" id="todoEditStart">
|
||
<input type="hidden" id="todoEditEnd">
|
||
<span class="field-error" id="todoEditRangeError" hidden></span>
|
||
<p class="hint">Ohne Zeitraum erscheint die Aufgabe nur unter „Aufgaben“, nicht im Terminplan.</p>
|
||
<label style="display:flex; align-items:center; gap:6px; margin-top:6px; font-weight:normal">
|
||
<input type="checkbox" id="todoEditShowInGantt" checked>
|
||
Im Terminplan (GANTT) anzeigen
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Zuständige</label>
|
||
<div id="todoEditAssignees" class="person-chips"></div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Status</label>
|
||
<div class="segmented" id="todoEditStatus">
|
||
<button type="button" data-status="offen" aria-pressed="false" onclick="setTodoEditStatus('offen')"><span class="segmented-dot"></span>Offen</button>
|
||
<button type="button" data-status="arbeit" aria-pressed="false" onclick="setTodoEditStatus('arbeit')"><span class="segmented-dot"></span>In Arbeit</button>
|
||
<button type="button" data-status="fertig" aria-pressed="false" onclick="setTodoEditStatus('fertig')"><span class="segmented-dot"></span>Erledigt</button>
|
||
</div>
|
||
<div class="progress-row" id="todoEditProgressRow">
|
||
<input type="range" id="todoEditProgress" min="0" max="100" step="5" value="0" aria-label="Fortschritt"
|
||
oninput="onTodoProgressInput(this)">
|
||
<output id="todoEditProgressVal">0 %</output>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<details class="fold" id="todoEditDependsFold">
|
||
<summary>
|
||
<span class="fold-caret">▶</span>
|
||
<span>Abhängigkeiten</span>
|
||
<span class="fold-count zero" id="todoEditDependsCount">0</span>
|
||
</summary>
|
||
<div class="fold-body">
|
||
<div class="fold-search">
|
||
<span class="fold-search-icon">⌕</span>
|
||
<input type="text" id="todoEditDependsSearch" placeholder="Aufgabe oder Meilenstein suchen…" oninput="filterTodoDepends()">
|
||
</div>
|
||
<div id="todoEditDependsOn" class="todo-depends-list flat" onchange="refreshTodoDependsCount()"></div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<div class="toolbar">
|
||
<button onclick="saveTodoEdit()">Speichern</button>
|
||
<button class="secondary" id="todoEditSaveNew" onclick="saveTodoEdit(true)">Speichern & Neu</button>
|
||
</div>
|
||
<div class="toolbar">
|
||
<button class="danger" id="todoEditDelete" onclick="deleteTodoEdit()">Löschen</button>
|
||
<button class="secondary" onclick="closeTodoModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Abwesenheits-Modal (Urlaube der Projektbeteiligten) -->
|
||
<div class="modal-bg" id="absenceModal" style="display:none">
|
||
<div class="modal">
|
||
<h2 id="absenceModalTitle">Abwesenheiten</h2>
|
||
<div class="form-row">
|
||
<div id="absenceModalList"></div>
|
||
</div>
|
||
<div class="absence-form" id="absenceModalForm">
|
||
<input type="text" id="absenceEditRange" class="range-picker-display" placeholder="Zeitraum wählen…" readonly
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'absenceEditStart', endInputId: 'absenceEditEnd', displayId: 'absenceEditRange' })">
|
||
<input type="hidden" id="absenceEditStart">
|
||
<input type="hidden" id="absenceEditEnd">
|
||
<select id="absenceEditType">
|
||
<option>Urlaub</option>
|
||
<option>Krank</option>
|
||
<option>Fortbildung</option>
|
||
<option>Sonstiges</option>
|
||
</select>
|
||
<input type="text" id="absenceEditNote" placeholder="Notiz (optional)" style="width:140px">
|
||
<button class="secondary small" onclick="addPlanAbsence()">+ Abwesenheit</button>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button class="secondary" onclick="closeAbsenceModal()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== Dialoge der Benutzerverwaltung =====================
|
||
Gehoeren zum Abschnitt „Benutzerverwaltung" des Bereichs #admin (frueher
|
||
die eigenstaendige Seite admin-users.html) und liegen wie alle anderen
|
||
Dialoge auf oberster Ebene, damit .modal-bg den ganzen Bildschirm
|
||
ueberdeckt. Befuellt werden sie von js/admin-users.js. -->
|
||
<!-- Neuer-Benutzer-Modal -->
|
||
<div class="modal-bg" id="newUserModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Neuer Benutzer</h2>
|
||
<div class="form-row">
|
||
<label>Benutzername</label>
|
||
<input type="text" id="newUserUsername" placeholder="z. B. mmustermann" autocomplete="off">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Anzeigename</label>
|
||
<input type="text" id="newUserDisplayName" placeholder="z. B. Max Mustermann" oninput="onNewUserDisplayNameInput()">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="newUserEmail" placeholder="max.mustermann@beispiel.de">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Rolle</label>
|
||
<select id="newUserRole" onchange="onUserRoleChange('new')">
|
||
<option value="member">Mitglied</option>
|
||
<option value="admin">Administrator</option>
|
||
<option value="kiosk">Kiosk (Wandtafel-Konto)</option>
|
||
</select>
|
||
<p class="hint" id="newUserRoleHint" style="display:none">Ein Kiosk-Konto hat nur Lesezugriff für die Wandtafel und 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 Anzeigenamen vorgeschlagen" maxlength="4">
|
||
<p class="hint">Kurzform fürs Projektboard (2-4 Zeichen), wird vorgeschlagen und kann angepasst werden.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Wochenstunden</label>
|
||
<input type="number" id="newUserCapacity" min="0" max="80" step="0.5" placeholder="z. B. 40">
|
||
<p class="hint">Vertragliche Arbeitszeit. Die Auslastung misst die Person daran: Eine halbe Stelle gilt schon bei halber Last als voll ausgelastet. Leer = volle Stelle.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Person-ID</label>
|
||
<input type="text" id="newUserPersonId" placeholder="wird aus dem Anzeigenamen vorgeschlagen">
|
||
<p class="hint">Verknüpft den Account mit einem Eintrag in der Projektbeteiligten-Liste (personIdForName). Bei Kollisionen anpassen.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Fachrolle(n)</label>
|
||
<div class="role-checkboxes" id="newUserRoleCheckboxes"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Passwort</label>
|
||
<div class="role-checkboxes">
|
||
<label><input type="radio" name="newUserPwMode" value="generate" checked onchange="onNewUserPwModeChange()"> Zufällig generieren</label>
|
||
<label><input type="radio" name="newUserPwMode" value="manual" onchange="onNewUserPwModeChange()"> Manuell festlegen</label>
|
||
</div>
|
||
<input type="password" id="newUserPassword" placeholder="mind. 8 Zeichen" style="display:none;margin-top:8px" autocomplete="new-password">
|
||
</div>
|
||
<div id="newUserError" style="color:#c00;"></div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveNewUser()">Anlegen</button>
|
||
<button class="secondary" onclick="closeNewUserModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Benutzer-bearbeiten-Modal -->
|
||
<div class="modal-bg" id="editUserModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Benutzer bearbeiten</h2>
|
||
<p class="hint" id="editUserUsernameLabel" style="margin-bottom:10px"></p>
|
||
<div class="form-row">
|
||
<label>Anzeigename</label>
|
||
<input type="text" id="editUserDisplayName">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="editUserEmail">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Rolle</label>
|
||
<select id="editUserRole" onchange="onUserRoleChange('edit')">
|
||
<option value="member">Mitglied</option>
|
||
<option value="admin">Administrator</option>
|
||
<option value="kiosk">Kiosk (Wandtafel-Konto)</option>
|
||
</select>
|
||
<p class="hint" id="editUserRoleHint" style="display:none">Ein Kiosk-Konto hat nur Lesezugriff für die Wandtafel und 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-Overlay: blockiert die gesamte App ohne gültige Session (siehe js/auth.js) -->
|
||
<div id="loginOverlay" style="display:none;" class="modal-bg">
|
||
<div class="modal">
|
||
<h2>Anmelden</h2>
|
||
<form id="loginForm">
|
||
<div class="form-row"><label>Benutzername</label><input type="text" id="loginUsername" autocomplete="username" required></div>
|
||
<div class="form-row"><label>Passwort</label><input type="password" id="loginPassword" autocomplete="current-password" required></div>
|
||
<div id="loginError" style="color:#c00;"></div>
|
||
<div class="toolbar"><button type="submit">Anmelden</button></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/globals.js?v=7"></script>
|
||
<script src="/js/utils.js?v=18"></script>
|
||
<script src="/js/icons.js?v=2"></script>
|
||
<script src="/js/ui.js?v=9"></script>
|
||
<script src="/js/date-picker.js?v=5"></script>
|
||
<script src="/js/server.js?v=6"></script>
|
||
<script src="/js/auth.js?v=4"></script>
|
||
<script src="/js/store.js?v=10"></script>
|
||
<script src="/js/weeks.js?v=11"></script>
|
||
<script src="/js/board/board.js?v=30"></script>
|
||
<script src="/js/wochenbericht/wochenbericht.js?v=20"></script>
|
||
<script src="/js/people/people.js?v=7"></script>
|
||
<script src="/js/planung/planung.js?v=1"></script>
|
||
<script src="/js/planchecker/planchecker.js?v=1"></script>
|
||
<script src="/js/projects/projects.js?v=7"></script>
|
||
<script src="/js/load/load.js?v=18"></script>
|
||
<!-- auftrag.js: Auftragsstatus und Dialog "Beauftragung". Nach load.js, weil
|
||
der Neues-Projekt-Dialog dort die Statusauswahl mit AUFTRAGSSTATUS füllt. -->
|
||
<script src="/js/auftrag.js?v=1"></script>
|
||
<!-- Assistent "Neues Projekt": braucht auftrag.js (Auftragsstatus) und
|
||
load.js (faktorFormat), steht deshalb dahinter. -->
|
||
<script src="/js/projekt-wizard.js?v=1"></script>
|
||
<script src="/js/dashboard/dashboard.js?v=5"></script>
|
||
<!-- 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=12"></script>
|
||
<script src="/js/settings/settings.js?v=9"></script>
|
||
<script src="/js/admin/admin.js?v=11"></script>
|
||
<script src="/js/admin-users.js?v=4"></script>
|
||
<script src="/js/gantt.js?v=7"></script>
|
||
<!-- Module des Projektbereichs (#plan). Sie liefen frueher nur in
|
||
project-plan.html; seit der Projektbereich ein Bereich dieser Seite ist,
|
||
gehoeren sie hierher. Reihenfolge wie gehabt: plan-state.js legt
|
||
window.PlanApp an, plan-history.js muss VOR plan-build-gantt.js stehen
|
||
(das seine Vorwochen-Helfer nutzt), und alle zusammen muessen nach
|
||
js/gantt.js kommen, dessen buildGanttHtml() plan-build-gantt.js
|
||
umhuellt. -->
|
||
<script src="/js/plan/plan-state.js?v=12"></script>
|
||
<script src="/js/plan/plan-sections.js?v=5"></script>
|
||
<script src="/js/plan/plan-info.js?v=8"></script>
|
||
<script src="/js/plan/plan-history.js?v=3"></script>
|
||
<script src="/js/plan/plan-build-gantt.js?v=7"></script>
|
||
<script src="/js/plan/plan-gantt.js?v=10"></script>
|
||
<script src="/js/plan/plan-drag.js?v=6"></script>
|
||
<script src="/js/plan/plan-modals.js?v=7"></script>
|
||
<script src="/js/plan/plan-tasks.js?v=2"></script>
|
||
<script src="/js/plan/plan-files.js?v=3"></script>
|
||
<script src="/js/plan/plan-protokoll.js?v=17"></script>
|
||
<script src="/js/plan/plan-templates.js?v=4"></script>
|
||
<script src="/js/plan/plan-protokoll-todos.js?v=1"></script>
|
||
<script src="/js/plan/plan-honorar.js?v=6"></script>
|
||
<!-- plan-leistung.js muss NACH plan-honorar.js stehen: es umhuellt dessen
|
||
invalidateHonorarConfig(), damit eine Aenderung der Anteile auch seinen
|
||
Zwischenspeicher verwirft. -->
|
||
<script src="/js/plan/plan-leistung.js?v=1"></script>
|
||
<script src="/js/plan/plan-settings.js?v=7"></script>
|
||
<script src="/js/plan/plan-schedule.js?v=5"></script>
|
||
<script src="/js/tabs-auth.js?v=14"></script>
|
||
<script src="/js/pdf.js?v=23"></script>
|
||
<script src="/js/backup.js?v=2"></script>
|
||
<script src="/js/import.js?v=1"></script>
|
||
<script src="/js/init.js?v=12"></script>
|
||
</body>
|
||
</html> |