Files
dogfather-universe/server/workspace-zentrale.js
T
DogFatherGit ec55b6c8d3 WIP Zentrale-Kachel nach VanVans Werktisch — NOCH NICHT FERTIG
Aufbau steht (Ring links, Titel mittig, Uhr rechts), aber
pruef-start-ansicht meldet 3 Befunde am MAUS-LICHT der Kacheln.
Gemessen: alter Stand 0 Befunde, dieser Stand 3 — kommt also von hier.
Kein JS-Fehler (pageerror/console sind still), also Zeitverhalten:
Die zusaetzliche Abfrage /api/zentrale verzoegert vermutlich den Aufbau
der Kacheln ueber den Zeitpunkt hinaus, an dem kopf.js lichtFolgen ruft.

Ausserdem offen: 2 Schriftgroessen unter 11,5 px (pruef-css-klassen).

NICHT ausliefern.
2026-09-08 01:44:36 +02:00

183 lines
7.9 KiB
JavaScript

/* =====================================================================
workspace-zentrale.js — die Zahlen fuer den Ring auf der Startseite.
Wunsch Filipe (08.09.2026): die Begruessungskachel nach dem Vorbild
von VanVans Business Hub umbauen -- grosser Ring links, Text in der
Mitte, Uhr rechts. Dort zeigt der Ring "91 % deiner Produktakten":
ein Segment je Produkt, eingefaerbt nach Vollstaendigkeit.
---------------------------------------------------------------------
WARUM ES DIESELBE ZAHL HIER NICHT GIBT
Nachgemessen an der echten Datenbank am 08.09.2026:
Termine 31 · Wissen 7 · Schulungen 4 · Personen 9 · AUFGABEN 1
Leistung, Leads, Profile, Content-Saeulen, Dateien: jeweils 0
VanVans Kranz funktioniert, weil 253 Produkte ein BILD ergeben. Ein
Kranz aus einer Aufgabe ergibt keins. Die naheliegende Uebertragung
("dann eben Aufgaben") haette eine leere Scheibe erzeugt, die aussieht
wie ein Ladefehler.
Filipe hat deshalb DAS TEAM gewaehlt: ein Segment je Person, gefaerbt
danach, wie es um sie steht. Neun Segmente sind wenige, aber sie sind
ECHT und aendern sich taeglich -- und die Zahl waechst mit dem Team.
---------------------------------------------------------------------
UND WARUM DAS NICHT FUER JEDEN GEHT
Ein Creator darf die Teamliste nicht sehen. Das ist keine
Feinheit, sondern die Regel, die diesen ganzen Arbeitsplatz traegt
(Wunsch Filipe, 07.09.2026: "jeder soll und darf im kalender immer nur
seine eigenen eintraege nur sehen"). Ein Ring, der neun Namen
einfaerbt, waere fuer einen Creator ein Leck -- er koennte daran
ablesen, wer heute Termine hat und wer nicht.
Deshalb liefert diese Route ZWEI VERSCHIEDENE RINGE, je nach Rolle:
Leitung (DogFather, Manager, Scout, Spicy)
-> das Team: ein Segment je aktiver Person
Creator -> der eigene Tag: ein Segment je Stunde, plus die
eigenen Termine als Marken
Das ist keine Notloesung, sondern die richtige Antwort auf dieselbe
Frage: "Wie weit ist das, wofuer ich zustaendig bin?" Fuer DogFather
ist das sein Team, fuer eine Creatorin ihr Tag.
KEINE EIGENEN DATEN, wie bei den Hinweisen: Alles hier ist eine SICHT
auf Zeilen, die ohnehin existieren. Es gibt nichts zu pflegen und
nichts, was veralten kann.
===================================================================== */
import express from "express";
import { db, sitzungLesen, istLeitung, betreuteIds } from "./workspace.js";
export const zentraleRouter = express.Router();
function angemeldet(req, res, next) {
const person = sitzungLesen(req);
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
req.person = person;
next();
}
zentraleRouter.use("/workspace/api/zentrale", angemeldet);
const p2 = (n) => String(n).padStart(2, "0");
/* ORTSZEIT, NICHT UTC. toISOString() liefert zwischen Mitternacht und
zwei Uhr noch den Vortag -- der Ring stuende dann auf den Terminen
von gestern. Derselbe Fehler ist mir am 08.09. zweimal an einem Abend
passiert; pruef-struktur.mjs sucht ihn inzwischen im ganzen Repo. */
function heuteLokal() {
const d = new Date();
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
}
zentraleRouter.get("/workspace/api/zentrale", (req, res) => {
try {
const d = db();
const heute = heuteLokal();
const ich = req.person;
/* Wessen Lage wird gezeigt? Bei aktiver Fremdsicht die der Person,
in die man hineinsieht -- sonst zeigte die Kachel die eigene Lage
und alles darunter eine fremde. Genau diese Uneinheitlichkeit hat
auf der Startseite schon einmal zu "ist das jetzt meins oder
ihrs?" gefuehrt. */
const zeigt = req.person.sicht || req.person;
if (istLeitung(ich)) {
/* ---------- DAS TEAM ----------
Ein Segment je aktiver Person. Der Zustand einer Person ist
bewusst GROB in drei Stufen -- mehr taeuschte Genauigkeit vor,
die es nicht gibt (dieselbe Ueberlegung steht in VanVans Kranz).
frei - nichts Offenes, keine Termine heute -> ruhig
dran - hat heute Termine -> die sind versorgt
offen - hat unerledigte Termine aus der VERGANGENHEIT
-> das ist das, was liegen bleibt
*/
const leute = d.prepare(
`SELECT id, name, rolle FROM personen WHERE aktiv = 1 ORDER BY rolle, name`).all();
const heuteZaehler = d.prepare(
`SELECT COUNT(*) AS n FROM termine t
WHERE t.erledigt = 0 AND substr(t.beginn, 1, 10) = ?
AND (t.creator_id = ? OR t.erstellt_von = ?
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
WHERE x.termin_id = t.id AND x.person_id = ?))`);
const altZaehler = d.prepare(
`SELECT COUNT(*) AS n FROM termine t
WHERE t.erledigt = 0 AND substr(t.beginn, 1, 10) < ?
AND (t.creator_id = ? OR t.erstellt_von = ?
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
WHERE x.termin_id = t.id AND x.person_id = ?))`);
const segmente = leute.map((p) => {
const heuteN = heuteZaehler.get(heute, p.id, p.id, p.id).n;
const altN = altZaehler.get(heute, p.id, p.id, p.id).n;
const stufe = altN > 0 ? "offen" : (heuteN > 0 ? "dran" : "frei");
/* NUR DER VORNAME nach aussen. Der Ring braucht ein Kuerzel fuer
die Sprechblase, nicht den Datensatz einer Person. */
return { name: String(p.name || "").trim().split(/\s+/)[0] || "?",
rolle: p.rolle, stufe, heute: heuteN, alt: altN };
});
const versorgt = segmente.filter((s) => s.stufe !== "offen").length;
return res.json({
art: "team",
titel: "Team versorgt",
prozent: segmente.length ? Math.round((versorgt / segmente.length) * 100) : 100,
segmente,
zahlen: [
{ wert: segmente.length, schild: "im Team" },
{ wert: segmente.filter((s) => s.stufe === "dran").length, schild: "heute dran" },
{ wert: segmente.filter((s) => s.stufe === "offen").length, schild: "liegt liegen" },
],
});
}
/* ---------- DER EIGENE TAG ----------
Fuer Creator. Ein Segment je Stunde von 6 bis 24 -- der Teil des
Tages, in dem gearbeitet wird. Nachts einen leeren Halbkreis zu
zeigen sagt nichts; 18 Segmente fuellen den Ring ordentlich.
Gefuellt sind die vergangenen Stunden, markiert die mit einem
eigenen Termin. Der Ring beantwortet damit "wie weit ist mein
Tag" -- dieselbe Frage wie bei VanVan, nur auf das bezogen,
wofuer eine Creatorin zustaendig ist. */
const VON = 6, BIS = 24;
const jetzt = new Date();
const stundeJetzt = jetzt.getHours();
const meine = d.prepare(
`SELECT t.beginn FROM termine t
WHERE substr(t.beginn, 1, 10) = ?
AND (t.creator_id = ? OR t.erstellt_von = ?
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
WHERE x.termin_id = t.id AND x.person_id = ?))`)
.all(heute, zeigt.id, zeigt.id, zeigt.id);
const mitTermin = new Set(meine.map((t) => Number(String(t.beginn).slice(11, 13))));
const segmente = [];
for (let s = VON; s < BIS; s++) {
segmente.push({ name: `${p2(s)} Uhr`, rolle: "stunde",
stufe: mitTermin.has(s) ? "dran" : (s < stundeJetzt ? "frei" : "offen") });
}
const durch = Math.min(Math.max(stundeJetzt - VON, 0), BIS - VON);
return res.json({
art: "tag",
titel: "Tag geschafft",
prozent: Math.round((durch / (BIS - VON)) * 100),
segmente,
zahlen: [
{ wert: meine.length, schild: "heute" },
{ wert: [...mitTermin].filter((s) => s >= stundeJetzt).length, schild: "kommt noch" },
{ wert: betreuteIds(zeigt).length, schild: "betreut" },
],
});
} catch (fehler) {
console.error("[workspace] Zentrale:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});