Auf der Startseite von Creator, rechter Hand und Modis ist der Ring das
groesste Element, ganz oben. Er rechnete:
prozent = (aktuelle Stunde - 6) / 18
...und nannte das "Tag geschafft". Um 9 Uhr also 17 %, um 23 Uhr 94 %
-- unabhaengig davon, ob jemand etwas getan hatte.
Aufgefallen auf einem Bildschirmfoto: Bei einer Modi stand "0 % TAG
GESCHAFFT", waehrend sie ueberhaupt nichts offen hatte. Beide Angaben
waren fuer sich richtig und zusammen Unsinn -- und es war das Erste,
was sie sah.
JETZT ZAEHLT ER ARBEIT. Nenner ist, was heute auf dem Tisch liegt:
heute faellig ODER laenger offen. Die naheliegende Rechnung ueber "nur
heute faellig" waere derselbe Fehler noch einmal gewesen -- bei drei
ueberfaelligen Sachen haette der Ring "nichts faellig" gemeldet.
Zaehler ist, was HEUTE fertig wurde (erledigt_am), nicht was irgendwann
abgehakt wurde. Liegt nichts an: voller Ring, und der Titel sagt es in
Worten statt nur eine 100 zu zeigen.
Gemessen danach, dieselbe Seite: "33 % Heute geschafft", darunter
"2 Sachen sind ueberfaellig -- das zuerst". Die Seite erzaehlt jetzt
eine Geschichte statt zweier.
NEU: pruef-zentrale-ring (10 Pruefungen)
Drei Lagen, drei Menschen: etwas anliegend und teils fertig / nichts
anliegend / drei ueberfaellige und nichts fertig. Dazu zwei
Abgrenzungen (morgen faellig und vorgestern erledigt duerfen nicht
mitzaehlen) und die Gegenprobe: zwischen zwei Abfragen wird eine
Aufgabe fertig gemacht, der Ring MUSS springen -- er tut es, 0 % -> 33 %.
Der letzte Abschnitt rechnet die alte Uhrzeit-Formel mit und meldet,
falls der Ring je wieder auf ihren Wert faellt.
NEU: mess-startseite -- ein Messwerkzeug, kein Pruefwerkzeug
Es sagt nicht richtig/falsch, sondern wie viel da ist: Seitenlaenge in
Handy-Bildschirmen, Bedienelemente, Woerter, Ring, je Rolle, mit
echten Aufgaben statt im leeren Zustand. Gemessener Stand:
admin 7,0 Schirme / 58 Bedienelemente hand 5,7 / 43
modi 5,0 / 37 manager 4,9 / 38
scout 4,5 / 34 creator 4,3 / 32
pruef-sicht, pruef-verborgen, pruef-modi-verborgen und
pruef-haus-trennung laufen unveraendert durch -- die neue Abfrage
folgt der fremden Sicht ueber dieselbe Variable wie der Rest.
Co-Authored-By: Claude Opus 5 <[email protected]>
330 lines
16 KiB
JavaScript
330 lines
16 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, scoutsVon, verborgeneIds, hausBedingung,
|
|
TEAM_DOGI_ROLLEN } 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();
|
|
/* WESSEN LAGE WIRD GEZEIGT? -- EINE VARIABLE, NICHT ZWEI.
|
|
|
|
Hier standen bis zum 11.09.2026 ZWEI: `ich` (immer der
|
|
Angemeldete) fuer den Ring, und `zeigt` fuer die drei Zahlen
|
|
darunter. Gedacht war, dass beide der fremden Sicht folgen; im
|
|
Code stand dafuer `req.person.sicht` -- ein Feld, das es nicht
|
|
gibt. Der Ausdruck war also immer `undefined || req.person`.
|
|
|
|
Ergebnis: Sah DogFather sich den Arbeitsplatz eines Modis an,
|
|
zeigte der Ring seine eigene Lage ("Team versorgt", alle Creator
|
|
des Hauses), waehrend darunter die Zahlen der anderen Person
|
|
standen. Zwei Wahrheiten in einer Kachel -- und weil ein Tippfehler
|
|
in einer Variablen nichts kaputt macht, sondern nur nichts tut,
|
|
faellt so etwas nie von selbst auf. Gefunden, indem die Antwort in
|
|
fremder Sicht mit der verglichen wurde, die die Person SELBST
|
|
bekommt.
|
|
|
|
Deshalb jetzt genau ein Name fuer genau eine Frage. Alles in
|
|
dieser Route ist Anzeige, kein Recht: Es gibt nichts zu schreiben
|
|
und nichts zu pruefen. Und weil DogFather ohnehin alles sehen
|
|
darf, kann die Sicht hier nur WENIGER zeigen, nie mehr. */
|
|
const zeigt = req.sicht || req.person;
|
|
|
|
/* ================================================================
|
|
WESSEN TEAM? JEDE ROLLE SIEHT IHR EIGENES (08.09.2026)
|
|
|
|
Filipe, nachdem Managerin Schulle "2 Creator" angezeigt bekam,
|
|
obwohl sie einen hat: "die zahl die da angezeigt wird soll bitte
|
|
immer jedem genau zutreffend sein" -- und dazu, wer was sieht:
|
|
|
|
"dogfather und cigdem haben die zahl vom insgesamten. manager
|
|
sehen nur die gesamte zahl ihrer scouts und ihren creator die
|
|
ihnen zugeteilt sind, die scout sehen die zahl nur von ihren
|
|
creator und die creator da termine vom tag selber"
|
|
|
|
Vorher stand hier `SELECT ... FROM personen WHERE aktiv = 1` --
|
|
ALLE, fuer jeden aus der Leitung gleich. Deshalb sah Schulle das
|
|
ganze Haus statt ihres Teams. Die Zahl war nicht falsch berechnet,
|
|
sie beantwortete die falsche Frage.
|
|
|
|
Die Zuordnung selbst wird NICHT hier nachgebaut: `betreuteIds`
|
|
kennt die Kette Manager -> Scouts -> deren Creator bereits, und
|
|
`scoutsVon` die Scouts. Eine zweite Rechenvorschrift fuer
|
|
dieselbe Frage waere genau der Weg, auf dem zwei Wahrheiten
|
|
entstehen.
|
|
|
|
SCOUTS BEKOMMEN JETZT AUCH DIESEN RING. Sie zaehlen nicht zur
|
|
Leitung und sahen deshalb den Stundenring des eigenen Tages --
|
|
aber ein Scout hat ein Team, naemlich seine Creator. Genau danach
|
|
hat Filipe gefragt. */
|
|
const istTeamsicht = istLeitung(zeigt) || zeigt.rolle === "scout";
|
|
|
|
if (istTeamsicht) {
|
|
/* ---------- DAS TEAM ----------
|
|
Ein Segment je Person im eigenen Verantwortungsbereich. 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
|
|
*/
|
|
let leute;
|
|
if (zeigt.rolle === "admin" || zeigt.rolle === "spicy") {
|
|
/* Das ganze Haus -- und zwar ohne sich selbst: Wer den Ring
|
|
ansieht, ist die Person, die ihn liest; sich selbst als
|
|
Segment im eigenen Team zu zaehlen, verschiebt jede Prozent-
|
|
angabe um einen Platz. */
|
|
/* AUCH HIER GILT DIE VERBERGUNGSREGEL (09.09.2026).
|
|
|
|
Diese Abfrage geht als einzige NICHT durch die zentralen
|
|
Listenfunktionen -- sie holt sich das Haus selbst. Genau
|
|
deshalb war sie die eine Stelle, an der Spicy Media VanVan
|
|
noch gesehen hat, nachdem alle anderen Wege zu waren.
|
|
`server/pruef-verborgen.mjs` hat es beim ersten Lauf
|
|
gemeldet; ohne die Pruefung waere es nicht aufgefallen, denn
|
|
im Ring steht nur ein farbiges Segment mit einem Namen in
|
|
der Sprechblase. */
|
|
const weg = verborgeneIds(zeigt);
|
|
const zusatz = weg.length ? ` AND id NOT IN (${weg.map(() => "?").join(",")})` : "";
|
|
/* UND AUF DER TEAM-ADRESSE NUR DAS TEAM (10.09.2026).
|
|
|
|
Genau die Stelle, auf die Filipe im Bildschirmfoto gezeigt
|
|
hat: Im Ring stand "9 IM TEAM", obwohl sein Team drei Leute
|
|
hat -- gezaehlt wurde das ganze Haus. Der Kommentar darueber
|
|
sagt es selbst: Diese Abfrage ist die einzige, die sich die
|
|
Menschen selbst holt, statt durch die Listenfunktionen zu
|
|
gehen. Wer dort etwas aendert, muss hier daran denken; die
|
|
Bedingung kommt deshalb aus derselben Funktion. */
|
|
leute = d.prepare(
|
|
`SELECT id, name, rolle FROM personen
|
|
WHERE aktiv = 1 AND id <> ?${zusatz}${hausBedingung(zeigt)} ORDER BY rolle, name`)
|
|
.all(zeigt.id, ...weg);
|
|
} else {
|
|
/* Manager: seine Scouts UND die Creator (eigene wie die seiner
|
|
Scouts). Scout: nur seine Creator. */
|
|
const ids = [...new Set([
|
|
...(zeigt.rolle === "manager" ? scoutsVon(zeigt.id) : []),
|
|
...betreuteIds(zeigt),
|
|
])].filter((id) => id !== zeigt.id);
|
|
|
|
leute = ids.length
|
|
? d.prepare(
|
|
`SELECT id, name, rolle FROM personen
|
|
WHERE aktiv = 1 AND id IN (${ids.map(() => "?").join(",")})
|
|
ORDER BY rolle, name`).all(...ids)
|
|
: [];
|
|
}
|
|
|
|
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;
|
|
|
|
/* Ein Scout fuehrt Creator, kein "Team" im Sinne des Hauses. Das
|
|
Wort auf dem Ring sagt deshalb, WEN er zaehlt -- sonst liest ein
|
|
Scout "Team versorgt" und sucht die anderen vier Rollen darin. */
|
|
const nurCreator = zeigt.rolle === "scout";
|
|
|
|
return res.json({
|
|
art: "team",
|
|
titel: nurCreator ? "Creator versorgt" : "Team versorgt",
|
|
prozent: segmente.length ? Math.round((versorgt / segmente.length) * 100) : 100,
|
|
segmente,
|
|
zahlen: [
|
|
{ wert: segmente.length, schild: nurCreator ? "meine Creator" : "im Team" },
|
|
{ wert: segmente.filter((s) => s.stufe === "dran").length, schild: "heute dran" },
|
|
/* "liegt liegen" stand hier und war nicht zu verstehen -- Filipe
|
|
wortwoertlich: "keine ahnung was das bedeuten soll". Gezaehlt
|
|
werden Personen mit unerledigten Terminen aus der
|
|
VERGANGENHEIT. "Ueberfaellig" sagt das in einem Wort und ist
|
|
der Begriff, der auch sonst im Haus dafuer steht. */
|
|
{ wert: segmente.filter((s) => s.stufe === "offen").length, schild: "überfällig" },
|
|
],
|
|
});
|
|
}
|
|
|
|
/* ---------- 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") });
|
|
}
|
|
/* ---------- DIE ZAHL IN DER MITTE (17.09.2026) ----------
|
|
|
|
HIER STAND DIE UHRZEIT, und sie nannte sich "Tag geschafft":
|
|
`(aktuelle Stunde - 6) / 18`. Um 9 Uhr also 17 %, um 23 Uhr
|
|
94 % -- unabhaengig davon, ob jemand auch nur eine Sache getan
|
|
hatte. Das groesste Element der Startseite, ganz oben, und es
|
|
beantwortete die Frage nicht, die es stellte.
|
|
|
|
Gesehen auf dem Bildschirmfoto eines Modis: "0 % TAG
|
|
GESCHAFFT", waehrend sie ueberhaupt nichts offen hatte. Beides
|
|
war "richtig" und zusammen war es Unsinn.
|
|
|
|
JETZT ZAEHLT ES ARBEIT. Nenner ist, was heute auf dem Tisch
|
|
liegt: was heute faellig ist ODER laenger offen steht -- denn
|
|
ein Ring, der bei drei ueberfaelligen Sachen "nichts faellig"
|
|
meldet, waere derselbe Fehler noch einmal. Zaehler ist, was
|
|
HEUTE fertig geworden ist (`erledigt_am`), nicht was irgendwann
|
|
einmal abgehakt wurde.
|
|
|
|
Liegt nichts an, sind es 100 % und der Titel sagt es in Worten.
|
|
Ein voller Ring ohne Erklaerung waere wieder eine Zahl, bei der
|
|
man raten muss. */
|
|
const meineAufgaben = d.prepare(
|
|
`SELECT status, frist, erledigt_am FROM aufgaben
|
|
WHERE verantwortlich_id = ?`).all(zeigt.id);
|
|
const nochOffen = (a) => ["offen", "arbeit", "review"].includes(a.status);
|
|
const offenDran = meineAufgaben.filter(
|
|
(a) => nochOffen(a) && a.frist && String(a.frist) <= heute).length;
|
|
const heuteFertig = meineAufgaben.filter(
|
|
(a) => String(a.erledigt_am || "").slice(0, 10) === heute).length;
|
|
const dran = offenDran + heuteFertig;
|
|
return res.json({
|
|
art: "tag",
|
|
titel: dran ? "Heute geschafft" : "Heute nichts offen",
|
|
prozent: dran ? Math.round((heuteFertig / dran) * 100) : 100,
|
|
segmente,
|
|
zahlen: [
|
|
{ wert: meine.length, schild: "heute" },
|
|
{ wert: [...mitTermin].filter((s) => s >= stundeJetzt).length, schild: "kommt noch" },
|
|
/* "BETREUT" IST FUER EINEN MODI IMMER NULL (10.09.2026).
|
|
|
|
Er betreut keine Creator -- das ist die Arbeit der Scouts und
|
|
Manager. Auf seiner Startseite stand deshalb dauerhaft "0
|
|
betreut": eine Zahl, die nie etwas anderes sagen kann, und
|
|
damit schlimmer als keine. Wer sie sieht, sucht nach dem
|
|
Fehler.
|
|
|
|
Stattdessen zaehlt sie, wie viele im Modi-Team sind. Das ist
|
|
die Zahl, die an dieser Stelle etwas bedeutet -- und sie
|
|
bekommt nur zu sehen, wer ohnehin weiss, dass es die Runde
|
|
gibt. */
|
|
TEAM_DOGI_ROLLEN.has(zeigt.rolle)
|
|
? { wert: db().prepare(
|
|
`SELECT COUNT(*) AS n FROM personen WHERE rolle IN (${
|
|
[...TEAM_DOGI_ROLLEN].map((r) => `'${r}'`).join(", ")}) AND aktiv = 1`).get().n,
|
|
schild: "im Team" }
|
|
: { wert: betreuteIds(zeigt).length, schild: "betreut" },
|
|
],
|
|
});
|
|
} catch (fehler) {
|
|
console.error("[workspace] Zentrale:", fehler?.message);
|
|
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
|
}
|
|
});
|