von single Page hin zur mehreren Seiten geändert
This commit is contained in:
1 parent
8c94fc91f6
commit
31c4bd47cd
36 files changed
+206
-96
No files matched your search
@@ -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 /<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
|
||||
`_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 `<script src>` tags; load order in the HTML defines dependencies,
|
||||
since there is no bundler:
|
||||
- `Test.html` loads `globals → utils → ui → date-picker → csv → server →
|
||||
store → weeks → board → people → projects → load → gantt → showmode →
|
||||
tabs-auth → pdf → init`. See the exact list in `Test.html`.
|
||||
store → weeks` (shared modules), then the tab-specific folders `board →
|
||||
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 →
|
||||
server → gantt` plus the `js/plan/*` modules. It must NOT load modules that
|
||||
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`.
|
||||
- State: `let db = { weeks, currentWeek, globalPeople }` (in-memory only).
|
||||
- Project IDs are regenerated on every CSV load, so projects are addressed
|
||||
cross-page by `department` + week `label` + `projNr` + `projName` via URL
|
||||
params (`openProjectPlan`, `findProjectForPlan`).
|
||||
cross-page by `department` + week `label` + `projNr` (+ readable projName path
|
||||
segment) via URL params: `openProjectPlan()` builds
|
||||
`/HLS/Projektplan/<ProjName>?weekLabel=…&projNr=…`, `findProjectForPlan`
|
||||
resolves them.
|
||||
- Person IDs are stable across reloads via `personIdForName(name)` →
|
||||
`p_<lowercased alnum>`.
|
||||
- `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
|
||||
load/clone/new-week; `localStorage` UI state is not restored for locked tabs.
|
||||
- `localStorage` keys: `projuebersicht_v1` (department only — tab is never
|
||||
restored), `projuebersicht-theme`, header-collapse key. Do **not** store data
|
||||
in `localStorage`; it is UI state only.
|
||||
restored from storage; at startup the tab comes from the deep-link URL if
|
||||
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
|
||||
|
||||
@@ -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
|
||||
internet access and won't work offline. `project-plan.html` loads neither.
|
||||
- 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
|
||||
|
||||
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"description": "Single-page App zur Verfolgung von TGA-Projekten",
|
||||
"description": "App zur Verfolgung von TGA-Projekten",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"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
|
||||
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,
|
||||
js/date-picker.js, js/csv.js, js/server.js, js/store.js, js/weeks.js, js/board.js,
|
||||
js/people.js, js/projects.js, js/load.js, 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()
|
||||
js/date-picker.js, js/csv.js, js/server.js, js/store.js, js/weeks.js, danach die
|
||||
tab-spezifischen Module js/board/board.js, js/people/people.js,
|
||||
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
|
||||
neuen Tab, die nur die gemeinsamen Module plus die js/plan/*-Module lädt.
|
||||
-->
|
||||
@@ -16,7 +18,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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/pdfmake/0.2.10/pdfmake.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js"></script>
|
||||
@@ -36,7 +38,7 @@
|
||||
<header>
|
||||
<div class="header-bar">
|
||||
<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>
|
||||
</header>
|
||||
|
||||
@@ -221,22 +223,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/globals.js?v=5"></script>
|
||||
<script src="js/utils.js?v=5"></script>
|
||||
<script src="js/ui.js?v=5"></script>
|
||||
<script src="js/date-picker.js?v=5"></script>
|
||||
<script src="js/csv.js?v=5"></script>
|
||||
<script src="js/server.js?v=5"></script>
|
||||
<script src="js/store.js?v=5"></script>
|
||||
<script src="js/weeks.js?v=5"></script>
|
||||
<script src="js/board.js?v=5"></script>
|
||||
<script src="js/people.js?v=5"></script>
|
||||
<script src="js/projects.js?v=5"></script>
|
||||
<script src="js/load.js?v=5"></script>
|
||||
<script src="js/gantt.js?v=5"></script>
|
||||
<script src="js/showmode.js?v=5"></script>
|
||||
<script src="js/tabs-auth.js?v=5"></script>
|
||||
<script src="js/pdf.js?v=5"></script>
|
||||
<script src="js/init.js?v=5"></script>
|
||||
<script src="/js/globals.js?v=5"></script>
|
||||
<script src="/js/utils.js?v=5"></script>
|
||||
<script src="/js/ui.js?v=5"></script>
|
||||
<script src="/js/date-picker.js?v=5"></script>
|
||||
<script src="/js/csv.js?v=5"></script>
|
||||
<script src="/js/server.js?v=5"></script>
|
||||
<script src="/js/store.js?v=5"></script>
|
||||
<script src="/js/weeks.js?v=5"></script>
|
||||
<script src="/js/board/board.js?v=6"></script>
|
||||
<script src="/js/people/people.js?v=6"></script>
|
||||
<script src="/js/projects/projects.js?v=6"></script>
|
||||
<script src="/js/load/load.js?v=6"></script>
|
||||
<script src="/js/gantt.js?v=5"></script>
|
||||
<script src="/js/showmode.js?v=5"></script>
|
||||
<script src="/js/tabs-auth.js?v=5"></script>
|
||||
<script src="/js/pdf.js?v=5"></script>
|
||||
<script src="/js/init.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
+10
-9
@@ -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 "/<Abteilung>/Projektplan/<Projektname>". 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');
|
||||
}
|
||||
|
||||
|
||||
+16
-1
@@ -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.
|
||||
|
||||
@@ -47,3 +47,18 @@ const DEPARTMENTS = {
|
||||
HLS: { label: 'HLS', 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()/
|
||||
// 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)
|
||||
|
||||
@@ -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.
|
||||
+2
-2
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
// "/<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.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
|
||||
|
||||
@@ -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) => {
|
||||
@@ -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
|
||||
|
||||
+6
-5
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ----------
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+17
-16
@@ -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
|
||||
"/<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
|
||||
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 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Projektplan</title>
|
||||
<link rel="stylesheet" href="styles.css?v=3">
|
||||
<link rel="stylesheet" href="/styles.css?v=3">
|
||||
<style>
|
||||
.plan-page { min-height: 100vh; display: flex; flex-direction: column; }
|
||||
.plan-header {
|
||||
@@ -313,18 +314,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/globals.js?v=5"></script>
|
||||
<script src="js/utils.js?v=5"></script>
|
||||
<script src="js/ui.js?v=5"></script>
|
||||
<script src="js/date-picker.js?v=5"></script>
|
||||
<script src="js/csv.js?v=5"></script>
|
||||
<script src="js/server.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-build-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-modals.js?v=5"></script>
|
||||
<script src="js/plan/plan-schedule.js?v=5"></script>
|
||||
<script src="/js/globals.js?v=5"></script>
|
||||
<script src="/js/utils.js?v=5"></script>
|
||||
<script src="/js/ui.js?v=5"></script>
|
||||
<script src="/js/date-picker.js?v=5"></script>
|
||||
<script src="/js/csv.js?v=5"></script>
|
||||
<script src="/js/server.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-build-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-modals.js?v=5"></script>
|
||||
<script src="/js/plan/plan-schedule.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -121,6 +121,34 @@ app.get('/', (req,res)=>{
|
||||
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)
|
||||
const server = app.listen(PORT, ()=> console.log(`CSV-Session server running on http://localhost:${PORT}`));
|
||||
server.on('error', (err) => {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Reference in new issue
Block a user