diff --git a/.DS_Store b/.DS_Store index acd0341..fb9a51a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/AGENTS.md b/AGENTS.md index b48b50c..7683395 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,7 +30,7 @@ user-facing text. ├── .vscode/extensions.json ├── public/ │ ├── Test.html # main app (served at /) -│ ├── js/ # main app modules (classic scripts, global fns) +│ ├── js/ # shared modules (classic scripts, global fns) │ │ ├── globals.js # Konstanten (LPHS, ROLES, LPH-Farben, DEPARTMENTS) + db/currentDepartment │ │ ├── utils.js # esc, uid, Datum-/Differenz-Helfer, personIdForName, weekSortKey-Quelle │ │ ├── ui.js # LocalStorage-UI-State, Header-Collapse, Theme @@ -39,15 +39,19 @@ user-facing text. │ │ ├── server.js # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl │ │ ├── store.js # save/syncAllToServer, switchDepartment, load, seed │ │ ├── weeks.js # Wochen-Auswahl, -Erzeugung, -Klonen, Auto-Lock, -Löschung -│ │ ├── board.js # Projektboard-Rendering, Status-Editor, Anwesenheit, Zuordnung -│ │ ├── people.js # Personen-Tab-Rendering + Drag-and-Drop + Abwesenheiten -│ │ ├── projects.js # Projekte-Tab-Rendering + Drag-and-Drop -│ │ ├── load.js # Auslastungs-Tab, LP-Modal, Neues-Projekt-Modal │ │ ├── gantt.js # GANTT-Diagramm (buildGanttHtml, openProjectPlan), geshared │ │ ├── showmode.js # Show-Mode (Präsentationsmodus) │ │ ├── tabs-auth.js # showTab, confirmPassword (SHA256-Passwortschutz) │ │ ├── pdf.js # PDF-Export (pdfmake, A3 quer) │ │ └── init.js # renderAll/initUiState + Seiteneinstieg (nur Test.html) +│ │ ├── board/ # tab-specific: Projektboard +│ │ │ └── board.js # Projektboard-Rendering, Status-Editor, Anwesenheit, Zuordnung +│ │ ├── people/ # tab-specific: Projektbeteiligte +│ │ │ └── people.js # Personen-Tab-Rendering + Drag-and-Drop + Abwesenheiten +│ │ ├── projects/ # tab-specific: Projekte +│ │ │ └── projects.js # Projekte-Tab-Rendering + Drag-and-Drop +│ │ ├── load/ # tab-specific: Auslastung +│ │ │ └── load.js # Auslastungs-Tab, LP-Modal, Neues-Projekt-Modal │ ├── project-plan.html # Gantt/TODO planning view │ ├── js/plan/ # planning modules (nur von project-plan.html geladen) │ │ ├── plan-state.js # PlanApp-Zustand, Datenladepath, savePlan, Toasts @@ -79,6 +83,16 @@ docker compose up --build # Docker variant (compose file is at repo root) ## Server API (`server.js`) - `GET /` → `Test.html`; `GET /health`. +- Deep links per department + tab: `GET //` → `Test.html`, e.g. + `/HLS/Auslastung` or `/ELT/Projektübersicht`. Tab slugs (`TAB_SLUGS` in + `js/globals.js`) are `Projektübersicht|Auslastung|Projekte|Projektbeteiligte`; + anything else returns 404. Exception: `GET //Projektplan(/)?` + (`PLAN_SLUG`) serves `project-plan.html`, the GANTT view, with + `weekLabel|projNr` as query params (the project name is a readable path + segment only; identification happens via weekLabel + projNr). The frontend + reads/writes these routes via `applyUrlRoute()`/`updateRouteUrl()` in + `js/ui.js` and all static assets in the HTML use **absolute** paths + (`/js/...`, `/styles.css`) so they resolve on nested routes. - `GET /load-all?department=` → `{sessions:[{filename,content}], people}` with `_people.csv` content in `people`; sessions sorted by filename. - `POST /save-csv {filename, csv, department}` → writes UTF-8 BOM CSV. @@ -112,8 +126,9 @@ docker compose up --build # Docker variant (compose file is at repo root) them via ` @@ -36,7 +38,7 @@

Projektübersicht Elektro

- +
@@ -221,22 +223,22 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/js/board.js b/public/js/board/board.js similarity index 99% rename from public/js/board.js rename to public/js/board/board.js index 10b7735..ad12263 100644 --- a/public/js/board.js +++ b/public/js/board/board.js @@ -4,10 +4,10 @@ // Diese Datei rendert das Projektboard (Tab "Projektboard" in `Test.html`), // die zentrale Tabelle mit Projekten, Status-Kommentaren, Personen-Spalten // (Anwesenheit/Zuordnung) und Leistungsphasen in Prozent. Sie wird von -// `Test.html` nach `js/weeks.js` (vor `js/people.js`) geladen und stellt +// `Test.html` nach `js/weeks.js` (vor `js/people/people.js`) geladen und stellt // globale Funktionen wie `renderBoard()`, `statusFocus()`, `cycleAssign()`, // `editLph()` und `delProj()` bereit. Sie nutzt `currentWeek()`/ -// `isWeekLocked()` aus `js/weeks.js`, `lpActiveList()` aus `js/load.js`, +// `isWeekLocked()` aus `js/weeks.js`, `lpActiveList()` aus `js/load/load.js`, // `assignKey()` aus `js/utils.js` und `openProjectPlan()` aus `js/gantt.js`. // currentStatusEditor: aktuell fokussiertes contenteditable-Element des diff --git a/public/js/date-picker.js b/public/js/date-picker.js index b7ea271..ba2bc40 100644 --- a/public/js/date-picker.js +++ b/public/js/date-picker.js @@ -9,7 +9,7 @@ // und stellt den Kalender-Picker (Einzeldatum "openDatePicker" und Bereich // "openRangePicker") bereit. Der Zustand des offenen Popups liegt in der // Modulvariablen rangePickerState. Genutzt wird der Picker v. a. von -// js/load.js (LP-Bereiche), js/people.js (Abwesenheiten) und den +// js/load/load.js (LP-Bereiche), js/people/people.js (Abwesenheiten) und den // Plan-Modulen unter js/plan/* (z. B. GANTT-Datumsangaben in plan-modals.js). // rangePickerState: Zustand des aktuell geöffneten Picker-Popups (Optionen, diff --git a/public/js/gantt.js b/public/js/gantt.js index 67e7748..aa92673 100644 --- a/public/js/gantt.js +++ b/public/js/gantt.js @@ -2,9 +2,9 @@ // Projektübersicht — GANTT-Diagramm (gemeinsam genutzt) // ----------------------------- // Gemeinsames Modul für das GANTT-Diagramm: Es wird von Test.html (nach -// js/load.js) UND von project-plan.html geladen – dort zusammen mit globals, +// js/load/load.js) UND von project-plan.html geladen – dort zusammen mit globals, // utils, ui, date-picker, csv, server und den js/plan/*-Modulen, aber OHNE -// js/store.js, js/weeks.js, js/board.js usw. Es darf daher auf der Plan-Seite +// js/store.js, js/weeks.js, js/board/board.js usw. Es darf daher auf der Plan-Seite // keine Hauptseiten-spezifischen Funktionen voraussetzen; Funktionen, die // currentWeek()/isWeekLocked() aus js/weeks.js brauchen (renderGantt, // fitGanttRange, openProjectPlan), werden nur vom Hauptfenster aufgerufen. @@ -328,19 +328,20 @@ function buildGanttHtml(projects, start, end, options = {}){ // ---------- Projektplan-Navigation ---------- // openProjectPlan: öffnet project-plan.html in einem neuen Tab. Weil -// Projekt-IDs nicht persistent sind, werden department, Wochen-Label, Proj.-Nr. -// und -Name als URL-Parameter übergeben. Parameter: pid = Projekt-ID der -// aktuellen Woche. Kein Rückgabewert. +// Projekt-IDs nicht persistent sind, werden Wochen-Label und Proj.-Nr. als +// URL-Parameter übergeben; Abteilung und Projektname stehen im Pfad nach dem +// Schema "//Projektplan/". Parameter: pid = +// Projekt-ID der aktuellen Woche. Kein Rückgabewert. function openProjectPlan(pid){ const w = currentWeek(); if(!w) return; const p = w.projects.find(x => x.id === pid); if(!p) return; // IDs sind nicht persistent (werden bei jedem CSV-Laden neu erzeugt). - // Daher werden stabile Werte (Label, Projekt-Nr., Name) in der URL übergeben. - const url = new URL('project-plan.html', window.location.href); - url.searchParams.set('department', currentDepartment); + // Daher werden stabile Werte (Label, Projekt-Nr.) in der URL übergeben. + // Absoluter Pfad mit Abteilung und Projektname als Route-Segment, damit der + // Aufruf auch von Deep-Link-URLs wie "/HLS/Auslastung" funktioniert. + const url = new URL('/' + currentDepartment + '/' + PLAN_SLUG + '/' + encodeURIComponent(p.name || ''), window.location.origin); url.searchParams.set('weekLabel', w.label); url.searchParams.set('projNr', p.nr || ''); - url.searchParams.set('projName', p.name || ''); window.open(url.toString(), '_blank'); } diff --git a/public/js/globals.js b/public/js/globals.js index 627eea8..9797e52 100644 --- a/public/js/globals.js +++ b/public/js/globals.js @@ -5,7 +5,7 @@ // legt alle zentralen Konstanten (Leistungsphasen, Rollen, Gruppen, Abteilungen, // Farben, Passwort-Hash) sowie den globalen In-Memory-Zustand (db, // currentDepartment, currentTab) fest. Sie stellt keine Funktionen, sondern nur -// Daten bereit, auf die alle weiteren Module (js/store.js, js/board.js, +// 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. @@ -46,4 +46,19 @@ const DEPARTMENTS = { ELT: { label: 'Elektro', icon: '' }, HLS: { label: 'HLS', icon: '' }, ARCH: { label: 'Architektur', icon: '' } -}; \ No newline at end of file +}; + +// TAB_SLUGS / TAB_BY_SLUG: sprechende URL-Abschnitte für die Tabs, damit jede +// Abteilungs-/Tab-Kombination eine eigene Seite hat (z. B. "/HLS/Auslastung"). +// Wird von js/ui.js (applyUrlRoute/updateRouteUrl) und dem Server genutzt. +const TAB_SLUGS = { + board: 'Projektübersicht', + load: 'Auslastung', + projects:'Projekte', + people: 'Projektbeteiligte' +}; +const TAB_BY_SLUG = Object.fromEntries(Object.entries(TAB_SLUGS).map(([k,v]) => [v,k])); + +// PLAN_SLUG: URL-Schlüssel für die Projektplan-Seite (project-plan.html), im +// gleichen Deep-Link-Schema wie die Tabs: "//Projektplan". +const PLAN_SLUG = 'Projektplan'; \ No newline at end of file diff --git a/public/js/init.js b/public/js/init.js index 3c43ab6..19e33dd 100644 --- a/public/js/init.js +++ b/public/js/init.js @@ -8,8 +8,8 @@ // initUiState() und syncRangeDisplay() bereit und nutzt loadUiState()/ // loadTheme()/loadHeaderState() aus js/ui.js, switchDepartment() aus // js/store.js, showTab() aus js/tabs-auth.js sowie die Render-Funktionen -// renderBoard()/renderLoad()/renderProjects()/renderPeople() aus js/board.js, -// js/load.js, js/projects.js, js/people.js. +// renderBoard()/renderLoad()/renderProjects()/renderPeople() aus js/board/board.js, +// js/load/load.js, js/projects/projects.js, js/people/people.js. // renderAll: Convenience um UI-Elemente zu initialisieren function renderAll(){ @@ -24,10 +24,16 @@ function renderAll(){ // initUiState: stellt beim Start den UI-Zustand her (LocalStorage-UI-State, // Theme, Header-Status), setzt Titel und aktive Abteilungs-Schaltfläche und -// zeigt den ersten Tab an – passwortgeschützte Tabs ('projects'/'people') -// werden beim Start nie direkt aktiviert. Kein Rückgabewert. +// zeigt den ersten Tab an. Eine Deep-Link-URL (z. B. "/HLS/Auslastung") aus +// applyUrlRoute() übersteuert dabei Abteilung und Tab; passwortgeschützte Tabs +// öffnen das Passwort-Modal (showTab ohne skipAuth). Kein Rückgabewert. function initUiState(){ loadUiState(); + const route = applyUrlRoute(); + if(route && DEPARTMENTS[route.department]){ + currentDepartment = route.department; + currentTab = route.tab; + } loadTheme(); loadHeaderState(); document.querySelectorAll('.dept-tab').forEach(btn => { @@ -39,9 +45,8 @@ function initUiState(){ if(titleEl) titleEl.textContent = info.icon + ' Projektübersicht ' + info.label; const tab = ['board','load','projects','people'].includes(currentTab) ? currentTab : DEFAULT_TAB; - // Beim initialen Start niemals einen passwortgeschützten Tab direkt anzeigen. - const safeTab = (tab === 'projects' || tab === 'people') ? DEFAULT_TAB : tab; - showTab(safeTab, true); + // Geschützte Tabs ('projects'/'people') verlangen hier ihr Passwort. + showTab(tab); } // syncRangeDisplay: schreibt den aus zwei Datumsfeldern (startId/endId) diff --git a/public/js/load.js b/public/js/load/load.js similarity index 99% rename from public/js/load.js rename to public/js/load/load.js index 0b6a9f6..cc56181 100644 --- a/public/js/load.js +++ b/public/js/load/load.js @@ -6,7 +6,7 @@ // people → projects → load → gantt → …). Es stellt renderLoad(), das LP- // Aufwand-Modal und das Neues-Projekt-Modal bereit. Genutzt werden LPHS/ROLES // aus js/globals.js, currentWeek()/save() aus js/store.js, guardWeekLocked() -// aus js/weeks.js sowie peopleByRole()/assignKey() aus js/board.js/js/utils.js. +// aus js/weeks.js sowie peopleByRole()/assignKey() aus js/board/board.js/js/utils.js. // Die Auslastungs-Logik (berechneAuslastung, lpActiveList, faktorFormat, // pdfLoadAmpelColor, LOAD_BAR_MAX) wird zusätzlich von js/pdf.js für den // PDF-Export wiederverwendet. diff --git a/public/js/pdf.js b/public/js/pdf.js index 289861a..e32f511 100644 --- a/public/js/pdf.js +++ b/public/js/pdf.js @@ -8,10 +8,10 @@ // vor js/init.js); exportPDF() hängt am Export-Button von Test.html und nutzt // die per CDN eingebundene Bibliothek pdfmake (offline nicht verfügbar). Es // liest die aktuelle Woche über currentWeek() aus js/store.js, Personen über -// peopleByRole() aus js/board.js, Helfer wie assignKey()/sortGroups()/ +// peopleByRole() aus js/board/board.js, Helfer wie assignKey()/sortGroups()/ // formatWeekDate() aus js/utils.js sowie die Auslastungs-Berechnung // (berechneAuslastung, lpActiveList, faktorFormat, pdfLoadAmpelColor, -// LOAD_BAR_MAX) aus js/load.js. Das Modul stellt ausschließlich exportPDF() +// LOAD_BAR_MAX) aus js/load/load.js. Das Modul stellt ausschließlich exportPDF() // bereit. // Farbkonstanten des PDF-Layouts: Rollen-Hintergründe, neutrale bzw. diff --git a/public/js/people.js b/public/js/people/people.js similarity index 98% rename from public/js/people.js rename to public/js/people/people.js index 2912153..34397fd 100644 --- a/public/js/people.js +++ b/public/js/people/people.js @@ -4,12 +4,12 @@ // Diese Datei rendert den Personen-Tab (Tab "Projektbeteiligte" in `Test.html`) // und verwaltet die globale Personenliste pro Abteilung: Anlegen, Bearbeiten, // Löschen, Rollen-Auswahl, Drag-and-Drop-Sortierung sowie Urlaub/Abwesenheiten. -// Sie wird von `Test.html` nach `js/board.js` (vor `js/projects.js`) geladen +// Sie wird von `Test.html` nach `js/board/board.js` (vor `js/projects/projects.js`) geladen // und stellt globale Funktionen wie `addPerson()`, `delPerson()`, // `editPerson()`, `renderPeople()` und `addAbsence()` bereit. Genutzt wird sie -// u.a. von `js/board.js` (`renderBoard`), `js/gantt.js` und +// u.a. von `js/board/board.js` (`renderBoard`), `js/gantt.js` und // `js/plan/plan-modals.js`; die Sortier-Hilfe `getDragAfterElement()` liegt in -// `js/projects.js`. +// `js/projects/projects.js`. // addPerson: fügt eine neue Person der globalen Liste hinzu und // synchronisiert sie in die aktuelle Sitzung. diff --git a/public/js/plan/plan-state.js b/public/js/plan/plan-state.js index d3dc0e1..fed88d1 100644 --- a/public/js/plan/plan-state.js +++ b/public/js/plan/plan-state.js @@ -3,8 +3,9 @@ // ----------------------------- // Dieses Modul wird als erstes der plan-*-Module von public/project-plan.html // geladen und definiert window.PlanApp als gemeinsamen Zustand aller -// Projektplan-Module. Es liest die URL-Parameter (department, weekLabel, -// projNr, projName), über die das zu bearbeitende Projekt adressiert wird. +// Projektplan-Module. Es liest die URL-Route (Abteilung + Projektname, z. B. +// "/HLS/Projektplan/Gymnasium%20Hemer") und die URL-Parameter (weekLabel, +// projNr), über die das zu bearbeitende Projekt adressiert wird. // Nach DOMContentLoaded werden Theme, Toolbar-Buttons und die Sitzungsdaten // über die Server-API geladen. Die hier definierten Funktionen (savePlan, // isLocked, showToast u. a.) werden über app.* den anderen plan-Modulen @@ -33,10 +34,14 @@ window.PlanApp = { (function(){ const app = window.PlanApp; - app.department = app.params.get('department') || 'ELT'; + // Abteilung und Projektname aus dem Deep-Link-Pfad + // "//Projektplan/" lesen. Fallback auf die früheren + // Query-Parameter ?department=/&projName= für alte project-plan.html-Links. + const routeMatch = window.location.pathname.match(/^\/([^/]+)\/(?:Projektplan|project-plan\.html)(?:\/(.+))?\/?$/); + app.department = routeMatch ? routeMatch[1] : (app.params.get('department') || 'ELT'); app.weekLabel = app.params.get('weekLabel'); app.projNr = app.params.get('projNr') || ''; - app.projName = app.params.get('projName') || ''; + app.projName = routeMatch && routeMatch[2] ? decodeURIComponent(routeMatch[2]) : (app.params.get('projName') || ''); // init: Einstiegspunkt nach DOMContentLoaded. Übernimmt das Theme aus // localStorage/System, prüft die URL-Parameter und bricht mit Fehlermeldung diff --git a/public/js/projects.js b/public/js/projects/projects.js similarity index 96% rename from public/js/projects.js rename to public/js/projects/projects.js index 8d494c5..6efdb97 100644 --- a/public/js/projects.js +++ b/public/js/projects/projects.js @@ -4,12 +4,12 @@ // Diese Datei rendert den Projekte-Tab (Tab "Projekte" in `Test.html`) als // sortierbare Listenansicht: Projekte werden nach Gruppe ("Aktive Projekte", // "Kleine Projekte" usw.) gruppiert, inline bearbeitbar und per -// Drag-and-Drop umsortiert. Sie wird von `Test.html` nach `js/people.js` -// (vor `js/load.js`) geladen und stellt globale Funktionen wie +// Drag-and-Drop umsortiert. Sie wird von `Test.html` nach `js/people/people.js` +// (vor `js/load/load.js`) geladen und stellt globale Funktionen wie // `renderProjects()`, `editProjectField()`, `editProjectActiveLP()` sowie die // Drag-and-Drop-Handler und die Sortier-Hilfe `getDragAfterElement()` bereit. -// Letztere wird auch von `js/people.js` (Personen-Sortierung) genutzt; -// `renderBoard()` aus `js/board.js` wird nach Änderungen aktualisiert. +// Letztere wird auch von `js/people/people.js` (Personen-Sortierung) genutzt; +// `renderBoard()` aus `js/board/board.js` wird nach Änderungen aktualisiert. // delProjectFromList: löscht ein Projekt (per Bestätigungsdialog) in der // Listen-Ansicht. Parameter `id`: Projekt-ID. Keine Rückgabe; bei gesperrter @@ -133,7 +133,7 @@ function handleDragEnd(event){ // gezogene Zeile bei Mausposition `y` abgelegt werden soll (Suche nach dem // Element mit der kleinsten negativen Distanz zur Mitte). Rückgabe: // Ziel-Element oder undefined (→ ans Ende anfügen). Wird auch von -// `js/people.js` für die Personen-Sortierung verwendet. +// `js/people/people.js` für die Personen-Sortierung verwendet. function getDragAfterElement(container, y){ const draggableElements = [...container.querySelectorAll('.person-list-item:not([style*="opacity"])')]; return draggableElements.reduce((closest, child) => { diff --git a/public/js/showmode.js b/public/js/showmode.js index 3058495..c67dcd4 100644 --- a/public/js/showmode.js +++ b/public/js/showmode.js @@ -7,7 +7,7 @@ // und Auslastung) und zeigt sie in einem Vollbild-Overlay. Genutzt werden // showTab() aus js/tabs-auth.js, switchDepartment() aus js/store.js, // setHeaderCollapsed() aus js/ui.js sowie renderBoard()/renderLoad() aus -// js/board.js/js/load.js. Der gesamte Lauf-Zustand liegt in den +// js/board/board.js/js/load/load.js. Der gesamte Lauf-Zustand liegt in den // Modulvariablen am Dateikopf. // showModeActive: true, solange der Show Mode läuft diff --git a/public/js/store.js b/public/js/store.js index cb9d5f8..39f7232 100644 --- a/public/js/store.js +++ b/public/js/store.js @@ -7,7 +7,7 @@ // Sie wird von `Test.html` nach `js/csv.js` und `js/server.js` geladen und stellt // die globalen Funktionen `save()`, `syncAllToServer()`, `switchDepartment()`, // `load()`, `seed()` und `demoProj()` bereit. Aufrufer sind u.a. `js/weeks.js`, -// `js/board.js`, `js/people.js`, `js/projects.js` und `js/load.js`; das Speichern +// `js/board/board.js`, `js/people/people.js`, `js/projects/projects.js` und `js/load/load.js`; das Speichern // ist über `saveSyncTimer` (in `js/globals.js` deklariert) um 500 ms debounced. // save: synchronisiert die aktuellen Daten als CSV-Dateien zum Server. @@ -62,6 +62,7 @@ function switchDepartment(dept){ if(!DEPARTMENTS[dept]) return; currentDepartment = dept; saveUiState(); + updateRouteUrl(dept, currentTab); // UI-Status der Abteilungs-Tabs aktualisieren document.querySelectorAll('.dept-tab').forEach(btn => { @@ -118,11 +119,11 @@ function load(){ } console.log(`Server-Load erfolgreich: ${db.weeks.length} Sitzung(en).`); } - // Geschützte Tabs dürfen nicht ohne Passwort angezeigt werden, auch wenn - // sie im UI-State gespeichert waren. - if(currentTab === 'projects' || currentTab === 'people') currentTab = DEFAULT_TAB; + // Geschützte Tabs ('projects'/'people') dürfen nur mit Passwort angezeigt + // werden. showTab() öffnet dafür automatisch das Passwort-Modal – auch + // wenn der Tab per Deep-Link-URL (z. B. "/ELT/Projekte") gewählt wurde. renderAll(); - showTab(currentTab, true); + showTab(currentTab); }) .catch(err => { console.error('Laden vom Server fehlgeschlagen:', err); diff --git a/public/js/tabs-auth.js b/public/js/tabs-auth.js index 1d04c06..9618be8 100644 --- a/public/js/tabs-auth.js +++ b/public/js/tabs-auth.js @@ -9,8 +9,8 @@ // ausstehende Aktion wird in requestedAction (js/globals.js) gemerkt – davon // profitieren auch die Passwort-Gates in js/weeks.js (Löschen/Entsperren von // Sitzungen). Gerendert werden beim Tab-Wechsel renderBoard()/renderLoad()/ -// renderProjects()/renderPeople() aus js/board.js, js/load.js, js/projects.js, -// js/people.js. +// renderProjects()/renderPeople() aus js/board/board.js, js/load/load.js, js/projects/projects.js, +// js/people/people.js. // showTab: wechselt zwischen Ansichten; für sensible Tabs wird ein Passwort verlangt function showTab(t, skipAuth = false){ @@ -24,6 +24,7 @@ function showTab(t, skipAuth = false){ } currentTab = t; saveUiState(); + updateRouteUrl(currentDepartment, t); ['board','projects','load','people'].forEach(x=>{ const el = document.getElementById(x); diff --git a/public/js/ui.js b/public/js/ui.js index a317f2d..79719d3 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -33,6 +33,36 @@ function loadUiState(){ } } +// ---------- Deep-Link-Routen (Abteilung + Tab als URL) ---------- + +// applyUrlRoute: wertet einen Deep-Link "//" aus der URL aus, +// z. B. "/HLS/Auslastung" oder "/ELT/Projektübersicht". Gültige Routen liefern +// { department, tab }, alles andere (auch file://-Pfade) liefert null. +function applyUrlRoute(){ + try { + const m = window.location.pathname.match(/^\/([^/]+)\/([^/]+)\/?$/); + if(!m) return null; + const dept = m[1]; + const tab = TAB_BY_SLUG[decodeURIComponent(m[2])]; + if(!DEPARTMENTS[dept] || !tab) return null; + return { department: dept, tab }; + } catch (err) { + return null; + } +} + +// updateRouteUrl: schreibt Abteilung + aktiven Tab als sprechende URL in die +// Adresszeile (z. B. "/HLS/Auslastung"), ohne die Seite neu zu laden. Nur über +// HTTP (nicht per file://) und nur bei bekannter Abteilung/Tab. Keine Rückgabe. +function updateRouteUrl(dept, tab){ + try { + if(!window.location.protocol || window.location.protocol.indexOf('http') !== 0) return; + const slug = TAB_SLUGS[tab]; + if(!slug || !DEPARTMENTS[dept]) return; + window.history.replaceState(null, '', '/' + dept + '/' + slug); + } catch (err) {} +} + // ---------- Kollabierbarer Header ---------- // HEADER_COLLAPSED_KEY: LocalStorage-Key für den Einklapp-Zustand des Header-Menüs diff --git a/public/js/utils.js b/public/js/utils.js index 92205fa..82c65e3 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -5,7 +5,7 @@ // ID-, Sortier- und Datumsfunktionen) und wird nach js/globals.js von beiden // HTML-Einstiegspunkten geladen. Sie stellt ausschließlich globale Funktionen // ohne eigenen Zustand bereit; genutzt wird sie praktisch von allen Modulen, -// u. a. js/csv.js, js/board.js, js/load.js sowie den Plan-Modulen unter js/plan/* +// u. a. js/csv.js, js/board/board.js, js/load/load.js sowie den Plan-Modulen unter js/plan/* // (z. B. für ISO-Datumsformate und Werktage) und von js/gantt.js. // departmentParam: gibt ein Query-String-Fragment fuer die aktuelle Abteilung zurueck. diff --git a/public/js/weeks.js b/public/js/weeks.js index fa21493..acbef08 100644 --- a/public/js/weeks.js +++ b/public/js/weeks.js @@ -6,7 +6,7 @@ // Sie wird von `Test.html` nach `js/store.js` geladen und stellt globale // Funktionen wie `currentWeek()`, `switchWeek()`, `newWeek()`, `cloneWeek()`, // `applyAutoLock()`, `guardWeekLocked()` und `deleteWeek()` bereit. Genutzt wird -// sie u.a. von `js/board.js` (`renderBoard`, `isWeekLocked`), `js/store.js` +// sie u.a. von `js/board/board.js` (`renderBoard`, `isWeekLocked`), `js/store.js` // (`load`) und `js/tabs-auth.js` (Passwort-Aktionen für Entsperren/Löschen). // refreshWeekSelect: aktualisiert das Dropdown `weekSelect` mit allen Sitzungen diff --git a/public/project-plan.html b/public/project-plan.html index 3ec2da7..9a79fe7 100644 --- a/public/project-plan.html +++ b/public/project-plan.html @@ -3,8 +3,9 @@ PROJEKTPLAN-ANSICHT: zeigt für ein einzelnes Projekt ein GANTT-Diagramm mit TODO- und Meilenstein-Zeilen, Abhängigkeiten, Zoom, kritischem Pfad, Auto-Schedule, Ressourcen- Auslastung und Abwesenheiten der Projektbeteiligten. Die Seite wird über openProjectPlan() - in js/gantt.js in einem neuen Tab geöffnet und über URL-Parameter (Abteilung, Woche, - Projekt-Nr./Name) adressiert. Sie lädt styles.css und ausschließlich die gemeinsamen + in js/gantt.js in einem neuen Tab geöffnet und per Deep-Link-Route + "//Projektplan/" adressiert; Woche und Projekt-Nr. + kommen als URL-Parameter (weekLabel, projNr). Sie lädt styles.css und ausschließlich die gemeinsamen Module js/globals.js, js/utils.js, js/ui.js, js/date-picker.js, js/csv.js, js/server.js und js/gantt.js sowie danach die Plan-Module js/plan/plan-state.js, js/plan/plan-build-gantt.js, js/plan/plan-gantt.js, js/plan/plan-drag.js, @@ -18,7 +19,7 @@ Projektplan - +