|
|
|
@ -145,13 +145,13 @@
|
|
|
|
{ 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:"amOverarching", subKey:"stScholenSub",
|
|
|
|
{ tab:"scholen", icon:"🏫", group:"system", order:30, labelKey:"amSchools", subKey:"stScholenSub",
|
|
|
|
show:()=>currentUser.role==="super",
|
|
|
|
show:()=>currentUser.role==="super",
|
|
|
|
keywords:()=>[T("amNewSchool"), T("amNewSuper"), "school","systeemmanager","system manager"],
|
|
|
|
keywords:()=>[T("amNewSchool"), T("amNewSuper"), "school","systeemmanager","system manager"],
|
|
|
|
search:q=>SCHOOLS
|
|
|
|
search:q=>SCHOOLS
|
|
|
|
.filter(s=>s.name.toLowerCase().includes(q))
|
|
|
|
.filter(s=>s.name.toLowerCase().includes(q))
|
|
|
|
.map(s=>({ icon:"🏫", label:s.name, sub:T("amSchools"), go:()=>goSettingsSection("scholen") })) },
|
|
|
|
.map(s=>({ icon:"🏫", label:s.name, sub:T("amSchools"), go:()=>goSettingsSection("scholen") })) },
|
|
|
|
{ tab:"opslag", icon:"🖼", group:"system", order:31, 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"] },
|
|
|
|
];
|
|
|
|
];
|
|
|
|
@ -194,6 +194,7 @@
|
|
|
|
document.addEventListener("userchange", ()=>{
|
|
|
|
document.addEventListener("userchange", ()=>{
|
|
|
|
if(!currentUser) resetResults = null;
|
|
|
|
if(!currentUser) resetResults = null;
|
|
|
|
loadPromise = null;
|
|
|
|
loadPromise = null;
|
|
|
|
|
|
|
|
selectedUserKey = null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
async function reload(){
|
|
|
|
async function reload(){
|
|
|
|
@ -232,29 +233,64 @@
|
|
|
|
return sel;
|
|
|
|
return sel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function userRow(u, opts){
|
|
|
|
/* ---- gebruikers: master-detail ---------------------------------------------
|
|
|
|
const row = h("div","am-row" + (opts && opts.sub ? " am-row-sub" : ""));
|
|
|
|
Links een compacte tabel (naam | functie | klas | status), rechts een vast
|
|
|
|
/* rustig standaardbeeld: alleen naam + rol/klas; alle knoppen en velden
|
|
|
|
detailpaneel waarin de geselecteerde gebruiker bewerkt wordt. De selectie
|
|
|
|
zitten achter het ✏-knopje in een uitklapbaar gereedschapsblok */
|
|
|
|
overleeft reload(); credential-acties selecteren de gebruiker zodat het
|
|
|
|
const tools = h("div","am-row-tools");
|
|
|
|
eenmalige wachtwoord/de code direct in het paneel zichtbaar is. */
|
|
|
|
const actions = h("div","am-actions");
|
|
|
|
let selectedUserKey = null; /* "u:<id>" | "req:<id>" | "new" | null */
|
|
|
|
const icon = opts && opts.icon ? opts.icon + " " : "";
|
|
|
|
let masterHost = null, detailHost = null;
|
|
|
|
row.appendChild(h("span","am-name", icon + u.displayName + (u.displayName!==u.username ? ` (${u.username})` : "")));
|
|
|
|
|
|
|
|
|
|
|
|
function avatar(nameStr, big){
|
|
|
|
|
|
|
|
const parts = String(nameStr || "?").trim().split(/\s+/);
|
|
|
|
|
|
|
|
const ini = ((parts[0][0] || "?") + (parts[1] ? parts[1][0] : "")).toUpperCase();
|
|
|
|
|
|
|
|
return h("span", "am-avatar" + (big ? " big" : ""), ini);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function userKlasText(u){
|
|
|
|
if(u.role==="pupil"){
|
|
|
|
if(u.role==="pupil"){
|
|
|
|
const cls = CLASSES.find(c=>Number(c.id)===Number(u.classId));
|
|
|
|
const cls = CLASSES.find(c=>Number(c.id)===Number(u.classId));
|
|
|
|
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (cls ? ` · ${cls.name}` : "")));
|
|
|
|
return cls ? cls.name : "";
|
|
|
|
}else if(u.role==="parent"){
|
|
|
|
|
|
|
|
/* de school ziet bij elke ouder direct om welke kinderen het gaat */
|
|
|
|
|
|
|
|
const kids = (u.childNames||[]).join(", ");
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (kids ? ` · 🧒 ${kids}` : "")));
|
|
|
|
|
|
|
|
}else{
|
|
|
|
|
|
|
|
const names = (u.classIds||[])
|
|
|
|
|
|
|
|
.map(cid=>CLASSES.find(c=>Number(c.id)===Number(cid))?.name)
|
|
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-role", T(roleKey(u.role))
|
|
|
|
|
|
|
|
+ (names.length ? ` · ${names.join(", ")}` : "")
|
|
|
|
|
|
|
|
+ (u.pending ? ` · ${T("amPending")}` : "")));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(u.role==="parent") return (u.childNames||[]).join(", ");
|
|
|
|
|
|
|
|
return (u.classIds||[])
|
|
|
|
|
|
|
|
.map(cid=>CLASSES.find(c=>Number(c.id)===Number(cid))?.name)
|
|
|
|
|
|
|
|
.filter(Boolean).join(", ");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function userStatus(u){
|
|
|
|
|
|
|
|
if(u.pending) return T("amPending");
|
|
|
|
|
|
|
|
if(u.role==="pupil" && !u.everLoggedIn) return T("amNeverLoggedIn");
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function nameCellFor(display, username){
|
|
|
|
|
|
|
|
const cell = h("span","am-name");
|
|
|
|
|
|
|
|
cell.appendChild(avatar(display));
|
|
|
|
|
|
|
|
const nm = h("span","am-name-txt", display);
|
|
|
|
|
|
|
|
if(username && display!==username) nm.appendChild(h("small","am-username"," · "+username));
|
|
|
|
|
|
|
|
cell.appendChild(nm);
|
|
|
|
|
|
|
|
return cell;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tabelrij in de gebruikerslijst: klik selecteert (geen her-render, zodat
|
|
|
|
|
|
|
|
het zoekveld focus houdt); bewerken gebeurt in het detailpaneel */
|
|
|
|
|
|
|
|
function userCells(u){
|
|
|
|
|
|
|
|
const row = h("div","am-row am-tr");
|
|
|
|
|
|
|
|
row.dataset.selkey = "u:"+u.id;
|
|
|
|
|
|
|
|
row.appendChild(nameCellFor(u.displayName, u.username));
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-cell", T(roleKey(u.role))));
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-cell", userKlasText(u) || "—"));
|
|
|
|
|
|
|
|
const stCell = h("span","am-cell am-status");
|
|
|
|
|
|
|
|
const st = userStatus(u);
|
|
|
|
|
|
|
|
if(st) stCell.appendChild(h("span","am-chip", st));
|
|
|
|
|
|
|
|
else stCell.textContent = "—";
|
|
|
|
|
|
|
|
row.appendChild(stCell);
|
|
|
|
|
|
|
|
row.addEventListener("click", ()=>{ selectedUserKey = "u:"+u.id; syncSelection(); });
|
|
|
|
|
|
|
|
return row;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* het bewerkformulier van één gebruiker; wordt gebruikt door het
|
|
|
|
|
|
|
|
detailpaneel (gebruikers) én inline door het Systeem-tabblad (subrijen) */
|
|
|
|
|
|
|
|
function userTools(u){
|
|
|
|
|
|
|
|
const tools = h("div","am-tools");
|
|
|
|
|
|
|
|
const actions = h("div","am-actions");
|
|
|
|
const isStaff = ["teacher","admin","super"].includes(u.role);
|
|
|
|
const isStaff = ["teacher","admin","super"].includes(u.role);
|
|
|
|
if(isStaff && can("users.role.change") && u.id!==currentUser.id){
|
|
|
|
if(isStaff && can("users.role.change") && u.id!==currentUser.id){
|
|
|
|
/* één functie per gebruiker: alleen de hoofdrol is te wijzigen (super);
|
|
|
|
/* één functie per gebruiker: alleen de hoofdrol is te wijzigen (super);
|
|
|
|
@ -322,30 +358,29 @@
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
});
|
|
|
|
});
|
|
|
|
pwWrap.appendChild(pw);
|
|
|
|
pwWrap.appendChild(pw);
|
|
|
|
const pwCopy = h("button","am-copy","📋");
|
|
|
|
const pwCopy = h("button","am-btn", T("amCopy"));
|
|
|
|
pwCopy.type = "button";
|
|
|
|
pwCopy.type = "button";
|
|
|
|
pwCopy.title = T("amCopy");
|
|
|
|
|
|
|
|
pwCopy.addEventListener("click", e=>{ e.preventDefault(); if(pw.value) copyText(pwCopy, pw.value); });
|
|
|
|
pwCopy.addEventListener("click", e=>{ e.preventDefault(); if(pw.value) copyText(pwCopy, pw.value); });
|
|
|
|
pwWrap.appendChild(pwCopy);
|
|
|
|
pwWrap.appendChild(pwCopy);
|
|
|
|
tools.appendChild(field("amFieldPw", pwWrap));
|
|
|
|
tools.appendChild(field("amFieldPw", pwWrap));
|
|
|
|
if(u.everLoggedIn){
|
|
|
|
if(u.everLoggedIn){
|
|
|
|
actions.appendChild(dangerBtn("🎲 " + T("amNewPw"), "amNewPwWarn", async ()=>{
|
|
|
|
actions.appendChild(dangerBtn(T("amNewPw"), "amNewPwWarn", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
const { password } = await api("/admin/genpw?role=pupil");
|
|
|
|
const { password } = await api("/admin/genpw?role=pupil");
|
|
|
|
await api("/admin/users/"+u.id, {method:"PATCH", body:{password}});
|
|
|
|
await api("/admin/users/"+u.id, {method:"PATCH", body:{password}});
|
|
|
|
rowResults.set(u.id, { label: T("amResultPw"), value: password });
|
|
|
|
rowResults.set(u.id, { label: T("amResultPw"), value: password });
|
|
|
|
openRows.add(u.id);
|
|
|
|
selectedUserKey = "u:"+u.id;
|
|
|
|
reload();
|
|
|
|
reload();
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* eenmalige ouder-code waarmee een ouder dit kind aan het eigen
|
|
|
|
/* eenmalige ouder-code waarmee een ouder dit kind aan het eigen
|
|
|
|
ouderaccount koppelt (ouderportaal); een eerdere code vervalt */
|
|
|
|
ouderaccount koppelt (ouderportaal); een eerdere code vervalt */
|
|
|
|
actions.appendChild(dangerBtn("👪 " + T("amParentCode"), "amParentCodeWarn", async ()=>{
|
|
|
|
actions.appendChild(dangerBtn(T("amParentCode"), "amParentCodeWarn", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
const r = await api("/admin/users/"+u.id+"/parent-code", { body:{} });
|
|
|
|
const r = await api("/admin/users/"+u.id+"/parent-code", { body:{} });
|
|
|
|
rowResults.set(u.id, { label: T("amResultParentCode"), value: r.parentCode });
|
|
|
|
rowResults.set(u.id, { label: T("amResultParentCode"), value: r.parentCode });
|
|
|
|
openRows.add(u.id);
|
|
|
|
selectedUserKey = "u:"+u.id;
|
|
|
|
reload();
|
|
|
|
reload();
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
@ -354,55 +389,115 @@
|
|
|
|
tools.appendChild(h("span","am-code", T("amCode")+": "+u.linkCode));
|
|
|
|
tools.appendChild(h("span","am-code", T("amCode")+": "+u.linkCode));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(can("users.staffCredentials") && u.role!=="pupil" && u.id!==currentUser.id){
|
|
|
|
if(can("users.staffCredentials") && u.role!=="pupil" && u.id!==currentUser.id){
|
|
|
|
actions.appendChild(dangerBtn("🔑 " + T("amNewCode"), "amNewCodeWarn", async ()=>{
|
|
|
|
actions.appendChild(dangerBtn(T("amNewCode"), "amNewCodeWarn", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
const r = await api("/admin/users/"+u.id, {method:"PATCH", body:{newLinkCode:true}});
|
|
|
|
const r = await api("/admin/users/"+u.id, {method:"PATCH", body:{newLinkCode:true}});
|
|
|
|
rowResults.set(u.id, { label: T("amResultCode"), value: r.linkCode });
|
|
|
|
rowResults.set(u.id, { label: T("amResultCode"), value: r.linkCode });
|
|
|
|
openRows.add(u.id);
|
|
|
|
selectedUserKey = "u:"+u.id;
|
|
|
|
reload();
|
|
|
|
reload();
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
/* direct een nieuw wachtwoord zetten, zonder de koppelcode-activatie */
|
|
|
|
/* direct een nieuw wachtwoord zetten, zonder de koppelcode-activatie */
|
|
|
|
actions.appendChild(dangerBtn("🎲 " + T("amNewPw"), "amNewPwWarn", async ()=>{
|
|
|
|
actions.appendChild(dangerBtn(T("amNewPw"), "amNewPwWarn", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
const r = await api("/admin/users/"+u.id, {method:"PATCH", body:{generatePassword:true}});
|
|
|
|
const r = await api("/admin/users/"+u.id, {method:"PATCH", body:{generatePassword:true}});
|
|
|
|
rowResults.set(u.id, { label: T("amResultPw"), value: r.password });
|
|
|
|
rowResults.set(u.id, { label: T("amResultPw"), value: r.password });
|
|
|
|
openRows.add(u.id);
|
|
|
|
selectedUserKey = "u:"+u.id;
|
|
|
|
reload();
|
|
|
|
reload();
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(u.id !== currentUser.id){
|
|
|
|
if(u.id !== currentUser.id){
|
|
|
|
const del = dangerBtn("🗑 " + T("amDelete"), "amDeleteWarn", async ()=>{
|
|
|
|
const del = dangerBtn(T("amDelete"), "amDeleteWarn", async ()=>{
|
|
|
|
try{ await api("/admin/users/"+u.id, {method:"DELETE"}); reload(); }
|
|
|
|
try{ await api("/admin/users/"+u.id, {method:"DELETE"}); selectedUserKey = null; reload(); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
});
|
|
|
|
});
|
|
|
|
actions.appendChild(del);
|
|
|
|
actions.appendChild(del);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(actions.childNodes.length) tools.appendChild(field("amFieldActions", actions));
|
|
|
|
|
|
|
|
return tools;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* inline-variant voor het Systeem-tabblad (schoolbeheerder-subrijen):
|
|
|
|
|
|
|
|
naam + rol met een "Bewerken"-tekstknop die het formulier uitklapt */
|
|
|
|
|
|
|
|
function userRow(u, opts){
|
|
|
|
|
|
|
|
const row = h("div","am-row" + (opts && opts.sub ? " am-row-sub" : ""));
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-name", u.displayName + (u.displayName!==u.username ? ` (${u.username})` : "")));
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (u.pending ? ` · ${T("amPending")}` : "")));
|
|
|
|
|
|
|
|
const tools = h("div","am-row-tools");
|
|
|
|
const badge = resultBadge(u);
|
|
|
|
const badge = resultBadge(u);
|
|
|
|
if(badge) tools.appendChild(badge);
|
|
|
|
if(badge) tools.appendChild(badge);
|
|
|
|
if(actions.childNodes.length) tools.appendChild(field("amFieldActions", actions));
|
|
|
|
tools.appendChild(userTools(u));
|
|
|
|
if(tools.childNodes.length){
|
|
|
|
const edit = h("button","am-edit", T("amEdit"));
|
|
|
|
const edit = h("button","am-edit","✏");
|
|
|
|
edit.type = "button";
|
|
|
|
edit.type = "button";
|
|
|
|
const sync = ()=>{
|
|
|
|
edit.title = T("amEdit");
|
|
|
|
const open = openRows.has(u.id);
|
|
|
|
const sync = ()=>{
|
|
|
|
tools.style.display = open ? "" : "none";
|
|
|
|
const open = openRows.has(u.id);
|
|
|
|
edit.classList.toggle("on", open);
|
|
|
|
tools.style.display = open ? "" : "none";
|
|
|
|
};
|
|
|
|
edit.classList.toggle("on", open);
|
|
|
|
edit.addEventListener("click", ()=>{
|
|
|
|
};
|
|
|
|
if(openRows.has(u.id)) openRows.delete(u.id); else openRows.add(u.id);
|
|
|
|
edit.addEventListener("click", ()=>{
|
|
|
|
|
|
|
|
if(openRows.has(u.id)) openRows.delete(u.id); else openRows.add(u.id);
|
|
|
|
|
|
|
|
sync();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
sync();
|
|
|
|
sync();
|
|
|
|
row.appendChild(edit);
|
|
|
|
});
|
|
|
|
row.appendChild(tools);
|
|
|
|
sync();
|
|
|
|
}
|
|
|
|
row.appendChild(edit);
|
|
|
|
|
|
|
|
row.appendChild(tools);
|
|
|
|
return row;
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* het detailpaneel rechts: leeg advies, gebruiker bewerken, aanmelding
|
|
|
|
|
|
|
|
afhandelen of een nieuwe gebruiker aanmaken - afhankelijk van de selectie */
|
|
|
|
|
|
|
|
function syncSelection(){
|
|
|
|
|
|
|
|
if(!masterHost || !detailHost) return;
|
|
|
|
|
|
|
|
/* selectie valideren tegen de actuele data (na reload kan hij weg zijn) */
|
|
|
|
|
|
|
|
if(selectedUserKey && selectedUserKey.startsWith("u:")
|
|
|
|
|
|
|
|
&& !USERS.some(u=>"u:"+u.id===selectedUserKey)) selectedUserKey = null;
|
|
|
|
|
|
|
|
if(selectedUserKey && selectedUserKey.startsWith("req:")
|
|
|
|
|
|
|
|
&& !LINK_REQUESTS.some(r=>"req:"+r.id===selectedUserKey)) selectedUserKey = null;
|
|
|
|
|
|
|
|
masterHost.querySelectorAll(".am-tr").forEach(r=>r.classList.toggle("sel", r.dataset.selkey===selectedUserKey));
|
|
|
|
|
|
|
|
const split = masterHost.closest(".am-split");
|
|
|
|
|
|
|
|
if(split) split.classList.toggle("has-sel", !!selectedUserKey);
|
|
|
|
|
|
|
|
renderDetail();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function detailHead(title, subtitle){
|
|
|
|
|
|
|
|
const head = h("div","am-detail-head");
|
|
|
|
|
|
|
|
head.appendChild(avatar(title, true));
|
|
|
|
|
|
|
|
const txt = h("div","am-detail-title");
|
|
|
|
|
|
|
|
txt.appendChild(h("h4", null, title));
|
|
|
|
|
|
|
|
if(subtitle) txt.appendChild(h("span","am-role", subtitle));
|
|
|
|
|
|
|
|
head.appendChild(txt);
|
|
|
|
|
|
|
|
const close = h("button","am-btn am-detail-close", T("close"));
|
|
|
|
|
|
|
|
close.type = "button";
|
|
|
|
|
|
|
|
close.addEventListener("click", ()=>{ selectedUserKey = null; syncSelection(); });
|
|
|
|
|
|
|
|
head.appendChild(close);
|
|
|
|
|
|
|
|
return head;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderDetail(){
|
|
|
|
|
|
|
|
if(!detailHost) return;
|
|
|
|
|
|
|
|
detailHost.innerHTML = "";
|
|
|
|
|
|
|
|
if(selectedUserKey === "new"){
|
|
|
|
|
|
|
|
detailHost.appendChild(detailHead(T("amNewUser"), ""));
|
|
|
|
|
|
|
|
detailHost.appendChild(newUserForm());
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(selectedUserKey && selectedUserKey.startsWith("u:")){
|
|
|
|
|
|
|
|
const u = USERS.find(x=>"u:"+x.id===selectedUserKey);
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(selectedUserKey && selectedUserKey.startsWith("req:")){
|
|
|
|
|
|
|
|
const r = LINK_REQUESTS.find(x=>"req:"+x.id===selectedUserKey);
|
|
|
|
|
|
|
|
detailHost.appendChild(detailHead(r.pupil.displayName, `${T("amRequestOne")} · ${T("amRequestVia")} ${r.parent.displayName}`));
|
|
|
|
|
|
|
|
detailHost.appendChild(requestTools(r));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
detailHost.appendChild(h("div","am-detail-empty", T("amDetailEmpty")));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* toevoegen vereist een bewuste extra stap: begint ingeklapt als alleen de "+ …"-knop,
|
|
|
|
/* toevoegen vereist een bewuste extra stap: begint ingeklapt als alleen de "+ …"-knop,
|
|
|
|
pas na een klik verschijnen de invoervelden. presetClassId zet de klas-select alvast op
|
|
|
|
pas na een klik verschijnen de invoervelden. presetClassId zet de klas-select alvast op
|
|
|
|
de klas waar deze toevoegrij bij hoort (leerkracht/beheerder kan dit nog wijzigen). */
|
|
|
|
de klas waar deze toevoegrij bij hoort (leerkracht/beheerder kan dit nog wijzigen). */
|
|
|
|
@ -487,6 +582,84 @@
|
|
|
|
return wrap;
|
|
|
|
return wrap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* nieuw-gebruiker-formulier in het detailpaneel: eerst de rol kiezen
|
|
|
|
|
|
|
|
(super aanmaken blijft op het Systeem-tabblad - die rol hoort niet bij
|
|
|
|
|
|
|
|
één school), daarna de velden die bij die rol horen */
|
|
|
|
|
|
|
|
function newUserForm(){
|
|
|
|
|
|
|
|
const wrap = h("div","am-tools");
|
|
|
|
|
|
|
|
const roles = creatableRoles().filter(r=>r!=="super");
|
|
|
|
|
|
|
|
const roleSel = h("select","am-sel");
|
|
|
|
|
|
|
|
roles.forEach(r=>roleSel.appendChild(new Option(T(roleKey(r)), r)));
|
|
|
|
|
|
|
|
roleSel.value = roles.includes("pupil") ? "pupil" : roles[0];
|
|
|
|
|
|
|
|
wrap.appendChild(field("amFieldRole", roleSel));
|
|
|
|
|
|
|
|
const body = h("div","am-tools");
|
|
|
|
|
|
|
|
wrap.appendChild(body);
|
|
|
|
|
|
|
|
const build = ()=>{
|
|
|
|
|
|
|
|
body.innerHTML = "";
|
|
|
|
|
|
|
|
const role = roleSel.value;
|
|
|
|
|
|
|
|
const name = h("input","am-inp");
|
|
|
|
|
|
|
|
name.placeholder = T("amUserPh");
|
|
|
|
|
|
|
|
name.addEventListener("keydown", ev=>ev.stopPropagation());
|
|
|
|
|
|
|
|
body.appendChild(field("amFieldName", name));
|
|
|
|
|
|
|
|
let cs = null;
|
|
|
|
|
|
|
|
if(role==="pupil"){ cs = classSel(""); body.appendChild(field("amFieldClass", cs)); }
|
|
|
|
|
|
|
|
let pwInp = null;
|
|
|
|
|
|
|
|
if(role!=="pupil"){
|
|
|
|
|
|
|
|
const pwWrap = h("div","am-inline");
|
|
|
|
|
|
|
|
pwInp = h("input","am-inp am-pwinp");
|
|
|
|
|
|
|
|
pwInp.type = "password";
|
|
|
|
|
|
|
|
pwInp.autocomplete = "new-password";
|
|
|
|
|
|
|
|
pwInp.placeholder = T("amPwOptional");
|
|
|
|
|
|
|
|
pwInp.addEventListener("keydown", ev=>ev.stopPropagation());
|
|
|
|
|
|
|
|
pwWrap.appendChild(pwInp);
|
|
|
|
|
|
|
|
const gen = h("button","am-btn", T("amGenerate"));
|
|
|
|
|
|
|
|
gen.type = "button";
|
|
|
|
|
|
|
|
gen.addEventListener("click", async ()=>{
|
|
|
|
|
|
|
|
try{
|
|
|
|
|
|
|
|
const { password } = await api("/admin/genpw");
|
|
|
|
|
|
|
|
pwInp.type = "text";
|
|
|
|
|
|
|
|
pwInp.value = password;
|
|
|
|
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
pwWrap.appendChild(gen);
|
|
|
|
|
|
|
|
body.appendChild(field("amFieldPw", pwWrap));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const go = h("button","am-btn am-primary", T("amNewUser"));
|
|
|
|
|
|
|
|
go.type = "button";
|
|
|
|
|
|
|
|
go.disabled = true;
|
|
|
|
|
|
|
|
name.addEventListener("input", ()=>{ go.disabled = name.value.trim().length < 2; });
|
|
|
|
|
|
|
|
const errEl = h("div","am-form-err");
|
|
|
|
|
|
|
|
go.addEventListener("click", async ()=>{
|
|
|
|
|
|
|
|
errEl.textContent = "";
|
|
|
|
|
|
|
|
const reqBody = { role, username: name.value.trim(), displayName: name.value.trim() };
|
|
|
|
|
|
|
|
if(role==="pupil" && cs && cs.value) reqBody.classId = +cs.value;
|
|
|
|
|
|
|
|
if(pwInp && pwInp.value.trim()) reqBody.password = pwInp.value.trim();
|
|
|
|
|
|
|
|
if(currentUser.role==="super") reqBody.school = +selSchool;
|
|
|
|
|
|
|
|
try{
|
|
|
|
|
|
|
|
const r = await api("/admin/users", { body: reqBody });
|
|
|
|
|
|
|
|
rowResults.set(r.user.id, {
|
|
|
|
|
|
|
|
label: r.user.password ? T("amResultPw") : T("amResultCode"),
|
|
|
|
|
|
|
|
value: r.user.password || r.user.linkCode,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if(role==="pupil"){
|
|
|
|
|
|
|
|
openGroups.add(r.user.classId ? "class:"+r.user.classId : "class:none");
|
|
|
|
|
|
|
|
openGroups.add("pupils");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* de nieuwe gebruiker wordt direct geselecteerd: het eenmalige
|
|
|
|
|
|
|
|
wachtwoord of de koppelcode staat dan in het detailpaneel */
|
|
|
|
|
|
|
|
selectedUserKey = "u:"+r.user.id;
|
|
|
|
|
|
|
|
reload();
|
|
|
|
|
|
|
|
}catch(e){ errEl.textContent = e.message; }
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
body.appendChild(go);
|
|
|
|
|
|
|
|
body.appendChild(errEl);
|
|
|
|
|
|
|
|
setTimeout(()=>name.focus(), 0);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
roleSel.addEventListener("change", build);
|
|
|
|
|
|
|
|
build();
|
|
|
|
|
|
|
|
return wrap;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* paneel met het resultaat van een multireset (klas/school): een lijst
|
|
|
|
/* paneel met het resultaat van een multireset (klas/school): een lijst
|
|
|
|
nieuwe leerlingwachtwoorden die stuk voor stuk of allemaal ineens
|
|
|
|
nieuwe leerlingwachtwoorden die stuk voor stuk of allemaal ineens
|
|
|
|
gekopieerd kunnen worden. Blijft staan tot de beheerder ✕ klikt. */
|
|
|
|
gekopieerd kunnen worden. Blijft staan tot de beheerder ✕ klikt. */
|
|
|
|
@ -548,7 +721,7 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
tools.appendChild(field("amFieldName", nameInp));
|
|
|
|
tools.appendChild(field("amFieldName", nameInp));
|
|
|
|
const acts = h("div","am-actions");
|
|
|
|
const acts = h("div","am-actions");
|
|
|
|
acts.appendChild(dangerBtn("🗑 " + T("amDelete"), "amDeleteSchoolWarn", async ()=>{
|
|
|
|
acts.appendChild(dangerBtn(T("amDelete"), "amDeleteSchoolWarn", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
await api("/admin/schools/"+s.id, {method:"DELETE"});
|
|
|
|
await api("/admin/schools/"+s.id, {method:"DELETE"});
|
|
|
|
if(String(selSchool)===String(s.id)) selSchool = "";
|
|
|
|
if(String(selSchool)===String(s.id)) selSchool = "";
|
|
|
|
@ -575,7 +748,7 @@
|
|
|
|
list.appendChild(row);
|
|
|
|
list.appendChild(row);
|
|
|
|
/* de schoolbeheerders van deze school, duidelijk onderscheiden van de
|
|
|
|
/* de schoolbeheerders van deze school, duidelijk onderscheiden van de
|
|
|
|
schoolrij (🛡-icoon, ingesprongen) maar met hetzelfde ✏-gereedschap */
|
|
|
|
schoolrij (🛡-icoon, ingesprongen) maar met hetzelfde ✏-gereedschap */
|
|
|
|
admins.forEach(a=>list.appendChild(userRow(a, { icon:"🛡", sub:true })));
|
|
|
|
admins.forEach(a=>list.appendChild(userRow(a, { sub:true })));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
panel.appendChild(list);
|
|
|
|
panel.appendChild(list);
|
|
|
|
|
|
|
|
|
|
|
|
@ -645,7 +818,7 @@
|
|
|
|
row.appendChild(h("span","am-role", `${pupilCount} ${(pupilCount===1 ? T("amPupilOne") : T("amPupils")).toLowerCase()}`));
|
|
|
|
row.appendChild(h("span","am-role", `${pupilCount} ${(pupilCount===1 ? T("amPupilOne") : T("amPupils")).toLowerCase()}`));
|
|
|
|
if(canResetPw){
|
|
|
|
if(canResetPw){
|
|
|
|
const acts = h("div","am-actions");
|
|
|
|
const acts = h("div","am-actions");
|
|
|
|
acts.appendChild(dangerBtn("🔄 " + T("amResetClass"), "amConfirmReset", async ()=>{
|
|
|
|
acts.appendChild(dangerBtn(T("amResetClass"), "amConfirmReset", async ()=>{
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
|
|
|
const r = await api("/admin/classes/"+c.id+"/reset-passwords", {method:"POST"});
|
|
|
|
await reload();
|
|
|
|
await reload();
|
|
|
|
@ -653,7 +826,7 @@
|
|
|
|
render();
|
|
|
|
render();
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
acts.appendChild(dangerBtn("🗑 " + T("amDelete"), "amDeleteClassWarn", async ()=>{
|
|
|
|
acts.appendChild(dangerBtn(T("amDelete"), "amDeleteClassWarn", async ()=>{
|
|
|
|
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
|
|
|
try{ await api("/admin/classes/"+c.id, {method:"DELETE"}); reload(); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
@ -684,7 +857,7 @@
|
|
|
|
panel.appendChild(f);
|
|
|
|
panel.appendChild(f);
|
|
|
|
|
|
|
|
|
|
|
|
/* multireset: alle leerlingwachtwoorden van de hele school in één keer */
|
|
|
|
/* multireset: alle leerlingwachtwoorden van de hele school in één keer */
|
|
|
|
panel.appendChild(dangerBtn("🔄 " + T("amResetSchool"), "amConfirmResetSchool", async ()=>{
|
|
|
|
panel.appendChild(dangerBtn(T("amResetSchool"), "amConfirmResetSchool", async ()=>{
|
|
|
|
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
|
|
|
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
|
|
|
if(!schoolId) return;
|
|
|
|
if(!schoolId) return;
|
|
|
|
try{
|
|
|
|
try{
|
|
|
|
@ -907,12 +1080,21 @@
|
|
|
|
adopteren (thuisaccount wordt de schoolleerling, evt. met klas en - bij
|
|
|
|
adopteren (thuisaccount wordt de schoolleerling, evt. met klas en - bij
|
|
|
|
naamconflict - een andere gebruikersnaam), koppelen aan een bestaande
|
|
|
|
naamconflict - een andere gebruikersnaam), koppelen aan een bestaande
|
|
|
|
leerling, of afwijzen */
|
|
|
|
leerling, of afwijzen */
|
|
|
|
function linkRequestRow(r){
|
|
|
|
/* aanmeldverzoek als tabelrij; afhandelen gebeurt in het detailpaneel */
|
|
|
|
const row = h("div","am-row");
|
|
|
|
function requestCells(r){
|
|
|
|
row.appendChild(h("span","am-name","🧒 " + r.pupil.displayName
|
|
|
|
const row = h("div","am-row am-tr");
|
|
|
|
+ (r.pupil.displayName!==r.pupil.username ? ` (${r.pupil.username})` : "")));
|
|
|
|
row.dataset.selkey = "req:"+r.id;
|
|
|
|
row.appendChild(h("span","am-role", `${T("amRequestVia")} 👪 ${r.parent.displayName}`));
|
|
|
|
row.appendChild(nameCellFor(r.pupil.displayName, r.pupil.username));
|
|
|
|
const tools = h("div","am-row-tools");
|
|
|
|
row.appendChild(h("span","am-cell", T("amRequestOne")));
|
|
|
|
|
|
|
|
row.appendChild(h("span","am-cell", `${T("amRequestVia")} ${r.parent.displayName}`));
|
|
|
|
|
|
|
|
const stCell = h("span","am-cell am-status");
|
|
|
|
|
|
|
|
stCell.appendChild(h("span","am-chip on", T("amReqNew")));
|
|
|
|
|
|
|
|
row.appendChild(stCell);
|
|
|
|
|
|
|
|
row.addEventListener("click", ()=>{ selectedUserKey = "req:"+r.id; syncSelection(); });
|
|
|
|
|
|
|
|
return row;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function requestTools(r){
|
|
|
|
|
|
|
|
const tools = h("div","am-tools");
|
|
|
|
|
|
|
|
|
|
|
|
const adoptWrap = h("div","am-inline");
|
|
|
|
const adoptWrap = h("div","am-inline");
|
|
|
|
const cs = classSel(null);
|
|
|
|
const cs = classSel(null);
|
|
|
|
@ -925,7 +1107,7 @@
|
|
|
|
adopt.addEventListener("click", async ()=>{
|
|
|
|
adopt.addEventListener("click", async ()=>{
|
|
|
|
const body = { action:"adopt", classId: cs.value ? +cs.value : null };
|
|
|
|
const body = { action:"adopt", classId: cs.value ? +cs.value : null };
|
|
|
|
if(un.style.display!=="none" && un.value.trim()) body.username = un.value.trim();
|
|
|
|
if(un.style.display!=="none" && un.value.trim()) body.username = un.value.trim();
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body }); reload(); }
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body }); selectedUserKey = null; reload(); }
|
|
|
|
catch(e){
|
|
|
|
catch(e){
|
|
|
|
/* naamconflict binnen de school: veld tonen om direct een andere
|
|
|
|
/* naamconflict binnen de school: veld tonen om direct een andere
|
|
|
|
gebruikersnaam mee te geven */
|
|
|
|
gebruikersnaam mee te geven */
|
|
|
|
@ -945,7 +1127,7 @@
|
|
|
|
mb.type = "button";
|
|
|
|
mb.type = "button";
|
|
|
|
mb.addEventListener("click", async ()=>{
|
|
|
|
mb.addEventListener("click", async ()=>{
|
|
|
|
if(!ms.value) return;
|
|
|
|
if(!ms.value) return;
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body:{ action:"match", pupilId: +ms.value } }); reload(); }
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body:{ action:"match", pupilId: +ms.value } }); selectedUserKey = null; reload(); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
});
|
|
|
|
});
|
|
|
|
matchWrap.appendChild(ms);
|
|
|
|
matchWrap.appendChild(ms);
|
|
|
|
@ -953,32 +1135,50 @@
|
|
|
|
tools.appendChild(field("amMatchBtn", matchWrap));
|
|
|
|
tools.appendChild(field("amMatchBtn", matchWrap));
|
|
|
|
|
|
|
|
|
|
|
|
const actions = h("div","am-actions");
|
|
|
|
const actions = h("div","am-actions");
|
|
|
|
actions.appendChild(dangerBtn("🗑 " + T("amReject"), "amRejectWarn", async ()=>{
|
|
|
|
actions.appendChild(dangerBtn(T("amReject"), "amRejectWarn", async ()=>{
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body:{ action:"reject" } }); reload(); }
|
|
|
|
try{ await api("/admin/link-requests/"+r.id+"/resolve", { body:{ action:"reject" } }); selectedUserKey = null; reload(); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
catch(e){ msg(e.message); }
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
tools.appendChild(field("amFieldActions", actions));
|
|
|
|
tools.appendChild(field("amFieldActions", actions));
|
|
|
|
row.appendChild(tools);
|
|
|
|
return tools;
|
|
|
|
return row;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderGebruikersPanel(panel){
|
|
|
|
function renderGebruikersPanel(panel){
|
|
|
|
/* openstaande aanmeldingen bovenaan: dit vraagt actie van de school */
|
|
|
|
const schoolChosenTop = currentUser.role!=="super" || !!selSchool;
|
|
|
|
if(LINK_REQUESTS.length){
|
|
|
|
/* werkbalk: zoeken links, nieuwe gebruiker rechts */
|
|
|
|
panel.appendChild(h("div","am-group", `${T("amLinkRequests")} (${LINK_REQUESTS.length})`));
|
|
|
|
const bar = h("div","am-toolbar");
|
|
|
|
const rl = h("div","am-reqlist am-list");
|
|
|
|
|
|
|
|
LINK_REQUESTS.forEach(r=>rl.appendChild(linkRequestRow(r)));
|
|
|
|
|
|
|
|
panel.appendChild(rl);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const filterBar = h("div","am-add");
|
|
|
|
|
|
|
|
const filterInp = h("input","am-inp am-filter");
|
|
|
|
const filterInp = h("input","am-inp am-filter");
|
|
|
|
filterInp.type = "search";
|
|
|
|
filterInp.type = "search";
|
|
|
|
filterInp.placeholder = T("amSearchPh");
|
|
|
|
filterInp.placeholder = T("amSearchPh");
|
|
|
|
filterInp.value = userFilter;
|
|
|
|
filterInp.value = userFilter;
|
|
|
|
filterInp.addEventListener("keydown", ev=>ev.stopPropagation());
|
|
|
|
filterInp.addEventListener("keydown", ev=>ev.stopPropagation());
|
|
|
|
filterInp.addEventListener("input", ()=>{ userFilter = filterInp.value; applyUserFilter(panel); });
|
|
|
|
filterInp.addEventListener("input", ()=>{ userFilter = filterInp.value; applyUserFilter(panel); });
|
|
|
|
filterBar.appendChild(filterInp);
|
|
|
|
bar.appendChild(filterInp);
|
|
|
|
panel.appendChild(filterBar);
|
|
|
|
if(schoolChosenTop && creatableRoles().some(r=>r!=="super")){
|
|
|
|
|
|
|
|
const add = h("button","am-btn am-primary", T("amNewUser"));
|
|
|
|
|
|
|
|
add.type = "button";
|
|
|
|
|
|
|
|
add.addEventListener("click", ()=>{ selectedUserKey = "new"; syncSelection(); });
|
|
|
|
|
|
|
|
bar.appendChild(add);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
panel.appendChild(bar);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* master (tabel) + detail (bewerkpaneel) */
|
|
|
|
|
|
|
|
const split = h("div","am-split");
|
|
|
|
|
|
|
|
const master = h("div","am-master");
|
|
|
|
|
|
|
|
const detail = h("div","am-detail");
|
|
|
|
|
|
|
|
masterHost = master; detailHost = detail;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* openstaande aanmeldingen bovenaan: dit vraagt actie van de school */
|
|
|
|
|
|
|
|
if(LINK_REQUESTS.length){
|
|
|
|
|
|
|
|
master.appendChild(h("div","am-group", `${T("amLinkRequests")} (${LINK_REQUESTS.length})`));
|
|
|
|
|
|
|
|
const rl = h("div","am-reqlist am-list");
|
|
|
|
|
|
|
|
LINK_REQUESTS.forEach(r=>rl.appendChild(requestCells(r)));
|
|
|
|
|
|
|
|
master.appendChild(rl);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* kolomkoppen boven de tabel */
|
|
|
|
|
|
|
|
const thead = h("div","am-thead");
|
|
|
|
|
|
|
|
["amFieldName","amFieldRole","amFieldClass","amColStatus"].forEach(k=>thead.appendChild(h("span",null,T(k))));
|
|
|
|
|
|
|
|
master.appendChild(thead);
|
|
|
|
|
|
|
|
|
|
|
|
const list = h("div","am-list am-userlist");
|
|
|
|
const list = h("div","am-list am-userlist");
|
|
|
|
const staffGroups = [["super", T("amSuperGroup")], ["admin", T("roleAdmin")], ["teacher", T("roleTeacher")], ["parent", T("amParents")]];
|
|
|
|
const staffGroups = [["super", T("amSuperGroup")], ["admin", T("roleAdmin")], ["teacher", T("roleTeacher")], ["parent", T("amParents")]];
|
|
|
|
@ -986,18 +1186,16 @@
|
|
|
|
const us = USERS.filter(u=>u.role===role);
|
|
|
|
const us = USERS.filter(u=>u.role===role);
|
|
|
|
if(!us.length) return;
|
|
|
|
if(!us.length) return;
|
|
|
|
list.appendChild(h("div","am-group", `${label} (${us.length})`));
|
|
|
|
list.appendChild(h("div","am-group", `${label} (${us.length})`));
|
|
|
|
us.forEach(u=>list.appendChild(userRow(u)));
|
|
|
|
us.forEach(u=>list.appendChild(userCells(u)));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const pupils = USERS.filter(u=>u.role==="pupil");
|
|
|
|
const pupils = USERS.filter(u=>u.role==="pupil");
|
|
|
|
const schoolChosen = currentUser.role!=="super" || !!selSchool;
|
|
|
|
const schoolChosen = schoolChosenTop;
|
|
|
|
const creatable = creatableRoles();
|
|
|
|
|
|
|
|
const canAddPupil = schoolChosen && creatable.includes("pupil");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* leerlingen staan onder inklapbare groepskoppen (standaard dicht): zo
|
|
|
|
/* leerlingen staan onder inklapbare groepskoppen (standaard dicht): zo
|
|
|
|
blijft de lijst overzichtelijk bij grote scholen; zoeken kijkt altijd
|
|
|
|
blijft de lijst overzichtelijk bij grote scholen; zoeken kijkt altijd
|
|
|
|
door alle groepen heen (zie applyUserFilter) */
|
|
|
|
door alle groepen heen (zie applyUserFilter) */
|
|
|
|
const pupilGroup = (key, label, us, formEl)=>{
|
|
|
|
const pupilGroup = (key, label, us)=>{
|
|
|
|
const g = h("div","am-group am-group-toggle");
|
|
|
|
const g = h("div","am-group am-group-toggle");
|
|
|
|
g.dataset.collapsible = "1";
|
|
|
|
g.dataset.collapsible = "1";
|
|
|
|
g.dataset.gkey = key;
|
|
|
|
g.dataset.gkey = key;
|
|
|
|
@ -1009,8 +1207,7 @@
|
|
|
|
applyUserFilter(panel);
|
|
|
|
applyUserFilter(panel);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
list.appendChild(g);
|
|
|
|
list.appendChild(g);
|
|
|
|
us.forEach(u=>list.appendChild(userRow(u)));
|
|
|
|
us.forEach(u=>list.appendChild(userCells(u)));
|
|
|
|
if(formEl) list.appendChild(formEl);
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* per-klas indeling heeft alleen zin met één gekozen school (CLASSES is dan
|
|
|
|
/* per-klas indeling heeft alleen zin met één gekozen school (CLASSES is dan
|
|
|
|
@ -1018,29 +1215,22 @@
|
|
|
|
één groep - anders vallen leerlingen met een classId die niet in
|
|
|
|
één groep - anders vallen leerlingen met een classId die niet in
|
|
|
|
de (dan lege) CLASSES-lijst voorkomt onterecht helemaal weg */
|
|
|
|
de (dan lege) CLASSES-lijst voorkomt onterecht helemaal weg */
|
|
|
|
if(currentUser.role==="teacher" || !schoolChosen){
|
|
|
|
if(currentUser.role==="teacher" || !schoolChosen){
|
|
|
|
if(pupils.length || canAddPupil){
|
|
|
|
if(pupils.length) pupilGroup("pupils", T("amPupils"), pupils);
|
|
|
|
pupilGroup("pupils", T("amPupils"), pupils, canAddPupil ? addForm("pupil") : null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}else{
|
|
|
|
}else{
|
|
|
|
CLASSES.forEach(c=>{
|
|
|
|
CLASSES.forEach(c=>{
|
|
|
|
const us = pupils.filter(u=>u.classId===c.id);
|
|
|
|
const us = pupils.filter(u=>u.classId===c.id);
|
|
|
|
if(!us.length && !canAddPupil) return;
|
|
|
|
if(us.length) pupilGroup("class:"+c.id, c.name, us);
|
|
|
|
pupilGroup("class:"+c.id, c.name, us, canAddPupil ? addForm("pupil", c.id) : null);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const noClass = pupils.filter(u=>!u.classId);
|
|
|
|
const noClass = pupils.filter(u=>!u.classId);
|
|
|
|
if(noClass.length || canAddPupil){
|
|
|
|
if(noClass.length) pupilGroup("class:none", T("amNoClass"), noClass);
|
|
|
|
pupilGroup("class:none", T("amNoClass"), noClass, canAddPupil ? addForm("pupil") : null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
panel.appendChild(list);
|
|
|
|
master.appendChild(list);
|
|
|
|
|
|
|
|
split.appendChild(master);
|
|
|
|
|
|
|
|
split.appendChild(detail);
|
|
|
|
|
|
|
|
panel.appendChild(split);
|
|
|
|
|
|
|
|
|
|
|
|
/* staf-aanmaakformulieren - welke rollen deze gebruiker mag aanmaken (CREATABLE_ROLES).
|
|
|
|
|
|
|
|
"super" aanmaken staat in het Systeem-tabblad, niet hier (die rol hoort niet
|
|
|
|
|
|
|
|
bij één school). */
|
|
|
|
|
|
|
|
if(schoolChosen){
|
|
|
|
|
|
|
|
["teacher","admin"].forEach(r=>{ if(creatable.includes(r)) panel.appendChild(addForm(r)); });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
applyUserFilter(panel);
|
|
|
|
applyUserFilter(panel);
|
|
|
|
|
|
|
|
syncSelection(); /* selectie valideren + detailpaneel vullen */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function switchAdminTab(tab){
|
|
|
|
function switchAdminTab(tab){
|
|
|
|
@ -1063,7 +1253,7 @@
|
|
|
|
SCHOOLS.forEach(s=>sel.appendChild(new Option(s.name, s.id)));
|
|
|
|
SCHOOLS.forEach(s=>sel.appendChild(new Option(s.name, s.id)));
|
|
|
|
sel.value = selSchool ?? "";
|
|
|
|
sel.value = selSchool ?? "";
|
|
|
|
sel.setAttribute("aria-label", T("amPickSchool"));
|
|
|
|
sel.setAttribute("aria-label", T("amPickSchool"));
|
|
|
|
sel.addEventListener("change", ()=>{ selSchool = sel.value; reload(); });
|
|
|
|
sel.addEventListener("change", ()=>{ selSchool = sel.value; selectedUserKey = null; reload(); });
|
|
|
|
bar.appendChild(sel);
|
|
|
|
bar.appendChild(sel);
|
|
|
|
return bar;
|
|
|
|
return bar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|