diff --git a/server/pruef-aufgaben-vorlagen.mjs b/server/pruef-aufgaben-vorlagen.mjs index 6e326fa0..2f72c92b 100644 --- a/server/pruef-aufgaben-vorlagen.mjs +++ b/server/pruef-aufgaben-vorlagen.mjs @@ -295,10 +295,50 @@ console.log("\n=== Auf der Aufgabenseite"); const st = getComputedStyle(k); return parseFloat(st.borderTopWidth) >= 1 && parseFloat(st.paddingLeft) >= 8; })(), + kopftext: document.querySelector("#vorlagenbrett .vorlagenbrett__unter")?.textContent, ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth, }; }); ok(d.sichtbar, "der Block ist da"); + + /* ZUGEKLAPPT ALS VORGABE (09.09.2026). + + Gemessen auf dem Handy: Der Block fuellte anderthalb Bildschirme, + BEVOR die erste eigene Aufgabe kam -- und nach dem Ausbau von Profi + und Meister am selben Tag noch mehr. Vorlagen holt man selten, das + Brett benutzt man taeglich. + + Geprueft wird, dass die Karten GAR NICHT GEBAUT werden, nicht nur + dass sie versteckt sind: Achtzig Karten bei jedem Neuzeichnen + anzulegen, die niemand sieht, waere Arbeit fuer nichts. */ + ok(d.karten === 0 && d.reihen === 0, + `zugeklappt steht nur die Zeile da (${d.karten} Karten, ${d.reihen} Reihen)`); + ok(/\d+ fertige Aufgaben/.test(d.kopftext || ""), + `und sagt, wie viel darin liegt ("${d.kopftext}")`); + + /* Aufmachen, und alles ist da. */ + await seite.click("#vorlagenbrett .vorlagenbrett__kopf"); + await seite.waitForTimeout(600); + const d2 = await seite.evaluate(() => ({ + reihen: document.querySelectorAll("#vorlagenbrett .stufenleiter").length, + karten: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__karte").length, + knoepfe: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__fuss .knopf").length, + auf: document.getElementById("vorlagenbrett").dataset.auf, + /* ERST JETZT MESSBAR. Zugeklappt gibt es keine Karte, und + getComputedStyle auf `null` liefert nichts -- die Messung von + vorhin war deshalb leer und der Haken darunter rot, ohne dass + etwas kaputt war. Eine Pruefung muss ihre Voraussetzung haben, + bevor sie misst. */ + gestaltet: (() => { + const k = document.querySelector("#vorlagenbrett .vorlagenbrett__karte"); + if (!k) return null; + const st = getComputedStyle(k); + return parseFloat(st.borderTopWidth) >= 1 && parseFloat(st.paddingLeft) >= 8; + })(), + ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth, + })); + Object.assign(d, d2); + ok(d.auf === "ja", "ein Klick auf den Kopf macht ihn auf"); ok(d.reihen === 2, `zwei Knopfreihen: Bereich und Stufe (${d.reihen})`); ok(d.karten === 3, `drei Aufgaben je Feld (${d.karten})`); ok(d.knoepfe === 3, `jede hat einen Übernehmen-Knopf (${d.knoepfe})`); @@ -320,6 +360,42 @@ console.log("\n=== Auf der Aufgabenseite"); const brettNachher = await zaehle(); ok(brettNachher === brettVorher + 1, `Übernehmen legt eine Aufgabe auf dem Brett an (${brettVorher} → ${brettNachher})`); + + /* ---- 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 weiss niemand auswendig, was er letzten Monat + schon geholt hat. */ + { + const m = await seite.evaluate(() => { + const k = [...document.querySelectorAll("#vorlagenbrett .vorlagenbrett__karte")]; + return { markiert: k.filter((x) => x.dataset.schon === "ja").length, + gesamt: k.length, + text: document.querySelector("#vorlagenbrett .vorlagenbrett__schon")?.textContent, + knopf: k.find((x) => x.dataset.schon === "ja") + ?.querySelector(".knopf")?.textContent, + stand: document.querySelector("#vorlagenbrett .vorlagenbrett__stand")?.textContent }; + }); + ok(m.markiert === 1, + `genau die übernommene Vorlage ist markiert (${m.markiert} von ${m.gesamt})`); + ok(/schon übernommen/.test(m.text || ""), `mit Zustand dabei ("${m.text}")`); + ok(m.knopf === "Nochmal", `und der Knopf heißt anders ("${m.knopf}")`); + ok(/\d+ von \d+/.test(m.stand || ""), `darunter steht der Stand ("${m.stand}")`); + + /* GEGENPROBE: Die übrigen sind NICHT markiert. Ohne sie hiesse + "eine ist markiert" auch dann ok, wenn alle es waeren. */ + ok(m.gesamt - m.markiert >= 1, + `die übrigen ${m.gesamt - m.markiert} sind nicht markiert`); + } + + /* Und der Zustand des Blocks überlebt ein Neuladen -- wer Vorlagen + holt, holt meistens mehrere. */ + await seite.reload({ waitUntil: "domcontentloaded" }); + await seite.waitForTimeout(2200); + const nochAuf = await seite.evaluate(() => + document.getElementById("vorlagenbrett")?.dataset.auf); + ok(nochAuf === "ja", `nach dem Neuladen ist er noch offen (${nochAuf})`); + ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? ": " + konsole[0] : ""}`); await browser.close(); } diff --git a/server/pruef-aufgabenbrett.mjs b/server/pruef-aufgabenbrett.mjs index 8077a4da..1a94a347 100644 --- a/server/pruef-aufgabenbrett.mjs +++ b/server/pruef-aufgabenbrett.mjs @@ -377,6 +377,85 @@ ok(rH.ueber <= 0, `auf dem Handy steht nichts ueber (${rH.ueber}px)`); ok(rH.schnitte.length === 4, `die Ausschnitte sind auch dort da (${rH.schnitte.length})`); await bHandy.kontext.close(); +/* --------------------------------------------------------------------- + DRINGEND SCHLAEGT WICHTIG (09.09.2026) + + Filipe: "das system von den aufgaben, die bedienung. wie gesagt + perfektionier das." + + Beim Durchgehen der Bedienung gefunden: Die Liste war nach + PRIORITAET sortiert, dann nach Frist. Auf dem Brett stand damit eine + seit Tagen ueberfaellige Aufgabe mit Prioritaet "mittel" UNTER einer, + die als "hoch" eingetragen ist und erst in Wochen faellig wird. Wer + die Spalte von oben liest, faengt dann mit dem Falschen an. + + Eine Prioritaet ist eine Einschaetzung von damals, eine + ueberschrittene Frist eine Tatsache von heute. + --------------------------------------------------------------------- */ +console.log("\n=== Die Reihenfolge auf dem Brett"); +{ + const dd = new DatabaseSync(process.env.WORKSPACE_DB); + const tagT = (v) => { const x = new Date(Date.now() + v * 86400000); + return `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, "0")}` + + `-${String(x.getDate()).padStart(2, "0")}`; }; + /* Absichtlich in der ungünstigsten Reihenfolge angelegt: Die + ueberfaellige zuerst mit NIEDRIGER Prioritaet, die weit entfernte + mit HOHER. Nach der alten Sortierung stuende die weit entfernte + oben. */ + dd.prepare(`INSERT INTO aufgaben (titel, beschreibung, status, prioritaet, frist, + creator_id, verantwortlich_id, erstellt, erstellt_von) + VALUES (?,?,'offen',?,?,?,?,?,?)`) + .run("SORT-ueberfaellig-niedrig", "", "niedrig", tagT(-4), idTili, idTili, jetzt, idDogi); + dd.prepare(`INSERT INTO aufgaben (titel, beschreibung, status, prioritaet, frist, + creator_id, verantwortlich_id, erstellt, erstellt_von) + VALUES (?,?,'offen',?,?,?,?,?,?)`) + .run("SORT-fern-hoch", "", "hoch", tagT(30), idTili, idTili, jetzt, idDogi); + dd.close(); + + const a = await fetch(BASIS + "/workspace/api/aufgaben", { headers: { cookie: kTili } }); + const liste = (await a.json()).aufgaben.filter((x) => x.titel.startsWith("SORT-")); + ok(liste.length === 2, `beide Probeaufgaben da (${liste.length})`); + ok(liste[0]?.titel === "SORT-ueberfaellig-niedrig", + `die überfällige steht oben, obwohl sie "niedrig" ist ` + + `(Reihenfolge: ${liste.map((x) => x.titel).join(" vor ")})`); + + /* GEGENPROBE: Bei gleicher Lage entscheidet weiterhin die Prioritaet. + Ohne sie hiesse "Frist zuerst", dass die Prioritaet gar nichts mehr + tut -- und das waere die andere Uebertreibung. */ + const dd2 = new DatabaseSync(process.env.WORKSPACE_DB); + for (const [t, pr] of [["SORTB-niedrig", "niedrig"], ["SORTB-hoch", "hoch"]]) { + dd2.prepare(`INSERT INTO aufgaben (titel, beschreibung, status, prioritaet, frist, + creator_id, verantwortlich_id, erstellt, erstellt_von) + VALUES (?,?,'offen',?,?,?,?,?,?)`) + .run(t, "", pr, tagT(10), idTili, idTili, jetzt, idDogi); + } + dd2.close(); + const b = await fetch(BASIS + "/workspace/api/aufgaben", { headers: { cookie: kTili } }); + const zwei = (await b.json()).aufgaben.filter((x) => x.titel.startsWith("SORTB-")); + ok(zwei[0]?.titel === "SORTB-hoch", + `bei gleicher Frist entscheidet weiter die Priorität ` + + `(${zwei.map((x) => x.titel).join(" vor ")})`); + + /* Und die Marke "wichtig" steht auf der Karte -- die Prioritaet war + vorher nur ein 3 px breiter Rand neben der farbigen Spaltenkante + und damit praktisch unsichtbar. */ + const b2 = await seiteAls("creator", "CODE-ANNA-0001"); + const marken = await b2.seite.evaluate(() => { + const k = [...document.querySelectorAll("#brett .karte")]; + return { mitMarke: k.filter((x) => x.querySelector(".karte__wichtig")).length, + gesamt: k.length, + hochOhne: k.filter((x) => x.dataset.prio === "hoch" + && x.dataset.status !== "erledigt" && !x.querySelector(".karte__wichtig")).length, + niedrigMit: k.filter((x) => x.dataset.prio !== "hoch" + && x.querySelector(".karte__wichtig")).length }; + }); + ok(marken.mitMarke > 0 && marken.hochOhne === 0, + `jede wichtige Aufgabe traegt die Marke (${marken.mitMarke} von ${marken.gesamt})`); + ok(marken.niedrigMit === 0, + `und keine unwichtige traegt sie (${marken.niedrigMit})`); + await b2.kontext.close(); +} + await browser.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } console.log(fehler ? `\n${fehler} Prüfung(en) fehlgeschlagen.` : "\nAlles in Ordnung."); diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 09ce89fb..45e7e6af 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -120,6 +120,9 @@ const SPALTEN = ` das lesen -- ein Nachladen je Zeile wäre bei zwanzig abgebrochenen Aufgaben zwanzig Anfragen. */ a.abbruch_grund, a.abgebrochen_am, a.status_vorher, + /* Die Vorlage, aus der sie entstanden ist (09.09.2026) -- damit das + Vorlagenbrett zeigen kann, was schon geholt wurde. */ + a.vorlage, pa.name AS abbruch_von_name`; const VERBUND = ` @@ -138,10 +141,28 @@ aufgabenRouter.get("/workspace/api/aufgaben", (req, res) => { (SELECT COUNT(*) FROM aufgaben_notizen n WHERE n.aufgabe_id = a.id) AS notizen ${VERBUND} WHERE ${wo} + /* DRINGEND SCHLAEGT WICHTIG (09.09.2026). + + Hier stand die Prioritaet an erster Stelle. Folge auf dem + Brett: Eine seit drei Tagen ueberfaellige Aufgabe mit + Prioritaet "mittel" stand UNTER einer, die als "hoch" + eingetragen ist und erst in drei Wochen faellig wird. Wer die + Spalte von oben liest, faengt dann mit dem Falschen an. + + Eine Prioritaet ist eine Einschaetzung von damals, eine + ueberschrittene Frist ist eine Tatsache von heute. Deshalb: + erst was faellig ist, dann nach Datum, und die Prioritaet + entscheidet nur noch bei gleichem Datum. + + Die Sortierung steht hier und nicht im Browser: Die Startseite + und die Uebersicht lesen dieselbe Liste, und zwei Sortierungen + fuer dieselbe Frage laufen auseinander. */ ORDER BY - CASE a.prioritaet WHEN 'hoch' THEN 0 WHEN 'mittel' THEN 1 ELSE 2 END, + CASE WHEN a.status <> 'erledigt' AND a.frist IS NOT NULL AND a.frist < ? + THEN 0 ELSE 1 END, CASE WHEN a.frist IS NULL THEN 1 ELSE 0 END, a.frist, - a.id DESC`).all(...werte); + CASE a.prioritaet WHEN 'hoch' THEN 0 WHEN 'mittel' THEN 1 ELSE 2 END, + a.id DESC`).all(...werte, heuteLokal()); res.json({ aufgaben: reihen }); } catch (fehler) { console.error("[workspace] Aufgaben lesen:", fehler?.message); diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index a2886d1c..aed88e9a 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -1212,11 +1212,15 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft, const d = new Date(Date.now() + (v.tage || 7) * 86400_000); const frist = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}` + `-${String(d.getDate()).padStart(2, "0")}`; + /* Die Kennung der Vorlage wandert mit (09.09.2026). Nur so + kann das Brett spaeter zeigen, was schon uebernommen ist -- + und der Titel taugt dafuer nicht: Er darf geaendert werden. */ db().prepare(`INSERT INTO aufgaben - (titel, beschreibung, status, prioritaet, creator_id, frist, erstellt, erstellt_von) - VALUES (?,?,'offen',?,?,?,?,?)`) + (titel, beschreibung, status, prioritaet, creator_id, frist, + vorlage, erstellt, erstellt_von) + VALUES (?,?,'offen',?,?,?,?,?,?)`) .run(v.titel, v.text, stufe === "anfaenger" ? "hoch" : "mittel", - creatorId, frist, jetzt(), req.person.id); + creatorId, frist, v.schluessel, jetzt(), req.person.id); angelegt.push(v.titel); } diff --git a/server/workspace.js b/server/workspace.js index 8506064d..d616aaf7 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -439,6 +439,25 @@ function umstellungen(d) { ["chat_nachrichten", "anhang_groesse", "INTEGER"], ["chat_nachrichten", "anhang_breite", "INTEGER"], ["chat_nachrichten", "anhang_hoehe", "INTEGER"], + + /* WELCHE VORLAGE DAHINTERSTECKT (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." + + Beim Durchgehen der Bedienung ist aufgefallen: Man konnte + dieselbe Vorlage zweimal uebernehmen und bekam die Aufgabe + zweimal auf das Brett -- ohne jeden Hinweis. Im Vorlagenbrett + sah man nicht, was man schon geholt hatte. + + Die Kennung der Vorlage steht jetzt an der Aufgabe. Damit kann + das Brett zeigen, was bereits uebernommen ist, und es braucht + dafuer KEINE zweite Abfrage: Die Aufgabenliste hat der Browser + ohnehin schon. Ueber den TITEL zu vergleichen waere die + naheliegende Abkuerzung gewesen -- und faellt in dem Moment um, + in dem jemand den Titel einer uebernommenen Aufgabe aendert. */ + ["aufgaben", "vorlage", "TEXT"], ]) { try { const vorhanden = d.prepare(`PRAGMA table_info(${tabelle})`).all().map((s) => s.name); diff --git a/workspace/assets/css/aufgaben.css b/workspace/assets/css/aufgaben.css index 16ce8673..97ef7ea1 100644 --- a/workspace/assets/css/aufgaben.css +++ b/workspace/assets/css/aufgaben.css @@ -915,3 +915,83 @@ .vorlagenbrett { padding: 16px 14px 18px; } .vorlagenbrett__liste { grid-template-columns: 1fr; } } + +/* ===================================================================== + DIE BEDIENUNG DER AUFGABEN (09.09.2026) + + Filipe: "ich will nur dass du dich informierst und schaust was man + vielleicht noch besser machen könnte von der benutzung wenn man die + benutzt. also ich red von den aufgaben. das system von den aufgaben, + die bedienung." + + Gemessen auf dem Handy: Der Vorlagenblock füllte anderthalb + Bildschirme, BEVOR die erste eigene Aufgabe kam -- und der Ausbau von + Profi und Meister am selben Tag hat ihn noch länger gemacht. Vorlagen + holt man selten, das Brett benutzt man jeden Tag. + ===================================================================== */ + +/* ---- Der Kopf ist der Klappknopf ------------------------------------ */ +.vorlagenbrett__kopf { + width: 100%; + display: flex; align-items: center; gap: 12px; + padding: 0; margin: 0 0 2px; + background: none; border: 0; + font: inherit; color: inherit; text-align: left; cursor: pointer; +} +.vorlagenbrett__kopftext { display: grid; gap: 4px; min-width: 0; flex: 1; } +.vorlagenbrett__kopf .klapp-pfeil { + flex: none; width: 20px; height: 20px; + fill: none; stroke: currentColor; stroke-width: 1.8; + stroke-linecap: round; stroke-linejoin: round; + color: var(--text-still); + transition: transform var(--tempo), color var(--tempo); +} +.vorlagenbrett__kopf[aria-expanded="true"] .klapp-pfeil { transform: rotate(180deg); } +.vorlagenbrett__kopf:hover .klapp-pfeil { color: var(--akzent); } +.vorlagenbrett__kopf:focus-visible { + outline: 2px solid var(--akzent); outline-offset: 4px; border-radius: 6px; +} +/* Zugeklappt braucht der Block nur die Höhe seiner Zeile -- sonst bliebe + ein leerer Kasten stehen und der gewonnene Platz wäre wieder weg. */ +.vorlagenbrett[data-auf="nein"] { padding-bottom: 14px; } +.vorlagenbrett__koerper { display: contents; } + +/* ---- Schon übernommen ----------------------------------------------- */ +/* Die Karte tritt zurück, statt zu verschwinden. Wer eine Vorlage + ausblendet, nimmt die Möglichkeit weg, sie bewusst noch einmal zu + holen -- und lässt den Verdacht offen, dass etwas fehlt. */ +.vorlagenbrett__karte[data-schon="ja"] { opacity: .62; } +.vorlagenbrett__karte[data-schon="ja"]:hover, +.vorlagenbrett__karte[data-schon="ja"]:focus-within { opacity: 1; } +.vorlagenbrett__schon { + font-size: .74rem; font-weight: 600; + color: var(--scout-haupt); +} +.vorlagenbrett__stand { + margin: 12px 0 0; + font-size: .8rem; color: var(--text-leise); + font-variant-numeric: tabular-nums; +} + +/* ---- Wichtig --------------------------------------------------------- + Die Priorität war ein 3 px breiter Rand links -- direkt neben der + farbigen Kante der Spalte und damit praktisch unsichtbar. Als Wort + steht sie dort, wo man sie liest. */ +.karte__wichtig { + flex: none; + padding: 1px 8px; border-radius: 999px; + /* 0,72 rem: Darunter liegt die Hausgrenze von 11,5 px. Gemeldet von + pruef-css-klassen, bevor es jemand lesen musste. */ + font-size: .72rem; font-weight: 700; + letter-spacing: .04em; text-transform: uppercase; + color: #ffb4ba; + background: rgba(216, 101, 110, .16); + border: 1px solid rgba(216, 101, 110, .38); +} + +@media (prefers-reduced-motion: reduce) { + .vorlagenbrett__kopf .klapp-pfeil { transition: none; } +} +@media (forced-colors: active) { + .karte__wichtig { border: 1px solid CanvasText; } +} diff --git a/workspace/assets/js/aufgaben.js b/workspace/assets/js/aufgaben.js index f8297205..f8407c0e 100644 --- a/workspace/assets/js/aufgaben.js +++ b/workspace/assets/js/aufgaben.js @@ -183,6 +183,17 @@ if (a.beschreibung) k.append(el('p', 'karte__text', a.beschreibung)); const zeile = el('div', 'karte__zeile'); + /* WICHTIG STEHT DA, statt nur eine Kante einzufaerben. + + Die Prioritaet gab es als 3 px breiten Rand links -- direkt neben + der farbigen Kante der Spalte, und damit praktisch unsichtbar. + Als Wort steht sie da, wo man sie liest. + + NUR bei "hoch". Eine Marke an jeder Karte waere keine Marke mehr: + Was immer da steht, sieht man nach zwei Tagen nicht mehr. */ + if (a.prioritaet === 'hoch' && a.status !== 'erledigt') { + zeile.append(el('span', 'karte__wichtig', 'wichtig')); + } if (a.frist) { const faellig = a.status !== 'erledigt' && a.frist < heute; const f = el('span', 'karte__frist' + (faellig ? ' karte__frist--faellig' : ''), @@ -882,6 +893,25 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe 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. */ + const VORLAGEN_OFFEN = 'dfw_vorlagenbrett_offen'; + let vOffen = false; + try { vOffen = localStorage.getItem(VORLAGEN_OFFEN) === 'ja'; } catch { vOffen = false; } + async function vorlagenbrettZeigen() { const ziel = $('vorlagenbrett'); if (!ziel || !ich) return; @@ -896,12 +926,48 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe ziel.hidden = false; ziel.textContent = ''; - const kopf = el('div', 'vorlagenbrett__kopf'); - kopf.append(el('h2', 'feldschild', 'Aufgaben aus der Vorlage')); - kopf.append(el('p', 'vorlagenbrett__unter', - 'Vier Bereiche, vier Stufen. Übernommen wird als ganz normale Aufgabe ' - + '– mit Frist, veränderbar, auf dem Brett darunter.')); + /* 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(vOffen)); + 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', vOffen + ? '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 = !vOffen; + try { localStorage.setItem(VORLAGEN_OFFEN, vOffen ? 'ja' : 'nein'); } catch { /* egal */ } + vorlagenbrettZeigen(); + }); ziel.append(kopf); + ziel.dataset.auf = vOffen ? '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 (!vOffen) return; + + const koerper = el('div', 'vorlagenbrett__koerper'); + koerper.id = 'vorlagenbrett-koerper'; + ziel.append(koerper); /* Reihe 1: der Bereich. */ const r1 = el('div', 'stufenleiter'); @@ -916,7 +982,7 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe k.addEventListener('click', () => { vBereich = b.schluessel; vorlagenbrettZeigen(); }); r1.append(k); } - ziel.append(r1); + koerper.append(r1); /* Reihe 2: die Stufe, mit der Zahl der Aufgaben darin. */ const r2 = el('div', 'stufenleiter'); @@ -935,43 +1001,84 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe k.addEventListener('click', () => { vStufe = st.schluessel; vorlagenbrettZeigen(); }); r2.append(k); } - ziel.append(r2); + koerper.append(r2); const hilfe = STUFEN_VORLAGE.find((x) => x.schluessel === vStufe)?.hilfe; - if (hilfe) ziel.append(el('p', 'stufenleiter__hilfe', hilfe)); + if (hilfe) koerper.append(el('p', 'stufenleiter__hilfe', hilfe)); /* Für wen? */ const fuer = ich.rolle === 'creator' ? ich.id : nurCreator; if (!fuer) { - ziel.append(el('p', 'vorlagenbrett__hinweis', + 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 alleAufgaben) { + 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 · ${STATUS_NAME[schon.status] || schon.status}`)); + } if (fuer) { - const k = el('button', 'knopf knopf--klein', 'Übernehmen'); + 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); } - ziel.append(box); + /* 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)); - ziel.append(alle); + koerper.append(alle); } } diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index c640164e..88e579f3 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -14,10 +14,10 @@ - - - - + + + +
@@ -231,10 +231,10 @@ - - - - - + + + + +