Verfuegbarkeits-Ampel im Eingang -- und eine eigene Gruppe fuer Team Dogi

Filipe: "ich will dass die kacheln von den modis bei der rolle dogfather
eine eigene kategorie haben. damit ich nicht zwischen den kacheln
suchen muss."

DIE AMPEL (Blueprint 7.1). Der Eingang zeigt je Person sieben Tage:
gruen frei, gelb belegt, rot voll ab 180 Minuten. Die Abfrage sammelt
Termine ueber VIER Wege (creator_id, teilnehmer_id, erstellt_von und
die Tabelle termin_teilnehmer) -- ueber nur einen davon waeren die
meisten Termine unsichtbar geblieben und die Ampel dauerhaft gruen.
Sie waehlt NIE titel, beschreibung oder ort: Filipe soll sehen, WANN
jemand kann, nicht WAS die Person vorhat. Belegung ist Arbeitslage,
Inhalt ist privat.

DIE GRUPPE. Die zwei Team-Kacheln standen zwischen einundzwanzig
anderen. Jetzt tragen sie `gruppe: "Team Dogi"` und `gruppeNach:
"Täglich"`; start.js setzt eine so markierte Gruppe direkt HINTER die
genannte statt ans Ende. Ohne das waere sie unten gelandet -- richtig
gruppiert und trotzdem zum Suchen.

Das Ideen-Board ist dabei aus workspace/assets/js/bereiche.js
ausgezogen. Es stand dort mit `rollen: ['admin']` in einer Datei, die
jeder Modi herunterlaedt: die Kachel war unsichtbar, ihr Name nicht.
Jetzt liefert der Server sie, wie den Eingang auch.

DREI FEHLER, DIE DER BILDSCHIRM GEZEIGT HAT, NICHT DER CODE:

  Das Profilbild sprengte die Karte. teamlage.js baute ein blankes
  <img> in `.tperson__zeichen` -- ohne die Klasse `tperson__bild`, die
  es auf 44 px begrenzt. Gemeldet hat es Filipe mit einem Bildschirm-
  foto, nicht eine Pruefung.

  Der Eingang hatte ueberhaupt keine Buehne. `zuSeite()` sucht nur in
  bereiche.js, und die Eingangs-Kachel kommt vom Server -- der Rueck-
  fall war ausgerechnet der Spicy-Wasserfall. Jetzt haengt das Bild an
  `data-buehne="eingang"` im CSS, wo kein Skript daran vorbeikommt.

  Pausierte Mitglieder verschwanden. `WHERE aktiv = 1` versteckte sie
  samt ihrer offenen Meldungen. Jetzt stehen sie hinten, sichtbar
  gekennzeichnet.

DIE ERWARTUNG IN pruef-start-ansicht steht auf FUENF Gruppen, in
ihrer Reihenfolge -- die Position ist hier die eigentliche Aussage.
Eine Gruppe, die ans Ende rutscht, faellt auf dem Bildschirm kaum auf.
Die Kachelzahl blieb bei 24: umgezogen, nichts hinzugefuegt.

pruef-team-ampel 28, pruef-team-stufen 24, pruef-rollen 274,
pruef-crew-adresse 123, pruef-modi-ideen 30, pruef-modi-wortleck 5,
pruef-zwischenspeicher 21, pruef-start-ansicht gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-10 18:08:27 +02:00
co-authored by Claude Opus 5
parent 241b408edc
commit 6b2f34d105
33 changed files with 870 additions and 280 deletions
+89
View File
@@ -232,6 +232,87 @@ teamlageRouter.get("/workspace/api/teamlage", (req, res) => {
WHERE e.bereich = 'angebote' AND e.status = 'offen'
ORDER BY e.bewertung DESC, e.datum DESC`).all();
/* =================================================================
DIE VERFUEGBARKEITS-AMPEL (Blueprint Kapitel 7.1, 10.09.2026)
"Persoenlicher Kalender (privat): Inhalt ausschliesslich fuer die
jeweilige Person sichtbar; Owner/rechte Hand sehen nur die
Verfuegbarkeits-Ampel, nicht den Klartext."
DIE ERSTE HAELFTE STAND SCHON: termineSichtbar() gibt jedem nur
seine eigenen Termine -- ausdruecklich auch DogFather. Es fehlte
genau die zweite: zu sehen, OB jemand kann, ohne zu sehen, WAS er
vorhat.
DIESE ABFRAGE HOLT NIEMALS titel, beschreibung ODER ort. Nicht
"sie zeigt sie nicht an" -- sie liest sie gar nicht erst. Was
nicht abgefragt wird, kann auch nicht versehentlich in einer
Antwort landen, wenn jemand spaeter ein Feld durchreicht.
WARUM DIE ZUGEHOERIGKEIT HIER NOCH EINMAL STEHT statt ueber
termineSichtbar(): Jene Funktion beantwortet die Frage fuer EINE
Person. Bei zehn Modis waeren das zehn Abfragen -- der Fehler,
den diese Datei zwei Bildschirme weiter oben ausdruecklich
vermeidet. Die Aufzaehlung unten ist dieselbe Regel als
Mengenvereinigung: angelegt von mir, mir zugeordnet, ich bin das
Gegenueber, oder ich stehe in der Teilnehmerliste.
DASS BEIDE DASSELBE SAGEN, IST GEMESSEN und nicht behauptet:
pruef-team-ampel.mjs stellt beide Wege nebeneinander. Ohne diese
Probe waere die Ampel eines Tages leise falsch -- und niemand
haette einen Anhaltspunkt, denn sie zeigt ja nur eine Farbe. */
const ampelTage = 7;
const bisTag = new Date(Date.parse(`${heute}T12:00:00Z`) + (ampelTage - 1) * 86400000)
.toISOString().slice(0, 10);
const belegt = new Map(); // person -> tag -> { termine, minuten }
{
const platzhalter = platz;
const zeilen = db().prepare(`
WITH meins AS (
SELECT creator_id AS pid, id FROM termine WHERE creator_id IN (${platzhalter})
UNION
SELECT teilnehmer_id AS pid, id FROM termine WHERE teilnehmer_id IN (${platzhalter})
UNION
SELECT erstellt_von AS pid, id FROM termine WHERE erstellt_von IN (${platzhalter})
UNION
SELECT tn.person_id AS pid, tn.termin_id FROM termin_teilnehmer tn
WHERE tn.person_id IN (${platzhalter})
)
SELECT m.pid AS p, date(t.beginn) AS tag,
COUNT(*) AS termine, SUM(t.dauer_min) AS minuten
FROM meins m JOIN termine t ON t.id = m.id
WHERE date(t.beginn) BETWEEN ? AND ?
GROUP BY m.pid, date(t.beginn)`)
.all(...nummern, ...nummern, ...nummern, ...nummern, heute, bisTag);
for (const z of zeilen) {
if (!belegt.has(z.p)) belegt.set(z.p, {});
belegt.get(z.p)[z.tag] = { termine: z.termine, minuten: z.minuten || 0 };
}
}
/* DIE DREI STUFEN.
Drei Stunden sind die Grenze zwischen "geht noch was" und "der
Tag ist verplant" -- ein Sendeabend dauert etwa so lang. Der Wert
ist ein Anfangswert und steht hier als Zahl mit Namen, damit man
ihn an EINER Stelle aendern kann, wenn sich das im Alltag anders
anfuehlt.
NULL MINUTEN HEISST "frei", nicht "unbekannt": Ein leerer
Kalender ist eine Aussage. Wer nichts eingetragen hat, ist
ansprechbar -- genau das will man wissen. */
const VOLL_AB_MIN = 180;
const ampelFuer = (eintrag) => {
if (!eintrag || !eintrag.minuten) return "frei";
return eintrag.minuten >= VOLL_AB_MIN ? "voll" : "teils";
};
const tagesliste = [];
for (let i = 0; i < ampelTage; i++) {
tagesliste.push(new Date(Date.parse(`${heute}T12:00:00Z`) + i * 86400000)
.toISOString().slice(0, 10));
}
/* Wie viele Rueckmeldungen an seinen Punkten haengen -- das ist
das Gespraech, nicht die Note. */
const nachrichten = new Map();
@@ -271,6 +352,14 @@ teamlageRouter.get("/workspace/api/teamlage", (req, res) => {
aktiv: !!m.aktiv,
seit: m.erstellt || null,
stufe: stufeVon(m.stufe),
/* NUR DIE AMPEL. Kein Titel, kein Ort, keine Beschreibung --
die stehen nicht einmal in der Abfrage. Die Zahl der
Termine und die belegten Minuten sagen, wie voll es ist,
ohne zu sagen, womit. */
verfuegbar: tagesliste.map((tag) => {
const e = (belegt.get(m.id) || {})[tag];
return { tag, ampel: ampelFuer(e), termine: e?.termine || 0, minuten: e?.minuten || 0 };
}),
aufgaben: aufgaben.get(m.id) || { offen: 0, ueberfaellig: 0 },
ideen: ideen.get(m.id) || 0,
nachrichten: nachrichten.get(m.id) || 0,