- Outlook: Kalender-Abo je Konto (eigene Aufgaben, Meilensteine, Abwesenheiten als .ics), einzurichten unter Einstellungen - Teams: Nachricht in einen Kanal bei neuen Aufgaben, mit Link direkt auf die Aufgabe; einzurichten unter Admin → Teams - Sitzungen nach Bedarf laden: beim Start nur die aktuelle und drei davor, im Verlauf „Weitere laden“ in Fünferschritten - Termin aus Mail: Wahl Aufgabe/Meilenstein, Notiz auch bei Meilensteinen, nur aktiv mit eingerichteter KI - Kalender zeigt die KW, auch statt der Browser-Datumsfelder - Pfeile bei den Abwesenheiten, kleinere Server-Korrekturen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026 lines
122 KiB
HTML
2026 lines
122 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="icon" type="image/svg+xml" href="/logo.svg">
|
||
<link rel="icon" type="image/png" href="/logo.png">
|
||
<link rel="alternate icon" href="/logo.ico">
|
||
<link rel="apple-touch-icon" href="/logo.png">
|
||
<link rel="stylesheet" href="/styles.css?v=110">
|
||
<link rel="stylesheet" href="/plan-styles.css?v=45">
|
||
<!-- Glas-Darstellung (public/glas.css, als letztes Stylesheet): greift nur mit
|
||
der Klasse html.glas. Gesetzt wird sie hier, vor dem ersten Zeichnen,
|
||
damit die Seite nicht erst ohne Glas aufblitzt; abschaltbar je Browser
|
||
unter Einstellungen → Mein Konto (localStorage projuebersicht-glas). -->
|
||
<link rel="stylesheet" href="/glas.css?v=14">
|
||
<script>
|
||
try { if(localStorage.getItem('projuebersicht-glas') !== 'aus') document.documentElement.classList.add('glas'); }
|
||
catch(e){ document.documentElement.classList.add('glas'); }
|
||
// Hell/Dunkel ebenfalls vor dem ersten Zeichnen setzen (wie loadTheme() in
|
||
// js/ui.js) — sonst blitzt die Seite bis zum Ende des Ladens hell auf.
|
||
try {
|
||
var thema = localStorage.getItem('projuebersicht-theme');
|
||
if(thema === 'dark' || thema === 'light') document.documentElement.setAttribute('data-theme', thema);
|
||
} catch(e){}
|
||
</script>
|
||
<!-- Outfit: die Kalenderwoche der Login-Seite und die Wandtafel (Ziffern,
|
||
Überschriften). Fällt ohne Internetzugang automatisch auf die System-
|
||
Schrift zurück (font-family-Stack in styles.css), bricht also nichts
|
||
offline. Fredoka/Nunito Sans der früheren Wandtafel sind entfallen. -->
|
||
<!-- Externe Dateien blockieren den Seitenaufbau nicht mehr (Ladezeit auf dem
|
||
Pi): die Schrift wird nachgeladen, pdfmake (rund 2 MB, nur für den
|
||
PDF-Export) erst nach dem Laden der Seite, vfs_fonts nach pdfmake. Die
|
||
Exporte prüfen window.pdfMake ohnehin. -->
|
||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&display=swap" onload="this.onload=null;this.rel='stylesheet'">
|
||
<script>
|
||
window.addEventListener('load', function(){
|
||
var lade = function(src, danach){ var s = document.createElement('script'); s.src = src; if(danach) s.onload = danach; document.head.appendChild(s); };
|
||
lade('https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/pdfmake.min.js', function(){
|
||
lade('https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js');
|
||
});
|
||
});
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="app-shell">
|
||
<header class="app-topbar">
|
||
<button class="mobile-nav-toggle menu-corner-btn" onclick="toggleHeader()" title="Menü öffnen/schließen" aria-expanded="false" aria-controls="headerMenu">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg></span>
|
||
</button>
|
||
|
||
<div class="topbar-brand">
|
||
<img src="/logo.svg" alt="Logo" class="topbar-logo">
|
||
<h1 id="pageTitle" class="topbar-title">Projektübersicht</h1>
|
||
</div>
|
||
|
||
<!-- Die Sitzungsauswahl stand hier früher als Dropdown. Sitzungen sind
|
||
reine Speicherung + Verlauf; die Oberfläche zeigt immer nur die
|
||
eine offene Sitzung, ohne das im Menüband zu betonen. Eine
|
||
gesperrte Sitzung lässt sich weiterhin ansehen und für eine
|
||
Korrektur entsperren — dafür jetzt Admin → Termine & Sitzungen
|
||
(renderAdminSitzungen(), js/admin/admin.js). -->
|
||
|
||
<div class="app-topbar-actions">
|
||
<!-- Glocke: Benachrichtigungen über neu zugewiesene Aufgaben
|
||
(js/benachrichtigungen.js). -->
|
||
<div class="glocke">
|
||
<button class="theme-toggle-circle" id="glockeBtn" onclick="toggleGlocke()" title="Benachrichtigungen" aria-expanded="false" aria-controls="glockeMenue" hidden>
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9.5a6 6 0 0 1 12 0c0 5 2 6.5 2 6.5H4s2-1.5 2-6.5Z"/><path d="M10 19.5a2 2 0 0 0 4 0"/></svg></span>
|
||
<span class="glocke-zahl" id="glockeZahl" hidden>0</span>
|
||
</button>
|
||
<div class="glocke-menue" id="glockeMenue" role="dialog" aria-label="Benachrichtigungen" hidden></div>
|
||
</div>
|
||
<button class="theme-toggle-circle" id="themeToggle" onclick="toggleTheme()" title="Hell/Dunkel umschalten">
|
||
<span id="themeIcon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 14.5A8.5 8.5 0 1 1 9.5 3.5a7 7 0 0 0 11 11Z"/></svg></span></span>
|
||
</button>
|
||
|
||
<div class="topbar-user">
|
||
<button class="theme-toggle-circle avatar-btn" id="userMenuToggle" onclick="toggleUserMenu()" title="Konto" aria-expanded="false" aria-controls="userMenuDropdown">
|
||
<span class="icon" id="userAvatarIcon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8.5" r="3.5"/><path d="M4.5 20c0-4 3.5-6.5 7.5-6.5s7.5 2.5 7.5 6.5"/></svg></span>
|
||
</button>
|
||
<div class="user-menu-dropdown" id="userMenuDropdown">
|
||
<div class="user-menu-info" id="userBarInfo"></div>
|
||
<button class="menu-item" onclick="logout()">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5.5A1.5 1.5 0 0 1 4 19.5v-15A1.5 1.5 0 0 1 5.5 3H9"/><path d="M16 17l5-5-5-5"/><line x1="21" y1="12" x2="9" y2="12"/></svg></span>
|
||
Abmelden
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="app-body">
|
||
<div id="headerMenu" class="side-menu" aria-hidden="true">
|
||
<div class="side-menu-mobile-header">
|
||
<button class="header-toggle secondary" onclick="toggleHeader()" title="Menü schließen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
|
||
</button>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav">
|
||
<button class="nav-item tab active" data-tab="dashboard" onclick="showTab('dashboard')" title="Dashboard">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.5"/><path d="m4.5 7.5 1.3 1.3L8.5 6"/><line x1="13" y1="6" x2="21" y2="6"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="m4.5 17.5 1.3 1.3 2.7-2.8"/><line x1="13" y1="16" x2="21" y2="16"/></svg></span></span><span class="nav-label">Dashboard</span>
|
||
</button>
|
||
|
||
<hr class="sidebar-nav-divider">
|
||
|
||
<div class="sidebar-section-label">Navigation</div>
|
||
<!-- Die eine Projektliste. Der frühere zweite Eintrag "Projekte" —
|
||
dieselben Projekte, dieselben Angaben, eine zweite Bearbeitungs-
|
||
maske — ist hier aufgegangen: Reihenfolge, Gruppenwechsel und
|
||
Löschen stehen in dieser Liste, die Projektangaben im
|
||
Projektbereich, die wöchentliche Erfassung im Wochenbericht. -->
|
||
<button class="nav-item tab" data-tab="board" onclick="showTab('board')" title="Projekte">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2.5"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg></span></span><span class="nav-label">Projekte</span>
|
||
</button>
|
||
<button class="nav-item tab" data-tab="wochenbericht" onclick="showTab('wochenbericht')" title="Wochenbericht">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3.5h8a1.5 1.5 0 0 1 1.5 1.5v14a1.5 1.5 0 0 1-1.5 1.5H8A1.5 1.5 0 0 1 6.5 19V5A1.5 1.5 0 0 1 8 3.5Z"/><path d="M9.5 3.5h5v2.2a.8.8 0 0 1-.8.8h-3.4a.8.8 0 0 1-.8-.8V3.5Z"/><path d="m9.3 12.2 1.5 1.5 3.4-3.4"/><line x1="9.3" y1="16.3" x2="14.7" y2="16.3"/></svg></span></span><span class="nav-label">Wochenbericht</span>
|
||
</button>
|
||
<button class="nav-item tab" data-tab="load" onclick="showTab('load')" title="Auslastung">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="21" x2="5" y2="10"/><line x1="12" y1="21" x2="12" y2="5"/><line x1="19" y1="21" x2="19" y2="14"/><line x1="3" y1="21" x2="21" y2="21"/></svg></span></span><span class="nav-label">Auslastung</span>
|
||
</button>
|
||
<!-- Abwesenheiten (js/abwesenheit/abwesenheit.js): sichtbar mit einem der
|
||
vier Abwesenheits-Rechte (data-recht mit "|" = eines davon genügt). -->
|
||
<button class="nav-item tab nur-recht" data-recht="team.abw|team.abwProjekte|team.abwGewerk|team.abwAlle" data-tab="abwesenheit" onclick="showTab('abwesenheit')" title="Abwesenheiten">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="15.5" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="8" y1="3" x2="8" y2="6.5"/><line x1="16" y1="3" x2="16" y2="6.5"/><line x1="8.5" y1="13" x2="15.5" y2="17.5"/><line x1="15.5" y1="13" x2="8.5" y2="17.5"/></svg></span></span><span class="nav-label">Abwesenheiten</span>
|
||
</button>
|
||
<!-- "Projektbeteiligte" stand hier bis zur Entfernung der Abteilungs-
|
||
trennung (migrateRemoveDepartmentPartition(), CLAUDE.md
|
||
"Departments were retired"): eine rein lesende Liste, deren
|
||
eigentlicher Zweck — Beteiligte gezielt aus einer ANDEREN Abteilung
|
||
in die eigene Sitzung zu holen — mit der Zusammenlegung zu EINEM
|
||
gemeinsamen Personenkreis entfiel. Übrig blieb eine Dopplung der
|
||
Rollen-/Avatar-Chips, die in Wochenbericht/Board/Auslastung ohnehin
|
||
schon sichtbar sind. Auf Eriks Wunsch aus der Seitenleiste entfernt;
|
||
der Tab selbst (`#people`, `js/people/people.js`) und sein Slug
|
||
`Projektbeteiligte` in VALID_TAB_SLUGS bestehen unverändert fort —
|
||
ein alter Link/Deep-Link funktioniert also weiterhin, nur der
|
||
direkte Weg über die Navigation fehlt. -->
|
||
|
||
<!-- Projektbereich: der einzige Navigationspunkt, der nicht immer da
|
||
ist. Er erscheint, sobald ein Projekt geöffnet wurde, und trägt
|
||
dessen Bezeichnung (syncPlanNavItem() in js/tabs-auth.js). Ohne
|
||
ihn wäre im Projektbereich gar kein Punkt hervorgehoben und die
|
||
Seitenleiste zeigte nicht mehr, wo man gerade ist. -->
|
||
<button class="nav-item tab" data-tab="plan" id="planNavItem" style="display:none" onclick="showTab('plan')" title="Projektbereich">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="14" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="7" y1="13" x2="13" y2="13"/><line x1="7" y1="16" x2="16" y2="16"/></svg></span></span><span class="nav-label" id="planNavItemLabel">Projektbereich</span>
|
||
</button>
|
||
|
||
<!-- Planung: eigener, zweiter Abschnitt (nicht Teil von "Navigation"),
|
||
wie gewünscht — eine Sandbox für Terminpläne unabhängig von einem
|
||
echten Projekt (aus Honorar errechnet oder frei angelegt), siehe
|
||
CLAUDE.md "Planung — projektunabhängige Terminplan-Sandbox". -->
|
||
<div class="sidebar-section-label nur-recht" data-recht="wz.planung">Planung</div>
|
||
<button class="nav-item tab nur-recht" data-recht="wz.planung" data-tab="planung" onclick="showTab('planung')" title="Terminplan">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="17" rx="2.5"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><line x1="8" y1="2.5" x2="8" y2="6.5"/><line x1="16" y1="2.5" x2="16" y2="6.5"/><line x1="7" y1="14" x2="12" y2="14"/><line x1="7" y1="17.5" x2="15" y2="17.5"/></svg></span></span><span class="nav-label">Terminplan</span>
|
||
</button>
|
||
|
||
<!-- Studenten: eigener, dritter Abschnitt (wie "Planung" nicht Teil
|
||
von "Navigation") — bislang nur ein Werkzeug, der Plan Checker:
|
||
lädt einen Bauplan als PDF hoch und lässt ihn von einer KI gegen
|
||
eine Liste von Merkmalen prüfen (Maßketten, Plankopf, ...),
|
||
siehe js/planchecker/planchecker.js. -->
|
||
<div class="sidebar-section-label nur-recht" data-recht="wz.checker">Studenten</div>
|
||
<button class="nav-item tab nur-recht" data-recht="wz.checker" data-tab="planchecker" onclick="showTab('planchecker')" title="Plan Checker">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="m9 12 2 2 4-4"/></svg></span></span><span class="nav-label">Plan Checker</span>
|
||
</button>
|
||
|
||
<hr class="sidebar-nav-divider">
|
||
|
||
<!-- Einstellungen: seit die firmenweiten Masken in den Admin-Bereich
|
||
gewandert sind, steht hier nur noch das eigene Konto — ein
|
||
Aufklapp-Menue fuer einen einzigen Punkt waere Ballast. -->
|
||
<button class="nav-item tab" data-tab="settings" onclick="showTab('settings')" title="Einstellungen">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M12 3.5v2.4M12 18.1v2.4M20.5 12h-2.4M5.9 12H3.5M17.7 6.3l-1.7 1.7M8 15.7l-1.7 1.7M17.7 17.7 16 16M8 8 6.3 6.3"/></svg></span></span><span class="nav-label">Einstellungen</span>
|
||
</button>
|
||
|
||
<!-- Der Bereich „Werkzeuge“ ist entfallen (Eriks Wunsch): „+ Neues
|
||
Projekt“ steht in der Projektliste („+ Projekt“), die Wandtafel
|
||
braucht nur das Wandtafel-Konto (landet dort automatisch, sonst
|
||
über /Wandtafel erreichbar). -->
|
||
|
||
<!-- Admin: aufklappbare Gruppe ueber die Abschnitte von #admin. Wie
|
||
bei "Einstellungen" zuvor ist das KEINE Aktionsliste, sondern
|
||
Navigation innerhalb eines Bereichs — deshalb fuehrt schon der
|
||
Kopf-Eintrag dorthin (toggleAdminNav()) und die Unterpunkte
|
||
schalten nur den sichtbaren Abschnitt um (showAdminSection() in
|
||
js/admin/admin.js). Die gesamte Gruppe blendet renderUserBar()
|
||
(js/auth.js) fuer Nicht-Administratoren aus; die eigentliche
|
||
Sperre sitzt serverseitig auf den Routen. -->
|
||
<div id="navAdminSection" style="display:none">
|
||
<button class="nav-item nav-group-toggle" id="adminAccordionToggle" onclick="toggleAdminNav()" title="Admin" aria-expanded="false" aria-controls="adminSubnav">
|
||
<span class="nav-icon"><span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5 5.5 6v5.3c0 4.4 2.9 7.4 6.5 8.7 3.6-1.3 6.5-4.3 6.5-8.7V6Z"/><path d="m9 12 2.1 2.1L15.5 9.5"/></svg></span></span><span class="nav-label">Admin</span>
|
||
<span class="nav-chevron"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
|
||
</button>
|
||
<div class="sidebar-subnav" id="adminSubnav">
|
||
<button class="menu-item sidebar-subitem" data-admin-section="benutzer" onclick="showAdminSection('benutzer')">Benutzerverwaltung</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="rollen" onclick="showAdminSection('rollen')">Rollen & Rechte</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="honorar" onclick="showAdminSection('honorar')">Honorar</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="auslastung" onclick="showAdminSection('auslastung')">Auslastung</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="termine" onclick="showAdminSection('termine')">Termine & 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="teams" onclick="showAdminSection('teams')">Teams</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="papierkorb" onclick="showAdminSection('papierkorb')">Papierkorb</button>
|
||
<button class="menu-item sidebar-subitem" data-admin-section="datenbank" onclick="showAdminSection('datenbank')">Datenbank & Sitzungen</button>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<button class="sidebar-collapse-bar" id="sidebarCollapseToggle" onclick="toggleSidebarCollapse()" title="Seitenleiste ein-/ausklappen" aria-pressed="false">
|
||
<span id="sidebarCollapseIcon" class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 5 8 12 15 19"/></svg></span>
|
||
</button>
|
||
</div>
|
||
|
||
<div id="sideMenuBackdrop" class="side-menu-backdrop" onclick="toggleHeader()"></div>
|
||
|
||
<div class="app-main">
|
||
<div class="container">
|
||
<!-- BOARD -->
|
||
<!-- display:none wie bei allen anderen Panels: showTab() blendet den
|
||
richtigen Bereich erst nach der Anmeldeprüfung ein. Ohne das stand
|
||
hinter dem Login-Fenster das Projektboard, während in der Navigation
|
||
"Dashboard" hervorgehoben war. -->
|
||
<div id="board" style="display:none">
|
||
<!-- Titelzeile mit "+ Projekt": Der Knopf stand bisher nur in der
|
||
Seitenleiste unter "Sitzung". Anlegen gehört dorthin, wo die Projekte
|
||
stehen — in dieser Liste, die seit der Zusammenlegung auch Reihenfolge,
|
||
Gruppe und Löschen verantwortet. In der Seitenleiste bleibt er
|
||
zusätzlich stehen, weil er dort dieselbe Maske öffnet. -->
|
||
<div class="bericht-titelzeile">
|
||
<div class="section-title" style="margin:0">Projekte</div>
|
||
<button type="button" class="secondary small nur-projektleitung" onclick="openNewProjWizard()">+ Projekt</button>
|
||
</div>
|
||
<!-- Die Farbe der Kreise richtet sich nach der ROLLE (siehe
|
||
.board-avatar.role-* in styles.css), die Füllung nach der Art der
|
||
Zuordnung. Die frühere Legende erklärte nur Grün und behauptete damit
|
||
implizit, Projektleitung (rot) und Bauüberwachung (blau) gäbe es nicht. -->
|
||
<div class="legend">
|
||
<span><span class="swatch" style="background:var(--green)"></span> Planung</span>
|
||
<span><span class="swatch" style="background:var(--red)"></span> Projektleitung</span>
|
||
<span><span class="swatch" style="background:var(--accent)"></span> Bauüberwachung</span>
|
||
<span class="legend-sep">·</span>
|
||
<span><span class="swatch" style="background:var(--text-subtle)"></span> gefüllt = hauptverantwortlich</span>
|
||
<span><span class="swatch" style="background:transparent;box-shadow:inset 0 0 0 2px var(--text-subtle)"></span> umrandet = unterstützend</span>
|
||
</div>
|
||
<div id="boardTable"></div>
|
||
</div>
|
||
|
||
<!-- WOCHENBERICHT -->
|
||
<div id="wochenbericht" style="display:none">
|
||
<div class="bericht-titelzeile">
|
||
<div class="section-title" style="margin:0">Wochenbericht</div>
|
||
<button type="button" class="secondary small" onclick="openBerichtProtokoll()">Protokoll…</button>
|
||
<!-- Anderer Export als "Protokoll": Das Protokoll ist eine Kurzfassung
|
||
(was hat sich geändert + neue Aufgaben) zum Kopieren/Mailen. Dieser
|
||
Knopf legt die ganze Tagesordnung als PDF ab — eine Karte je
|
||
Projekt mit vollständigem Kommentar, Fortschritt und Beteiligten,
|
||
siehe exportWochenberichtPDF() in js/pdf.js. -->
|
||
<button type="button" class="secondary small" onclick="exportWochenberichtPDF()">Als PDF ablegen</button>
|
||
</div>
|
||
<div id="wochenberichtContent"></div>
|
||
<div id="weekAttendance"></div>
|
||
</div>
|
||
|
||
<!-- LOAD -->
|
||
<!-- ABWESENHEITEN (js/abwesenheit/abwesenheit.js) -->
|
||
<div id="abwesenheit" style="display:none"></div>
|
||
|
||
<div id="load" style="display:none">
|
||
<div class="section-title">Auslastung der Projektbeteiligten</div>
|
||
<div class="load-grid" id="loadGrid"></div>
|
||
</div>
|
||
|
||
<!-- PEOPLE -->
|
||
<div id="people" style="display:none">
|
||
<div class="section-title">Projektbeteiligte</div>
|
||
<div id="peopleList"></div>
|
||
</div>
|
||
|
||
<!-- PLANUNG (js/planung/planung.js) — Terminplan-Sandbox, unabhängig von
|
||
einem echten Projekt. Bearbeitet wird ein einzelner Plan im ganz
|
||
normalen Projektbereich (#plan) — siehe openSandboxPlan(),
|
||
js/plan/plan-state.js. -->
|
||
<div id="planung" style="display:none">
|
||
<div class="bericht-titelzeile">
|
||
<div class="section-title" style="margin:0">Terminplan</div>
|
||
<button type="button" class="secondary small" onclick="oeffneNeuerTerminplanDialog()">+ Neuer Terminplan</button>
|
||
</div>
|
||
<p class="hint" style="margin:0 0 16px">Terminpläne unabhängig von einem laufenden Projekt — zum Ausprobieren, für Angebote, um Kapazität grob abzuschätzen.</p>
|
||
<div id="planungList"></div>
|
||
</div>
|
||
|
||
<!-- PLAN CHECKER (js/planchecker/planchecker.js) — Studenten-Werkzeug: lädt
|
||
einen Bauplan als PDF hoch und lässt eine KI ihn gegen die unter
|
||
Admin → Plan-Checker-Merkmale gepflegten Merkmale prüfen. Weder die
|
||
Datei noch das Ergebnis werden gespeichert — ein Ad-hoc-Werkzeug, kein
|
||
Dokumentenarchiv (siehe POST /plan-checker-pruefen, server.js). -->
|
||
<div id="planchecker" style="display:none">
|
||
<div class="section-title">Plan Checker</div>
|
||
<p class="hint">Lädt einen Bauplan als PDF hoch und prüft ihn mit einer KI gegen die hinterlegten Merkmale.</p>
|
||
|
||
<div class="pc-layout">
|
||
<div class="pc-panel">
|
||
<label class="pc-drop" id="planCheckerDrop" for="planCheckerFile"
|
||
ondragover="planCheckerDragOver(event)" ondragleave="planCheckerDragLeave(event)" ondrop="planCheckerDrop(event)">
|
||
<span id="planCheckerDropText">PDF hierher ziehen oder klicken</span>
|
||
<div class="pc-drop-name" id="planCheckerDateiname" hidden></div>
|
||
</label>
|
||
<input type="file" id="planCheckerFile" accept="application/pdf" hidden onchange="planCheckerDateiGewaehlt(this.files[0])">
|
||
|
||
<div class="pc-section">
|
||
<label class="pc-label">Geprüfte Merkmale</label>
|
||
<div id="planCheckerMerkmaleListe"><span class="hint" style="margin:0">Lade Merkmale…</span></div>
|
||
<div class="pc-zusatz">
|
||
<input type="text" class="pc-input" id="planCheckerZusatz" placeholder="Weiteres Merkmal für diese Prüfung…">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pc-section">
|
||
<label class="pc-label">Projekt (optional)</label>
|
||
<select id="planCheckerProjekt" class="pc-select" style="width:100%">
|
||
<option value="">Kein Projekt wählen</option>
|
||
</select>
|
||
<p class="hint" style="margin:4px 0 0">Prüft zusätzlich, ob der Plankopf zu diesem Projekt passt (Nummer/Bezeichnung).</p>
|
||
</div>
|
||
|
||
<div class="pc-section">
|
||
<label class="pc-label">KI-Anbieter</label>
|
||
<select id="planCheckerAnbieter" class="pc-select" style="width:100%"></select>
|
||
</div>
|
||
|
||
<div class="pc-section">
|
||
<button type="button" class="pc-btn-primary pc-btn" id="planCheckerPruefenBtn" onclick="planCheckerPruefen()" style="width:100%">Plan prüfen</button>
|
||
<p class="hint pc-error" id="planCheckerError" hidden></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pc-panel pc-results">
|
||
<div class="pc-results-empty" id="planCheckerLeer">
|
||
<span class="big">Noch kein Plan geprüft</span>
|
||
PDF auswählen und auf „Plan prüfen" klicken.
|
||
</div>
|
||
<div id="planCheckerErgebnis"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Plan Checker: Planausschnitt-Vorschau (js/planchecker/planchecker.js,
|
||
planCheckerZeigeScreenshot()) — zeigt den Screenshot vergrößert, den
|
||
die KI für ein Merkmal als Beleg gewählt hat. Ein einfaches In-Page-
|
||
Modal statt eines <a target="_blank"> auf die data:-URL: Chrome
|
||
blockiert das Öffnen einer data:-URL als Top-Level-Navigation in einem
|
||
neuen Tab, ein Link blieb deshalb wirkungslos. -->
|
||
<div class="modal-bg" id="pcShotModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2 id="pcShotModalTitel">Planausschnitt</h2>
|
||
<img id="pcShotModalImg" src="" alt="" style="width:100%;height:auto;border-radius:var(--r-md);display:block">
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button class="secondary" onclick="planCheckerSchliesseScreenshot()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DASHBOARD (Begrüßung/Uhr, Meine Aufgaben, Freigaben, Kollegen-Abwesenheiten) -->
|
||
<div id="dashboard" style="display:none">
|
||
<div id="dashboardContent"></div>
|
||
</div>
|
||
|
||
<!-- WANDTAFEL (js/wand/wand.js) — gewerke- und nutzerübergreifendes
|
||
Kachel-Dashboard für ein dauerhaft laufendes Touch-Display. Ersetzt
|
||
den früheren Show Mode, der nur die gerade angemeldete Sitzung
|
||
großflächig klonte und keine echte Zusammenfassung über Gewerke/
|
||
Nutzer hinweg bot. showTab() blendet beim Betreten zusätzlich
|
||
Seitenleiste und Kopfzeile aus (body.wandtafel-aktiv, styles.css) —
|
||
das Kiosk-Konto hat echte Schreibrechte, eine zufällige Berührung der
|
||
Navigation soll dort nicht versehentlich in einen bearbeitbaren
|
||
Bereich führen. Der kleine Rücksprung-Link dient einer Vorschau durch
|
||
reguläres Personal; das Kiosk-Konto selbst braucht ihn nie. -->
|
||
<div id="wand" style="display:none">
|
||
<div class="wand-kopfzeile">
|
||
<button type="button" class="wand-zurueck" onclick="showTab((typeof previousTab==='string' && previousTab && previousTab!=='wand') ? previousTab : DEFAULT_TAB)">← Zur Übersicht</button>
|
||
<span id="wandIdleCountdown" class="wand-idle-countdown"></span>
|
||
</div>
|
||
<div id="wandContent" class="wand-grid-wrap"></div>
|
||
</div>
|
||
|
||
<!-- EINSTELLUNGEN (js/settings/settings.js)
|
||
Nur noch das eigene Konto: Stammdaten zur Ansicht und der
|
||
Passwortwechsel. Die firmenweiten Masken standen hier zunaechst
|
||
daneben, gehoeren aber zum Bereich "Admin" weiter unten — sie
|
||
verschieben die Zahlen ALLER Nutzer und stehen nur Administratoren zu,
|
||
waehrend dieser Bereich jedem angemeldeten Konto gehoert. -->
|
||
<div id="settings" style="display:none">
|
||
<div class="section-title">Einstellungen</div>
|
||
|
||
<div class="settings-grid">
|
||
<section class="settings-card" id="settingsAccountCard" data-settings-section="konto">
|
||
<h3 class="settings-card-title">Mein Konto</h3>
|
||
<div id="settingsAccountInfo" class="settings-kv"></div>
|
||
<p class="hint">Anzeigename, Rolle und Personen-Zuordnung pflegt ein
|
||
Administrator in der Benutzerverwaltung. Die E-Mail-Adresse und das
|
||
Passwort gehören dem Konto selbst.</p>
|
||
<div class="toolbar">
|
||
<button class="secondary" onclick="openPasswordModal(false)">Passwort ändern</button>
|
||
<button class="secondary" onclick="einfuehrungStarten()">Rundgang starten</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- KI-Zugangsdaten: persönlich je Konto, nicht firmenweit — jeder
|
||
Nutzer hinterlegt seinen eigenen Schlüssel (+ optional eine
|
||
abweichende API-URL für einen lokalen Server wie LM Studio/
|
||
Ollama oder einen Firmen-Proxy) für die KI-gestützte
|
||
Punkt-Erkennung beim Protokoll-Import. Die Schlüssel werden
|
||
geschrieben, aber nie zurückgelesen (wie ein Passwort), siehe
|
||
readUserAiKey()/writeUserAiKey() in server.js und CLAUDE.md
|
||
"### Protokolle". Je Anbieter ein eigener, umrandeter Block mit
|
||
Cloud/Eigener-Server-Umschalter statt eines eingeklappten
|
||
"API-URL"-Links — die eigene Anbindung (z. B. LM Studio) ist ein
|
||
Kernfall, kein Nebendetail, siehe CLAUDE.md. Dazu ein
|
||
"Neu verbinden"-Knopf (echter Testaufruf über
|
||
POST /my-ai-settings/test, nicht nur Anzeige von "configured")
|
||
mit Fehlertext, der bis zum nächsten Testergebnis stehen bleibt
|
||
(Design-Entwürfe mit Erik abgestimmt: Artifacts d7ab2945,
|
||
01fe1346). -->
|
||
<!-- Kalender-Abo für Outlook (renderSettingsKalender(), js/settings/settings.js;
|
||
Server: /mein-kalender, /kalender/<token>.ics) -->
|
||
<section class="settings-card" id="settingsKalenderCard">
|
||
<h3 class="settings-card-title">Kalender in Outlook</h3>
|
||
<p class="hint">Ihre offenen Aufgaben, die Meilensteine Ihrer Projekte und Ihre
|
||
Abwesenheiten als Kalender in Outlook — nur zum Ansehen, geändert wird weiter hier.
|
||
Outlook aktualisiert ihn selbst (je nach Outlook stündlich bis alle paar Stunden).</p>
|
||
<div id="settingsKalender"></div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="settingsAiCard">
|
||
<h3 class="settings-card-title">KI-Zugangsdaten</h3>
|
||
<p class="hint">Für die automatische Punkt-Erkennung beim Protokoll-Import
|
||
(Projektbereich → Protokolle → „Protokoll importieren"). Ohne
|
||
hinterlegten Schlüssel wird automatisch die Textmuster-Erkennung
|
||
verwendet — ein Schlüssel ist also optional, nicht Voraussetzung.</p>
|
||
|
||
<div class="ai-provider" id="aiProviderChatgpt">
|
||
<div class="ai-provider-head">
|
||
<div class="ai-provider-mark chatgpt">Ai</div>
|
||
<div class="ai-provider-info">
|
||
<div class="ai-provider-name">ChatGPT</div>
|
||
<div class="ai-provider-sub">OpenAI</div>
|
||
</div>
|
||
<span class="ai-status-chip" id="aiOpenaiStatus"><span class="dot"></span>–</span>
|
||
<button class="ai-reconnect-btn" id="aiOpenaiReconnectBtn" onclick="testMyAiConnection('chatgpt')" title="Neu verbinden">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="ai-provider-row">
|
||
<input type="password" id="setAiOpenaiKey" placeholder="API-Schlüssel einfügen…" autocomplete="off">
|
||
<button class="ai-icon-btn" onclick="saveMyAiKey('chatgpt')" title="Speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
<button class="ai-text-btn" id="aiOpenaiClearBtn" onclick="clearMyAiKey('chatgpt')" title="Schlüssel entfernen">Entfernen</button>
|
||
</div>
|
||
<div class="ai-test-error" id="aiOpenaiTestError"></div>
|
||
<div class="ai-mode-toggle" id="aiOpenaiModeToggle">
|
||
<button type="button" class="is-active" onclick="setAiMode('chatgpt','cloud')">Cloud</button>
|
||
<button type="button" onclick="setAiMode('chatgpt','lokal')">Eigener Server</button>
|
||
</div>
|
||
<div class="ai-url-panel" id="aiOpenaiUrlPanel">
|
||
<label>Adresse des eigenen Servers</label>
|
||
<div class="ai-provider-row">
|
||
<input type="text" id="setAiOpenaiUrl" placeholder="z. B. http://localhost:1234 für LM Studio">
|
||
<button class="ai-icon-btn" onclick="saveMyAiUrl('chatgpt')" title="URL speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
</div>
|
||
<!-- Modellname: nur nötig, wenn der eigene Server/Anbieter einen
|
||
bestimmten Namen erwartet (z. B. ein eigenständiger Anbieter
|
||
wie Mammouth.ai mit eigenem Modell-Katalog, bei dem der feste
|
||
OpenAI-Name "gpt-4o"/"gpt-4o-mini" nicht existiert) - leer
|
||
bedeutet weiterhin die hier hinterlegte Vorgabe. -->
|
||
<label>Modellname (optional)</label>
|
||
<div class="ai-provider-row">
|
||
<input type="text" id="setAiOpenaiModel" placeholder="leer = Vorgabe (gpt-4o-mini / gpt-4o)">
|
||
<button class="ai-icon-btn" onclick="saveMyAiModell('chatgpt')" title="Modellname speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ai-provider" id="aiProviderClaude">
|
||
<div class="ai-provider-head">
|
||
<div class="ai-provider-mark claude">Cl</div>
|
||
<div class="ai-provider-info">
|
||
<div class="ai-provider-name">Claude</div>
|
||
<div class="ai-provider-sub">Anthropic</div>
|
||
</div>
|
||
<span class="ai-status-chip" id="aiClaudeStatus"><span class="dot"></span>–</span>
|
||
<button class="ai-reconnect-btn" id="aiClaudeReconnectBtn" onclick="testMyAiConnection('claude')" title="Neu verbinden">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="ai-provider-row">
|
||
<input type="password" id="setAiClaudeKey" placeholder="API-Schlüssel einfügen…" autocomplete="off">
|
||
<button class="ai-icon-btn" onclick="saveMyAiKey('claude')" title="Speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
<button class="ai-text-btn" id="aiClaudeClearBtn" onclick="clearMyAiKey('claude')" title="Schlüssel entfernen">Entfernen</button>
|
||
</div>
|
||
<div class="ai-test-error" id="aiClaudeTestError"></div>
|
||
<div class="ai-mode-toggle" id="aiClaudeModeToggle">
|
||
<button type="button" class="is-active" onclick="setAiMode('claude','cloud')">Cloud</button>
|
||
<button type="button" onclick="setAiMode('claude','lokal')">Eigener Server</button>
|
||
</div>
|
||
<div class="ai-url-panel" id="aiClaudeUrlPanel">
|
||
<label>Adresse des eigenen Servers</label>
|
||
<div class="ai-provider-row">
|
||
<input type="text" id="setAiClaudeUrl" placeholder="z. B. http://localhost:11434 für Ollama">
|
||
<button class="ai-icon-btn" onclick="saveMyAiUrl('claude')" title="URL speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
</div>
|
||
<label>Modellname (optional)</label>
|
||
<div class="ai-provider-row">
|
||
<input type="text" id="setAiClaudeModel" placeholder="leer = Vorgabe (claude-haiku-4-5 / claude-sonnet-5)">
|
||
<button class="ai-icon-btn" onclick="saveMyAiModell('claude')" title="Modellname speichern">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 13 10 18 19 7"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<span class="field-error" id="settingsAiError" hidden></span>
|
||
</section>
|
||
</div>
|
||
|
||
<div id="settingsNote" class="settings-note"></div>
|
||
</div>
|
||
|
||
<!-- ===================== ADMIN (js/admin/admin.js) =====================
|
||
Alles, was nur Administratoren zusteht, an einer Stelle. Die
|
||
firmenweiten Masken standen zuvor im Bereich „Einstellungen" neben
|
||
„Mein Konto" — das vermischte die eigenen Kontoangaben mit Vorgaben,
|
||
die die Zahlen ALLER verschieben. Die drei Datenbank-/Sitzungs-
|
||
Aktionen waren nackte Menüpunkte in der Seitenleiste, wo ein
|
||
Fehlklick genügte; sie stehen jetzt im Abschnitt „Datenbank &
|
||
Sitzungen" mit dem Hinweis, was sie anrichten.
|
||
Sichtbar ist immer genau ein Abschnitt (showAdminSection()); der
|
||
gewählte steht im URL-Fragment (…/Admin#auslastung). -->
|
||
<div id="admin" style="display:none">
|
||
<div class="section-title" id="adminSectionTitle">Admin</div>
|
||
|
||
<div class="settings-grid">
|
||
<section class="settings-card" id="adminHonorarCard" data-admin-section="honorar" style="display:none">
|
||
<h3 class="settings-card-title">Honorar <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Grundlage der Terminplan-Berechnung aus dem Honorar („+ Terminplan" im
|
||
Projektbereich): Honorar × Anteil der Leistungsphase ÷ Stundensatz = Stunden.</p>
|
||
<div id="honorarAdminFields" class="honorar-anteil-grid"></div>
|
||
<div class="honorar-admin-foot">
|
||
<div id="honorarAdminSum"></div>
|
||
<label class="honorar-satz-field">
|
||
<span>Stundensatz</span>
|
||
<input type="number" id="honorarStundensatz" min="1" step="1" value="135"> €
|
||
</label>
|
||
<button class="secondary small" onclick="saveHonorarConfig()">Speichern</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminAuslastungCard" data-admin-section="auslastung" style="display:none">
|
||
<h3 class="settings-card-title">Auslastung <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Die Auslastung entsteht allein aus <strong>Aufgaben mit Aufwand</strong>:
|
||
Die Stunden einer Aufgabe verteilen sich zu gleichen Teilen auf ihre Zuständigen
|
||
und auf die Arbeitstage der Aufgabe. Die Ampel misst jede Person an IHRER Stelle —
|
||
1,0 heißt voll ausgelastet; die vertraglichen Wochenstunden stehen am Konto
|
||
(Benutzerverwaltung).</p>
|
||
<div class="settings-field-grid">
|
||
<label class="settings-field"><span>Wochenstunden einer vollen Stelle</span>
|
||
<input type="number" id="setVollzeitStunden" min="1" max="80" step="0.5"></label>
|
||
<label class="settings-field"><span>Ampel gelb ab</span>
|
||
<input type="number" id="setSchwelleGelb" min="0.05" max="5" step="0.05"></label>
|
||
<label class="settings-field"><span>Ampel rot ab</span>
|
||
<input type="number" id="setSchwelleRot" min="0.05" max="5" step="0.05"></label>
|
||
</div>
|
||
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="saveAuslastungSettings()">Speichern</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminTerminCard" data-admin-section="termine" style="display:none">
|
||
<h3 class="settings-card-title">Termine & Sitzungen <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<div class="settings-field-grid">
|
||
<label class="settings-field"><span>Arbeitsstunden je Person und Werktag</span>
|
||
<input type="number" id="setStundenProTag" min="1" max="24" step="0.5"></label>
|
||
</div>
|
||
<label class="settings-check">
|
||
<input type="checkbox" id="setAutoLock">
|
||
<span>Ältere Sitzungen automatisch sperren</span>
|
||
</label>
|
||
<p class="hint">Ist das aus, bleibt jede Sitzung bearbeitbar. Bereits gesperrte Sitzungen
|
||
bleiben gesperrt — entsperrt wird weiterhin bewusst über „Sitzung entsperren".</p>
|
||
<div class="toolbar">
|
||
<button class="secondary small" onclick="saveTerminSettings()">Speichern</button>
|
||
</div>
|
||
|
||
<!-- Die Oberfläche zeigt sonst nirgends mehr, welche Sitzung gerade
|
||
offen ist — dafür genügt eine Zeile, da es ohnehin immer nur
|
||
eine gibt. Diese Liste ist die einzige Stelle, die alle
|
||
Sitzungen nebeneinander zeigt: zur Kontrolle und um eine
|
||
gesperrte für eine nachträgliche Korrektur zu entsperren. -->
|
||
<h4 class="settings-subtitle">Sitzungen</h4>
|
||
<p class="hint">Jede Sitzung ist ein Wochenstand für Speicherung und Verlauf — bearbeitet
|
||
wird immer nur die eine offene. Eine gesperrte Sitzung lässt sich hier entsperren und
|
||
öffnen, etwa um einen Kommentar nachträglich zu korrigieren; beim nächsten Laden sperrt
|
||
die Automatik sie wieder, sofern sie aktiv ist.</p>
|
||
<div class="template-admin-list" id="adminSessionsList"></div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminHolidayCard" data-admin-section="feiertage" style="display:none">
|
||
<h3 class="settings-card-title">Feiertage <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Arbeitsfreie Tage zählen bei der Terminplan-Berechnung aus dem Honorar und
|
||
bei der Ressourcen-Auslastung nicht als Arbeitstag. Vorher kannte die Anwendung nur Wochenenden.</p>
|
||
<div id="holidayYearHint" class="settings-warn" style="display:none;margin-bottom:10px"></div>
|
||
|
||
<h4 class="settings-subtitle">Gesetzliche Feiertage übernehmen</h4>
|
||
<p class="hint">Werden aus dem Kalender berechnet (feste Termine plus alles, was am
|
||
Osterdatum hängt) — kein Netzdienst, kein Internetzugang nötig. Vorgeschlagen, nicht
|
||
automatisch eingetragen: Feiertage verschieben jeden gerechneten Terminplan.</p>
|
||
<div class="settings-inline-field">
|
||
<span>Jahr</span>
|
||
<input type="number" id="holidayYear" min="1970" max="2200" step="1" style="width:90px">
|
||
<span>Bundesland</span>
|
||
<select id="holidayState"></select>
|
||
<button class="secondary small" onclick="suggestHolidays()">Vorschlagen</button>
|
||
</div>
|
||
<div id="holidaySuggestion"></div>
|
||
|
||
<h4 class="settings-subtitle">Hinterlegte Tage</h4>
|
||
<div id="adminHolidayList" class="settings-holiday-list"></div>
|
||
<div class="template-add-row">
|
||
<input type="date" id="holidayNewDate">
|
||
<input type="text" id="holidayNewName" placeholder="Bezeichnung, z. B. „Betriebsruhe“">
|
||
<button class="secondary small" onclick="addHoliday()">+ Einzelner Tag</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Teams-Anbindung (loadAdminTeams(), js/admin/admin.js; Server: /admin/teams*) -->
|
||
<section class="settings-card" id="adminTeamsCard" data-admin-section="teams" style="display:none">
|
||
<h3 class="settings-card-title">Teams <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Bekommt jemand eine Aufgabe zugewiesen, erscheint in einem Teams-Kanal eine
|
||
Nachricht mit Link direkt auf die Aufgabe im Projekt.</p>
|
||
<div id="adminTeamsInhalt"></div>
|
||
<details class="kalender-anleitung">
|
||
<summary>So richten Sie die Adresse in Teams ein</summary>
|
||
<ol>
|
||
<li>In Teams den gewünschten Kanal öffnen, oben bei den drei Punkten
|
||
<i>Workflows</i> wählen.</li>
|
||
<li>Die Vorlage <i>„Beim Empfang einer Webhook-Anfrage in einem Kanal posten“</i>
|
||
auswählen, einen Namen vergeben (z. B. „Projektübersicht“) und bestätigen.</li>
|
||
<li>Teams zeigt am Ende eine lange Adresse (beginnt mit https://). Diese kopieren,
|
||
hier einfügen und speichern.</li>
|
||
<li>„Testnachricht senden“ — im Kanal sollte eine Karte erscheinen.</li>
|
||
</ol>
|
||
<p class="hint">Der Server braucht dafür Internetzugang nach außen. Der Link in der
|
||
Nachricht führt auf die Adresse, unter der die App im Büro aufgerufen wird — er
|
||
funktioniert also nur im Büronetz (bzw. per VPN).</p>
|
||
</details>
|
||
</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</span></h3>
|
||
<p class="hint">Wiederkehrende Schritte einer Leistungsphase, getrennt je Gewerk. Im
|
||
Projektbereich fügt „+ Vorlage" sie als Aufgaben ein; danach sind es ganz normale
|
||
Aufgaben — eine später geänderte Vorlage wirkt NICHT rückwirkend. Änderungen an
|
||
Bezeichnung, Anteil, Start und Dauer werden beim Verlassen des Feldes gespeichert.
|
||
<strong>Anteil %</strong> (vorbelegt mit den Mittelwerten der Siemon-Tabellen zur HOAI 2013):
|
||
Beim Einfügen bekommt jeder Schritt diesen Anteil an der Dauer der Phase — die Schritte
|
||
laufen nacheinander durch den Phasenbalken. „in a)“ heißt: in Schritt a) enthalten, läuft
|
||
zeitgleich mit ihm. Ohne Anteil gelten Start und Dauer; ohne Dauer wird die Aufgabe ohne Termin
|
||
eingefügt (nur in „Aufgaben“, nicht im Terminplan). Die Schritte
|
||
sind mit den Grundleistungen der HOAI vorbelegt (ARCH: Anlage 10.1, ELT/HLS: Anlage 15.1,
|
||
Buchstabe in Klammern).</p>
|
||
<div class="vorlagen-wahl">
|
||
<div class="vorlagen-gewerke" id="adminTemplateGewerk" role="group" aria-label="Gewerk"></div>
|
||
<label class="settings-inline-field">
|
||
<span>Leistungsphase</span>
|
||
<select id="adminTemplateLph" onchange="renderAdminTemplates()"></select>
|
||
</label>
|
||
</div>
|
||
<div class="vorlagen-kopf"><span>Schritt</span><span title="Anteil an der Phase, z. B. nach Siemon-Tabelle">Anteil %</span><span>Start (Tag +)</span><span title="leer = ohne Termin">Dauer (Tage)</span><span></span></div>
|
||
<div id="templateAdminList" class="template-admin-list"></div>
|
||
<div class="template-add-row vorlagen-neu">
|
||
<input type="text" id="templateNewTitle" placeholder="Neuer Schritt, z. B. „LV erstellen“" onkeydown="if(event.key==='Enter') addTemplateEntry()">
|
||
<input type="text" id="templateNewAnteil" inputmode="decimal" placeholder="Anteil %" title="Anteil an der Phase in Prozent, z. B. nach Siemon-Tabelle">
|
||
<input type="number" id="templateNewOffset" value="0" min="0" title="Start in Tagen nach dem Startdatum">
|
||
<input type="number" id="templateNewDuration" value="" min="0" placeholder="ohne" title="Dauer in Tagen, leer = ohne Termin">
|
||
<button class="secondary small" onclick="addTemplateEntry()">+ Schritt</button>
|
||
</div>
|
||
</section>
|
||
<section class="settings-card settings-card-wide" id="adminPlanCheckerCard" data-admin-section="planchecker" style="display:none">
|
||
<h3 class="settings-card-title">Plan-Checker-Merkmale <span class="settings-scope">firmenweit · nur Administratoren</span></h3>
|
||
<p class="hint">Die Grundliste, gegen die der Plan Checker (Bereich „Studenten") jeden hochgeladenen Plan prüft.
|
||
„Prüfanweisung" geht wortwörtlich an die KI — „Titel" ist zugleich die Anzeige-Bezeichnung.</p>
|
||
<div id="planCheckerMerkmaleAdminListe" class="template-admin-list"></div>
|
||
<div class="template-add-row">
|
||
<input type="text" id="merkmalNeuTitel" placeholder="Titel, z. B. „Maßketten vorhanden“">
|
||
<input type="text" id="merkmalNeuPrompt" placeholder="Prüfanweisung für die KI" style="flex:2">
|
||
<button class="secondary small" onclick="addPlanCheckerMerkmal()">+ Merkmal</button>
|
||
</div>
|
||
</section>
|
||
<section class="settings-card settings-card-wide" id="adminRollenCard" data-admin-section="rollen" style="display:none">
|
||
<h3 class="settings-card-title">Rollen & Rechte <span class="settings-scope">firmenweit</span></h3>
|
||
<p class="hint">Jedes Konto hat genau eine Rolle (zugewiesen in der Benutzerverwaltung). Hier legst
|
||
du fest, was eine Rolle darf. Änderungen gelten sofort, für angemeldete Konten nach dem
|
||
nächsten Neuladen der Seite. Administrator hat immer alle Rechte. Die Fachrollen
|
||
(Planung, Projektleitung, Bauüberwachung) sind davon unabhängig.</p>
|
||
<div id="rollenRechte" class="rr"></div>
|
||
</section>
|
||
<section class="settings-card settings-card-wide" id="adminUsersCard" data-admin-section="benutzer" style="display:none">
|
||
<h3 class="settings-card-title">Benutzerverwaltung <span class="settings-scope">nur Administratoren</span></h3>
|
||
<p class="hint">Konten anlegen, bearbeiten, deaktivieren, Passwörter zurücksetzen und
|
||
Abwesenheiten pflegen. Die <strong>Wochenstunden</strong> hier sind die Bezugsgröße der
|
||
Auslastungs-Ampel; die <strong>Person-ID</strong> verbindet das Konto mit den
|
||
Aufgaben-Zuordnungen und füllt damit „Meine Aufgaben".</p>
|
||
<div id="generatedPasswordBox"></div>
|
||
|
||
<div class="toolbar admin-toolbar">
|
||
<button onclick="openNewUserModal()">+ Neuer Benutzer</button>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table id="usersTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzername</th>
|
||
<th>Name</th>
|
||
<th>E-Mail</th>
|
||
<th>Rolle</th>
|
||
<th>Abteilung</th>
|
||
<th>Kürzel</th>
|
||
<th>Std./Woche</th>
|
||
<th>Person-ID</th>
|
||
<th>Status</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="usersTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-card settings-card-wide" id="adminPapierkorbCard" data-admin-section="papierkorb" style="display:none">
|
||
<h3 class="settings-card-title">Papierkorb <span class="settings-scope">nur Administratoren</span></h3>
|
||
<p class="hint">Aus der Projektliste gelöschte Projekte — samt Terminplan, Beteiligten und
|
||
Leistungsphasen. Sie bleiben 30 Tage hier und lassen sich in die aktuelle Sitzung
|
||
zurückholen; danach werden sie endgültig entfernt. Dateien und Protokolle hängen an der
|
||
Projektnummer und sind nach dem Wiederherstellen ebenfalls wieder da.</p>
|
||
<div id="adminPapierkorbList"><p class="hint">Wird geladen…</p></div>
|
||
</section>
|
||
|
||
<section class="settings-card" id="adminDatenbankCard" data-admin-section="datenbank" style="display:none">
|
||
<h3 class="settings-card-title">Datenbank & 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 nur-recht" data-recht="a.backup">
|
||
<button class="secondary small" onclick="downloadBackup()">DB Backup herunterladen</button>
|
||
</div>
|
||
|
||
<h4 class="settings-subtitle">Wiederherstellen</h4>
|
||
<p class="hint">Spielt eine mit „DB Backup" erzeugte Datei ein und <strong>ersetzt damit
|
||
alle Sitzungsdaten</strong>. Konten, Rollen, Abwesenheiten und Datei-Freigaben bleiben
|
||
unberührt. Unmittelbar vorher sichert der Server den aktuellen Stand, ein Einspielen
|
||
lässt sich also rückgängig machen.</p>
|
||
<div class="toolbar nur-recht" data-recht="a.import">
|
||
<button class="secondary small" onclick="importDb()">DB Import…</button>
|
||
</div>
|
||
|
||
<!-- Das Löschen einzelner Sitzungen ist zur Sitzungsliste unter
|
||
"Termine & Sitzungen" gewandert (renderAdminSitzungen()) — dort
|
||
steht ohnehin schon, welche Sitzung welche ist; ein zweiter,
|
||
von "der im Menüband gewählten" abhängiger Knopf hier wäre seit
|
||
dem Wegfall der Menüband-Auswahl gegenstandslos gewesen. -->
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div id="adminNote" class="settings-note"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ===================== PROJEKTBEREICH (#plan) =====================
|
||
Der eigene Bereich EINES Projekts — frueher die eigenstaendige Seite
|
||
public/project-plan.html, jetzt ein ganz normaler Bereich dieser Seite.
|
||
Der Wechsel dorthin laeuft ueber showTab('plan') (js/tabs-auth.js) wie
|
||
jeder andere Navigationswechsel, sodass Menueband und Seitenleiste
|
||
stehen bleiben; geoeffnet wird er ueber openProjectPlan() (js/gantt.js).
|
||
Er steht bewusst AUSSERHALB von .container, weil er seine eigene
|
||
Kopfzeile und eigene Innenabstaende mitbringt (siehe plan-styles.css).
|
||
Die fuenf Unterbereiche (Dashboard, Terminplan, Aufgaben, Dateien,
|
||
Einstellungen) schaltet .plan-nav um (showPlanSection() in
|
||
js/plan/plan-sections.js); alle liegen gleichzeitig im DOM. -->
|
||
<div id="plan" style="display:none">
|
||
<div class="plan-page">
|
||
<!-- .plan-kopf fasst Kopfzeile und Bereichs-Navigation zu EINER Fläche
|
||
zusammen — unter Glas eine schwebende Karte wie die Seitenleiste
|
||
(glas.css, "Alles schwebt"); ohne Glas wirkungslos. -->
|
||
<div class="plan-kopf">
|
||
<div class="plan-header">
|
||
<span class="plan-id" id="planIdBadge"></span>
|
||
<h1 id="planTitle">Projektplan</h1>
|
||
<div class="tag-chip-list" id="planTagsList"></div>
|
||
<div class="plan-header-spacer"></div>
|
||
<div class="subtitle" id="planSubtitle"></div>
|
||
<div class="plan-avatar-stack" id="planPeopleList"></div>
|
||
<div class="plan-header-actions">
|
||
<button class="theme-toggle-circle" onclick="todoMail()" title="E-Mail erstellen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 6.5 8 6.5 8-6.5"/></svg></span>
|
||
</button>
|
||
<button class="theme-toggle-circle" onclick="closePlanPage()" title="Schließen">
|
||
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bereichs-Navigation des Projektbereichs: schaltet zwischen den fünf
|
||
Bereichen um (showPlanSection() in js/plan/plan-sections.js). Die
|
||
Bereiche liegen alle gleichzeitig im DOM und werden nur ein- bzw.
|
||
ausgeblendet, damit die vorhandenen Render-Funktionen ihre Elemente
|
||
unabhängig von der Sichtbarkeit befüllen können. -->
|
||
<nav class="plan-nav" id="planNav">
|
||
<button class="plan-nav-item" data-section="dashboard" onclick="showPlanSection('dashboard')">Dashboard</button>
|
||
<button class="plan-nav-item" data-section="terminplan" onclick="showPlanSection('terminplan')">Terminplan</button>
|
||
<button class="plan-nav-item" data-section="phasen" onclick="showPlanSection('phasen')">Leistungsphasen</button>
|
||
<button class="plan-nav-item" data-section="aufgaben" onclick="showPlanSection('aufgaben')">Aufgaben</button>
|
||
<button class="plan-nav-item" data-section="leistung" onclick="showPlanSection('leistung')">Leistung</button>
|
||
<button class="plan-nav-item" data-section="dateien" onclick="showPlanSection('dateien')">Dateien</button>
|
||
<button class="plan-nav-item" data-section="protokolle" onclick="showPlanSection('protokolle')">Protokolle</button>
|
||
<button class="plan-nav-item" data-section="einstellungen" onclick="showPlanSection('einstellungen')">Einstellungen</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="plan-body">
|
||
<div class="plan-main">
|
||
<div class="plan-main-content">
|
||
|
||
<!-- ===================== Bereich: Dashboard ===================== -->
|
||
<section class="plan-section" id="planSection-dashboard" data-section="dashboard">
|
||
<!-- Anordnung "Lagebild" (Eriks Wahl aus drei Entwürfen): oben ein
|
||
schmales Band mit laufender Phase und drei Kennzahlen
|
||
(renderPlanKpis(), plan-gantt.js), darunter zwei Spalten, die
|
||
sich unabhängig füllen — links Kommentar und Termine, rechts
|
||
Leistungsphasen, Team und Unterlagen. Vorher stand ein Raster
|
||
aus vier gleichen Karten, in dem "Team" allein in einer zweiten
|
||
Reihe hing, und vier halbleere Kurzkarten darunter. -->
|
||
<div class="plan-lage-band" id="planKpiRow"></div>
|
||
|
||
<div class="plan-lage-raster">
|
||
<div class="plan-lage-spalte">
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Kommentar der Sitzung</h3>
|
||
<!-- Nur zu lesen: Der Kommentar entsteht in der wöchentlichen
|
||
Besprechung und wird allein im Wochenbericht erfasst. -->
|
||
<div class="plan-kommentar-hinweis" id="planStatusHinweis" hidden></div>
|
||
<div class="status" id="planStatusWrap">
|
||
<div class="status-edit" id="planStatusEdit"></div>
|
||
</div>
|
||
<div class="plan-kommentar-fuss">
|
||
<span class="plan-kommentar-autor" id="planStatusAutor"></span>
|
||
<span class="plan-verweis-row" id="planStatusVerweis"></span>
|
||
</div>
|
||
<div class="plan-kommentar-verlauf" id="planStatusVerlauf" hidden></div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Termine und Aufgaben
|
||
<button type="button" class="plan-panel-link" onclick="showPlanSection('aufgaben')">Alle Aufgaben</button></h3>
|
||
<div id="planDashUpcoming"></div>
|
||
</div>
|
||
|
||
<!-- Protokolle: letztes Protokoll mit seinen offenen Punkten und
|
||
die früheren als Kurzliste (renderProtokolleSummary(),
|
||
plan-protokoll.js). Ohne Projektnummer ausgeblendet. -->
|
||
<div class="plan-panel" id="planDashProtokollePanel">
|
||
<h3 class="plan-panel-title">Protokolle
|
||
<button type="button" class="plan-panel-link" onclick="openProtokollePanel()">Alle Protokolle</button></h3>
|
||
<div id="planDashProtokolle"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plan-lage-spalte">
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Leistungsphasen</h3>
|
||
<div class="board-lph-list" id="planLphList"></div>
|
||
<div class="plan-verweis-row" id="planLphVerweis"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Team
|
||
<button type="button" class="plan-panel-link" onclick="showPlanSection('einstellungen')">Bearbeiten</button></h3>
|
||
<div id="planDashTeam"></div>
|
||
</div>
|
||
|
||
<!-- Unterlagen: die früheren Kurzkarten als eine Liste; die
|
||
Inhalte füllen weiterhin renderResourceLoad(),
|
||
renderProjectFiles(). Protokolle haben eine eigene Kachel
|
||
(links), daher hier nicht mehr.
|
||
Der Eintrag "Verlauf" ist entfallen — der Kommentar-
|
||
Verlauf steht jetzt in der Kachel "Kommentar der Sitzung". -->
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Unterlagen</h3>
|
||
<div class="plan-unterlagen">
|
||
<div class="plan-unterlage" onclick="openFilesPanel()">
|
||
<div class="plan-unterlage-kopf"><b>Dateien und Freigaben</b><span class="plan-panel-link">Öffnen</span></div>
|
||
<div id="planFilesSummary"></div>
|
||
</div>
|
||
<div class="plan-unterlage" onclick="openResourceModal()">
|
||
<div class="plan-unterlage-kopf"><b>Auslastung des Teams</b><span class="plan-panel-link">Öffnen</span></div>
|
||
<div id="planResourceSummary"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== Bereich: Terminplan ==================== -->
|
||
<section class="plan-section" id="planSection-terminplan" data-section="terminplan">
|
||
<!-- Ruhige Leiste (Eriks Wahl "A"): Maßstab, Heute, Zeitraum, Filter,
|
||
dahinter die Menüs "Ansicht" und "+ Neu". Alle Knöpfe behalten ids und Funktionen
|
||
(renderPlanPage() blendet sie in gesperrten Sitzungen aus). -->
|
||
<div class="plan-main-header tp-leiste">
|
||
<span class="segmented tp-seg" id="tpMassstab" role="group" aria-label="Maßstab">
|
||
<button type="button" data-stufe="fit" aria-pressed="true" onclick="tpMassstab('fit')">Übersicht</button>
|
||
<button type="button" data-stufe="woche" aria-pressed="false" onclick="tpMassstab('woche')">Wochen</button>
|
||
<button type="button" data-stufe="tag" aria-pressed="false" onclick="tpMassstab('tag')">Tage</button>
|
||
</span>
|
||
<button type="button" class="secondary" onclick="tpHeute()">Heute</button>
|
||
<input type="text" id="planGanttRange" class="range-picker-display tp-zeitraum" placeholder="Zeitraum wählen…" readonly aria-label="Zeitraum"
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'planGanttStart', endInputId: 'planGanttEnd', displayId: 'planGanttRange', onApply: renderPlanGantt })">
|
||
<input type="hidden" id="planGanttStart">
|
||
<input type="hidden" id="planGanttEnd">
|
||
<span class="tp-luecke"></span>
|
||
<span class="segmented tp-seg" id="tpFilter" role="group" aria-label="Filter">
|
||
<button type="button" data-filter="alle" aria-pressed="true" onclick="tpFilter('alle')">Alle</button>
|
||
<button type="button" data-filter="meine" aria-pressed="false" onclick="tpFilter('meine')">Meine</button>
|
||
<button type="button" data-filter="ueberfaellig" aria-pressed="false" onclick="tpFilter('ueberfaellig')">Überfällig</button>
|
||
</span>
|
||
<details class="tp-menue">
|
||
<summary class="secondary">Ansicht ▾</summary>
|
||
<div class="tp-menue-liste">
|
||
<label><input type="checkbox" id="planShowVacation" checked onchange="renderPlanGantt()"> Urlaub anzeigen</label>
|
||
<label title="Termine der vorherigen Sitzung als gestrichelten Umriss einblenden"><input type="checkbox" id="planShowGhost" checked onchange="renderPlanGantt()"> Stand der Vorwoche zeigen</label>
|
||
<label><input type="checkbox" id="planShowPfeile" checked onchange="renderPlanGantt()"> Abhängigkeiten als Linien</label>
|
||
<!-- Nur für Sandbox-Pläne der Planung sichtbar (renderPlanPage()). -->
|
||
<button type="button" onclick="exportSandboxPlanPDF(PlanApp.project.id); tpMenueZu()" id="sandboxPdfBtn">Als PDF exportieren</button>
|
||
</div>
|
||
</details>
|
||
<details class="tp-menue tp-neu">
|
||
<summary>+ Neu ▾</summary>
|
||
<div class="tp-menue-liste">
|
||
<button type="button" onclick="tpMenueZu(); addTodo()" id="addTodoBtn"><span class="tp-sym-aufgabe"></span>Aufgabe</button>
|
||
<button type="button" onclick="tpMenueZu(); addPlanMilestone()" id="addMilestoneBtn"><span class="tp-sym-ms"></span>Meilenstein</button>
|
||
<button type="button" onclick="tpMenueZu(); addPlanPhase()" id="addPhaseBtn" title="Leistungsphase als Balken anlegen; Aufgaben in ihrem Zeitraum stehen darunter"><span class="tp-sym-phase"></span>Leistungsphase</button>
|
||
<hr>
|
||
<button type="button" onclick="tpMenueZu(); openTemplateModal()" id="templateBtn" title="Standard-Aufgaben einer Leistungsphase einfügen">Aus Vorlage</button>
|
||
<button type="button" onclick="tpMenueZu(); openHonorarModal()" id="honorarBtn" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">Terminplan aus Honorar</button>
|
||
<button type="button" onclick="tpMenueZu(); openProtokollTodosModal()" id="protokollTodosBtn" title="Externes Besprechungsprotokoll (PDF) per KI einlesen und daraus Aufgaben ableiten">Aufgaben aus Protokoll</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div id="planWarnings"></div>
|
||
<div id="planGanttContainer" class="gantt-container"></div>
|
||
<button type="button" class="tp-ohne" id="planOhneTermin" hidden onclick="showPlanSection('aufgaben')"></button>
|
||
|
||
<div class="plan-panel" style="margin-top:18px">
|
||
<h3 class="plan-panel-title">Ressourcen-Auslastung</h3>
|
||
<div id="resourceLoadContainer"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ====================== Bereich: Aufgaben ===================== -->
|
||
<!-- ================== Bereich: Leistungsphasen ================== -->
|
||
<!-- Eriks Wahl "B": die Phasenbalken des Terminplans als eigener
|
||
Bereich — Ablaufbalken über alle Phasen, darunter eine Kachel je
|
||
Phase (renderPlanPhasen(), js/plan/plan-phasen.js). In der
|
||
Aufgabenliste stehen sie dafür nicht mehr. -->
|
||
<section class="plan-section" id="planSection-phasen" data-section="phasen">
|
||
<div class="plan-main-header">
|
||
<button onclick="addPlanPhase()" id="addPhaseBtnPhasen">+ Leistungsphase</button>
|
||
<button class="secondary" onclick="openHonorarModal()" id="honorarBtnPhasen" title="Terminplan aus Honorar und Leistungsphasen-Anteilen errechnen">Terminplan aus Honorar</button>
|
||
</div>
|
||
<div id="planPhasenWrap"></div>
|
||
</section>
|
||
|
||
<section class="plan-section" id="planSection-aufgaben" data-section="aufgaben">
|
||
<div class="plan-main-header">
|
||
<button onclick="addTodo()" id="addTodoBtnList">+ Aufgabe</button>
|
||
<button onclick="addPlanMilestone()" id="addMilestoneBtnList">+ Meilenstein</button>
|
||
<button onclick="openTemplateModal()" id="templateBtnList" title="Standard-Aufgaben einer Leistungsphase einfügen">+ Vorlage</button>
|
||
<button onclick="openProtokollTodosModal()" id="protokollTodosBtnList" title="Externes Besprechungsprotokoll (PDF) per KI einlesen und daraus Aufgaben ableiten">+ TODOs aus Protokoll</button>
|
||
<button class="secondary" onclick="autoScheduleTodos()" id="autoScheduleBtnList" title="Aufgaben nach Abhängigkeiten automatisch anordnen">Auto-Schedule</button>
|
||
</div>
|
||
|
||
<div class="plan-panel">
|
||
<h3 class="plan-panel-title">Aufgaben <span class="plan-panel-count" id="planTodoCount"></span></h3>
|
||
<div id="planTodoFilter" class="plan-task-filter"></div>
|
||
<div id="planTodoList" class="plan-task-list"></div>
|
||
</div>
|
||
|
||
<div class="plan-panel" style="margin-top:18px">
|
||
<h3 class="plan-panel-title">Meilensteine <span class="plan-panel-count" id="planMilestoneCount"></span></h3>
|
||
<div id="planMilestoneList" class="plan-task-list"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== Bereich: Leistung ====================== -->
|
||
<!-- Leistungsstand & Abrechnung: der erbrachte Leistungswert aus
|
||
Honorar × HOAI-Anteil je Phase × Fortschritt der Phase
|
||
(renderPlanLeistung(), js/plan/plan-leistung.js).
|
||
Ein eigener Bereich und keine Dashboard-Karte: Die Aufschlüsselung
|
||
hat eine Zeile je beauftragter Leistungsphase mit fünf Spalten und
|
||
stünde im Raster des Dashboards entweder gedrängt oder ein zweites
|
||
Mal — genau die Doppelung, die überall sonst gerade beseitigt
|
||
wurde. Die Zahlen beruhen auf Honorar und beauftragten Phasen aus
|
||
dem Bereich "Einstellungen" und auf dem Fortschritt, der im
|
||
Wochenbericht erfasst wird; hier wird nur gerechnet, nichts
|
||
eingegeben. -->
|
||
<section class="plan-section" id="planSection-leistung" data-section="leistung">
|
||
<div class="plan-panel plan-panel-wide">
|
||
<h3 class="plan-panel-title">Leistungsstand & 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>
|
||
|
||
<!-- Einstellungen mit Unternavigation (Eriks Wahl "C"): links vier
|
||
Abschnitte mit Kurzfassung, rechts immer genau einer
|
||
(zeigeEinstTeil(), plan-settings.js). Alle Felder behalten ihre
|
||
ids und Schreibwege; die Sandbox blendet über .form-row aus. -->
|
||
<div class="plan-einst">
|
||
<nav class="plan-einst-nav" aria-label="Abschnitte der Einstellungen">
|
||
<button type="button" class="plan-einst-reiter" data-teil="allgemein" onclick="zeigeEinstTeil('allgemein')"><b>Allgemein</b><span id="einstSumAllgemein"></span></button>
|
||
<button type="button" class="plan-einst-reiter" data-teil="auftrag" onclick="zeigeEinstTeil('auftrag')"><b>Auftrag & Honorar</b><span id="einstSumAuftrag"></span></button>
|
||
<button type="button" class="plan-einst-reiter" data-teil="phasen" onclick="zeigeEinstTeil('phasen')"><b>Leistungsphasen</b><span id="einstSumPhasen"></span></button>
|
||
<button type="button" class="plan-einst-reiter" data-teil="team" onclick="zeigeEinstTeil('team')"><b>Beteiligte</b><span id="einstSumTeam"></span></button>
|
||
</nav>
|
||
|
||
<div class="plan-panel plan-einst-inhalt">
|
||
<div class="plan-einst-teil" data-teil="allgemein">
|
||
<h2 class="plan-einst-titel">Allgemein</h2>
|
||
<div class="plan-einst-paar">
|
||
<div class="form-row">
|
||
<label for="settingsProjNr">Projektnummer</label>
|
||
<input type="text" id="settingsProjNr" placeholder="z. B. 26-4071"
|
||
onchange="setPlanProjNr(this.value)"
|
||
title="Kennzeichen des Projekts. Hochgeladene Dateien hängen an dieser Nummer — eine Änderung wird deshalb noch einmal abgefragt.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="settingsProjName">Projektname</label>
|
||
<input type="text" id="settingsProjName" placeholder="Bezeichnung"
|
||
onchange="setPlanProjName(this.value)">
|
||
</div>
|
||
</div>
|
||
<p class="hint plan-einst-hint" id="settingsProjNrHinweis">Dateien und Protokolle hängen an der Projektnummer — eine Änderung wird deshalb noch einmal abgefragt.</p>
|
||
<div class="form-row">
|
||
<label>Gewerke</label>
|
||
<div id="settingsGewerke" class="plan-einst-chips"></div>
|
||
<span class="hint plan-einst-hint">Bestimmt, in welcher Gewerke-Besprechung des Wochenberichts dieses Projekt erscheint.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="planGroupSelect">Gruppe</label>
|
||
<select id="planGroupSelect" class="plan-einst-schmal" onchange="setPlanGroup(this.value)">
|
||
<option>Aktive Projekte</option>
|
||
<option>Kleine Projekte</option>
|
||
<option>In Anbahnung</option>
|
||
<option>Allgemein</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="planActiveLpSelect">Amtierende Leistungsphase</label>
|
||
<select id="planActiveLpSelect" class="plan-einst-schmal" onchange="setPlanActiveLp(this.value)"></select>
|
||
<span class="hint plan-einst-hint">Die Phase, in der das Projekt gerade steht. Im Bereich „Leistungsphasen“ mit „läuft“ markiert.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Schlagwörter</label>
|
||
<div class="tag-chip-list" id="settingsProjTags"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plan-einst-teil" data-teil="auftrag">
|
||
<h2 class="plan-einst-titel">Auftrag & Honorar</h2>
|
||
<!-- Auftragsstatus: Anfrage → Angebot → beauftragt →
|
||
abgeschlossen. Der Sprung auf "beauftragt" öffnet den
|
||
Beauftragungs-Dialog (js/auftrag.js). -->
|
||
<div class="form-row">
|
||
<label>Auftragsstatus</label>
|
||
<div id="settingsAuftragsstatus" class="plan-einst-schmal"></div>
|
||
</div>
|
||
<div class="plan-einst-paar">
|
||
<div class="form-row">
|
||
<label for="settingsHonorar">Honorar netto (€)</label>
|
||
<input type="number" id="settingsHonorar" min="0" step="100" placeholder="0"
|
||
onchange="setHonorarBetrag(this.value)"
|
||
title="Vereinbartes Netto-Honorar. Grundlage der Terminplan-Berechnung und dauerhaft am Projekt gespeichert.">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="plan-einst-teil" data-teil="phasen">
|
||
<h2 class="plan-einst-titel">Leistungsphasen</h2>
|
||
<p class="hint plan-einst-hint">Beauftragte Phasen bestimmen den Terminplan aus dem Honorar, den Leistungsstand und die Phasen im Wochenbericht.</p>
|
||
<div id="settingsBeauftragteLp" class="plan-einst-phasen"></div>
|
||
</div>
|
||
|
||
<!-- Beteiligte als Personenliste (Eriks Wahl "B"): Besetzungsleiste,
|
||
Suchfeld, Zeilen je Person nach Gewerk mit Rollen-Kapseln,
|
||
Entfernen mit "Rückgängig" (plan-settings.js). -->
|
||
<div class="plan-einst-teil" data-teil="team">
|
||
<div class="plan-einst-team-kopfzeile">
|
||
<div>
|
||
<h2 class="plan-einst-titel">Beteiligte</h2>
|
||
<p class="hint plan-einst-hint">Eine Zeile je Person. Rolle antippen: Haupt, dann Unterstützung, dann aus.</p>
|
||
</div>
|
||
<div class="plan-einst-legende" aria-hidden="true">
|
||
<span><i class="ist-haupt"></i>Haupt</span>
|
||
<span><i class="ist-neben"></i>Unterstützung</span>
|
||
</div>
|
||
</div>
|
||
<div id="planEinstSperre" class="plan-einst-sperre" hidden>Diese Sitzung ist gesperrt. Die Zuordnung ist nur zu sehen, nicht zu ändern.</div>
|
||
<div id="planEinstBesetzung" class="plan-einst-besetzung"></div>
|
||
<div class="plan-einst-suche" id="planEinstSucheWrap">
|
||
<label for="planEinstSuche">Person hinzufügen</label>
|
||
<input type="text" id="planEinstSuche" autocomplete="off" placeholder="Name eingeben, z. B. Körner"
|
||
oninput="einstSucheZeigen()" onfocus="einstSucheZeigen()" onblur="einstSucheSchliessen()"
|
||
onkeydown="einstSucheTaste(event)">
|
||
<div id="planEinstVorschlaege" class="plan-einst-vorschlaege" hidden></div>
|
||
</div>
|
||
<div id="planAssignMatrix" class="plan-einst-team"></div>
|
||
<div id="planEinstToast" class="plan-einst-toast" hidden></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Neues Projekt Modal (inkl. LP-Aufwand) -->
|
||
<!-- Assistent "Neues Projekt" (js/projekt-wizard.js). Er ersetzt den
|
||
früheren Dialog #newProjModal, der alle Felder auf einmal zeigte — samt
|
||
einer 27-Felder-Matrix für den Aufwand je Phase und Rolle. Der Rumpf
|
||
wird vollständig aus JavaScript gefüllt; hier steht nur die Hülle. -->
|
||
<div class="modal-bg" id="wizardModal" style="display:none">
|
||
<div class="modal wizard-modal">
|
||
<div class="wiz-kopf">
|
||
<h2>Neues Projekt</h2>
|
||
<button type="button" class="wiz-schliessen" onclick="closeNewProjWizard()" title="Abbrechen">×</button>
|
||
</div>
|
||
<div id="wizardBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Beauftragung: der Übergang vom Angebot zum beauftragten Projekt
|
||
(js/auftrag.js). Erfasst genau die Angaben, die zur Beauftragung gehören
|
||
und die der Terminplan aus dem Honorar braucht (js/plan/plan-honorar.js):
|
||
vereinbartes Netto-Honorar und beauftragte Leistungsphasen. -->
|
||
<div class="modal-bg" id="beauftragungModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Beauftragung</h2>
|
||
<p style="color:#888;margin-bottom:2px" id="beauftragungProjekt"></p>
|
||
<p class="hint" style="margin:0 0 10px" id="beauftragungStatusHinweis"></p>
|
||
<div class="form-row">
|
||
<label>Honorar (netto)</label>
|
||
<!-- type="number" wie im Honorar-Dialog (#honorarBetrag): Der Browser
|
||
liefert den Wert dann immer mit Punkt als Dezimaltrenner, und ein
|
||
Tausenderpunkt ("48.500") kann gar nicht erst als 48,5 durchgehen. -->
|
||
<input type="number" min="0" step="100" id="beauftragungHonorar" placeholder="z. B. 48500" style="width:100%"
|
||
title="Vereinbartes Netto-Honorar in Euro. Grundlage des Terminplans aus dem Honorar.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Beauftragte Leistungsphasen</label>
|
||
<div id="beauftragungBeauftragteLp" class="beauftragte-lp-grid"></div>
|
||
<span class="hint" style="margin:4px 0 0">
|
||
Das Honorar wird auf genau diese Phasen verteilt.
|
||
</span>
|
||
</div>
|
||
<!-- Nur sichtbar, solange das Projekt in „In Anbahnung" liegt: Diese
|
||
Gruppe wird von der Auslastung nicht eingerechnet. -->
|
||
<div class="form-row" id="beauftragungGruppeWrap" style="display:none">
|
||
<label class="person-chip-label">
|
||
<input type="checkbox" id="beauftragungGruppeCheck">
|
||
Projekt aus „In Anbahnung" nach „Aktive Projekte" verschieben
|
||
</label>
|
||
<span class="hint" style="margin:4px 0 0">
|
||
Projekte in „In Anbahnung" zählen in der Auslastung nicht mit.
|
||
</span>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveBeauftragung(false)">Als beauftragt kennzeichnen</button>
|
||
<button onclick="saveBeauftragung(true)" title="Speichert und öffnet den Projektbereich, dort „+ Terminplan“">Beauftragen und zum Terminplan</button>
|
||
<button class="secondary" onclick="closeBeauftragungModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Neuer Terminplan (js/planung/planung.js) — schlanker Anlege-Dialog der
|
||
Planung-Sandbox: nur Name + Art, bewusst kein mehrstufiger Assistent wie
|
||
"Neues Projekt" (der verlangt Gewerke/Team/Auftragsstatus, die hier nicht
|
||
zutreffen), siehe CLAUDE.md "Planung — projektunabhängige Terminplan-
|
||
Sandbox". -->
|
||
<div class="modal-bg" id="neuerTerminplanModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Neuer Terminplan</h2>
|
||
<p class="hint">Unabhängig von einem echten Projekt — für Angebote, Kapazitätstests oder eigene Vorhaben.</p>
|
||
<div class="form-row">
|
||
<label>Name</label>
|
||
<input type="text" id="planungName" placeholder="z. B. Angebot Sporthalle Menden" style="width:100%">
|
||
</div>
|
||
<div class="form-row">
|
||
<label style="display:block;margin-bottom:10px">Wie soll's losgehen?</label>
|
||
<div class="planung-choice-cards">
|
||
<div class="choice-card" data-art="honorar" onclick="waehlePlanungArt('honorar')">
|
||
<span class="choice-card-icon">€</span>
|
||
<div class="choice-card-title">Aus Honorar berechnen</div>
|
||
<div class="choice-card-desc">Honorar und Leistungsphasen angeben, der vorhandene Terminplan-Rechner erstellt die Aufgaben.</div>
|
||
</div>
|
||
<div class="choice-card" data-art="leer" onclick="waehlePlanungArt('leer')">
|
||
<span class="choice-card-icon">✎</span>
|
||
<div class="choice-card-title">Leer beginnen</div>
|
||
<div class="choice-card-desc">Direkt in den leeren Terminplan, Aufgaben und Meilensteine von Hand anlegen.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="erstellePlanungTerminplan()">Anlegen</button>
|
||
<button class="secondary" onclick="schliesseNeuerTerminplanDialog()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== Dialoge des Projektbereichs =====================
|
||
Gehoeren zum Bereich #plan (frueher project-plan.html) und liegen wie alle
|
||
anderen Dialoge dieser Seite auf oberster Ebene, damit .modal-bg den
|
||
ganzen Bildschirm ueberdeckt. Befuellt werden sie von den Modulen unter
|
||
public/js/plan/ (plan-modals.js, plan-templates.js, plan-honorar.js,
|
||
plan-history.js). -->
|
||
<!-- Protokoll-Import: externes Protokoll (PDF/Word) hochladen, Punkte werden
|
||
serverseitig heuristisch erkannt (POST /project-protokolle/import,
|
||
js/plan/plan-protokoll.js) — Ergebnis landet als ganz normales,
|
||
editierbares Protokoll, keine eigene Vorschau-Oberfläche nötig. -->
|
||
<div class="modal-bg" id="protokollImportModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Protokoll importieren</h2>
|
||
<p class="hint">Ein extern erstelltes Protokoll (PDF oder Word) hochladen — die
|
||
App versucht, die Punkte automatisch zu erkennen. Das Ergebnis ist ein
|
||
Entwurf: bitte vor dem Speichern prüfen und bei Bedarf anpassen.</p>
|
||
<div class="form-row">
|
||
<label>Datei</label>
|
||
<input type="file" id="protokollImportFile" accept=".pdf,.docx">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Erkennung</label>
|
||
<select id="protokollImportErkennung"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Datum</label>
|
||
<input type="text" id="protokollImportDatumDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'protokollImportDatum', displayId: 'protokollImportDatumDisplay' })">
|
||
<input type="hidden" id="protokollImportDatum">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Betreff</label>
|
||
<input type="text" id="protokollImportBetreff" placeholder="z. B. Baubesprechung TGA">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Ort</label>
|
||
<input type="text" id="protokollImportOrt">
|
||
</div>
|
||
<span class="field-error" id="protokollImportError" hidden></span>
|
||
<!-- Läuft nur bei der KI-Erkennung an, kann dort je nach Anbieter/
|
||
eigenem Server (KI_TIMEOUT_MS_LOKAL, server.js) bis zu 30 Minuten
|
||
dauern — ohne diesen Hinweis wirkte der Dialog in der Zeit wie
|
||
hängengeblieben. -->
|
||
<div class="protokoll-import-loading" id="protokollImportLoading" hidden>
|
||
<span class="protokoll-import-spinner" aria-hidden="true"></span>
|
||
<span>Importiere … bei KI-Erkennung über einen eigenen Server kann dies
|
||
bis zu 30 Minuten dauern.</span>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="importiereProtokoll()">Importieren</button>
|
||
<button class="secondary" onclick="schliesseProtokollImportDialog()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- "TODOs aus Protokoll" (js/plan/plan-protokoll-todos.js): liest ein
|
||
EXTERNES Besprechungsprotokoll (PDF) per KI ein und leitet daraus
|
||
Aufgaben für dieses Projekt ab. Bewusst unabhängig vom Protokoll-Import
|
||
oben: nichts davon wird gespeichert, kein Bezug zu project_protokolle —
|
||
ein reines Ad-hoc-Werkzeug wie der Plan Checker. Drei Schritte
|
||
(Hochladen → Punkte wählen → Aufgaben bestätigen) in EINEM Dialog mit
|
||
sichtbarem Fortschritt (#protokollTodosSteps) und fester Zurück/
|
||
Abbrechen/Weiter-Fußzeile — Titel, Fortschritt und der Inhalt von
|
||
#protokollTodosBody werden je Schritt neu gerendert, der Rahmen bleibt
|
||
stehen. Entwurfsrichtung "A — Schritt-Dialog" aus dem Entwurfsvergleich
|
||
(Taste-Skill), von Erik gewählt. -->
|
||
<div class="modal-bg" id="protokollTodosModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2 id="protokollTodosTitle">Externes Protokoll einlesen</h2>
|
||
<p class="hint" id="protokollTodosSubtitle" style="margin-top:-4px"></p>
|
||
<div class="protokoll-todos-steps" id="protokollTodosSteps">
|
||
<div class="seg" data-i="0"></div><div class="seg" data-i="1"></div><div class="seg" data-i="2"></div>
|
||
</div>
|
||
<div class="protokoll-ki-fallback-banner" id="protokollTodosFallback" hidden></div>
|
||
<div id="protokollTodosBody"></div>
|
||
<span class="field-error" id="protokollTodosError" hidden></span>
|
||
<div class="protokoll-import-loading" id="protokollTodosLoading" hidden>
|
||
<span class="protokoll-import-spinner" aria-hidden="true"></span>
|
||
<span id="protokollTodosLoadingText">Wird verarbeitet …</span>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:14px; justify-content:space-between">
|
||
<button type="button" class="secondary" id="protokollTodosBackBtn" onclick="protokollTodosZurueck()" disabled>Zurück</button>
|
||
<div style="display:flex; gap:8px">
|
||
<button type="button" class="secondary" onclick="closeProtokollTodosModal()">Abbrechen</button>
|
||
<button type="button" id="protokollTodosPrimaryBtn" onclick="protokollTodosWeiter()">Einlesen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Vorlagen-Dialog: Standard-Aufgaben einer Leistungsphase einfügen.
|
||
Eigenständiger Dialog — der frühere gemeinsame Dialog mit Modus-Umschalter
|
||
("Aus Vorlage" / "Aus Honorar") wurde in zwei getrennte Dialoge aufgeteilt,
|
||
die je eine eigene Schaltfläche in der GANTT-Toolbar haben. Damit steht auf
|
||
dem Knopf, was dahinter passiert, und keine der beiden Masken trägt die
|
||
Eingaben der jeweils anderen mit. -->
|
||
<div class="modal-bg" id="lphTemplateModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2>Aufgaben aus Vorlage</h2>
|
||
|
||
<div class="template-head-grid">
|
||
<div class="form-row">
|
||
<label>Gewerk</label>
|
||
<select id="templateGewerkSelect" onchange="renderTemplateList()"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Leistungsphase</label>
|
||
<select id="templateLphSelect" onchange="templatePhaseVorbelegen(); renderTemplateList()"></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Startdatum</label>
|
||
<input type="text" id="templateStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'templateStart', displayId: 'templateStartDisplay', onApply: renderTemplateList })">
|
||
<input type="hidden" id="templateStart">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="templatePhasenTage">Dauer der Phase (Arbeitstage)</label>
|
||
<input type="number" id="templatePhasenTage" min="1" placeholder="z. B. 40" oninput="renderTemplateList()">
|
||
<span class="hint" id="templatePhasenHinweis" style="margin:2px 0 0"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<label class="form-row" style="margin-bottom:4px"><span>Schritte</span></label>
|
||
<div id="templateTaskList" class="todo-depends-list"></div>
|
||
|
||
<!-- Gepflegt werden die Vorlagen im Admin-Bereich; hier werden
|
||
sie nur noch eingefügt. Der Verweis erscheint nur für Administratoren
|
||
(renderTemplateHint() in js/plan/plan-templates.js). -->
|
||
<p class="hint" id="templateAdminHint" style="display:none">
|
||
Vorlagen pflegen: <a href="/Admin#vorlagen" onclick="closeTemplateModal();showAdminSection('vorlagen');return false;">Admin → Aufgaben-Vorlagen</a>
|
||
</p>
|
||
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button onclick="applyTemplate()">Aufgaben einfügen</button>
|
||
<button class="secondary" onclick="closeTemplateModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Honorar-Dialog: kompletten Terminplan aus dem Honorar errechnen
|
||
(js/plan/plan-honorar.js). Zweiter der beiden getrennten Dialoge, siehe
|
||
Kommentar beim Vorlagen-Dialog oben. -->
|
||
<div class="modal-bg" id="honorarPlanModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2>Terminplan aus Honorar</h2>
|
||
|
||
<div class="template-head-grid">
|
||
<div class="form-row">
|
||
<label>Honorar (netto)</label>
|
||
<input type="number" id="honorarBetrag" min="0" step="100" placeholder="0"
|
||
onchange="setHonorarBetrag(this.value)"
|
||
title="Vereinbartes Netto-Honorar. Wird am Projekt gespeichert und bleibt erhalten.">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Beginn der ersten Leistungsphase</label>
|
||
<input type="text" id="honorarStartDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly
|
||
onclick="openDatePicker({ anchor: this, inputId: 'honorarStart', displayId: 'honorarStartDisplay', onApply: renderHonorarPlan })">
|
||
<input type="hidden" id="honorarStart">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<label>Beauftragte Leistungsphasen</label>
|
||
<div id="honorarBeauftragteLp" class="beauftragte-lp-grid"></div>
|
||
</div>
|
||
|
||
<label class="honorar-check" title="Sind nur einzelne Phasen beauftragt, ist das vereinbarte Honorar das Honorar für genau diese Phasen. Ohne Normierung bliebe ein Teil des Honorars unverplant.">
|
||
<input type="checkbox" id="honorarNormieren" checked onchange="renderHonorarPlan()">
|
||
Honorar auf die beauftragten Phasen normieren
|
||
</label>
|
||
|
||
<div id="honorarPlanTable" class="honorar-plan-wrap"></div>
|
||
|
||
<!-- Anteile und Stundensatz werden im Bereich „Einstellungen" gepflegt;
|
||
hier wird damit nur gerechnet. Der Verweis erscheint nur für
|
||
Administratoren (renderHonorarHint() in js/plan/plan-honorar.js). -->
|
||
<p class="hint" id="honorarAdminHint" style="display:none">
|
||
Anteile und Stundensatz pflegen: <a href="/Einstellungen" onclick="closeHonorarModal();showTab('settings');return false;">Einstellungen → Honorar</a>
|
||
</p>
|
||
|
||
<div class="toolbar" style="margin-top:16px">
|
||
<button onclick="applyHonorarPlan()">Terminplan einfügen</button>
|
||
<button class="secondary" onclick="closeHonorarModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Meilenstein und Aufgabe als SEITENPANEL rechts (Eriks Wahl "B"): der
|
||
Terminplan bleibt links sichtbar. Beide Panels gleich gebaut: Kopf mit
|
||
Art und Titel, Abschnitte Wann / Wo bzw. Wer / Stand / Hängt ab von,
|
||
feste Fußleiste. Alle Feld-ids wie zuvor; befüllt und ausgewertet von
|
||
js/plan/plan-modals.js. Stile: Block "Seitenpanel" in plan-styles.css. -->
|
||
<div class="modal-bg als-panel" id="milestoneModal" style="display:none">
|
||
<div class="modal pd">
|
||
<div class="pd-kopf">
|
||
<div class="pd-art"><span class="pd-raute" aria-hidden="true"></span><span id="milestoneModalTitle">Meilenstein</span>
|
||
<span class="modal-kbd">Esc schließt</span>
|
||
<button type="button" class="pd-zu" onclick="closeMilestoneModal()" aria-label="Schließen">×</button></div>
|
||
<input type="text" id="milestoneEditName" class="pd-titel" placeholder="z. B. Abgabe Genehmigungsplanung" aria-label="Name des Meilensteins">
|
||
</div>
|
||
<div class="pd-inhalt">
|
||
<section class="pd-abs">
|
||
<h3>Wann</h3>
|
||
<!-- sichtbares Anzeigefeld (deutsches Format) + verstecktes ISO-Feld -->
|
||
<input type="text" id="milestoneEditDateDisplay" class="range-picker-display" placeholder="Datum wählen…" readonly aria-label="Datum"
|
||
onclick="openDatePicker({ anchor: this, inputId: 'milestoneEditDate', displayId: 'milestoneEditDateDisplay' })">
|
||
<input type="hidden" id="milestoneEditDate">
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Wo im Terminplan</h3>
|
||
<div class="segmented pd-seg" id="milestoneEditLage">
|
||
<button type="button" data-lage="zeile" aria-pressed="false" onclick="msLageSetzen('zeile')">Oben, eigene Zeile</button>
|
||
<button type="button" data-lage="aufgabe" aria-pressed="false" onclick="msLageSetzen('aufgabe')">Bei einer Aufgabe</button>
|
||
</div>
|
||
<select id="milestoneEditPosition" aria-label="Position bei einer Aufgabe" onchange="msLageSync()">
|
||
<option value="">Oben in der Projektzeile</option>
|
||
</select>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Hängt ab von</h3>
|
||
<div id="milestoneEditDependsOn" class="todo-depends-list pd-liste"></div>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Notiz</h3>
|
||
<p class="hint pd-hint pd-mail-hinweis" id="milestoneEditMailHinweis" hidden></p>
|
||
<textarea id="milestoneEditNotiz" class="pd-notiz" rows="4" placeholder="Optional, z. B. Hintergrund oder die Mail, aus der der Meilenstein stammt" aria-label="Notiz"></textarea>
|
||
</section>
|
||
</div>
|
||
<div class="pd-fuss">
|
||
<button type="button" class="pd-loeschen" id="milestoneEditDelete" onclick="deleteMilestoneEdit()">Löschen</button>
|
||
<button type="button" class="pd-speichern" onclick="saveMilestoneEdit()">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-bg als-panel" id="todoModal" style="display:none">
|
||
<div class="modal pd">
|
||
<div class="pd-kopf">
|
||
<div class="pd-art"><span class="pd-kasten" aria-hidden="true"></span><span id="todoModalTitle">Aufgabe bearbeiten</span>
|
||
<span class="modal-kbd" id="todoModalKbd">Esc · Strg+↵</span>
|
||
<button type="button" class="pd-zu" onclick="closeTodoModal()" aria-label="Schließen">×</button></div>
|
||
<input type="text" id="todoEditTitle" class="pd-titel" placeholder="Titel der Aufgabe" aria-label="Titel">
|
||
<span class="field-error" id="todoEditTitleError" hidden></span>
|
||
</div>
|
||
<div class="pd-inhalt">
|
||
<!-- Nur im Phasen-Modus ("+ Neu" → "Leistungsphase" oder Bearbeiten
|
||
eines Phasenbalkens): Welche Leistungsphase dieser Balken ist. Nur
|
||
darüber wird ein Balken zum Phasenkopf, nicht mehr über den Titel. -->
|
||
<section class="pd-abs" id="todoEditPhaseAbs" hidden>
|
||
<h3>Leistungsphase</h3>
|
||
<select id="todoEditLph" aria-label="Leistungsphase" onchange="todoPhaseGewechselt()"></select>
|
||
<p class="hint pd-hint">Aufgaben, die im Zeitraum dieser Phase beginnen, stehen im Terminplan darunter.</p>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Wann</h3>
|
||
<div class="field-inline">
|
||
<input type="text" id="todoEditRange" class="range-picker-display" placeholder="Start – Ende wählen…" readonly aria-label="Zeitraum"
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'todoEditStart', endInputId: 'todoEditEnd', displayId: 'todoEditRange', onApply: refreshTodoDuration })">
|
||
<span class="field-badge" id="todoEditDuration" hidden></span>
|
||
</div>
|
||
<input type="hidden" id="todoEditStart">
|
||
<input type="hidden" id="todoEditEnd">
|
||
<span class="field-error" id="todoEditRangeError" hidden></span>
|
||
<label class="pd-haken"><input type="checkbox" id="todoEditShowInGantt" checked> Im Terminplan als Balken zeigen</label>
|
||
<p class="hint pd-hint">Ohne Zeitraum erscheint die Aufgabe nur unter „Aufgaben“, nicht im Terminplan.</p>
|
||
</section>
|
||
<section class="pd-abs" id="todoEditWerAbs">
|
||
<h3>Wer</h3>
|
||
<div id="todoEditAssignees" class="person-chips"></div>
|
||
<label class="pd-aufwand" for="todoEditAufwand">
|
||
<span>Aufwand</span>
|
||
<input type="number" id="todoEditAufwand" min="0" step="0.5" placeholder="0" inputmode="decimal">
|
||
<span>Stunden, optional</span>
|
||
</label>
|
||
<p class="hint pd-hint">Zählt zur Auslastung der Zuständigen, verteilt auf den Zeitraum der Aufgabe.</p>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Stand</h3>
|
||
<div class="segmented pd-seg" id="todoEditStatus">
|
||
<button type="button" data-status="offen" aria-pressed="false" onclick="setTodoEditStatus('offen')"><span class="segmented-dot"></span>Offen</button>
|
||
<button type="button" data-status="arbeit" aria-pressed="false" onclick="setTodoEditStatus('arbeit')"><span class="segmented-dot"></span>In Arbeit</button>
|
||
<button type="button" data-status="fertig" aria-pressed="false" onclick="setTodoEditStatus('fertig')"><span class="segmented-dot"></span>Erledigt</button>
|
||
</div>
|
||
<div class="progress-row" id="todoEditProgressRow">
|
||
<input type="range" id="todoEditProgress" min="0" max="100" step="5" value="0" aria-label="Fortschritt"
|
||
oninput="onTodoProgressInput(this)">
|
||
<output id="todoEditProgressVal">0 %</output>
|
||
</div>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<h3>Notiz</h3>
|
||
<p class="hint pd-hint pd-mail-hinweis" id="todoEditMailHinweis" hidden></p>
|
||
<textarea id="todoEditNotiz" class="pd-notiz" rows="4" placeholder="Optional, z. B. Hintergrund oder die Mail, aus der die Aufgabe stammt" aria-label="Notiz"></textarea>
|
||
</section>
|
||
<section class="pd-abs">
|
||
<details class="fold" id="todoEditDependsFold">
|
||
<summary>
|
||
<span class="fold-caret">▶</span>
|
||
<span>Hängt ab von</span>
|
||
<span class="fold-count zero" id="todoEditDependsCount">0</span>
|
||
</summary>
|
||
<div class="fold-body">
|
||
<div class="fold-search">
|
||
<span class="fold-search-icon">⌕</span>
|
||
<input type="text" id="todoEditDependsSearch" placeholder="Aufgabe oder Meilenstein suchen…" oninput="filterTodoDepends()">
|
||
</div>
|
||
<div id="todoEditDependsOn" class="todo-depends-list flat" onchange="refreshTodoDependsCount()"></div>
|
||
</div>
|
||
</details>
|
||
</section>
|
||
</div>
|
||
<div class="pd-fuss">
|
||
<button type="button" class="pd-loeschen" id="todoEditDelete" onclick="deleteTodoEdit()">Löschen</button>
|
||
<button type="button" class="secondary" id="todoEditSaveNew" onclick="saveTodoEdit(true)">Speichern & neu</button>
|
||
<button type="button" class="pd-speichern" onclick="saveTodoEdit()">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Abwesenheits-Modal (Urlaube der Projektbeteiligten) -->
|
||
<div class="modal-bg" id="absenceModal" style="display:none">
|
||
<div class="modal">
|
||
<h2 id="absenceModalTitle">Abwesenheiten</h2>
|
||
<div class="form-row">
|
||
<div id="absenceModalList"></div>
|
||
</div>
|
||
<div class="absence-form" id="absenceModalForm">
|
||
<input type="text" id="absenceEditRange" class="range-picker-display" placeholder="Zeitraum wählen…" readonly
|
||
onclick="openRangePicker({ anchor: this, startInputId: 'absenceEditStart', endInputId: 'absenceEditEnd', displayId: 'absenceEditRange' })">
|
||
<input type="hidden" id="absenceEditStart">
|
||
<input type="hidden" id="absenceEditEnd">
|
||
<select id="absenceEditType">
|
||
<option>Urlaub</option>
|
||
<option>Krank</option>
|
||
<option>Fortbildung</option>
|
||
<option>Sonstiges</option>
|
||
</select>
|
||
<input type="text" id="absenceEditNote" placeholder="Notiz (optional)" style="width:140px">
|
||
<button class="secondary small" onclick="addPlanAbsence()">+ Abwesenheit</button>
|
||
</div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button class="secondary" onclick="closeAbsenceModal()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== Dialoge der Benutzerverwaltung =====================
|
||
Gehoeren zum Abschnitt „Benutzerverwaltung" des Bereichs #admin (frueher
|
||
die eigenstaendige Seite admin-users.html) und liegen wie alle anderen
|
||
Dialoge auf oberster Ebene, damit .modal-bg den ganzen Bildschirm
|
||
ueberdeckt. Befuellt werden sie von js/admin-users.js. -->
|
||
<!-- Neuer-Benutzer-Modal -->
|
||
<div class="modal-bg" id="newUserModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Neuer Benutzer</h2>
|
||
<div class="form-row-pair">
|
||
<div class="form-row">
|
||
<label for="newUserFirstName">Vorname</label>
|
||
<input type="text" id="newUserFirstName" placeholder="z. B. Max" autocomplete="off" oninput="onNewUserNameInput()">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="newUserLastName">Nachname</label>
|
||
<input type="text" id="newUserLastName" placeholder="z. B. Mustermann" autocomplete="off" oninput="onNewUserNameInput()">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="newUserEmail">E-Mail</label>
|
||
<input type="email" id="newUserEmail" placeholder="v.nachname@rauschenberg-ing.de">
|
||
<span class="hint" style="margin:4px 0 0">Der Anfang der Adresse ist zugleich der Benutzername für die Anmeldung.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="newUserUsername">Benutzername</label>
|
||
<input type="text" id="newUserUsername" placeholder="Anfang der E-Mail, z. B. m.mustermann" autocomplete="off">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Rolle</label>
|
||
<select id="newUserRole" onchange="onUserRoleChange('new')">
|
||
<!-- Optionen aus „Rollen & Rechte“ (ladeSystemRollen(), js/admin-users.js) -->
|
||
</select>
|
||
<p class="hint" id="newUserRoleHint" style="display:none">Diese Rolle sieht nur die Wandtafel und hat keine Fachrolle.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Abteilung</label>
|
||
<select id="newUserDepartment" required></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Kürzel</label>
|
||
<input type="text" id="newUserInitials" placeholder="wird aus dem Namen vorgeschlagen" maxlength="4">
|
||
<p class="hint">Kurzform fürs Projektboard (2-4 Zeichen), wird vorgeschlagen und kann angepasst werden.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Wochenstunden</label>
|
||
<input type="number" id="newUserCapacity" min="0" max="80" step="0.5" placeholder="z. B. 40">
|
||
<p class="hint">Vertragliche Arbeitszeit. Die Auslastung misst die Person daran: Eine halbe Stelle gilt schon bei halber Last als voll ausgelastet. Leer = volle Stelle.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Person-ID</label>
|
||
<input type="text" id="newUserPersonId" placeholder="wird aus dem Kürzel gebildet, z. B. p_mm">
|
||
<p class="hint">Verknüpft den Account mit einem Eintrag in der Projektbeteiligten-Liste (personIdForName). Bei Kollisionen anpassen.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Fachrolle(n)</label>
|
||
<div class="role-checkboxes" id="newUserRoleCheckboxes"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Passwort</label>
|
||
<div class="role-checkboxes">
|
||
<label><input type="radio" name="newUserPwMode" value="generate" checked onchange="onNewUserPwModeChange()"> Zufällig generieren</label>
|
||
<label><input type="radio" name="newUserPwMode" value="manual" onchange="onNewUserPwModeChange()"> Manuell festlegen</label>
|
||
</div>
|
||
<input type="password" id="newUserPassword" placeholder="mind. 8 Zeichen" style="display:none;margin-top:8px" autocomplete="new-password">
|
||
</div>
|
||
<div id="newUserError" style="color:#c00;"></div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveNewUser()">Anlegen</button>
|
||
<button class="secondary" onclick="closeNewUserModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Benutzer-bearbeiten-Modal -->
|
||
<div class="modal-bg" id="editUserModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Benutzer bearbeiten</h2>
|
||
<p class="hint" id="editUserUsernameLabel" style="margin-bottom:10px"></p>
|
||
<div class="form-row-pair">
|
||
<div class="form-row">
|
||
<label for="editUserFirstName">Vorname</label>
|
||
<input type="text" id="editUserFirstName" oninput="onEditUserNameInput()">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="editUserLastName">Nachname</label>
|
||
<input type="text" id="editUserLastName" oninput="onEditUserNameInput()">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>E-Mail</label>
|
||
<input type="email" id="editUserEmail" placeholder="v.nachname@rauschenberg-ing.de">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Rolle</label>
|
||
<select id="editUserRole" onchange="onUserRoleChange('edit')">
|
||
<!-- Optionen aus „Rollen & Rechte“ (ladeSystemRollen(), js/admin-users.js) -->
|
||
</select>
|
||
<p class="hint" id="editUserRoleHint" style="display:none">Diese Rolle sieht nur die Wandtafel und hat keine Fachrolle.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Abteilung</label>
|
||
<select id="editUserDepartment" required></select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Kürzel</label>
|
||
<input type="text" id="editUserInitials" maxlength="4">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Wochenstunden</label>
|
||
<input type="number" id="editUserCapacity" min="0" max="80" step="0.5" placeholder="leer = volle Stelle">
|
||
<p class="hint">Vertragliche Arbeitszeit; Grundlage der Auslastungs-Ampel.</p>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Person-ID</label>
|
||
<input type="text" id="editUserPersonId">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Fachrolle(n)</label>
|
||
<div class="role-checkboxes" id="editUserRoleCheckboxes"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Abwesenheiten</label>
|
||
<div id="editUserAbsenceList" class="absence-list"></div>
|
||
<div class="absence-form">
|
||
<input type="date" id="editUserAbsenceStart">
|
||
<input type="date" id="editUserAbsenceEnd">
|
||
<select id="editUserAbsenceType">
|
||
<option>Urlaub</option>
|
||
<option>Krank</option>
|
||
<option>Fortbildung</option>
|
||
<option>Sonstiges</option>
|
||
</select>
|
||
<input type="text" id="editUserAbsenceNote" placeholder="Notiz (optional)" style="width:140px">
|
||
<button class="secondary small" onclick="addEditUserAbsence()">+ Abwesenheit</button>
|
||
</div>
|
||
<div id="editUserAbsenceError" style="color:#c00;"></div>
|
||
</div>
|
||
<div id="editUserError" style="color:#c00;"></div>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button onclick="saveEditUser()">Speichern</button>
|
||
<button class="secondary" onclick="closeEditUserModal()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Aufgabe aus dem Kommentar (js/wochenbericht/wochenbericht.js).
|
||
Macht aus einem im Meeting gefassten Beschluss eine echte Aufgabe im Plan
|
||
des Projekts — bis dahin blieben solche Absprachen Fließtext im Kommentar
|
||
und tauchten im Terminplan nie auf. -->
|
||
<div class="modal-bg" id="todoKommentarModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Aufgabe anlegen</h2>
|
||
<p class="hint" id="todoKommentarProjekt" style="margin-top:-4px"></p>
|
||
<div class="form-row">
|
||
<label for="todoKommentarTitel">Was ist zu tun?</label>
|
||
<input type="text" id="todoKommentarTitel" placeholder="z. B. „LV an T.W. zur Prüfung">
|
||
<span class="hint" style="margin:4px 0 0">Vorgeschlagen ist die erste Zeile des Kommentars.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Zuständige</label>
|
||
<div id="todoKommentarPersonen" class="person-chips"></div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="todoKommentarEnde">Fällig am</label>
|
||
<input type="date" id="todoKommentarEnde">
|
||
<span class="hint" style="margin:4px 0 0">Vorgeschlagen ist die kommende Woche.</span>
|
||
</div>
|
||
<div class="field-error" id="todoKommentarFehler" hidden></div>
|
||
<div class="toolbar">
|
||
<button type="button" onclick="saveTodoAusKommentar()">Aufgabe anlegen</button>
|
||
<button type="button" class="secondary" onclick="closeTodoAusKommentar()">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Protokoll der Sitzung (js/wochenbericht/wochenbericht.js). Stellt
|
||
zusammen, was sich gegenüber der vorherigen Sitzung geändert hat, samt
|
||
der heute angelegten Aufgaben. -->
|
||
<div class="modal-bg" id="protokollModal" style="display:none">
|
||
<div class="modal modal-wide">
|
||
<h2>Protokoll der Sitzung</h2>
|
||
<p class="hint" style="margin-top:-4px">
|
||
<span id="protokollAnzahl"></span> — unveränderte Projekte stehen nicht drin.
|
||
</p>
|
||
<textarea id="protokollText" rows="16" class="protokoll-text" spellcheck="false"></textarea>
|
||
<div class="toolbar" style="margin-top:12px">
|
||
<button type="button" onclick="kopiereProtokoll()">In die Zwischenablage</button>
|
||
<button type="button" class="secondary" onclick="protokollAlsMail()">Als Mail öffnen</button>
|
||
<span class="hint" id="protokollHinweis" style="margin:0"></span>
|
||
<span class="comment-foot-spacer"></span>
|
||
<button type="button" class="secondary" onclick="closeBerichtProtokoll()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Passwort ändern (js/auth.js). Zwei Betriebsarten: freiwillig aus dem
|
||
Bereich „Einstellungen" heraus und ERZWUNGEN, wenn das Konto
|
||
must_change_password trägt — dann ohne Abbrechen. Der Server verlangt in
|
||
beiden Fällen das aktuelle Passwort. -->
|
||
<div class="modal-bg" id="passwordModal" style="display:none">
|
||
<div class="modal">
|
||
<h2>Passwort ändern</h2>
|
||
<p class="hint" id="passwordForcedHint" hidden>
|
||
Für dieses Konto ist ein Passwortwechsel erforderlich — das Startpasswort
|
||
wurde von einem Administrator vergeben. Erst danach geht es weiter.
|
||
</p>
|
||
<form id="passwordForm" onsubmit="submitPasswordChange(event)">
|
||
<div class="form-row">
|
||
<label for="passwordCurrent">Aktuelles Passwort</label>
|
||
<input type="password" id="passwordCurrent" autocomplete="current-password" required>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="passwordNew">Neues Passwort</label>
|
||
<input type="password" id="passwordNew" autocomplete="new-password" minlength="8" required>
|
||
<span class="hint" style="margin:4px 0 0">Mindestens 8 Zeichen.</span>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="passwordRepeat">Neues Passwort wiederholen</label>
|
||
<input type="password" id="passwordRepeat" autocomplete="new-password" minlength="8" required>
|
||
</div>
|
||
<div class="field-error" id="passwordError" hidden></div>
|
||
<div class="toolbar">
|
||
<button type="submit">Passwort ändern</button>
|
||
<button type="button" class="secondary" id="passwordCancelBtn" onclick="closePasswordModal()">Abbrechen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Login-Seite: blockiert die gesamte App ohne gültige Session (siehe
|
||
js/auth.js). Design "Kalenderwoche" (Eriks Wahl B aus dem Vergleich der
|
||
Login-Entwürfe): links Logo und Formular, rechts die laufende
|
||
Kalenderwoche als große Zahl mit ihren Arbeitstagen — die App denkt in
|
||
Sitzungen je KW. Gefüllt von renderLoginWoche(). Das Logo liegt als
|
||
blasses Wasserzeichen über die ganze Seite (Variante E aus dem
|
||
Logo-Vergleich), ein kleines Logo gibt es bewusst nicht. -->
|
||
<div id="loginOverlay" class="login-seite" style="display:none;">
|
||
<div class="login-wz-rahmen" aria-hidden="true"><img class="login-wasserzeichen" src="/logo.svg" alt=""></div>
|
||
<main class="login-raster" aria-label="Anmeldung bei der Projektübersicht">
|
||
<section class="login-links">
|
||
<h1 class="login-titel">Projektübersicht</h1>
|
||
<p class="login-unter">Melde dich mit dem Anfang deiner E-Mail-Adresse an.</p>
|
||
<form id="loginForm" class="login-form" novalidate>
|
||
<div class="login-feld">
|
||
<label for="loginUsername">Benutzername</label>
|
||
<input type="text" id="loginUsername" name="username" autocomplete="username" placeholder="z. B. m.mustermann" spellcheck="false" autocapitalize="off">
|
||
</div>
|
||
<div class="login-feld">
|
||
<label for="loginPassword">Passwort</label>
|
||
<div class="login-pw">
|
||
<input type="password" id="loginPassword" name="password" autocomplete="current-password">
|
||
<button type="button" id="loginPwToggle" class="login-pw-toggle" aria-pressed="false" aria-controls="loginPassword">Anzeigen</button>
|
||
</div>
|
||
<p id="loginCaps" class="login-caps" hidden>Die Feststelltaste ist aktiv.</p>
|
||
</div>
|
||
<p id="loginError" class="login-fehler" role="alert" hidden></p>
|
||
<button type="submit" id="loginSubmit" class="login-btn"><span class="login-spinner" aria-hidden="true"></span><span class="login-btn-text">Anmelden</span></button>
|
||
</form>
|
||
<p class="login-hilfe">Passwort vergessen? Ein Administrator setzt es dir zurück.</p>
|
||
</section>
|
||
<section class="login-woche" aria-label="Laufende Kalenderwoche">
|
||
<span class="login-kw-label">Kalenderwoche</span>
|
||
<span class="login-kw-zahl" id="loginKwZahl"></span>
|
||
<span class="login-kw-jahr" id="loginKwJahr"></span>
|
||
<ol class="login-tage" id="loginTage"></ol>
|
||
<p class="login-datum"><span id="loginDatum"></span>. Die Sitzung dieser Woche ist geöffnet.</p>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
|
||
<script src="/js/globals.js?v=9"></script>
|
||
<script src="/js/utils.js?v=31"></script>
|
||
<script src="/js/icons.js?v=2"></script>
|
||
<script src="/js/ui.js?v=9"></script>
|
||
<script src="/js/date-picker.js?v=6"></script>
|
||
<script src="/js/server.js?v=7"></script>
|
||
<script src="/js/auth.js?v=11"></script>
|
||
<script src="/js/store.js?v=15"></script>
|
||
<script src="/js/weeks.js?v=12"></script>
|
||
<script src="/js/board/board.js?v=41"></script>
|
||
<script src="/js/wochenbericht/wochenbericht.js?v=29"></script>
|
||
<script src="/js/people/people.js?v=8"></script>
|
||
<script src="/js/planung/planung.js?v=3"></script>
|
||
<script src="/js/planchecker/planchecker.js?v=1"></script>
|
||
<script src="/js/abwesenheit/abwesenheit.js?v=10"></script>
|
||
<script src="/js/projects/projects.js?v=10"></script>
|
||
<script src="/js/load/load.js?v=23"></script>
|
||
<!-- auftrag.js: Auftragsstatus und Dialog "Beauftragung". Nach load.js, weil
|
||
der Neues-Projekt-Dialog dort die Statusauswahl mit AUFTRAGSSTATUS füllt. -->
|
||
<script src="/js/auftrag.js?v=2"></script>
|
||
<!-- Assistent "Neues Projekt": braucht auftrag.js (Auftragsstatus) und
|
||
load.js (faktorFormat), steht deshalb dahinter. -->
|
||
<script src="/js/projekt-wizard.js?v=6"></script>
|
||
<script src="/js/dashboard/dashboard.js?v=10"></script>
|
||
<script src="/js/benachrichtigungen.js?v=5"></script>
|
||
<!-- wand.js braucht berechneAuslastung()/loadAmpelColor()/kapazitaetsFaktor()/
|
||
stundenFormat() (load.js), classifyProjectChange()/findPreviousWeek()
|
||
(wochenbericht.js), statusPreviewText() (board.js) und
|
||
relativerTag()/tageBis()/initialsFor() (dashboard.js) — alle laden
|
||
bereits vorher, die Reihenfolge hier ist also nur Zuordnung zum
|
||
Tab-Ordner, keine neue Abhängigkeit nach vorn. -->
|
||
<script src="/js/wand/wand.js?v=21"></script>
|
||
<script src="/js/settings/settings.js?v=14"></script>
|
||
<script src="/js/admin/admin.js?v=21"></script>
|
||
<script src="/js/admin-users.js?v=10"></script>
|
||
<script src="/js/admin/rollen.js?v=1"></script>
|
||
<script src="/js/einfuehrung.js?v=2"></script>
|
||
<script src="/js/gantt.js?v=10"></script>
|
||
<!-- Module des Projektbereichs (#plan). Sie liefen frueher nur in
|
||
project-plan.html; seit der Projektbereich ein Bereich dieser Seite ist,
|
||
gehoeren sie hierher. Reihenfolge wie gehabt: plan-state.js legt
|
||
window.PlanApp an, plan-history.js muss VOR plan-build-gantt.js stehen
|
||
(das seine Vorwochen-Helfer nutzt), und alle zusammen muessen nach
|
||
js/gantt.js kommen, dessen buildGanttHtml() plan-build-gantt.js
|
||
umhuellt. -->
|
||
<script src="/js/plan/plan-state.js?v=24"></script>
|
||
<script src="/js/plan/plan-sections.js?v=10"></script>
|
||
<script src="/js/plan/plan-info.js?v=15"></script>
|
||
<script src="/js/plan/plan-history.js?v=4"></script>
|
||
<script src="/js/plan/plan-build-gantt.js?v=11"></script>
|
||
<script src="/js/plan/plan-gantt.js?v=21"></script>
|
||
<script src="/js/plan/plan-drag.js?v=8"></script>
|
||
<script src="/js/plan/plan-modals.js?v=15"></script>
|
||
<script src="/js/plan/plan-tasks.js?v=4"></script>
|
||
<script src="/js/plan/plan-phasen.js?v=2"></script>
|
||
<script src="/js/plan/plan-files.js?v=4"></script>
|
||
<script src="/js/plan/plan-protokoll.js?v=20"></script>
|
||
<script src="/js/plan/plan-templates.js?v=11"></script>
|
||
<script src="/js/plan/plan-protokoll-todos.js?v=2"></script>
|
||
<script src="/js/plan/plan-mail-eintrag.js?v=4"></script>
|
||
<script src="/js/plan/plan-honorar.js?v=9"></script>
|
||
<!-- plan-leistung.js muss NACH plan-honorar.js stehen: es umhuellt dessen
|
||
invalidateHonorarConfig(), damit eine Aenderung der Anteile auch seinen
|
||
Zwischenspeicher verwirft. -->
|
||
<script src="/js/plan/plan-leistung.js?v=1"></script>
|
||
<script src="/js/plan/plan-settings.js?v=14"></script>
|
||
<script src="/js/plan/plan-schedule.js?v=6"></script>
|
||
<script src="/js/tabs-auth.js?v=17"></script>
|
||
<script src="/js/pdf.js?v=31"></script>
|
||
<script src="/js/backup.js?v=2"></script>
|
||
<script src="/js/import.js?v=1"></script>
|
||
<script src="/js/init.js?v=14"></script>
|
||
</body>
|
||
</html> |