Files
projektuebersicht/public/js/globals.js
T
erikandClaude Sonnet 5 6ba93b2a92 Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff
Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker-
Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn
gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js +
@napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und
WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste
Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter
bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der
gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im
Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome
blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt
pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit
abweichendem Modell-Katalog.

Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der
Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in
aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen.

Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der
Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen
Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die
beauftragten Leistungsphasen.

Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu
strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde
anschließend auf den gesamten App-weiten Dark Mode übernommen.

Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 14:01:41 +02:00

166 lines
8.7 KiB
JavaScript

// -----------------------------
// 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: [],
// 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',
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/<Projektname>",
// 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';