von single Page hin zur mehreren Seiten geändert
This commit is contained in:
1 parent
8c94fc91f6
commit
31c4bd47cd
36 files changed
+207
-97
No files matched your search
@@ -30,7 +30,7 @@ user-facing text.
|
|||||||
├── .vscode/extensions.json
|
├── .vscode/extensions.json
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── Test.html # main app (served at /)
|
│ ├── 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
|
│ │ ├── globals.js # Konstanten (LPHS, ROLES, LPH-Farben, DEPARTMENTS) + db/currentDepartment
|
||||||
│ │ ├── utils.js # esc, uid, Datum-/Differenz-Helfer, personIdForName, weekSortKey-Quelle
|
│ │ ├── utils.js # esc, uid, Datum-/Differenz-Helfer, personIdForName, weekSortKey-Quelle
|
||||||
│ │ ├── ui.js # LocalStorage-UI-State, Header-Collapse, Theme
|
│ │ ├── ui.js # LocalStorage-UI-State, Header-Collapse, Theme
|
||||||
@@ -39,15 +39,19 @@ user-facing text.
|
|||||||
│ │ ├── server.js # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl
|
│ │ ├── server.js # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl
|
||||||
│ │ ├── store.js # save/syncAllToServer, switchDepartment, load, seed
|
│ │ ├── store.js # save/syncAllToServer, switchDepartment, load, seed
|
||||||
│ │ ├── weeks.js # Wochen-Auswahl, -Erzeugung, -Klonen, Auto-Lock, -Löschung
|
│ │ ├── 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
|
│ │ ├── gantt.js # GANTT-Diagramm (buildGanttHtml, openProjectPlan), geshared
|
||||||
│ │ ├── showmode.js # Show-Mode (Präsentationsmodus)
|
│ │ ├── showmode.js # Show-Mode (Präsentationsmodus)
|
||||||
│ │ ├── tabs-auth.js # showTab, confirmPassword (SHA256-Passwortschutz)
|
│ │ ├── tabs-auth.js # showTab, confirmPassword (SHA256-Passwortschutz)
|
||||||
│ │ ├── pdf.js # PDF-Export (pdfmake, A3 quer)
|
│ │ ├── pdf.js # PDF-Export (pdfmake, A3 quer)
|
||||||
│ │ └── init.js # renderAll/initUiState + Seiteneinstieg (nur Test.html)
|
│ │ └── 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
|
│ ├── project-plan.html # Gantt/TODO planning view
|
||||||
│ ├── js/plan/ # planning modules (nur von project-plan.html geladen)
|
│ ├── js/plan/ # planning modules (nur von project-plan.html geladen)
|
||||||
│ │ ├── plan-state.js # PlanApp-Zustand, Datenladepath, savePlan, Toasts
|
│ │ ├── 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`)
|
## Server API (`server.js`)
|
||||||
|
|
||||||
- `GET /` → `Test.html`; `GET /health`.
|
- `GET /` → `Test.html`; `GET /health`.
|
||||||
|
- Deep links per department + tab: `GET /<Dept>/<Tab>` → `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 /<Dept>/Projektplan(/<ProjName>)?`
|
||||||
|
(`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
|
- `GET /load-all?department=` → `{sessions:[{filename,content}], people}` with
|
||||||
`_people.csv` content in `people`; sessions sorted by filename.
|
`_people.csv` content in `people`; sessions sorted by filename.
|
||||||
- `POST /save-csv {filename, csv, department}` → writes UTF-8 BOM CSV.
|
- `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 `<script src>` tags; load order in the HTML defines dependencies,
|
them via `<script src>` tags; load order in the HTML defines dependencies,
|
||||||
since there is no bundler:
|
since there is no bundler:
|
||||||
- `Test.html` loads `globals → utils → ui → date-picker → csv → server →
|
- `Test.html` loads `globals → utils → ui → date-picker → csv → server →
|
||||||
store → weeks → board → people → projects → load → gantt → showmode →
|
store → weeks` (shared modules), then the tab-specific folders `board →
|
||||||
tabs-auth → pdf → init`. See the exact list in `Test.html`.
|
people → projects → load`, finally `gantt → showmode → tabs-auth → pdf →
|
||||||
|
init`. See the exact list in `Test.html`.
|
||||||
- `project-plan.html` loads only `globals → utils → ui → date-picker → csv →
|
- `project-plan.html` loads only `globals → utils → ui → date-picker → csv →
|
||||||
server → gantt` plus the `js/plan/*` modules. It must NOT load modules that
|
server → gantt` plus the `js/plan/*` modules. It must NOT load modules that
|
||||||
touch main-only page elements (`store`, `weeks`, `board`, …, `init`).
|
touch main-only page elements (`store`, `weeks`, `board`, …, `init`).
|
||||||
@@ -122,8 +137,10 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
|||||||
`db`/`currentDepartment` live in `js/globals.js`.
|
`db`/`currentDepartment` live in `js/globals.js`.
|
||||||
- State: `let db = { weeks, currentWeek, globalPeople }` (in-memory only).
|
- State: `let db = { weeks, currentWeek, globalPeople }` (in-memory only).
|
||||||
- Project IDs are regenerated on every CSV load, so projects are addressed
|
- Project IDs are regenerated on every CSV load, so projects are addressed
|
||||||
cross-page by `department` + week `label` + `projNr` + `projName` via URL
|
cross-page by `department` + week `label` + `projNr` (+ readable projName path
|
||||||
params (`openProjectPlan`, `findProjectForPlan`).
|
segment) via URL params: `openProjectPlan()` builds
|
||||||
|
`/HLS/Projektplan/<ProjName>?weekLabel=…&projNr=…`, `findProjectForPlan`
|
||||||
|
resolves them.
|
||||||
- Person IDs are stable across reloads via `personIdForName(name)` →
|
- Person IDs are stable across reloads via `personIdForName(name)` →
|
||||||
`p_<lowercased alnum>`.
|
`p_<lowercased alnum>`.
|
||||||
- `save()` debounces 500 ms then `syncAllToServer()` writes **all** weeks plus
|
- `save()` debounces 500 ms then `syncAllToServer()` writes **all** weeks plus
|
||||||
@@ -135,8 +152,12 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
|||||||
- `applyAutoLock()` locks every week except the chronologically last one after
|
- `applyAutoLock()` locks every week except the chronologically last one after
|
||||||
load/clone/new-week; `localStorage` UI state is not restored for locked tabs.
|
load/clone/new-week; `localStorage` UI state is not restored for locked tabs.
|
||||||
- `localStorage` keys: `projuebersicht_v1` (department only — tab is never
|
- `localStorage` keys: `projuebersicht_v1` (department only — tab is never
|
||||||
restored), `projuebersicht-theme`, header-collapse key. Do **not** store data
|
restored from storage; at startup the tab comes from the deep-link URL if
|
||||||
in `localStorage`; it is UI state only.
|
present, otherwise `board`), `projuebersicht-theme`, header-collapse key. Do
|
||||||
|
**not** store data in `localStorage`; it is UI state only.
|
||||||
|
- Deep-link URLs (`/<Dept>/<Tab>`) are shared via the address bar:
|
||||||
|
`updateRouteUrl()` rewrites the path via `history.replaceState` on tab and
|
||||||
|
department switches; protected tabs keep the URL but open the password modal.
|
||||||
|
|
||||||
## Auth / security quirk
|
## Auth / security quirk
|
||||||
|
|
||||||
@@ -147,7 +168,7 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
|||||||
loaded from `cdnjs.cloudflare.com` in `Test.html`, so those features need
|
loaded from `cdnjs.cloudflare.com` in `Test.html`, so those features need
|
||||||
internet access and won't work offline. `project-plan.html` loads neither.
|
internet access and won't work offline. `project-plan.html` loads neither.
|
||||||
- User-provided status HTML is sanitized on render via a small whitelist
|
- User-provided status HTML is sanitized on render via a small whitelist
|
||||||
(`sanitizeStatusHtml` in `js/board.js`) to mitigate XSS.
|
(`sanitizeStatusHtml` in `js/board/board.js`) to mitigate XSS.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "projektuebersicht",
|
"name": "projektuebersicht",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Single-page App zur Verfolgung von TGA-Projekten",
|
"description": "App zur Verfolgung von TGA-Projekten",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+24
-22
@@ -5,9 +5,11 @@
|
|||||||
und verwaltet wöchentliche Sitzungen (Sitzungswahl, -anlage, -duplikat und -löschung im
|
und verwaltet wöchentliche Sitzungen (Sitzungswahl, -anlage, -duplikat und -löschung im
|
||||||
seitlichen Menü) je Abteilung (ELT, HLS, Architektur). Sie lädt das gemeinsame Stylesheet
|
seitlichen Menü) je Abteilung (ELT, HLS, Architektur). Sie lädt das gemeinsame Stylesheet
|
||||||
styles.css sowie der Reihe nach die Module js/globals.js, js/utils.js, js/ui.js,
|
styles.css sowie der Reihe nach die Module js/globals.js, js/utils.js, js/ui.js,
|
||||||
js/date-picker.js, js/csv.js, js/server.js, js/store.js, js/weeks.js, js/board.js,
|
js/date-picker.js, js/csv.js, js/server.js, js/store.js, js/weeks.js, danach die
|
||||||
js/people.js, js/projects.js, js/load.js, js/gantt.js, js/showmode.js, js/tabs-auth.js,
|
tab-spezifischen Module js/board/board.js, js/people/people.js,
|
||||||
js/pdf.js und js/init.js; crypto-js und pdfmake kommen von einem CDN. Über openProjectPlan()
|
js/projects/projects.js, js/load/load.js sowie js/gantt.js, js/showmode.js,
|
||||||
|
js/tabs-auth.js, js/pdf.js und js/init.js; crypto-js und pdfmake kommen von einem
|
||||||
|
CDN. Über openProjectPlan()
|
||||||
aus js/gantt.js öffnet diese Seite die Projektplan-Ansicht project-plan.html in einem
|
aus js/gantt.js öffnet diese Seite die Projektplan-Ansicht project-plan.html in einem
|
||||||
neuen Tab, die nur die gemeinsamen Module plus die js/plan/*-Module lädt.
|
neuen Tab, die nur die gemeinsamen Module plus die js/plan/*-Module lädt.
|
||||||
-->
|
-->
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Projektübersicht Elektro</title>
|
<title>Projektübersicht Elektro</title>
|
||||||
<link rel="stylesheet" href="styles.css?v=3">
|
<link rel="stylesheet" href="/styles.css?v=3">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
||||||
<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/pdfmake.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js"></script>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="header-bar">
|
<div class="header-bar">
|
||||||
<h1 id="pageTitle">Projektübersicht Elektro</h1>
|
<h1 id="pageTitle">Projektübersicht Elektro</h1>
|
||||||
<img src="dein_bild.ico" alt="Logo" class="header-logo">
|
<img src="/dein_bild.ico" alt="Logo" class="header-logo">
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -221,22 +223,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/globals.js?v=5"></script>
|
<script src="/js/globals.js?v=5"></script>
|
||||||
<script src="js/utils.js?v=5"></script>
|
<script src="/js/utils.js?v=5"></script>
|
||||||
<script src="js/ui.js?v=5"></script>
|
<script src="/js/ui.js?v=5"></script>
|
||||||
<script src="js/date-picker.js?v=5"></script>
|
<script src="/js/date-picker.js?v=5"></script>
|
||||||
<script src="js/csv.js?v=5"></script>
|
<script src="/js/csv.js?v=5"></script>
|
||||||
<script src="js/server.js?v=5"></script>
|
<script src="/js/server.js?v=5"></script>
|
||||||
<script src="js/store.js?v=5"></script>
|
<script src="/js/store.js?v=5"></script>
|
||||||
<script src="js/weeks.js?v=5"></script>
|
<script src="/js/weeks.js?v=5"></script>
|
||||||
<script src="js/board.js?v=5"></script>
|
<script src="/js/board/board.js?v=6"></script>
|
||||||
<script src="js/people.js?v=5"></script>
|
<script src="/js/people/people.js?v=6"></script>
|
||||||
<script src="js/projects.js?v=5"></script>
|
<script src="/js/projects/projects.js?v=6"></script>
|
||||||
<script src="js/load.js?v=5"></script>
|
<script src="/js/load/load.js?v=6"></script>
|
||||||
<script src="js/gantt.js?v=5"></script>
|
<script src="/js/gantt.js?v=5"></script>
|
||||||
<script src="js/showmode.js?v=5"></script>
|
<script src="/js/showmode.js?v=5"></script>
|
||||||
<script src="js/tabs-auth.js?v=5"></script>
|
<script src="/js/tabs-auth.js?v=5"></script>
|
||||||
<script src="js/pdf.js?v=5"></script>
|
<script src="/js/pdf.js?v=5"></script>
|
||||||
<script src="js/init.js?v=5"></script>
|
<script src="/js/init.js?v=5"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
// Diese Datei rendert das Projektboard (Tab "Projektboard" in `Test.html`),
|
// Diese Datei rendert das Projektboard (Tab "Projektboard" in `Test.html`),
|
||||||
// die zentrale Tabelle mit Projekten, Status-Kommentaren, Personen-Spalten
|
// die zentrale Tabelle mit Projekten, Status-Kommentaren, Personen-Spalten
|
||||||
// (Anwesenheit/Zuordnung) und Leistungsphasen in Prozent. Sie wird von
|
// (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()`,
|
// globale Funktionen wie `renderBoard()`, `statusFocus()`, `cycleAssign()`,
|
||||||
// `editLph()` und `delProj()` bereit. Sie nutzt `currentWeek()`/
|
// `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`.
|
// `assignKey()` aus `js/utils.js` und `openProjectPlan()` aus `js/gantt.js`.
|
||||||
|
|
||||||
// currentStatusEditor: aktuell fokussiertes contenteditable-Element des
|
// currentStatusEditor: aktuell fokussiertes contenteditable-Element des
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// und stellt den Kalender-Picker (Einzeldatum "openDatePicker" und Bereich
|
// und stellt den Kalender-Picker (Einzeldatum "openDatePicker" und Bereich
|
||||||
// "openRangePicker") bereit. Der Zustand des offenen Popups liegt in der
|
// "openRangePicker") bereit. Der Zustand des offenen Popups liegt in der
|
||||||
// Modulvariablen rangePickerState. Genutzt wird der Picker v. a. von
|
// 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).
|
// Plan-Modulen unter js/plan/* (z. B. GANTT-Datumsangaben in plan-modals.js).
|
||||||
|
|
||||||
// rangePickerState: Zustand des aktuell geöffneten Picker-Popups (Optionen,
|
// rangePickerState: Zustand des aktuell geöffneten Picker-Popups (Optionen,
|
||||||
|
|||||||
+10
-9
@@ -2,9 +2,9 @@
|
|||||||
// Projektübersicht — GANTT-Diagramm (gemeinsam genutzt)
|
// Projektübersicht — GANTT-Diagramm (gemeinsam genutzt)
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Gemeinsames Modul für das GANTT-Diagramm: Es wird von Test.html (nach
|
// 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
|
// 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
|
// keine Hauptseiten-spezifischen Funktionen voraussetzen; Funktionen, die
|
||||||
// currentWeek()/isWeekLocked() aus js/weeks.js brauchen (renderGantt,
|
// currentWeek()/isWeekLocked() aus js/weeks.js brauchen (renderGantt,
|
||||||
// fitGanttRange, openProjectPlan), werden nur vom Hauptfenster aufgerufen.
|
// fitGanttRange, openProjectPlan), werden nur vom Hauptfenster aufgerufen.
|
||||||
@@ -328,19 +328,20 @@ function buildGanttHtml(projects, start, end, options = {}){
|
|||||||
// ---------- Projektplan-Navigation ----------
|
// ---------- Projektplan-Navigation ----------
|
||||||
|
|
||||||
// openProjectPlan: öffnet project-plan.html in einem neuen Tab. Weil
|
// openProjectPlan: öffnet project-plan.html in einem neuen Tab. Weil
|
||||||
// Projekt-IDs nicht persistent sind, werden department, Wochen-Label, Proj.-Nr.
|
// Projekt-IDs nicht persistent sind, werden Wochen-Label und Proj.-Nr. als
|
||||||
// und -Name als URL-Parameter übergeben. Parameter: pid = Projekt-ID der
|
// URL-Parameter übergeben; Abteilung und Projektname stehen im Pfad nach dem
|
||||||
// aktuellen Woche. Kein Rückgabewert.
|
// Schema "/<Abteilung>/Projektplan/<Projektname>". Parameter: pid =
|
||||||
|
// Projekt-ID der aktuellen Woche. Kein Rückgabewert.
|
||||||
function openProjectPlan(pid){
|
function openProjectPlan(pid){
|
||||||
const w = currentWeek(); if(!w) return;
|
const w = currentWeek(); if(!w) return;
|
||||||
const p = w.projects.find(x => x.id === pid); if(!p) return;
|
const p = w.projects.find(x => x.id === pid); if(!p) return;
|
||||||
// IDs sind nicht persistent (werden bei jedem CSV-Laden neu erzeugt).
|
// IDs sind nicht persistent (werden bei jedem CSV-Laden neu erzeugt).
|
||||||
// Daher werden stabile Werte (Label, Projekt-Nr., Name) in der URL übergeben.
|
// Daher werden stabile Werte (Label, Projekt-Nr.) in der URL übergeben.
|
||||||
const url = new URL('project-plan.html', window.location.href);
|
// Absoluter Pfad mit Abteilung und Projektname als Route-Segment, damit der
|
||||||
url.searchParams.set('department', currentDepartment);
|
// 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('weekLabel', w.label);
|
||||||
url.searchParams.set('projNr', p.nr || '');
|
url.searchParams.set('projNr', p.nr || '');
|
||||||
url.searchParams.set('projName', p.name || '');
|
|
||||||
window.open(url.toString(), '_blank');
|
window.open(url.toString(), '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -5,7 +5,7 @@
|
|||||||
// legt alle zentralen Konstanten (Leistungsphasen, Rollen, Gruppen, Abteilungen,
|
// legt alle zentralen Konstanten (Leistungsphasen, Rollen, Gruppen, Abteilungen,
|
||||||
// Farben, Passwort-Hash) sowie den globalen In-Memory-Zustand (db,
|
// Farben, Passwort-Hash) sowie den globalen In-Memory-Zustand (db,
|
||||||
// currentDepartment, currentTab) fest. Sie stellt keine Funktionen, sondern nur
|
// 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/gantt.js, js/plan/*, ...) zugreifen. Auf dem Plan-Gantt lädt zusätzlich
|
||||||
// js/plan/plan-state.js Zustand für die Planungsseite.
|
// js/plan/plan-state.js Zustand für die Planungsseite.
|
||||||
|
|
||||||
@@ -46,4 +46,19 @@ const DEPARTMENTS = {
|
|||||||
ELT: { label: 'Elektro', icon: '' },
|
ELT: { label: 'Elektro', icon: '' },
|
||||||
HLS: { label: 'HLS', icon: '' },
|
HLS: { label: 'HLS', icon: '' },
|
||||||
ARCH: { label: 'Architektur', icon: '' }
|
ARCH: { label: 'Architektur', icon: '' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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: "/<Abteilung>/Projektplan".
|
||||||
|
const PLAN_SLUG = 'Projektplan';
|
||||||
+12
-7
@@ -8,8 +8,8 @@
|
|||||||
// initUiState() und syncRangeDisplay() bereit und nutzt loadUiState()/
|
// initUiState() und syncRangeDisplay() bereit und nutzt loadUiState()/
|
||||||
// loadTheme()/loadHeaderState() aus js/ui.js, switchDepartment() aus
|
// loadTheme()/loadHeaderState() aus js/ui.js, switchDepartment() aus
|
||||||
// js/store.js, showTab() aus js/tabs-auth.js sowie die Render-Funktionen
|
// js/store.js, showTab() aus js/tabs-auth.js sowie die Render-Funktionen
|
||||||
// renderBoard()/renderLoad()/renderProjects()/renderPeople() aus js/board.js,
|
// renderBoard()/renderLoad()/renderProjects()/renderPeople() aus js/board/board.js,
|
||||||
// js/load.js, js/projects.js, js/people.js.
|
// js/load/load.js, js/projects/projects.js, js/people/people.js.
|
||||||
|
|
||||||
// renderAll: Convenience um UI-Elemente zu initialisieren
|
// renderAll: Convenience um UI-Elemente zu initialisieren
|
||||||
function renderAll(){
|
function renderAll(){
|
||||||
@@ -24,10 +24,16 @@ function renderAll(){
|
|||||||
|
|
||||||
// initUiState: stellt beim Start den UI-Zustand her (LocalStorage-UI-State,
|
// initUiState: stellt beim Start den UI-Zustand her (LocalStorage-UI-State,
|
||||||
// Theme, Header-Status), setzt Titel und aktive Abteilungs-Schaltfläche und
|
// Theme, Header-Status), setzt Titel und aktive Abteilungs-Schaltfläche und
|
||||||
// zeigt den ersten Tab an – passwortgeschützte Tabs ('projects'/'people')
|
// zeigt den ersten Tab an. Eine Deep-Link-URL (z. B. "/HLS/Auslastung") aus
|
||||||
// werden beim Start nie direkt aktiviert. Kein Rückgabewert.
|
// applyUrlRoute() übersteuert dabei Abteilung und Tab; passwortgeschützte Tabs
|
||||||
|
// öffnen das Passwort-Modal (showTab ohne skipAuth). Kein Rückgabewert.
|
||||||
function initUiState(){
|
function initUiState(){
|
||||||
loadUiState();
|
loadUiState();
|
||||||
|
const route = applyUrlRoute();
|
||||||
|
if(route && DEPARTMENTS[route.department]){
|
||||||
|
currentDepartment = route.department;
|
||||||
|
currentTab = route.tab;
|
||||||
|
}
|
||||||
loadTheme();
|
loadTheme();
|
||||||
loadHeaderState();
|
loadHeaderState();
|
||||||
document.querySelectorAll('.dept-tab').forEach(btn => {
|
document.querySelectorAll('.dept-tab').forEach(btn => {
|
||||||
@@ -39,9 +45,8 @@ function initUiState(){
|
|||||||
if(titleEl) titleEl.textContent = info.icon + ' Projektübersicht ' + info.label;
|
if(titleEl) titleEl.textContent = info.icon + ' Projektübersicht ' + info.label;
|
||||||
|
|
||||||
const tab = ['board','load','projects','people'].includes(currentTab) ? currentTab : DEFAULT_TAB;
|
const tab = ['board','load','projects','people'].includes(currentTab) ? currentTab : DEFAULT_TAB;
|
||||||
// Beim initialen Start niemals einen passwortgeschützten Tab direkt anzeigen.
|
// Geschützte Tabs ('projects'/'people') verlangen hier ihr Passwort.
|
||||||
const safeTab = (tab === 'projects' || tab === 'people') ? DEFAULT_TAB : tab;
|
showTab(tab);
|
||||||
showTab(safeTab, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncRangeDisplay: schreibt den aus zwei Datumsfeldern (startId/endId)
|
// syncRangeDisplay: schreibt den aus zwei Datumsfeldern (startId/endId)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// people → projects → load → gantt → …). Es stellt renderLoad(), das LP-
|
// people → projects → load → gantt → …). Es stellt renderLoad(), das LP-
|
||||||
// Aufwand-Modal und das Neues-Projekt-Modal bereit. Genutzt werden LPHS/ROLES
|
// 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/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,
|
// Die Auslastungs-Logik (berechneAuslastung, lpActiveList, faktorFormat,
|
||||||
// pdfLoadAmpelColor, LOAD_BAR_MAX) wird zusätzlich von js/pdf.js für den
|
// pdfLoadAmpelColor, LOAD_BAR_MAX) wird zusätzlich von js/pdf.js für den
|
||||||
// PDF-Export wiederverwendet.
|
// PDF-Export wiederverwendet.
|
||||||
+2
-2
@@ -8,10 +8,10 @@
|
|||||||
// vor js/init.js); exportPDF() hängt am Export-Button von Test.html und nutzt
|
// 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
|
// die per CDN eingebundene Bibliothek pdfmake (offline nicht verfügbar). Es
|
||||||
// liest die aktuelle Woche über currentWeek() aus js/store.js, Personen über
|
// 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
|
// formatWeekDate() aus js/utils.js sowie die Auslastungs-Berechnung
|
||||||
// (berechneAuslastung, lpActiveList, faktorFormat, pdfLoadAmpelColor,
|
// (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.
|
// bereit.
|
||||||
|
|
||||||
// Farbkonstanten des PDF-Layouts: Rollen-Hintergründe, neutrale bzw.
|
// Farbkonstanten des PDF-Layouts: Rollen-Hintergründe, neutrale bzw.
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
// Diese Datei rendert den Personen-Tab (Tab "Projektbeteiligte" in `Test.html`)
|
// Diese Datei rendert den Personen-Tab (Tab "Projektbeteiligte" in `Test.html`)
|
||||||
// und verwaltet die globale Personenliste pro Abteilung: Anlegen, Bearbeiten,
|
// und verwaltet die globale Personenliste pro Abteilung: Anlegen, Bearbeiten,
|
||||||
// Löschen, Rollen-Auswahl, Drag-and-Drop-Sortierung sowie Urlaub/Abwesenheiten.
|
// 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()`,
|
// und stellt globale Funktionen wie `addPerson()`, `delPerson()`,
|
||||||
// `editPerson()`, `renderPeople()` und `addAbsence()` bereit. Genutzt wird sie
|
// `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/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
|
// addPerson: fügt eine neue Person der globalen Liste hinzu und
|
||||||
// synchronisiert sie in die aktuelle Sitzung.
|
// synchronisiert sie in die aktuelle Sitzung.
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Dieses Modul wird als erstes der plan-*-Module von public/project-plan.html
|
// Dieses Modul wird als erstes der plan-*-Module von public/project-plan.html
|
||||||
// geladen und definiert window.PlanApp als gemeinsamen Zustand aller
|
// geladen und definiert window.PlanApp als gemeinsamen Zustand aller
|
||||||
// Projektplan-Module. Es liest die URL-Parameter (department, weekLabel,
|
// Projektplan-Module. Es liest die URL-Route (Abteilung + Projektname, z. B.
|
||||||
// projNr, projName), über die das zu bearbeitende Projekt adressiert wird.
|
// "/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
|
// Nach DOMContentLoaded werden Theme, Toolbar-Buttons und die Sitzungsdaten
|
||||||
// über die Server-API geladen. Die hier definierten Funktionen (savePlan,
|
// über die Server-API geladen. Die hier definierten Funktionen (savePlan,
|
||||||
// isLocked, showToast u. a.) werden über app.* den anderen plan-Modulen
|
// isLocked, showToast u. a.) werden über app.* den anderen plan-Modulen
|
||||||
@@ -33,10 +34,14 @@ window.PlanApp = {
|
|||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
const app = window.PlanApp;
|
const app = window.PlanApp;
|
||||||
app.department = app.params.get('department') || 'ELT';
|
// Abteilung und Projektname aus dem Deep-Link-Pfad
|
||||||
|
// "/<Abteilung>/Projektplan/<Projektname>" 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.weekLabel = app.params.get('weekLabel');
|
||||||
app.projNr = app.params.get('projNr') || '';
|
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
|
// init: Einstiegspunkt nach DOMContentLoaded. Übernimmt das Theme aus
|
||||||
// localStorage/System, prüft die URL-Parameter und bricht mit Fehlermeldung
|
// localStorage/System, prüft die URL-Parameter und bricht mit Fehlermeldung
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
// Diese Datei rendert den Projekte-Tab (Tab "Projekte" in `Test.html`) als
|
// Diese Datei rendert den Projekte-Tab (Tab "Projekte" in `Test.html`) als
|
||||||
// sortierbare Listenansicht: Projekte werden nach Gruppe ("Aktive Projekte",
|
// sortierbare Listenansicht: Projekte werden nach Gruppe ("Aktive Projekte",
|
||||||
// "Kleine Projekte" usw.) gruppiert, inline bearbeitbar und per
|
// "Kleine Projekte" usw.) gruppiert, inline bearbeitbar und per
|
||||||
// Drag-and-Drop umsortiert. Sie wird von `Test.html` nach `js/people.js`
|
// Drag-and-Drop umsortiert. Sie wird von `Test.html` nach `js/people/people.js`
|
||||||
// (vor `js/load.js`) geladen und stellt globale Funktionen wie
|
// (vor `js/load/load.js`) geladen und stellt globale Funktionen wie
|
||||||
// `renderProjects()`, `editProjectField()`, `editProjectActiveLP()` sowie die
|
// `renderProjects()`, `editProjectField()`, `editProjectActiveLP()` sowie die
|
||||||
// Drag-and-Drop-Handler und die Sortier-Hilfe `getDragAfterElement()` bereit.
|
// Drag-and-Drop-Handler und die Sortier-Hilfe `getDragAfterElement()` bereit.
|
||||||
// Letztere wird auch von `js/people.js` (Personen-Sortierung) genutzt;
|
// Letztere wird auch von `js/people/people.js` (Personen-Sortierung) genutzt;
|
||||||
// `renderBoard()` aus `js/board.js` wird nach Änderungen aktualisiert.
|
// `renderBoard()` aus `js/board/board.js` wird nach Änderungen aktualisiert.
|
||||||
|
|
||||||
// delProjectFromList: löscht ein Projekt (per Bestätigungsdialog) in der
|
// delProjectFromList: löscht ein Projekt (per Bestätigungsdialog) in der
|
||||||
// Listen-Ansicht. Parameter `id`: Projekt-ID. Keine Rückgabe; bei gesperrter
|
// 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
|
// gezogene Zeile bei Mausposition `y` abgelegt werden soll (Suche nach dem
|
||||||
// Element mit der kleinsten negativen Distanz zur Mitte). Rückgabe:
|
// Element mit der kleinsten negativen Distanz zur Mitte). Rückgabe:
|
||||||
// Ziel-Element oder undefined (→ ans Ende anfügen). Wird auch von
|
// 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){
|
function getDragAfterElement(container, y){
|
||||||
const draggableElements = [...container.querySelectorAll('.person-list-item:not([style*="opacity"])')];
|
const draggableElements = [...container.querySelectorAll('.person-list-item:not([style*="opacity"])')];
|
||||||
return draggableElements.reduce((closest, child) => {
|
return draggableElements.reduce((closest, child) => {
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
// und Auslastung) und zeigt sie in einem Vollbild-Overlay. Genutzt werden
|
// und Auslastung) und zeigt sie in einem Vollbild-Overlay. Genutzt werden
|
||||||
// showTab() aus js/tabs-auth.js, switchDepartment() aus js/store.js,
|
// showTab() aus js/tabs-auth.js, switchDepartment() aus js/store.js,
|
||||||
// setHeaderCollapsed() aus js/ui.js sowie renderBoard()/renderLoad() aus
|
// 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.
|
// Modulvariablen am Dateikopf.
|
||||||
|
|
||||||
// showModeActive: true, solange der Show Mode läuft
|
// showModeActive: true, solange der Show Mode läuft
|
||||||
|
|||||||
+6
-5
@@ -7,7 +7,7 @@
|
|||||||
// Sie wird von `Test.html` nach `js/csv.js` und `js/server.js` geladen und stellt
|
// Sie wird von `Test.html` nach `js/csv.js` und `js/server.js` geladen und stellt
|
||||||
// die globalen Funktionen `save()`, `syncAllToServer()`, `switchDepartment()`,
|
// die globalen Funktionen `save()`, `syncAllToServer()`, `switchDepartment()`,
|
||||||
// `load()`, `seed()` und `demoProj()` bereit. Aufrufer sind u.a. `js/weeks.js`,
|
// `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.
|
// ist über `saveSyncTimer` (in `js/globals.js` deklariert) um 500 ms debounced.
|
||||||
|
|
||||||
// save: synchronisiert die aktuellen Daten als CSV-Dateien zum Server.
|
// save: synchronisiert die aktuellen Daten als CSV-Dateien zum Server.
|
||||||
@@ -62,6 +62,7 @@ function switchDepartment(dept){
|
|||||||
if(!DEPARTMENTS[dept]) return;
|
if(!DEPARTMENTS[dept]) return;
|
||||||
currentDepartment = dept;
|
currentDepartment = dept;
|
||||||
saveUiState();
|
saveUiState();
|
||||||
|
updateRouteUrl(dept, currentTab);
|
||||||
|
|
||||||
// UI-Status der Abteilungs-Tabs aktualisieren
|
// UI-Status der Abteilungs-Tabs aktualisieren
|
||||||
document.querySelectorAll('.dept-tab').forEach(btn => {
|
document.querySelectorAll('.dept-tab').forEach(btn => {
|
||||||
@@ -118,11 +119,11 @@ function load(){
|
|||||||
}
|
}
|
||||||
console.log(`Server-Load erfolgreich: ${db.weeks.length} Sitzung(en).`);
|
console.log(`Server-Load erfolgreich: ${db.weeks.length} Sitzung(en).`);
|
||||||
}
|
}
|
||||||
// Geschützte Tabs dürfen nicht ohne Passwort angezeigt werden, auch wenn
|
// Geschützte Tabs ('projects'/'people') dürfen nur mit Passwort angezeigt
|
||||||
// sie im UI-State gespeichert waren.
|
// werden. showTab() öffnet dafür automatisch das Passwort-Modal – auch
|
||||||
if(currentTab === 'projects' || currentTab === 'people') currentTab = DEFAULT_TAB;
|
// wenn der Tab per Deep-Link-URL (z. B. "/ELT/Projekte") gewählt wurde.
|
||||||
renderAll();
|
renderAll();
|
||||||
showTab(currentTab, true);
|
showTab(currentTab);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('Laden vom Server fehlgeschlagen:', err);
|
console.error('Laden vom Server fehlgeschlagen:', err);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
// ausstehende Aktion wird in requestedAction (js/globals.js) gemerkt – davon
|
// ausstehende Aktion wird in requestedAction (js/globals.js) gemerkt – davon
|
||||||
// profitieren auch die Passwort-Gates in js/weeks.js (Löschen/Entsperren von
|
// profitieren auch die Passwort-Gates in js/weeks.js (Löschen/Entsperren von
|
||||||
// Sitzungen). Gerendert werden beim Tab-Wechsel renderBoard()/renderLoad()/
|
// Sitzungen). Gerendert werden beim Tab-Wechsel renderBoard()/renderLoad()/
|
||||||
// renderProjects()/renderPeople() aus js/board.js, js/load.js, js/projects.js,
|
// renderProjects()/renderPeople() aus js/board/board.js, js/load/load.js, js/projects/projects.js,
|
||||||
// js/people.js.
|
// js/people/people.js.
|
||||||
|
|
||||||
// showTab: wechselt zwischen Ansichten; für sensible Tabs wird ein Passwort verlangt
|
// showTab: wechselt zwischen Ansichten; für sensible Tabs wird ein Passwort verlangt
|
||||||
function showTab(t, skipAuth = false){
|
function showTab(t, skipAuth = false){
|
||||||
@@ -24,6 +24,7 @@ function showTab(t, skipAuth = false){
|
|||||||
}
|
}
|
||||||
currentTab = t;
|
currentTab = t;
|
||||||
saveUiState();
|
saveUiState();
|
||||||
|
updateRouteUrl(currentDepartment, t);
|
||||||
|
|
||||||
['board','projects','load','people'].forEach(x=>{
|
['board','projects','load','people'].forEach(x=>{
|
||||||
const el = document.getElementById(x);
|
const el = document.getElementById(x);
|
||||||
|
|||||||
@@ -33,6 +33,36 @@ function loadUiState(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Deep-Link-Routen (Abteilung + Tab als URL) ----------
|
||||||
|
|
||||||
|
// applyUrlRoute: wertet einen Deep-Link "/<Abteilung>/<Tab>" 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 ----------
|
// ---------- Kollabierbarer Header ----------
|
||||||
|
|
||||||
// HEADER_COLLAPSED_KEY: LocalStorage-Key für den Einklapp-Zustand des Header-Menüs
|
// HEADER_COLLAPSED_KEY: LocalStorage-Key für den Einklapp-Zustand des Header-Menüs
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
// ID-, Sortier- und Datumsfunktionen) und wird nach js/globals.js von beiden
|
// ID-, Sortier- und Datumsfunktionen) und wird nach js/globals.js von beiden
|
||||||
// HTML-Einstiegspunkten geladen. Sie stellt ausschließlich globale Funktionen
|
// HTML-Einstiegspunkten geladen. Sie stellt ausschließlich globale Funktionen
|
||||||
// ohne eigenen Zustand bereit; genutzt wird sie praktisch von allen Modulen,
|
// 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.
|
// (z. B. für ISO-Datumsformate und Werktage) und von js/gantt.js.
|
||||||
|
|
||||||
// departmentParam: gibt ein Query-String-Fragment fuer die aktuelle Abteilung zurueck.
|
// departmentParam: gibt ein Query-String-Fragment fuer die aktuelle Abteilung zurueck.
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
// Sie wird von `Test.html` nach `js/store.js` geladen und stellt globale
|
// Sie wird von `Test.html` nach `js/store.js` geladen und stellt globale
|
||||||
// Funktionen wie `currentWeek()`, `switchWeek()`, `newWeek()`, `cloneWeek()`,
|
// Funktionen wie `currentWeek()`, `switchWeek()`, `newWeek()`, `cloneWeek()`,
|
||||||
// `applyAutoLock()`, `guardWeekLocked()` und `deleteWeek()` bereit. Genutzt wird
|
// `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).
|
// (`load`) und `js/tabs-auth.js` (Passwort-Aktionen für Entsperren/Löschen).
|
||||||
|
|
||||||
// refreshWeekSelect: aktualisiert das Dropdown `weekSelect` mit allen Sitzungen
|
// refreshWeekSelect: aktualisiert das Dropdown `weekSelect` mit allen Sitzungen
|
||||||
|
|||||||
+17
-16
@@ -3,8 +3,9 @@
|
|||||||
PROJEKTPLAN-ANSICHT: zeigt für ein einzelnes Projekt ein GANTT-Diagramm mit TODO- und
|
PROJEKTPLAN-ANSICHT: zeigt für ein einzelnes Projekt ein GANTT-Diagramm mit TODO- und
|
||||||
Meilenstein-Zeilen, Abhängigkeiten, Zoom, kritischem Pfad, Auto-Schedule, Ressourcen-
|
Meilenstein-Zeilen, Abhängigkeiten, Zoom, kritischem Pfad, Auto-Schedule, Ressourcen-
|
||||||
Auslastung und Abwesenheiten der Projektbeteiligten. Die Seite wird über openProjectPlan()
|
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,
|
in js/gantt.js in einem neuen Tab geöffnet und per Deep-Link-Route
|
||||||
Projekt-Nr./Name) adressiert. Sie lädt styles.css und ausschließlich die gemeinsamen
|
"/<Abteilung>/Projektplan/<Projektname>" 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
|
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,
|
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,
|
js/plan/plan-build-gantt.js, js/plan/plan-gantt.js, js/plan/plan-drag.js,
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Projektplan</title>
|
<title>Projektplan</title>
|
||||||
<link rel="stylesheet" href="styles.css?v=3">
|
<link rel="stylesheet" href="/styles.css?v=3">
|
||||||
<style>
|
<style>
|
||||||
.plan-page { min-height: 100vh; display: flex; flex-direction: column; }
|
.plan-page { min-height: 100vh; display: flex; flex-direction: column; }
|
||||||
.plan-header {
|
.plan-header {
|
||||||
@@ -313,18 +314,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/globals.js?v=5"></script>
|
<script src="/js/globals.js?v=5"></script>
|
||||||
<script src="js/utils.js?v=5"></script>
|
<script src="/js/utils.js?v=5"></script>
|
||||||
<script src="js/ui.js?v=5"></script>
|
<script src="/js/ui.js?v=5"></script>
|
||||||
<script src="js/date-picker.js?v=5"></script>
|
<script src="/js/date-picker.js?v=5"></script>
|
||||||
<script src="js/csv.js?v=5"></script>
|
<script src="/js/csv.js?v=5"></script>
|
||||||
<script src="js/server.js?v=5"></script>
|
<script src="/js/server.js?v=5"></script>
|
||||||
<script src="js/gantt.js?v=5"></script>
|
<script src="/js/gantt.js?v=5"></script>
|
||||||
<script src="js/plan/plan-state.js?v=5"></script>
|
<script src="/js/plan/plan-state.js?v=5"></script>
|
||||||
<script src="js/plan/plan-build-gantt.js?v=5"></script>
|
<script src="/js/plan/plan-build-gantt.js?v=5"></script>
|
||||||
<script src="js/plan/plan-gantt.js?v=5"></script>
|
<script src="/js/plan/plan-gantt.js?v=5"></script>
|
||||||
<script src="js/plan/plan-drag.js?v=5"></script>
|
<script src="/js/plan/plan-drag.js?v=5"></script>
|
||||||
<script src="js/plan/plan-modals.js?v=5"></script>
|
<script src="/js/plan/plan-modals.js?v=5"></script>
|
||||||
<script src="js/plan/plan-schedule.js?v=5"></script>
|
<script src="/js/plan/plan-schedule.js?v=5"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -121,6 +121,34 @@ app.get('/', (req,res)=>{
|
|||||||
res.sendFile(path.join(__dirname, 'public', 'Test.html'));
|
res.sendFile(path.join(__dirname, 'public', 'Test.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Gültige Tab-Slugs für Deep-Link-Routen (muss zu TAB_SLUGS in js/globals.js passen)
|
||||||
|
const VALID_TAB_SLUGS = new Set(['Projektübersicht','Auslastung','Projekte','Projektbeteiligte']);
|
||||||
|
// Slug der Projektplan-Seite (muss zu PLAN_SLUG in js/globals.js passen)
|
||||||
|
const PLAN_SLUG = 'Projektplan';
|
||||||
|
|
||||||
|
// Route: Deep-Links je Abteilung + Tab, z. B. "/HLS/Auslastung" oder
|
||||||
|
// "/ELT/Projektübersicht". Liefert wie "/" die Hauptseite; unbekannte
|
||||||
|
// Abteilungen/Tabs werden abgelehnt (404), damit keine Test.html auf beliebigen
|
||||||
|
// Pfaden ausgeliefert wird.
|
||||||
|
app.get('/:department/:tab', (req, res) => {
|
||||||
|
const { department, tab } = req.params;
|
||||||
|
if(!VALID_DEPARTMENTS.has(department)) return res.status(404).send('Nicht gefunden');
|
||||||
|
if(tab === PLAN_SLUG){
|
||||||
|
// Projektplan-Seite (GANTT), z. B. "/HLS/Projektplan?weekLabel=..."
|
||||||
|
return res.sendFile(path.join(__dirname, 'public', 'project-plan.html'));
|
||||||
|
}
|
||||||
|
if(!VALID_TAB_SLUGS.has(tab)) return res.status(404).send('Nicht gefunden');
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'Test.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route: Projektplan-Seite mit Projektname im Pfad, z. B.
|
||||||
|
// "/HLS/Projektplan/Gymnasium%20Hemer?weekLabel=..." – das Pfad-Segment dient
|
||||||
|
// nur der lesbaren Adresse, die Identifikation erfolgt über weekLabel/projNr.
|
||||||
|
app.get('/:department/Projektplan/:projName', (req, res) => {
|
||||||
|
if(!VALID_DEPARTMENTS.has(req.params.department)) return res.status(404).send('Nicht gefunden');
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'project-plan.html'));
|
||||||
|
});
|
||||||
|
|
||||||
// Serverstart mit Fehlerbehandlung (z.B. Port bereits belegt)
|
// Serverstart mit Fehlerbehandlung (z.B. Port bereits belegt)
|
||||||
const server = app.listen(PORT, ()=> console.log(`CSV-Session server running on http://localhost:${PORT}`));
|
const server = app.listen(PORT, ()=> console.log(`CSV-Session server running on http://localhost:${PORT}`));
|
||||||
server.on('error', (err) => {
|
server.on('error', (err) => {
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Reference in new issue
Block a user