From f4fb3c550bc11b1ef1260a98be1e2d7ae0acc38b Mon Sep 17 00:00:00 2001 From: Erik Dunin Date: Thu, 22 Oct 2026 09:25:33 +0200 Subject: [PATCH] Projektleiter-Rolle, Papierkorb, neue Login-Seite und Benutzerverwaltung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue Rolle "Projektleiter": Projekte anlegen und löschen nur noch für Projektleiter und Administratoren (serverseitig in /save-week geprüft) - Papierkorb: gelöschte Projekte 30 Tage durch Admins wiederherstellbar - Benutzerverwaltung: Aktionen im Drei-Punkte-Menü mit Rückfrage im Menü, getrennte Felder für Vor- und Nachname, E-Mail-Vorschlag v.nachname@rauschenberg-ing.de, Benutzername = Anfang der E-Mail, Person-ID aus dem Kürzel - Endgültiges Löschen eines Benutzers entfernt ihn auch aus den Projekten und Aufgaben der aktuellen Sitzung; Dateien bleiben als "Gelöschter Benutzer" - Login-Seite neu (Design "Kalenderwoche") mit Logo als Wasserzeichen; Anmeldung ohne Groß-/Kleinschreibung, auch mit voller E-Mail-Adresse Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 104 +++++++++- public/App.html | 132 ++++++++---- public/js/admin-users.js | 287 ++++++++++++++++++++++--- public/js/admin/admin.js | 74 +++++++ public/js/auth.js | 107 +++++++++- public/js/board/board.js | 2 +- public/js/projects/projects.js | 16 +- public/js/projekt-wizard.js | 8 +- public/js/settings/settings.js | 2 +- public/js/utils.js | 44 ++++ public/logo.png | Bin 0 -> 9526 bytes public/styles.css | 185 ++++++++++++++++- server.js | 369 ++++++++++++++++++++++++++++++--- 13 files changed, 1214 insertions(+), 116 deletions(-) create mode 100644 public/logo.png diff --git a/CLAUDE.md b/CLAUDE.md index d58627f..adf3a61 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -423,10 +423,21 @@ on the JSON endpoints. forces `must_change_password=1`, deletes all sessions of that user (forces re-login), returns the plaintext once as `generatedPassword`. - `DELETE /admin/users/:id[?hard=1]` → Admin. Default is a soft-delete - (`active=0` + delete sessions); `?hard=1` permanently deletes the row, but - only if the user is referenced nowhere in `project_files.uploaded_by_user_id` - / `project_file_reviewers.user_id` / `project_file_comments.user_id` (else - 409). An admin can never delete their own account (soft or hard). + (`active=0` + delete sessions). `?hard=1` (`loescheBenutzerEndgueltig()`, + one transaction) permanently deletes the account **and, on Erik's request, + removes the person from the projects and thereby the Auslastung — but only + in the current session (latest real week) and the Planung sandbox**; older + sessions are history and stay untouched. Removed there: `project_assign`, + `plan_todo_assignees`, `plan_people`, `week_people`/`_roles`, + `attendance`, matched via `personenKennungenInWoche()` = `users.person_id` + plus every id in that week's `week_people` with the same full name + (legacy short ids like `p_ap`). Those weeks get a new `updated_at`, so a + stale browser gets a 409 instead of writing the person back. Files are no + longer a blocker: uploads/comments stay and show as "Gelöschter Benutzer" + (`COALESCE(..., 'Gelöschter Benutzer')` in the file queries), the person's + review assignments are deleted and pending files' status recomputed. + Response includes counts (`zuordnungen`, `aufgaben`, `pruefauftraege`). + An admin can never delete their own account (soft or hard). - `GET /users` → Auth (not admin-only). Lists active users (`id, username, displayName, email, personId, department, initials, roles`) — used both by the reviewer-picker in the file upload UI and by the @@ -1176,6 +1187,39 @@ Phase eine Aufgabe in `project.plan.todos`. 180.000 € rechnerisch 172 Arbeitstage für eine Person, und der Plan lief über Jahre. +### Projekte anlegen/löschen — Rolle „Projektleiter" und Papierkorb + +- **Projekte anlegen und aus der Projektliste löschen dürfen nur + `projektleiter` und `admin`** (`users.role`; `VALID_ROLES` enthält jetzt + `admin|member|projektleiter|kiosk`). `projektleiter` ist sonst ein + gewöhnliches Mitglied — nicht zu verwechseln mit der Fachrolle + `Projektleitung` (`user_roles`), die keine Berechtigung verleiht. Vergeben + in Admin → Benutzerverwaltung. Bearbeiten bestehender Projekte bleibt + jedem angemeldeten Konto erlaubt. +- **Serverseitig durchgesetzt**, nicht nur UI: Da `/save-week` immer die ganze + Woche schreibt, vergleicht `projektlisteAenderungen()` (`server.js`) den + eingehenden mit dem gespeicherten Stand (Abgleich per `id` oder + `projectUid`) und liefert neue und fehlende Projekte; ohne + `darfProjektlisteAendern(req.user)` → 403 vor jedem Schreiben. Die + Planung-Sandbox ist ausgenommen. Clientseitig blendet + `darfProjektlisteAendern()` (`js/auth.js`) den Löschen-Knopf in + `renderBoardRow()` aus, `renderUserBar()` alle Elemente mit der Klasse + `.nur-projektleitung` (die beiden „+ Projekt"-Knöpfe), und + `openNewProjWizard()` weist zusätzlich ab. +- **Papierkorb** (`deleted_projects`: Schnappschuss des kompletten + Projekt-Objekts als JSON, wer/wann/aus welcher Sitzung). Nach dem + erfolgreichen Speichern legt `legeInPapierkorb()` die gelöschten Projekte + dort ab. `purgeGeloeschteProjekte()` entfernt Einträge nach + `GELOESCHTE_PROJEKTE_TAGE` (30) — beim Start und stündlich. + Admin → **Papierkorb** (`loadAdminPapierkorb()`, `js/admin/admin.js`): + `GET /admin/deleted-projects`, `POST /admin/deleted-projects/:id/restore` + (setzt das Projekt ans Ende seiner Gruppe in der **jüngsten echten + Sitzung**, leert den Sitzungskommentar, wenn es aus einer älteren Sitzung + stammt; 409 bei Nummern-Kollision), `DELETE /admin/deleted-projects/:id` + (sofort endgültig) — alle `requireRole('admin')`. Dateien/Protokolle + hängen an `project_nr` und werden beim Löschen nie angefasst. Nicht in + `IMPORT_TABLES` — ein DB-Import lässt den Papierkorb unberührt. + ### Auftragsstatus — von der Anfrage zum Projekt `projects.auftragsstatus` (`anfrage|angebot|beauftragt|abgeschlossen`, default @@ -2490,6 +2534,23 @@ gleichzeitig bearbeiten. `+ Neues Projekt`, `Wandtafel` (the former `Show Mode` entry — see "Wandtafel" below). +### Login-Seite (`#loginOverlay`) + +Full-screen page, not a dialog: design "Kalenderwoche" (Erik's pick B from +three Taste-Skill drafts). The logo is a large, pale watermark across the +whole page (`.login-wasserzeichen`, `/logo.png` converted from `logo.ico`; +Erik's pick E from a second comparison of logo placements) — deliberately +no small logo. Left: heading and form; right: the current ISO calendar week as a large +number with Mo-Fr and today highlighted (`renderLoginWoche()` in +`js/auth.js`, called by `showLoginOverlay()`) — the app thinks in weekly +sessions, so the week is what you see before logging in. Form has inline +errors (`.login-feld.hat-fehler`, `#loginError`), a loading state on the +button, "Anzeigen" for the password and a caps-lock hint +(`verdrahteLoginHilfen()`). Styles: "LOGIN-SEITE" block at the end of +`styles.css`, app tokens plus `--login-zahl`/`--login-jahr`. The number uses +the web font Outfit (same Google-Fonts link as the Wandtafel, falls back to +the system font offline). `z-index: 400`, above `.modal-bg` (300). + ### Admin (tab `admin`, `js/admin/admin.js`) - Everything only admins may do, in one place. Split into **sections, one @@ -2514,7 +2575,40 @@ gleichzeitig bearbeiten. `renderAdminArea()` renders nothing for them. - Sections: - **Benutzerverwaltung** — accounts, roles, weekly hours, absences - (`js/admin-users.js`, `renderAdminUsers()`); reloaded from the server + (`js/admin-users.js`, `renderAdminUsers()`). Row actions: "Bearbeiten" + stays a visible button, everything else sits behind a "⋯" menu + (`openUserActionMenu()`, Erik's pick "C" from a three-way comparison): + Passwort zurücksetzen, De-/Aktivieren, and — only for deactivated + accounts — Endgültig löschen. Irreversible actions confirm inside the + menu instead of `confirm()`; the action functions take an + `ohneRueckfrage` flag for that. The menu is appended to `` with + `position: fixed` (the table's horizontally scrolling frame would clip + it) and flips upward when there is no room below. + **Vor- und Nachname** are separate fields and columns + (`users.first_name`/`last_name`, migration `migrateUsersVorNachname()`); + `display_name` stays and is always written as "Vorname Nachname" by + `POST`/`PATCH /admin/users*` — every other place still reads + `display_name`; existing accounts got Vor-/Nachname once from + `display_name` (first word / rest). **The username is the local part of + the e-mail address** (`e.dunin`) — Erik first wanted "Vorname Nachname", + then reverted to this before it ever reached the real database, so + existing usernames were never renamed. In the new-user dialog the + username follows the e-mail field (`benutzernameAusEmail()`), which + itself follows Vor-/Nachname; each field stops following once edited by + hand. **Person-IDs of new accounts come from the Kürzel** (`p_mk`, + `personIdAusKuerzel()`), with a digit appended if taken (`p_mk2`) — the + scheme every existing account and its project data already use. Before, + the dialog derived them from the full name (`p_miriam_k_rner`), which + split one person into two ids (account vs. project data). + `/login` matches `COLLATE NOCASE` and also accepts the full + address (everything from "@" is dropped); `POST /admin/users` checks + uniqueness case-insensitively (409). + The e-mail field + pre-fills `v.nachname@rauschenberg-ing.de` from Vor-/Nachname + (`emailVorschlag()`/`EMAIL_DOMAIN`, `js/utils.js`; umlauts transliterated) + while not edited by hand — same pattern as Person-ID/Kürzel; the edit + dialog and Einstellungen → Mein Konto only show it as a placeholder and + never overwrite a stored address; reloaded from the server every time the section is entered, since the list changes outside this browser and the other masks compute with it (weekly hours → Auslastung). Marked `wide: true` in `ADMIN_SECTIONS`, which lifts the grid's width cap diff --git a/public/App.html b/public/App.html index 824e50c..6e39580 100644 --- a/public/App.html +++ b/public/App.html @@ -34,12 +34,13 @@ Projektübersicht - + - + Dateikopf; Outfit: nur für die Kalenderwoche der Login-Seite. Alle fallen + ohne Internetzugang automatisch auf die System-Schrift zurück + (font-family-Stack in styles.css), brechen also nichts offline. --> + @@ -186,7 +187,7 @@ js/pdf.js. Damit fällt die einzige gewerkeübergreifende Sitzungsübersicht als PDF weg; die einzelnen Gewerke-Berichte im Wochenbericht bleiben die Ablage. --> - + @@ -212,6 +213,7 @@ + @@ -239,7 +241,7 @@ zusätzlich stehen, weil er dort dieselbe Maske öffnet. -->
Projekte
- +
-