Auf Wunsch von Filipe. Betrifft ausschliesslich die Anzeige.
Der Rollenschluessel bleibt "admin". Er steckt in der CHECK-Regel der
Datenbank, in jeder bestehenden Sitzung und in jeder Rechteabfrage --
ihn umzubenennen haette alle drei gebrochen, und bestehende Anmeldungen
waeren ungueltig geworden. Umbenannt wird nur, was man LIEST.
Der Anzeigename steht jetzt an EINER Stelle (ROLLEN_NAME in
workspace.js) und kommt ueber /workspace/api/ich als rolle_name mit.
Stand er in elf Dateien, waere er beim naechsten Mal in zehn davon
geaendert.
Dabei aufgefallen: In der Kopfleiste stand auf JEDER Seite der rohe
Rollenschluessel -- "Chef · admin". Das war schon vorher unschoen, faellt
aber jetzt erst richtig auf. Alle elf Seiten zeigen jetzt "Chef ·
DogFather". Geprueft: kein rohes "admin" mehr in der Oberflaeche.
Geaendert: Rollenkachel und Untertitel auf der Anmeldeseite, Kopfleiste
aller Seiten, Rollentext auf dem Dashboard, Rollenmarken und Auswahl in
der Personenverwaltung, die Betreuungs-Auswahl ("nur DogFather"), der
Hinweis auf der Dateienseite, die Erklaerung zur internen Notiz im
Profil, die Hinweise fuer Scouts ohne Zuteilung -- und vier
Fehlermeldungen vom Server.
Schreibweise "DogFather" wie von Filipe geschrieben; das ist auch auf
der oeffentlichen Website die haeufigste Form (717 von 1216).
In den Code-Kommentaren der Fachmodule heisst die Rolle weiterhin "das
Management". Das bleibt bewusst so -- eine Massenaenderung an vierzig
Kommentaren waere reines Risiko ohne sichtbaren Nutzen. Ein Hinweis an
der ROLLEN_NAME-Zuordnung erklaert den Zusammenhang.
192 lines
8.3 KiB
JavaScript
192 lines
8.3 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',
|
||
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. */
|
||
const BEREICHE = [
|
||
{ name: 'Dashboard', phase: 1, rollen: ['admin', 'creator', 'scout'] },
|
||
{ name: 'Personen & Zugänge', phase: 1, rollen: ['admin'], ziel: 'personen.html' },
|
||
{ name: 'Creator-Profile', phase: 1, rollen: ['admin', 'scout'], ziel: 'profil.html' },
|
||
{ name: 'Mein Profil', phase: 1, rollen: ['creator'], ziel: 'profil.html' },
|
||
{ name: 'Start-Check', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'startcheck.html' },
|
||
{ name: 'Aufgaben', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'aufgaben.html' },
|
||
{ name: 'Kalender', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'kalender.html' },
|
||
{ name: 'Dateien', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'dateien.html' },
|
||
{ name: 'LIVE-Analyse', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'bereich.html?b=live' },
|
||
{ name: 'Content-Planung', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'bereich.html?b=content' },
|
||
{ name: 'Technik', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'bereich.html?b=technik' },
|
||
{ name: 'Community', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'bereich.html?b=community' },
|
||
{ name: 'Schutz & Regeln', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'bereich.html?b=schutz' },
|
||
{ name: 'Reports & Review', phase: 2, rollen: ['admin', 'creator', 'scout'], ziel: 'report.html' },
|
||
{ name: 'Calls & Protokolle', phase: 3, rollen: ['admin', 'creator', 'scout'], ziel: 'calls.html' },
|
||
{ name: 'Scout-Pipeline', phase: 3, rollen: ['admin', 'scout'], ziel: 'scouting.html' },
|
||
{ name: 'Automationen', phase: 4, rollen: ['admin'] },
|
||
];
|
||
|
||
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();
|
||
})();
|