feat: Scholen, Klassen en Gebruikers samengevoegd tot één menu-item "School" (v0.4.09-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 40s

- ADMIN_SECTIONS: de drie losse tabbladen zijn samengevoegd tot 1 sectie
  ("school"); zoeken op een school-, klas- of gebruikersnaam werkt nog
  hetzelfde, navigeert nu alleen naar de ene samengevoegde sectie
- nieuwe renderSchoolPanel() zet de drie bestaande, ongewijzigde
  deelpanelen (renderScholenPanel/renderKlassenPanel/renderGebruikersPanel)
  na elkaar, elk met een eigen kop - geen van de drie is herschreven, alleen
  samengesteld, om het risico op regressies te beperken
- rolgedrag blijft exact zoals het was (nu allemaal binnen die ene sectie):
  systeemmanager ziet Scholen+Klassen+Gebruikers en de schoolkiezer,
  schoolbeheerder ziet Klassen+Gebruikers voor de eigen school (geen
  schoolkiezer, geen Scholen-blok), groepsleiding ziet alleen Gebruikers
  (geen Klassen-blok) en daarbinnen alleen de leerling-gerelateerde knoppen
- geverifieerd in een echte headless Chromium voor alle drie rollen: juiste
  secties, juiste knoppen, juiste zichtbaarheid van de schoolkiezer

Backup van de oude driedelige UI staat als git-tag
ui-before-merged-beheer-v0.4.08 (commit e34bda5).
This commit is contained in:
Ramon 2026-07-18 05:04:43 +02:00
parent e34bda5251
commit 56125c6968
4 changed files with 70 additions and 32 deletions

View file

@ -1 +1 @@
0.4.08-beta 0.4.09-beta

View file

@ -1265,6 +1265,9 @@
.am-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;} .am-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;}
.am-head h2{margin:0; font-size:20px;} .am-head h2{margin:0; font-size:20px;}
.am-h3{margin:14px 0 6px; font-size:14px; color:var(--muted); text-transform:uppercase; letter-spacing:.5px;} .am-h3{margin:14px 0 6px; font-size:14px; color:var(--muted); text-transform:uppercase; letter-spacing:.5px;}
/* School/Klassen/Gebruikers staan nu na elkaar in één tabblad i.p.v. drie
losse - wat lucht onder elk blok zodat de volgende kop niet knelt */
.am-section{margin-bottom:10px;}
.am-msg{ .am-msg{
min-height:20px; font-size:13.5px; font-weight:700; color:var(--green); margin:4px 0; min-height:20px; font-size:13.5px; font-weight:700; color:var(--green); margin:4px 0;
display:flex; align-items:center; gap:8px; flex-wrap:wrap; display:flex; align-items:center; gap:8px; flex-wrap:wrap;

View file

@ -18,7 +18,7 @@
meteen weer verdwijnen na een reload() */ meteen weer verdwijnen na een reload() */
let resetResults = null; let resetResults = null;
/* actief tabblad, blijft behouden over her-renders heen (na elke actie) */ /* actief tabblad, blijft behouden over her-renders heen (na elke actie) */
let adminTab = "gebruikers"; let adminTab = "school";
/* live zoekfilter voor de gebruikerslijst, blijft ook behouden over her-renders */ /* live zoekfilter voor de gebruikerslijst, blijft ook behouden over her-renders */
let userFilter = ""; let userFilter = "";
/* uitgeklapte leerling-groepen en gebruikersrijen (standaard alles dicht /* uitgeklapte leerling-groepen en gebruikersrijen (standaard alles dicht
@ -176,38 +176,41 @@
/* ---- beheer-secties in het instellingen-register (settings.js) ------------ /* ---- beheer-secties in het instellingen-register (settings.js) ------------
Elke sectie meldt zichzelf aan met rol-zichtbaarheid, een activate() en Elke sectie meldt zichzelf aan met rol-zichtbaarheid, een activate() en
zoekgegevens; de schil bouwt daar navigatie + zoeken uit. Per rol alleen zoekgegevens; de schil bouwt daar navigatie + zoeken uit. Scholen, klassen
de essentie: groepsleiding ziet gebruikers/toewijzingen/voortgang, en gebruikers zijn hier bewust ÉÉN sectie ("school"): voor een
schoolbeheer ook klassen, de systeemmanager ook scholen en opslag. */ schoolbeheerder/leerkracht is dat toch al hun ene eigen school, en een
systeemmanager ziet binnen die ene sectie alsnog alles (zie
renderSchoolPanel hieronder, dat de drie oorspronkelijke deelpanelen
ongewijzigd naast elkaar zet). Per rol alleen de essentie: groepsleiding
ziet hierbinnen alleen gebruikers, schoolbeheer ook klassen, de
systeemmanager ook scholen (en daarnaast losstaand nog opslag). */
const ADMIN_SECTIONS = [ const ADMIN_SECTIONS = [
{ tab:"gebruikers", icon:"👥", group:"school", order:10, labelKey:"amUsers", subKey:"stUsersSub", { tab:"school", icon:"🏫", group:"school", order:10, labelKey:"amSchoolMenu", subKey:"stSchoolMenuSub",
show:()=>true, show:()=>true,
keywords:()=>[T("amPupils"), T("roleTeacher"), T("roleAdmin"), T("amParents"), T("amLinkRequests"), keywords:()=>[T("amPupils"), T("roleTeacher"), T("roleAdmin"), T("amParents"), T("amLinkRequests"),
T("amNewPw"), T("amParentCode"), "leerling","pupil","ouder","parent","wachtwoord","password", T("amNewPw"), T("amParentCode"), "leerling","pupil","ouder","parent","wachtwoord","password",
"koppelcode","aanmelding","enrolment"], "koppelcode","aanmelding","enrolment",
search:q=>USERS T("amNewClass"), T("amResetClass"), "klas","class","groep","group",
T("amNewSchool"), T("amNewSuper"), "school","systeemmanager","system manager"],
search:q=>[
...USERS
.filter(u=>`${u.displayName} ${u.username}`.toLowerCase().includes(q)) .filter(u=>`${u.displayName} ${u.username}`.toLowerCase().includes(q))
.map(u=>({ icon: u.role==="pupil" ? "🧒" : u.role==="parent" ? "👪" : "🧑‍🏫", .map(u=>({ icon: u.role==="pupil" ? "🧒" : u.role==="parent" ? "👪" : "🧑‍🏫",
label:u.displayName, sub:T(roleKey(u.role)), label:u.displayName, sub:T(roleKey(u.role)),
go:()=>goSettingsSection("gebruikers", { filter:u.displayName }) })) }, go:()=>goSettingsSection("school", { filter:u.displayName }) })),
{ tab:"klassen", icon:"🏷", group:"school", order:11, labelKey:"amClasses", subKey:"stKlassenSub", ...CLASSES
show:()=>can("classes.manage"),
keywords:()=>[T("amNewClass"), T("amResetClass"), "klas","class","groep","group"],
search:q=>CLASSES
.filter(c=>c.name.toLowerCase().includes(q)) .filter(c=>c.name.toLowerCase().includes(q))
.map(c=>({ icon:"🏷", label:c.name, sub:T("amClasses"), go:()=>goSettingsSection("klassen") })) }, .map(c=>({ icon:"🏷", label:c.name, sub:T("amClasses"), go:()=>goSettingsSection("school") })),
...SCHOOLS
.filter(s=>s.name.toLowerCase().includes(q))
.map(s=>({ icon:"🏫", label:s.name, sub:T("amSchools"), go:()=>goSettingsSection("school") })),
] },
{ tab:"toewijzingen", icon:"🎯", group:"school", order:12, labelKey:"amAssignments", subKey:"stToewijzSub", { tab:"toewijzingen", icon:"🎯", group:"school", order:12, labelKey:"amAssignments", subKey:"stToewijzSub",
show:()=>can("assignments.manage"), show:()=>can("assignments.manage"),
keywords:()=>[T("amModeWatch"), T("amModeWork"), "bord","board","delen","share","toewijzen","assign","widget"] }, keywords:()=>[T("amModeWatch"), T("amModeWork"), "bord","board","delen","share","toewijzen","assign","widget"] },
{ tab:"voortgang", icon:"📈", group:"school", order:13, labelKey:"amProgress", subKey:"stVoortgangSub", { tab:"voortgang", icon:"📈", group:"school", order:13, labelKey:"amProgress", subKey:"stVoortgangSub",
show:()=>can("progress.view"), show:()=>can("progress.view"),
keywords:()=>["voortgang","progress","score","resultaten","results","tijdlijn","timeline"] }, keywords:()=>["voortgang","progress","score","resultaten","results","tijdlijn","timeline"] },
{ tab:"scholen", icon:"🏫", group:"system", order:30, labelKey:"amSchools", subKey:"stScholenSub",
show:()=>currentUser.role==="super",
keywords:()=>[T("amNewSchool"), T("amNewSuper"), "school","systeemmanager","system manager"],
search:q=>SCHOOLS
.filter(s=>s.name.toLowerCase().includes(q))
.map(s=>({ icon:"🏫", label:s.name, sub:T("amSchools"), go:()=>goSettingsSection("scholen") })) },
{ tab:"opslag", icon:"🖼", group:"system", order:32, labelKey:"stOpslag", subKey:"stOpslagSub", { tab:"opslag", icon:"🖼", group:"system", order:32, labelKey:"stOpslag", subKey:"stOpslagSub",
show:()=>currentUser.role==="super", show:()=>currentUser.role==="super",
keywords:()=>["opslag","storage","afbeeldingen","images","quota"] }, keywords:()=>["opslag","storage","afbeeldingen","images","quota"] },
@ -238,11 +241,11 @@
visible:()=>!!currentUser && can("admin.access") && d.show(), visible:()=>!!currentUser && can("admin.access") && d.show(),
async activate(payload){ async activate(payload){
await ensureLoaded(); await ensureLoaded();
if(d.tab==="gebruikers" && payload && payload.filter !== undefined) userFilter = payload.filter; if(d.tab==="school" && payload && payload.filter !== undefined) userFilter = payload.filter;
showAdminHost(); showAdminHost();
switchAdminTab(d.tab); switchAdminTab(d.tab);
if(d.tab==="gebruikers" && payload && payload.filter !== undefined){ if(d.tab==="school" && payload && payload.filter !== undefined){
const panel = modal.querySelector('[data-panel="gebruikers"]'); const panel = modal.querySelector('[data-panel="school"]');
const inp = panel && panel.querySelector(".am-filter"); const inp = panel && panel.querySelector(".am-filter");
if(inp){ inp.value = userFilter; applyUserFilter(panel); } if(inp){ inp.value = userFilter; applyUserFilter(panel); }
} }
@ -1406,13 +1409,43 @@
syncSelection(); /* selectie valideren + detailpaneel vullen */ syncSelection(); /* selectie valideren + detailpaneel vullen */
} }
/* "School": scholen, klassen en gebruikers samen in één tabblad i.p.v. drie
losse. De drie oorspronkelijke deelpanelen blijven ongewijzigd (elk met
hun eigen, al-bestaande rolcontrole) - dit stelt ze alleen na elkaar
samen, elk met een eigen kop, zodat de pagina scanbaar blijft:
- Scholen: alleen systeemmanager (die scholen kan aanmaken/beheren);
onafhankelijk van de gekozen school (was al zo).
- Klassen: alleen wie classes.manage heeft (super/admin) - een
leerkracht ziet dit blok dus helemaal niet.
- Gebruikers: altijd. */
function renderSchoolPanel(panel){
if(currentUser.role==="super"){
panel.appendChild(h("h3","am-h3", T("amSchools")));
const sub = h("div","am-section");
renderScholenPanel(sub);
panel.appendChild(sub);
}
if(can("classes.manage")){
panel.appendChild(h("h3","am-h3", T("amClasses")));
const sub = h("div","am-section");
renderKlassenPanel(sub);
panel.appendChild(sub);
}
panel.appendChild(h("h3","am-h3", T("amUsers")));
const sub = h("div","am-section");
renderGebruikersPanel(sub);
panel.appendChild(sub);
}
function switchAdminTab(tab){ function switchAdminTab(tab){
adminTab = tab; adminTab = tab;
modal.querySelectorAll(".am-tabpanel").forEach(p=>{ p.style.display = p.dataset.panel===tab ? "block" : "none"; }); modal.querySelectorAll(".am-tabpanel").forEach(p=>{ p.style.display = p.dataset.panel===tab ? "block" : "none"; });
/* het Systeem-tabblad is school-onafhankelijk: daar is de schoolkiezer /* Opslag is school-onafhankelijk: daar is de schoolkiezer alleen maar
alleen maar verwarrend, dus die verbergen we er */ verwarrend, dus die verbergen we er. "school" toont 'm juist wél (het
Klassen/Gebruikers-deel daarbinnen hangt ervan af voor een
systeemmanager) - het Scholen-deel daarbinnen negeert selSchool toch al. */
const sb = modal.querySelector(".am-schoolbar"); const sb = modal.querySelector(".am-schoolbar");
if(sb) sb.style.display = ["scholen","opslag"].includes(tab) ? "none" : ""; if(sb) sb.style.display = tab==="opslag" ? "none" : "";
} }
/* vaste contextbalk voor de systeemmanager: op elk beheer-tabblad direct /* vaste contextbalk voor de systeemmanager: op elk beheer-tabblad direct
@ -1442,7 +1475,7 @@
const tabDefs = adminTabDefs(); const tabDefs = adminTabDefs();
if(!tabDefs.some(([t])=>t===adminTab)) adminTab = tabDefs[0][0]; if(!tabDefs.some(([t])=>t===adminTab)) adminTab = tabDefs[0][0];
const panels = { scholen: renderScholenPanel, klassen: renderKlassenPanel, gebruikers: renderGebruikersPanel, toewijzingen: renderToewijzingenPanel, voortgang: renderVoortgangPanel, opslag: renderImageStoragePanel }; const panels = { school: renderSchoolPanel, toewijzingen: renderToewijzingenPanel, voortgang: renderVoortgangPanel, opslag: renderImageStoragePanel };
tabDefs.forEach(([t])=>{ tabDefs.forEach(([t])=>{
const panel = h("div","am-tabpanel"); const panel = h("div","am-tabpanel");
panel.dataset.panel = t; panel.dataset.panel = t;

View file

@ -2,7 +2,7 @@
"use strict"; "use strict";
/* version shown until /api/version resolves (or if the fetch fails, e.g. offline). /* version shown until /api/version resolves (or if the fetch fails, e.g. offline).
Kept in sync by hand with the VERSION file at the repo root on every release. */ Kept in sync by hand with the VERSION file at the repo root on every release. */
const VERSION = "0.4.08-beta"; const VERSION = "0.4.09-beta";
(function(){ (function(){
const tag = document.getElementById("verTag"); const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION; tag.textContent = "v"+VERSION;
@ -212,6 +212,7 @@ const I18N = {
stToewijzSub:"Bepaal wat een klas of leerling te zien krijgt.", stToewijzSub:"Bepaal wat een klas of leerling te zien krijgt.",
stVoortgangSub:"Volg per leerling de resultaten in de tijd.", stVoortgangSub:"Volg per leerling de resultaten in de tijd.",
stScholenSub:"Scholen en systeemmanagers, over alle scholen heen.", stScholenSub:"Scholen en systeemmanagers, over alle scholen heen.",
amSchoolMenu:"School", stSchoolMenuSub:"Scholen, klassen en gebruikers beheren.",
stOpslag:"Opslag", stOpslagSub:"Afbeeldingencatalogus en opslagruimte per school.", stOpslag:"Opslag", stOpslagSub:"Afbeeldingencatalogus en opslagruimte per school.",
stInhoud:"Inhoud", stInhoudSub:"Welkomstwoord, meldingen en widget-uitleg - direct aanpasbaar, zonder release.", stInhoud:"Inhoud", stInhoudSub:"Welkomstwoord, meldingen en widget-uitleg - direct aanpasbaar, zonder release.",
cmsWelcomeH:"Welkomstwoord (inlogscherm)", cmsWelcomeH:"Welkomstwoord (inlogscherm)",
@ -511,6 +512,7 @@ const I18N = {
stToewijzSub:"Choose what a class or pupil gets to see.", stToewijzSub:"Choose what a class or pupil gets to see.",
stVoortgangSub:"Follow each pupil's results over time.", stVoortgangSub:"Follow each pupil's results over time.",
stScholenSub:"Schools and system managers, across all schools.", stScholenSub:"Schools and system managers, across all schools.",
amSchoolMenu:"School", stSchoolMenuSub:"Manage schools, classes and users.",
stOpslag:"Storage", stOpslagSub:"Image catalogue and storage per school.", stOpslag:"Storage", stOpslagSub:"Image catalogue and storage per school.",
stInhoud:"Content", stInhoudSub:"Welcome text, announcements and widget descriptions - editable without a release.", stInhoud:"Content", stInhoudSub:"Welcome text, announcements and widget descriptions - editable without a release.",
cmsWelcomeH:"Welcome text (login screen)", cmsWelcomeH:"Welcome text (login screen)",