Gelesene Antworten rutschen zur Seite -- statt sich zu stapeln

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 <[email protected]>
This commit is contained in:
2026-10-03 00:10:15 +02:00
co-authored by Claude Opus 5
parent 018d02c3b8
commit f2f11e8091
51 changed files with 1074 additions and 694 deletions
+65 -2
View File
@@ -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