/* =================================================================== 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; let kiDa = false; 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(); }); /* Die KI formuliert daraus eine Handlung. Der einfache Vorschlag oben bleibt bestehen -- er funktioniert auch ohne KI. */ if (kiDa && p.notiz) { form.append(window.KI.knopf('Besser formulieren', async () => { const d = await window.KI.frage('aufgabentitel', { befund: p.notiz, bereich: feld.name }); if (!d.titel) throw new Error('Die KI hat nichts geliefert.'); titel.value = d.titel; titel.focus(); })); } 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; } window.werZeigen?.(ich); kiDa = await window.KI.verfuegbar(); let liste; try { liste = await (await hole('/workspace/api/startcheck/creator')).json(); } catch { return; } if (!liste.creator.length) { $('felder').textContent = ''; /* aria-busy zurücksetzen, sonst meldet die Seite für immer "wird geladen" -- an einer Stelle, an der gerade fertig geladen und festgestellt wurde, dass es nichts zu laden gibt. Für ein Vorleseprogramm ist eine dauerhaft beschäftigte Fläche das Zeichen, dass es sich nicht lohnt weiterzulesen; gefunden beim Rollen-Rundgang am 02.09.2026. */ $('felder').setAttribute('aria-busy', 'false'); $('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(); })(); })();