Die Kachel "Dashboard" fuehrte nirgendwohin -- man stand ja schon auf der
Startseite. Statt sie zu streichen bekommt sie ein Ziel: die
Gesamtuebersicht je Creator, die das Konzept auf Seite 1 als "UEBERSICHT
-- alles zentral" verspricht und die es bisher nirgends gab. Die
Startseite bleibt der Einstieg (was ist dran, wohin gehe ich), das
Dashboard ist der Ueberblick (wie steht es um wen).
Eine Karte je Creator: ueberfaellig, dringend, offen, in Arbeit, im
Review, erledigt in 30 Tagen, dazu der naechste Termin, der Start-Check
als Balken und wie lange die Person nicht mehr da war. Jede Zahl ist ein
Verweis -- eine Zahl ohne Weg dorthin waere nur ein schlechtes Gewissen.
Sortiert nach dem, was Handlung verlangt, nicht alphabetisch.
Sichtbarkeit serverseitig wie ueberall: Creator sieht sich selbst
("Mein Stand"), Scout nur seine zugeteilten, DogFather und Manager alle.
Der naechste Review-Termin faellt fuer alle ausser der Leitung weg.
Nebenbei zwei alte Fehler gefunden und behoben:
1. Die Kopfleiste schob auf einem 390px-Handy 214 px aus dem Bild -- der
Abmelden-Knopf war dort auf JEDER Seite unerreichbar. Ursache: In der
Flex-Zeile fehlte min-width: 0, also konnte nichts schrumpfen. Auf
kleinen Schirmen weicht jetzt die Marke (steht direkt darunter noch
einmal als Ueberschrift) und der Zurueck-Knopf zeigt nur den Pfeil.
Geprueft von 320 bis 1440 px auf 14 Seiten.
2. .inhalt--breit, .kopf-zeile, .zurueck und .leise standen in
aufgaben.css -- einer Datei, die eine neue Seite gar nicht laden muss.
Derselbe Fehler wie frueher bei .knopf-still und .schalter. Jetzt in
start.css, die jede Seite laedt.
Co-Authored-By: Claude Opus 5 <[email protected]>
201 lines
9.1 KiB
JavaScript
201 lines
9.1 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" führte lange nirgendwohin -- man stand ja schon auf
|
||
dieser Seite. Statt die Kachel zu streichen, hat sie jetzt ein
|
||
Ziel: die Gesamtübersicht je Creator, die das Konzept auf Seite 1
|
||
als "ÜBERSICHT -- alles zentral" verspricht und die es bisher
|
||
nirgends gab. Diese Seite hier ist der Einstieg, die andere der
|
||
Überblick. */
|
||
const BEREICHE = [
|
||
{ name: 'Dashboard', phase: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'uebersicht.html' },
|
||
{ 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();
|
||
})();
|