Files
dogfather-universe/workspace/assets/js/startcheck.js
T
DogFatherGit 1c3e642f12 Workspace: Rolle "Management" heisst jetzt "DogFather"
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.
2026-08-28 11:55:05 +02:00

290 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ===================================================================
Start-Check / Erstanalyse (Konzept, Seite 5).
Bewusst ohne Speichern-Knopf: Jede Bewertung wird sofort gesichert.
Eine Erstanalyse geht man im Gespraech durch -- da will niemand am
Ende noch daran denken, unten auf einen Knopf zu druecken.
Und: Jeder Punkt mit Handlungsbedarf bietet direkt an, daraus eine
Aufgabe zu machen. "Ausgangslage verstehen UND daraus konkrete
Arbeitspakete bauen" -- der zweite Teil ist der, der zaehlt.
=================================================================== */
(() => {
'use strict';
const $ = (id) => document.getElementById(id);
const STUFEN = [
{ wert: 'ok', name: 'Passt' },
{ wert: 'mittel', name: 'Ausbaufähig' },
{ wert: 'handlung', name: 'Handlungsbedarf' },
];
let ich = null;
let daten = null;
let aktuell = 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 || ''; };
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'); }
return a;
}
async function setze(feld, punkt, aenderung) {
try {
const a = await hole('/workspace/api/startcheck/' + aktuell, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ feld, punkt, ...aenderung }),
});
if (!a.ok) {
melde((await a.json().catch(() => ({}))).fehler || 'Konnte nicht gespeichert werden.');
return false;
}
melde('');
return true;
} catch { return false; }
}
/* ---------- Ein Punkt ---------------------------------------------------- */
function punktZeile(feld, p) {
const k = el('div', 'punkt');
if (p.bewertung) k.dataset.bewertung = p.bewertung;
const kopf = el('div', 'punkt__kopf');
const links = el('div');
links.append(el('h3', 'punkt__name', p.name));
links.append(el('p', 'punkt__hinweis', p.hinweis));
kopf.append(links);
/* Bewertung */
if (daten.darf_aendern) {
const wahl = el('div', 'stufen');
wahl.setAttribute('role', 'group');
wahl.setAttribute('aria-label', 'Bewertung für ' + p.name);
for (const s of STUFEN) {
const b = el('button', 'stufe-knopf', s.name);
b.type = 'button';
b.dataset.wert = s.wert;
if (p.bewertung === s.wert) b.dataset.aktiv = 'ja';
b.addEventListener('click', async () => {
/* Nochmal auf dieselbe Stufe = zurücknehmen. */
const neu = p.bewertung === s.wert ? null : s.wert;
if (await setze(feld.schluessel, p.schluessel, { bewertung: neu, notiz: p.notiz })) {
await laden();
}
});
wahl.append(b);
}
kopf.append(wahl);
} else if (p.bewertung) {
const m = el('span', 'stufe-marke', STUFEN.find((s) => s.wert === p.bewertung)?.name);
m.dataset.wert = p.bewertung;
kopf.append(m);
}
k.append(kopf);
/* Notiz erst, wenn bewertet wurde. Sechzehn offene Textfelder
machen aus einer Checkliste eine Wand -- und vor der Bewertung
hat man ohnehin nichts zu notieren. */
if (daten.darf_aendern && (p.bewertung || p.notiz)) {
const n = el('textarea', 'punkt__notiz');
n.rows = 2;
n.maxLength = 2000;
n.placeholder = 'Was ist dir aufgefallen?';
n.value = p.notiz || '';
let warte = null;
n.addEventListener('input', () => {
clearTimeout(warte);
warte = setTimeout(() => {
setze(feld.schluessel, p.schluessel, { bewertung: p.bewertung, notiz: n.value });
p.notiz = n.value;
}, 700);
});
k.append(n);
} else if (p.notiz) {
k.append(el('p', 'punkt__notiz-lesen', p.notiz));
}
/* Aus dem Befund ein Arbeitspaket machen. */
if (daten.darf_aendern && (p.bewertung === 'handlung' || p.bewertung === 'mittel')) {
k.append(aufgabenTeil(feld, p));
}
if (p.von && p.geaendert) {
k.append(el('p', 'punkt__quelle',
`${p.von} · ${p.geaendert.slice(0, 10).split('-').reverse().join('.')}`));
}
return k;
}
function aufgabenTeil(feld, p) {
const box = el('div', 'paket');
const auf = el('button', 'knopf-still', '+ Daraus eine Aufgabe machen');
auf.type = 'button';
const form = el('form', 'paket__form');
form.hidden = true;
const titel = el('input', 'paket__titel');
titel.type = 'text';
titel.maxLength = 160;
titel.placeholder = 'Was genau ist zu tun?';
const frist = el('input', 'paket__frist');
frist.type = 'date';
frist.title = 'Frist (optional)';
const ok = el('button', 'knopf knopf--klein', 'Anlegen');
ok.type = 'submit';
form.append(titel, frist, ok);
const stand = el('span', 'leise');
auf.addEventListener('click', () => {
form.hidden = false;
auf.hidden = true;
/* Ein sinnvoller Vorschlag statt eines leeren Feldes -- die Notiz
ist meistens schon die halbe Aufgabe. */
titel.value = p.notiz ? p.notiz.split(/[.\n]/)[0].trim().slice(0, 160) : p.name;
titel.focus();
titel.select();
});
form.addEventListener('submit', async (e) => {
e.preventDefault();
ok.disabled = true;
try {
const a = await hole(`/workspace/api/startcheck/${aktuell}/aufgabe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
feld: feld.schluessel, punkt: p.schluessel,
titel: titel.value, frist: frist.value,
}),
});
if (!a.ok) {
melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.');
return;
}
const r = await a.json();
form.hidden = true;
stand.textContent = `Im Aufgabenbrett angelegt (Priorität ${r.prioritaet}) ✓`;
setTimeout(() => { stand.textContent = ''; auf.hidden = false; }, 4000);
} catch { /* umgeleitet */ } finally { ok.disabled = false; }
});
box.append(auf, form, stand);
return box;
}
/* ---------- Zeichnen ----------------------------------------------------- */
function zeichne() {
const ziel = $('felder');
ziel.textContent = '';
ziel.setAttribute('aria-busy', 'false');
for (const f of daten.felder) {
const s = el('section', 'feld-block');
const kopf = el('div', 'feld-block__kopf');
kopf.append(el('h2', 'feld-block__titel', f.name));
const fertig = f.punkte.filter((p) => p.bewertung).length;
const z = el('span', 'feld-block__zahl', `${fertig}/${f.punkte.length}`);
if (fertig === f.punkte.length) z.dataset.fertig = 'ja';
kopf.append(z);
s.append(kopf);
const box = el('div', 'feld-block__punkte');
for (const p of f.punkte) box.append(punktZeile(f, p));
s.append(box);
ziel.append(s);
}
/* Fortschritt */
$('fortschritt').hidden = false;
$('f-zahl').textContent = `${daten.geprueft} von ${daten.gesamt}`;
const anteil = Math.round((daten.geprueft / daten.gesamt) * 100);
$('f-balken').style.width = anteil + '%';
const aussen = $('f-balken-aussen');
aussen.setAttribute('aria-valuenow', String(daten.geprueft));
aussen.setAttribute('aria-valuemax', String(daten.gesamt));
const h = $('f-hinweis');
if (daten.geprueft === 0) {
h.textContent = daten.darf_aendern
? 'Noch nichts geprüft. Am besten im Gespräch durchgehen – gespeichert wird sofort.'
: 'Die Erstanalyse wurde noch nicht ausgefüllt.';
} else if (daten.handlungsbedarf) {
h.textContent = `${daten.handlungsbedarf} ${daten.handlungsbedarf === 1
? 'Punkt braucht' : 'Punkte brauchen'} Handlung`
+ (daten.darf_aendern ? ' – daraus lassen sich direkt Aufgaben machen.' : '.');
} else if (daten.geprueft === daten.gesamt) {
h.textContent = 'Vollständig geprüft, nichts Dringendes offen.';
} else {
h.textContent = '';
}
$('fortschritt').dataset.stufe = daten.handlungsbedarf ? 'handlung'
: daten.geprueft === daten.gesamt ? 'fertig' : 'laeuft';
}
async function laden() {
try {
const a = await hole('/workspace/api/startcheck/' + aktuell);
if (a.status === 404) {
$('felder').textContent = '';
$('felder').append(el('p', 'leer-hinweis', 'Dieser Start-Check ist für dich nicht sichtbar.'));
return;
}
if (!a.ok) { melde('Start-Check konnte nicht geladen werden.'); return; }
daten = await a.json();
zeichne();
} catch { /* umgeleitet */ }
}
/* ---------- 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);
let liste;
try { liste = await (await hole('/workspace/api/startcheck/creator')).json(); } catch { return; }
if (!liste.creator.length) {
$('felder').textContent = '';
$('felder').append(el('p', 'leer-hinweis', ich.rolle === 'scout'
? 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.'
: 'Es gibt noch keinen Creator.'));
return;
}
if (!liste.eigen) {
$('creator-block').hidden = false;
for (const c of liste.creator) {
const o = document.createElement('option');
o.value = String(c.id); o.textContent = c.name;
$('f-creator').append(o);
}
$('f-creator').addEventListener('change', () => {
aktuell = Number($('f-creator').value);
laden();
});
} else {
$('unterzeile').textContent =
'Die Erstanalyse zu deinem Kanal – Grundlage für deinen 90-Tage-Plan.';
}
aktuell = liste.creator[0].id;
await laden();
})();
})();