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.
183 lines
7.9 KiB
JavaScript
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" });
|
|
}
|
|
});
|