Filipe: "da sind aber noch zwei kategorien zu". Er hat recht, und es waren zwei VERSCHIEDENE Fehler. 1) "DASHBOARD" war eine Kachel, die man nicht oeffnen kann -- man steht ja schon darauf. Sie stand seit dem ersten Tag als Platzhalter da. Ein Eintrag, der nirgendwohin fuehrt, sieht aus wie etwas Unfertiges. Entfernt. 2) "AUTOMATIONEN" trug noch "Phase 4", obwohl die Automationen laengst liefen -- sie waren nur verteilt (Hinweise auf der Uebersicht, Suche ueberall, KI-Vorschlaege an drei Stellen) und hatten keinen Ort. Jetzt haben sie einen. DIE SEITE FUEHRT BEWUSST NICHTS EIGENES. Was dort steht, kommt aus den Bereichen selbst -- eine Seite, die Automationen doppelt abbildet, waere die naechste Stelle, die irgendwann etwas anderes behauptet als die Wirklichkeit. Vier Abschnitte: - Die lokale KI mit Zustand, Modell, Ort, Kosten und Grenzen -- und dem einzigen Schalter der Seite. Sie ist die einzige Automation, die Rechenzeit kostet, die der Website gehoert. - "Was gerade anliegt": dieselben Hinweise wie auf der Uebersicht, hier vollstaendig statt gekuerzt. - "Laeuft dauerhaft im Hintergrund": sieben Regeln im Klartext. Jede beschreibt etwas, das tatsaechlich im Code steht. - "Zuletzt automatisch passiert": aus dem Protokoll, gefiltert auf das, was ohne Zutun geschah. DER KI-SCHALTER WIRD AN ZWEI STELLEN GEPRUEFT: beim Anzeigen der Knoepfe UND in jedem Endpunkt. Eine Oberflaeche, die etwas versteckt, ist keine Sperre. Geprueft: abgeschaltet liefert der Endpunkt 403, ein Creator darf gar nicht schalten (403), die Einstellung ueberdauert einen Neustart (eigene Tabelle, damit sie dieselbe Sicherung bekommt wie alles andere). NEBENBEI DERSELBE FEHLER WIE FRUEHER: Der Schalter-Baustein lag in wissen.css und fehlte prompt auf der neuen Seite -- dort erschien er als nacktes Kaestchen. Wie damals bei .knopf-still. Liegt jetzt in gate.css. Geprueft: 17 Kacheln, KEINE mehr ohne Ziel. Creator wird von der Seite weggeleitet (302). Handy ohne Ueberlauf, 0 Konsolenfehler.
197 lines
8.8 KiB
JavaScript
197 lines
8.8 KiB
JavaScript
/* ===================================================================
|
||
Startseite nach dem Anmelden.
|
||
Die Seite ist bereits serverseitig geschützt (workspace.js). Dieses
|
||
Skript holt nur noch, WER angemeldet ist, und regelt das Abmelden.
|
||
=================================================================== */
|
||
(() => {
|
||
'use strict';
|
||
|
||
const ROLLENTEXT = {
|
||
admin: 'DogFather · Gesamtübersicht und Freigaben',
|
||
manager: 'Manager · dieselben Rechte, ohne Personenverwaltung',
|
||
creator: 'Creator · dein eigener Bereich',
|
||
scout: 'Scout · deine Pipeline und die Creator, die du betreust',
|
||
};
|
||
|
||
/* Bereiche aus dem Konzept, mit der Phase aus dem Umsetzungsplan.
|
||
`rollen` steuert, wem ein Bereich überhaupt angezeigt wird - schon
|
||
jetzt, damit die Trennung von Anfang an sichtbar ist.
|
||
|
||
"Dashboard" stand hier als Kachel, die man nicht öffnen kann -- man
|
||
ist ja schon darauf. Ein Eintrag, der nirgendwohin führt, sieht aus
|
||
wie etwas Unfertiges. Raus. */
|
||
const BEREICHE = [
|
||
{ name: 'Personen & Zugänge', phase: 1, rollen: ['admin', 'manager'], ziel: 'personen.html' },
|
||
{ name: 'Creator-Profile', phase: 1, rollen: ['admin', 'manager', 'scout'], ziel: 'profil.html' },
|
||
{ name: 'Mein Profil', phase: 1, rollen: ['creator'], ziel: 'profil.html' },
|
||
{ name: 'Start-Check', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'startcheck.html' },
|
||
{ name: 'Wissens-Bibliothek', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'wissen.html' },
|
||
{ name: 'Aufgaben', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'aufgaben.html' },
|
||
{ name: 'Kalender', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'kalender.html' },
|
||
{ name: 'Dateien', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'dateien.html' },
|
||
{ name: 'LIVE-Analyse', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=live' },
|
||
{ name: 'Content-Planung', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=content' },
|
||
{ name: 'Technik', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=technik' },
|
||
{ name: 'Community', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=community' },
|
||
{ name: 'Schutz & Regeln', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=schutz' },
|
||
{ name: 'Reports & Review', phase: 2, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'report.html' },
|
||
{ name: 'Calls & Protokolle', phase: 3, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'calls.html' },
|
||
{ name: 'Scout-Pipeline', phase: 3, rollen: ['admin', 'manager', 'scout'], ziel: 'scouting.html' },
|
||
{ name: 'Automationen', phase: 4, rollen: ['admin', 'manager'], ziel: 'automation.html' },
|
||
];
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
|
||
async function laden() {
|
||
let ich;
|
||
try {
|
||
const antwort = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
||
if (!antwort.ok) { location.assign('/workspace/'); return; }
|
||
ich = await antwort.json();
|
||
} catch {
|
||
location.assign('/workspace/');
|
||
return;
|
||
}
|
||
|
||
$('wer').textContent = `${ich.name} · ${ich.rolle_name || ich.rolle}`;
|
||
$('gruss').textContent = `Willkommen, ${ich.name}`;
|
||
$('rollentext').textContent = ROLLENTEXT[ich.rolle] || ich.rolle;
|
||
|
||
const liste = $('bereiche');
|
||
for (const b of BEREICHE.filter((b) => b.rollen.includes(ich.rolle))) {
|
||
const li = document.createElement('li');
|
||
li.className = 'bereich';
|
||
li.dataset.phase = String(b.phase);
|
||
if (b.ziel) li.dataset.fertig = 'ja';
|
||
|
||
const name = document.createElement('span');
|
||
name.className = 'bereich__name';
|
||
name.textContent = b.name;
|
||
|
||
const stand = document.createElement('span');
|
||
stand.className = 'bereich__stand';
|
||
stand.textContent = b.ziel ? 'öffnen' : (b.phase === 1 ? 'als Nächstes' : `Phase ${b.phase}`);
|
||
|
||
if (b.ziel) {
|
||
/* Fertige Bereiche sind echte Verweise -- Tastatur und
|
||
Rechtsklick funktionieren damit wie gewohnt. */
|
||
const a = document.createElement('a');
|
||
a.className = 'bereich__link';
|
||
a.href = b.ziel;
|
||
a.append(name, stand);
|
||
li.append(a);
|
||
} else {
|
||
li.append(name, stand);
|
||
}
|
||
liste.append(li);
|
||
}
|
||
|
||
await hinweiseLaden();
|
||
await zahlenLaden();
|
||
}
|
||
|
||
/* Was ist dran? Erster Baustein der Automationen aus dem Konzept.
|
||
Fuehrt bewusst keine eigenen Daten -- jeder Hinweis ist nur eine
|
||
Sicht auf etwas, das schon da ist, und verschwindet von selbst,
|
||
sobald die Sache erledigt ist. Nichts zu quittieren. */
|
||
const EINZAHL = {
|
||
aufgaben_ueberfaellig: (n) => `${n} ${n === 1 ? 'Aufgabe ist' : 'Aufgaben sind'} überfällig`,
|
||
aufgaben_heute: (n) => `${n} ${n === 1 ? 'Aufgabe ist' : 'Aufgaben sind'} heute fällig`,
|
||
aufgaben_review: (n) => `${n} ${n === 1 ? 'Aufgabe wartet' : 'Aufgaben warten'} auf Freigabe`,
|
||
termine_heute: (n) => `${n} ${n === 1 ? 'Termin steht' : 'Termine stehen'} heute an`,
|
||
protokoll_fehlt: (n) => `${n} ${n === 1 ? 'Gespräch hat' : 'Gespräche haben'} noch kein Protokoll`,
|
||
dateien_review: (n) => `${n} ${n === 1 ? 'Datei liegt' : 'Dateien liegen'} im Review`,
|
||
bereiche_dringend: (n) => `${n} ${n === 1 ? 'dringender Punkt' : 'dringende Punkte'} in den Bereichen`,
|
||
followup_faellig: (n) => `${n} Follow-up${n === 1 ? '' : 's'} ${n === 1 ? 'ist' : 'sind'} fällig`,
|
||
uebergabe_offen: (n) => `${n} übergebene${n === 1 ? 'r Lead wartet' : ' Leads warten'} auf das Onboarding`,
|
||
review_faellig: (n) => `${n} Review-Termin${n === 1 ? ' ist' : 'e sind'} überfällig`,
|
||
review_bald: (n) => `${n} Review${n === 1 ? ' steht' : 's stehen'} in den nächsten 7 Tagen an`,
|
||
ohne_betreuung: (n) => `${n} Creator ${n === 1 ? 'hat' : 'haben'} noch keine zuständige Person`,
|
||
ohne_profil: (n) => `${n} Creator-Profil${n === 1 ? ' ist' : 'e sind'} noch leer`,
|
||
ohne_startcheck: (n) => `${n} Creator ${n === 1 ? 'hat' : 'haben'} noch keinen Start-Check`,
|
||
startcheck_handlung: (n) => `${n} ${n === 1 ? 'Punkt im Start-Check braucht' : 'Punkte im Start-Check brauchen'} Handlung`,
|
||
};
|
||
|
||
async function hinweiseLaden() {
|
||
let daten;
|
||
try {
|
||
const antwort = await fetch('/workspace/api/hinweise', { credentials: 'same-origin' });
|
||
if (!antwort.ok) return;
|
||
daten = await antwort.json();
|
||
} catch { return; }
|
||
|
||
const liste = $('dran');
|
||
liste.textContent = '';
|
||
if (!daten.hinweise.length) { $('dran-block').hidden = true; return; }
|
||
|
||
for (const h of daten.hinweise) {
|
||
const li = document.createElement('li');
|
||
li.className = 'dran__punkt';
|
||
li.dataset.stufe = h.stufe;
|
||
|
||
/* Jeder Hinweis fuehrt irgendwohin -- ein Hinweis ohne Ziel ist
|
||
nur ein schlechtes Gewissen. */
|
||
const a = document.createElement('a');
|
||
a.className = 'dran__link';
|
||
a.href = h.ziel;
|
||
|
||
const text = document.createElement('span');
|
||
text.className = 'dran__text';
|
||
text.textContent = (EINZAHL[h.art] || ((n) => `${n} × ${h.text}`))(h.anzahl);
|
||
|
||
const pfeil = document.createElement('span');
|
||
pfeil.className = 'dran__pfeil';
|
||
pfeil.setAttribute('aria-hidden', 'true');
|
||
pfeil.textContent = '→';
|
||
|
||
a.append(text, pfeil);
|
||
li.append(a);
|
||
liste.append(li);
|
||
}
|
||
$('dran-block').hidden = false;
|
||
}
|
||
|
||
/* Die Zahlen kommen aus derselben Quelle wie das Brett und sind bereits
|
||
serverseitig auf die eigene Rolle eingegrenzt. */
|
||
const ZAHLEN = [
|
||
{ schluessel: 'ueberfaellig', name: 'Überfällig', warn: true },
|
||
{ schluessel: 'heute', name: 'Heute fällig' },
|
||
{ schluessel: 'offen', name: 'Offen' },
|
||
{ schluessel: 'arbeit', name: 'In Arbeit' },
|
||
{ schluessel: 'review', name: 'Review' },
|
||
{ schluessel: 'erledigt', name: 'Erledigt' },
|
||
];
|
||
|
||
async function zahlenLaden() {
|
||
let daten;
|
||
try {
|
||
const antwort = await fetch('/workspace/api/uebersicht', { credentials: 'same-origin' });
|
||
if (!antwort.ok) return;
|
||
daten = await antwort.json();
|
||
} catch { return; }
|
||
|
||
const liste = $('zahlen');
|
||
liste.textContent = '';
|
||
for (const z of ZAHLEN) {
|
||
const wert = daten[z.schluessel] ?? 0;
|
||
const li = document.createElement('li');
|
||
li.className = 'zahl';
|
||
if (z.warn && wert > 0) li.dataset.warn = 'ja';
|
||
|
||
const n = document.createElement('span');
|
||
n.className = 'zahl__wert';
|
||
n.textContent = String(wert);
|
||
|
||
const t = document.createElement('span');
|
||
t.className = 'zahl__name';
|
||
t.textContent = z.name;
|
||
|
||
li.append(n, t);
|
||
liste.append(li);
|
||
}
|
||
}
|
||
|
||
|
||
laden();
|
||
})();
|