Letztes unumgesetztes Stueck aus dem Konzept, Seite 5, Block 03. Profil,
Ziele und 90-Tage-Plan gab es schon -- die Erstanalyse dazwischen fehlte.
"Die Betreuung startet strukturiert: Ausgangslage verstehen, Ziele
festlegen und daraus konkrete Arbeitspakete bauen."
Der letzte Halbsatz ist der entscheidende. Ein Check, der mit einer Note
endet, ist eine Beurteilung. Ein Check, der mit Aufgaben endet, ist
Betreuung. Jeder Punkt mit Handlungsbedarf bietet deshalb direkt an,
daraus eine Aufgabe zu machen -- mit dem Befund als Herkunft und
automatisch hoher Prioritaet. Dieselbe Linie wie beim Review ("endet mit
einer Entscheidung") und beim Call ("endet mit To-dos").
Die vier Felder aus dem Deck, mit je vier Punkten: Profil & Auftritt,
LIVE-Struktur, Content-Muster, Community & Modis. Drei Stufen: Passt,
Ausbaufaehig, Handlungsbedarf.
Die Punkte stehen fest im Code, NICHT in der Datenbank. Eine
Erstanalyse, bei der jeder eigene Punkte anlegt, ist keine Erstanalyse
mehr -- man koennte zwei Creator nicht mehr vergleichen, und genau das
ist ihr Zweck.
Sichtbarkeit, bewusst anders als beim Profil:
- LESEN darf auch der Creator selbst. Der Check ist Teil SEINES
Entwicklungsplans ("jeder Creator bekommt einen eigenen, lebenden
Entwicklungsplan"), kein Urteil hinter seinem Ruecken. Wer etwas
festhalten will, das er nicht sehen soll, hat dafuer die interne
Notiz im Profil -- die bleibt beim Management.
- AENDERN duerfen nur Management und zustaendiger Scout. Es ist eine
Fremdeinschaetzung; koennte der Creator sie selbst setzen, waere es
eine Selbsteinschaetzung. Geprueft: Luna bekommt 403 beim Bewerten und
beim Anlegen einer Aufgabe, sieht aber ihren Check inklusive Notizen.
- Ein nicht zustaendiger Scout bekommt 404, nicht 403.
Kein Speichern-Knopf: Jede Bewertung und jede Notiz wird sofort
gesichert. Eine Erstanalyse geht man im Gespraech durch -- da will
niemand am Ende noch an einen Knopf denken.
Ein leergeraeumter Punkt (keine Bewertung, keine Notiz) wird geloescht
statt als leere Zeile stehen zu bleiben. So sagt der Bestand direkt, wie
weit die Analyse ist.
Zwei neue Hinweise auf dem Dashboard:
- "Creator ohne Start-Check" -- die Sorte Luecke, die sonst niemandem
auffaellt, weil ja nichts fehlt: es fing nur nie an.
- "Punkt im Start-Check braucht Handlung", sichtbar fuer alle, die den
Check auch sehen duerfen.
Aus dem Test: Sechzehn dauerhaft offene Notizfelder machten die Seite
7026 px lang. Das Feld erscheint jetzt erst mit der Bewertung -- vorher
hat man ohnehin nichts zu notieren. 2460 px.
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: 'Management · 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}`;
|
||
$('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();
|
||
})();
|