v0.2.17-beta: beheerpaneel opgesplitst in tabs (Scholen/Klassen/Gebruikers)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 10s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 10s
- render() bouwde tot nu toe alles in één lange, doorlopende lijst; nu drie tabbladen die hetzelfde ed-tabs-patroon hergebruiken als de login- en Instellingen-tabs - "Scholen" alleen zichtbaar voor super, "Gebruikers" is het standaard-tabblad - Actief tabblad blijft behouden na acties (nieuwe klas/gebruiker toevoegen springt niet meer terug naar het eerste tabblad) - Zuiver een frontend-herstructurering: geen gedragswijziging aan de onderliggende functionaliteit (reload/msg/classSel/userRow/addForm/resetPanel ongewijzigd) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
270739da09
commit
1b6960b7ba
1 changed files with 128 additions and 93 deletions
|
|
@ -11,6 +11,8 @@
|
||||||
totdat de beheerder het venster zelf sluit, zodat wachtwoorden niet
|
totdat de beheerder het venster zelf sluit, zodat wachtwoorden niet
|
||||||
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) */
|
||||||
|
let adminTab = "gebruikers";
|
||||||
|
|
||||||
const esc = s => String(s ?? "").replace(/&/g,"&").replace(/</g,"<");
|
const esc = s => String(s ?? "").replace(/&/g,"&").replace(/</g,"<");
|
||||||
const h = (tag, cls, text)=>{
|
const h = (tag, cls, text)=>{
|
||||||
|
|
@ -264,107 +266,100 @@
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(){
|
/* super: schoolkiezer + nieuwe school */
|
||||||
modal.innerHTML = "";
|
function renderScholenPanel(panel){
|
||||||
|
const bar = h("div","am-add");
|
||||||
|
const sel = h("select","am-sel");
|
||||||
|
sel.appendChild(new Option(T("amAllSchools"), ""));
|
||||||
|
SCHOOLS.forEach(s=>sel.appendChild(new Option(s.name, s.id)));
|
||||||
|
sel.value = selSchool ?? "";
|
||||||
|
sel.addEventListener("change", ()=>{ selSchool = sel.value; reload(); });
|
||||||
|
bar.appendChild(sel);
|
||||||
|
const inp = h("input","am-inp");
|
||||||
|
inp.placeholder = T("amNamePh");
|
||||||
|
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
|
bar.appendChild(inp);
|
||||||
|
const add = h("button","tbtn", T("amNewSchool"));
|
||||||
|
add.addEventListener("click", async ()=>{
|
||||||
|
if(!inp.value.trim()) return;
|
||||||
|
try{
|
||||||
|
const r = await api("/admin/schools", { body:{ name: inp.value.trim() } });
|
||||||
|
selSchool = r.school.id; inp.value = "";
|
||||||
|
reload();
|
||||||
|
}catch(e){ msg(e.message); }
|
||||||
|
});
|
||||||
|
bar.appendChild(add);
|
||||||
|
panel.appendChild(bar);
|
||||||
|
}
|
||||||
|
|
||||||
/* super: schoolkiezer + nieuwe school */
|
/* klassen (admin/super binnen een school) */
|
||||||
if(currentUser.role==="super"){
|
function renderKlassenPanel(panel){
|
||||||
const bar = h("div","am-add");
|
|
||||||
const sel = h("select","am-sel");
|
|
||||||
sel.appendChild(new Option(T("amAllSchools"), ""));
|
|
||||||
SCHOOLS.forEach(s=>sel.appendChild(new Option(s.name, s.id)));
|
|
||||||
sel.value = selSchool ?? "";
|
|
||||||
sel.addEventListener("change", ()=>{ selSchool = sel.value; reload(); });
|
|
||||||
bar.appendChild(sel);
|
|
||||||
const inp = h("input","am-inp");
|
|
||||||
inp.placeholder = T("amNamePh");
|
|
||||||
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
|
||||||
bar.appendChild(inp);
|
|
||||||
const add = h("button","tbtn", T("amNewSchool"));
|
|
||||||
add.addEventListener("click", async ()=>{
|
|
||||||
if(!inp.value.trim()) return;
|
|
||||||
try{
|
|
||||||
const r = await api("/admin/schools", { body:{ name: inp.value.trim() } });
|
|
||||||
selSchool = r.school.id; inp.value = "";
|
|
||||||
reload();
|
|
||||||
}catch(e){ msg(e.message); }
|
|
||||||
});
|
|
||||||
bar.appendChild(add);
|
|
||||||
modal.appendChild(bar);
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.appendChild(h("div","am-msg"));
|
|
||||||
const rp = resetPanel();
|
|
||||||
if(rp) modal.appendChild(rp);
|
|
||||||
|
|
||||||
/* klassen (admin/super binnen een school) */
|
|
||||||
const schoolChosen = currentUser.role!=="super" || !!selSchool;
|
const schoolChosen = currentUser.role!=="super" || !!selSchool;
|
||||||
|
if(!schoolChosen) return;
|
||||||
const canResetPw = can("schools.resetPasswords");
|
const canResetPw = can("schools.resetPasswords");
|
||||||
if(schoolChosen){
|
const cwrapEl = h("div","am-classes");
|
||||||
modal.appendChild(h("h3","am-h3", T("amClasses")));
|
CLASSES.forEach(c=>{
|
||||||
const cwrapEl = h("div","am-classes");
|
const chip = h("span","am-chip", c.name);
|
||||||
CLASSES.forEach(c=>{
|
|
||||||
const chip = h("span","am-chip", c.name);
|
|
||||||
if(canResetPw){
|
|
||||||
const rs = h("button","am-chipx","🔄");
|
|
||||||
rs.title = T("amResetClass");
|
|
||||||
rs.addEventListener("click", async ()=>{
|
|
||||||
if(!confirm(`${T("amConfirmReset")} ${c.name}?`)) return;
|
|
||||||
try{
|
|
||||||
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
|
||||||
await reload();
|
|
||||||
resetResults = { title: `${T("amResetClass")}: ${c.name}`, entries: r.users };
|
|
||||||
render();
|
|
||||||
}catch(e){ msg(e.message); }
|
|
||||||
});
|
|
||||||
chip.appendChild(rs);
|
|
||||||
const x = h("button","am-chipx","✕");
|
|
||||||
x.addEventListener("click", async ()=>{
|
|
||||||
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
|
||||||
catch(e){ msg(e.message); }
|
|
||||||
});
|
|
||||||
chip.appendChild(x);
|
|
||||||
}
|
|
||||||
cwrapEl.appendChild(chip);
|
|
||||||
});
|
|
||||||
modal.appendChild(cwrapEl);
|
|
||||||
if(canResetPw){
|
if(canResetPw){
|
||||||
const f = h("div","am-add");
|
const rs = h("button","am-chipx","🔄");
|
||||||
const inp = h("input","am-inp");
|
rs.title = T("amResetClass");
|
||||||
inp.placeholder = T("amNamePh");
|
rs.addEventListener("click", async ()=>{
|
||||||
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
if(!confirm(`${T("amConfirmReset")} ${c.name}?`)) return;
|
||||||
f.appendChild(inp);
|
|
||||||
const go = h("button","tbtn", T("amNewClass"));
|
|
||||||
go.addEventListener("click", async ()=>{
|
|
||||||
if(!inp.value.trim()) return;
|
|
||||||
try{
|
try{
|
||||||
await api("/admin/classes", { body:{ name: inp.value.trim(), school: selSchool ? +selSchool : undefined } });
|
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
||||||
reload();
|
|
||||||
}catch(e){ msg(e.message); }
|
|
||||||
});
|
|
||||||
f.appendChild(go);
|
|
||||||
modal.appendChild(f);
|
|
||||||
|
|
||||||
/* multireset: alle leerlingwachtwoorden van de hele school in één keer */
|
|
||||||
const rsAll = h("button","am-btn","🔄 "+T("amResetSchool"));
|
|
||||||
rsAll.type = "button";
|
|
||||||
rsAll.addEventListener("click", async ()=>{
|
|
||||||
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
|
||||||
if(!schoolId) return;
|
|
||||||
if(!confirm(T("amConfirmResetSchool"))) return;
|
|
||||||
try{
|
|
||||||
const r = await api("/admin/schools/"+schoolId+"/reset-passwords", {method:"POST"});
|
|
||||||
await reload();
|
await reload();
|
||||||
resetResults = { title: T("amResetSchool"), entries: r.users };
|
resetResults = { title: `${T("amResetClass")}: ${c.name}`, entries: r.users };
|
||||||
render();
|
render();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
modal.appendChild(rsAll);
|
chip.appendChild(rs);
|
||||||
|
const x = h("button","am-chipx","✕");
|
||||||
|
x.addEventListener("click", async ()=>{
|
||||||
|
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
||||||
|
catch(e){ msg(e.message); }
|
||||||
|
});
|
||||||
|
chip.appendChild(x);
|
||||||
}
|
}
|
||||||
}
|
cwrapEl.appendChild(chip);
|
||||||
|
});
|
||||||
|
panel.appendChild(cwrapEl);
|
||||||
|
if(canResetPw){
|
||||||
|
const f = h("div","am-add");
|
||||||
|
const inp = h("input","am-inp");
|
||||||
|
inp.placeholder = T("amNamePh");
|
||||||
|
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
|
f.appendChild(inp);
|
||||||
|
const go = h("button","tbtn", T("amNewClass"));
|
||||||
|
go.addEventListener("click", async ()=>{
|
||||||
|
if(!inp.value.trim()) return;
|
||||||
|
try{
|
||||||
|
await api("/admin/classes", { body:{ name: inp.value.trim(), school: selSchool ? +selSchool : undefined } });
|
||||||
|
reload();
|
||||||
|
}catch(e){ msg(e.message); }
|
||||||
|
});
|
||||||
|
f.appendChild(go);
|
||||||
|
panel.appendChild(f);
|
||||||
|
|
||||||
/* gebruikers */
|
/* multireset: alle leerlingwachtwoorden van de hele school in één keer */
|
||||||
modal.appendChild(h("h3","am-h3", T("amUsers")));
|
const rsAll = h("button","am-btn","🔄 "+T("amResetSchool"));
|
||||||
|
rsAll.type = "button";
|
||||||
|
rsAll.addEventListener("click", async ()=>{
|
||||||
|
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
||||||
|
if(!schoolId) return;
|
||||||
|
if(!confirm(T("amConfirmResetSchool"))) return;
|
||||||
|
try{
|
||||||
|
const r = await api("/admin/schools/"+schoolId+"/reset-passwords", {method:"POST"});
|
||||||
|
await reload();
|
||||||
|
resetResults = { title: T("amResetSchool"), entries: r.users };
|
||||||
|
render();
|
||||||
|
}catch(e){ msg(e.message); }
|
||||||
|
});
|
||||||
|
panel.appendChild(rsAll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gebruikers: lijst (altijd, ook zonder gekozen school) + aanmaak-formulieren */
|
||||||
|
function renderGebruikersPanel(panel){
|
||||||
const list = h("div","am-list");
|
const list = h("div","am-list");
|
||||||
const groups = [["super", T("amSuperGroup")], ["admin", T("roleAdmin")], ["teacher", T("roleTeacher")], ["pupil", T("rolePupil")]];
|
const groups = [["super", T("amSuperGroup")], ["admin", T("roleAdmin")], ["teacher", T("roleTeacher")], ["pupil", T("rolePupil")]];
|
||||||
groups.forEach(([role, label])=>{
|
groups.forEach(([role, label])=>{
|
||||||
|
|
@ -373,13 +368,53 @@
|
||||||
list.appendChild(h("div","am-group", label));
|
list.appendChild(h("div","am-group", label));
|
||||||
us.forEach(u=>list.appendChild(userRow(u)));
|
us.forEach(u=>list.appendChild(userRow(u)));
|
||||||
});
|
});
|
||||||
modal.appendChild(list);
|
panel.appendChild(list);
|
||||||
|
|
||||||
/* toevoegen - welke rollen deze gebruiker mag aanmaken (CREATABLE_ROLES) */
|
/* toevoegen - welke rollen deze gebruiker mag aanmaken (CREATABLE_ROLES) */
|
||||||
|
const schoolChosen = currentUser.role!=="super" || !!selSchool;
|
||||||
const creatable = CREATABLE_ROLES[currentUser.role];
|
const creatable = CREATABLE_ROLES[currentUser.role];
|
||||||
if(schoolChosen){
|
if(schoolChosen){
|
||||||
["pupil","teacher","admin"].forEach(r=>{ if(creatable.includes(r)) modal.appendChild(addForm(r)); });
|
["pupil","teacher","admin"].forEach(r=>{ if(creatable.includes(r)) panel.appendChild(addForm(r)); });
|
||||||
}
|
}
|
||||||
if(creatable.includes("super")) modal.appendChild(addForm("super"));
|
if(creatable.includes("super")) panel.appendChild(addForm("super"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchAdminTab(tab){
|
||||||
|
adminTab = tab;
|
||||||
|
modal.querySelectorAll(".ed-tabs button").forEach(b=>b.classList.toggle("active", b.dataset.tab===tab));
|
||||||
|
modal.querySelectorAll(".am-tabpanel").forEach(p=>{ p.style.display = p.dataset.panel===tab ? "block" : "none"; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(){
|
||||||
|
modal.innerHTML = "";
|
||||||
|
modal.appendChild(h("div","am-msg"));
|
||||||
|
const rp = resetPanel();
|
||||||
|
if(rp) modal.appendChild(rp);
|
||||||
|
|
||||||
|
const tabDefs = [];
|
||||||
|
if(currentUser.role==="super") tabDefs.push(["scholen", T("amSchools")]);
|
||||||
|
tabDefs.push(["klassen", T("amClasses")]);
|
||||||
|
tabDefs.push(["gebruikers", T("amUsers")]);
|
||||||
|
if(!tabDefs.some(([t])=>t===adminTab)) adminTab = tabDefs[0][0];
|
||||||
|
|
||||||
|
const tabsBar = h("div","ed-tabs");
|
||||||
|
tabDefs.forEach(([t, label])=>{
|
||||||
|
const b = h("button", null, label);
|
||||||
|
b.type = "button";
|
||||||
|
b.dataset.tab = t;
|
||||||
|
b.addEventListener("click", ()=>switchAdminTab(t));
|
||||||
|
tabsBar.appendChild(b);
|
||||||
|
});
|
||||||
|
modal.appendChild(tabsBar);
|
||||||
|
|
||||||
|
const panels = { scholen: renderScholenPanel, klassen: renderKlassenPanel, gebruikers: renderGebruikersPanel };
|
||||||
|
tabDefs.forEach(([t])=>{
|
||||||
|
const panel = h("div","am-tabpanel");
|
||||||
|
panel.dataset.panel = t;
|
||||||
|
panels[t](panel);
|
||||||
|
modal.appendChild(panel);
|
||||||
|
});
|
||||||
|
|
||||||
|
switchAdminTab(adminTab);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue