/* =================================================================== Unterweisungen — durchgehen und beidseitig bestätigen. Erscheint im Bereich "Schutz & Regeln". Der Ablauf ist bewusst dreistufig sichtbar, damit niemand raten muss, woran es hängt: offen niemand hat bestätigt halb einer von beiden -- und die Seite sagt, wer fehlt abgeschlossen beide, mit Datum und Namen. Ab hier unveränderlich. Warum die halbe Stufe sichtbar ist: Ein Nachweis, bei dem nur einer unterschrieben hat, ist kein Nachweis. Wer das nicht sieht, hält ihn für fertig -- und merkt es erst, wenn jemand danach fragt. =================================================================== */ (() => { 'use strict'; const el = (tag, klasse, text) => { const k = document.createElement(tag); if (klasse) k.className = klasse; if (text !== undefined) k.textContent = text; return k; }; 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; } const datum = (iso) => (iso ? new Date(iso).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : null); let daten = null; let wahl = null; /* welcher Creator wird gerade angesehen */ window.schulungBlock = async function (behaelter) { if (!behaelter) return; try { const a = await hole('/workspace/api/schulung'); if (!a.ok) return; daten = await a.json(); } catch { return; } behaelter.textContent = ''; const block = el('section', 'schulung'); /* ---- Kopf ---- */ const kopf = el('div', 'schulung__kopf'); const t = el('div', null); t.append(el('p', 'feldschild', 'Unterweisungen')); t.append(el('p', 'schulung__unter', 'Dokumente, die gemeinsam durchgegangen und von BEIDEN Seiten bestätigt ' + 'werden. Eine abgeschlossene Bestätigung ist der Nachweis und lässt sich ' + 'danach nicht mehr ändern.')); kopf.append(t); /* Betreuer wählen aus, für wen sie gerade bestätigen. */ if (daten.ich.rolle !== 'creator' && daten.creatoren.length) { const feld = el('div', 'schulung__wahl'); feld.append(el('label', 'feld-schild', 'Creator')); const s = el('select'); s.id = 'schulung-creator'; for (const c of daten.creatoren) { const o = document.createElement('option'); o.value = String(c.id); o.textContent = c.name; s.append(o); } wahl = wahl && daten.creatoren.some((c) => c.id === wahl) ? wahl : daten.creatoren[0].id; s.value = String(wahl); s.addEventListener('change', () => { wahl = Number(s.value); window.schulungBlock(behaelter); }); feld.append(s); kopf.append(feld); } else { wahl = daten.ich.id; } block.append(kopf); if (!daten.schulungen.length) { const leer = el('div', 'leer-hinweis'); leer.append(el('p', null, daten.darf_anlegen ? 'Noch keine Unterweisung angelegt.' : 'Es gibt noch keine Unterweisungen.')); if (daten.darf_anlegen) { leer.append(el('p', 'leer-hinweis__tipp', 'Wähle unten ein Dokument aus der Wissens-Bibliothek – oder lade dort ' + 'zuerst eines hoch.')); } block.append(leer); } /* ---- Die Liste ---- */ const liste = el('div', 'schulung__liste'); for (const s of daten.schulungen) { const stand = daten.stand.find((b) => b.schulung_id === s.id && b.creator_id === wahl); const stufe = !stand ? 'offen' : stand.abgeschlossen ? 'fertig' : 'halb'; const k = el('article', 'unterweisung'); k.dataset.stufe = stufe; const zeile = el('div', 'unterweisung__kopf'); const links = el('div', null); links.append(el('h3', 'unterweisung__titel', s.titel)); if (s.datei) links.append(el('p', 'unterweisung__datei', s.datei)); if (s.beschreibung) links.append(el('p', 'unterweisung__text', s.beschreibung)); zeile.append(links); const marke = el('span', 'unterweisung__marke', stufe === 'fertig' ? 'Bestätigt' : stufe === 'halb' ? 'Halb – einer fehlt' : 'Offen'); zeile.append(marke); k.append(zeile); /* ---- Die beiden Unterschriften ---- */ const beide = el('div', 'unterschriften'); for (const [wer, wann, name] of [ ['Creator', stand?.creator_am, stand?.creator_name], ['Betreuung', stand?.betreuer_am, stand?.betreuer_name], ]) { const u = el('div', 'unterschrift'); u.dataset.da = wann ? 'ja' : 'nein'; u.append(el('span', 'unterschrift__wer', wer)); u.append(el('span', 'unterschrift__wann', wann ? `${name || ''} · ${datum(wann)}` : 'steht noch aus')); beide.append(u); } k.append(beide); /* ---- Was man jetzt tun kann ---- */ const fuss = el('div', 'unterweisung__fuss'); if (s.wissen) { const auf = el('a', 'schritt', 'Dokument öffnen'); auf.href = `/workspace/api/wissen/${s.wissen}/datei`; auf.target = '_blank'; auf.rel = 'noopener noreferrer'; fuss.append(auf); } const alsCreator = daten.ich.rolle === 'creator'; const schonIch = alsCreator ? stand?.creator_am : stand?.betreuer_am; if (stufe === 'fertig') { const fest = el('span', 'unterweisung__fest', `Abgeschlossen am ${datum(stand.abgeschlossen_am)} – unveränderlich.`); fuss.append(fest); /* Stimmt die Datei noch mit der bestätigten überein? Diese Frage stellt sonst niemand -- und sie entscheidet, ob der Nachweis noch etwas wert ist. */ if (stand.datei_stimmt === false) { fuss.append(el('span', 'unterweisung__warnung', 'Achtung: Das Dokument wurde seit der Bestätigung ausgetauscht.')); } } else if (schonIch) { fuss.append(el('span', 'unterweisung__wartet', alsCreator ? 'Du hast bestätigt. Es fehlt noch deine Betreuung.' : 'Du hast bestätigt. Es fehlt noch der Creator.')); } else { const b = el('button', 'knopf knopf--klein', 'Durchgegangen und bestätigt'); b.type = 'button'; b.addEventListener('click', async () => { if (!confirm('Bestätigen, dass ihr das Dokument gemeinsam durchgegangen seid?\n\n' + 'Sobald BEIDE bestätigt haben, ist der Nachweis endgültig und lässt sich ' + 'nicht mehr ändern.')) return; b.disabled = true; b.textContent = 'wird gespeichert …'; try { const a = await hole(`/workspace/api/schulung/${s.id}/bestaetigen`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ creator_id: wahl }), }); if (!a.ok) { const f = await a.json().catch(() => ({})); alert(f.fehler || 'Ging nicht.'); b.disabled = false; b.textContent = 'Durchgegangen und bestätigt'; return; } window.schulungBlock(behaelter); } catch { b.disabled = false; } }); fuss.append(b); } k.append(fuss); liste.append(k); } block.append(liste); /* ---- Neue Unterweisung (nur Leitung) ---- */ if (daten.darf_anlegen) block.append(await neuFormular(behaelter)); behaelter.append(block); behaelter.hidden = false; }; /* Eine neue Unterweisung entsteht aus einem Dokument, das SCHON in der Bibliothek liegt. Kein zweiter Upload-Weg: Sonst gäbe es Dateien, die nur hier existieren und in der Bibliothek fehlen -- und niemand wüsste, welche Fassung die richtige ist. */ async function neuFormular(behaelter) { const f = el('form', 'schulung__neu'); f.append(el('p', 'feldschild', 'Unterweisung hinzufügen')); f.append(el('p', 'schulung__unter', 'Wähle ein Dokument aus der Wissens-Bibliothek. Liegt es dort noch nicht, ' + 'lade es zuerst dort hoch – so gibt es jede Datei nur einmal.')); const raster = el('div', 'neu__raster'); const fTitel = el('div', null); fTitel.append(el('label', 'feld-schild', 'Worum geht es?')); const iTitel = el('input'); iTitel.maxLength = 160; iTitel.placeholder = 'z. B. Community-Richtlinien durchgehen'; fTitel.append(iTitel); raster.append(fTitel); const fDok = el('div', null); fDok.append(el('label', 'feld-schild', 'Dokument')); const sDok = el('select'); sDok.append(new Option('— aus der Bibliothek wählen —', '')); try { const a = await hole('/workspace/api/wissen'); if (a.ok) { const { eintraege } = await a.json(); for (const w of eintraege) sDok.append(new Option(w.titel, String(w.id))); } } catch { /* leer lassen */ } fDok.append(sDok); raster.append(fDok); f.append(raster); const fehler = el('p', 'fehler'); f.append(fehler); const knopf = el('button', 'knopf knopf--klein', 'Hinzufügen'); knopf.type = 'submit'; f.append(knopf); f.addEventListener('submit', async (e) => { e.preventDefault(); fehler.textContent = ''; knopf.disabled = true; try { const a = await hole('/workspace/api/schulung', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ titel: iTitel.value, wissen_id: Number(sDok.value) || null }), }); if (!a.ok) { fehler.textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.'; return; } window.schulungBlock(behaelter); } catch { /* umgeleitet */ } finally { knopf.disabled = false; } }); return f; } })();