diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index fd7fc487..8278da92 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -843,6 +843,121 @@ melde("=== 6. Am echten Bildschirm ==="); } finally { await m.ctx.close(); } } + /* ================================================================= + DIE SPALTE „BEWERBUNGEN" IM AUFGABENBRETT (25.09.2026) + ================================================================= + + Filipe: „ich will in dieser seite auch eine eigene kategorie fuer + die aufgaben wo die modis sich selbst bewerben. ich will dass man + die getrennt sieht. mach es richtig uebersichtlich." + + BISHER STANDEN BEWERBUNGEN NUR AUF DEM VORLAGENBRETT, an der + jeweiligen Karte. Wer morgens das Aufgabenbrett oeffnete, sah + nicht, dass jemand auf eine Antwort wartet. + + DREI DINGE WERDEN GEMESSEN, und das dritte ist das, was ohne + Pruefung irgendwann kippt: + 1. Wer entscheidet, sieht die Spalte MIT den zwei Knoepfen. + 2. Der Modi sieht seine eigene -- ohne Knoepfe, mit dem Satz + „wartet auf Antwort". Ein Knopf, der eine Absage holt, + waere schlimmer als keiner. + 3. Ohne Bewerbung gibt es die Spalte GAR NICHT. Eine leere + Spalte stuende 360 Tage im Jahr im Weg. + ================================================================= */ + { + /* ERST DER ZUSTAND OHNE: Ohne diese Messung waere „die Spalte ist + da" auch dann gruen, wenn sie immer da waere. */ + const vorher = await seiteAls("admin", "CODE-DOGI-0001", 1400); + let ohne = null; + try { + await vorher.s.goto(`${SEITE}/workspace/aufgaben.html`, { waitUntil: "networkidle" }); + await vorher.s.waitForTimeout(1700); + ohne = await vorher.s.evaluate(() => + document.querySelectorAll(".spalte--bewerbung").length); + } finally { await vorher.ctx.close(); } + ok(ohne === 0, `ohne Bewerbung gibt es die Spalte nicht (${ohne})`); + + /* JETZT BEWIRBT SICH EIN MODI -- ueber die echte Schnittstelle + der Seite, damit Sitzung und Herkunftspruefung mitlaufen. */ + const m = await seiteAls("modi", "CODE-MODI-0001", 1400); + let beworben = null, beiModi = null; + try { + await m.s.goto(`${SEITE}/workspace/entwicklung.html`, { waitUntil: "networkidle" }); + await m.s.waitForTimeout(1500); + beworben = await m.s.evaluate(async () => { + const k = await fetch("/workspace/api/vorlagen", { credentials: "same-origin" }); + const d = await k.json(); + const v = (d.katalog?.aufgaben || [])[0]; + if (!v) return { fehler: "kein Katalog" }; + const a = await fetch("/workspace/api/vorlagen/bewerben", { + method: "POST", credentials: "same-origin", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ vorlage: v.schluessel, text: "ich habe Freitag Zeit" }), + }); + return { code: a.status, titel: v.titel }; + }); + /* UND DER MODI SIEHT SEINE EIGENE -- auf dem Aufgabenbrett. */ + await m.s.goto(`${SEITE}/workspace/aufgaben.html`, { waitUntil: "networkidle" }); + await m.s.waitForTimeout(1800); + beiModi = await m.s.evaluate(() => { + const sp = document.querySelector(".spalte--bewerbung"); + if (!sp) return { spalte: false }; + return { + spalte: true, + karten: sp.querySelectorAll(".karte--bewerbung").length, + knoepfe: sp.querySelectorAll(".karte--bewerbung button").length, + warte: !!sp.querySelector(".karte__warte"), + titel: sp.querySelector(".karte__titel")?.textContent || "", + wort: sp.querySelector(".karte__bwort")?.textContent || "", + }; + }); + } finally { await m.ctx.close(); } + + ok(beworben?.code === 201, + `ein Modi bewirbt sich (HTTP ${beworben?.code} ${beworben?.fehler || ""})`); + ok(beiModi?.spalte === true && beiModi.karten === 1, + `er sieht sie im Aufgabenbrett in einer eigenen Spalte (${beiModi?.karten})`); + ok(beiModi?.titel === beworben?.titel, + `mit dem TITEL der Aufgabe, nicht dem Schluessel ("${beiModi?.titel}")`); + ok((beiModi?.wort || "").includes("Freitag"), + `und mit seinem eigenen Satz dazu ("${beiModi?.wort}")`); + ok(beiModi?.knoepfe === 0 && beiModi?.warte === true, + `Gegenprobe: er bekommt KEINE Entscheidungsknoepfe (${beiModi?.knoepfe}), ` + + `sondern „wartet auf Antwort" (${beiModi?.warte})`); + + /* UND DER, DER ENTSCHEIDET. */ + const e2 = await seiteAls("admin", "CODE-DOGI-0001", 1400); + let beiDogi = null; + try { + await e2.s.goto(`${SEITE}/workspace/aufgaben.html`, { waitUntil: "networkidle" }); + await e2.s.waitForTimeout(1800); + beiDogi = await e2.s.evaluate(() => { + const sp = document.querySelector(".spalte--bewerbung"); + if (!sp) return { spalte: false }; + return { + spalte: true, + karten: sp.querySelectorAll(".karte--bewerbung").length, + knoepfe: [...sp.querySelectorAll(".karte--bewerbung button")] + .map((b) => b.textContent.trim()), + /* Die Spalte muss die ERSTE sein: Was auf eine Antwort + wartet, gehoert an die Stelle, die man zuerst ansieht. */ + erste: document.querySelector("#brett .spalte")?.classList + .contains("spalte--bewerbung"), + wer: sp.querySelector(".karte__wer")?.textContent || "", + }; + }); + } finally { await e2.ctx.close(); } + + ok(beiDogi?.spalte === true && beiDogi.karten === 1, + `DogFather sieht dieselbe Bewerbung (${beiDogi?.karten})`); + ok(beiDogi?.erste === true, + `und die Spalte steht ganz vorn (${beiDogi?.erste})`); + ok(JSON.stringify(beiDogi?.knoepfe) === JSON.stringify(["Annehmen", "Ablehnen"]), + `mit beiden Knoepfen (${JSON.stringify(beiDogi?.knoepfe)})`); + ok((beiDogi?.wer || "").includes("möchte das übernehmen"), + `und dem Namen dessen, der wartet ("${beiDogi?.wer}")`); + } + ok(meckern.length === 0, meckern.length ? `der Browser meldet: ${meckern.slice(0, 3).join(" | ")}` : "kein Absturz und keine Fehlermeldung im Browser"); diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index 42655be7..41cf40ae 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -1542,12 +1542,30 @@ function vorlagenBewerbungenFuer(person) { LEFT JOIN personen p ON p.id = b.person_id WHERE b.zustand = 'beworben' ${darf ? "" : "AND b.person_id = ?"} ORDER BY b.id`).all(...(darf ? [] : [person.id])); - return zeilen.map((z) => ({ - id: z.id, vorlage: z.vorlage, kategorie: z.kategorie, - person_id: z.person_id, person_name: z.person_name || "Jemand", - text: z.text || null, erstellt: z.erstellt, - selbst: z.person_id === person.id, - })); + return zeilen.map((z) => { + /* DER TITEL KOMMT MIT (25.09.2026). + + Bis heute reichte der Schluessel: Die Bewerbung stand auf dem + Vorlagenbrett AN der Karte, und die Karte trug den Titel. Seit + sie auch im Aufgabenbrett steht (Filipes Wunsch, eigene + Kategorie), gibt es dort keine Karte daneben -- und ein + Schluessel wie "regeln_lesen" ist kein Satz, den jemand liest. + + NACHGESCHLAGEN, NICHT MITGESPEICHERT: Der Titel gehoert dem + Katalog. Wuerde er in der Bewerbungszeile stehen, haette man + zwei Wahrheiten, und die aeltere gewinnt still, sobald jemand + eine Vorlage umbenennt. */ + const v = vorlageZuSchluessel(z.vorlage); + return { + id: z.id, vorlage: z.vorlage, kategorie: z.kategorie, + titel: v?.titel || z.vorlage, + text_vorlage: v?.text || null, + tage: v?.tage ?? null, + person_id: z.person_id, person_name: z.person_name || "Jemand", + text: z.text || null, erstellt: z.erstellt, + selbst: z.person_id === person.id, + }; + }); } /* ===================================================================== @@ -1589,6 +1607,39 @@ function vorlageZuSchluessel(schluessel) { return MODI_AUFGABEN_FLACH.find((v) => v.schluessel === schluessel) || null; } +/* ==== NUR DIE BEWERBUNGEN, OHNE DEN GANZEN KATALOG (25.09.2026) ==== + + Filipe: „ich will in dieser seite auch eine eigene kategorie fuer + die aufgaben wo die modis sich selbst bewerben. ich will dass man + die getrennt sieht." + + Das Aufgabenbrett braucht die Bewerbungen -- aber nicht den Katalog + mit allen Vorlagen, der an `/workspace/api/vorlagen` haengt. Ein + eigener, kleiner Weg statt eines Mitschleppens: Das Brett laedt bei + jedem Zeichnen, und der Katalog ist um ein Vielfaches groesser als + die Handvoll Bewerbungen. + + WER WAS SIEHT, ENTSCHEIDET UNVERAENDERT `vorlagenBewerbungenFuer`: + Wer entscheidet, sieht alle offenen; alle anderen nur die eigenen. + Diese Route waehlt nichts selbst aus -- sonst gaebe es eine zweite + Stelle, an der die Sichtbarkeit falsch werden kann. */ +vorlagenRouter.get("/workspace/api/vorlagen/bewerbungen", (req, res) => { + try { + /* WER ENTSCHEIDET, KOMMT MIT. Die Oberflaeche soll die zwei + Knoepfe nur dort zeigen, wo der Server sie auch annimmt -- und + sie darf es nicht am Rollennamen ausrechnen: `assets/js` bekommt + jeder, der die Seite oeffnet, und ein Rollenvergleich im Browser + ist in diesem Haus diese Woche dreimal veraltet. */ + res.json({ + bewerbungen: vorlagenBewerbungenFuer(req.person), + darf_entscheiden: entscheidetUeberAufgaben(req.person), + }); + } catch (f) { + console.error("[vorlagen] Bewerbungen lesen:", f?.message); + res.status(503).json({ fehler: "nicht_verfuegbar" }); + } +}); + vorlagenRouter.post("/workspace/api/vorlagen/bewerben", gleicheHerkunft, express.json({ limit: "4kb" }), (req, res) => { try { diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 60292bd2..9e64136a 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +