/* =================================================================== Eine Ansicht für fünf Bereiche (LIVE, Content, Technik, Community, Schutz). Welcher gemeint ist, steht in der Adresse: ?b=live Welche Arten es gibt und ob Bewertung oder Dringlichkeit dazugehören, sagt der Server in der Antwort -- diese Datei kennt die Bereiche nicht auswendig. Ein neuer Bereich braucht hier also keine Zeile. =================================================================== */ (() => { 'use strict'; /* DogFather und Manager haben dieselben Rechte -- die Unterscheidung macht nur die Personenverwaltung, und die prueft der Server. */ const LEITUNG = new Set(['admin', 'manager']); /* Wer hier eintraegt: DogFather, Manager und Scout. Der Creator liest nur mit -- diese Bereiche sind die Betreuungsakte, nicht sein Notizbuch. Der Server setzt das ebenfalls durch (siehe workspace-bereiche.js); hier geht es nur darum, ihm keine Knoepfe hinzustellen, die ohnehin nur eine Absage bringen. */ const darfEintragen = () => ich && ich.rolle !== 'creator'; const $ = (id) => document.getElementById(id); const UNTERZEILE = { live: 'Vorbereitung, Mitschrift und Auswertung – LIVE systematisch verbessern.', content: 'Ideen, Produktion und Veröffentlichtes an einer Stelle statt in Chats.', technik: 'Setups, Fehlerbilder und Lösungen pro Creator sauber dokumentiert.', community: 'Moderation, Aktionen und Konflikte – ruhig statt Drama.', schutz: 'Richtlinien, Vorfälle und was daraus gelernt wurde.', }; let ich = null; let bereich = null; let einstellung = null; let filter = ''; 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('unbekannt'); } return a; } function tuKnopf(text, tu) { const b = el('button', 'schritt', text); b.type = 'button'; b.addEventListener('click', async () => { b.disabled = true; melde(''); try { await tu(); } catch { /* umgeleitet */ } finally { b.disabled = false; } }); return b; } /* ---------- Zeichnen --------------------------------------------------- */ function karte(e) { const k = el('article', 'eintrag-karte'); k.dataset.dringlich = e.dringlichkeit; k.dataset.status = e.status; const kopf = el('div', 'eintrag-kopf'); kopf.append(el('span', 'marke-art', einstellung.arten[e.art] || e.art)); kopf.append(el('h3', 'eintrag-titel', e.titel)); if (einstellung.bewertung && e.bewertung) { const b = el('span', 'bewertung'); b.setAttribute('aria-label', `Bewertung ${e.bewertung} von 5`); for (let i = 1; i <= 5; i++) { const punkt = el('span', 'bewertung__punkt'); if (i <= e.bewertung) punkt.dataset.voll = 'ja'; b.append(punkt); } kopf.append(b); } k.append(kopf); if (e.text) k.append(el('p', 'eintrag-text', e.text)); const fuss = el('div', 'eintrag-fuss'); fuss.append(el('span', null, datum(e.datum))); if (LEITUNG.has(ich.rolle) && e.creator_name) fuss.append(el('span', null, '· ' + e.creator_name)); if (e.erstellt_name) fuss.append(el('span', null, '· von ' + e.erstellt_name)); if (e.status === 'erledigt') fuss.append(el('span', null, '· erledigt')); if (!darfEintragen()) { k.append(fuss); return k; } const knoepfe = el('div', 'eintrag-knoepfe'); knoepfe.append(tuKnopf(e.status === 'offen' ? 'erledigt ▶' : '◀ wieder offen', async () => { const a = await hole(`/workspace/api/bereich/${bereich}/${e.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: e.status === 'offen' ? 'erledigt' : 'offen' }), }); if (!a.ok) { melde('Ändern hat nicht geklappt.'); return; } await laden(); })); if (LEITUNG.has(ich.rolle) || e.erstellt_von === ich.id) { knoepfe.append(tuKnopf('löschen', async () => { if (!confirm(`„${e.titel}" wirklich löschen?`)) return; const a = await hole(`/workspace/api/bereich/${bereich}/${e.id}`, { method: 'DELETE' }); if (!a.ok) { melde('Löschen hat nicht geklappt.'); return; } await laden(); })); } fuss.append(knoepfe); k.append(fuss); return k; } function filterBauen() { const ziel = $('filter'); ziel.textContent = ''; const knopf = (wert, text) => { const b = el('button', 'schritt', text); b.type = 'button'; b.setAttribute('aria-pressed', String(filter === wert)); b.addEventListener('click', async () => { filter = wert; await laden(); }); return b; }; ziel.append(knopf('', 'Alle')); ziel.append(knopf('offen', 'Nur offene')); for (const [wert, name] of Object.entries(einstellung.arten)) ziel.append(knopf(wert, name)); } async function laden() { try { const a = await hole(`/workspace/api/bereich/${bereich}`); if (!a.ok) { melde('Einträge konnten nicht geladen werden.'); return; } const daten = await a.json(); einstellung = daten.einstellung; filterBauen(); const ziel = $('liste'); ziel.textContent = ''; ziel.setAttribute('aria-busy', 'false'); const sichtbar = daten.eintraege.filter((e) => { if (!filter) return true; if (filter === 'offen') return e.status === 'offen'; return e.art === filter; }); if (!sichtbar.length) { ziel.append(el('p', 'leer-hinweis', daten.eintraege.length ? 'Zu dieser Auswahl gibt es nichts.' : 'Noch kein Eintrag in diesem Bereich.')); return; } for (const e of sichtbar) ziel.append(karte(e)); } catch { /* umgeleitet */ } } /* ---------- Neuer Eintrag ---------------------------------------------- */ const umschalten = (auf) => { /* Auch hier die Regel, nicht nur einmal beim Aufbau: Diese Funktion ist die einzige Stelle, die das Formular je wieder sichtbar macht. Sperrt man nur beim Laden, holt der naechste Aufruf den Knopf zurueck -- und niemand faellt es auf, weil es der Alltagsweg nicht ist. */ if (!darfEintragen()) { $('neu').hidden = true; $('neu-oeffnen').hidden = true; return; } $('neu').hidden = !auf; $('neu-oeffnen').hidden = auf; if (auf) $('f-titel').focus(); }; $('neu-oeffnen').addEventListener('click', () => { $('f-datum').value = new Date().toISOString().slice(0, 10); umschalten(true); }); $('neu-abbrechen').addEventListener('click', () => { $('neu').reset(); $('neu-fehler').textContent = ''; umschalten(false); }); $('neu').addEventListener('submit', async (e) => { e.preventDefault(); $('neu-fehler').textContent = ''; const knopf = $('neu-speichern'); const daten = { art: $('f-art').value, titel: $('f-titel').value, text: $('f-text').value, datum: $('f-datum').value, }; if (einstellung.dringlichkeit) daten.dringlichkeit = $('f-dringlich').value; if (einstellung.bewertung) daten.bewertung = $('f-bewertung').value || null; if (LEITUNG.has(ich.rolle)) daten.creator_id = $('f-creator').value || null; knopf.disabled = true; try { const a = await hole(`/workspace/api/bereich/${bereich}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(daten), }); if (!a.ok) { $('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.'; return; } $('neu').reset(); umschalten(false); await laden(); } catch { /* umgeleitet */ } finally { knopf.disabled = false; } }); /* ---------- Start -------------------------------------------------------- */ (async () => { bereich = new URLSearchParams(location.search).get('b') || ''; if (!/^[a-z]+$/.test(bereich)) { location.assign('/workspace/start.html'); return; } try { const a = await hole('/workspace/api/ich'); if (!a.ok) { location.assign('/workspace/'); return; } ich = await a.json(); } catch { return; } window.werZeigen?.(ich); let daten; try { const a = await hole(`/workspace/api/bereich/${bereich}`); if (!a.ok) { location.assign('/workspace/start.html'); return; } daten = await a.json(); } catch { return; } einstellung = daten.einstellung; document.title = einstellung.name + ' · Creator Workspace'; $('marke-bereich').textContent = einstellung.name; $('titel').textContent = einstellung.name; $('unterzeile').textContent = UNTERZEILE[bereich] || ''; /* Fuer den Creator verschwindet alles Schreibende -- und die Unterzeile sagt, warum. Ein leerer Bereich ohne Erklaerung wirkt kaputt; einer mit Erklaerung wirkt gedacht. */ if (!darfEintragen()) { $('neu-oeffnen').hidden = true; $('neu').hidden = true; $('unterzeile').textContent = (UNTERZEILE[bereich] || '') + ' Hier trägt deine Betreuung ein – du siehst alles, was zu dir gehört.'; } for (const [wert, name] of Object.entries(einstellung.arten)) { const o = document.createElement('option'); o.value = wert; o.textContent = name; $('f-art').append(o); } $('feld-dringlich').hidden = !einstellung.dringlichkeit; $('feld-bewertung').hidden = !einstellung.bewertung; if (LEITUNG.has(ich.rolle)) { $('feld-creator').hidden = false; try { const { personen } = await (await hole('/workspace/api/personen')).json(); for (const p of personen.filter((p) => p.rolle === 'creator')) { const o = document.createElement('option'); o.value = String(p.id); o.textContent = p.name; $('f-creator').append(o); } } catch { /* Zuordnung bleibt leer */ } } await laden(); })(); })();