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.
235 lines
9.0 KiB
JavaScript
235 lines
9.0 KiB
JavaScript
/* ===================================================================
|
||
Reports & Review.
|
||
|
||
Die Fragen der Abschnitte stammen woertlich aus dem Konzept, Seite 16:
|
||
"Was wurde erledigt? Was blockiert? Was hat funktioniert? Was kommt
|
||
naechste Woche?"
|
||
=================================================================== */
|
||
(() => {
|
||
'use strict';
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
const BEREICHSNAME = {
|
||
live: 'LIVE', content: 'Content', technik: 'Technik',
|
||
community: 'Community', schutz: 'Schutz',
|
||
};
|
||
|
||
let ich = null;
|
||
|
||
const el = (tag, klasse, text) => {
|
||
const k = document.createElement(tag);
|
||
if (klasse) k.className = klasse;
|
||
if (text !== undefined) k.textContent = text;
|
||
return k;
|
||
};
|
||
const melde = (t) => { $('fehler').textContent = t || ''; };
|
||
const datum = (iso) => (iso ? iso.slice(0, 10).split('-').reverse().join('.') : '—');
|
||
|
||
async function hole(pfad, optionen = {}) {
|
||
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||
if (a.status === 404) { location.assign('/workspace/start.html'); throw new Error('kein Zugriff'); }
|
||
return a;
|
||
}
|
||
|
||
/* Vorher/Nachher. `gut` sagt, ob ein Anstieg erfreulich ist -- bei
|
||
offenen Problemen ist er das nicht. */
|
||
function trend(jetzt, davor, gut = true) {
|
||
const t = el('span', 'trend');
|
||
t.dataset.gut = gut ? 'ja' : 'nein';
|
||
const diff = jetzt - davor;
|
||
if (!davor && !jetzt) { t.dataset.richtung = 'gleich'; t.textContent = '–'; return t; }
|
||
if (diff === 0) { t.dataset.richtung = 'gleich'; t.textContent = 'unverändert'; return t; }
|
||
t.dataset.richtung = diff > 0 ? 'hoch' : 'runter';
|
||
t.textContent = (diff > 0 ? '▲ +' : '▼ ') + diff + ' ggü. davor';
|
||
return t;
|
||
}
|
||
|
||
function kachel(zahl, name, { trendEl = null, warn = false } = {}) {
|
||
const k = el('div', 'kachel');
|
||
if (warn) k.dataset.warn = 'ja';
|
||
const wert = el('div', 'kachel__wert');
|
||
wert.append(el('span', 'kachel__zahl', String(zahl)));
|
||
if (trendEl) wert.append(trendEl);
|
||
k.append(wert, el('span', 'kachel__name', name));
|
||
return k;
|
||
}
|
||
|
||
function block(titel, frage, inhalt) {
|
||
const b = el('section', 'block');
|
||
b.append(el('h2', 'block__titel', titel));
|
||
if (frage) b.append(el('p', 'block__frage', frage));
|
||
b.append(inhalt);
|
||
return b;
|
||
}
|
||
|
||
/* ---------- Bericht zeichnen ------------------------------------------- */
|
||
|
||
function zeichne(d) {
|
||
const ziel = $('bericht');
|
||
ziel.textContent = '';
|
||
ziel.setAttribute('aria-busy', 'false');
|
||
|
||
$('unterzeile').textContent =
|
||
`${d.creator_name ? d.creator_name + ' · ' : 'Alle Creator · '}`
|
||
+ `${datum(d.zeitraum.ab)} bis ${datum(d.zeitraum.bis)}`
|
||
+ ` · Vergleich mit den ${d.zeitraum.tage} Tagen davor`;
|
||
|
||
/* Was wurde erledigt? */
|
||
const k1 = el('div', 'kacheln');
|
||
k1.append(kachel(d.aufgaben.erledigt, 'Aufgaben erledigt',
|
||
{ trendEl: trend(d.aufgaben.erledigt, d.aufgaben.erledigt_davor) }));
|
||
k1.append(kachel(d.aufgaben.angelegt, 'neu angelegt',
|
||
{ trendEl: trend(d.aufgaben.angelegt, d.aufgaben.angelegt_davor) }));
|
||
k1.append(kachel(d.termine.gewesen, 'Termine gewesen'));
|
||
k1.append(kachel(d.dateien.freigegeben, 'Dateien freigegeben'));
|
||
ziel.append(block('Was wurde erledigt?', null, k1));
|
||
|
||
/* Was blockiert? */
|
||
const k2 = el('div', 'kacheln');
|
||
k2.append(kachel(d.aufgaben.offen, 'Aufgaben offen'));
|
||
k2.append(kachel(d.aufgaben.ueberfaellig, 'überfällig', { warn: d.aufgaben.ueberfaellig > 0 }));
|
||
const dringend = Object.values(d.bereiche).reduce((s, b) => s + b.dringend, 0);
|
||
k2.append(kachel(dringend, 'dringende Punkte', { warn: dringend > 0 }));
|
||
k2.append(kachel(d.dateien.offen_review, 'Dateien im Review'));
|
||
const b2 = block('Was blockiert?', 'Die drei Aufgaben, die am längsten offen liegen:', k2);
|
||
if (d.aufgaben.aeltester.length) {
|
||
const liste = el('ul', 'liste-klein');
|
||
for (const a of d.aufgaben.aeltester) {
|
||
const li = el('li');
|
||
li.append(document.createTextNode(a.titel));
|
||
li.append(el('span', 'klein',
|
||
`offen seit ${datum(a.erstellt)}${a.frist ? ' · Frist ' + datum(a.frist) : ''}`));
|
||
liste.append(li);
|
||
}
|
||
b2.append(liste);
|
||
} else {
|
||
b2.append(el('p', 'leer-hinweis', 'Nichts offen – sauber.'));
|
||
}
|
||
ziel.append(b2);
|
||
|
||
/* Was hat funktioniert? */
|
||
const k3 = el('div', 'kacheln');
|
||
const jetztS = d.live.jetzt.schnitt;
|
||
const davorS = d.live.davor.schnitt;
|
||
const kLive = kachel(jetztS === null ? '–' : String(jetztS).replace('.', ','),
|
||
`LIVE-Schnitt aus ${d.live.jetzt.anzahl} Bewertung(en)`);
|
||
if (jetztS !== null && davorS !== null) {
|
||
kLive.querySelector('.kachel__wert').append(
|
||
trend(Math.round(jetztS * 10), Math.round(davorS * 10)));
|
||
}
|
||
k3.append(kLive);
|
||
for (const [b, w] of Object.entries(d.bereiche)) {
|
||
k3.append(kachel(w.neu, BEREICHSNAME[b] + ': neue Einträge',
|
||
{ trendEl: trend(w.neu, w.neu_davor) }));
|
||
}
|
||
ziel.append(block('Was hat funktioniert?',
|
||
'Bewegung in den Betreuungsbereichen im Vergleich zum Zeitraum davor:', k3));
|
||
|
||
/* Was kommt als Nächstes? */
|
||
const k4 = el('div', 'kacheln');
|
||
k4.append(kachel(d.termine.kommend, 'Termine stehen an'));
|
||
for (const [b, w] of Object.entries(d.bereiche)) {
|
||
if (w.offen) k4.append(kachel(w.offen, BEREICHSNAME[b] + ': offen',
|
||
{ warn: w.dringend > 0 }));
|
||
}
|
||
const b4 = block('Was kommt als Nächstes?', null, k4);
|
||
if (d.profil && (d.profil.plan_prio1 || d.profil.plan_prio2 || d.profil.plan_prio3)) {
|
||
const liste = el('ul', 'liste-klein');
|
||
for (const [i, t] of [d.profil.plan_prio1, d.profil.plan_prio2, d.profil.plan_prio3].entries()) {
|
||
if (!t) continue;
|
||
const li = el('li');
|
||
li.append(document.createTextNode(t));
|
||
li.append(el('span', 'klein', `Priorität ${i + 1} aus dem 90-Tage-Plan`));
|
||
liste.append(li);
|
||
}
|
||
b4.append(liste);
|
||
}
|
||
if (d.profil?.naechster_review) {
|
||
b4.append(el('p', 'block__frage', 'Nächster Review-Termin: ' + datum(d.profil.naechster_review)));
|
||
}
|
||
ziel.append(b4);
|
||
|
||
$('entscheidung-block').hidden = false;
|
||
}
|
||
|
||
async function laden() {
|
||
melde('');
|
||
const creator = $('f-creator').value || 'alle';
|
||
const tage = $('f-tage').value;
|
||
try {
|
||
const a = await hole(`/workspace/api/report?creator=${encodeURIComponent(creator)}&tage=${tage}`);
|
||
if (!a.ok) { melde('Report konnte nicht erstellt werden.'); return; }
|
||
zeichne(await a.json());
|
||
} catch { /* umgeleitet */ }
|
||
}
|
||
|
||
/* ---------- Entscheidung ------------------------------------------------ */
|
||
|
||
$('entscheidung').addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
$('e-fehler').textContent = '';
|
||
const knopf = $('e-speichern');
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/report/entscheidung', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
titel: $('e-titel').value,
|
||
frist: $('e-frist').value,
|
||
creator_id: $('f-creator').value || null,
|
||
}),
|
||
});
|
||
if (!a.ok) {
|
||
$('e-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
||
return;
|
||
}
|
||
$('entscheidung').reset();
|
||
$('e-stand').textContent = 'Als Aufgabe angelegt ✓';
|
||
setTimeout(() => { $('e-stand').textContent = ''; }, 3200);
|
||
await laden();
|
||
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
||
});
|
||
|
||
/* ---------- Start -------------------------------------------------------- */
|
||
|
||
(async () => {
|
||
try {
|
||
const a = await hole('/workspace/api/ich');
|
||
if (!a.ok) { location.assign('/workspace/'); return; }
|
||
ich = await a.json();
|
||
} catch { return; }
|
||
$('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
|
||
|
||
try {
|
||
const { creator } = await (await hole('/workspace/api/report/creator')).json();
|
||
if (ich.rolle === 'admin') {
|
||
$('creator-block').hidden = false;
|
||
const alle = document.createElement('option');
|
||
alle.value = ''; alle.textContent = 'Alle Creator';
|
||
$('f-creator').append(alle);
|
||
} else if (ich.rolle === 'scout') {
|
||
/* Ein Scout waehlt zwischen SEINEN Creator -- "alle" gibt es
|
||
fuer ihn nicht, das waere die Gesamtuebersicht. */
|
||
if (!creator.length) {
|
||
$('unterzeile').textContent =
|
||
'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.';
|
||
return;
|
||
}
|
||
$('creator-block').hidden = false;
|
||
}
|
||
for (const c of creator) {
|
||
const o = document.createElement('option');
|
||
o.value = String(c.id); o.textContent = c.name;
|
||
$('f-creator').append(o);
|
||
}
|
||
if (ich.rolle === 'creator') $('f-creator').value = String(ich.id);
|
||
} catch { return; }
|
||
|
||
$('f-creator').addEventListener('change', laden);
|
||
$('f-tage').addEventListener('change', laden);
|
||
await laden();
|
||
})();
|
||
})();
|