Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.
Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.
WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE
Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus
„<a href="https://… ein kaputtes Attribut
/^["'„»\s]+|["'“«.\s]+$/ einen kaputten Ausdruck
"… nichts „mal " + "eben …" eine kaputte Zeichenkette
DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter.
Ein deutsches Schlusszeichen steht NIE
hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.
Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.
Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.
KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.
DIE WACHE DAZU
pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.
GEPRUEFT
pruef-struktur 59 -> 68 Pruefungen, 0 Fehler
node --check auf allen geaenderten JS-Dateien
nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben
gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
reaktion 421, support 63, content 45, terminregel 35, treff 85
Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
Ausgabe, keine einzige ein Vergleich mit dem Seitentext.
GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.
Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.
Co-Authored-By: Claude Opus 5 <[email protected]>
1614 lines
75 KiB
JavaScript
1614 lines
75 KiB
JavaScript
/* =======================================================================
|
||
AUFGABEN AUS DER VORLAGE -- fuer beide Seiten, die sie brauchen
|
||
=======================================================================
|
||
|
||
WARUM DAS EINE EIGENE DATEI IST (23.09.2026).
|
||
|
||
Filipe: "das soll auch bitte nicht in der kategorie aufgaben sondern
|
||
eure aufgaben sein bitte. setzt das perfekt da rein und pass es
|
||
uebeertrieben krass rein."
|
||
|
||
Hier stecken ZWEI Kataloge, und sie gehen an zwei verschiedene
|
||
Personenkreise -- das entscheidet der Server (/workspace/api/vorlagen):
|
||
|
||
* der TEAM-Katalog: 101 fertige Aufgaben in 14 Kategorien.
|
||
Er zieht auf "Eure Aufgaben" (entwicklung.html) um, denn dort
|
||
wird verteilt: dort steht die Personenwahl, dort das Formular,
|
||
dort die Liste dessen, was bei ihr liegt.
|
||
|
||
* der CREATOR-Katalog: vier Bereiche, vier Stufen.
|
||
Er bleibt auf "Aufgaben" (aufgaben.html), wo ein Betreuer einen
|
||
Creator auswaehlt.
|
||
|
||
Der erste Anlauf schnitt schlicht den ganzen Block heraus und legte
|
||
ihn drueben wieder hin. Das haette dem zweiten Katalog die Heimat
|
||
genommen -- und gemerkt hat es keine Ueberlegung, sondern die Frage,
|
||
wer die Zeile "ich.rolle === 'creator'" eigentlich bedient.
|
||
pruef-aufgaben-vorlagen misst sie, Zeile 285.
|
||
|
||
Die Alternative waere gewesen, die gemeinsamen Teile -- Abruf,
|
||
Klappkopf, Uebernehmen -- zweimal hinzuschreiben. Zwei Abschriften
|
||
derselben Sache laufen auseinander, sobald jemand nur eine davon
|
||
anfasst; dafuer gibt es in diesem Haus genug Beispiele. Also EINE
|
||
Datei, und jede Seite sagt beim Einrichten, welchen Zweig sie zeigt.
|
||
|
||
WAS DIE SEITE MITBRINGEN MUSS, steht in "einrichten" -- und fehlt
|
||
etwas davon, sagt die Datei das in der Konsole, statt stumm nichts
|
||
zu zeichnen. Ein Block, der ohne Grund leer bleibt, sieht aus wie
|
||
ein kaputter Server.
|
||
======================================================================= */
|
||
(() => {
|
||
'use strict';
|
||
const $ = (id) => document.getElementById(id);
|
||
const el = (tag, klasse, text) => {
|
||
const k = document.createElement(tag);
|
||
if (klasse) k.className = klasse;
|
||
if (text !== undefined) k.textContent = text;
|
||
return k;
|
||
};
|
||
|
||
/* WAS DIE SEITE MITBRINGT. Bis "einrichten" gelaufen ist, gibt es
|
||
hier nichts -- und "zeigen" sagt das dann auch. */
|
||
let U = null;
|
||
|
||
/* Der Fehlertext des Hauses (meldung.js). Ausdruecklich ueber
|
||
"window" und mit Rueckfall: Wuerde die Datei ohne meldung.js
|
||
geladen, waere "sagWas(...)" ein ReferenceError -- und der traete
|
||
ausgerechnet dann auf, wenn ohnehin schon etwas schiefgegangen
|
||
ist. Eine Fehlerbehandlung, die selbst abstuerzt, verschluckt den
|
||
eigentlichen Fehler. */
|
||
const sagWas = (fehler, ersatz) => (typeof window.sagWas === 'function'
|
||
? window.sagWas(fehler, ersatz)
|
||
: (ersatz || 'Das hat nicht geklappt.'));
|
||
|
||
/* ==== WELCHE KARTE SICH NACH DEM VERTEILEN ZEIGEN SOLL ==========
|
||
|
||
Filipe: „wenn ich drauf drücke dreht sich die kachel und dan seh
|
||
ich wer es gemacht hat, und immer noch die option es nochmal zu
|
||
verteilen falls neue leute ins team zustoßen."
|
||
|
||
WARUM EIN MERKZETTEL UND KEIN DIREKTES UMSCHALTEN: Nach dem
|
||
Verteilen wird das ganze Brett NEU gezeichnet (die Zahlen an den
|
||
Namen stimmen sonst nicht mehr). Eine Karte, die man vorher
|
||
umdreht, ist danach weg -- samt ihrer Drehung. Der Schluessel
|
||
ueberlebt das Neuzeichnen; die Karte findet sich selbst wieder.
|
||
|
||
EINER UND NICHT EINE LISTE: Man verteilt eine Karte und sieht sie
|
||
an. Zwei gleichzeitig gedrehte Karten waeren ein Zustand, den
|
||
niemand hergestellt hat. */
|
||
let zuletztVerteilt = null;
|
||
|
||
/* DIE PFLICHTANGABEN als Liste und nicht als Reihe von if-Abfragen:
|
||
Eine Liste laesst sich nicht halb vergessen. */
|
||
const NOETIG = ['zweig', 'hole', 'melde', 'ich', 'aufgaben', 'ziel',
|
||
'zielName', 'zielbare', 'zielWaehlen', 'statusName', 'nachAenderung'];
|
||
|
||
/* ---- Der Satz "an wen geht das hier" --------------------------------
|
||
|
||
OHNE RICHTUNGSANGABE. Im Entwurf stand "Waehle oben jemanden aus"
|
||
-- auf "Eure Aufgaben" steht die Personenwahl aber WEITER UNTEN.
|
||
Eine Wegbeschreibung, die nicht stimmt, ist schlimmer als keine:
|
||
Man sucht an der falschen Stelle und haelt die Seite fuer kaputt.
|
||
Genau dieser Fehler stand bis zum 22.09. am Verteil-Knopf
|
||
("Waehl links jemanden aus", und die Auswahl lag 206 px darunter).
|
||
|
||
Statt einer Richtung ein KNOPF, der hinfuehrt. Der weiss immer, wo
|
||
die Wahl steht, auch wenn die Seite spaeter umgebaut wird. */
|
||
function zielSatz(koerper, name) {
|
||
if (name) {
|
||
koerper.append(el('p', 'stufenleiter__hilfe',
|
||
'Was du hier übernimmst, geht an ' + name + '.'));
|
||
return;
|
||
}
|
||
const zeile = el('p', 'stufenleiter__hilfe',
|
||
'Noch niemand gewählt – tippe oben jemanden an, dann kannst du hier '
|
||
+ 'etwas für ihn übernehmen. ');
|
||
const wahl = $('personen');
|
||
if (wahl) {
|
||
const hin = el('button', 'knopf-still', 'Zur Personenwahl');
|
||
hin.type = 'button';
|
||
hin.addEventListener('click', () => {
|
||
wahl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
window.zielMarkieren?.([wahl], 'hier wählst du');
|
||
});
|
||
zeile.append(hin);
|
||
}
|
||
koerper.append(zeile);
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE KATALOGE
|
||
|
||
Vier Bereiche, vier Stufen, drei Aufgaben je Feld. Der Block ist
|
||
bewusst KEINE zweite Aufgabenverwaltung: Er legt eine ganz normale
|
||
Aufgabe an, mit Frist und Verantwortlichem, und verschwindet danach
|
||
aus dem Weg. Alles Weitere macht das Brett darunter.
|
||
|
||
WARUM DIE BEIDEN REIHEN DIESELBEN KNOEPFE SIND WIE IN DER
|
||
CHECKLISTE: Es sind dieselben vier Bereiche und dieselben vier
|
||
Stufen. Zwei verschiedene Darstellungen fuer dieselbe Einteilung
|
||
waeren zwei Sachen zum Lernen statt einer.
|
||
|
||
OHNE CREATOR KEINE AUFGABE. Ein Creator legt sie fuer sich an, ein
|
||
Betreuer fuer den, den er oben gewaehlt hat. Ist keiner gewaehlt,
|
||
steht das da -- statt eines Knopfes, der beim Druecken einen Fehler
|
||
zeigt.
|
||
===================================================================== */
|
||
const BEREICHE_VORLAGE = [
|
||
{ schluessel: 'live', name: 'LIVE-Analyse' },
|
||
{ schluessel: 'content', name: 'Content-Ideen' },
|
||
{ schluessel: 'technik', name: 'Technik' },
|
||
{ schluessel: 'community', name: 'Community' },
|
||
];
|
||
const STUFEN_VORLAGE = [
|
||
{ schluessel: 'anfaenger', name: 'Anfänger',
|
||
hilfe: 'Was von Anfang an sitzen muss. Meist an einem Abend zu schaffen.' },
|
||
{ schluessel: 'fort', name: 'Fortgeschritten',
|
||
hilfe: 'Aus Zufall wird Routine. Braucht Wiederholung, also Wochen.' },
|
||
{ schluessel: 'profi', name: 'Profi',
|
||
hilfe: 'Gemessen statt geschätzt: ein Wert, ein Versuch, ein Beleg.' },
|
||
{ schluessel: 'meister', name: 'Meister',
|
||
hilfe: 'Was auch ohne dich weiterläuft — Vorlage, Vertretung, Ablage.' },
|
||
];
|
||
let vorlagen = null;
|
||
/* Der zweite Katalog -- welcher es ist, sagt der Server. Hier steht
|
||
bewusst nur "katalog": Im Browser darf nichts stehen, woraus sich
|
||
schliessen laesst, fuer wen er gedacht ist. */
|
||
let katalog = null;
|
||
let kGruppe = null;
|
||
/* WELCHE STUFE. `null` heisst alle -- und das ist die Vorgabe:
|
||
Wer eine Kategorie aufmacht, will erst sehen, was es gibt. */
|
||
let kStufe = null;
|
||
/* Getrennt vom Inhalt, weil "nichts bekommen" ein gueltiges Ergebnis
|
||
ist. Ohne dieses Merkmal fragte die Seite bei jedem Neuzeichnen
|
||
erneut nach -- und bekaeme jedes Mal wieder nichts. */
|
||
let vorlagenGeladen = false;
|
||
/* ==== BEWERBUNGEN AUF EINE VORLAGE (23.09.2026) ====================
|
||
|
||
Filipe: „die modis sollen bei all diesen voschlägen auch nur
|
||
bewerben können ... und nur dogfather und die rechte hand sollen
|
||
annehmen oder ablehnen können, mit einem text als notiz."
|
||
|
||
WER WAS DARF, SAGT DER SERVER. Hier steht kein Rollenname -- diese
|
||
Datei bekommt jeder, der die Seite öffnet, und genau daran ist
|
||
dieses Brett am 17.09. schon einmal gescheitert. */
|
||
let bewerbungen = [];
|
||
let antworten = [];
|
||
let absagen = null;
|
||
let darfEntscheidenV = false;
|
||
let darfVerteilenV = false;
|
||
let vBereich = 'live';
|
||
let vStufe = 'anfaenger';
|
||
|
||
/* ---------- DAS VORLAGENBRETT IST ZUGEKLAPPT (09.09.2026) ------------
|
||
Filipe: "ich will nur dass du dich informierst und schaust was man
|
||
vielleicht noch besser machen koennte von der benutzung ... ich red
|
||
von den aufgaben. das system von den aufgaben, die bedienung."
|
||
|
||
Gemessen auf dem Handy: Der Vorlagenblock fuellte anderthalb
|
||
Bildschirme, BEVOR die erste eigene Aufgabe kam. Nach dem Ausbau
|
||
von Profi und Meister am selben Tag ist er noch laenger geworden.
|
||
|
||
Das ist die falsche Reihenfolge: Vorlagen holt man selten, das
|
||
Brett benutzt man jeden Tag. Der Block bleibt an seiner Stelle --
|
||
dort gehoert er hin, weil daraus Aufgaben auf das Brett darunter
|
||
wandern --, aber er ist jetzt zu. Wer ihn aufmacht, findet ihn
|
||
beim naechsten Mal wieder offen: Wer Vorlagen holt, holt meistens
|
||
mehrere. */
|
||
/* JE ZWEIG EIN EIGENES GEDAECHTNIS (23.09.2026).
|
||
|
||
Vorher EIN Schluessel fuer beide Kataloge -- das ging, solange
|
||
beide auf derselben Seite standen und einander ausschlossen. Seit
|
||
der Team-Katalog auf „Eure Aufgaben" steht, waeren es zwei Bloecke
|
||
auf zwei Seiten mit einem gemeinsamen Merker: Wer hier zuklappt,
|
||
faende drueben alles zu und wuesste nicht, warum.
|
||
|
||
Gelesen wird ERST, wenn die Seite gesagt hat, welcher Zweig sie
|
||
ist -- beim Laden der Datei steht das noch nicht fest. */
|
||
const VORLAGEN_OFFEN = () => 'dfw_vorlagenbrett_offen'
|
||
+ (U.zweig === 'team' ? '_team' : '');
|
||
let vOffen = null;
|
||
const klappStand = () => {
|
||
if (vOffen !== null) return vOffen;
|
||
try { vOffen = localStorage.getItem(VORLAGEN_OFFEN()) === 'ja'; }
|
||
catch { vOffen = false; }
|
||
return vOffen;
|
||
};
|
||
|
||
/** Den Katalog holen -- und mit ihm die Bewerbungen.
|
||
*
|
||
* ALS EIGENE FUNKTION, weil sie zweimal gebraucht wird: beim ersten
|
||
* Aufbau und nach JEDER Entscheidung. Ohne das zweite bliebe eine
|
||
* beantwortete Bewerbung stehen, bis jemand die Seite neu lädt --
|
||
* und zwei Leute würden über dieselbe Bitte zweimal entscheiden.
|
||
*
|
||
* DIE BEWERBUNGEN KOMMEN IN DERSELBEN ANTWORT wie der Katalog. Eine
|
||
* zweite Abfrage daneben wäre ein zweiter Weg, auf dem die beiden
|
||
* auseinanderlaufen können. */
|
||
async function vorlagenHolen() {
|
||
try {
|
||
const a = await U.hole('/workspace/api/vorlagen');
|
||
if (!a.ok) return false;
|
||
const d = await a.json();
|
||
vorlagen = d.aufgaben || null;
|
||
/* Ein Katalog mit null Eintraegen ist keiner. Ohne diese Pruefung
|
||
haette ein leeres `{gruppen:[],aufgaben:[]}` einen Block mit
|
||
Ueberschrift und nichts darin ergeben -- das sieht aus wie ein
|
||
Fehler, und es waere auch einer. */
|
||
katalog = d.katalog && Array.isArray(d.katalog.aufgaben) && d.katalog.aufgaben.length
|
||
? d.katalog : null;
|
||
bewerbungen = Array.isArray(d.bewerbungen) ? d.bewerbungen : [];
|
||
/* ==== WAS SCHON BEANTWORTET IST (30.09.2026) ==============
|
||
VanVan im Support: „dann bekommt der Modi keine
|
||
Benachrichtigung darüber, dass die Aufgabe abgelehnt wurde
|
||
und sieht somit auch eine eventuelle Begründung nicht."
|
||
|
||
ZWEI EIGENE FELDER, nicht `bewerbungen` erweitert: Dort
|
||
heisst eine Zeile „wartet auf Antwort", und genau das
|
||
stuende dann bei einer Absage. */
|
||
antworten = Array.isArray(d.antworten) ? d.antworten : [];
|
||
absagen = d.absagen && Array.isArray(d.absagen.liste) ? d.absagen : null;
|
||
darfEntscheidenV = !!d.darf_entscheiden;
|
||
darfVerteilenV = !!d.darf_verteilen;
|
||
vorlagenGeladen = true;
|
||
return true;
|
||
} catch { return false; }
|
||
}
|
||
|
||
async function vorlagenbrettZeigen() {
|
||
const ziel = $('vorlagenbrett');
|
||
if (!ziel) return;
|
||
/* WER BIN ICH? DIE ANTWORT KOMMT SPAETER ALS DIESE SEITE.
|
||
|
||
Hier stand ein stummes "|| !U.ich()) return". Auf "Aufgaben"
|
||
war das folgenlos: Dort holt die Seite ihre Auskunft selbst und
|
||
zeichnet den Katalog erst danach. Auf "Eure Aufgaben" kommt sie
|
||
ueber kopf.js aus dem Netz -- beim ersten Zeichnen steht sie
|
||
noch nicht da, und weil niemand ein zweites Mal zeichnet, blieb
|
||
der Katalog fuer einen Modi DAUERHAFT weg.
|
||
|
||
Gefunden hat das kein Codelesen, sondern ein Bildschirmfoto:
|
||
Die Seite sah vollstaendig aus, nur ohne den Block. Genau das
|
||
ist die Sorte Fehler, die ein stummes "return" erzeugt -- es
|
||
gibt nichts zu sehen, was falsch waere.
|
||
|
||
Gewartet wird mit der Wartestelle des Hauses (kopf.js), nicht
|
||
mit einer geratenen Zahl Millisekunden. Sie gibt nach laengstens
|
||
drei Sekunden auf; danach wird hier NICHT weiter versucht, also
|
||
kann daraus keine Schleife werden. */
|
||
if (!U.ich()) {
|
||
if (typeof window.wennIchDaBin === 'function') {
|
||
await window.wennIchDaBin();
|
||
if (U.ich()) return vorlagenbrettZeigen();
|
||
}
|
||
return;
|
||
}
|
||
/* WER DIESEN KATALOG BEKOMMT, ENTSCHEIDET DER SERVER.
|
||
|
||
Hier stand kurzzeitig ein Vergleich auf einen Rollennamen -- in
|
||
einer Datei, die JEDER bekommt, der die Seite oeffnet. Jetzt
|
||
liefert /workspace/api/vorlagen fuer die Betroffenen schlicht
|
||
nichts, und `if (!vorlagen) return` weiter unten laesst das Brett
|
||
verborgen. Dieselbe Wirkung, ohne eine Zeile, die verraet, fuer
|
||
wen sie gilt. */
|
||
if (!vorlagenGeladen) {
|
||
if (!(await vorlagenHolen())) return;
|
||
}
|
||
/* ZWEI KATALOGE, ZWEI SEITEN (23.09.2026).
|
||
|
||
Bis heute entschied allein der Server, welcher der beiden
|
||
Kataloge kommt -- sie schlossen einander aus, und beide standen
|
||
auf „Aufgaben".
|
||
|
||
Filipe: „das soll auch bitte nicht in der kategorie aufgaben
|
||
sondern eure aufgaben sein bitte."
|
||
|
||
Seither entscheidet ZUSAETZLICH die Seite, welchen Zweig sie
|
||
zeigt. „Aufgaben" zeigt, was liegt; verteilt wird auf „Eure
|
||
Aufgaben" -- dort steht die Personenwahl, dort das Formular,
|
||
dort ihre Aufgaben. Ein Katalog ist nichts anderes als ein
|
||
zweiter Weg zu derselben Handlung, also gehoert er dorthin.
|
||
|
||
Der Server bleibt die Schranke: Wer einen Katalog nicht
|
||
bekommen darf, bekommt ihn auch dann nicht, wenn die Seite ihn
|
||
anfordert. Die Seite waehlt nur aus, was SIE zeigen will. */
|
||
if (U.zweig === 'team') {
|
||
if (!katalog) { ziel.hidden = true; return; }
|
||
katalogBrett(ziel); return;
|
||
}
|
||
if (!vorlagen) { ziel.hidden = true; return; }
|
||
ziel.hidden = false;
|
||
ziel.textContent = '';
|
||
|
||
/* DER KOPF IST DER KNOPF. Ein eigener kleiner Schalter daneben waere
|
||
ein zweites Ziel fuer dieselbe Absicht -- und ein kleineres. */
|
||
const kopf = el('button', 'vorlagenbrett__kopf');
|
||
kopf.type = 'button';
|
||
kopf.setAttribute('aria-expanded', String(klappStand()));
|
||
kopf.setAttribute('aria-controls', 'vorlagenbrett-koerper');
|
||
const kText = el('span', 'vorlagenbrett__kopftext');
|
||
kText.append(el('span', 'feldschild', 'Aufgaben aus der Vorlage'));
|
||
/* Zugeklappt zaehlt nicht die Erklaerung, sondern WIE VIEL darin
|
||
liegt -- sonst sieht der Block aus wie eine Ueberschrift ohne
|
||
Inhalt und niemand macht ihn je auf. */
|
||
const gesamt = Object.values(vorlagen)
|
||
.reduce((n, b) => n + Object.values(b).reduce((m, l) => m + l.length, 0), 0);
|
||
kText.append(el('span', 'vorlagenbrett__unter', klappStand()
|
||
? 'Vier Bereiche, vier Stufen. Übernommen wird als ganz normale Aufgabe '
|
||
+ '– mit Frist, veränderbar, auf dem Brett darunter.'
|
||
: `${gesamt} fertige Aufgaben in vier Bereichen und vier Stufen – zum Übernehmen.`));
|
||
kopf.append(kText);
|
||
const pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||
pfeil.setAttribute('viewBox', '0 0 24 24');
|
||
pfeil.setAttribute('class', 'klapp-pfeil');
|
||
pfeil.setAttribute('aria-hidden', 'true');
|
||
const pfad = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||
pfad.setAttribute('d', 'm6 9.5 6 6 6-6');
|
||
pfeil.append(pfad);
|
||
kopf.append(pfeil);
|
||
kopf.addEventListener('click', () => {
|
||
vOffen = !klappStand();
|
||
try { localStorage.setItem(VORLAGEN_OFFEN(), vOffen ? 'ja' : 'nein'); } catch { /* egal */ }
|
||
vorlagenbrettZeigen();
|
||
});
|
||
ziel.append(kopf);
|
||
ziel.dataset.auf = klappStand() ? 'ja' : 'nein';
|
||
|
||
/* Zugeklappt wird der Rest gar nicht erst gebaut. Ihn nur zu
|
||
verstecken hiesse, bei jedem Neuzeichnen achtzig Karten
|
||
anzulegen, die niemand sieht. */
|
||
if (!klappStand()) return;
|
||
|
||
const koerper = el('div', 'vorlagenbrett__koerper');
|
||
koerper.id = 'vorlagenbrett-koerper';
|
||
ziel.append(koerper);
|
||
|
||
/* Reihe 1: der Bereich. */
|
||
const r1 = el('div', 'stufenleiter');
|
||
r1.setAttribute('role', 'group');
|
||
r1.setAttribute('aria-label', 'Bereich wählen');
|
||
for (const b of BEREICHE_VORLAGE) {
|
||
const k = el('button', 'stufenleiter__knopf');
|
||
k.type = 'button';
|
||
k.dataset.stufe = b.schluessel;
|
||
k.setAttribute('aria-pressed', String(vBereich === b.schluessel));
|
||
k.append(el('span', 'stufenleiter__name', b.name));
|
||
k.addEventListener('click', () => { vBereich = b.schluessel; vorlagenbrettZeigen(); });
|
||
r1.append(k);
|
||
}
|
||
koerper.append(r1);
|
||
|
||
/* Reihe 2: die Stufe, mit der Zahl der Aufgaben darin. */
|
||
const r2 = el('div', 'stufenleiter');
|
||
r2.setAttribute('role', 'group');
|
||
r2.setAttribute('aria-label', 'Könnensstufe wählen');
|
||
for (const st of STUFEN_VORLAGE) {
|
||
const n = ((vorlagen[vBereich] || {})[st.schluessel] || []).length;
|
||
if (!n) continue;
|
||
const k = el('button', 'stufenleiter__knopf');
|
||
k.type = 'button';
|
||
k.dataset.stufe = st.schluessel;
|
||
k.setAttribute('aria-pressed', String(vStufe === st.schluessel));
|
||
k.title = st.hilfe;
|
||
k.append(el('span', 'stufenleiter__name', st.name));
|
||
k.append(el('span', 'stufenleiter__zahl', String(n)));
|
||
k.addEventListener('click', () => { vStufe = st.schluessel; vorlagenbrettZeigen(); });
|
||
r2.append(k);
|
||
}
|
||
koerper.append(r2);
|
||
const hilfe = STUFEN_VORLAGE.find((x) => x.schluessel === vStufe)?.hilfe;
|
||
if (hilfe) koerper.append(el('p', 'stufenleiter__hilfe', hilfe));
|
||
|
||
/* Für wen? */
|
||
const fuer = U.ziel();
|
||
if (!fuer) {
|
||
koerper.append(el('p', 'vorlagenbrett__hinweis',
|
||
'Wähle oben einen Creator, dann lassen sich diese Aufgaben übernehmen.'));
|
||
}
|
||
|
||
/* WAS SCHON GEHOLT WURDE, STEHT DA (09.09.2026).
|
||
|
||
Vorher konnte man dieselbe Vorlage zweimal uebernehmen und
|
||
bekam die Aufgabe zweimal auf das Brett -- ohne jeden Hinweis.
|
||
Bei achtzig Vorlagen ueber vier Stufen weiss niemand auswendig,
|
||
was er letzten Monat schon geholt hat.
|
||
|
||
Verglichen wird ueber die KENNUNG der Vorlage, nicht ueber den
|
||
Titel: Ein Titel darf geaendert werden, und dann waere der
|
||
Vergleich still falsch. Die Kennung liegt seit heute an der
|
||
Aufgabe (Spalte `vorlage`).
|
||
|
||
Es braucht dafuer keine zusaetzliche Abfrage -- die Aufgaben
|
||
liegen ohnehin schon im Browser. */
|
||
const schonDa = new Map();
|
||
for (const a of U.aufgaben()) {
|
||
if (!a.vorlage) continue;
|
||
if (fuer && a.creator_id !== fuer) continue;
|
||
schonDa.set(a.vorlage, a);
|
||
}
|
||
|
||
const liste = (vorlagen[vBereich] || {})[vStufe] || [];
|
||
const box = el('div', 'vorlagenbrett__liste');
|
||
for (const [nr, v] of liste.entries()) {
|
||
const karte = el('div', 'vorlagenbrett__karte');
|
||
const schon = schonDa.get(v.schluessel);
|
||
if (schon) karte.dataset.schon = 'ja';
|
||
karte.append(el('h3', 'vorlagenbrett__titel', v.titel));
|
||
karte.append(el('p', 'vorlagenbrett__text', v.text));
|
||
const fuss = el('div', 'vorlagenbrett__fuss');
|
||
fuss.append(el('span', 'vorlagenbrett__frist',
|
||
v.tage === 1 ? 'Frist: morgen' : `Frist: in ${v.tage} Tagen`));
|
||
if (schon) {
|
||
/* Der Zustand steht dabei, nicht nur die Tatsache. "Schon
|
||
uebernommen" allein liesse offen, ob sie noch offen ist oder
|
||
laengst abgehakt -- und danach entscheidet man, ob man sie
|
||
ein zweites Mal holt. */
|
||
fuss.append(el('span', 'vorlagenbrett__schon',
|
||
`schon übernommen · ${U.statusName[schon.status] || schon.status}`));
|
||
}
|
||
if (fuer) {
|
||
const k = el('button', 'knopf knopf--klein', schon ? 'Nochmal' : 'Übernehmen');
|
||
k.type = 'button';
|
||
if (schon) k.title = 'Diese Vorlage liegt schon auf dem Brett – noch einmal übernehmen?';
|
||
k.addEventListener('click', () => uebernehmen(k, { art: 'aufgabe', nr }, fuer));
|
||
fuss.append(k);
|
||
}
|
||
karte.append(fuss);
|
||
box.append(karte);
|
||
}
|
||
/* Und wie viele der Stufe noch offen sind -- die Zahl, nach der man
|
||
eigentlich sucht, wenn man den Block aufmacht. */
|
||
if (liste.length) {
|
||
const offen = liste.filter((v) => !schonDa.has(v.schluessel)).length;
|
||
koerper.append(el('p', 'vorlagenbrett__stand', offen
|
||
? `${offen} von ${liste.length} noch nicht übernommen.`
|
||
: `Alle ${liste.length} dieser Stufe sind übernommen.`));
|
||
}
|
||
koerper.append(box);
|
||
|
||
if (fuer && liste.length > 1) {
|
||
const alle = el('button', 'abmelden', `Alle ${liste.length} übernehmen`);
|
||
alle.title = 'Legt alle Aufgaben dieser Stufe an – auch die, die schon auf dem Brett liegen.';
|
||
alle.type = 'button';
|
||
alle.style.marginTop = '12px';
|
||
alle.addEventListener('click', () => uebernehmen(alle, { art: 'aufgaben_alle' }, fuer));
|
||
koerper.append(alle);
|
||
}
|
||
}
|
||
|
||
|
||
/* =====================================================================
|
||
DAS ZWEITE VORLAGENBRETT (10.09.2026)
|
||
|
||
Derselbe Block, derselbe Kopf, dieselben Karten -- nur eine andere
|
||
Einteilung: neun Etappen statt vier Bereichen mal vier Stufen. Die
|
||
Bausteine sind bewusst dieselben (vorlagenbrett__karte,
|
||
stufenleiter, ...): Ein zweites Aussehen fuer dieselbe Sache waere
|
||
zwei Dinge zum Lernen statt einem -- und eine zweite Stelle, an der
|
||
jemand eine neue CSS-Klasse in sieben Kopien pflegen muesste.
|
||
|
||
WAS DRINSTEHT, KOMMT VOM SERVER, samt Gruppennamen und Texten. In
|
||
dieser Datei steht kein Wort darueber, fuer wen dieser Katalog ist.
|
||
===================================================================== */
|
||
function katalogBrett(ziel) {
|
||
ziel.hidden = false;
|
||
ziel.textContent = '';
|
||
if (!kGruppe) kGruppe = (katalog.gruppen[0] || {}).schluessel || null;
|
||
|
||
const kopf = el('button', 'vorlagenbrett__kopf');
|
||
kopf.type = 'button';
|
||
kopf.setAttribute('aria-expanded', String(klappStand()));
|
||
kopf.setAttribute('aria-controls', 'vorlagenbrett-koerper');
|
||
const kText = el('span', 'vorlagenbrett__kopftext');
|
||
kText.append(el('span', 'feldschild', 'Aufgaben aus der Vorlage'));
|
||
/* DER SATZ MUSS SAGEN, WONACH SORTIERT IST. Bis zum 16.09.2026 stand
|
||
hier "Nach Etappen sortiert" -- und seit dem Umbau auf Kategorie
|
||
mal Stufe war das schlicht falsch. Ein Satz, der etwas anderes
|
||
beschreibt als das, was darunter steht, kostet mehr Zeit als er
|
||
spart. */
|
||
/* "AUF DEM BRETT DARUNTER" STIMMTE BIS ZUM 23.09.2026.
|
||
|
||
Solange der Katalog auf "Aufgaben" stand, lag darunter wirklich
|
||
das Brett. Auf "Eure Aufgaben" liegt darunter die Aufgabenliste
|
||
der gewaehlten Person -- und wer dem Satz folgt, sucht ein
|
||
Brett, das es auf dieser Seite nicht gibt. Dieselbe Sorte
|
||
Fehler wie "Waehl links jemanden aus", als die Auswahl
|
||
darunter stand.
|
||
|
||
Der Satz sagt jetzt, WAS entsteht, nicht WO es landet. Das
|
||
gilt auf beiden Seiten und ueberlebt den naechsten Umbau. */
|
||
kText.append(el('span', 'vorlagenbrett__unter', klappStand()
|
||
? 'Nach Kategorie und Stufe sortiert. Daraus wird eine ganz normale '
|
||
+ 'Aufgabe – mit Frist und Kategorie, jederzeit veränderbar.'
|
||
: `${katalog.aufgaben.length} fertige Aufgaben in `
|
||
+ `${katalog.gruppen.length} Kategorien – zum Übernehmen.`));
|
||
kopf.append(kText);
|
||
const pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||
pfeil.setAttribute('viewBox', '0 0 24 24');
|
||
pfeil.setAttribute('class', 'klapp-pfeil');
|
||
pfeil.setAttribute('aria-hidden', 'true');
|
||
const pfad = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||
pfad.setAttribute('d', 'm6 9.5 6 6 6-6');
|
||
pfeil.append(pfad);
|
||
kopf.append(pfeil);
|
||
kopf.addEventListener('click', () => {
|
||
vOffen = !klappStand();
|
||
try { localStorage.setItem(VORLAGEN_OFFEN(), vOffen ? 'ja' : 'nein'); } catch { /* egal */ }
|
||
vorlagenbrettZeigen();
|
||
});
|
||
ziel.append(kopf);
|
||
ziel.dataset.auf = klappStand() ? 'ja' : 'nein';
|
||
|
||
/* ==== DIE ANTWORT AUF DIE EIGENE BEWERBUNG (30.09.2026) =======
|
||
|
||
VanVan im Support: „dann bekommt der Modi keine Benachrichtigung
|
||
darüber, dass die Aufgabe abgelehnt wurde und sieht somit auch
|
||
eine eventuelle Begründung nicht."
|
||
|
||
Die Benachrichtigung ging hinaus (`bewerbung_antwort`, seit dem
|
||
23.09.). Nur war sie das EINZIGE: Sobald jemand geantwortet
|
||
hatte, verschwand die Bewerbung aus jeder Ansicht -- mitsamt
|
||
der Begründung, die man ausdrücklich verlangt hatte. Wer die
|
||
Meldung wegwischte oder kein Gerät angemeldet hat, erfuhr nie,
|
||
warum.
|
||
|
||
BEI EINER ZUSAGE ENTSTEHT EINE AUFGABE, und an ihrer Karte
|
||
steht die Antwort. Bei einer ABSAGE entsteht nichts — deshalb
|
||
steht sie hier. Beides wird gezeigt: „angenommen" ohne die
|
||
Absage daneben wäre eine Liste, die nur schlechte Nachrichten
|
||
sammelt. */
|
||
if (antworten.length) {
|
||
const band = el('div', 'vorlagenbrett__antwort');
|
||
band.append(el('p', 'vorlagenbrett__antwort-schild',
|
||
antworten.length === 1
|
||
? 'Antwort auf deine Bewerbung:'
|
||
: `${antworten.length} Antworten auf deine Bewerbungen:`));
|
||
for (const a2 of antworten) {
|
||
const z = el('div', 'vorlagenbrett__antwort-zeile');
|
||
z.dataset.zustand = a2.zustand;
|
||
z.append(el('span', 'vorlagenbrett__antwort-wort',
|
||
a2.zustand === 'angenommen' ? 'Angenommen' : 'Diesmal nicht'));
|
||
z.append(el('span', 'vorlagenbrett__antwort-was', a2.titel));
|
||
/* DER SATZ IST DER GANZE ZWECK. Ohne ihn ist „diesmal nicht"
|
||
eine Absage ohne Grund — und die liest sich wie eine über
|
||
den Menschen, nicht über die Aufgabe. */
|
||
if (a2.entscheid_text) {
|
||
z.append(el('span', 'vorlagenbrett__antwort-grund',
|
||
'„' + a2.entscheid_text + '“'
|
||
+ (a2.von_name ? ' – ' + a2.von_name : '')));
|
||
}
|
||
band.append(z);
|
||
}
|
||
ziel.append(band);
|
||
}
|
||
|
||
|
||
/* Zugeklappt wird der Rest gar nicht erst gebaut -- sonst lägen bei
|
||
jedem Neuzeichnen sechzig Karten da, die niemand sieht.
|
||
|
||
DIE ANTWORT DARUEBER IST DAVON AUSGENOMMEN (30.09.2026): Sie
|
||
ist eine Nachricht, kein Katalogeintrag. Eine Antwort, die man
|
||
erst aufklappen muss, ist wieder keine -- und genau das hat
|
||
VanVan gemeldet. Es sind ein bis zwei Zeilen, und nach zwei
|
||
Wochen verschwinden sie von selbst. */
|
||
if (!klappStand()) return;
|
||
|
||
const koerper = el('div', 'vorlagenbrett__koerper');
|
||
koerper.id = 'vorlagenbrett-koerper';
|
||
ziel.append(koerper);
|
||
|
||
/* =====================================================================
|
||
WAS WARTET -- QUER ÜBER ALLE KATEGORIEN (23.09.2026)
|
||
=====================================================================
|
||
|
||
DAS HIER SCHLIESST EIN LOCH, das ich selbst gebaut habe. Die
|
||
Bewerbungen stehen an ihrer Karte, und das ist richtig: Man
|
||
entscheidet über eine bestimmte Aufgabe, und ihr Text ist die
|
||
halbe Auskunft.
|
||
|
||
Nur steht die Karte in EINER von vierzehn Kategorien. Bewirbt
|
||
sich Frida unter „Wachstum" und DogFather öffnet wie immer
|
||
„Chat-Moderation", sieht er nichts — die Bewerbung ist da, das
|
||
Brett ist offen, und trotzdem findet er sie nie.
|
||
|
||
Die Benachrichtigung hilft, aber sie ist ein Moment: Wer sie
|
||
wegwischt, hat keinen zweiten Weg mehr. Ein Brett, auf dem
|
||
etwas wartet, muss das selbst sagen können.
|
||
|
||
ES DIENT BEIDEN SEITEN, und deshalb steht es nur einmal hier:
|
||
Wer entscheidet, liest „wartet auf deine Antwort"; wer sich
|
||
beworben hat, liest „du hast dich beworben". Der Server schickt
|
||
ohnehin jedem nur, was ihn angeht (vorlagenBewerbungenFuer).
|
||
|
||
DER SPRUNG STELLT AUCH DIE STUFE ZURÜCK. Ohne das landet man in
|
||
der richtigen Kategorie und sieht trotzdem nichts, weil der
|
||
Stufenfilter die Karte gerade ausblendet — eine Reise ins
|
||
Nichts ist schlimmer als kein Knopf. */
|
||
if (bewerbungen.length) {
|
||
const band = el('div', 'vorlagenbrett__warten');
|
||
const fuerMich = darfEntscheidenV;
|
||
band.append(el('p', 'vorlagenbrett__warten-schild',
|
||
fuerMich
|
||
? (bewerbungen.length === 1
|
||
? 'Eine Bewerbung wartet auf deine Antwort:'
|
||
: `${bewerbungen.length} Bewerbungen warten auf deine Antwort:`)
|
||
: (bewerbungen.length === 1
|
||
? 'Du hast dich beworben — hier steht sie:'
|
||
: `Du hast dich ${bewerbungen.length}-mal beworben:`)));
|
||
const reihe = el('div', 'vorlagenbrett__warten-reihe');
|
||
for (const b2 of bewerbungen) {
|
||
const v2 = katalog.aufgaben.find((x) => x.schluessel === b2.vorlage);
|
||
const k2 = el('button', 'vorlagenbrett__warten-knopf');
|
||
k2.type = 'button';
|
||
/* WER UND WAS -- in dieser Reihenfolge. Bei drei Bitten
|
||
untereinander will man zuerst wissen, von wem. Steht man
|
||
selbst davor, ist der Name überflüssig: Man weiß, dass man
|
||
es war. */
|
||
if (fuerMich) k2.append(el('span', 'vorlagenbrett__warten-wer', b2.person_name));
|
||
k2.append(el('span', 'vorlagenbrett__warten-was', v2?.titel || b2.vorlage));
|
||
k2.addEventListener('click', () => {
|
||
if (v2) { kGruppe = v2.gruppe; kStufe = null; }
|
||
vorlagenbrettZeigen().then(() => {
|
||
const ziel = document.querySelector(
|
||
`.vorlagenbrett__karte[data-vorlage="${b2.vorlage}"]`);
|
||
if (!ziel) return;
|
||
ziel.scrollIntoView({ block: 'center', behavior: window.sanft?.() || 'auto' });
|
||
/* KURZ HERVORHEBEN. Wer auf einem Brett mit zwölf Karten
|
||
landet, sucht sonst die, wegen der er hergekommen ist. */
|
||
ziel.dataset.hervor = 'ja';
|
||
setTimeout(() => { delete ziel.dataset.hervor; }, 2400);
|
||
});
|
||
});
|
||
reihe.append(k2);
|
||
}
|
||
band.append(reihe);
|
||
koerper.append(band);
|
||
}
|
||
|
||
/* ==== WAS WIR SCHON ABGESAGT HABEN (30.09.2026) ===============
|
||
|
||
VanVan: „Außerdem haben auch wir nirgendwo eine Übersicht, bei
|
||
wem wir welche Aufgaben schon abgelehnt haben. Falls sich
|
||
jemand immer wieder auf Aufgaben bewirbt die immer wieder
|
||
abgelehnt werden, wäre das sinnvoll."
|
||
|
||
DIE ZAHL JE MENSCH STEHT VORN, die Liste darunter. Ihre Frage
|
||
ist „wiederholt sich das?", und die beantwortet eine Liste von
|
||
zwanzig Zeilen nicht.
|
||
|
||
ZUGEKLAPPT, solange niemand hinsieht: Es ist eine Auskunft zum
|
||
Nachschlagen, kein Aushang. Eine Liste von Absagen, die immer
|
||
offen steht, ist eine Sammlung von Nein-Sagen über Menschen,
|
||
mit denen man morgen wieder arbeitet. */
|
||
if (absagen && absagen.liste.length) {
|
||
const block = el('details', 'vorlagenbrett__absagen');
|
||
const kopf = el('summary', 'vorlagenbrett__absagen-kopf');
|
||
kopf.append(el('span', 'vorlagenbrett__absagen-schild', 'Schon abgesagt'));
|
||
kopf.append(el('span', 'vorlagenbrett__absagen-zahl',
|
||
`${absagen.liste.length} in ${absagen.seit_tagen} Tagen`));
|
||
block.append(kopf);
|
||
|
||
if (absagen.je_mensch.length) {
|
||
const reihe2 = el('div', 'vorlagenbrett__absagen-leute');
|
||
for (const m of absagen.je_mensch) {
|
||
const t = el('span', 'vorlagenbrett__absagen-mensch');
|
||
/* AB ZWEI WIRD ES HERVORGEHOBEN -- einmal „nein" ist kein
|
||
Muster, dreimal schon. Die Grenze steht am Element und
|
||
nicht in der Farbe allein: Wer Farben schlecht
|
||
unterscheidet, liest die Zahl. */
|
||
if (m.anzahl > 1) t.dataset.oft = 'ja';
|
||
t.append(el('span', 'vorlagenbrett__absagen-name', m.name || 'Jemand'));
|
||
t.append(el('span', 'vorlagenbrett__absagen-mal', `${m.anzahl}×`));
|
||
reihe2.append(t);
|
||
}
|
||
block.append(reihe2);
|
||
}
|
||
|
||
for (const a3 of absagen.liste) {
|
||
const z = el('div', 'vorlagenbrett__absagen-zeile');
|
||
z.append(el('span', 'vorlagenbrett__absagen-wer', a3.person_name || 'Jemand'));
|
||
z.append(el('span', 'vorlagenbrett__absagen-was', a3.titel));
|
||
if (a3.entscheid_text) {
|
||
z.append(el('span', 'vorlagenbrett__absagen-grund',
|
||
'„' + a3.entscheid_text + '“'
|
||
+ (a3.von_name ? ' – ' + a3.von_name : '')));
|
||
}
|
||
block.append(z);
|
||
}
|
||
koerper.append(block);
|
||
}
|
||
|
||
|
||
/* Die Etappen als Reihe -- mit der Zahl darin, damit man sieht, wo
|
||
etwas liegt, bevor man klickt. */
|
||
const reihe = el('div', 'stufenleiter');
|
||
reihe.setAttribute('role', 'group');
|
||
reihe.setAttribute('aria-label', 'Kategorie wählen');
|
||
for (const g of katalog.gruppen) {
|
||
const n = katalog.aufgaben.filter((v) => v.gruppe === g.schluessel).length;
|
||
if (!n) continue;
|
||
const k = el('button', 'stufenleiter__knopf');
|
||
k.type = 'button';
|
||
k.dataset.stufe = g.schluessel;
|
||
k.setAttribute('aria-pressed', String(kGruppe === g.schluessel));
|
||
k.append(el('span', 'stufenleiter__name', g.name));
|
||
k.append(el('span', 'stufenleiter__zahl', String(n)));
|
||
k.addEventListener('click', () => { kGruppe = g.schluessel; vorlagenbrettZeigen(); });
|
||
reihe.append(k);
|
||
}
|
||
koerper.append(reihe);
|
||
|
||
/* DERSELBE BAUSTEIN WIE BEIM ANDEREN BRETT (16.09.2026).
|
||
|
||
Filipe: "ich will dass es vom aussehen auch genau gleich
|
||
aussieht und so geil wie bei den anderen kategorien."
|
||
|
||
Hier stand `vorlagenbrett__stand` -- das ist die Zeile fuer
|
||
ZAHLEN ("3 von 11 noch nicht uebernommen"), kleiner und mit
|
||
Ziffern in Tabellenbreite. Die Erklaerung unter der Auswahl
|
||
heisst drueben `stufenleiter__hilfe` und sieht anders aus.
|
||
Zwei Bretter, die dasselbe tun und verschieden aussehen, wirken
|
||
wie zwei verschiedene Sachen. */
|
||
const gruppe = katalog.gruppen.find((g) => g.schluessel === kGruppe);
|
||
if (gruppe?.text) koerper.append(el('p', 'stufenleiter__hilfe', gruppe.text));
|
||
|
||
/* ---- Die Stufe (16.09.2026) ---------------------------------------
|
||
|
||
Filipe: "wie bei den aufgaben wo sie für mich haben auch mit
|
||
kategorien und vielen aufgaben."
|
||
|
||
Dieselbe Kategorie bedeutet für jemanden in der ersten Woche
|
||
etwas anderes als für jemanden, der einarbeitet. Eine Aufgabe
|
||
der Stufe "erfahren" an einen Neuen zu geben ist kein
|
||
Kompliment, sondern ein Überfallen.
|
||
|
||
DIE STUFEN KOMMEN VOM SERVER, nicht aus einer Liste hier: Eine
|
||
zweite Liste im Browser wäre die, die beim nächsten Zusatz
|
||
veraltet -- und ausgeliefert wird sie an jeden. */
|
||
const inGruppe = katalog.aufgaben.filter((v) => v.gruppe === kGruppe);
|
||
if (Array.isArray(katalog.stufen) && katalog.stufen.length) {
|
||
/* VOLLE GROESSE, nicht `--fein`: Drueben ist die zweite Reihe
|
||
(die Stufen) genauso gross wie die erste. */
|
||
const sr = el('div', 'stufenleiter');
|
||
sr.setAttribute('role', 'group');
|
||
sr.setAttribute('aria-label', 'Stufe wählen');
|
||
const alle = el('button', 'stufenleiter__knopf');
|
||
alle.type = 'button';
|
||
alle.setAttribute('aria-pressed', String(kStufe === null));
|
||
alle.append(el('span', 'stufenleiter__name', 'Alle'));
|
||
alle.append(el('span', 'stufenleiter__zahl', String(inGruppe.length)));
|
||
alle.addEventListener('click', () => { kStufe = null; vorlagenbrettZeigen(); });
|
||
sr.append(alle);
|
||
for (const st of katalog.stufen) {
|
||
const n = inGruppe.filter((v) => v.stufe === st.schluessel).length;
|
||
if (!n) continue;
|
||
const k = el('button', 'stufenleiter__knopf');
|
||
k.type = 'button';
|
||
k.dataset.stufe = st.schluessel;
|
||
k.setAttribute('aria-pressed', String(kStufe === st.schluessel));
|
||
k.title = st.text || '';
|
||
k.append(el('span', 'stufenleiter__name', st.name));
|
||
k.append(el('span', 'stufenleiter__zahl', String(n)));
|
||
k.addEventListener('click', () => { kStufe = st.schluessel; vorlagenbrettZeigen(); });
|
||
sr.append(k);
|
||
}
|
||
koerper.append(sr);
|
||
const gewaehlt = katalog.stufen.find((x) => x.schluessel === kStufe);
|
||
if (gewaehlt?.text) koerper.append(el('p', 'stufenleiter__hilfe', gewaehlt.text));
|
||
}
|
||
|
||
/* Was schon auf dem Brett liegt -- aus den Aufgaben, die ohnehin
|
||
geladen sind. Verglichen wird die Kennung der Vorlage und nicht
|
||
der Titel: Der darf geändert werden.
|
||
|
||
BEI WEM SIE LIEGT, ZAEHLT MIT (23.09.2026).
|
||
|
||
Hier stand keine Bedingung: Sobald IRGENDWER eine Vorlage hatte,
|
||
stand an der Karte „schon übernommen" -- auch fuer alle anderen.
|
||
Solange der Block auf einer Seite ohne Personenwahl stand, fiel
|
||
das kaum auf. Auf „Eure Aufgaben" waere es offen falsch: Man
|
||
waehlt Frida, und der Katalog behauptet, sie habe die Aufgabe
|
||
schon -- weil Rieke sie hat.
|
||
|
||
Der Creator-Zweig machte es von Anfang an richtig (er
|
||
vergleicht `creator_id`); nur dieser hier nicht. Ohne gewaehlte
|
||
Person bleibt es wie bisher: dann ist „irgendwer" die einzige
|
||
Auskunft, die es gibt. */
|
||
/* WESSEN AUFGABEN SEHE ICH UEBERHAUPT? Daran haengt, was
|
||
"schon uebernommen" bedeuten kann.
|
||
|
||
Wer NICHT verteilt, bekommt vom Server nur die eigenen -- bei
|
||
ihm heisst die Markierung zurecht "die habe ich schon", ohne
|
||
dass er jemanden waehlen muesste (er sieht die Personenwahl gar
|
||
nicht).
|
||
|
||
Wer verteilt, bekommt die Aufgaben ALLER. Ohne gewaehlte Person
|
||
hiesse die Markierung dann "irgendwer hat sie" -- und das ist
|
||
keine Antwort auf die Frage, die man an dieser Stelle stellt.
|
||
Man liest sie als "brauche ich nicht mehr zu vergeben" und
|
||
ueberspringt eine Aufgabe, die dem Menschen vor einem fehlt.
|
||
|
||
LIEBER KEINE AUSKUNFT ALS EINE, DIE MAN FALSCH LIEST. Sobald
|
||
jemand gewaehlt ist, steht sie wieder da -- und meint dann
|
||
genau ihn. */
|
||
const wem = U.ziel();
|
||
const anAlle = String(wem) === 'alle';
|
||
|
||
/* DIE LEUTE UND DIE ZEITRECHNUNG STEHEN HIER OBEN, weil sie ab
|
||
heute an ZWEI Stellen gebraucht werden: in der Reihe "An wen"
|
||
weiter unten und an jeder Karte. Zweimal dieselbe Zeile waere
|
||
der Anfang zweier Fassungen, die irgendwann verschieden sind --
|
||
und genau daran ist "alle" heute frueh gescheitert. */
|
||
const zielbar = U.zielbare() || [];
|
||
const heute = typeof window.heuteLokal === 'function'
|
||
? window.heuteLokal() : new Date().toISOString().slice(0, 10);
|
||
const nochOffen = (a) => a.status !== 'erledigt' && a.status !== 'abgebrochen';
|
||
const istSpaet = (a) => nochOffen(a) && a.frist && a.frist < heute;
|
||
|
||
/* ==== "ALLE" WAR HIER NICHT VORGESEHEN (behoben 24.09.2026) =====
|
||
|
||
Filipe hat es aufgenommen: Er waehlt "Alle", drueckt bei einer
|
||
Karte auf "An alle" -- und auf dem Bildschirm aendert sich
|
||
NICHTS. Die Karte behaelt ihren Knopf, und darueber steht
|
||
weiter "12 von 12 noch nicht uebernommen". Die Aufgaben waren
|
||
angelegt (die Zahlen an den Namen sind gestiegen), nur sah man
|
||
es an der Stelle nicht, an der man gerade arbeitet.
|
||
|
||
DIE URSACHE STAND IN DER ZEILE DARUNTER: verglichen wurde
|
||
`String(a.verantwortlich_id) !== String(wem)`. Bei "alle" ist
|
||
`wem` das WORT "alle" -- und keine Aufgabe hat je einen
|
||
Verantwortlichen namens "alle". Die Bedingung war also IMMER
|
||
wahr, jede Aufgabe wurde uebersprungen, und die Liste blieb
|
||
leer. Kein Absturz, keine Meldung: Es sah aus, als waere noch
|
||
nichts uebernommen.
|
||
|
||
Gebaut wurde "alle" am selben Vormittag; `zielWaehlen`,
|
||
`zielName` und `nachAenderung` wurden angepasst -- diese eine
|
||
Stelle nicht. Genau die Art Fehler, die eine Liste macht, die
|
||
man von Hand nachzieht.
|
||
|
||
WARUM NICHT NUR EINE ZAHL, SONDERN WER. Filipe: "wenn ich eine
|
||
aufgabe an alle verteile will ich dass dogfather und die rechte
|
||
hand individuel von jedem sehen wer es gemacht hat oder nicht."
|
||
Eine Zahl ("4 von 5") beantwortet "wie weit", aber nicht "wer
|
||
noch" -- und danach handelt man. Deshalb steht unten eine
|
||
Marke je Mensch und nicht nur ein Zaehler. */
|
||
const schonDa = new Map();
|
||
const schonWer = new Map(); // Vorlage -> Map(Personennummer -> Aufgabe)
|
||
/* ==== WER SIE HAT, WIRD IMMER AUSGERECHNET (25.09.2026) =========
|
||
|
||
Filipe: „wer hat sie soll bitte bei all diesen aufgaben stehen.
|
||
bei all diesen kategorien da. … es soll auch immer gleich
|
||
aussehen und nicht manchmal verschoben und so."
|
||
|
||
Hier stand `if (anAlle)`. Die Rueckseite der Karte -- und damit
|
||
der Knopf „Wer hat sie?" -- entstand deshalb nur, solange oben
|
||
„Alle" gewaehlt war. Wer auf einen Namen tippte, verlor den
|
||
Knopf an ALLEN zwoelf Karten, ohne dass irgendwo stand, warum.
|
||
|
||
DIE AUSKUNFT IST DIESELBE, egal wen man oben gewaehlt hat: „wer
|
||
aus dem Team hat diese Vorlage schon". Sie haengt an der
|
||
Vorlage, nicht an der Auswahl. Sie deshalb an die Auswahl zu
|
||
binden war der Fehler.
|
||
|
||
`schonDa` BLEIBT, WIE ES WAR: Das ist die andere Frage („liegt
|
||
sie bei DER Person, die ich gerade meine") und die gehoert
|
||
weiterhin zur Auswahl. Zwei Fragen, zwei Karten. */
|
||
{
|
||
const zielIds = new Set(zielbar.map((p) => String(p.id)));
|
||
for (const a of U.aufgaben()) {
|
||
if (!a.vorlage) continue;
|
||
const werHats = String(a.verantwortlich_id);
|
||
if (!zielIds.has(werHats)) continue;
|
||
if (!schonWer.has(a.vorlage)) schonWer.set(a.vorlage, new Map());
|
||
/* HAT JEMAND DIESELBE VORLAGE MEHRFACH, ZAEHLT DIE OFFENE.
|
||
Manches holt man jeden Monat neu; dann liegt daneben eine
|
||
erledigte von letztem Mal. Stuende die hier, sagte die
|
||
Karte "Erledigt", waehrend die Person die Aufgabe noch vor
|
||
sich hat -- und man haekte sie gedanklich ab. */
|
||
const bisher = schonWer.get(a.vorlage).get(werHats);
|
||
if (!bisher || (nochOffen(a) && !nochOffen(bisher))) {
|
||
schonWer.get(a.vorlage).set(werHats, a);
|
||
}
|
||
if (anAlle) schonDa.set(a.vorlage, a);
|
||
}
|
||
}
|
||
if (!anAlle && (wem || !darfVerteilenV)) {
|
||
for (const a of U.aufgaben()) {
|
||
if (!a.vorlage) continue;
|
||
if (wem && String(a.verantwortlich_id) !== String(wem)) continue;
|
||
schonDa.set(a.vorlage, a);
|
||
}
|
||
}
|
||
|
||
/* ---- AN WEN? Mit der Last daneben (16.09.2026) -------------------
|
||
|
||
Bis heute kam die Person aus dem Formular GANZ OBEN auf der
|
||
Seite: Man wählt sie dort, scrollt herunter und drückt hier
|
||
„Übernehmen". Wer das nicht weiß, bekommt „Bitte zuerst eine
|
||
Person wählen" und sucht.
|
||
|
||
UND DAS WICHTIGERE: Man sah beim Vergeben nicht, wer schon wie
|
||
viel hat. Zu wenig Zeit ist in den Untersuchungen zu
|
||
Moderatoren der meistgenannte Grund fürs Aufhören — und die
|
||
Person, die Aufgaben verteilt, ist die einzige, die das
|
||
verhindern kann. Dafür muss die Zahl dort stehen, wo entschieden
|
||
wird, nicht auf einer Auswertung, die man hinterher aufruft.
|
||
|
||
KEINE SCHWELLE, KEINE WARNFARBE. Was „zu viel" ist, hängt vom
|
||
Menschen ab; eine feste Grenze wäre geraten. Was NICHT geraten
|
||
ist: dass etwas überfällig liegt. Nur das wird markiert. */
|
||
/* ==== AN WEN GEHT DAS HIER? ===================================
|
||
|
||
DIESE REIHE WAR EINMAL WEG, UND DAS WAR EIN FEHLER (23.09.2026).
|
||
|
||
Beim Umzug des Katalogs habe ich sie geloescht, weil "Eure
|
||
Aufgaben" oben schon eine Personenwahl hat. Verlangt war das
|
||
nicht -- verlangt war, den Katalog zu VERSCHIEBEN.
|
||
|
||
Filipe: "da ist scheisse wenn du einfach sachen machst die ich
|
||
nicht verlange."
|
||
|
||
Sie steht wieder da, vollstaendig, mit den Zahlen daneben. Was
|
||
sich geaendert hat, ist nur, WAS SIE SETZT: Frueher hatte sie
|
||
eine eigene Auswahl, die nichts von der Seite wusste -- man
|
||
konnte oben den einen und hier den anderen waehlen, und dann
|
||
standen zwei Antworten auf einem Bildschirm. Jetzt waehlt ein
|
||
Tipp hier dieselbe Person wie ein Tipp auf eine Kachel oben.
|
||
Zwei Stellen zum Bedienen, EINE Antwort.
|
||
|
||
WARUM DIE ZAHLEN WICHTIG SIND (16.09.2026, unveraendert
|
||
gueltig): Zu wenig Zeit ist in den Untersuchungen zu Moderatoren
|
||
der meistgenannte Grund fuers Aufhoeren -- und wer Aufgaben
|
||
verteilt, ist der einzige, der das verhindern kann. Dafuer muss
|
||
die Zahl dort stehen, wo entschieden wird.
|
||
|
||
KEINE SCHWELLE, KEINE WARNFARBE. Was "zu viel" ist, haengt vom
|
||
Menschen ab; eine feste Grenze waere geraten. Was NICHT geraten
|
||
ist: dass etwas ueberfaellig liegt. Nur das wird markiert. */
|
||
const zielName = U.zielName();
|
||
/* `zielbar`, `heute`, `nochOffen` und `istSpaet` stehen weiter
|
||
oben -- es sind dieselben, die auch die Karten benutzen. */
|
||
if (darfVerteilenV && zielbar.length) {
|
||
koerper.append(el('p', 'feldschild', 'An wen'));
|
||
const wer = el('div', 'stufenleiter stufenleiter--wer');
|
||
wer.setAttribute('role', 'group');
|
||
wer.setAttribute('aria-label', 'Person wählen');
|
||
/* ==== „ALLE" NEBEN DEN NAMEN (24.09.2026) ===================
|
||
|
||
Filipe: „bei an ween fehlt noch die option alle neben den
|
||
namen allen."
|
||
|
||
ER STEHT VORN, nicht hinten: Wer etwas an das ganze Team
|
||
geben will, soll nicht erst an sechs Namen vorbeilesen. Und
|
||
er traegt die Summe aller offenen Aufgaben -- dieselbe Zahl,
|
||
die neben jedem Namen steht, nur zusammengezaehlt.
|
||
|
||
NUR AB ZWEI PERSONEN. Bei einer einzigen waere „alle"
|
||
dasselbe wie ihr Name, also ein zweiter Knopf fuer denselben
|
||
Handgriff. */
|
||
if (zielbar.length > 1) {
|
||
const alleOffen = (U.aufgaben() || []).filter(nochOffen).length;
|
||
const alleSpaet = (U.aufgaben() || []).filter(istSpaet).length;
|
||
const ka = el('button', 'stufenleiter__knopf');
|
||
ka.type = 'button';
|
||
ka.dataset.alle = 'ja';
|
||
if (alleSpaet) ka.dataset.spaet = 'ja';
|
||
ka.setAttribute('aria-pressed', String(String(wem) === 'alle'));
|
||
ka.title = `An alle ${zielbar.length} -- zusammen ${alleOffen} offen`
|
||
+ (alleSpaet ? `, davon ${alleSpaet} überfällig` : '');
|
||
ka.append(el('span', 'stufenleiter__name', 'Alle'));
|
||
ka.append(el('span', 'stufenleiter__zahl', String(zielbar.length)));
|
||
if (alleSpaet) ka.append(el('span', 'stufenleiter__spaet', `${alleSpaet} spät`));
|
||
ka.addEventListener('click', () => U.zielWaehlen('alle'));
|
||
wer.append(ka);
|
||
}
|
||
for (const pp of zielbar) {
|
||
const seine = (U.aufgaben() || [])
|
||
.filter((a) => String(a.verantwortlich_id) === String(pp.id));
|
||
const offenN = seine.filter(nochOffen).length;
|
||
const spaet = seine.filter(istSpaet).length;
|
||
const k = el('button', 'stufenleiter__knopf');
|
||
k.type = 'button';
|
||
if (spaet) k.dataset.spaet = 'ja';
|
||
k.setAttribute('aria-pressed', String(String(wem) === String(pp.id)));
|
||
k.title = spaet
|
||
? `${pp.name}: ${offenN} offen, davon ${spaet} überfällig`
|
||
: `${pp.name}: ${offenN} offen`;
|
||
k.append(el('span', 'stufenleiter__name', pp.name));
|
||
k.append(el('span', 'stufenleiter__zahl', String(offenN)));
|
||
if (spaet) k.append(el('span', 'stufenleiter__spaet', `${spaet} spät`));
|
||
k.addEventListener('click', () => U.zielWaehlen(pp.id));
|
||
wer.append(k);
|
||
}
|
||
koerper.append(wer);
|
||
}
|
||
if (darfVerteilenV) {
|
||
zielSatz(koerper, zielName);
|
||
}
|
||
const liste = katalog.aufgaben
|
||
.map((v, nr) => ({ ...v, nr }))
|
||
.filter((v) => v.gruppe === kGruppe)
|
||
.filter((v) => !kStufe || v.stufe === kStufe);
|
||
const box = el('div', 'vorlagenbrett__liste');
|
||
for (const v of liste) {
|
||
const karte = el('div', 'vorlagenbrett__karte');
|
||
/* DIE KENNUNG AN DER KARTE, damit das Band oben sie
|
||
wiederfinden kann. Über den Titel zu suchen wäre die
|
||
naheliegende Abkürzung — und sie fällt um, sobald zwei
|
||
Vorlagen ähnlich heißen. */
|
||
karte.dataset.vorlage = v.schluessel;
|
||
const schon = schonDa.get(v.schluessel);
|
||
/* WER SIE HAT -- je Mensch, nicht als Zahl. Bei EINER gewaehlten
|
||
Person bleibt es beim alten Weg: Dort ist `schon` bereits die
|
||
individuelle Auskunft. */
|
||
const werHat = schonWer.get(v.schluessel) || new Map();
|
||
const alleHabenSie = zielbar.length > 0 && werHat.size >= zielbar.length;
|
||
/* DIE KARTE TRITT ERST ZURUECK, WENN SIE WIRKLICH DURCH IST.
|
||
Bei "alle" heisst `schon` ab EINEM Menschen -- eine Vorlage,
|
||
die einer von fuenf hat, halb auszublenden waere das Gegenteil
|
||
der Auskunft: Sie ist die, die noch Arbeit macht. */
|
||
/* ==== ZWEI FESTE REIHEN STATT EINER, DIE UMBRICHT =============
|
||
(25.09.2026)
|
||
|
||
Filipe: „es soll auch immer gleich aussehen und nicht manchmal
|
||
verschoben und so."
|
||
|
||
WORAN ES LAG, und es war kein Zufall: Im Fuss standen Angaben
|
||
und Knoepfe in EINER Reihe mit `flex-wrap`. Wie viele Angaben
|
||
es gibt, haengt aber von der Karte ab -- „Frist", manchmal
|
||
„fuer: Rolle", manchmal „liegt bei 4 von 5". Karten ohne den
|
||
dritten Text hatten in der ersten Reihe noch Platz fuer einen
|
||
Knopf, Karten mit ihm nicht. Also stand „An alle" mal neben
|
||
der Frist und mal darunter, und „Wer hat sie?" rutschte
|
||
hinterher. Zwoelf Karten, drei verschiedene Fuesse.
|
||
|
||
JETZT ZWEI REIHEN MIT FESTER AUFGABE: oben, was man LIEST,
|
||
unten, was man DRUECKT. Die Knopfreihe sitzt am unteren Rand
|
||
der Karte (`margin-top: auto` im Stilblatt), damit sie bei
|
||
allen Karten einer Zeile auf derselben Hoehe steht -- auch
|
||
wenn der Text darueber verschieden lang ist. */
|
||
if (anAlle ? alleHabenSie : !!schon) karte.dataset.schon = 'ja';
|
||
karte.append(el('h3', 'vorlagenbrett__titel', v.titel));
|
||
karte.append(el('p', 'vorlagenbrett__text', v.text));
|
||
const fuss = el('div', 'vorlagenbrett__fuss');
|
||
const fussInfo = el('div', 'vorlagenbrett__fuss-info');
|
||
const fussTun = el('div', 'vorlagenbrett__fuss-tun');
|
||
fuss.append(fussInfo, fussTun);
|
||
fussInfo.append(el('span', 'vorlagenbrett__frist',
|
||
v.tage === 1 ? 'Frist: morgen' : `Frist: in ${v.tage} Tagen`));
|
||
/* Die empfohlene Rolle steht im Dokument neben jeder Aufgabe und
|
||
ist die halbe Auskunft: "Technik-Check" heisst etwas anderes,
|
||
je nachdem, wer ihn macht. */
|
||
if (v.rolle) fussInfo.append(el('span', 'vorlagenbrett__frist', `für: ${v.rolle}`));
|
||
if (schon && !anAlle) {
|
||
fussInfo.append(el('span', 'vorlagenbrett__schon',
|
||
`schon übernommen · ${U.statusName[schon.status] || schon.status}`));
|
||
} else if (anAlle && werHat.size) {
|
||
/* BEI "ALLE" IST EIN FREMDER STATUS KEINE AUSKUNFT: Fuenf
|
||
Menschen haben fuenf Staende, und hier stuende der einer
|
||
einzigen -- welcher, haengt von der Reihenfolge der Daten ab.
|
||
Der Fuss sagt deshalb nur, WIE WEIT es ist; WER, steht eine
|
||
Zeile tiefer namentlich. */
|
||
fussInfo.append(el('span', 'vorlagenbrett__schon', alleHabenSie
|
||
? `liegt bei allen ${zielbar.length}`
|
||
: `liegt bei ${werHat.size} von ${zielbar.length}`));
|
||
}
|
||
|
||
/* ==== ÜBERNEHMEN ODER BEWERBEN? (23.09.2026) ==================
|
||
|
||
Filipe: „die modis sollen bei all diesen voschlägen auch nur
|
||
bewerben können."
|
||
|
||
DER SERVER SAGT, WAS ICH DARF (`darf_verteilen`). Hier steht
|
||
kein Rollenname — diese Datei bekommt jeder, der die Seite
|
||
öffnet. Und die Schranke selbst liegt ohnehin in der Route:
|
||
Ein ausgeblendeter Knopf ist eine Bitte, abgelehnt wird am
|
||
Server. */
|
||
const meine = bewerbungen.find((b2) => b2.selbst && b2.vorlage === v.schluessel);
|
||
if (darfVerteilenV) {
|
||
/* DER KNOPF SAGT, AN WEN (23.09.2026). „Übernehmen" allein
|
||
laesst offen, wer sie bekommt -- und die Antwort steht weit
|
||
oben auf der Seite. „An Rieke" beantwortet die Frage dort,
|
||
wo man drueckt. Ohne gewaehlte Person bleibt es beim kurzen
|
||
Wort; dann waere ein Name geraten. */
|
||
/* "NOCHMAL" NUR, WENN ES WIRKLICH EIN NOCHMAL WAERE.
|
||
|
||
Bei "alle" heisst "schon da" ab EINEM Menschen. Haetten dann
|
||
alle Karten "Nochmal" gesagt, waere der Knopf eine Warnung
|
||
vor etwas, das gar nicht passiert: Der Server ueberspringt
|
||
die, die sie schon haben, und legt sie nur bei den uebrigen
|
||
an. Erst wenn wirklich alle sie liegen haben, ist der
|
||
naechste Druck eine Wiederholung. */
|
||
const wirklichNochmal = anAlle ? alleHabenSie : !!schon;
|
||
const k = el('button', 'knopf knopf--klein',
|
||
wirklichNochmal ? 'Nochmal' : (zielName ? 'An ' + zielName : 'Übernehmen'));
|
||
k.type = 'button';
|
||
if (wirklichNochmal) {
|
||
k.title = zielName
|
||
? 'Diese Vorlage liegt schon bei ' + zielName + ' – noch einmal übernehmen?'
|
||
: 'Diese Vorlage liegt schon auf dem Brett – noch einmal übernehmen?';
|
||
} else if (anAlle && werHat.size) {
|
||
k.title = `${werHat.size} haben sie schon – die bekommen sie nicht doppelt.`;
|
||
}
|
||
/* `nochmal` sagt dem Server, dass die Wiederholung GEMEINT
|
||
ist -- nicht, dass er die Sperre vergessen soll. Bei "alle"
|
||
fuellt er sonst nur die Luecken; steht hier "Nochmal", haben
|
||
wirklich alle sie, und dann ist der Druck eine Entscheidung. */
|
||
k.addEventListener('click', () => {
|
||
/* VOR dem Verteilen merken, nicht danach: `uebernehmen`
|
||
zeichnet am Ende neu, und dann gibt es diese Karte als
|
||
Objekt nicht mehr. */
|
||
if (anAlle) zuletztVerteilt = v.schluessel;
|
||
uebernehmen(k, {
|
||
art: 'katalog', nr: v.nr, gruppe: kGruppe,
|
||
person_id: katalogZiel(), nochmal: wirklichNochmal,
|
||
});
|
||
});
|
||
fussTun.append(k);
|
||
} else if (meine) {
|
||
/* SCHON BEWORBEN. Der Satz sagt, WER antwortet -- sonst wartet
|
||
man auf niemanden und fragt am nächsten Tag im Chat. */
|
||
fussInfo.append(el('span', 'vorlagenbrett__warte',
|
||
'beworben – DogFather oder die rechte Hand antworten'));
|
||
const zurueck = el('button', 'knopf knopf--klein', 'Zurückziehen');
|
||
zurueck.type = 'button';
|
||
zurueck.addEventListener('click', () => bewerbungZurueck(zurueck, meine));
|
||
fussTun.append(zurueck);
|
||
} else if (!schon) {
|
||
const k = el('button', 'knopf knopf--klein', 'Bewerben');
|
||
k.type = 'button';
|
||
k.title = 'DogFather oder die rechte Hand entscheiden, ob du sie bekommst.';
|
||
k.addEventListener('click', () => bewerben(k, v));
|
||
fussTun.append(k);
|
||
}
|
||
/* WER SIE SCHON HAT, BEKOMMT KEINEN BEWERBEN-KNOPF. Der Server
|
||
lehnt das ohnehin ab (liegt_schon_bei_dir) -- ein Knopf, der
|
||
eine Absage holt, ist schlimmer als keiner. Der Vermerk „schon
|
||
übernommen" steht weiter daneben; er ist die Antwort auf die
|
||
Frage, warum hier nichts zu tun ist. */
|
||
karte.append(fuss);
|
||
|
||
/* ==== WER HAT SIE, WER NICHT -- NAMENTLICH (24.09.2026) =======
|
||
|
||
Filipe: „wenn ich eine aufgabe an alle verteile will ich dass
|
||
dogfather und die rechte hand individuel von jedem sehen wer
|
||
es gemacht hat oder nicht. aufgabe an alle verteilen aber
|
||
individuel sehen wir das."
|
||
|
||
NUR BEI „ALLE". Ist eine einzelne Person gewaehlt, steht ihr
|
||
Stand schon im Fuss -- eine Reihe mit einem einzigen Namen
|
||
waere dieselbe Auskunft ein zweites Mal.
|
||
|
||
DAS WORT STEHT DA, NICHT NUR DIE FARBE. „Erledigt" liest auch,
|
||
wer Farben nicht unterscheidet oder die Seite schwarzweiss
|
||
ausdruckt; die Farbe ist die Abkuerzung, nicht die Auskunft.
|
||
Deshalb traegt jede Marke beides.
|
||
|
||
UND „HAT SIE NICHT" IST AUCH EINE ANTWORT -- die wichtigste
|
||
sogar. Wer nur die auffuehrt, die sie haben, beantwortet
|
||
„wie weit sind wir", aber nicht „wen muss ich noch fragen".
|
||
Die Fehlenden stehen deshalb mit da, gedaempft, aber lesbar. */
|
||
if (darfVerteilenV && zielbar.length) {
|
||
const reihe = el('div', 'vorlagenbrett__wer');
|
||
reihe.setAttribute('role', 'list');
|
||
reihe.setAttribute('aria-label', `Wer „${v.titel}“ schon hat`);
|
||
for (const p2 of zielbar) {
|
||
const a2 = werHat.get(String(p2.id));
|
||
const spaet = !!a2 && istSpaet(a2);
|
||
const marke = el('span', 'vorlagenbrett__wer-p');
|
||
marke.setAttribute('role', 'listitem');
|
||
/* EIN ZUSTAND JE MARKE, und zwar der, nach dem man handelt:
|
||
ueberfaellig sticht „offen", sonst zaehlt der Status. */
|
||
marke.dataset.stand = !a2 ? 'fehlt' : (spaet ? 'spaet' : (a2.status || 'offen'));
|
||
marke.append(el('span', 'vorlagenbrett__wer-name', p2.name));
|
||
marke.append(el('span', 'vorlagenbrett__wer-stand', !a2
|
||
? 'hat sie nicht'
|
||
: ((U.statusName[a2.status] || a2.status) + (spaet ? ' · überfällig' : ''))));
|
||
reihe.append(marke);
|
||
}
|
||
|
||
/* ==== DIE KARTE BEKOMMT ZWEI SEITEN (25.09.2026) =============
|
||
|
||
Bis heute stand die Namensreihe UNTER der Karte -- immer
|
||
sichtbar, bei zwoelf Karten also zwoelfmal. Das war Filipes
|
||
erste Entscheidung und richtig, solange es nur um "wie
|
||
weit" ging. Jetzt will er das Einzelne auf Druck sehen:
|
||
„wenn ich drauf drücke dreht sich die kachel".
|
||
|
||
VORNE BLEIBT DIE KURZFASSUNG. „liegt bei 3 von 4" steht
|
||
weiterhin im Fuss -- man soll den Stand ueberfliegen
|
||
koennen, ohne zwoelf Karten umzudrehen. Die Namen sind die
|
||
Vertiefung, nicht die Grundauskunft.
|
||
|
||
UMGEBAUT STATT NEU GEBAUT: Alles, was schon an der Karte
|
||
haengt, wandert geschlossen auf die Vorderseite. So kann
|
||
die Rueckseite nichts vergessen, was vorne steht -- und ein
|
||
kuenftiger Zusatz landet von selbst richtig. */
|
||
const vorn = el('div', 'vorlagenbrett__seite vorlagenbrett__seite--vorn');
|
||
while (karte.firstChild) vorn.append(karte.firstChild);
|
||
|
||
const hinten = el('div', 'vorlagenbrett__seite vorlagenbrett__seite--hinten');
|
||
hinten.append(el('p', 'vorlagenbrett__wer-schild', alleHabenSie
|
||
? `Alle ${zielbar.length} haben sie`
|
||
: `${werHat.size} von ${zielbar.length} haben sie`));
|
||
hinten.append(reihe);
|
||
|
||
const fussH = el('div', 'vorlagenbrett__fuss');
|
||
/* ==== „NOCHMAL VERTEILEN" GEHOERT AUF DIE RUECKSEITE =========
|
||
|
||
Filipe ausdruecklich: „und immer noch die option es nochmal
|
||
zu verteilen falls neue leute ins team zustoßen."
|
||
|
||
DER KNOPF SAGT, WAS ER TUT, und das ist bei den zwei Faellen
|
||
NICHT dasselbe: Fehlt noch jemand, holt er die Fehlenden
|
||
nach und laesst die anderen in Ruhe. Haben alle sie, ist der
|
||
naechste Druck eine bewusste Wiederholung -- dann heisst er
|
||
so und sagt es dem Server auch (`nochmal`). */
|
||
/* DIE ZAHL IN KLAMMERN UND NICHT IM SATZ (25.09.2026).
|
||
Der erste Entwurf setzte sie hinein: „Die 1 Fehlenden
|
||
nachholen" -- bei genau einer Person falsches Deutsch, und
|
||
„Die 1 Fehlende" waere je nach Mensch das falsche
|
||
Geschlecht. So stimmt der Satz bei jeder Zahl, ohne
|
||
Sonderfall und ohne zu raten. */
|
||
const fehlt = zielbar.length - werHat.size;
|
||
const nachholen = el('button', 'knopf knopf--klein', alleHabenSie
|
||
? 'Nochmal an alle'
|
||
: `Nachholen – ${fehlt === 1 ? '1 fehlt' : fehlt + ' fehlen'}`);
|
||
nachholen.type = 'button';
|
||
nachholen.title = alleHabenSie
|
||
? 'Alle bekommen sie noch einmal – auch die, die sie schon haben.'
|
||
: 'Nur die, die sie noch nicht haben. Niemand bekommt sie doppelt.';
|
||
nachholen.addEventListener('click', () => {
|
||
zuletztVerteilt = v.schluessel;
|
||
/* DIE RUECKSEITE GEHT IMMER AN ALLE (25.09.2026).
|
||
|
||
Hier stand `katalogZiel()` -- also die Person, die oben
|
||
gewaehlt ist. Solange die Rueckseite nur bei „Alle"
|
||
existierte, war das dasselbe. Seit sie immer da ist,
|
||
waere es eine Falle: Der Knopf sagt „Nachholen – 3
|
||
fehlen" und gaebe sie einer einzigen Person. Die
|
||
Rueckseite beantwortet eine Frage ueber das TEAM, also
|
||
handelt ihr Knopf auch am Team. */
|
||
uebernehmen(nachholen, {
|
||
art: 'katalog', nr: v.nr, gruppe: kGruppe,
|
||
person_id: 'alle', nochmal: alleHabenSie,
|
||
});
|
||
});
|
||
fussH.append(nachholen);
|
||
|
||
const zurueck = el('button', 'knopf knopf--klein vorlagenbrett__zurueck', 'Zurück');
|
||
zurueck.type = 'button';
|
||
zurueck.addEventListener('click', () => { karte.dataset.gedreht = 'nein'; });
|
||
fussH.append(zurueck);
|
||
hinten.append(fussH);
|
||
|
||
karte.append(vorn);
|
||
karte.append(hinten);
|
||
karte.dataset.dreh = 'ja';
|
||
/* WER SIE SCHON HAT, KANN MAN AUCH OHNE VERTEILEN NACHSEHEN --
|
||
sonst waere die Rueckseite nur als Nebenwirkung einer
|
||
Handlung erreichbar, und wer nur nachsehen will, muesste
|
||
erst etwas verteilen. */
|
||
const umdrehen = el('button', 'knopf knopf--klein vorlagenbrett__drehknopf',
|
||
'Wer hat sie?');
|
||
umdrehen.type = 'button';
|
||
umdrehen.addEventListener('click', () => { karte.dataset.gedreht = 'ja'; });
|
||
/* IMMER AN DERSELBEN STELLE: in der Knopfreihe, hinter dem
|
||
Verteilen-Knopf. Vorher ging er an den Fuss als Ganzes und
|
||
landete damit hinter dem letzten TEXT -- auf Karten ohne
|
||
„liegt bei" war das eine Zeile hoeher als auf Karten mit. */
|
||
vorn.querySelector('.vorlagenbrett__fuss-tun')?.append(umdrehen);
|
||
|
||
/* Und wenn gerade DIESE Karte verteilt wurde: gedreht zeigen. */
|
||
if (zuletztVerteilt === v.schluessel) {
|
||
karte.dataset.gedreht = 'ja';
|
||
zuletztVerteilt = null;
|
||
} else {
|
||
karte.dataset.gedreht = 'nein';
|
||
}
|
||
}
|
||
|
||
/* ==== DIE BEWERBUNGEN ANDERER -- nur für die, die entscheiden ==
|
||
|
||
Sie stehen AN DER KARTE und nicht in einer eigenen Liste
|
||
irgendwo: Entschieden wird über eine bestimmte Aufgabe, und
|
||
die Frage „will ich, dass Frida das macht?" beantwortet man
|
||
besser mit dem Text der Aufgabe davor.
|
||
|
||
JE MENSCH EINE ZEILE, nicht ein Knopf für alle. Man entscheidet
|
||
über einen Menschen, nicht über einen Listeneintrag — dieselbe
|
||
Überlegung wie am Aufgabenbrett. */
|
||
if (darfEntscheidenV) {
|
||
const offeneB = bewerbungen.filter((b2) => b2.vorlage === v.schluessel);
|
||
if (offeneB.length) {
|
||
karte.dataset.beworben = 'ja';
|
||
const block = el('div', 'vorlagenbrett__bewerbungen');
|
||
block.append(el('p', 'vorlagenbrett__bschild', offeneB.length === 1
|
||
? 'Eine Bewerbung wartet auf deine Antwort:'
|
||
: `${offeneB.length} Bewerbungen warten auf deine Antwort:`));
|
||
offeneB.forEach((b2, i) => {
|
||
const reihe = el('div', 'vorlagenbrett__bewerbung');
|
||
reihe.append(el('span', 'vorlagenbrett__bname', b2.person_name));
|
||
/* DER ERSTE WIRD BENANNT, nicht nur oben hingestellt. Eine
|
||
Reihenfolge allein hält man für Zufall oder Alphabet.
|
||
Nur bei mehr als einer -- sonst ist es Füllwerk. */
|
||
if (i === 0 && offeneB.length > 1) {
|
||
reihe.append(el('span', 'vorlagenbrett__bzuerst', 'zuerst da'));
|
||
}
|
||
if (b2.text) reihe.append(el('span', 'vorlagenbrett__bwort', `„${b2.text}“`));
|
||
for (const [weg, schild, klasse] of [
|
||
['annehmen', 'Annehmen', 'knopf--klar'],
|
||
['ablehnen', 'Ablehnen', ''],
|
||
]) {
|
||
const b3 = el('button', `knopf knopf--klein ${klasse}`, schild);
|
||
b3.type = 'button';
|
||
b3.addEventListener('click', () => entscheiden(b3, weg, schild, b2, v));
|
||
reihe.append(b3);
|
||
}
|
||
block.append(reihe);
|
||
});
|
||
karte.append(block);
|
||
}
|
||
}
|
||
|
||
box.append(karte);
|
||
}
|
||
|
||
if (liste.length) {
|
||
const offen = liste.filter((v) => !schonDa.has(v.schluessel)).length;
|
||
koerper.append(el('p', 'vorlagenbrett__stand', offen
|
||
? `${offen} von ${liste.length} noch nicht übernommen.`
|
||
: `Alle ${liste.length} davon sind übernommen.`));
|
||
}
|
||
koerper.append(box);
|
||
|
||
/* „ALLE ÜBERNEHMEN" GIBT ES NUR FÜR DIE, DIE VERTEILEN (23.09.2026).
|
||
|
||
Ein „Alle bewerben" waere der schnellste Weg, zwölf Bitten auf
|
||
einmal loszuschicken -- und damit zwölf Entscheidungen fuer
|
||
jemand anderen. Wer sich bewirbt, waehlt aus; das ist der
|
||
Unterschied zwischen einer Bewerbung und einem Griff ins Regal. */
|
||
if (liste.length > 1 && darfVerteilenV) {
|
||
const alle = el('button', 'abmelden', `Alle ${liste.length} übernehmen`);
|
||
/* "auf dem Brett" hiess bis zum 23.09.2026 das Aufgabenbrett
|
||
einer anderen Seite. Hier zaehlt, ob sie bei DIESER Person
|
||
schon liegt -- und genau das markiert der Katalog seit heute
|
||
auch an den Karten. */
|
||
alle.title = 'Legt alle Aufgaben an, die gerade in der Liste stehen – '
|
||
+ 'auch die, die schon übernommen sind.';
|
||
alle.type = 'button';
|
||
alle.style.marginTop = '12px';
|
||
/* DIE STUFE WANDERT MIT (16.09.2026). Ohne sie legte dieser Knopf
|
||
alle Aufgaben der Kategorie an -- auch die, die gerade
|
||
ausgeblendet sind. Der Knopf sagt "Alle 4 übernehmen" und
|
||
legte elf an; das merkt man erst auf dem Brett. */
|
||
alle.addEventListener('click', () => uebernehmen(alle,
|
||
{ art: 'katalog_alle', gruppe: kGruppe, stufe: kStufe || '',
|
||
person_id: katalogZiel() }));
|
||
koerper.append(alle);
|
||
}
|
||
}
|
||
|
||
/* FUER WEN wird uebernommen? Wer nur eigene Aufgaben hat, meint sich
|
||
selbst -- der Server setzt das dann ein und braucht hier nichts.
|
||
Wer fuer andere eintragen darf, muss die Person oben ausgewaehlt
|
||
haben; sonst sagt der Server es.
|
||
|
||
SEIT DEM 23.09.2026 FRAGT DAS DIE SEITE. Vorher hielt dieser Block
|
||
eine eigene Auswahl (`kZiel`) und fiel notfalls auf zwei
|
||
Formularfelder zurueck -- drei Quellen fuer eine Frage. Jetzt gibt
|
||
es genau eine, und sie steht dort, wo man sie auch SIEHT: in der
|
||
Personenwahl der Seite. */
|
||
function katalogZiel() { return U.ziel(); }
|
||
|
||
/* Ein Weg fuer beide Knoepfe -- einzeln und alle. Zwei fast gleiche
|
||
Funktionen waeren der sichere Weg dazu, dass die eine irgendwann
|
||
anders reagiert als die andere. */
|
||
/* =====================================================================
|
||
BEWERBEN, ZURÜCKZIEHEN, ENTSCHEIDEN (23.09.2026)
|
||
=====================================================================
|
||
|
||
DREI HANDGRIFFE, EIN MUSTER: fragen, schicken, frisch holen,
|
||
neu zeichnen. Das „frisch holen" ist der Teil, den man vergisst --
|
||
ohne ihn bliebe eine beantwortete Bewerbung stehen, bis jemand die
|
||
Seite neu lädt, und zwei Leute entschieden über dieselbe Bitte.
|
||
|
||
GEFRAGT WIRD MIT `frageNach`, dem Kasten des Hauses. Er bringt das
|
||
Textfeld für die Notiz gleich mit -- ein eigener wäre ein zweiter
|
||
Dialog mit eigener Tastaturbedienung. */
|
||
|
||
/** Nach einer Entscheidung: Katalog UND Brett neu -- in dieser
|
||
* Reihenfolge. Die Aufgabe, die gerade entstanden ist, soll man
|
||
* dort sehen, wo sie ab jetzt lebt. */
|
||
async function nachBewerbung() {
|
||
await vorlagenHolen();
|
||
await U.nachAenderung();
|
||
await vorlagenbrettZeigen();
|
||
}
|
||
|
||
async function bewerben(knopf, v) {
|
||
const antwort = await window.frageNach({
|
||
titel: 'Für diese Aufgabe bewerben?',
|
||
was: `„${v.titel}“ – DogFather oder die rechte Hand entscheiden, ob du sie bekommst.`,
|
||
bleibt: 'Du kannst deine Bewerbung jederzeit wieder zurückziehen.',
|
||
grund: 'Willst du etwas dazuschreiben? (freiwillig)',
|
||
grundPflicht: false,
|
||
platzhalter: 'z. B. ich habe Freitag Zeit dafür',
|
||
ja: 'Bewerben', nein: 'Doch nicht',
|
||
});
|
||
if (!antwort) return;
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await U.hole('/workspace/api/vorlagen/bewerben', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ vorlage: v.schluessel, text: (antwort && antwort.grund) || '' }),
|
||
});
|
||
if (!a.ok) {
|
||
const f = await a.json().catch(() => ({}));
|
||
U.melde(sagWas(f.fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false;
|
||
return;
|
||
}
|
||
U.melde('');
|
||
await nachBewerbung();
|
||
} catch {
|
||
U.melde('Keine Verbindung – die Bewerbung ist nicht angekommen.');
|
||
knopf.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function bewerbungZurueck(knopf, b) {
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await U.hole('/workspace/api/vorlagen/bewerbung-zurueck', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ id: b.id }),
|
||
});
|
||
if (!a.ok) {
|
||
const f = await a.json().catch(() => ({}));
|
||
U.melde(sagWas(f.fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false;
|
||
return;
|
||
}
|
||
U.melde('');
|
||
await nachBewerbung();
|
||
} catch {
|
||
U.melde('Keine Verbindung.');
|
||
knopf.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function entscheiden(knopf, weg, schild, b, v) {
|
||
const wem = b.person_name;
|
||
const antwort = await window.frageNach({
|
||
titel: `${schild} — ${wem}`,
|
||
was: weg === 'annehmen'
|
||
? `${wem} bekommt „${v.titel}“ als Aufgabe auf das Brett.`
|
||
: `${wem} bekommt sie nicht. Die Vorlage bleibt für alle offen.`,
|
||
/* DER TEXT IST DER GRUND, WARUM FILIPE DAS SO WOLLTE: „mit einem
|
||
text als notiz". Eine Absage ohne Satz ist die, nach der
|
||
niemand weiß, woran es lag. */
|
||
bleibt: `Dein Text bleibt an der Entscheidung stehen.`,
|
||
grund: 'Willst du etwas dazuschreiben? (freiwillig)',
|
||
grundPflicht: false,
|
||
platzhalter: weg === 'annehmen'
|
||
? 'z. B. danke dir, mach das gern'
|
||
: 'z. B. das macht diesmal jemand anders',
|
||
ja: schild, nein: 'Abbrechen',
|
||
});
|
||
if (!antwort) return;
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await U.hole(`/workspace/api/vorlagen/bewerbung/${weg}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ id: b.id, kommentar: (antwort && antwort.grund) || '' }),
|
||
});
|
||
if (!a.ok) {
|
||
const f = await a.json().catch(() => ({}));
|
||
U.melde(sagWas(f.fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false;
|
||
return;
|
||
}
|
||
U.melde('');
|
||
await nachBewerbung();
|
||
} catch {
|
||
U.melde('Keine Verbindung – die Entscheidung ist nicht angekommen.');
|
||
knopf.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function uebernehmen(knopf, was, fuer) {
|
||
knopf.disabled = true;
|
||
const vorher = knopf.textContent;
|
||
knopf.textContent = 'Wird übernommen …';
|
||
try {
|
||
const a = await U.hole('/workspace/api/vorlagen/uebernehmen', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ...was, bereich: vBereich, stufe: vStufe, creator_id: fuer }),
|
||
});
|
||
if (!a.ok) {
|
||
const f = await a.json().catch(() => ({}));
|
||
U.melde(sagWas(f.fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false; knopf.textContent = vorher;
|
||
return;
|
||
}
|
||
/* WAS WIRKLICH PASSIERT IST, STEHT DA (23.09.2026).
|
||
|
||
Der Server legt seit heute nichts doppelt an, was noch offen
|
||
liegt. Ohne diese Zeilen hiesse der Knopf "Alle 12
|
||
uebernehmen", es entstuenden drei, und niemand wuesste warum.
|
||
Ein Knopf, der weniger tut als er verspricht UND schweigt, ist
|
||
schlimmer als einer, der zu viel tut: Man drueckt ihn noch
|
||
einmal -- und genau das war am 22.09. der Schaden.
|
||
|
||
GRUEN, NICHT ROT: Das ist keine Stoerung, sondern die
|
||
Auskunft, dass die Sperre gegriffen hat. */
|
||
const d = await a.json().catch(() => ({}));
|
||
if (d.uebersprungen) {
|
||
U.melde(d.angelegt
|
||
? d.angelegt + " übernommen. " + d.uebersprungen
|
||
+ " lagen schon offen da – die kommen nicht doppelt."
|
||
: "Alles davon liegt schon offen da (" + d.uebersprungen
|
||
+ ") – nichts doppelt angelegt.", true);
|
||
} else {
|
||
U.melde("");
|
||
}
|
||
/* Erst die Aufgaben der Person neu, dann der Block -- so sieht
|
||
man die neue Aufgabe sofort dort, wo sie ab jetzt lebt. */
|
||
await U.nachAenderung();
|
||
await vorlagenbrettZeigen();
|
||
} catch {
|
||
U.melde('Das hat nicht geklappt.');
|
||
knopf.disabled = false; knopf.textContent = vorher;
|
||
}
|
||
}
|
||
|
||
/* =====================================================================
|
||
WAS DIE SEITE BENUTZT
|
||
===================================================================== */
|
||
window.Vorlagenbrett = {
|
||
/** Die Seite sagt, welchen Zweig sie zeigt und woher die Daten
|
||
* kommen. Alles als FUNKTION, nicht als Wert: Die gewaehlte Person
|
||
* und die Aufgabenliste aendern sich, waehrend die Seite offen ist
|
||
* -- ein Wert waere der Stand vom Seitenaufbau. */
|
||
einrichten(o) {
|
||
const fehlt = NOETIG.filter((k) => o?.[k] === undefined);
|
||
if (fehlt.length) {
|
||
/* DER DRITTE AUSGANG: nicht in Ordnung, aber auch nicht stumm.
|
||
pruef-sicht liest die Browserkonsole mit -- damit faellt ein
|
||
vergessenes Feld beim naechsten Lauf auf und nicht erst,
|
||
wenn jemand sich wundert, warum der Block leer bleibt. */
|
||
console.error('Vorlagenbrett: die Seite hat nichts gesagt zu: '
|
||
+ fehlt.join(', '));
|
||
return false;
|
||
}
|
||
if (o.zweig !== 'team' && o.zweig !== 'creator') {
|
||
console.error('Vorlagenbrett: unbekannter Zweig ' + o.zweig);
|
||
return false;
|
||
}
|
||
U = o;
|
||
return true;
|
||
},
|
||
|
||
/** Neu zeichnen -- beim Start und nach jedem Wechsel der Person. */
|
||
async zeigen() {
|
||
if (!U) { console.error('Vorlagenbrett: zeigen() vor einrichten()'); return; }
|
||
return vorlagenbrettZeigen();
|
||
},
|
||
|
||
/** Nach einer Aenderung ANDERSWO (jemand hat von Hand eine Aufgabe
|
||
* angelegt): Der Katalog markiert "schon uebernommen" aus der
|
||
* Aufgabenliste, also muss er mitkommen. */
|
||
async auffrischen() {
|
||
if (!U) return;
|
||
vorlagenGeladen = false;
|
||
return vorlagenbrettZeigen();
|
||
},
|
||
};
|
||
})();
|