Kleine Verbesserungen und Teams, Outlook Integration

- Outlook: Kalender-Abo je Konto (eigene Aufgaben, Meilensteine,
  Abwesenheiten als .ics), einzurichten unter Einstellungen
- Teams: Nachricht in einen Kanal bei neuen Aufgaben, mit Link direkt
  auf die Aufgabe; einzurichten unter Admin → Teams
- Sitzungen nach Bedarf laden: beim Start nur die aktuelle und drei
  davor, im Verlauf „Weitere laden“ in Fünferschritten
- Termin aus Mail: Wahl Aufgabe/Meilenstein, Notiz auch bei
  Meilensteinen, nur aktiv mit eingerichteter KI
- Kalender zeigt die KW, auch statt der Browser-Datumsfelder
- Pfeile bei den Abwesenheiten, kleinere Server-Korrekturen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
erikandClaude Opus 5.5 committed 2026-10-30 08:23:02 +01:00
1 parent a2266662ad
commit 4c2e04e78b
19 files changed
+1100 -139

No files matched your search

+128 -15
View File
@@ -1422,8 +1422,9 @@ step into a project explicit. Everything lives in `public/js/auftrag.js`:
`renderPlanKommentarVerlauf()` in `plan-info.js`): je Gewerk nur, wenn
sich der Text gegenüber der Sitzung davor geändert hat; gleicher Text in
mehreren Gewerken einer Sitzung (Altbestand aus der Umstellung auf
Kommentare je Gewerk) wird EIN Eintrag „HLS, ARCH“. Drei sichtbar, Rest
über „Alle N anzeigen“. Keine neue Datenhaltung — jede Sitzung hält ihre
Kommentare je Gewerk) wird EIN Eintrag „HLS, ARCH“. Gezeigt wird, was
geladen ist, darunter „Weitere laden“ (siehe „Sitzungen nach Bedarf
laden“). Keine neue Datenhaltung — jede Sitzung hält ihre
Kopie des Projekts. Stile: Block „Kommentar der Sitzung mit Verlauf“ am
Ende von `plan-styles.css`.
**Eigene Kachel „Protokolle“** (Eriks Wunsch; links unter „Termine und
@@ -1791,8 +1792,11 @@ step into a project explicit. Everything lives in `public/js/auftrag.js`:
Eriks Wunsch: Mailtext per Drag & Drop (markierter Text oder ganze
`.eml`-Datei) oder Strg+V (außerhalb von Eingabefeldern) auf die Bereiche
**Terminplan** oder **Aufgaben** eines Projekts; eine KI macht daraus
Einträge. **Nur Aufgaben, keine Meilensteine** — Eriks Vorgabe: Der
Terminplan ist nur die grafische Darstellung der Aufgabenliste.
Einträge — **Aufgabe oder Meilenstein**: Die KI schlägt die Art vor
(`art` in `KI_MAIL_SCHEMA`), in der Auswahlliste `#mailAuswahlModal` (immer,
auch bei nur einem Vorschlag) steht je Zeile der Umschalter
„Aufgabe | Meilenstein“ (Eriks Wunsch). Danach öffnet sich je Eintrag das
passende Panel.
- Beim Hineinziehen erscheint die Fläche `#mailAblage` (fest, `pointer-events:
none`, Zähler für dragenter/-leave). Nicht in gesperrten Sitzungen. Kein
Konflikt mit dem Balken-Ziehen (das läuft über Maus-Ereignisse).
@@ -1805,15 +1809,25 @@ Terminplan ist nur die grafische Darstellung der Aufgabenliste.
„nächsten Freitag“ stimmt. Ohne KI/bei Fehler: ein Eintrag mit dem Betreff
als Titel. **Speichert nichts.** `.msg` (Outlook) wird abgewiesen mit dem
Hinweis, Text zu markieren.
- **Nur mit eingerichteter KI aktiv** (Eriks Wunsch): `mailKiStatusLaden()`
holt nach der Anmeldung (`afterAuthenticated()`) und nach jeder Änderung
der KI-Zugangsdaten (`putMyAiKey()`) die Anbieter mit Schlüssel; ohne
einen gibt es weder Ablagefläche noch abgefangenes Strg+V.
- Anbieter: zuletzt erfolgreich genutzter (`localStorage
projuebersicht-mail-ki`), sonst der erste mit Schlüssel.
- Jeder Vorschlag öffnet das normale Aufgaben-Panel vorausgefüllt
(`openTodoModal(null, prefill)`, neu: `title`, `aufwand`, `notiz`,
`mailHinweis`, `nachSchliessen`); bei mehreren erst eine Auswahl
(`#mailAuswahlModal`), dann nacheinander. Ohne Speichern entsteht nichts.
- **Notiz je Aufgabe** (`t.notiz`, `plan_todos.notiz`, Migration
`migrateAddPlanTodoNotiz()`, in `IMPORT_OPTIONAL_COLS`): Feld „Notiz“ im
Aufgaben-Panel für jede Aufgabe; aus einer Mail mit Absender, Datum,
- Umschalter „Aufgabe | Meilenstein“: eigene Stile `.segmented.mail-auswahl-art`
und Klasse `mail-art-knopf` (sonst greift unter Glas
`html.glas button:not([class])` mit Rand und Glanzkante, und der gewählte
Knopf war von `var(--surface)` kaum zu unterscheiden).
- Jeder gewählte Vorschlag öffnet nacheinander das normale Panel
vorausgefüllt: `openTodoModal(null, prefill)` (`title`, `start`/`end`,
`assignees`, `aufwand`, `notiz`, `mailHinweis`, `nachSchliessen`) bzw.
`openMilestoneModal(null, prefill)` (`name`, `date` = Ende des Vorschlags,
`notiz`, `mailHinweis`, `nachSchliessen`). Ohne Speichern entsteht nichts.
- **Notiz je Aufgabe und Meilenstein** (`t.notiz`/`m.notiz`,
`plan_todos.notiz`/`plan_milestones.notiz`, Migration
`migrateAddPlanTodoNotiz()`, in `IMPORT_OPTIONAL_COLS`): Feld „Notiz“ in
beiden Panels; aus einer Mail mit Absender, Datum,
Betreff, Bezugssatz und Mailtext. In der Aufgabenliste ein Symbol mit
Tooltip. Stile: Block „Termin aus Mail“ am Ende von `plan-styles.css`.
@@ -3458,8 +3472,8 @@ nur, wenn sich der Kommentar DES GEWÄHLTEN GEWERKS gegenüber der Sitzung davor
geändert hat; Sitzungen dazwischen als grauer Vermerk („KW 40 bis KW 43 ohne
neuen Kommentar“); geänderte Leistungsphasen seit dem vorigen Eintrag als
Chips („LPH 5: 60 → 75 %“); Verfasser und Datum; „übernehmen“
(`berichtVerlaufUebernehmen()` → `takeCommentFromHistory()`); drei
sichtbar, Rest über „Alle N anzeigen“ (`berichtVerlaufUmschalten()` zeichnet
(`berichtVerlaufUebernehmen()` → `takeCommentFromHistory()`); gezeigt wird,
was geladen ist, „Weitere laden“ holt ältere (`berichtVerlaufNeu()` zeichnet
nur den Abschnitt neu, ein angefangener Kommentar bleibt). Die dichte Karte
bekommt dafür `opts.ohneVerlauf` (`renderBoardDrawer()`), damit der
Kachel-Streifen dort nicht zusätzlich erscheint. Projektliste und
@@ -4168,6 +4182,94 @@ across Gewerke or users. Erik's own words: "macht so keinen Sinn mehr."
no special-casing needed. A redirect `GET /Test.html`-style old link isn't
needed here since nothing ever pointed at this tab before it existed.
## Kalender-Abo für Outlook (iCalendar)
Eriks Wunsch: die eigenen Termine in Outlook sehen, nur zum Lesen.
Einstellungen → Mein Konto → Karte „Kalender in Outlook“
(`renderSettingsKalender()`, `js/settings/settings.js`): Adresse erzeugen,
kopieren, „In Outlook öffnen“ (`webcal:`-Link), Anleitung, „Neue Adresse“
(alte wird ungültig), „Abschalten“.
- **Server:** `users.kalender_token` (Migration in `migrateUsersDepartment()`,
leer = aus). `GET`/`POST /mein-kalender` (requireAuth, nur eigenes Konto;
POST `{aus:true}` schaltet ab, sonst neue Adresse). **`GET
/kalender/<token>.ics` ist ohne Sitzung erreichbar** — der Token (24 Byte
zufällig) IST die Anmeldung, weil Outlook sich nicht einloggen kann.
Unbekannt, deaktiviertes Konto oder Rolle ohne „Anwendung nutzen“ → 404.
- **Inhalt** (`kalenderEreignisse()`, rein und testbar): aus der jüngsten
echten Sitzung die eigenen offenen Aufgaben mit Termin (ohne erledigte und
ohne Phasenbalken), Meilensteine der Projekte, denen man zugeordnet ist
oder in denen man eine eigene Aufgabe hat, dazu die eigenen
Abwesenheiten. Alles ganztägig (`VALUE=DATE`, DTEND = Tag danach), als
„frei“ (Abwesenheit „abwesend“). Notizen gehen in die Beschreibung.
`kalenderText()` maskiert nach RFC 5545 und faltet Zeilen auf 75 Byte.
- **Adresse** (`kalenderUrl()`): der Host, über den der Browser die App
aufruft — wer über `localhost` öffnet, bekommt eine `localhost`-Adresse
(eine automatische Ersetzung durch die Geräte-IP hat Erik verworfen, weil
sie im Docker-Container nicht verlässlich ist).
- **Grenze:** Neues Outlook / Outlook im Browser holen das Abo über
Microsoft-Server aus dem Internet ab — dafür muss der Server von außen
erreichbar sein. Das klassische Outlook am PC holt es im Firmennetz direkt.
## Teams: Nachricht bei neuen Aufgaben
Eriks Wunsch: Wird jemandem eine Aufgabe zugewiesen, erscheint in einem
Teams-Kanal eine Karte mit Link direkt auf die Aufgabe.
- **Einrichten:** In Teams im Kanal *Workflows* → „Beim Empfang einer
Webhook-Anfrage in einem Kanal posten“; die erzeugte https-Adresse unter
**Admin → Teams** eintragen (`loadAdminTeams()`, `js/admin/admin.js`,
Recht **`a.teams`**, neu im Katalog „Admin-Bereich“), „Testnachricht
senden“. Anleitung steht aufklappbar in der Karte.
- **Speicher:** Tabelle `integrationen` (schluessel/wert; `teams_webhook`,
`teams_neue_aufgaben`), bewusst NICHT `app_settings` (das darf jedes
Konto lesen) und nicht in `IMPORT_TABLES`. Die Adresse geht nie an den
Browser zurück — `GET /admin/teams` liefert nur `konfiguriert`/`host`.
`PUT /admin/teams {webhook?, neueAufgaben?}` (nur https), `POST
/admin/teams/test` (Fehler als `{ok:false}` mit HTTP 200).
- **Auslöser:** `neueZuweisungenMelden()` (aus `/save-week` und
`/save-projects`, jetzt mit `req`) sammelt je Aufgabe die NEU Zuständigen
— auch Personen ohne Konto, nicht wer sich selbst zuweist, keine
Phasenbalken, nie die Sandbox — und `teamsNeueAufgaben()` schickt je
Aufgabe EINE Adaptive Card (`teamsKarte()`: Titel, Projekt, Zuständig,
Fällig/Zeitraum, Aufwand, Zugewiesen von, Knopf „Aufgabe öffnen“). Im
Hintergrund; Fehler nur im Log, das Speichern scheitert nie daran.
- **Link** (`aufgabenLink()`): `/Projektplan/<Name>?weekLabel=…&projNr=…
&aufgabe=<todoId>#aufgaben`, Host aus der speichernden Anfrage (= Adresse
der App im Büro; funktioniert nur im Büronetz/VPN). Im Browser liest
`plan-state.js` `aufgabe` beim Laden (`startAufgabe`): öffnet die
JÜNGSTE Sitzung, falls der Link eine ältere nennt (Aufgaben-ids bleiben
beim Sitzungswechsel gleich), springt nach „Aufgaben“ und öffnet das
Aufgaben-Panel; Adresszeile wird nachgezogen.
- Braucht Internetzugang nach außen (Microsoft-Server).
## Sitzungen nach Bedarf laden (`js/store.js`)
Eriks Wunsch: `/load-all` wuchs jede Woche (alle Sitzungen, ~17 KB je
Woche). Jetzt lädt der Browser beim Start nur die jüngsten
`SITZUNGEN_START` (4: aktuelle + drei davor) über `GET /load-all?wochen=4`;
dieselbe Antwort trägt `wochenListe` = ALLE echten Sitzungen schlank
(`{id, label, date, locked, updatedAt}`, `echteSitzungenSortiert()`), im
Browser `db.wochenListe`. **`db.weeks` hält damit nur die GELADENEN
Sitzungen** — wer über `db.weeks` iteriert, sieht nicht alle.
- **Verlauf** (Wochenbericht „Verlauf der Vorwochen“, Kommentar-Verlauf im
Projekt-Dashboard, Verlauf-Kacheln der Projektliste): zeigt, was geladen
ist; darunter `weitereLadenKnopf()` → `weitereSitzungenLaden()` →
`GET /weeks-aelter?vor=<älteste geladene>&anzahl=5` holt je
`SITZUNGEN_NACHLADEN` (5) ältere. Die früheren Knöpfe „Alle N anzeigen“
sind entfallen. Der Wochenbericht zeichnet danach nur den Verlauf neu
(`berichtVerlaufNeu()`), damit ein angefangener Kommentar bleibt; der
Kachel-Verlauf merkt sich, ob er aufgeklappt war (`verlaufOffen`).
- **Nicht geladene Sitzungen bei Bedarf**: `sitzungSicherLaden(label)` (über
`GET /week`). Genutzt von Admin → Sitzungen (Liste aus `adminSitzungen()`
= alle; Entsperren/Öffnen/Löschen/Sperren nehmen jetzt das Label) und von
`openPlanView()` bei einem Link auf ein Projekt einer älteren Sitzung.
- Live: Ein Ereignis zu einer nicht geladenen Sitzung zieht nur deren Eintrag
in `db.wochenListe` nach, statt alles neu zu laden. `deleteWeekById()`
zählt für „mindestens eine Sitzung“ die `wochenListe`.
- `/load-all` ohne `wochen` liefert weiterhin alles (ältere Browser-Stände).
`loadWeeks()` nimmt dafür auch eine Liste von Labels (`IN (…)`).
- Gemessen: 98 KB statt 158 KB bei 9 Sitzungen; bleibt künftig etwa gleich,
statt mit jeder Woche zu wachsen.
## Gleichzeitiges Arbeiten (Zusammenführen statt Verwerfen, `js/store.js`)
Eine Sitzung wird weiterhin als Ganzes über `POST /save-week` mit
@@ -4324,6 +4426,17 @@ Rechner.
kein `backdrop-filter` — eigenes Design, Kiosk-Rechner) und
`prefers-reduced-transparency` (deckende Flächen).
## Datums-Kalender mit KW (`js/date-picker.js`)
Eriks Wunsch: Jeder Kalender beim Anlegen/Bearbeiten zeigt die
Kalenderwoche. `renderRangePicker()` hat eine erste Spalte „KW“
(`getWeekNumber()`, `.range-picker-kw`, Popup 316px breit). Native
`<input type="date">` (Feiertage, Abwesenheiten in der Benutzerverwaltung,
„Aufgabe aus Kommentar“, Assistent, TODOs aus Protokoll) öffnen über einen
globalen Klick-Abfänger denselben Kalender statt des Browser-Kalenders; der
Wert wird ins Feld geschrieben und `input`/`change` ausgelöst. Ausnahme per
Attribut `data-nativ`.
## Dunkelmodus nachgeschärft (Kalender, Sitzungskommentar)
Eriks Wahl „A“ aus einem Kontrast-Vergleich; Block „DUNKELMODUS
@@ -4460,8 +4573,8 @@ Speicherüberlauf. Vier Ebenen, jede fängt ab, was die darüber verpasst:
(`wlan-energiesparen-aus.service`), `wlan-waechter.timer` jede Minute —
ohne Gateway ab 2 min neu verbinden, nach 10 min `systemctl reboot`.
- **Prüfen:** `Docker/pi-pruefen.sh` (Speicher, Neustarts, OOM, Strom).
- Offen (Schritt 4 des Plans): `/load-all` liefert weiter ALLE Sitzungen —
wächst wöchentlich auf Server und Browser.
- **Sitzungen nach Bedarf** (Schritt 4, umgesetzt 30.10.2026): siehe
„Sitzungen nach Bedarf laden“ unten.
## Docker