From f2f11e8091cfdc4ee050091d57248f257299405a Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 3 Oct 2026 00:10:15 +0200 Subject: [PATCH] Gelesene Antworten rutschen zur Seite -- statt sich zu stapeln MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VanVan im Support, Meldung #15: „Die Modis können die Antworten auf die Bewerbungen im Bereich eure Aufgaben noch nicht einklappen. Das wird mit der Zeit unübersichtlich." WARUM ES NICHT EINFACH EIN SCHALTER GEWORDEN IST Am 30.09. habe ich diesen Kasten absichtlich NICHT einklappbar gebaut, und die Begruendung steht woertlich im Quelltext: „Eine Antwort, die man erst aufklappen muss, ist wieder keine." Sie ist richtig -- fuer eine Antwort, die man noch nicht gelesen hat. Danach ist sie falsch herum, und genau das meldet VanVan. Ein Schalter, der alles wegklappt, haette die naechste Absage mitversteckt: die Zeile, derentwegen der Kasten ueberhaupt entstanden ist. Deshalb nicht „einklappbar", sondern GELESEN: · Was noch niemand quittiert hat, steht offen da -- wie bisher. · „Verstanden" schiebt eine Zeile hinter „N ältere Antworten", zugeklappt, jederzeit wieder aufzumachen. Nichts wird geloescht; eine Absage samt Begruendung wegzuwerfen, weil jemand sie einmal gelesen hat, waere das Gegenteil des Umbaus vom 30.09. · Ab zwei offenen Antworten gibt es „Alle N verstanden" -- wer nach dem Urlaub sieben vorfindet, soll nicht siebenmal tippen, und sieben Anfragen waeren sieben Gelegenheiten, dass eine verloren geht. AM MENSCHEN, NICHT AM GERAET `gesehen_am` steht in `vorlagen_bewerbungen`, nicht im Browserspeicher. „Habe ich das gelesen?" ist eine Frage ueber die Person: Sonst waere dieselbe Antwort auf dem Handy wieder neu, nachdem man sie am Rechner gelesen hat -- und das waere genau die Unuebersichtlichkeit, die gemeldet wurde, nur eine Tuer weiter. Das AUFKLAPPEN der aelteren bleibt dagegen im Augenblick: kein Zustand, den man mitschleppt. Nach dem Neuladen ist wieder zu. GEGEN FREMDE ZEILEN GESCHUETZT: Nur die eigenen, nur die beantworteten, 404 statt 403 -- wie ueberall im Haus. Ein zweites „Verstanden" zaehlt nicht noch einmal, sonst wanderte die Zeile bei jedem Klick ans Ende und man saehe nicht mehr, wann man sie wirklich gelesen hat. WAS ICH FALSCH ERWARTET HATTE: Meine erste Pruefung verlangte, dass nach einem „Verstanden" OBEN NICHTS mehr steht. Sie wurde rot -- Frida hatte zwei Antworten, und der Knopf gilt je Zeile. Die Pruefung hatte recht; dass er nur seine eigene Zeile nimmt, ist das gewollte Verhalten und steht jetzt als Aussage dort. NEBENBEI: Ein 11,2-px-Pfeil (gemeldet von pruef-css-klassen). Unter 11,5 px faengt im Haus die Grenze an, ab der man zusammenkneift -- dass es „nur ein Zeichen" ist, aendert daran nichts. GEPRUEFT pruef-bewerbung-aufgaben 164 -> 178 ok Im echten Browser, am Handy (390 px): die ungelesene Antwort steht offen mit „Verstanden", danach ist genau diese eine Zeile weg (2 -> 1), sie liegt hinter „1 ältere Antwort", zugeklappt wird sie gar nicht erst gebaut, aufgeklappt steht sie samt Begruendung wieder da, „Alle verstanden" raeumt den Rest, nach dem Neuladen gilt beides weiter als gelesen und ist wieder zu. Am Server: fremde Antwort 404, erfundene Nummer 404, zweites „Verstanden" zaehlt 0. pruef-struktur 102 ok, 414 Routen (eine neue) · pruef-css-klassen · pruef-zeichen 7 · pruef-modi-katalog 150 · pruef-vorlagen 24 · pruef-aufgaben-vorlagen 60 NUR DAS AGENTURHAUS. „Eure Aufgaben" liegt unter `/workspace`; am Crew-Haus aendert sich keine Zeile. Co-Authored-By: Claude Opus 5 --- server/pruef-bewerbung-aufgaben.mjs | 119 +++++++++++++++++++++++++++ server/workspace-vorlagen.js | 67 ++++++++++++++- server/workspace.js | 39 +++++++++ workspace/anruf-probe.html | 10 +-- workspace/app.webmanifest | 8 +- workspace/assets/css/aufgaben.css | 61 ++++++++++++++ workspace/assets/js/vorlagenbrett.js | 114 +++++++++++++++++++++++-- workspace/aufgaben.html | 38 ++++----- workspace/automation.html | 32 +++---- workspace/befinden.html | 34 ++++---- workspace/bereich.html | 44 +++++----- workspace/bewerben.html | 38 ++++----- workspace/bewerbungen.html | 36 ++++---- workspace/buehne.html | 6 +- workspace/calls.html | 34 ++++---- workspace/chat.html | 40 ++++----- workspace/content.html | 34 ++++---- workspace/crew-index.html | 12 +-- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++++---- workspace/entwicklung.html | 36 ++++---- workspace/hilfe.html | 30 +++---- workspace/index.html | 10 +-- workspace/kalender.html | 34 ++++---- workspace/leistung.html | 32 +++---- workspace/manager-ziele.html | 28 +++---- workspace/material.html | 32 +++---- workspace/notizen.html | 28 +++---- workspace/personen.html | 32 +++---- workspace/profil.html | 36 ++++---- workspace/reaktion.html | 38 ++++----- workspace/rechte.html | 34 ++++---- workspace/report.html | 36 ++++---- workspace/scouting.html | 32 +++---- workspace/start.html | 32 +++---- workspace/startcheck.html | 34 ++++---- workspace/steckbrief.html | 36 ++++---- workspace/support.html | 28 +++---- workspace/tafel.html | 6 +- workspace/talente.html | 36 ++++---- workspace/team.html | 32 +++---- workspace/teamlage.html | 34 ++++---- workspace/teilen.html | 30 +++---- workspace/treff-moderation.html | 32 +++---- workspace/treff-regeln.html | 32 +++---- workspace/uebersicht.html | 32 +++---- workspace/unsere-seiten.html | 30 +++---- workspace/unterstuetzen.html | 32 +++---- workspace/werdegang.html | 32 +++---- workspace/willkommen.html | 30 +++---- workspace/wissen.html | 34 ++++---- 51 files changed, 1074 insertions(+), 694 deletions(-) diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index c2b360d0..46440bf0 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -848,8 +848,127 @@ melde("=== 6. Am echten Bildschirm ==="); await kasten2.screenshot({ path: "server/pruef-bewerbung-antwort.png" }); console.log(" -- Bild: server/pruef-bewerbung-antwort.png"); } + + /* ==== „VERSTANDEN" RAEUMT AUF (03.10.2026) ============== + + VanVan im Support, Meldung #15: „Die Modis können die + Antworten auf die Bewerbungen im Bereich eure Aufgaben noch + nicht einklappen. Das wird mit der Zeit unübersichtlich." + + GEMESSEN WIRD DER GANZE WEG, nicht nur die Route: Knopf + druecken, Zeile rutscht in die zugeklappte Gruppe, Gruppe + laesst sich oeffnen, Text ist noch da -- und nach dem + Neuladen ist sie immer noch gelesen. Der letzte Punkt ist + der eigentliche: Haette ich es im Browserspeicher gemerkt, + waere dieselbe Antwort auf dem naechsten Geraet wieder neu. */ + const stand = () => m5.s.evaluate(() => { + const neuB = document.querySelector('.vorlagenbrett__antwort:not([data-alt])'); + const altB = document.querySelector('.vorlagenbrett__antwort[data-alt="ja"]'); + return { + neuZeilen: neuB ? neuB.querySelectorAll(".vorlagenbrett__antwort-zeile").length : 0, + altKopf: altB + ? (altB.querySelector(".vorlagenbrett__antwort-kopf")?.innerText || "").trim() + : null, + altZeilen: altB ? altB.querySelectorAll(".vorlagenbrett__antwort-zeile").length : 0, + knopf: !!document.querySelector(".vorlagenbrett__antwort-weg"), + }; + }); + + const vorher = await stand(); + ok(vorher.neuZeilen >= 1 && vorher.knopf === true, + `die ungelesene Antwort steht offen da, mit „Verstanden" (${vorher.neuZeilen})`); + ok(vorher.altKopf === null, + `und es gibt noch keine aeltere Gruppe (${vorher.altKopf})`); + + await m5.s.click(".vorlagenbrett__antwort-weg"); + await m5.s.waitForTimeout(1200); + const danach = await stand(); + /* EINE WENIGER, NICHT KEINE. Mein erster Anlauf erwartete hier + null -- Frida hat aber ZWEI Antworten (eine Zusage von + weiter oben und diese Absage), und „Verstanden" gilt je + Zeile. Die Pruefung hatte recht, die Erwartung war falsch; + dass der Knopf nur seine eigene Zeile nimmt, ist genau das + gewollte Verhalten und steht jetzt hier als Aussage. */ + ok(danach.neuZeilen === vorher.neuZeilen - 1, + `nach „Verstanden" ist genau diese eine Zeile weg ` + + `(${vorher.neuZeilen} → ${danach.neuZeilen})`); + /* SIE IST NICHT WEG, SIE IST NUR ZU. Eine Absage samt + Begruendung zu loeschen, weil jemand sie einmal gelesen + hat, waere das Gegenteil von dem, was am 30.09. gebaut + wurde. */ + ok(/1 ältere Antwort/.test(danach.altKopf || ""), + `sondern hinter einer zugeklappten Zeile („${danach.altKopf}")`); + ok(danach.altZeilen === 0, + ` und zugeklappt wird sie gar nicht erst gebaut (${danach.altZeilen})`); + + await m5.s.click(".vorlagenbrett__antwort-kopf"); + await m5.s.waitForTimeout(800); + const auf = await m5.s.evaluate(() => { + const altB = document.querySelector('.vorlagenbrett__antwort[data-alt="ja"]'); + return { + zeilen: altB ? altB.querySelectorAll(".vorlagenbrett__antwort-zeile").length : 0, + text: altB ? (altB.innerText || "").replace(/\s+/g, " ").trim() : "", + }; + }); + ok(auf.zeilen === 1, `aufgeklappt steht sie wieder da (${auf.zeilen})`); + ok(/schon drei offene/.test(auf.text), + "mitsamt der Begruendung — nachlesen geht weiter"); + + /* ---- UND DER KNOPF FUER ALLE AUF EINMAL ---------------- + Wer nach dem Urlaub sieben Antworten vorfindet, soll nicht + siebenmal tippen. Er steht erst ab zwei da -- bei einer + waere er dasselbe wie der daneben, nur laenger. */ + await m5.s.click(".vorlagenbrett__antwort-kopf"); /* wieder zu */ + await m5.s.waitForTimeout(500); + const vorAlle = await m5.s.evaluate(() => + !!document.querySelector(".vorlagenbrett__antwort-alle")); + if (vorAlle) { + await m5.s.click(".vorlagenbrett__antwort-alle"); + await m5.s.waitForTimeout(1200); + } else { + /* Nur noch eine offen -- dann nimmt sie ihr eigener Knopf. */ + await m5.s.click(".vorlagenbrett__antwort-weg"); + await m5.s.waitForTimeout(1200); + } + const leer = await stand(); + ok(leer.neuZeilen === 0, + `danach steht oben gar nichts mehr (${leer.neuZeilen})`); + ok(/2 ältere Antworten/.test(leer.altKopf || ""), + `und beide stehen in der zugeklappten Gruppe („${leer.altKopf}")`); + + /* UND DER SERVER HAT ES SICH GEMERKT. */ + await m5.s.reload({ waitUntil: "networkidle" }); + await m5.s.waitForTimeout(1800); + const nachLaden = await stand(); + ok(nachLaden.neuZeilen === 0 && /2 ältere Antworten/.test(nachLaden.altKopf || ""), + `nach dem Neuladen gelten sie weiter als gelesen („${nachLaden.altKopf}")`); + /* UND SIE IST WIEDER ZU. Das Aufklappen ist eine Frage des + Augenblicks, kein Zustand, den man mitschleppt. */ + ok(nachLaden.altZeilen === 0, + `und wieder zugeklappt (${nachLaden.altZeilen} Zeilen)`); } finally { await m5.ctx.close(); } + /* GEGENPROBE AM SERVER: Eine fremde Antwort laesst sich nicht + als gelesen melden -- und eine, auf die noch niemand + geantwortet hat, auch nicht. Ohne diese beiden waere + „gesehen" ein Knopf, mit dem man in fremden Zeilen schreibt. */ + { + const fremd = await roh("/workspace/api/vorlagen/bewerbung/gesehen", { + methode: "POST", keks: hand.keks, body: { id: bw2?.id } }); + ok(fremd.code === 404, + `eine fremde Antwort kann niemand als gelesen melden (${fremd.code})`); + + const erfunden = await roh("/workspace/api/vorlagen/bewerbung/gesehen", { + methode: "POST", keks: modi.keks, body: { id: 999999 } }); + ok(erfunden.code === 404, `eine erfundene Nummer auch nicht (${erfunden.code})`); + + /* UND ZWEIMAL „VERSTANDEN" AENDERT NICHTS MEHR. */ + const nochmal = await roh("/workspace/api/vorlagen/bewerbung/gesehen", { + methode: "POST", keks: modi.keks, body: { id: bw2?.id } }); + ok(nochmal.code === 200 && nochmal.json?.wieviele === 0, + `ein zweites „Verstanden" zaehlt nicht noch einmal (${nochmal.json?.wieviele})`); + } + const h2 = await seiteAls("hand", "CODE-HAND-0001", 1280); try { await h2.s.goto(`${SEITE}/workspace/entwicklung.html`, { waitUntil: "networkidle" }); diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index 9b6944f8..b1ca6f88 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -1678,7 +1678,7 @@ function vorlagenAntwortenFuer(person) { try { return db().prepare(` SELECT b.id, b.vorlage, b.zustand, b.text, b.entscheid_text, - b.entschieden_am, b.aufgabe_id, e.name AS von_name + b.entschieden_am, b.aufgabe_id, b.gesehen_am, e.name AS von_name FROM vorlagen_bewerbungen b LEFT JOIN personen e ON e.id = b.entschieden_von WHERE b.person_id = ? @@ -1686,7 +1686,16 @@ function vorlagenAntwortenFuer(person) { AND COALESCE(b.entschieden_am, '') >= ? ORDER BY b.entschieden_am DESC`) .all(person.id, vorTagen(ANTWORT_TAGE)) - .map((z) => ({ ...z, titel: vorlageZuSchluessel(z.vorlage)?.titel || z.vorlage })); + /* `gesehen` ALS JA/NEIN und nicht der Zeitstempel: Die + Oberflaeche fragt „stand das schon offen da?", nicht „wann". + Ein Datum dorthin zu schicken hiesse, dass sie es + vergleichen muss -- und beim naechsten Mal anders. */ + .map((z) => ({ + ...z, + gesehen: !!z.gesehen_am, + gesehen_am: undefined, + titel: vorlageZuSchluessel(z.vorlage)?.titel || z.vorlage, + })); } catch (fehler) { console.error("[vorlagen] Antworten:", fehler?.message); return []; @@ -1979,6 +1988,60 @@ vorlagenRouter.post("/workspace/api/vorlagen/bewerbung-zurueck", gleicheHerkunft } }); +/* ---- „Verstanden" -- die Antwort zur Kenntnis nehmen ----------------- + + VanVan im Support, Meldung #15: „Die Modis können die Antworten auf + die Bewerbungen im Bereich eure Aufgaben noch nicht einklappen. Das + wird mit der Zeit unübersichtlich." + + NUR DIE EIGENEN, und nur die BEANTWORTETEN. Eine Bewerbung, auf die + noch niemand geantwortet hat, kann man nicht gelesen haben -- der + Versuch bekommt deshalb 404 und nicht etwa ein stilles „ok". + + EINMAL IST EINMAL: Ein zweiter Druck aendert nichts. `gesehen_am` + wird nur gesetzt, wo es noch leer ist -- sonst wanderte die Zeile + bei jedem Klick ans Ende der Liste und man saehe nicht mehr, wann + man sie wirklich gelesen hat. + + `alle: true` NIMMT ALLE AUF EINMAL. Wer nach dem Urlaub sieben + Antworten vorfindet, soll nicht siebenmal tippen muessen -- und + sieben einzelne Anfragen waeren sieben Gelegenheiten, dass eine + davon unterwegs verlorengeht. */ +vorlagenRouter.post("/workspace/api/vorlagen/bewerbung/gesehen", gleicheHerkunft, + express.json({ limit: "2kb" }), (req, res) => { + try { + const nun = jetzt(); + if (req.body?.alle === true) { + const n = db().prepare(`UPDATE vorlagen_bewerbungen + SET gesehen_am = ? + WHERE person_id = ? AND gesehen_am IS NULL + AND zustand IN ('angenommen','abgelehnt')`) + .run(nun, req.person.id).changes; + return res.json({ ok: true, wieviele: n, + antworten: vorlagenAntwortenFuer(req.person) }); + } + const id = Number(req.body?.id); + if (!Number.isInteger(id)) return res.status(400).json({ fehler: "ungueltig" }); + const b = db().prepare("SELECT * FROM vorlagen_bewerbungen WHERE id = ?").get(id); + /* NICHT MEINE = GIBT ES NICHT -- dieselbe Antwort wie beim + Zurueckziehen eine Route weiter oben. Ein 403 verriete, dass + es sie gibt und wem sie gehoert. */ + if (!b || b.person_id !== req.person.id + || !["angenommen", "abgelehnt"].includes(b.zustand)) { + return res.status(404).json({ fehler: "nicht_gefunden" }); + } + if (!b.gesehen_am) { + db().prepare("UPDATE vorlagen_bewerbungen SET gesehen_am = ? WHERE id = ?") + .run(nun, id); + } + res.json({ ok: true, wieviele: b.gesehen_am ? 0 : 1, + antworten: vorlagenAntwortenFuer(req.person) }); + } catch (f) { + console.error("[vorlagen] bewerbung/gesehen:", f?.message); + res.status(503).json({ fehler: "nicht_verfuegbar" }); + } + }); + /* ---- Entscheiden: annehmen oder ablehnen ------------------------------ EINE SCHLEIFE FUER BEIDE WEGE. Zwei fast gleiche Routen waeren zwei diff --git a/server/workspace.js b/server/workspace.js index 4ec28e0b..5a5ab76c 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -4384,6 +4384,40 @@ function umstellungen(d) { console.error("[workspace] Spalte dauerhaft:", fehler?.message); } + /* ==== GELESEN IST GELESEN (03.10.2026) ============================= + + VanVan im Support, Meldung #15: „Die Modis können die Antworten + auf die Bewerbungen im Bereich eure Aufgaben noch nicht + einklappen. Das wird mit der Zeit unübersichtlich." + + Am 30.09. habe ich diese Antworten absichtlich NICHT einklappbar + gemacht, mit der Begruendung: „Eine Antwort, die man erst + aufklappen muss, ist wieder keine." Das stimmt -- fuer eine + Antwort, die man noch nicht gelesen hat. Danach ist es falsch + herum, und genau das meldet sie. + + DESHALB NICHT „EINKLAPPBAR", SONDERN „GELESEN". Ein Schalter, + der alles wegklappt, haette die naechste Absage mitversteckt -- + die Zeile, die man unbedingt sehen soll. Eine Antwort, die man + mit „Verstanden" quittiert hat, rutscht dagegen hinter eine + zugeklappte Zeile und bleibt nachlesbar. Beides zusammen loest + ihr Problem UND behaelt den Grund von vorgestern. + + AM MENSCHEN UND NICHT AM GERAET. Sie haette auch in den + Browserspeicher gekonnt; dann waere dieselbe Antwort auf dem + Handy wieder neu. „Habe ich das gelesen?" ist eine Frage ueber + die Person, nicht ueber das Geraet. */ + try { + const spalten = d.prepare("PRAGMA table_info(vorlagen_bewerbungen)").all() + .map((x) => x.name); + if (spalten.length && !spalten.includes("gesehen_am")) { + d.exec("ALTER TABLE vorlagen_bewerbungen ADD COLUMN gesehen_am TEXT"); + console.log("[workspace] Spalte gesehen_am an vorlagen_bewerbungen angelegt."); + } + } catch (fehler) { + console.error("[workspace] Spalte gesehen_am:", fehler?.message); + } + /* ===================================================================== WER VERANTWORTLICH IST, BRAUCHT EINE ZUTEILUNGSZEILE (30.09.2026) ===================================================================== @@ -5480,6 +5514,11 @@ export function db() { entschieden_von INTEGER REFERENCES personen(id) ON DELETE SET NULL, entschieden_am TEXT, aufgabe_id INTEGER, + /* Wann die Person die Antwort darauf zur Kenntnis genommen + hat (03.10.2026, VanVan #15). NULL heisst: noch nicht -- + dann steht sie offen da. Siehe die ausfuehrliche + Begruendung bei der Nachruestung weiter oben. */ + gesehen_am TEXT, erstellt TEXT NOT NULL ); /* EINMAL BEWERBEN, NICHT DREIMAL -- und zwar von der Datenbank diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 1908039b..2c7e85b0 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +