// ----------------------------- // Projektbereich — Bereichs-Navigation & Dashboard // ----------------------------- // Der Projektbereich ist der EIGENE BEREICH eines Projekts — seit dem Umbau // kein eigenes Dokument mehr, sondern der Bereich #plan der Hauptseite // App.html. Er gliedert sich in sieben Unterbereiche, zwischen denen die // Leiste .plan-nav umschaltet (Markup in App.html): // // dashboard — Kennzahlen, Sitzungskommentar, Leistungsphasen- // Fortschritt, nächste Termine, Team, Kompakt-Karten // terminplan — GANTT mit Werkzeugleiste und Ressourcen-Auslastung // aufgaben — Aufgaben- und Meilensteinliste (js/plan/plan-tasks.js) // leistung — Leistungsstand & Abrechnung (js/plan/plan-leistung.js) // dateien — Datei-Upload/-Freigabe (js/plan/plan-files.js) // protokolle — Besprechungsprotokolle (js/plan/plan-protokoll.js) // einstellungen — Projektangaben, Honorar, beauftragte Leistungsphasen, // Beteiligte (js/plan/plan-settings.js) // // Alle Bereiche liegen gleichzeitig im DOM und werden nur über die Klasse // .is-active ein- bzw. ausgeblendet. Das ist Absicht: die vorhandenen // Render-Funktionen (renderPlanInfo, renderResourceLoad, renderProjectFiles …) // befüllen ihre Elemente unabhängig davon, ob der Bereich gerade sichtbar ist, // und brauchten für den Umbau keine Änderung. // // Der gewählte Bereich steht im URL-Fragment (z. B. …/Projektplan/Haus%20A // ?weekLabel=…&projNr=…#aufgaben), damit ein Link auf einen bestimmten Bereich // verschickt werden kann. Ein unbekanntes Fragment fällt auf "dashboard" // zurück. // // Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp // (plan-state.js). (function(){ const app = window.PlanApp; // PLAN_SECTIONS: gültige Bereichsschlüssel in der Reihenfolge der // Navigationsleiste. Muss zu den data-section-Attributen in // App.html passen. const PLAN_SECTIONS = ['dashboard', 'terminplan', 'aufgaben', 'leistung', 'dateien', 'protokolle', 'einstellungen']; // showPlanSection: schaltet auf einen Bereich um, hebt den zugehörigen // Knopf hervor und rendert den Inhalt des Bereichs neu. Das Neu-Rendern ist // beim Terminplan zwingend: die Abhängigkeitslinien im GANTT werden aus // Pixel-Positionen berechnet, die in einem ausgeblendeten Container alle 0 // wären. Parameter: key (Bereichsschlüssel). Keine Rückgabe. window.showPlanSection = function(key){ if(PLAN_SECTIONS.indexOf(key) === -1) key = 'dashboard'; app.section = key; PLAN_SECTIONS.forEach(k => { const sec = document.getElementById('planSection-' + k); if(sec) sec.classList.toggle('is-active', k === key); }); document.querySelectorAll('#planNav .plan-nav-item').forEach(btn => { btn.classList.toggle('is-active', btn.dataset.section === key); }); // Bereich im URL-Fragment festhalten (teilbarer Link), ohne einen // History-Eintrag je Klick zu erzeugen. try { history.replaceState(null, '', location.pathname + location.search + '#' + key); } catch(e) { /* z. B. file:// — dann bleibt die Adresse eben unverändert */ } if(!app.project) return; if(key === 'terminplan' && typeof renderPlanGantt === 'function') renderPlanGantt(); if(key === 'aufgaben' && typeof renderPlanTasks === 'function') renderPlanTasks(); if(key === 'leistung' && typeof renderPlanLeistung === 'function') renderPlanLeistung(); if(key === 'protokolle' && typeof renderPlanProtokolle === 'function') renderPlanProtokolle(); if(key === 'einstellungen' && typeof renderPlanSettings === 'function') renderPlanSettings(); if(key === 'dashboard' && typeof window.renderPlanDashboard === 'function') window.renderPlanDashboard(); }; // initPlanSections: wählt beim Seitenaufbau den Bereich aus dem // URL-Fragment (sonst "dashboard"). Wird von renderPlanPage() // (plan-state.js) aufgerufen, also erst wenn Woche und Projekt geladen // sind. Keine Parameter, keine Rückgabe. function initPlanSections(){ const fromHash = (location.hash || '').replace(/^#/, ''); // Ein Link auf #dateien/#leistung/#protokolle eines Sandbox-Plans (dessen // Knöpfe ausgeblendet sind, siehe renderPlanPage(), plan-state.js) fiele // sonst auf einen Bereich zurück, der zwar existiert, aber keinen // sichtbaren Weg mehr dorthin hätte. Protokolle hängen wie Dateien an // project.nr, den ein Sandbox-Plan nicht hat. const gesperrtFuerSandbox = app.isSandboxPlan() && (fromHash === 'dateien' || fromHash === 'leistung' || fromHash === 'protokolle'); window.showPlanSection(!gesperrtFuerSandbox && PLAN_SECTIONS.indexOf(fromHash) !== -1 ? fromHash : 'dashboard'); } // hidePlanNav: blendet die Bereichsleiste aus. Wird von plan-state.js // gerufen, wenn gar kein Projekt geladen werden konnte (fehlende // URL-Parameter, unbekannte Sitzung, Ladefehler) — dann führte jeder // Bereich nur in eine leere Maske. Keine Parameter, keine Rückgabe. function hidePlanNav(){ const nav = document.getElementById('planNav'); if(nav) nav.style.display = 'none'; } // showPlanNav: Gegenstück zu hidePlanNav(). Nötig, seit der Projektbereich // ein Bereich der Hauptseite ist und nacheinander mehrere Projekte zeigen // kann: Nach einem Fehlschlag (hidePlanNav) muss die Leiste beim nächsten, // erfolgreichen openPlanView() wieder erscheinen. Auf der früheren // eigenständigen Seite war das unnötig, weil jeder Aufruf eine frische // Seite lud. Keine Parameter, keine Rückgabe. function showPlanNav(){ const nav = document.getElementById('planNav'); if(nav) nav.style.display = ''; } // upcomingEntries: sammelt die nächsten Termine des Projekts — offene // Aufgaben mit Endtermin und Meilensteine mit Datum — in einer gemeinsamen, // chronologisch sortierten Liste. Überfällige Einträge stehen zuerst, damit // sie im Dashboard nicht untergehen. Rückgabe: Array von // { kind, title, date, overdue }. function upcomingEntries(){ const plan = app.ensurePlanLocal(); const today = new Date(); today.setHours(0, 0, 0, 0); const rows = []; (plan.todos || []).forEach(t => { if(t.done) return; const d = parseDateISO(t.end); if(!d) return; rows.push({ kind: 'Aufgabe', title: t.title || 'Aufgabe', date: d, overdue: d < today }); }); (plan.milestones || []).forEach(m => { const d = parseDateISO(m.date); if(!d) return; rows.push({ kind: 'Meilenstein', title: m.name || 'Meilenstein', date: d, overdue: d < today }); }); rows.sort((a, b) => a.date - b.date); return rows; } // renderPlanDashboard: füllt die beiden eigenen Karten des Dashboards // ("Nächste Termine" und "Team"). Die übrigen Inhalte des Bereichs // stammen aus bereits vorhandenen Render-Funktionen: die KPI-Kacheln aus // renderPlanKpis() (plan-gantt.js), Kommentar und Leistungsphasen aus // renderPlanInfo() (plan-info.js), die Kompakt-Karten aus // renderResourceLoad()/renderProjectFiles()/renderPlanHistory(). // Keine Parameter, keine Rückgabe. window.renderPlanDashboard = function(){ if(!app.project) return; const upcomingEl = document.getElementById('planDashUpcoming'); if(upcomingEl){ const rows = upcomingEntries().slice(0, 8); upcomingEl.innerHTML = rows.length === 0 ? '