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.
This commit is contained in:
@@ -26,6 +26,7 @@ import { reportRouter } from "./workspace-reports.js";
|
||||
import { scoutRouter } from "./workspace-scouts.js";
|
||||
import { callRouter } from "./workspace-calls.js";
|
||||
import { hinweisRouter } from "./workspace-hinweise.js";
|
||||
import { zentraleRouter } from "./workspace-zentrale.js";
|
||||
import { sucheRouter } from "./workspace-suche.js";
|
||||
import { startcheckRouter } from "./workspace-startcheck.js";
|
||||
import { wissenRouter } from "./workspace-wissen.js";
|
||||
@@ -216,6 +217,7 @@ app.use(reportRouter);
|
||||
app.use(scoutRouter);
|
||||
app.use(callRouter);
|
||||
app.use(hinweisRouter);
|
||||
app.use(zentraleRouter);
|
||||
app.use(sucheRouter);
|
||||
app.use(startcheckRouter);
|
||||
app.use(wissenRouter);
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/* =====================================================================
|
||||
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" });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user