Im Code stand die Begruendung selbst: "«Review» allein sagt einem Neuen nichts." Das Wort ist englisch, ein Hauptwort ohne Handlung, und es verraet nicht, WER jetzt dran ist. "Zur Freigabe" sagt beides: fertig von mir, wartet auf jemanden. Geaendert wurde nur, was ein Mensch LIEST -- der Zustandsschluessel bleibt `review`, der gehoert der Datenbank. Betroffen: Spalte und Weiterknopf im Aufgabenbrett, Dateien-Filter, Startseiten-Zaehler, Kachel-Unterzeile, Hinweis "Datei wartet auf Freigabe", Report. NICHT geaendert: der Kalender. Dort ist `review` eine TERMINART (ein Gespraech, in dem man zurueckschaut), kein Zustand. Ich hatte das beim Umbenennen selbst verwechselt und wieder zurueckgenommen -- ein Kommentar an der Stelle haelt die zwei Bedeutungen jetzt auseinander. pruef-sprung hing an der Wortwahl (`/review/i` auf der Beschriftung) und wurde rot, obwohl der Filter richtig stand. Sie prueft jetzt `data-status` -- den Schluessel, der sich nicht mit der Sprache aendert. DAZU, unabhaengig gefunden: Datumsfelder waren 48 px hoch, alle anderen Felder 44. Gemessen auf drei Seiten bei 390 px. Alle liegen auf dem 44-px-Beruehrziel -- nur das Datumsfeld drueckte sich darueber, weil Chromium in `::-webkit-datetime-edit` eine eigene Innenpolsterung setzt, die sogar ein gesetztes `height: 44px` ueberstimmt. Zwei Anteile, einzeln nachgemessen (jeder allein 48->46, erst beide zusammen 48->44): 1 px Polsterung oben und unten im Feldkasten, und eine Zeilenhoehe von 24 statt 22. Keine feste Hoehe gesetzt -- die Zeilenhoehe wird aus Beruehrziel und Polsterung gerechnet, damit sie mitwandert, wenn sich eines davon aendert. Geprueft: pruef-formulare 19/0 (war 16 mit 3 Fehlern), pruef-sprung 43/0, pruef-start-ansicht 151/0, pruef-aufgabenbrett 49/0, pruef-uebersicht 35/0, pruef-uebersicht-browser 20/0, pruef-deutsche-texte, pruef-css-klassen. Ausserdem: vorlagen.js geloescht (8,2 KB). `vorlagenBlock(` wurde inca104799eingebaut und in9267797dwieder ausgebaut -- seither laedt die Datei auf zwei Seiten, ohne dass jemand sie aufruft. Co-Authored-By: Claude Opus 5 <[email protected]>
408 lines
18 KiB
JavaScript
408 lines
18 KiB
JavaScript
/* =====================================================================
|
||
workspace-hinweise.js — "Was ist dran?" (Konzept, Phase 4).
|
||
|
||
Der erste Baustein der Automationen. Bis hierher musste man selbst
|
||
daran denken, in den richtigen Bereich zu schauen. Diese Datei dreht
|
||
das um: Das System sagt, was liegen bleibt.
|
||
|
||
Drei Regeln, an die sich das hier haelt:
|
||
|
||
1. KEINE eigenen Daten. Ein Hinweis ist immer nur eine Sicht auf
|
||
etwas, das ohnehin schon existiert. Wird die Aufgabe erledigt,
|
||
verschwindet der Hinweis von selbst -- es gibt nichts zu quittieren
|
||
und nichts, was veralten kann.
|
||
|
||
2. JEDER HINWEIS FUEHRT IRGENDWOHIN. Ein Hinweis ohne Ziel ist nur
|
||
ein schlechtes Gewissen. Deshalb traegt jeder einen Link zu der
|
||
Stelle, an der man das Problem tatsaechlich loesen kann.
|
||
|
||
3. DIESELBE SICHTBARKEIT WIE UEBERALL. Die Regeln werden aus den
|
||
Fachmodulen importiert, nicht abgeschrieben. Ein Hinweis darf
|
||
niemals etwas verraten, das die zugehoerige Seite verbergen wuerde
|
||
-- sonst waere die Uebersicht ein Leck.
|
||
===================================================================== */
|
||
|
||
import express from "express";
|
||
import {
|
||
db, sitzungLesen, betreuteIds, istLeitung, istDogFather, siehtAlles, pipelineIds,
|
||
gehoertAufDieseAdresse,
|
||
} from "./workspace.js";
|
||
import { darfSeite } from "./rechte.js";
|
||
import { sichtbar as sichtbarAufgaben } from "./workspace-aufgaben.js";
|
||
import { sichtbar as sichtbarTermine } from "./workspace-kalender.js";
|
||
import { sichtbar as sichtbarDateien } from "./workspace-dateien.js";
|
||
import { sichtbar as sichtbarEintraege } from "./workspace-bereiche.js";
|
||
|
||
export const hinweisRouter = express.Router();
|
||
|
||
/* Reihenfolge = Dringlichkeit. "warnung" steht immer oben. */
|
||
const RANG = { warnung: 0, offen: 1, ruhig: 2 };
|
||
|
||
function angemeldet(req, res, next) {
|
||
const person = sitzungLesen(req);
|
||
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
||
req.person = person;
|
||
next();
|
||
}
|
||
|
||
hinweisRouter.use("/workspace/api/hinweise", angemeldet);
|
||
|
||
const p2 = (n) => String(n).padStart(2, "0");
|
||
function heuteLokal() {
|
||
const d = new Date();
|
||
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
||
}
|
||
function jetztLokal() {
|
||
const d = new Date();
|
||
return `${heuteLokal()}T${p2(d.getHours())}:${p2(d.getMinutes())}`;
|
||
}
|
||
function inTagen(n) {
|
||
const d = new Date(Date.now() + n * 86400_000);
|
||
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
||
}
|
||
|
||
/* Kleine Hilfe: eine Zahl abfragen, Fehler eines einzelnen Hinweises
|
||
duerfen nie die ganze Uebersicht kippen. */
|
||
function zaehle(sql, werte) {
|
||
try {
|
||
return db().prepare(sql).get(...werte)?.n ?? 0;
|
||
} catch {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
hinweisRouter.get("/workspace/api/hinweise", (req, res) => {
|
||
try {
|
||
/* Die gewaehlte Sicht (nur DogFather, sonst er selbst). Waeren die
|
||
Hinweise davon ausgenommen, staende auf der Startseite die eigene
|
||
Lage, waehrend die Kacheln darunter die eines Scouts zeigen --
|
||
zwei Wahrheiten auf einem Bildschirm. */
|
||
const person = req.sicht || req.person;
|
||
const heute = heuteLokal();
|
||
const jetzt = jetztLokal();
|
||
const hinweise = [];
|
||
|
||
/* DIE ADRESSE IST IMMER DIE ECHTE (15.09.2026).
|
||
|
||
`person` kann die angesehene sein -- der Sicht-Umschalter zeigt
|
||
die Lage eines anderen. Das aendert, WESSEN Zahlen dastehen,
|
||
nicht, auf welcher Wand man steht. */
|
||
const hier = { ...person, haus: (req.person || person)?.haus };
|
||
|
||
const dazu = (art, stufe, text, ziel, anzahl) => {
|
||
if (!anzahl) return;
|
||
/* EIN HINWEIS AUF EINE SEITE, DIE ES HIER NICHT GIBT, IST KEIN
|
||
HINWEIS (15.09.2026).
|
||
|
||
Filipe, mit dem Bildschirmfoto auf crew.: „wieso steht es noch
|
||
da?" -- „1 Punkt im Start-Check braucht Handlung". Die Seite
|
||
war da schon gesperrt; der Hinweis zeigte ins Leere.
|
||
|
||
Das war MEIN Fehler von heute Mittag: Ich hatte die Seiten
|
||
gesperrt und ausdruecklich dazugeschrieben „nur die Seiten,
|
||
nicht die Schnittstellen" -- und dabei uebersehen, dass die
|
||
Hinweise eine DRITTE Stelle sind. Sie sind keine Seite und
|
||
keine Schnittstelle, sondern eine Liste von Verweisen.
|
||
|
||
DIESELBE REGEL, NICHT EINE ZWEITE: Was auf dieser Adresse
|
||
keine Seite hat, bekommt hier auch keinen Hinweis. Eine eigene
|
||
Liste hier waere die, die beim naechsten Umbau auseinanderlaeuft. */
|
||
const zielPfad = ziel ? "/workspace/" + String(ziel).split("?")[0] : null;
|
||
if (zielPfad && !gehoertAufDieseAdresse(hier, zielPfad)) return;
|
||
|
||
/* ==== UND DIE ROLLE, NICHT NUR DIE ADRESSE (19.09.2026) =========
|
||
|
||
`gehoertAufDieseAdresse` beantwortet die Frage "gibt es diese
|
||
Seite hier?" -- nicht "darf DIESE Person sie oeffnen?". Der
|
||
Unterschied ist am 19.09. sofort aufgefallen, als der erste
|
||
Hinweis dazukam, der nicht fuer alle gilt:
|
||
|
||
Ein Mitglied der Community bekam "Bei dir klingelt nichts" mit
|
||
Weg auf `anruf-probe.html` -- eine Seite, die ihm ausdruecklich
|
||
NICHT offensteht. Gefunden von pruef-community-sicht, die
|
||
genau danach sucht: sichtbare Wege, die ins Leere fuehren.
|
||
|
||
Das ist der 15.09. noch einmal, nur eine Ebene tiefer. Damals
|
||
war es die Adresse ("wieso steht es noch da?"), diesmal die
|
||
Rolle. Und es ist dieselbe Lehre: Die Hinweise sind eine
|
||
DRITTE Stelle neben Seiten und Schnittstellen, und jede
|
||
Schranke, die es dort gibt, muss es auch hier geben.
|
||
|
||
DIESELBE TAFEL, NICHT EINE ZWEITE: `darfSeite` ist die
|
||
Funktion, die auch die Tuer bewacht. Eine eigene Liste hier
|
||
waere die, die beim naechsten Umbau auseinanderlaeuft. */
|
||
if (zielPfad && !darfSeite(person, zielPfad)) return;
|
||
hinweise.push({ art, stufe, text, ziel, anzahl });
|
||
};
|
||
|
||
/* ---------- Aufgaben ---------- */
|
||
const a = sichtbarAufgaben(person);
|
||
if (a) {
|
||
dazu("aufgaben_ueberfaellig", "warnung",
|
||
"überfällig", "aufgaben.html",
|
||
zaehle(`SELECT COUNT(*) n FROM aufgaben a
|
||
WHERE ${a.wo} AND a.status NOT IN ('erledigt', 'abgebrochen')
|
||
AND a.frist IS NOT NULL AND a.frist < ?`, [...a.werte, heute]));
|
||
|
||
dazu("aufgaben_heute", "offen",
|
||
"heute fällig", "aufgaben.html",
|
||
zaehle(`SELECT COUNT(*) n FROM aufgaben a
|
||
WHERE ${a.wo} AND a.status NOT IN ('erledigt', 'abgebrochen') AND a.frist = ?`,
|
||
[...a.werte, heute]));
|
||
|
||
/* Aufgaben, die im Review haengen. Sie warten auf jemanden --
|
||
das ist genau die Sorte Stillstand, die niemandem auffaellt. */
|
||
dazu("aufgaben_review", "offen",
|
||
"wartet auf Freigabe", "aufgaben.html",
|
||
zaehle(`SELECT COUNT(*) n FROM aufgaben a
|
||
WHERE ${a.wo} AND a.status = 'review'`, a.werte));
|
||
}
|
||
|
||
/* ---------- Termine und Calls ---------- */
|
||
const t = sichtbarTermine(person);
|
||
if (t) {
|
||
dazu("termine_heute", "offen",
|
||
"heute im Kalender", "kalender.html",
|
||
zaehle(`SELECT COUNT(*) n FROM termine t
|
||
WHERE ${t.wo} AND t.beginn >= ? AND t.beginn < ?`,
|
||
[...t.werte, heute + "T00:00", inTagen(1) + "T00:00"]));
|
||
|
||
/* Vergangene Gespraeche ohne Protokoll -- der einzige Zustand im
|
||
Call-Bereich, der aktiv etwas verlangt. */
|
||
dazu("protokoll_fehlt", "warnung",
|
||
"Gespräch ohne Protokoll", "calls.html",
|
||
zaehle(`SELECT COUNT(*) n FROM termine t
|
||
LEFT JOIN protokolle pr ON pr.termin_id = t.id
|
||
WHERE ${t.wo} AND t.art IN ('call','review')
|
||
AND t.beginn <= ? AND pr.id IS NULL`, [...t.werte, jetzt]));
|
||
}
|
||
|
||
/* ---------- Dateien ---------- */
|
||
const d = sichtbarDateien(person);
|
||
if (d) {
|
||
dazu("dateien_review", istLeitung(person) ? "warnung" : "offen",
|
||
"Datei wartet auf Freigabe", "dateien.html",
|
||
zaehle(`SELECT COUNT(*) n FROM dateien d
|
||
WHERE ${d.wo} AND d.status = 'review'`, d.werte));
|
||
}
|
||
|
||
/* ---------- Betreuungsbereiche ----------
|
||
HIER BLEIBT ES ABSICHTLICH BEI sichtbar() (07.09.2026).
|
||
|
||
Seit heute gibt es daneben sichtbarEintrag(), das Agentur-
|
||
Eintraege fuer alle sichtbar macht. Hier waere das falsch, und
|
||
zwar aus zwei Gruenden: Der Hinweis fuehrt fest nach
|
||
`bereich.html?b=live` -- ein Creator bekaeme also eine Zahl aus
|
||
der Agentur und landete beim Klick in einem Bereich, in dem
|
||
nichts davon steht. Und ein dringender Agentur-Punkt ist die
|
||
Aufgabe der Agentur, nicht seine; ein Hinweis, den man nicht
|
||
abarbeiten kann, ist Laerm.
|
||
|
||
Wer das eines Tages umstellen will, muss zuerst das Sprungziel
|
||
mitwandern lassen. */
|
||
const e = sichtbarEintraege(person);
|
||
if (e) {
|
||
dazu("bereiche_dringend", "warnung",
|
||
"dringender Punkt in den Bereichen", "bereich.html?b=live",
|
||
zaehle(`SELECT COUNT(*) n FROM eintraege e
|
||
WHERE ${e.wo} AND e.status = 'offen' AND e.dringlichkeit = 'hoch'`,
|
||
e.werte));
|
||
}
|
||
|
||
/* Offener Handlungsbedarf aus der Erstanalyse. Sichtbar fuer
|
||
Management, zustaendigen Scout und den Creator selbst -- genau
|
||
wie der Start-Check. */
|
||
{
|
||
let woS = null;
|
||
let werteS = [];
|
||
if (istLeitung(person)) woS = "1=1";
|
||
else if (person.rolle === "creator") { woS = "s.creator_id = ?"; werteS = [person.id]; }
|
||
else {
|
||
const ids = betreuteIds(person);
|
||
if (ids.length) {
|
||
woS = `s.creator_id IN (${ids.map(() => "?").join(",")})`;
|
||
werteS = ids;
|
||
}
|
||
}
|
||
if (woS) {
|
||
dazu("startcheck_handlung", "offen",
|
||
"Punkt im Start-Check braucht Handlung", "startcheck.html",
|
||
zaehle(`SELECT COUNT(*) n FROM startcheck s
|
||
WHERE ${woS} AND s.bewertung = 'handlung'`, werteS));
|
||
}
|
||
}
|
||
|
||
/* ---------- Markierte Punkte aus den festen Checklisten ----------
|
||
|
||
Wunsch vom 01.09.2026: "so dass die sehen, da ist was -- und es
|
||
soll auch oben in der Kachel angezeigt werden."
|
||
|
||
NUR FUER CREATOR, und das ist eine bewusste Entscheidung. Fuer
|
||
einen Betreuer waere die Zahl eine Liste dessen, was er selbst
|
||
angehakt hat: Sie waechst mit seiner Arbeit, und er kann sie nicht
|
||
abbauen -- nur der Creator kann. Ein Zaehler, den man nicht auf
|
||
null bringen kann, wird nach zwei Wochen ignoriert, und dann sind
|
||
auch die daneben nichts mehr wert.
|
||
|
||
Vier Bereiche, vier Hinweise, weil vier verschiedene Kacheln
|
||
gemeint sind. Ein Sammelhinweis "6 markierte Punkte" liesse offen,
|
||
wo man nachsehen soll. */
|
||
if (person.rolle === "creator") {
|
||
const BEREICHE = [
|
||
["live", "LIVE-Analyse", "bereich.html?b=live"],
|
||
["content", "Content-Ideen", "content.html"],
|
||
["community", "Community", "bereich.html?b=community"],
|
||
["technik", "Technik", "bereich.html?b=technik"],
|
||
];
|
||
for (const [schluessel, name, ziel] of BEREICHE) {
|
||
dazu(`markiert_${schluessel}`, "warnung",
|
||
`markierter Punkt in ${name}`, ziel,
|
||
/* zaehle() faengt Fehler ab -- gibt es die Tabelle noch nicht
|
||
(niemand hat je eine Checkliste geoeffnet), steht hier 0
|
||
statt eines Ausfalls der ganzen Uebersicht. */
|
||
zaehle(`SELECT COUNT(*) n FROM punkt_stand
|
||
WHERE bereich = ? AND creator_id = ? AND stufe = 'verbessern'`,
|
||
[schluessel, person.id]));
|
||
}
|
||
}
|
||
|
||
/* ---------- Scout-Pipeline ---------- */
|
||
/* NUR DogFather sieht alle Leads (01.09.2026). Ein Scout seine
|
||
eigenen, ein Manager seine eigenen und die seiner zugeteilten
|
||
Scouts -- dieselbe Regel wie in der Pipeline selbst, aus
|
||
derselben Quelle (pipelineIds in workspace.js). Ein Hinweis, der
|
||
mehr verraet als die Seite dahinter, waere ein Leck. */
|
||
if (siehtAlles(person) || person.rolle === "scout" || person.rolle === "manager") {
|
||
const ids = siehtAlles(person) ? null : pipelineIds(person);
|
||
const nur = ids === null ? "1=1"
|
||
: ids.length ? `l.scout_id IN (${ids.map(() => "?").join(",")})` : "0=1";
|
||
const werte = ids === null ? [] : ids;
|
||
dazu("followup_faellig", "warnung",
|
||
"Follow-up fällig", "scouting.html",
|
||
zaehle(`SELECT COUNT(*) n FROM leads l
|
||
WHERE ${nur} AND l.status NOT IN ('uebergeben','abgelehnt')
|
||
AND l.naechster_followup IS NOT NULL AND l.naechster_followup <= ?`,
|
||
[...werte, heute]));
|
||
|
||
/* Uebergebene Leads, aus denen nie ein Creator wurde. Die
|
||
Uebergabe ist sonst eine Sackgasse, die niemand bemerkt. */
|
||
dazu("uebergabe_offen", "offen",
|
||
"übergeben, aber noch kein Creator angelegt", "scouting.html",
|
||
zaehle(`SELECT COUNT(*) n FROM leads l
|
||
WHERE ${nur} AND l.status = 'uebergeben' AND l.creator_id IS NULL`,
|
||
werte));
|
||
}
|
||
|
||
/* ---------- Nur Management ----------
|
||
Review-Termine und fehlende Zustaendigkeit sind Steuerungswissen.
|
||
Sie erscheinen deshalb weder bei Creator noch bei Scouts -- so wie
|
||
die zugehoerigen Felder im Profil auch. */
|
||
/* DIE AGENTUR-HINWEISE NICHT AUF DER TEAM-ADRESSE (10.09.2026).
|
||
|
||
"Creator-Profile sind noch leer" und "Creator hat noch keinen
|
||
Start-Check" standen im Bildschirmfoto mitten auf der Team-Seite.
|
||
Sie zaehlen ausdruecklich Creator -- auf dieser Adresse gibt es
|
||
keine, und ein Hinweis auf etwas, das man dort weder sehen noch
|
||
anfassen kann, ist eine Aufgabe ohne Tuer.
|
||
|
||
Die Bedingung steht an DIESER Stelle und nicht in den vier
|
||
Abfragen darunter: Wer eine fuenfte hinzufuegt, bekommt sie
|
||
dadurch mit, ohne daran zu denken. */
|
||
if (istLeitung(person) && person.haus !== "crew") {
|
||
dazu("review_faellig", "warnung",
|
||
"Review-Termin überfällig", "profil.html",
|
||
zaehle(`SELECT COUNT(*) n FROM profile f
|
||
JOIN personen p ON p.id = f.person_id AND p.aktiv = 1
|
||
WHERE f.naechster_review IS NOT NULL AND f.naechster_review < ?`, [heute]));
|
||
|
||
dazu("review_bald", "offen",
|
||
"Review steht in den nächsten 7 Tagen an", "profil.html",
|
||
zaehle(`SELECT COUNT(*) n FROM profile f
|
||
JOIN personen p ON p.id = f.person_id AND p.aktiv = 1
|
||
WHERE f.naechster_review BETWEEN ? AND ?`, [heute, inTagen(7)]));
|
||
|
||
dazu("ohne_betreuung", "ruhig",
|
||
"Creator ohne zuständige Person", "personen.html",
|
||
zaehle(`SELECT COUNT(*) n FROM personen p
|
||
LEFT JOIN betreuung b ON b.creator_id = p.id
|
||
WHERE p.rolle = 'creator' AND p.aktiv = 1 AND b.creator_id IS NULL`, []));
|
||
|
||
/* Ein Creator ohne Erstanalyse ist die Sorte Luecke, die sonst
|
||
niemandem auffaellt -- es fehlt ja nichts, es fing nur nie an. */
|
||
dazu("ohne_startcheck", "ruhig",
|
||
"Creator ohne Start-Check", "startcheck.html",
|
||
zaehle(`SELECT COUNT(*) n FROM personen p
|
||
WHERE p.rolle = 'creator' AND p.aktiv = 1
|
||
AND NOT EXISTS (SELECT 1 FROM startcheck s WHERE s.creator_id = p.id)`, []));
|
||
|
||
dazu("ohne_profil", "ruhig",
|
||
"Creator ohne ausgefülltes Profil", "profil.html",
|
||
zaehle(`SELECT COUNT(*) n FROM personen p
|
||
LEFT JOIN profile f ON f.person_id = p.id
|
||
WHERE p.rolle = 'creator' AND p.aktiv = 1 AND f.person_id IS NULL`, []));
|
||
}
|
||
|
||
/* ---------- Anfragen für Modi ----------
|
||
|
||
WARUM ES DAFUER EINEN HINWEIS BRAUCHT: Die Anfragen haben
|
||
ausdruecklich KEINE Kachel -- sie stehen auf der Talente-Seite,
|
||
weil sie der Anfang desselben Trichters sind. Damit gibt es auf
|
||
der Startseite nichts, was auf sie zeigt.
|
||
|
||
Und eine Anfrage, die drei Wochen unbeantwortet liegt, ist eine
|
||
Absage, die niemand ausgesprochen hat. Genau dafuer ist die
|
||
Glocke da.
|
||
|
||
`dazu` prueft selbst, ob die Person diese Seite hier oeffnen
|
||
darf -- ein Hinweis auf eine gesperrte Seite waere derselbe
|
||
Fehler wie am 15.09. beim Start-Check. */
|
||
dazu("bewerbungen_neu", "offen",
|
||
"neue Anfrage für Modi", "bewerbungen.html",
|
||
zaehle("SELECT COUNT(*) n FROM bewerbungen WHERE zustand = 'neu'", []));
|
||
|
||
/* ==== BEI DIR KLINGELT NICHTS (19.09.2026) =======================
|
||
|
||
Gemessen an diesem Tag: Von elf Zugaengen hatten SIEBEN kein
|
||
einziges Geraet angemeldet. Bei ihnen kommt keine einzige
|
||
Erinnerung an, solange die Seite zu ist -- keine Aufgabe, kein
|
||
Termin, kein Anruf.
|
||
|
||
Die Glocke oben rechts sagt das seit jeher korrekt ("Benach-
|
||
richtigungen aus") und kennt sogar den iPhone-Sonderfall. Nur
|
||
tippt sie niemand an: Sie ist ein stiller Schalter in einer
|
||
Leiste, kein Hinweis. Deshalb steht es jetzt dort, wo man
|
||
hinsieht.
|
||
|
||
DIESER HINWEIS GEHT NUR AN DIE PERSON SELBST, nicht an die
|
||
Sicht. `req.person` und nicht `person`: Wenn DogFather die Lage
|
||
eines Scouts ansieht, waere "bei DIR klingelt nichts" eine
|
||
Aussage ueber den falschen Menschen -- und eine, die er nicht
|
||
loesen kann.
|
||
|
||
STUFE "offen", NICHT "warnung". Es ist kein Ausfall, sondern
|
||
etwas, das man einmal einrichtet. Ein rotes Warnzeichen, das
|
||
bei sieben von elf Leuten dauerhaft oben steht, waere nach
|
||
einer Woche unsichtbar -- und mit ihm die echten Warnungen
|
||
daneben.
|
||
|
||
ER VERSCHWINDET VON SELBST, sobald ein Geraet angemeldet ist.
|
||
Nichts zu quittieren, nichts, was veralten kann -- Regel 1
|
||
dieser Datei. */
|
||
if (req.person?.id) {
|
||
const geraete = zaehle(
|
||
"SELECT COUNT(*) n FROM push_anmeldungen WHERE person_id = ?", [req.person.id]);
|
||
if (!geraete) {
|
||
dazu("kein_geraet", "offen",
|
||
"Bei dir klingelt nichts – kein Gerät angemeldet",
|
||
"anruf-probe.html", 1);
|
||
}
|
||
}
|
||
|
||
hinweise.sort((x, y) => (RANG[x.stufe] - RANG[y.stufe]) || (y.anzahl - x.anzahl));
|
||
res.json({ hinweise, stand: new Date().toISOString() });
|
||
} catch (fehler) {
|
||
console.error("[workspace] Hinweise:", fehler?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|