diff --git a/pruef-personen.png b/pruef-personen.png index 2f6ef233..b85ac91c 100644 Binary files a/pruef-personen.png and b/pruef-personen.png differ diff --git a/server/pruef-personen-liste.mjs b/server/pruef-personen-liste.mjs index 5b3250ff..1f374562 100644 --- a/server/pruef-personen-liste.mjs +++ b/server/pruef-personen-liste.mjs @@ -72,6 +72,14 @@ async function seiteFuer(kontext) { const lesen = (seite) => seite.evaluate(() => ({ namen: [...document.querySelectorAll(".person__name")].map((n) => n.textContent.replace(" (du)", "")), + /* Die Abschnitte in der Reihenfolge, in der sie tatsächlich stehen -- + nicht in der, in der sie gemeint waren. */ + abschnitte: [...document.querySelectorAll(".gruppe")].map((g) => ({ + name: g.querySelector(".gruppe__name")?.textContent, + zahl: g.querySelector(".gruppe__zahl")?.textContent, + unter: g.querySelector(".gruppe__unter")?.textContent, + personen: [...g.querySelectorAll(".person__name")].map((n) => n.textContent.replace(" (du)", "")), + })), knopfDa: !document.getElementById("rest-schalter")?.hidden, knopfText: document.getElementById("rest-schalter-text")?.textContent, auf: document.getElementById("rest-schalter")?.getAttribute("aria-expanded"), @@ -90,6 +98,8 @@ console.log("\n1) Standardmäßig nur DogFather"); console.log(" sichtbar:", r.namen.join(", ")); console.log(" Knopf: ", r.knopfText); ok(r.namen.length === 2, `zwei Personen sichtbar (${r.namen.length})`); + ok(r.abschnitte.length === 1 && r.abschnitte[0].name === "DogFather", + `zugeklappt steht nur der DogFather-Abschnitt da (${r.abschnitte.map((a) => a.name).join(", ") || "keiner"})`); ok(r.namen.includes("Dogfather") && r.namen.includes("VanVan"), `Dogfather und VanVan (${r.namen.join(", ")})`); ok(!r.namen.includes("Tili"), "kein Creator"); @@ -113,6 +123,28 @@ console.log("\n2) Auf Knopfdruck kommen alle dazu"); ok(/Nur DogFather zeigen/.test(r.knopfText || ""), `Knopf bietet das Zuklappen an (${r.knopfText})`); ok(r.auf === "true", `aria-expanded=true (${r.auf})`); ok(r.ueber <= 0, `nichts steht über (${r.ueber}px)`); + + console.log(" Abschnitte:", r.abschnitte.map((a) => `${a.name}(${a.zahl})`).join(" → ")); + ok(r.abschnitte.length === 4, `vier Abschnitte (${r.abschnitte.length})`); + ok(r.abschnitte.map((a) => a.name).join(",") === "DogFather,Manager,Scout,Creator", + `Reihenfolge wie auf der Zugangsseite (${r.abschnitte.map((a) => a.name).join(", ")})`); + ok(r.abschnitte.map((a) => a.zahl).join(",") === "2,1,2,2", + `Anzahl je Rolle (${r.abschnitte.map((a) => a.zahl).join(", ")})`); + + /* Der eigentliche Fehler von vorher: Der GESPERRTE Manager stand ganz + unten unter den Creators, weil "aktiv" vor der Rolle sortierte. */ + const manager = r.abschnitte.find((a) => a.name === "Manager"); + ok(manager?.personen.includes("BanaStift"), + `der gesperrte Manager steht bei den Managern (${manager?.personen.join(", ")})`); + const creator = r.abschnitte.find((a) => a.name === "Creator"); + ok(!creator?.personen.includes("BanaStift"), "und nicht mehr bei den Creators"); + ok(creator?.personen.join(",") === "SpongBobSchwammKopf,Tili", + `innerhalb der Rolle nach Namen (${creator?.personen.join(", ")})`); + + /* Dieselben Zusatztexte wie auf der Zugangsseite -- wer sich eben + angemeldet hat, findet hier dieselbe Sprache wieder. */ + ok(r.abschnitte[2]?.unter === "Eigene Pipeline & Kontakte", + `Zusatztext wörtlich von der Zugangsseite ("${r.abschnitte[2]?.unter}")`); } console.log("\n3) Der Zustand überlebt das Neuladen"); diff --git a/server/workspace-personen.js b/server/workspace-personen.js index 9c1520d4..40580840 100644 --- a/server/workspace-personen.js +++ b/server/workspace-personen.js @@ -73,6 +73,19 @@ function nurDogFatherBeiLeitung(req, res, next) { /* ---------- Übersicht --------------------------------------------------- */ +/* Sortierung: Die ROLLE zuerst, dann erst der Zustand. + + Vorher stand "p.aktiv DESC" ganz vorn -- dadurch wanderte jede + gesperrte Person ans Ende der GESAMTEN Liste, quer durch alle Rollen. + Ein gesperrter Manager stand also unter den Creators, und die + Reihenfolge DogFather-Manager-Scout-Creator, die ueberall sonst gilt, + war ausgerechnet auf der Personenseite aufgehoben. + + Jetzt: Rolle, dann Gesperrtes ans Ende SEINER Rolle, dann Name. + + (Der Kommentar steht bewusst hier und nicht in der Abfrage: Er enthaelt + Rueckwaerts-Anfuehrungszeichen, und die beenden mitten in einer + Zeichenkette genau diese -- der Server startete danach nicht mehr.) */ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => { try { res.json({ @@ -87,7 +100,7 @@ personenRouter.get("/workspace/api/verwaltung/personen", (req, res) => { (SELECT COUNT(*) FROM betreuung y WHERE y.betreuer_id = p.id) AS betreut_anzahl FROM personen p LEFT JOIN betreuung b ON b.creator_id = p.id - ORDER BY p.aktiv DESC, ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.name`).all(), + ORDER BY ${ROLLEN_SORTIERUNG.replace("rolle", "p.rolle")}, p.aktiv DESC, p.name`).all(), /* Wer ueberhaupt als zustaendig eingetragen werden kann. */ betreuer: db().prepare( "SELECT id, name FROM personen WHERE rolle = 'scout' AND aktiv = 1 ORDER BY name").all(), diff --git a/workspace/assets/css/personen.css b/workspace/assets/css/personen.css index 605794ad..ecc116e9 100644 --- a/workspace/assets/css/personen.css +++ b/workspace/assets/css/personen.css @@ -169,3 +169,16 @@ "Person anlegen" – die Hauptsache gehört ganz nach außen, sonst springt der Blick beim Suchen daran vorbei. */ .kopf-knoepfe { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } + +/* Die Anzahl neben der Rollen-Überschrift. Sitzt zwischen Name und + Erklärung, damit sie zur Überschrift gehört und nicht zum Zusatztext. */ +.gruppe__zahl { + min-width: 20px; padding: 1px 7px; text-align: center; + font-size: .72rem; font-weight: 700; color: var(--text-still); + background: rgba(255, 255, 255, .06); border-radius: 999px; + font-variant-numeric: tabular-nums; +} + +/* In der Personenliste liegen die Karten direkt untereinander; der + Abstand zur nächsten Rolle kommt von .gruppe selbst. */ +.gruppe .person:last-child { margin-bottom: 0; } diff --git a/workspace/assets/js/personen.js b/workspace/assets/js/personen.js index 8884fd20..31c90d61 100644 --- a/workspace/assets/js/personen.js +++ b/workspace/assets/js/personen.js @@ -243,13 +243,51 @@ let alleZeigen = false; try { alleZeigen = localStorage.getItem(SPEICHER) === 'ja'; } catch { /* egal */ } + /* Jede Rolle bekommt einen eigenen Abschnitt, in der Reihenfolge der + ZUGANGSSEITE: DogFather, Manager, Scout, Creator. Die Zusatztexte + stehen dort wörtlich genauso -- wer sich eben angemeldet hat, findet + hier dieselbe Sprache wieder. + + Eine Rolle ohne Personen wird weggelassen. Eine leere Überschrift + ist kein Ordnungsmerkmal, sondern eine Lücke. */ + const ROLLEN_ABSCHNITTE = [ + { rolle: 'admin', name: 'DogFather', unter: 'Gesamtübersicht & Freigaben' }, + { rolle: 'manager', name: 'Manager', unter: 'Betreuung & Freigaben' }, + { rolle: 'scout', name: 'Scout', unter: 'Eigene Pipeline & Kontakte' }, + { rolle: 'creator', name: 'Creator', unter: 'Eigener Bereich & Fortschritt' }, + ]; + function listeZeigen() { const ziel = $('liste'); ziel.textContent = ''; const oben = alle.filter((p) => p.rolle === 'admin'); const rest = alle.filter((p) => p.rolle !== 'admin'); - for (const p of (alleZeigen ? alle : oben)) ziel.append(zeile(p)); + const sichtbar = alleZeigen ? alle : oben; + + for (const abschnitt of ROLLEN_ABSCHNITTE) { + const drin = sichtbar.filter((p) => p.rolle === abschnitt.rolle); + if (!drin.length) continue; + + const block = document.createElement('section'); + block.className = 'gruppe'; + const kopf = document.createElement('div'); + kopf.className = 'gruppe__kopf'; + const h = document.createElement('h2'); + h.className = 'gruppe__name'; + h.textContent = abschnitt.name; + const zahl = document.createElement('span'); + zahl.className = 'gruppe__zahl'; + zahl.textContent = String(drin.length); + const unter = document.createElement('span'); + unter.className = 'gruppe__unter'; + unter.textContent = abschnitt.unter; + kopf.append(h, zahl, unter); + block.append(kopf); + + for (const p of drin) block.append(zeile(p)); + ziel.append(block); + } const schalter = $('rest-schalter'); schalter.hidden = rest.length === 0; diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 8be15cf4..5618ca30 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -7,9 +7,9 @@ - - - + + +
@@ -134,8 +134,8 @@ - - - + + +