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