// ----------------------------- // Projektübersicht — globale Konstanten & Zustand // ----------------------------- // Diese Datei wird als Erstes von App.html geladen und // legt alle zentralen Konstanten (Leistungsphasen, Rollen, Gruppen, Abteilungen, // Farben, Passwort-Hash) sowie den globalen In-Memory-Zustand (db, currentTab) // fest. Sie stellt keine Funktionen, sondern nur Daten bereit, auf die alle // weiteren Module (js/store.js, js/board/board.js, js/gantt.js, js/plan/*, ...) // zugreifen. Auf dem Plan-Gantt lädt zusätzlich js/plan/plan-state.js Zustand // für die Planungsseite. DEPARTMENTS/DEFAULT_DEPARTMENT bleiben trotz Wegfall // der Abteilungstrennung erhalten — sie werden weiterhin für die // Benutzerverwaltung (users.department, Profil-Anzeige) gebraucht. // Konstanten: Leistungsphasen, Rollen und LocalStorage-Key // LPHS: alle Leistungsphasen in fachlicher Reihenfolge. "W+M" steht bewusst // AM ENDE und ist keine HOAI-Phase, sondern eine eigene Sammelkategorie — // Stellen, die nur die echten Leistungsphasen brauchen (amtierende Phase, // Aufwandsmodell), filtern deshalb gezielt "W+M" heraus statt nach Index zu // schneiden. const LPHS = ["LPH 1","LPH 2","LPH 3","LPH 4","LPH 5","LPH 6","LPH 7","LPH 8","LPH 9", "W+M"]; // Spalten für Leistungsphasen const ROLES = ["Planung","Projektleitung","Bauüberwachung"]; // Gruppierung der Personen const GROUP_ORDER = ["Aktive Projekte","Kleine Projekte","In Anbahnung","Allgemein"]; // Reihenfolge der Projektgruppen const STORAGE = "projuebersicht_v1"; // Durch lokale UI-State-Persistenz genutzter Key // DEFAULT_DEPARTMENT: Fallback-Abteilung beim ersten Start const DEFAULT_DEPARTMENT = 'ELT'; // DEFAULT_TAB: Tab, der beim ersten Start angezeigt wird ('dashboard' = Meine // Aufgaben, das persönliche Dashboard — Projektboard bleibt über die // Navigation vollständig erreichbar, ist aber nicht mehr die Startansicht) const DEFAULT_TAB = 'dashboard'; // APP_SETTINGS: die firmenweiten Einstellungen (GET /app-settings). Sie // waren bis zum Ausbau der Einstellungsseite Konstanten im Quelltext — // Aufwandsvorlage, Ampel-Schwellen, Balkenskala, Arbeitsstunden je Tag — und // verschieben allesamt Zahlen, die alle Nutzer sehen. Deshalb sind sie jetzt // firmenweit gepflegt (Admin) statt im Code verteilt. // // Die Werte hier sind die VORGABEN und zugleich die Rückfallebene: Sie // entsprechen APP_SETTING_DEFS in server.js und gelten, solange // loadAppSettings() (js/store.js) noch nicht geantwortet hat oder der Abruf // scheitert. Ohne diese Kopie stünde vor dem ersten Rendern gar kein Wert // bereit und die Auslastung rechnete mit undefined. let APP_SETTINGS = { stundensatz: 135, arbeitsstundenProTag: 8, vollzeitStundenProWoche: 40, auslastungSchwelleGelb: 0.75, auslastungSchwelleRot: 1.0, // referenzHonorar/verteilzeitraumWochen fehlten hier, obwohl // berechneAuslastung() (js/load/load.js) beide liest. Die Aufrufstellen // fingen das mit eigenen Rückfallwerten ab (|| 250000, || 12) — die Vorgabe // stand damit an drei Stellen, und eine im Admin-Bereich geänderte // Server-Vorgabe wäre an den Rückfallwerten vorbeigelaufen. Werte identisch // zu APP_SETTING_DEFS (server.js), wie bei allen Schlüsseln hier. referenzHonorar: 250000, verteilzeitraumWochen: 12, aufwandVorlage: { 'LPH 1': { Planung: 0.4, Projektleitung: 0.2, Bauüberwachung: 0 }, 'LPH 2': { Planung: 0.4, Projektleitung: 0.2, Bauüberwachung: 0 }, 'LPH 3': { Planung: 1.0, Projektleitung: 0.3, Bauüberwachung: 0 }, 'LPH 4': { Planung: 0.0, Projektleitung: 0.3, Bauüberwachung: 0 }, 'LPH 5': { Planung: 1.0, Projektleitung: 0.4, Bauüberwachung: 0 }, 'LPH 6': { Planung: 0.2, Projektleitung: 0.7, Bauüberwachung: 0.3 }, 'LPH 7': { Planung: 0.1, Projektleitung: 0.3, Bauüberwachung: 0.5 }, 'LPH 8': { Planung: 0, Projektleitung: 0.2, Bauüberwachung: 1.0 }, 'LPH 9': { Planung: 0, Projektleitung: 0, Bauüberwachung: 0 } }, autoLockAktiv: 1, feiertagBundesland: 'NW', maxBackups: 10, sessionTage: 30, loginMaxVersuche: 10, loginSperreMinuten: 10 }; // HONORAR_ANTEILE: firmenweite HOAI-Anteile je Leistungsphase in Prozent // ({ 'LPH 1': 2, … }), gefüllt aus GET /honorar-config beim Start // (loadAppSettings() in js/store.js). Sie sind die Brücke vom Honorar eines // Projekts zum Aufwand einer einzelnen Phase und damit Grundlage der // Auslastungsrechnung in Stunden (berechneAuslastung(), js/load/load.js). // Gepflegt werden sie im Admin-Bereich unter „Honorar"; der Projektbereich // holt sie sich für den Leistungsstand weiterhin selbst (plan-leistung.js), // weil er sie auch nach einer Änderung im laufenden Betrieb neu braucht. // Leer = nicht geladen: Die Auslastung weist dann eine Lücke aus, statt mit // erfundenen Anteilen zu rechnen. let HONORAR_ANTEILE = {}; // HOLIDAYS: Feiertage und sonstige arbeitsfreie Tage als Menge von // ISO-Datumsangaben ("2026-12-25"), gefüllt aus GET /holidays. Wirkt in // workdaysBetween()/addWorkdays() (js/utils.js) und damit in der // Terminplan-Berechnung aus dem Honorar und in der Ressourcen-Auslastung. let HOLIDAYS = new Set(); // HOLIDAY_NAMES: Datum -> Bezeichnung, nur für die Pflege-Liste im Bereich // „Admin" (die Rechnung braucht allein die Menge oben). let HOLIDAY_NAMES = {}; // GERMAN_STATES: Kürzel -> Name der Bundesländer, vom Server mitgeliefert // (GET /holidays). Er ist die eine Stelle, an der die Liste steht; sie wird // nur für die Auswahl beim Berechnen der gesetzlichen Feiertage gebraucht. let GERMAN_STATES = {}; // Farbpalette für GANTT-Balken (eine pro Projekt, wiederholend) const GANTT_COLORS = ['#0071e3','#248a3d','#b25000','#7c3aed','#d70015','#0ca678','#d9480f','#5f3dc4']; // saveSyncTimer: Handle für den debounced Speicher-Timer (Sync auf Server) let saveSyncTimer = null; // In-memory Datenbank: Sitzungen (weeks) und aktuell ausgewählte Sitzung. // globalPeople ist ein flaches Array (keine Abteilungstrennung mehr). let db = { weeks: [], currentWeek: null, globalPeople: [], // wochenListe: ALLE Sitzungen schlank (id, label, date, locked, updatedAt) — // db.weeks hält nur die geladenen (siehe SITZUNGEN_START, js/store.js). wochenListe: [], // sandboxWeek: Platzhalter bis zum ersten erfolgreichen load() (js/store.js) // befüllt ihn — leer statt undefined, damit ein Zugriff vor dem Laden nicht // crasht, sondern nur eine leere Planung-Liste zeigt. sandboxWeek: { id: 'sandbox', label: '__sandbox__', date: '', people: [], projects: [], attendance: {}, locked: false, updatedAt: '' } }; // currentTab: aktuell aktiver Tab der Hauptseite (z. B. 'board') let currentTab = DEFAULT_TAB; // previousTab: der davor angezeigte Bereich. Gebraucht vom "Schließen"-Knopf // des Projektbereichs (closePlanPage() in js/gantt.js), der damit dorthin // zurückführt, von wo aus das Projekt geöffnet wurde — früher erledigte das // die Browser-Historie, weil der Projektbereich ein eigenes Dokument war. let previousTab = DEFAULT_TAB; // DEPARTMENTS: Auflistung der gültigen Abteilungen mit Anzeigename und Icon // (nur noch für users.department/Benutzerverwaltung relevant, keine // App-weite Datentrennung mehr) const DEPARTMENTS = { ELT: { label: 'Elektro', icon: '' }, HLS: { label: 'HLS', icon: '' }, ARCH: { label: 'Architektur', icon: '' } }; // SANDBOX_WEEK_LABEL: die eine, dauerhaft reservierte "Sitzung", in der alle // Planung-Terminpläne liegen (CLAUDE.md, "Planung — projektunabhängige // Terminplan-Sandbox"). MUSS mit der gleichnamigen Konstante in server.js // übereinstimmen — kein gemeinsames Modulsystem zwischen Server und Client. const SANDBOX_WEEK_LABEL = '__sandbox__'; // TAB_SLUGS / TAB_BY_SLUG: sprechende URL-Abschnitte für die Tabs, damit jeder // Tab eine eigene Seite hat (z. B. "/Auslastung"). Wird von js/ui.js // (applyUrlRoute/updateRouteUrl) und dem Server genutzt. const TAB_SLUGS = { board: 'Projektübersicht', wochenbericht: 'Wochenbericht', load: 'Auslastung', abwesenheit: 'Abwesenheiten', people: 'Projektbeteiligte', planung: 'Planung', planchecker: 'PlanChecker', dashboard:'MeineAufgaben', wand: 'Wandtafel', settings: 'Einstellungen', admin: 'Admin' }; const TAB_BY_SLUG = Object.fromEntries(Object.entries(TAB_SLUGS).map(([k,v]) => [v,k])); // Der Bereich "Projekte" ist in der Projektliste aufgegangen (siehe // renderBoard() in js/board/board.js). Sein Adressabschnitt bleibt gültig und // führt dorthin, damit weitergegebene Links nicht ins Leere zeigen — er wird // aber nicht mehr erzeugt, weil er in TAB_SLUGS fehlt. TAB_BY_SLUG['Projekte'] = 'board'; // PLAN_SLUG: URL-Schlüssel des Projektbereichs (Bereich #plan von App.html), // im gleichen Deep-Link-Schema wie die Tabs: "/Projektplan/", // dazu ?weekLabel=…&projNr=… als Parameter. Steht nicht in TAB_SLUGS, weil // die Adresse das geöffnete Projekt mitträgt und deshalb nicht fest ist — // gebaut wird sie in updateRouteUrl() (js/ui.js). const PLAN_SLUG = 'Projektplan';