fix: gebruikers gegroepeerd per school in alle-scholen-weergave + zichtbare verwijderfouten (v0.3.94-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- In de "alle scholen"-weergave stonden gebruikers op rol gegroepeerd, waardoor je niet zag bij welke school ze horen (Ramon/Rubi/leerlingb1b leken los te staan). Ze worden nu per school gegroepeerd (inklapbaar), met systeemmanagers, ouders en losse accounts apart eronder - je ziet meteen wie bij De Ziep hoort - Bij één gekozen school blijft de indeling op rol/klas; die rolgroepen zijn nu ook inklapbaar - Verwijderfouten (en andere actie-fouten in het detailpaneel) verschijnen nu in het detailpaneel zelf i.p.v. alleen bovenaan (buiten beeld op mobiel). Als een verwijdering serverzijdig faalt, zie je nu de reden - het lijkt niet langer of "niets gebeurt" - Verwijderen zelf is end-to-end geverifieerd (DELETE verstuurd, gebruiker verdwijnt); een gesimuleerde 409 toont de melding in het paneel en laat de gebruiker staan - 7 nieuwe Playwright-checks (school-groepering, zichtbare fout, geslaagde verwijdering) + volledige regressie van 8 suites; 94 servertests groen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
This commit is contained in:
parent
c86abcf794
commit
30818dc5f2
3 changed files with 57 additions and 53 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.3.93-beta
|
||||
0.3.94-beta
|
||||
|
|
|
|||
|
|
@ -359,7 +359,8 @@
|
|||
|
||||
/* het bewerkformulier van één gebruiker; wordt gebruikt door het
|
||||
detailpaneel (gebruikers) én inline door het Systeem-tabblad (subrijen) */
|
||||
function userTools(u){
|
||||
function userTools(u, note){
|
||||
note = note || msg;
|
||||
const tools = h("div","am-tools");
|
||||
const actions = h("div","am-actions");
|
||||
const isStaff = ["teacher","admin","super"].includes(u.role);
|
||||
|
|
@ -373,7 +374,7 @@
|
|||
ss.value = u.schoolId ?? "";
|
||||
ss.addEventListener("change", async ()=>{
|
||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{ schoolId: ss.value ? +ss.value : null }}); reload(); }
|
||||
catch(e){ msg(e.message); ss.value = u.schoolId ?? ""; }
|
||||
catch(e){ note(e.message); ss.value = u.schoolId ?? ""; }
|
||||
});
|
||||
tools.appendChild(field("amFieldSchool", ss));
|
||||
}
|
||||
|
|
@ -385,7 +386,7 @@
|
|||
rs.value = u.role;
|
||||
rs.addEventListener("change", async ()=>{
|
||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{role: rs.value}}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
tools.appendChild(field("amFieldRole", rs));
|
||||
}
|
||||
|
|
@ -403,7 +404,7 @@
|
|||
x.title = T("amClassRemove");
|
||||
x.addEventListener("click", async ()=>{
|
||||
try{ await api("/admin/classes/"+cid+"/teachers", {body:{ userId: u.id, remove:true }}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
chip.appendChild(x);
|
||||
wrap.appendChild(chip);
|
||||
|
|
@ -416,7 +417,7 @@
|
|||
addSel.addEventListener("change", async ()=>{
|
||||
if(!addSel.value) return;
|
||||
try{ await api("/admin/classes/"+addSel.value+"/teachers", {body:{ userId: u.id }}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
wrap.appendChild(addSel);
|
||||
}
|
||||
|
|
@ -427,7 +428,7 @@
|
|||
const cs = classSel(u.classId);
|
||||
cs.addEventListener("change", async ()=>{
|
||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{classId: cs.value ? +cs.value : null}}); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
tools.appendChild(field("amFieldClass", cs));
|
||||
/* wachtwoord inzien en aanpassen - alleen zichtbaar zolang de leerling
|
||||
|
|
@ -440,7 +441,7 @@
|
|||
pw.addEventListener("change", async ()=>{
|
||||
if(pw.value.length < 4) return;
|
||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{password: pw.value}}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
pwWrap.appendChild(pw);
|
||||
const pwCopy = h("button","am-btn", T("amCopy"));
|
||||
|
|
@ -456,7 +457,7 @@
|
|||
rowResults.set(u.id, { label: T("amResultPw"), value: password });
|
||||
selectedUserKey = "u:"+u.id;
|
||||
reload();
|
||||
}catch(e){ msg(e.message); }
|
||||
}catch(e){ note(e.message); }
|
||||
}));
|
||||
}
|
||||
/* eenmalige ouder-code waarmee een ouder dit kind aan het eigen
|
||||
|
|
@ -467,7 +468,7 @@
|
|||
rowResults.set(u.id, { label: T("amResultParentCode"), value: r.parentCode });
|
||||
selectedUserKey = "u:"+u.id;
|
||||
reload();
|
||||
}catch(e){ msg(e.message); }
|
||||
}catch(e){ note(e.message); }
|
||||
}));
|
||||
}else{
|
||||
if(u.pending && u.linkCode){
|
||||
|
|
@ -480,7 +481,7 @@
|
|||
rowResults.set(u.id, { label: T("amResultCode"), value: r.linkCode });
|
||||
selectedUserKey = "u:"+u.id;
|
||||
reload();
|
||||
}catch(e){ msg(e.message); }
|
||||
}catch(e){ note(e.message); }
|
||||
}));
|
||||
/* direct een nieuw wachtwoord zetten, zonder de koppelcode-activatie */
|
||||
actions.appendChild(dangerBtn(T("amNewPw"), "amNewPwWarn", async ()=>{
|
||||
|
|
@ -489,14 +490,14 @@
|
|||
rowResults.set(u.id, { label: T("amResultPw"), value: r.password });
|
||||
selectedUserKey = "u:"+u.id;
|
||||
reload();
|
||||
}catch(e){ msg(e.message); }
|
||||
}catch(e){ note(e.message); }
|
||||
}));
|
||||
}
|
||||
}
|
||||
if(u.id !== currentUser.id){
|
||||
const del = dangerBtn(T("amDelete"), "amDeleteWarn", async ()=>{
|
||||
try{ await api("/admin/users/"+u.id, {method:"DELETE"}); selectedUserKey = null; reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
catch(e){ note(e.message); }
|
||||
});
|
||||
actions.appendChild(del);
|
||||
}
|
||||
|
|
@ -580,7 +581,11 @@
|
|||
detailHost.appendChild(detailHead(u.displayName, T(roleKey(u.role)) + (u.displayName!==u.username ? ` · ${u.username}` : "")));
|
||||
const badge = resultBadge(u);
|
||||
if(badge) detailHost.appendChild(badge);
|
||||
detailHost.appendChild(userTools(u));
|
||||
/* fouten (bv. verwijderen mislukt) tonen we hier, waar de beheerder kijkt,
|
||||
niet alleen bovenaan het paneel (daar valt het buiten beeld) */
|
||||
const note = h("div","am-detail-msg formMsg");
|
||||
detailHost.appendChild(note);
|
||||
detailHost.appendChild(userTools(u, (t)=>{ note.textContent = t || ""; }));
|
||||
return;
|
||||
}
|
||||
if(selectedUserKey && selectedUserKey.startsWith("req:")){
|
||||
|
|
@ -1297,25 +1302,10 @@
|
|||
master.appendChild(thead);
|
||||
|
||||
const list = h("div","am-list am-userlist");
|
||||
/* losse accounts (ooit aan een school gekoppeld, nu zonder): alleen
|
||||
relevant voor de systeemmanager in de alle-scholen-weergave */
|
||||
const showLoose = currentUser.role==="super" && !selSchool;
|
||||
const isLoose = u=>showLoose && ["teacher","admin","pupil"].includes(u.role) && u.schoolId==null;
|
||||
const staffGroups = [["super", T("amSuperGroup")], ["admin", T("roleAdmin")], ["teacher", T("roleTeacher")]];
|
||||
staffGroups.forEach(([role, label])=>{
|
||||
const us = USERS.filter(u=>u.role===role && !isLoose(u));
|
||||
if(!us.length) return;
|
||||
list.appendChild(h("div","am-group", `${label} (${us.length})`));
|
||||
us.forEach(u=>list.appendChild(userCells(u)));
|
||||
});
|
||||
|
||||
const pupils = USERS.filter(u=>u.role==="pupil" && !isLoose(u));
|
||||
const schoolChosen = schoolChosenTop;
|
||||
|
||||
/* leerlingen staan onder inklapbare groepskoppen (standaard dicht): zo
|
||||
blijft de lijst overzichtelijk bij grote scholen; zoeken kijkt altijd
|
||||
door alle groepen heen (zie applyUserFilter) */
|
||||
const pupilGroup = (key, label, us)=>{
|
||||
/* inklapbare groepskop; standaard dicht tenzij defaultOpen (dan onthoudt
|
||||
closedGroups een bewuste dichtklap). Zoeken kijkt door alle groepen heen. */
|
||||
const group = (key, label, us, defaultOpen)=>{
|
||||
if(defaultOpen && !closedGroups.has(key)) openGroups.add(key);
|
||||
const g = h("div","am-group am-group-toggle");
|
||||
g.dataset.collapsible = "1";
|
||||
g.dataset.gkey = key;
|
||||
|
|
@ -1331,27 +1321,41 @@
|
|||
us.forEach(u=>list.appendChild(userCells(u)));
|
||||
};
|
||||
|
||||
/* per-klas indeling heeft alleen zin met één gekozen school (CLASSES is dan
|
||||
betrouwbaar); zonder gekozen school (super, alle scholen) of voor groepsleiding
|
||||
één groep - anders vallen leerlingen met een classId die niet in
|
||||
de (dan lege) CLASSES-lijst voorkomt onterecht helemaal weg */
|
||||
if(currentUser.role==="teacher" || !schoolChosen){
|
||||
if(pupils.length) pupilGroup("pupils", T("amPupils"), pupils);
|
||||
if(currentUser.role==="super" && !selSchool){
|
||||
/* ALLE SCHOLEN: groepeer per school zodat je meteen ziet wie waar hoort;
|
||||
daarna systeemmanagers, ouders en losse accounts apart */
|
||||
const roleOrder = { admin:0, teacher:1, pupil:2 };
|
||||
SCHOOLS.forEach(sc=>{
|
||||
const us = USERS.filter(u=>Number(u.schoolId)===Number(sc.id))
|
||||
.sort((a,b)=>(roleOrder[a.role]??9)-(roleOrder[b.role]??9) || a.displayName.localeCompare(b.displayName));
|
||||
if(us.length) group("gschool:"+sc.id, sc.name, us);
|
||||
});
|
||||
const supers = USERS.filter(u=>u.role==="super");
|
||||
if(supers.length) group("gsys", T("amSuperGroup"), supers);
|
||||
const parents = USERS.filter(u=>u.role==="parent");
|
||||
if(parents.length) group("gparents", T("amParents"), parents);
|
||||
const loose = USERS.filter(u=>["teacher","admin","pupil"].includes(u.role) && u.schoolId==null);
|
||||
if(loose.length) group("gnoschool", T("amNoSchoolGroup"), loose);
|
||||
}else{
|
||||
/* één school gekozen: groepeer op rol, leerlingen per klas */
|
||||
[["admin", T("roleAdmin")], ["teacher", T("roleTeacher")]].forEach(([role, label])=>{
|
||||
const us = USERS.filter(u=>u.role===role);
|
||||
if(us.length) group("role:"+role, label, us, true);
|
||||
});
|
||||
const pupils = USERS.filter(u=>u.role==="pupil");
|
||||
if(currentUser.role==="teacher"){
|
||||
if(pupils.length) group("pupils", T("amPupils"), pupils, true);
|
||||
}else{
|
||||
CLASSES.forEach(c=>{
|
||||
const us = pupils.filter(u=>u.classId===c.id);
|
||||
/* met een gekozen school staan de klasgroepen standaard open - de
|
||||
schoolkeuze wás al het filter; dichtklappen wordt onthouden */
|
||||
if(us.length && !closedGroups.has("class:"+c.id)) openGroups.add("class:"+c.id);
|
||||
if(us.length) pupilGroup("class:"+c.id, c.name, us);
|
||||
if(us.length) group("class:"+c.id, c.name, us, true);
|
||||
});
|
||||
const noClass = pupils.filter(u=>!u.classId);
|
||||
if(noClass.length) pupilGroup("class:none", T("amNoClass"), noClass);
|
||||
if(noClass.length) group("class:none", T("amNoClass"), noClass, true);
|
||||
}
|
||||
const parents = USERS.filter(u=>u.role==="parent");
|
||||
if(parents.length) pupilGroup("parents", T("amParents"), parents);
|
||||
const loose = USERS.filter(isLoose);
|
||||
if(loose.length) pupilGroup("noschool", T("amNoSchoolGroup"), loose);
|
||||
if(parents.length) group("parents", T("amParents"), parents);
|
||||
}
|
||||
master.appendChild(list);
|
||||
split.appendChild(master);
|
||||
split.appendChild(detail);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"use strict";
|
||||
/* 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. */
|
||||
const VERSION = "0.3.93-beta";
|
||||
const VERSION = "0.3.94-beta";
|
||||
(function(){
|
||||
const tag = document.getElementById("verTag");
|
||||
tag.textContent = "v"+VERSION;
|
||||
|
|
|
|||
Loading…
Reference in a new issue