Unnötige Datein gelöscht. DB normalisiert
This commit is contained in:
1 parent
da64472251
commit
4e083bee73
32 files changed
+971
-5542
No files matched your search
@@ -4,39 +4,38 @@
|
||||
|
||||
Single-page app for tracking engineering projects across weekly sessions
|
||||
("Sitzungen"). Vanilla JS/HTML/CSS frontend + Node.js/Express server. No build
|
||||
step, no bundler, no tests. Data is persisted as semicolon-separated CSVs
|
||||
under `sessions/`.
|
||||
step, no bundler, no tests. Data is persisted in a SQLite database
|
||||
(`node:sqlite`, needs Node >= 22.13) at `sessions/projektuebersicht.db`.
|
||||
|
||||
Two HTML entrypoints, both served statically by Express:
|
||||
- `Test.html` — the main app (tabs: Projektboard, Auslastung, Projekte,
|
||||
Projektbeteiligte). Served at `/`.
|
||||
- `project-plan.html` — per-project Gantt/TODO planning view, opened in a new
|
||||
tab via `openProjectPlan()` in `js/gantt.js`. Loads the shared helper modules
|
||||
(`globals`, `utils`, `ui`, `date-picker`, `csv`, `server`, `gantt`) plus the
|
||||
(`globals`, `utils`, `ui`, `date-picker`, `server`, `gantt`) plus the
|
||||
plan modules under `js/plan/` — NOT the full main module set.
|
||||
|
||||
All UI strings/comments/CSV content are German. Preserve German in
|
||||
All UI strings/comments are German. Preserve German in
|
||||
user-facing text.
|
||||
|
||||
## File structure
|
||||
|
||||
```
|
||||
.
|
||||
├── server.js # Express server + CSV API
|
||||
├── server.js # Express server + SQLite API
|
||||
├── package.json # deps: express, cors only
|
||||
├── docker-compose.yml # at repo ROOT; build context ., dockerfile Docker/Dockerfile
|
||||
├── Docker/Dockerfile # node:20-alpine
|
||||
├── Docker/Dockerfile # node:22-alpine (nötig für node:sqlite)
|
||||
├── .dockerignore
|
||||
├── .vscode/extensions.json
|
||||
├── public/
|
||||
│ ├── Test.html # main app (served at /)
|
||||
│ ├── 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, peopleByRole, assignKey, weekSortKey-Quelle
|
||||
│ │ ├── utils.js # esc, uid, Datum-/Differenz-Helfer, personIdForName, peopleByRole, assignKey, weekSortKey
|
||||
│ │ ├── ui.js # LocalStorage-UI-State, Header-Collapse, Theme
|
||||
│ │ ├── date-picker.js # Datums-/Bereichspicker-Overlays
|
||||
│ │ ├── csv.js # CSV-Parser/Serializer + weekSortKey, filenameForWeek
|
||||
│ │ ├── server.js # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl
|
||||
│ │ ├── server.js # baseUrl, sendWeekToServer, sendPeopleToServer, blobToDataUrl
|
||||
│ │ ├── store.js # save/syncAllToServer, switchDepartment, load, seed
|
||||
│ │ ├── weeks.js # Wochen-Auswahl, -Erzeugung, -Klonen, Auto-Lock, -Löschung
|
||||
│ │ ├── gantt.js # GANTT-Diagramm (buildGanttHtml, openProjectPlan), geshared
|
||||
@@ -62,8 +61,8 @@ user-facing text.
|
||||
│ │ └── plan-schedule.js # Kritischer Pfad, Auto-Schedule, Mail-Link
|
||||
│ ├── styles.css
|
||||
│ └── dein_bild.ico
|
||||
└── sessions/ # data; bind-mounted into Docker container
|
||||
├── ELT/ HLS/ ARCH/ # one folder per department
|
||||
└── sessions/ # SQLite-DB; bind-mounted into Docker container
|
||||
└── projektuebersicht.db # node:sqlite database (WAL mode)
|
||||
```
|
||||
|
||||
## Running
|
||||
@@ -93,31 +92,55 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
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.
|
||||
- `POST /delete-csv {filename, department}`.
|
||||
- `GET /load-all?department=` → `{weeks:[weekObj], people:[personObj]}` with the
|
||||
global people list in `people`; weeks are full JSON week objects (id, label,
|
||||
date, locked, people, attendance, projects), sorted by label.
|
||||
- `POST /save-week {department, week}` → upserts one week (JSON object, keyed by
|
||||
department + label). Used by `sendWeekToServer()`.
|
||||
- `POST /save-people {department, people}` → replaces the department's global
|
||||
people list (JSON array).
|
||||
- `POST /delete-week {department, label}` → deletes the week; missing label
|
||||
yields 400, missing week tolerates with `deleted:false`.
|
||||
- Valid departments hard-coded as `ELT|HLS|ARCH` in `server.js`
|
||||
(`VALID_DEPARTMENTS`); anything else silently falls back to `ELT`. Same set in
|
||||
`DEPARTMENTS` in `js/globals.js`.
|
||||
- `safeName()` strictly validates filenames (path traversal protection);
|
||||
static assets get `Cache-Control: no-store`.
|
||||
- Static assets get `Cache-Control: no-store`.
|
||||
- JSON body limit is 2 MB.
|
||||
|
||||
## CSV format
|
||||
## Persistence (SQLite)
|
||||
|
||||
- Delimiter `;`, UTF-8 with BOM. Hand-editing is possible but risky; the parser
|
||||
reconstructs objects from the fixed column layout.
|
||||
- Week files `<Label>.csv` (e.g. `KW_30___2026.csv` — `filenameForWeek()`
|
||||
replaces non-word chars with `_`). Row layout is positional:
|
||||
`WOCHE` (label, date, locked flag) → `PERSONEN` (`name|role|fullName` per
|
||||
column) → header row → `ANWESENHEIT` → one row per project.
|
||||
- `_people.csv` per department: `NAME;Rollen;Voller Name;E-Mail;Abwesenheiten`
|
||||
(absences as JSON). People are global per department; new people are copied
|
||||
into the current week. Historical weeks keep their own person copies, so
|
||||
deleting a global person leaves old sessions unaffected no matter what.
|
||||
- Don't change the column layout without updating both `weekToCsv()` and
|
||||
`parseSessionCsv()` (`js/csv.js`), which must stay inverse of each other.
|
||||
- Data lives in `sessions/projektuebersicht.db` (`node:sqlite` `DatabaseSync`,
|
||||
WAL mode, `PRAGMA foreign_keys = ON`), created automatically. The schema is
|
||||
**fully relational** (18 tables) instead of one big JSON document per week:
|
||||
- `weeks` (department, label, week_id, date, locked; PK `(department,label)`)
|
||||
- `week_people` / `week_people_roles` — person copies per week (`array_order`
|
||||
keeps the list order, `role_order` the role array order)
|
||||
- `attendance` — présence per week/person
|
||||
- `projects` (`array_order` = position in the JSON array, `order_value` = the
|
||||
`p.order` field, which is only unique *within a group*, not globally!)
|
||||
- `project_lph`, `project_assign` (person_id|role keys split into columns),
|
||||
`project_aufwand` (LP effort per role), `project_aktuelle_lp`
|
||||
- `project_plan`, `plan_people`, `plan_todos` / `plan_todo_assignees` /
|
||||
`plan_todo_deps`, `plan_milestones` (`position` column!) /
|
||||
`plan_milestone_deps`
|
||||
- `people` / `person_roles` / `person_absences` — global people per department
|
||||
- The HTTP API stays JSON-shaped: `serializeWeek()` /
|
||||
`serializeGlobalPeople()` split incoming week/people objects across the
|
||||
tables inside a transaction, `loadWeeks()` / `loadGlobalPeople()`
|
||||
(re)assemble them on read. `saveWeekRelational()` deletes the week's rows
|
||||
first (`deleteWeekData()`, note `weeks` uses `label`, all child tables
|
||||
`week_label`).
|
||||
- Migration on every start (idempotent):
|
||||
`migrateLegacySchema()` converts a DB from the old single-JSON-document format
|
||||
(detected via a `data` column in `weeks`): it reads the documents, drops all
|
||||
tables, recreates the relational schema and inserts everything in one
|
||||
transaction.
|
||||
- The DB file sits in `sessions/`, so the existing Docker bind-mount
|
||||
`./sessions:/app/sessions` keeps it persistent. The path is overridable via
|
||||
the `DB_PATH` environment variable (used for tests), production default is
|
||||
`sessions/projektuebersicht.db`.
|
||||
- Project/person IDs are stable across reloads; only week-level fields are
|
||||
queryable columns.
|
||||
|
||||
## Frontend architecture
|
||||
|
||||
@@ -125,28 +148,28 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
`public/js/`; **all functions are globals on `window`**. Two HTML files include
|
||||
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 →
|
||||
- `Test.html` loads `globals → utils → ui → date-picker → server →
|
||||
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 →
|
||||
- `project-plan.html` loads only `globals → utils → ui → date-picker →
|
||||
server → gantt` plus the `js/plan/*` modules. It must NOT load modules that
|
||||
touch main-only page elements (`store`, `weeks`, `board`, …, `init`).
|
||||
- Shared state object `window.PlanApp` (`js/plan/plan-state.js`) holds all
|
||||
project-plan page state (week, project, zoom level, drag/`modal`-state),
|
||||
`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` (+ 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
|
||||
`_people.csv` — not just the changed week. Exception: `js/plan/plan-state.js`
|
||||
`savePlan()` bypasses `save()` and writes only the current week's CSV directly
|
||||
via `sendCsvToServer()`.
|
||||
- Project/person IDs are stable across reloads because the server splits and
|
||||
reassembles week objects deterministically (JSON IDs are kept in the DB
|
||||
columns); projects are addressed 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 follow
|
||||
`personIdForName(name)` → `p_<lowercased alnum>`.
|
||||
- `save()` debounces 500 ms then `syncAllToServer()` writes **all** weeks (via
|
||||
`sendWeekToServer()`) plus the people list (via `sendPeopleToServer()`) — not
|
||||
just the changed week. Exception: `js/plan/plan-state.js` `savePlan()` bypasses
|
||||
`save()` and writes only the current week directly via `sendWeekToServer()`.
|
||||
- `switchDepartment()` cancels a pending save debounce before switching, and
|
||||
`load()` discards stale `/load-all` responses after a department switch
|
||||
(frozen `requestedDepartment`), so late responses can never render or persist
|
||||
@@ -185,9 +208,10 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
|
||||
- `docker compose up --build` from the repo root. Compose maps `3003:3003`,
|
||||
bind-mounts `./sessions:/app/sessions`, `restart: unless-stopped`.
|
||||
- Dockerfile: `node:20-alpine`, `npm ci --production`, runs as non-root `node`,
|
||||
HEALTHCHECK on `/health`. `.dockerignore` excludes `Docker/*` (except
|
||||
`Docker/Dockerfile`), `node_modules/`, `.git`, `npm-debug.log`.
|
||||
- Dockerfile: `node:22-alpine` (nötig für node:sqlite), `npm ci --production`,
|
||||
runs as non-root `node`, HEALTHCHECK on `/health`. `.dockerignore` excludes
|
||||
`Docker/*` (except `Docker/Dockerfile`), `node_modules/`, `.git`,
|
||||
`npm-debug.log`.
|
||||
|
||||
## Conventions & verification
|
||||
|
||||
@@ -195,4 +219,4 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
`http://192.168.3.187:3000/erik/projektuebersicht.git`.
|
||||
- No linter, formatter, typecheck, or test suite. Manual verification only:
|
||||
start server, open `http://localhost:3003`, exercises the relevant tab,
|
||||
confirm CSV changes in `sessions/`.
|
||||
confirm SQLite changes (the DB file `sessions/projektuebersicht.db` grows).
|
||||
Reference in new issue
Block a user