diff --git a/server/pruef-call-kategorien.mjs b/server/pruef-call-kategorien.mjs index 15b77fee..8b682299 100644 --- a/server/pruef-call-kategorien.mjs +++ b/server/pruef-call-kategorien.mjs @@ -340,6 +340,73 @@ melde("\n=== Gegenprobe ==="); } await ctx.close(); +/* ======================================================================= + 4. WIEDERHOLUNGEN STEHEN IN IHRER EIGENEN GRUPPE -- UND NUR DER + LAUFENDE MONAT (07.09.2026) + + Filipe: "ich will da auch noch eine kategorie fuer automatische + wiederholungen. die sollen dann auch nur fuer den monat selbst + angezeigt werden und nicht monate im voraus." + + Der Nachfueller haelt einen Horizont von 180 Tagen gefuellt; ein + woechentlicher Termin ergibt darin sechsundzwanzig Zeilen, und alle + standen unter "Steht an". Geprueft werden deshalb DREI Aussagen -- + eine allein waere auch erfuellt, wenn schlicht nichts ankaeme: + + 1. die Wiederholung dieses Monats steht in `serien` + 2. die des naechsten Monats NICHT + 3. und in `anstehend` steht keine von beiden + ======================================================================= */ +melde("\n=== Wiederholungen: eigene Gruppe, nur dieser Monat ==="); +{ + const inTagen = (n) => { + const t = new Date(Date.now() + n * 86400000); + const p = (x) => String(x).padStart(2, "0"); + return `${t.getFullYear()}-${p(t.getMonth() + 1)}-${p(t.getDate())}T18:00`; + }; + const d2 = new DatabaseSync(process.env.WORKSPACE_DB); + d2.prepare(`INSERT INTO termin_serien (titel, art, takt, start_tag, uhrzeit, dauer_min, aktiv, erstellt, erstellt_von) + VALUES ('Community-Talk','call','woechentlich',?,'18:00',30,1,?,1)`) + .run(inTagen(0).slice(0, 10), new Date().toISOString()); + const serieId = d2.prepare("SELECT last_insert_rowid() AS id").get().id; + const rein = d2.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, serie_id, erstellt, erstellt_von) + VALUES (?,?,?,?,?,?,1)`); + /* Zwei Auspraegungen derselben Serie: eine in vier Tagen (also mit + grosser Wahrscheinlichkeit dieser Monat) und eine in sechzig Tagen + (sicher ein anderer). Der Monatsschnitt wird aus dem Datumstext + gerechnet, nicht aus einer Zeitzone. */ + const nah = inTagen(4), fern = inTagen(60); + rein.run("Serie dieser Monat", "call", nah, 30, serieId, new Date().toISOString()); + rein.run("Serie spaeter", "call", fern, 30, serieId, new Date().toISOString()); + d2.close(); + + /* OHNE BROWSER. Die Seite ist an dieser Stelle laengst geschlossen + (der Block darueber macht seinen Kontext zu), und `page.evaluate` + lief deshalb in "Target page has been closed". Diese Aussage + braucht ohnehin keinen Browser: Sie betrifft die Schnittstelle. */ + const anmeldung = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }), + }); + const keks = (anmeldung.headers.getSetCookie?.() || []) + .map((z) => z.split(";")[0]).join("; "); + ok(!!keks, "als DogFather angemeldet"); + const antwort = await (await fetch(BASIS + "/workspace/api/calls", + { headers: { cookie: keks } })).json(); + const serien = (antwort.serien || []).map((c) => c.titel); + const anstehend = (antwort.anstehend || []).map((c) => c.titel); + + const gleicherMonat = nah.slice(0, 7) === fern.slice(0, 7); + ok(!gleicherMonat, + `die beiden Auspraegungen liegen in verschiedenen Monaten (${nah.slice(0, 7)} / ${fern.slice(0, 7)})`); + ok(serien.includes("Serie dieser Monat"), + `die Wiederholung dieses Monats steht in "Laeuft automatisch" (${serien.join(", ") || "leer"})`); + ok(!serien.includes("Serie spaeter"), + "die des naechsten Monats nicht -- keine Monate im Voraus"); + ok(!anstehend.includes("Serie dieser Monat") && !anstehend.includes("Serie spaeter"), + `und unter "Steht an" steht keine von beiden (${anstehend.length} Eintraege)`); +} + await browser.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } diff --git a/server/workspace-calls.js b/server/workspace-calls.js index 57a47abc..772c8cea 100644 --- a/server/workspace-calls.js +++ b/server/workspace-calls.js @@ -69,7 +69,7 @@ callRouter.use("/workspace/api/calls", angemeldet); const SPALTEN = ` t.id, t.titel, t.beschreibung, t.art, t.beginn, t.dauer_min, t.ort, - t.creator_id, t.teilnehmer_id, t.erledigt, + t.creator_id, t.teilnehmer_id, t.erledigt, t.serie_id, pc.name AS creator_name, pt.name AS teilnehmer_name, pr.id AS protokoll_id, pr.punkte, pr.entscheidungen, pr.erstellt AS protokoll_erstellt, pr.naechster_termin_id, @@ -133,8 +133,34 @@ callRouter.get("/workspace/api/calls", (req, res) => { call_link: einstellung("call_link", "") || "", /* Die Reihenfolge der Gruppen ist die Dringlichkeit: Was fehlt, steht oben -- nicht was als Naechstes kommt. */ + /* WIEDERHOLUNGEN BEKOMMEN EINE EIGENE GRUPPE (07.09.2026). + + Filipe: "ich will da auch noch eine kategorie fuer automatische + wiederholungen. die sollen dann auch nur fuer den monat selbst + angezeigt werden und nicht monate im voraus." + + DAS PROBLEM WAR ECHT UND GROSS: Der Nachfueller haelt einen + Horizont von 180 Tagen gefuellt (siehe workspace-serien.js). + Ein woechentlicher Community-Talk ergibt darin sechsundzwanzig + Zeilen -- und alle standen unter "Steht an". Auf Filipes Bild + waren es siebenundzwanzig Karten, fast alle derselbe Termin. + Die Liste war damit unbrauchbar fuer das, wofuer sie da ist: + zu sehen, was WIRKLICH ansteht. + + Jetzt zwei getrennte Fragen: + STEHT AN was einmalig bevorsteht + LAEUFT AUTOMATISCH was von allein wiederkommt, und davon + nur der LAUFENDE MONAT + + Der Monatsschnitt ist die eigentliche Antwort auf "nicht Monate + im Voraus": Eine Wiederholung im November sagt einem heute + nichts, was man nicht schon weiss. Gerechnet wird auf dem + reinen Datumstext (`beginn` beginnt mit JJJJ-MM) -- kein + `new Date`, keine Zeitzone, kein Nachtfehler. */ offen: alle.filter((c) => c.beginn <= grenze && !c.protokoll_id).reverse(), - anstehend: alle.filter((c) => c.beginn > grenze).reverse(), + anstehend: alle.filter((c) => c.beginn > grenze && !c.serie_id).reverse(), + serien: alle.filter((c) => c.beginn > grenze && c.serie_id + && String(c.beginn).slice(0, 7) === grenze.slice(0, 7)).reverse(), erledigt: alle.filter((c) => c.beginn <= grenze && c.protokoll_id), }); } catch (fehler) { diff --git a/workspace/assets/js/calls.js b/workspace/assets/js/calls.js index c189051d..2ad4ef0e 100644 --- a/workspace/assets/js/calls.js +++ b/workspace/assets/js/calls.js @@ -403,6 +403,8 @@ { schluessel: 'offen', titel: 'Protokoll fehlt', frage: 'Diese Gespräche sind vorbei, aber noch nicht festgehalten.' }, { schluessel: 'anstehend', titel: 'Steht an', frage: null }, + { schluessel: 'serien', titel: 'Läuft automatisch', + frage: 'Wiederholungen dieses Monats. Was danach kommt, steht im Kalender.' }, { schluessel: 'erledigt', titel: 'Festgehalten', frage: null }, ]; @@ -460,7 +462,8 @@ ziel.textContent = ''; ziel.setAttribute('aria-busy', 'false'); - if (!daten.offen.length && !daten.anstehend.length && !daten.erledigt.length) { + if (!daten.offen.length && !daten.anstehend.length + && !(daten.serien || []).length && !daten.erledigt.length) { const leer = el('div', 'leer-hinweis'); leer.append(document.createTextNode('Noch keine Calls. Termine mit der Art „Call" oder „Review" ')); const a = el('a', 'call__verweis', 'im Kalender'); @@ -493,7 +496,11 @@ ziel.className = 'call-brett'; for (const g of GRUPPEN) { - const liste = daten[g.schluessel]; + /* `|| []` fuer den Fall, dass ein Server ohne die neue Gruppe + antwortet -- etwa wenn die Seite noch aus dem Zwischenspeicher + kommt, der Dienst aber schon neu ist. Eine Oberflaeche, die + daran abstuerzt, waere der schlechtere Ausgang. */ + const liste = daten[g.schluessel] || []; if (!liste.length) continue; const s = el('section', 'gruppe'); s.dataset.gruppe = g.schluessel; diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 9a2b0729..7113543e 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -9,10 +9,10 @@ - - - - + + + +
@@ -212,10 +212,10 @@ - - - - - + + + + +