Filipe: "eine community rolle und community kategorie, wo die community auch dann nur die sieht" · "wieso nur eine kachel — ich will mehrere, mit mehreren optionen" · "egal welche rolle hinzugefügt wird soll immer nur das sehen was ich erlaube". DIE DRITTE ADRESSE. treff.dogfather-universe.com bekommt eine eigene Weiche (treff-adresse.js), eine eigene Zugangswand, ein eigenes Manifest und acht eigene Bühnenbilder. Ohne sie stünde dort die Wand der Agentur: Spicy-Logo, "Creator Workspace", fünf Rollenkacheln mit den Namen Admin, Manager, Scout, Creator — die komplette Struktur des Unternehmens, vor der Anmeldung, für jeden mit der Adresse. Welche Wand zu welcher Tür gehört, steht jetzt in EINER Tafel (istFremdeWand) statt als Sonderfall im Code. Bei zwei Wänden war ein Sonderfall richtig; bei drei wären es drei geworden, bei vier sechs. SIEBEN BRETTER UND EIN ACHTES NUR FÜRS TEAM. Anschlagbrett, Was ansteht, Der Treff, Wunschliste, Highlights, Regeln & Hilfe, Mitmachen — dazu "Meldungen & Maßnahmen", das ausdrücklich NICHT in TREFF_BEREICHE steht: dort wäre es lautlos bei der Community gelandet und hätte ausgesehen wie die anderen sieben. WAS AUF DEN BRETTERN PASSIERT: "Will ich auch" (eine Stimme je Person, erzwungen durch den Schlüssel, nicht durch eine Prüfung) · Anheften, höchstens drei, gezählt IN der Transaktion · Freigabe vor Sichtbarkeit für Termine und Highlights, wobei Abwesenheit der Ruhezustand ist · Melden mit Pflicht-Grund (DSA Art. 16) · Entfernen mit Grund, der den Beitrag ÜBERLEBT (DSA Art. 17) · drei Stufen, gerechnet statt gespeichert · die Bannleiter: Modi bis 7 Tage, dauerhaft nur DogFather (seine Entscheidung) · "Mitmachen" wird zum Talent, genau einmal. Der Ausschluss wirkt in sitzungLesen() — also nicht nur beim Schreiben. Wer ausgeschlossen ist, ist weg, nicht stumm. ZWEI SEITEN MEHR. treff-regeln.html holt seine ZAHLEN vom Server (Mindestalter 18, Fristen, Anschläge) — ein Regeltext, der eine andere Zahl nennt als die Regel, ist schlimmer als keiner. rechte.html rechnet "Wer sieht was" aus derselben Tafel aus, aus der die Schranke ihre Entscheidung holt. Die Community steht dort bei 3 von 23. SECHS BEFUNDE, KEINEN HAT DAS LESEN GEFUNDEN: · Ein Modi kam über den verborgenen Zugang in den Treff — die Bedingung war durch Ausschluss formuliert und nahm die neue Wand automatisch mit · /api/personen gab einem Mitglied Namen und Rolle von DogFather · Ein DELETE mit Körper wird von Nodes HTTP-Parser mit einem leeren 400 abgewiesen, bevor Express ihn sieht (gemessen). Der Grund reist jetzt in der Adresse · Der Kommentar, der erklärt, warum die Zugangswand keine Rollennamen nennt, nannte selbst einen — in einer ausgelieferten Datei · Zwei Listen für "welche Seiten sind ohne Anmeldung offen". Sie stimmten, weil beide am selben Tag entstanden · Ein Portkonflikt, den ich selbst gebaut hatte (4391 gehört pruef-chat-aufloesen) Dazu zwei feste Zahlen abgeschafft (die 20 in pruef-rechtetafel, die Kachelzahl in pruef-treff) und zwei Werkzeuge, die jetzt SUCHEN statt aufzuzählen: Der Versionsstempel findet seine Manifeste selbst — das dritte hätte sonst keinen bekommen, und die neue App hätte bis zu vier Stunden lang das falsche Zeichen getragen. Das Bildwerkzeug baut beide Zugangswände aus einer Vorlage mit zwei Werten. Zwei neue Kacheltöne, gemessen statt gewählt: #2f2f7f (Abstand 31,4 in Lab) und #7c2771 (29,7). Das schwächste Paar der vorhandenen 34 liegt bei 24,9. Geprüft: treff 69 · treff-werkzeuge 60 · rollen 312 · start-ansicht 147 · crew-adresse 129 · sicht 84 · modi-verborgen 80 · haus-trennung 62 · bereiche-lesend 37 · css-klassen 30 · zwischenspeicher 21 · alle-wege 19 · rechtetafel 19 — alles grün. Co-Authored-By: Claude Opus 5 <[email protected]>
897 lines
40 KiB
JavaScript
897 lines
40 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. */
|
||
/* Die Leitungsliste steht in bereiche.js -- eine Antwort statt neun.
|
||
Sie stand hier frueher selbst, ohne 'spicy', und genau das war der
|
||
Fehler, den Filipe am 07.09.2026 gefunden hat. */
|
||
const LEITUNG = window.Bereiche.LEITUNG;
|
||
|
||
/* 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. */
|
||
/* WER HIER ETWAS EINTRAGEN DARF.
|
||
|
||
Bis zum 11.09.2026 stand hier nur "alle außer dem Creator". Das
|
||
stimmte, solange jedes Brett jedem Angemeldeten offenstand. Mit den
|
||
sieben Brettern des Treffs stimmt es nicht mehr: Auf dem
|
||
Anschlagbrett und bei "Was ansteht" schreibt nur das Team, und wer
|
||
neu ist, darf die erste Woche überhaupt nicht schreiben.
|
||
|
||
OHNE DIESE ZEILE HÄTTE EIN MITGLIED DEN KNOPF GESEHEN und beim
|
||
Drücken eine Absage bekommen -- genau die Sorte Knopf, die in
|
||
diesem Haus als Fehler gilt: Er verspricht etwas, das der Server
|
||
ablehnt, und der Mensch davor hält sich selbst für den Fehler.
|
||
|
||
DIE ANTWORT KOMMT VOM SERVER (/api/treff/lage), nicht aus einer
|
||
zweiten Regel hier. Sonst gäbe es die Schreibregel zweimal, und die
|
||
Fassung im Browser kann man ändern. Faellt die Auskunft aus, bleibt
|
||
die Lage leer und es gilt wieder die alte Regel -- der Knopf ist
|
||
dann da und der Server sagt notfalls nein. Das ist die richtige
|
||
Richtung: Ein Knopf zu viel ist ärgerlich, ein fehlender Riegel
|
||
wäre ein Loch. */
|
||
const darfEintragen = () => {
|
||
if (!ich || ich.rolle === 'creator') return false;
|
||
if (treffBrett && lage && lage.schreiben) return lage.schreiben[bereich] === true;
|
||
return true;
|
||
};
|
||
|
||
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;
|
||
/* Ist dies ein Brett des Treffs, und was darf ich dort? Beides sagt
|
||
der Server (daten.treff und /api/treff/lage) -- im Browser steht
|
||
keine Liste der Bretter und keine Rechteregel, sondern nur die
|
||
Antwort auf "was darf ICH hier gerade". Eine Regel, die im Browser
|
||
noch einmal steht, ist die, die auseinanderlaeuft; und die Fassung
|
||
im Browser kann man aendern. */
|
||
let treffBrett = false;
|
||
let lage = 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));
|
||
/* DIE VERTRAULICHE ZEILE SAGT ES SELBST (10.09.2026).
|
||
|
||
Sie kommt ohnehin nur bei DogFather und beim Schreiber an -- der
|
||
Server laesst sie sonst gar nicht heraus. Der Hinweis ist also
|
||
keine Sperre, sondern eine Auskunft: Ohne ihn saehe die eigene
|
||
Zeile aus wie jede andere, und beim naechsten Mal schriebe man
|
||
offen, was man vertraulich gemeint hat. */
|
||
/* AN DIE PERSON SCHICKEN (11.09.2026).
|
||
|
||
Nur im Entwicklungs-Bereich, und nur dort, wo ein Eintrag
|
||
überhaupt zu jemandem gehört. Er ist die einzige Brücke aus einer
|
||
Aufzeichnung, die die Person selbst nicht sieht -- und deshalb
|
||
ein ausdrücklicher Schritt und keine Automatik.
|
||
|
||
WURDE SCHON GESCHICKT? Dann steht dort ein Satz statt eines
|
||
Knopfs. Ein Knopf, der beim zweiten Drücken eine Absage bringt,
|
||
ist eine Falle; und zweimal dasselbe Lob ist kein doppeltes Lob,
|
||
sondern ein Versehen, das jeder bemerkt. */
|
||
if (bereich === 'entwicklung' && e.creator_id) {
|
||
if (e.gesendet_am) {
|
||
const hin = el('span', 'marke-art marke-art--weg',
|
||
`geschickt ${new Date(e.gesendet_am).toLocaleDateString('de-DE',
|
||
{ day: 'numeric', month: 'short' })}`);
|
||
hin.title = 'Diese Person hat den Eintrag als Nachricht bekommen.';
|
||
kopf.append(hin);
|
||
} else {
|
||
const b = el('button', 'eintrag-schicken', 'An die Person schicken');
|
||
b.type = 'button';
|
||
b.title = 'Schickt Art, Titel und Text als Nachricht in den Chat.';
|
||
b.addEventListener('click', async () => {
|
||
if (!confirm(`„${e.titel}“ als Nachricht schicken?\n\n`
|
||
+ 'Die Person bekommt Art, Titel und Text im Chat – einmalig.')) return;
|
||
b.disabled = true;
|
||
const a = await hole(`/workspace/api/bereich/entwicklung/${e.id}/senden`,
|
||
{ method: 'POST', headers: { 'Content-Type': 'application/json' } });
|
||
if (!a.ok) {
|
||
melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.');
|
||
b.disabled = false;
|
||
return;
|
||
}
|
||
await laden();
|
||
});
|
||
kopf.append(b);
|
||
}
|
||
}
|
||
|
||
if (e.nur_leitung) {
|
||
const v = el('span', 'marke-art marke-art--nur', 'nur DogFather');
|
||
v.title = 'Das liest ausser dir nur DogFather.';
|
||
kopf.append(v);
|
||
}
|
||
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'));
|
||
|
||
/* ---- Der Treff -------------------------------------------------
|
||
|
||
DER VOLLE ROLLENNAME AM BEITRAG (Filipe, 11.09.2026: "voller
|
||
Rollenname zeigen"). Er kommt fertig vom Server; hier steht
|
||
keine Zuordnung Rolle -> Name, die waere eine Liste aller Rollen
|
||
dieses Hauses auf jedem Rechner der Community.
|
||
|
||
WARUM DAS UEBERHAUPT DRANSTEHT: In einem Raum mit Zuschauern und
|
||
Moderation muss man sehen, wer spricht. "Frida" und "Frida ·
|
||
Modi" sind zwei verschiedene Saetze -- der zweite ist eine
|
||
Ansage, der erste eine Meinung.
|
||
|
||
BEI MITGLIEDERN STEHT NICHTS DA. "Kessi · Community" waere eine
|
||
Auszeichnung fuer das Normale; der Rollenname ist hier eine
|
||
Auskunft ueber Verantwortung, nicht ueber Zugehoerigkeit. */
|
||
if (treffBrett && e.erstellt_rolle_name && e.erstellt_rolle !== 'gast') {
|
||
fuss.append(el('span', 'treff-rolle', '· ' + e.erstellt_rolle_name));
|
||
}
|
||
|
||
if (treffBrett && lage) {
|
||
const zeile = el('div', 'treff-knoepfe');
|
||
|
||
/* WILL ICH AUCH. Die Zahl steht AM Knopf und nicht daneben: Sie
|
||
ist das Ergebnis des Knopfes, nicht eine Angabe ueber den
|
||
Eintrag. Und sie aendert sich sofort im Bild, bevor der Server
|
||
geantwortet hat -- bei einem Knopf, der eine Meinung abgibt,
|
||
ist eine halbe Sekunde Verzoegerung der Unterschied zwischen
|
||
"hat geklappt" und "hab ich das jetzt gedrueckt?". Antwortet
|
||
der Server anders, gilt seine Zahl. */
|
||
if (bereich === 'wunsch') {
|
||
const w = el('button', 'treff-will', '');
|
||
w.type = 'button';
|
||
let meine = !!e.meine_stimme;
|
||
let zahl = Number(e.stimmen) || 0;
|
||
const male = () => {
|
||
w.textContent = (meine ? '★ ' : '☆ ') + 'Will ich auch · ' + zahl;
|
||
w.setAttribute('aria-pressed', String(meine));
|
||
};
|
||
male();
|
||
w.addEventListener('click', async () => {
|
||
w.disabled = true;
|
||
meine = !meine; zahl += meine ? 1 : -1; male();
|
||
try {
|
||
/* EIN DELETE TRAEGT HIER KEINEN KOERPER (gemessen 11.09.2026).
|
||
|
||
Der naheliegende Aufbau waere gewesen, PUT und DELETE
|
||
gleich zu behandeln -- gleicher Kopf, gleicher leerer
|
||
Koerper. Das geht NICHT: Nodes HTTP-Parser weist ein
|
||
DELETE mit Koerper mit einem leeren 400 ab, noch bevor
|
||
Express die Anfrage sieht. Man sucht den Fehler dann in
|
||
der Route, und dort ist keiner.
|
||
|
||
Nachgemessen: PUT mit Koerper -> 200, DELETE ohne
|
||
Koerper -> 200, DELETE mit Koerper -> 400 mit genau
|
||
einem Kopffeld ("connection: close"). Das ist die
|
||
Handschrift des Parsers, nicht die einer Anwendung. */
|
||
const a = await hole('/workspace/api/treff/eintrag/' + e.id + '/willich',
|
||
meine
|
||
? { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: '{}' }
|
||
: { method: 'DELETE' });
|
||
if (a.ok) { const j = await a.json(); zahl = j.stimmen; meine = j.meine; male(); }
|
||
} catch { /* umgeleitet */ } finally { w.disabled = false; }
|
||
});
|
||
zeile.append(w);
|
||
}
|
||
|
||
/* MELDEN. Nur fuer die Community und nur an fremden Beitraegen --
|
||
den eigenen kann man loeschen, das ist der kuerzere Weg.
|
||
Der Grund ist Pflicht (DSA Art. 16); der Server prueft ihn
|
||
noch einmal, die Abfrage hier erspart nur den Fehlschlag. */
|
||
if (!lage.team && e.erstellt_von !== ich.id) {
|
||
zeile.append(tuKnopf('melden', async () => {
|
||
const grund = prompt('Was ist daran nicht in Ordnung?\n\n'
|
||
+ 'Bitte schreib es in einem Satz – sonst können wir es nicht prüfen.');
|
||
if (grund === null) return;
|
||
const a = await hole('/workspace/api/treff/eintrag/' + e.id + '/melden', {
|
||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ grund }),
|
||
});
|
||
melde(a.ok ? 'Danke – wir sehen uns das an.'
|
||
: ((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'));
|
||
}));
|
||
}
|
||
|
||
if (lage.team) {
|
||
/* ANHEFTEN. Hoechstens drei; die Zahl kommt vom Server
|
||
(lage.anschlag_max), damit sie nicht an zwei Stellen steht. */
|
||
if (bereich === 'anschlag') {
|
||
const dran = !!e.angeheftet;
|
||
zeile.append(tuKnopf(dran ? 'abnehmen' : 'anheften', async () => {
|
||
const a = await hole('/workspace/api/treff/eintrag/' + e.id + '/anheften',
|
||
dran
|
||
? { method: 'DELETE' }
|
||
: { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: '{}' });
|
||
if (!a.ok) {
|
||
melde((await a.json().catch(() => ({}))).fehler
|
||
|| ('Es hängen schon ' + lage.anschlag_max + ' Ansagen.'));
|
||
return;
|
||
}
|
||
await laden();
|
||
}));
|
||
}
|
||
|
||
/* FREIGEBEN. Bei Terminen heisst es "im Treff zeigen", bei
|
||
Highlights "freigeben" -- derselbe Vorgang, zwei Woerter,
|
||
weil es zwei verschiedene Entscheidungen sind. */
|
||
if (bereich === 'ansteht' || bereich === 'highlight') {
|
||
const frei = !!e.freigegeben;
|
||
const wort = bereich === 'ansteht'
|
||
? (frei ? 'nicht mehr zeigen' : 'im Treff zeigen')
|
||
: (frei ? 'zurückziehen' : 'freigeben');
|
||
if (!frei) {
|
||
const m = el('span', 'marke-art marke-art--nur', 'noch nicht sichtbar');
|
||
m.title = 'Die Community sieht das erst nach der Freigabe.';
|
||
zeile.append(m);
|
||
}
|
||
zeile.append(tuKnopf(wort, async () => {
|
||
const a = await hole('/workspace/api/treff/eintrag/' + e.id + '/freigeben',
|
||
frei
|
||
? { method: 'DELETE' }
|
||
: { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: '{}' });
|
||
if (!a.ok) { melde('Ging nicht.'); return; }
|
||
await laden();
|
||
}));
|
||
}
|
||
|
||
/* AUS "MITMACHEN" WIRD EIN TALENT. Ein ausdruecklicher Schritt
|
||
und keine Automatik: Wer sich meldet, ist ein Kandidat --
|
||
dass er einer wird, entscheidet ein Mensch. */
|
||
if (bereich === 'mitmachen' && e.erstellt_rolle === 'gast') {
|
||
zeile.append(tuKnopf('zu den Talenten', async () => {
|
||
if (!confirm('„' + e.titel + '“ zu den Talenten übernehmen?')) return;
|
||
const a = await hole('/workspace/api/treff/mitmachen/' + e.id + '/uebernehmen',
|
||
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
|
||
const j = await a.json().catch(() => ({}));
|
||
melde(a.ok ? 'Steht jetzt bei den Talenten.'
|
||
: (j.fehler === 'schon_uebernommen' ? 'Steht dort schon.'
|
||
: (j.fehler || 'Ging nicht.')));
|
||
}));
|
||
}
|
||
|
||
/* ENTFERNEN MIT GRUND (DSA Art. 17). Eigener Knopf neben dem
|
||
gewohnten "loeschen" -- denn "loeschen" gibt es weiterhin
|
||
fuer den eigenen Beitrag, und der braucht keine Begruendung. */
|
||
if (e.erstellt_von !== ich.id) {
|
||
zeile.append(tuKnopf('entfernen', async () => {
|
||
const grund = prompt('Warum wird „' + e.titel + '“ entfernt?\n\n'
|
||
+ 'Der Grund wird festgehalten – die Person hat ein Recht darauf.');
|
||
if (grund === null) return;
|
||
/* DER GRUND REIST IN DER ADRESSE, nicht im Koerper -- aus
|
||
demselben gemessenen Grund wie oben. Er ist kein
|
||
Geheimnis: Er wird der Person, die den Beitrag
|
||
geschrieben hat, ohnehin mitgeteilt, und er steht im
|
||
Protokoll. */
|
||
const a = await hole('/workspace/api/bereich/' + bereich + '/' + e.id
|
||
+ '?grund=' + encodeURIComponent(grund), { method: 'DELETE' });
|
||
if (!a.ok) {
|
||
melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.');
|
||
return;
|
||
}
|
||
await laden();
|
||
}));
|
||
}
|
||
}
|
||
|
||
if (zeile.childNodes.length) k.append(zeile);
|
||
}
|
||
|
||
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(); });
|
||
}
|
||
}
|
||
|
||
/* Einmal je Seitenaufruf. Sie wird an ZWEI Stellen gebraucht -- beim
|
||
Aufbau des Formulars und beim Zeichnen der Karten -- und beide
|
||
brauchen sie, BEVOR sie etwas anzeigen. Ein zweiter Abruf wäre eine
|
||
zweite Gelegenheit für zwei verschiedene Antworten auf dem selben
|
||
Bildschirm. */
|
||
async function lageHolen() {
|
||
if (!treffBrett || lage) return;
|
||
try {
|
||
const l = await fetch('/workspace/api/treff/lage', { credentials: 'same-origin' });
|
||
if (l.ok) lage = await l.json();
|
||
} catch { /* dann gilt die alte Regel, siehe darfEintragen */ }
|
||
}
|
||
|
||
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;
|
||
treffBrett = !!daten.treff;
|
||
await lageHolen();
|
||
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;
|
||
if (einstellung?.vertraulich) $('feld-vertraulich').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.vertraulich) daten.nur_leitung = $('f-vertraulich').checked ? 1 : 0;
|
||
/* Verborgen heisst auch: nichts mitschicken. Ein Wert, der in einem
|
||
unsichtbaren Feld stehenbleibt, wandert sonst beim naechsten
|
||
Speichern mit -- und niemand sieht, woher er kommt. */
|
||
if (einstellung.einsatzFeld && $('f-einsatz')) daten.einsatz = $('f-einsatz').value.trim();
|
||
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;
|
||
/* Das Wort ueber dem Titel. Es steht fest in bereich.html
|
||
("Betreuung") und stimmte fuer jede Akte -- nicht aber fuer eine
|
||
Sammelstelle, die niemandem gehoert. Bringt ein Bereich ein
|
||
eigenes mit, gilt seines; sonst bleibt es wie bisher. */
|
||
if (einstellung.ober && $('ober')) $('ober').textContent = einstellung.ober;
|
||
$('unterzeile').textContent = UNTERZEILE[bereich] || '';
|
||
|
||
/* VOR dem Aufbau des Formulars. Danach wäre zu spät: Der Knopf
|
||
stünde dann kurz da und verschwände wieder -- sichtbar, und
|
||
ausgerechnet an der Stelle, an der jemand gerade überlegt, ob er
|
||
schreiben darf. */
|
||
treffBrett = !!daten.treff;
|
||
await lageHolen();
|
||
|
||
/* DER WEG ZU DEN REGELN STEHT AUF JEDEM BRETT DES TREFFS
|
||
(11.09.2026).
|
||
|
||
Nicht nur auf dem Brett "Regeln & Hilfe". Wer die Regeln braucht,
|
||
braucht sie in dem Moment, in dem er etwas schreiben will -- und
|
||
das ist fast nie dort. Ein Mensch, der erst zurueck zur
|
||
Startseite und dann auf die richtige Kachel muss, liest sie
|
||
nicht; er schreibt einfach.
|
||
|
||
ALS LINK UND NICHT ALS EINGEBLENDETER TEXT: Die Regeln sind zu
|
||
lang, um sie ueber jedes Brett zu haengen, und ein Kasten, den
|
||
man jeden Tag wegklickt, wird nach drei Tagen nicht mehr
|
||
gesehen. */
|
||
if (daten.treff) {
|
||
const vorhanden = $('treff-regelweg');
|
||
if (!vorhanden) {
|
||
const a = el('a', 'treff-regelweg', 'Regeln & Hilfe');
|
||
a.id = 'treff-regelweg';
|
||
a.href = 'treff-regeln.html';
|
||
$('unterzeile').after(a);
|
||
}
|
||
}
|
||
|
||
/* 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? */
|
||
/* IM TREFF STIMMT KEINER DER BEIDEN SÄTZE. Dort geht es weder um
|
||
die Agentur noch um Betreuung, sondern entweder um ein Brett,
|
||
das dem Team gehört, oder um eine Stufe, die noch nicht
|
||
erreicht ist. Und der Server weiß beides genau -- er schickt
|
||
den Satz mit, statt dass der Browser ihn errät. */
|
||
$('unterzeile').textContent = (UNTERZEILE[bereich] || '')
|
||
+ (treffBrett
|
||
? ' ' + (lage?.warum?.[bereich]
|
||
|| 'Hier schreibt das Team – lesen kannst du alles.')
|
||
: 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-vertraulich').hidden = !einstellung.vertraulich;
|
||
/* =================================================================
|
||
ZWEI ZAHLEN, ANDERE NAMEN (10.09.2026)
|
||
|
||
Bei den Angeboten heisst `bewertung` NUTZEN und `dringlichkeit`
|
||
AUFWAND. Die Felder gibt es laengst -- sie mit neuen Namen zu
|
||
belegen ist besser, als zwei weitere Spalten anzulegen, die
|
||
dasselbe koennen. Aber ein Feld, ueber dem "Dringlichkeit" steht
|
||
und das den Aufwand meint, ist falsch beschriftet, und das faellt
|
||
beim Ausfuellen niemandem auf -- erst beim Lesen.
|
||
|
||
Die Namen kommen aus den Bereichsangaben; steht dort keiner,
|
||
bleibt es wie bisher. */
|
||
if (einstellung.dringlichkeitName && $('dringlich-schild')) {
|
||
$('dringlich-schild').textContent = einstellung.dringlichkeitName;
|
||
}
|
||
if (einstellung.bewertungName && $('bewertung-schild')) {
|
||
$('bewertung-schild').textContent = `${einstellung.bewertungName} (1–5)`;
|
||
}
|
||
/* Das Einsatz-Feld nur, wo der Bereich es kennt. */
|
||
if ($('feld-einsatz')) $('feld-einsatz').hidden = !einstellung.einsatzFeld;
|
||
if (einstellung.einsatzName && $('einsatz-schild')) {
|
||
$('einsatz-schild').textContent = einstellung.einsatzName;
|
||
}
|
||
$('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;
|
||
/* Bild ans <option> -- wahl.js zeichnet daraus das Gesicht. */
|
||
o.dataset.person = 'ja';
|
||
if (p.bild) o.dataset.bild = p.bild;
|
||
$('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();
|
||
})();
|
||
})();
|