Gemessen am 07.09.2026, bevor irgendetwas geaendert wurde: Von drei Agentur-Eintraegen sah eine Creatorin genau EINEN -- den, der ihr zugeordnet war. Ein Event fuer alle traf weder `creator_id = ich` noch `erstellt_von = ich`; die Agentur-Seite war fuer jeden Creator leer. Nicht kaputt, nicht fehlerhaft: leer, so wie eine Seite aussieht, auf der noch nichts steht. Die Pruefung dazu war gruen. Sie legte ihre Testeintraege mit `creator_id: idLuna` an und pruefte damit einen Fall, den es im Alltag nicht gibt. - Bereichseinstellung `fuerAlle` + `ohneCreatorBezug`, daraus abgeleitet sichtbarEintrag(). BEWUSST neben sichtbar() statt darin: an derselben Funktion haengen Aufgaben, Dateien, Termine und Calls -- wer dort "1=1" einschleust, gibt nebenbei fremde Akten frei. Eine Gegenprobe mit einer zweiten Creatorin haelt das fest. - Die Zuordnung bietet nur noch "Agentur" an. Der Server verwirft eine Zuordnung ausserdem selbst -- inklusive des frei getippten Namens, und zwar NACH externPruefen: davor haette der Name den Riegel wieder aufgemacht. - "Event & Kampagne" heisst jetzt "Agentur-Events" und hat ein eigenes Formular: Von/Bis, Titel, Beschreibung, Aufgaben (Punkte und Preise), Regeln. Die Karte zeigt den Zustand als WORT (laeuft bis / startet / vorbei seit), nicht nur als Farbe. - Drei neue Spalten -- und sie stehen auch im Tabellenneubau vom 06.09. Der laeuft NACH dem Spaltennachtrag und haette sie samt Inhalt weggeworfen, ohne Fehler und mit stimmender Zeilenzahl. - Creator sehen weiterhin alles und tragen weiterhin nichts ein (403). Die Unterzeile sagt jetzt "alles, was hier steht" statt "alles, was zu dir gehoert" -- eine vollstaendige Liste soll sich nicht wie ein Ausschnitt lesen. pruef-agentur: 61 Pruefungen (vorher 40), alle gruen. Zwei eigene Fehler nebenbei gefunden und behoben: eine Beschriftung mit 11,2 px (Grenze 11,5) und ein Testdatum aus UTC statt Ortszeit. Zusaetzlich gruen: css-klassen, struktur, formulare, barrierefrei-workspace, handy. Co-Authored-By: Claude Opus 5 <[email protected]>
552 lines
24 KiB
JavaScript
552 lines
24 KiB
JavaScript
/* ===================================================================
|
||
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.',
|
||
/* Der Satz nennt die Trennung beim Namen, die das Konzept fordert:
|
||
Betreuung und Umsetzung sind unsere Seite, offizielle Wege sind
|
||
Agenturseite. Solange das nur im Kopf steht, wird es bei jedem
|
||
Streitfall neu verhandelt. */
|
||
agentur: 'Kampagnen, Schulungen und der offizielle Weg zur Agentur – mit Stand und Antwort.',
|
||
};
|
||
|
||
let ich = null;
|
||
let bereich = null;
|
||
let einstellung = null;
|
||
let filter = '';
|
||
/* Nur beim ersten Laden springen -- sonst reisst es die Seite nach
|
||
jeder Bewertung erneut an dieselbe Stelle. */
|
||
let gesprungen = 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 || ''; };
|
||
const datum = (iso) => (iso ? iso.slice(0, 10).split('-').reverse().join('.') : '—');
|
||
|
||
/* ---------- Agentur-Events ---------------------------------------------
|
||
Ein Event ist kein Eintrag mit Ueberschrift und Text, sondern eine
|
||
Aktion mit Zeitraum, Aufgaben und Regeln. Welche Art das ist, sagt
|
||
der Server (die Arten kommen aus workspace-bereiche.js) -- die
|
||
Kennung steht hier trotzdem woertlich, weil das Formular je Art
|
||
anders aussieht und es dafuer keine allgemeine Regel gibt. Sie
|
||
steht an EINER Stelle, damit sie sich nicht verteilt. */
|
||
const EVENT_BEREICH = 'agentur';
|
||
const EVENT_ART = 'kampagne';
|
||
const istEventArt = (art) => bereich === EVENT_BEREICH && art === EVENT_ART;
|
||
|
||
/* Laeuft das Event noch, kommt es erst, ist es vorbei?
|
||
Der Vergleich laeuft auf den ISO-Zeichenketten, nicht auf Date --
|
||
"2026-09-12" < "2026-09-30" ist als Text schon richtig, und ein
|
||
Date() haette hier eine Zeitzone erfunden, die niemand gemeint hat.
|
||
Ohne Ende gilt der Beginntag als der ganze Zeitraum. */
|
||
function eventStand(e) {
|
||
const heute = window.heuteLokal ? window.heuteLokal() : new Date().toISOString().slice(0, 10);
|
||
const von = (e.datum || '').slice(0, 10);
|
||
const bis = (e.event_ende || von).slice(0, 10);
|
||
if (!von) return null;
|
||
if (heute < von) return { schluessel: 'kommt', text: 'startet ' + datum(von) };
|
||
if (heute > bis) return { schluessel: 'vorbei', text: 'vorbei seit ' + datum(bis) };
|
||
return { schluessel: 'laeuft', text: von === bis ? 'heute' : 'läuft bis ' + datum(bis) };
|
||
}
|
||
|
||
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);
|
||
|
||
/* ---- Agentur-Event: Zeitraum, Aufgaben, Regeln -------------------
|
||
Die Reihenfolge ist nicht beliebig. Wer auf ein Event schaut,
|
||
fragt in genau dieser Folge: Laeuft das ueberhaupt noch? Was
|
||
muss ich tun? Was gilt dabei? Steht der Zeitraum unten im Fuss
|
||
-- da, wo bei allen anderen Karten das Datum steht --, liest man
|
||
erst die Aufgaben und danach, dass es seit einer Woche vorbei
|
||
ist. */
|
||
const stand = istEventArt(e.art) ? eventStand(e) : null;
|
||
if (stand) {
|
||
k.dataset.event = stand.schluessel;
|
||
const zeile = el('p', 'event-zeitraum');
|
||
zeile.dataset.stand = stand.schluessel;
|
||
zeile.append(el('span', 'event-zeitraum__marke', stand.text));
|
||
zeile.append(el('span', 'event-zeitraum__spanne',
|
||
e.event_ende && e.event_ende !== e.datum
|
||
? `${datum(e.datum)} – ${datum(e.event_ende)}`
|
||
: datum(e.datum)));
|
||
k.append(zeile);
|
||
}
|
||
|
||
if (e.text) k.append(el('p', 'eintrag-text', e.text));
|
||
|
||
if (stand) {
|
||
for (const [feld, name] of [['event_aufgaben', 'Aufgaben'], ['event_regeln', 'Regeln']]) {
|
||
if (!e[feld]) continue;
|
||
const block = el('div', 'event-block');
|
||
block.append(el('h4', 'event-block__schild', name));
|
||
/* Zeilenumbrueche bleiben stehen (white-space: pre-line im CSS).
|
||
Eine Aufzaehlung, die zu einem Absatz zusammenlaeuft, ist
|
||
genau das, was diese beiden Felder verhindern sollen. */
|
||
block.append(el('p', 'event-block__text', e[feld]));
|
||
k.append(block);
|
||
}
|
||
}
|
||
|
||
const fuss = el('div', 'eintrag-fuss');
|
||
/* Beim Event steht der Zeitraum schon oben -- das Datum hier waere
|
||
dieselbe Zahl ein zweites Mal. */
|
||
if (!stand) 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;
|
||
}
|
||
|
||
/* Sprung vom Bericht (01.09.2026). "dringend" ist kein Filter der
|
||
Seite -- die Dringlichkeit steht als data-dringlich an der Karte,
|
||
und genau die wird hervorgehoben. "offen" dagegen IST ein Filter,
|
||
der wurde schon vor dem ersten Laden gesetzt. */
|
||
function sprungZeigen(ziel, karten) {
|
||
if (gesprungen) return;
|
||
gesprungen = true;
|
||
const was = window.zielSchluessel?.();
|
||
if (was === 'dringend') {
|
||
/* OFFEN und hoch -- genau so zaehlt der Bericht (workspace-reports.js:
|
||
"status = 'offen' AND dringlichkeit = 'hoch'"). Ohne das
|
||
status-Merkmal hebt die Seite auch erledigte dringende Eintraege
|
||
hervor: Man klickt auf "2" und bekommt drei markiert. Gefunden
|
||
von pruef-sprung.mjs -- mit blossem Auge waere das nie
|
||
aufgefallen, beide Zahlen sehen plausibel aus. */
|
||
window.zielMarkieren?.(
|
||
ziel.querySelectorAll('.eintrag-karte[data-dringlich="hoch"][data-status="offen"]'),
|
||
'Einträge, die offen und als dringend eingestuft sind');
|
||
} else if (was === 'offen') {
|
||
window.zielMarkieren?.([...karten.values()], 'Einträge, die noch offen sind',
|
||
async () => { filter = ''; await laden(); });
|
||
}
|
||
}
|
||
|
||
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.'));
|
||
/* AUCH HIER springen -- und das ist kein Schoenheitsfehler,
|
||
sondern der wichtigere Fall: Wer auf "3 dringende Punkte"
|
||
klickt und auf einer leeren Seite landet, glaubt, er sei
|
||
falsch abgebogen. Die Leiste sagt ihm, dass er richtig ist und
|
||
die Sache erledigt. */
|
||
sprungZeigen(ziel, new Map());
|
||
return;
|
||
}
|
||
const karten = new Map();
|
||
for (const e of sichtbar) {
|
||
const k = karte(e);
|
||
karten.set(e.id, k);
|
||
ziel.append(k);
|
||
}
|
||
sprungZeigen(ziel, karten);
|
||
|
||
/* Bewertung und Rueckmeldungen. In EINER Abfrage fuer die ganze
|
||
Liste -- zwanzig Eintraege einzeln zu fragen waeren zwanzig
|
||
Anfragen, und die Seite ruckelte sichtbar beim Aufbau.
|
||
|
||
Nur in den Bereichen, in denen eine Beurteilung ueberhaupt
|
||
Sinn ergibt: LIVE, Community und Technik. Bei Content sind es
|
||
eigene Ideen des Creators -- die bewertet niemand mit einer
|
||
Ampel. */
|
||
/* "agentur" ist seit dem 06.09.2026 dabei, und aus demselben
|
||
Grund wie die anderen vier: Ein Anliegen an die Agentur hat
|
||
einen STAND -- eingereicht, in Klärung, beantwortet. Genau
|
||
dafür ist die Ampel da, samt der Nachrichten daran. Ohne sie
|
||
wäre der "offizielle Weg" wieder nur eine Liste, in der etwas
|
||
steht und niemand weiß, wie es ausgegangen ist. */
|
||
if (["live", "community", "technik", "schutz", "agentur"].includes(bereich)
|
||
&& window.ampelAnhaengen && karten.size) {
|
||
const stand = await window.ampelnHolen([...karten.keys()]);
|
||
for (const [id, k] of karten) {
|
||
window.ampelAnhaengen(k, id, {
|
||
ampel: stand.ampeln?.[id] || null,
|
||
anzahl: stand.anzahlNachrichten?.[id] || 0,
|
||
});
|
||
}
|
||
}
|
||
} catch { /* umgeleitet */ } finally {
|
||
/* IM finally, nicht dahinter. laden() steigt oben frueh aus,
|
||
wenn die Liste leer ist -- und dann wurden diese beiden
|
||
Bloecke nie gebaut. Also ausgerechnet auf der leeren Seite,
|
||
fuer die sie gedacht sind. Die Browserpruefung hat es sofort
|
||
gemeldet ("eine Unterweisung: 0"). */
|
||
await vorlagenZeigen();
|
||
await schulungZeigen();
|
||
}
|
||
}
|
||
|
||
/* Unterweisungen -- nur im Bereich Schutz & Regeln. Dort gehoeren
|
||
Richtlinien hin, und dort sucht sie auch jemand. */
|
||
async function schulungZeigen() {
|
||
const behaelter = $('schulung');
|
||
if (!behaelter || bereich !== "schutz" || !window.schulungBlock) return;
|
||
await window.schulungBlock(behaelter);
|
||
}
|
||
|
||
/* Fertige Punkte zum Uebernehmen -- derzeit nur fuer die LIVE-Analyse.
|
||
Sie stehen ueber der Liste: Wer noch nichts hat, soll nicht erst an
|
||
einem leeren Kasten vorbeiscrollen.
|
||
|
||
Welcher Abschnitt gezeigt wird, richtet sich nach dem gewaehlten
|
||
Filter. Ohne Filter die Vorbereitung -- das ist der Teil, ohne den
|
||
die anderen beiden gar nicht erst sinnvoll sind. */
|
||
/* Die FESTE Liste -- nicht mehr Vorschlaege zum Uebernehmen.
|
||
|
||
Der Unterschied ist grundlegend: Eine Liste, die man sich erst
|
||
holen muss, ist keine Checkliste. Und zwei Creator haetten
|
||
unterschiedliche Listen gehabt, je nachdem wer sich was geholt
|
||
hat -- was einen Vergleich unmoeglich macht, und um Vergleich
|
||
geht es bei einer Betreuung.
|
||
|
||
Jetzt stehen dieselben Punkte fuer alle fest da, gruppiert. Was
|
||
sich unterscheidet, ist der Stand je Creator. */
|
||
async function vorlagenZeigen() {
|
||
const behaelter = $('vorlagen');
|
||
if (!behaelter || !window.checkliste) return;
|
||
if (!['live', 'community', 'technik'].includes(bereich)) return;
|
||
await window.checkliste(behaelter, bereich);
|
||
}
|
||
|
||
/* Fuer wen wird uebernommen? Ein Creator immer fuer sich (der Server
|
||
setzt das ohnehin), ein Betreuer fuer den gewaehlten Creator. */
|
||
function creatorWahl() {
|
||
const w = document.getElementById('f-creator');
|
||
return w && w.value ? Number(w.value) : null;
|
||
}
|
||
|
||
/* ---------- Neuer Eintrag ---------------------------------------------- */
|
||
|
||
/* Das Formular sieht bei einem Agentur-Event anders aus als bei allem
|
||
anderen -- nicht "zusaetzlich", sondern anders: Aus "Überschrift"
|
||
wird der Titel des Events, aus "Text" die Beschreibung, aus dem
|
||
Datum ein Zeitraum, und Dringlichkeit hat hier nichts zu suchen
|
||
(ein Event ist nicht dringend, es hat einen Termin).
|
||
|
||
Umgeschaltet wird bei JEDEM Artwechsel und beim Oeffnen. Nur beim
|
||
Oeffnen zu schalten waere der bequeme Fehler: Man oeffnet mit
|
||
"Schulung", waehlt "Agentur-Events" und fuellt ein Formular aus,
|
||
das noch nach der alten Art fragt. */
|
||
function formularAnpassen() {
|
||
const event = istEventArt($('f-art').value);
|
||
|
||
$('feld-ende').hidden = !event;
|
||
$('feld-aufgaben').hidden = !event;
|
||
$('feld-regeln').hidden = !event;
|
||
if (einstellung?.dringlichkeit) $('feld-dringlich').hidden = event;
|
||
|
||
$('schild-datum').textContent = event ? 'Von' : 'Datum';
|
||
$('schild-titel').textContent = event ? 'Titel des Events' : 'Überschrift';
|
||
/* textContent wuerde das <span class="leise"> mit erschlagen --
|
||
deshalb nur der erste Textknoten, das kleine "(optional)" bleibt
|
||
stehen. */
|
||
const textSchild = $('schild-text').firstChild;
|
||
if (textSchild) textSchild.nodeValue = event ? 'Beschreibung ' : 'Text ';
|
||
|
||
/* Leeren, was gerade unsichtbar wird. Sonst reicht ein Wechsel von
|
||
"Agentur-Events" auf "Schulung", und das Ende des Zeitraums
|
||
faehrt unsichtbar mit -- der Server nimmt es an, weil es zum
|
||
Bereich gehoert, und an der Schulung stuende ein Enddatum, das
|
||
niemand eingetragen hat und niemand sieht. */
|
||
if (!event) {
|
||
$('f-ende').value = '';
|
||
$('f-aufgaben').value = '';
|
||
$('f-regeln').value = '';
|
||
}
|
||
}
|
||
|
||
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 = window.heuteLokal();
|
||
formularAnpassen();
|
||
umschalten(true);
|
||
});
|
||
$('neu-abbrechen').addEventListener('click', () => {
|
||
$('neu').reset(); $('neu-fehler').textContent = ''; formularAnpassen(); umschalten(false);
|
||
});
|
||
/* "change" und nicht "input": Die Auswahl wird ueber wahl.js zu einer
|
||
Knopfliste umgebaut, und die meldet ihre Wahl als change-Ereignis
|
||
am urspruenglichen <select> -- ohne dieses Ereignis merkte das
|
||
Formular vom Artwechsel nichts. */
|
||
$('f-art').addEventListener('change', formularAnpassen);
|
||
|
||
$('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,
|
||
};
|
||
const event = istEventArt(daten.art);
|
||
if (einstellung.dringlichkeit && !event) daten.dringlichkeit = $('f-dringlich').value;
|
||
if (einstellung.bewertung) daten.bewertung = $('f-bewertung').value || null;
|
||
if (event) {
|
||
daten.event_ende = $('f-ende').value || null;
|
||
daten.event_aufgaben = $('f-aufgaben').value || null;
|
||
daten.event_regeln = $('f-regeln').value || null;
|
||
}
|
||
/* Im Agentur-Bereich gehoert ein Eintrag allen -- es gibt nichts
|
||
zuzuordnen, also wird auch nichts mitgeschickt. Der Server setzt
|
||
beides ohnehin auf leer (ohneCreatorBezug); hier zu schweigen
|
||
spart die stille Annahme, das Formular haette etwas zu sagen. */
|
||
if (LEITUNG.has(ich.rolle) && !einstellung.ohneCreatorBezug) {
|
||
daten.creator_id = $('f-creator').value || null;
|
||
daten.creator_extern = $('f-creator').dataset.extern || 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') || '';
|
||
|
||
/* Wird eine Bewertung gesetzt, baut sich die Liste neu auf. Der
|
||
Zuhoerer haengt am Dokument und wird genau einmal gesetzt --
|
||
laden() laeuft mehrfach, und zwei Zuhoerer wuerden die Liste
|
||
zweimal neu bauen. */
|
||
document.addEventListener('ampel-geaendert', () => laden());
|
||
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;
|
||
/* Zwei verschiedene Saetze, weil es zwei verschiedene Wahrheiten
|
||
sind. In den Betreuungsbereichen sieht ein Creator, was ZU IHM
|
||
gehoert -- in der Agentur sieht er ALLES. Stuende hier weiter
|
||
"was zu dir gehört", laese sich eine vollstaendige Liste wie
|
||
ein Ausschnitt, und die naheliegende Frage waere: Was sehe ich
|
||
hier eigentlich nicht? */
|
||
$('unterzeile').textContent = (UNTERZEILE[bereich] || '')
|
||
+ (einstellung.fuerAlle
|
||
? ' Eingetragen wird von der Agentur – du siehst alles, was hier steht.'
|
||
: ' 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;
|
||
|
||
/* AGENTUR: EINE EINZIGE WAHL, UND DIE HEISST "AGENTUR" (07.09.2026).
|
||
|
||
Hier stand dieselbe Creator-Liste wie ueberall -- und sie war
|
||
eine Falle. Wer ein Event fuer alle anlegt und dabei aus
|
||
Gewohnheit einen Namen anklickt, hat es damit UNSICHTBAR
|
||
gemacht: fuer alle anderen Creator, ohne Warnung, ohne dass es
|
||
der Liste danach anzusehen waere.
|
||
|
||
Kein Feld auszublenden waere die halbe Loesung gewesen: Dann
|
||
fragt man sich, wem der Eintrag gehoert. Eine Liste mit genau
|
||
einer Wahl beantwortet die Frage, statt sie wegzunehmen.
|
||
|
||
Die Personenwahl (freier Name, Suchfeld) wird hier bewusst
|
||
NICHT aufgesetzt -- sie erlaubt getippte Namen, und genau das
|
||
soll es hier nicht geben. */
|
||
if (einstellung.ohneCreatorBezug) {
|
||
$('schild-creator').textContent = 'Gilt für';
|
||
$('f-creator').textContent = '';
|
||
const o = document.createElement('option');
|
||
o.value = ''; o.textContent = 'Agentur';
|
||
$('f-creator').append(o);
|
||
const hinweis = $('hinweis-creator');
|
||
hinweis.textContent = 'Agentur-Einträge gehören allen – jeder im Workspace sieht sie.';
|
||
hinweis.hidden = false;
|
||
} else {
|
||
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 */ }
|
||
window.personenwahl?.('f-creator', { hinweis: "Kein Konto im Workspace – der Eintrag gehört dann zu keiner Person und taucht in personenbezogenen Auswertungen nicht auf. Sichtbar bleibt er für dich." });
|
||
}
|
||
}
|
||
formularAnpassen();
|
||
|
||
/* Kommt jemand vom Bericht mit "nur die offenen", wird der Filter
|
||
VOR dem ersten Laden gesetzt -- sonst baut die Seite alles auf,
|
||
um es gleich wieder wegzuwerfen, und das flackert sichtbar. */
|
||
if (window.zielSchluessel?.() === 'offen') filter = 'offen';
|
||
|
||
await laden();
|
||
})();
|
||
})();
|