Voeg 28 herkenbare kleurplaten en creatieve niveaus toe (v0.4.66-beta) #4
4 changed files with 185 additions and 70 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.33-beta
|
0.3.34-beta
|
||||||
|
|
|
||||||
|
|
@ -1164,6 +1164,23 @@
|
||||||
}
|
}
|
||||||
.am-edit:hover{opacity:1; background:var(--surface-3);}
|
.am-edit:hover{opacity:1; background:var(--surface-3);}
|
||||||
.am-edit.on{opacity:1; background:var(--accent-soft);}
|
.am-edit.on{opacity:1; background:var(--accent-soft);}
|
||||||
|
/* één interactiesysteem: mini-label boven elk veld/select/knopgroep */
|
||||||
|
.am-field{display:flex; flex-direction:column; gap:3px;}
|
||||||
|
.am-field-lbl{
|
||||||
|
font-size:10.5px; font-weight:800; color:var(--muted);
|
||||||
|
text-transform:uppercase; letter-spacing:.4px;
|
||||||
|
}
|
||||||
|
.am-inline{display:flex; gap:6px; align-items:center;}
|
||||||
|
/* gevaarlijke acties: afwijkende kleur + tweede klik als bevestiging */
|
||||||
|
.am-btn.am-danger{background:var(--red-soft); color:var(--red); font-weight:800;}
|
||||||
|
.am-btn.am-danger.armed{background:var(--red); color:#fff;}
|
||||||
|
/* credential-resultaat direct bij de gebruikersrij */
|
||||||
|
.am-result{
|
||||||
|
display:flex; align-items:center; gap:8px; width:100%;
|
||||||
|
background:var(--accent-soft); color:var(--accent-ink);
|
||||||
|
border-radius:10px; padding:8px 12px; font-weight:800;
|
||||||
|
}
|
||||||
|
.am-result code{font-size:16px; letter-spacing:.5px;}
|
||||||
.am-row{
|
.am-row{
|
||||||
display:flex; gap:8px; align-items:center; background:var(--surface-2);
|
display:flex; gap:8px; align-items:center; background:var(--surface-2);
|
||||||
border-radius:var(--radius); padding:7px 10px; flex-wrap:wrap;
|
border-radius:var(--radius); padding:7px 10px; flex-wrap:wrap;
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,55 @@
|
||||||
if(copyValue) m.appendChild(copyBtn(copyValue));
|
if(copyValue) m.appendChild(copyBtn(copyValue));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ---- één interactiesysteem voor het hele beheer ---------------------------
|
||||||
|
field(): elk invoerveld/select krijgt een zichtbaar mini-label erboven
|
||||||
|
(een placeholder alleen is geen toelichting).
|
||||||
|
dangerBtn(): acties met gevolgen (wachtwoord/koppelcode vernieuwen,
|
||||||
|
verwijderen, resetten) zien er anders uit dan gewone knoppen
|
||||||
|
en vragen een tweede klik als bevestiging (zelfde
|
||||||
|
arm-then-confirm als elders in de app); wapening loopt
|
||||||
|
vanzelf af. Geen native confirm()-dialogen meer.
|
||||||
|
rowResults: nieuw gegenereerde wachtwoorden/codes verschijnen direct bij
|
||||||
|
de gebruiker in de rij (met kopieerknop), niet bovenaan. */
|
||||||
|
function field(labelKey, control){
|
||||||
|
const w = h("label","am-field");
|
||||||
|
w.appendChild(h("span","am-field-lbl", T(labelKey)));
|
||||||
|
w.appendChild(control);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
function dangerBtn(label, warnKey, fn){
|
||||||
|
const b = h("button","am-btn am-danger", label);
|
||||||
|
b.type = "button";
|
||||||
|
if(warnKey) b.title = T(warnKey);
|
||||||
|
let timer = null;
|
||||||
|
b.addEventListener("click", async ()=>{
|
||||||
|
if(!timer){
|
||||||
|
b.classList.add("armed");
|
||||||
|
b.textContent = "⚠ " + T("amSure");
|
||||||
|
timer = setTimeout(()=>{ timer = null; b.classList.remove("armed"); b.textContent = label; }, 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearTimeout(timer); timer = null;
|
||||||
|
b.classList.remove("armed"); b.textContent = label;
|
||||||
|
await fn(b);
|
||||||
|
});
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
/* per gebruiker het laatste credential-resultaat, overleeft de re-render
|
||||||
|
na de actie zodat het bij de rij zichtbaar is tot het paneel sluit */
|
||||||
|
const rowResults = new Map();
|
||||||
|
document.addEventListener("userchange", ()=>rowResults.clear());
|
||||||
|
function resultBadge(u){
|
||||||
|
const r = rowResults.get(u.id);
|
||||||
|
if(!r) return null;
|
||||||
|
const badge = h("div","am-result");
|
||||||
|
badge.appendChild(h("span", null, r.label + ":"));
|
||||||
|
const code = h("code", null, r.value);
|
||||||
|
badge.appendChild(code);
|
||||||
|
badge.appendChild(copyBtn(r.value));
|
||||||
|
return badge;
|
||||||
|
}
|
||||||
|
|
||||||
/* de beheer-tabs staan als losse knoppen in de ene instellingen-tabbalk
|
/* de beheer-tabs staan als losse knoppen in de ene instellingen-tabbalk
|
||||||
(#adminTabsSlot in core.js/index.html) - geen geneste tabbalk meer */
|
(#adminTabsSlot in core.js/index.html) - geen geneste tabbalk meer */
|
||||||
const adminTabDefs = ()=>{
|
const adminTabDefs = ()=>{
|
||||||
|
|
@ -151,7 +200,7 @@
|
||||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{role: rs.value}}); reload(); }
|
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{role: rs.value}}); reload(); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
tools.appendChild(rs);
|
tools.appendChild(field("amFieldRole", rs));
|
||||||
|
|
||||||
/* extra functies naast de hoofdrol (bv. een teacher die ook beheerdersrechten
|
/* extra functies naast de hoofdrol (bv. een teacher die ook beheerdersrechten
|
||||||
krijgt) - los van de hoofdrol hierboven, direct toggelbaar per rol */
|
krijgt) - los van de hoofdrol hierboven, direct toggelbaar per rol */
|
||||||
|
|
@ -168,7 +217,7 @@
|
||||||
});
|
});
|
||||||
extraWrap.appendChild(chip);
|
extraWrap.appendChild(chip);
|
||||||
});
|
});
|
||||||
tools.appendChild(extraWrap);
|
tools.appendChild(field("amFieldExtraRoles", extraWrap));
|
||||||
}
|
}
|
||||||
if(u.role==="pupil"){
|
if(u.role==="pupil"){
|
||||||
/* klas (uitwisselen met andere klassen) */
|
/* klas (uitwisselen met andere klassen) */
|
||||||
|
|
@ -177,75 +226,71 @@
|
||||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{classId: cs.value ? +cs.value : null}}); }
|
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{classId: cs.value ? +cs.value : null}}); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
tools.appendChild(cs);
|
tools.appendChild(field("amFieldClass", cs));
|
||||||
/* wachtwoord inzien en aanpassen - alleen zichtbaar zolang de leerling
|
/* wachtwoord inzien en aanpassen - alleen zichtbaar zolang de leerling
|
||||||
nog niet is ingelogd; daarna kan alleen een nieuw wachtwoord gezet worden */
|
nog niet is ingelogd; daarna kan alleen een nieuw wachtwoord gezet worden */
|
||||||
|
const pwWrap = h("div","am-inline");
|
||||||
const pw = h("input","am-pw");
|
const pw = h("input","am-pw");
|
||||||
pw.value = u.password || "";
|
pw.value = u.password || "";
|
||||||
pw.placeholder = u.everLoggedIn ? T("amPwHidden") : "";
|
pw.placeholder = u.everLoggedIn ? T("amPwHidden") : T("amPwTypePh");
|
||||||
pw.title = T("amPw");
|
|
||||||
pw.addEventListener("keydown", ev=>ev.stopPropagation());
|
pw.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
pw.addEventListener("change", async ()=>{
|
pw.addEventListener("change", async ()=>{
|
||||||
if(pw.value.length < 4) return;
|
if(pw.value.length < 4) return;
|
||||||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{password: pw.value}}); reload(); }
|
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{password: pw.value}}); reload(); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
tools.appendChild(pw);
|
pwWrap.appendChild(pw);
|
||||||
const pwCopy = h("button","am-copy","📋");
|
const pwCopy = h("button","am-copy","📋");
|
||||||
pwCopy.type = "button";
|
pwCopy.type = "button";
|
||||||
pwCopy.title = T("amCopy");
|
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); });
|
||||||
actions.appendChild(pwCopy);
|
pwWrap.appendChild(pwCopy);
|
||||||
|
tools.appendChild(field("amFieldPw", pwWrap));
|
||||||
if(u.everLoggedIn){
|
if(u.everLoggedIn){
|
||||||
const gen = h("button","am-btn","🎲");
|
actions.appendChild(dangerBtn("🎲 " + T("amNewPw"), "amNewPwWarn", async ()=>{
|
||||||
gen.title = T("amGenerate");
|
|
||||||
gen.addEventListener("click", 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}});
|
||||||
await reload();
|
rowResults.set(u.id, { label: T("amResultPw"), value: password });
|
||||||
msg(`${T("amPwSet")} ${password}`, password);
|
openRows.add(u.id);
|
||||||
|
reload();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
}));
|
||||||
actions.appendChild(gen);
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if(u.pending && u.linkCode){
|
if(u.pending && u.linkCode){
|
||||||
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){
|
||||||
const nc = h("button","am-btn", T("amNewCode"));
|
actions.appendChild(dangerBtn("🔑 " + T("amNewCode"), "amNewCodeWarn", async ()=>{
|
||||||
nc.addEventListener("click", 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}});
|
||||||
await reload();
|
rowResults.set(u.id, { label: T("amResultCode"), value: r.linkCode });
|
||||||
msg(`${T("amCreated")} ${r.linkCode}`, r.linkCode);
|
openRows.add(u.id);
|
||||||
|
reload();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
}));
|
||||||
actions.appendChild(nc);
|
|
||||||
/* direct een nieuw wachtwoord zetten, zonder de koppelcode-activatie */
|
/* direct een nieuw wachtwoord zetten, zonder de koppelcode-activatie */
|
||||||
const np = h("button","am-btn", T("amNewPw"));
|
actions.appendChild(dangerBtn("🎲 " + T("amNewPw"), "amNewPwWarn", async ()=>{
|
||||||
np.addEventListener("click", 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}});
|
||||||
await reload();
|
rowResults.set(u.id, { label: T("amResultPw"), value: r.password });
|
||||||
msg(`${T("amPwSet")} ${r.password}`, r.password);
|
openRows.add(u.id);
|
||||||
|
reload();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
}));
|
||||||
actions.appendChild(np);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(u.id !== currentUser.id){
|
if(u.id !== currentUser.id){
|
||||||
const del = h("button","am-del","🗑");
|
const del = dangerBtn("🗑 " + T("amDelete"), "amDeleteWarn", async ()=>{
|
||||||
del.title = T("amDelete") || "";
|
|
||||||
del.addEventListener("click", async ()=>{
|
|
||||||
if(!confirm(T("amConfirmDel"))) return;
|
|
||||||
try{ await api("/admin/users/"+u.id, {method:"DELETE"}); reload(); }
|
try{ await api("/admin/users/"+u.id, {method:"DELETE"}); reload(); }
|
||||||
catch(e){ msg(e.message); }
|
catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
actions.appendChild(del);
|
actions.appendChild(del);
|
||||||
}
|
}
|
||||||
if(actions.childNodes.length) tools.appendChild(actions);
|
const badge = resultBadge(u);
|
||||||
|
if(badge) tools.appendChild(badge);
|
||||||
|
if(actions.childNodes.length) tools.appendChild(field("amFieldActions", actions));
|
||||||
if(tools.childNodes.length){
|
if(tools.childNodes.length){
|
||||||
const edit = h("button","am-edit","✏");
|
const edit = h("button","am-edit","✏");
|
||||||
edit.type = "button";
|
edit.type = "button";
|
||||||
|
|
@ -281,19 +326,20 @@
|
||||||
const name = h("input","am-inp");
|
const name = h("input","am-inp");
|
||||||
name.placeholder = T("amUserPh");
|
name.placeholder = T("amUserPh");
|
||||||
name.addEventListener("keydown", ev=>ev.stopPropagation());
|
name.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
f.appendChild(name);
|
f.appendChild(field("amFieldName", name));
|
||||||
let cs = null;
|
let cs = null;
|
||||||
if(role==="pupil"){ cs = classSel(presetClassId ?? ""); f.appendChild(cs); }
|
if(role==="pupil"){ cs = classSel(presetClassId ?? ""); f.appendChild(field("amFieldClass", cs)); }
|
||||||
let pwInp = null;
|
let pwInp = null;
|
||||||
if(role!=="pupil"){
|
if(role!=="pupil"){
|
||||||
/* optioneel: meteen zelf een wachtwoord instellen i.p.v. een koppelcode
|
/* optioneel: meteen zelf een wachtwoord instellen i.p.v. een koppelcode
|
||||||
(die de gebruiker normaal zelf gebruikt om een wachtwoord te kiezen) */
|
(die de gebruiker normaal zelf gebruikt om een wachtwoord te kiezen) */
|
||||||
|
const pwWrap = h("div","am-inline");
|
||||||
pwInp = h("input","am-inp am-pwinp");
|
pwInp = h("input","am-inp am-pwinp");
|
||||||
pwInp.type = "password";
|
pwInp.type = "password";
|
||||||
pwInp.autocomplete = "new-password";
|
pwInp.autocomplete = "new-password";
|
||||||
pwInp.placeholder = T("amPwOptional");
|
pwInp.placeholder = T("amPwOptional");
|
||||||
pwInp.addEventListener("keydown", ev=>ev.stopPropagation());
|
pwInp.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
f.appendChild(pwInp);
|
pwWrap.appendChild(pwInp);
|
||||||
const gen = h("button","am-btn","🎲");
|
const gen = h("button","am-btn","🎲");
|
||||||
gen.type = "button";
|
gen.type = "button";
|
||||||
gen.title = T("amGenerate");
|
gen.title = T("amGenerate");
|
||||||
|
|
@ -304,9 +350,13 @@
|
||||||
pwInp.value = password;
|
pwInp.value = password;
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
});
|
||||||
f.appendChild(gen);
|
pwWrap.appendChild(gen);
|
||||||
|
f.appendChild(field("amFieldPw", pwWrap));
|
||||||
}
|
}
|
||||||
const go = h("button","tbtn", label);
|
const go = h("button","tbtn", label);
|
||||||
|
go.type = "button";
|
||||||
|
go.disabled = true;
|
||||||
|
name.addEventListener("input", ()=>{ go.disabled = name.value.trim().length < 2; });
|
||||||
go.addEventListener("click", async ()=>{
|
go.addEventListener("click", async ()=>{
|
||||||
const body = { role, username: name.value.trim(), displayName: name.value.trim() };
|
const body = { role, username: name.value.trim(), displayName: name.value.trim() };
|
||||||
if(role==="pupil" && cs && cs.value) body.classId = +cs.value;
|
if(role==="pupil" && cs && cs.value) body.classId = +cs.value;
|
||||||
|
|
@ -370,20 +420,38 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* systeem: nieuwe school + systeemmanagers beheren. Dit tabblad is waar een
|
/* systeem: nieuwe school + systeemmanagers beheren. Dit tabblad is waar een
|
||||||
super zaken regelt die niet aan één school vastzitten; de schoolkiezer
|
super zaken regelt die niet aan één school vastzitten; de schoolkiezer-
|
||||||
zelf staat als vaste contextbalk boven álle beheer-tabs (schoolContextBar),
|
contextbalk wordt hier juist verborgen (zie switchAdminTab), want niets
|
||||||
zodat geen enkel tabblad meer afhankelijk is van een ander tabblad. */
|
op dit tabblad hangt van een gekozen school af. Een school aanmaken is
|
||||||
|
bewust een twee-staps-actie: de knop wordt pas actief bij een echte naam
|
||||||
|
en vraagt daarna een bevestigende tweede klik (typefout-bescherming). */
|
||||||
function renderScholenPanel(panel){
|
function renderScholenPanel(panel){
|
||||||
|
panel.appendChild(h("div","am-h3", T("amNewSchool")));
|
||||||
|
panel.appendChild(h("div","guestnote", T("amNewSchoolDesc")));
|
||||||
const bar = h("div","am-add");
|
const bar = h("div","am-add");
|
||||||
const inp = h("input","am-inp");
|
const inp = h("input","am-inp");
|
||||||
inp.placeholder = T("amNamePh");
|
inp.placeholder = T("amSchoolNamePh");
|
||||||
|
inp.maxLength = 80;
|
||||||
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
bar.appendChild(inp);
|
bar.appendChild(field("amFieldName", inp));
|
||||||
const add = h("button","tbtn", T("amNewSchool"));
|
const add = h("button","tbtn", T("amNewSchool"));
|
||||||
|
add.type = "button";
|
||||||
|
add.disabled = true;
|
||||||
|
let armTimer = null;
|
||||||
|
const disarm = ()=>{ clearTimeout(armTimer); armTimer = null; add.classList.remove("danger"); add.textContent = T("amNewSchool"); };
|
||||||
|
inp.addEventListener("input", ()=>{ add.disabled = inp.value.trim().length < 2; disarm(); });
|
||||||
add.addEventListener("click", async ()=>{
|
add.addEventListener("click", async ()=>{
|
||||||
if(!inp.value.trim()) return;
|
const name = inp.value.trim();
|
||||||
|
if(name.length < 2) return;
|
||||||
|
if(!armTimer){
|
||||||
|
add.classList.add("danger");
|
||||||
|
add.textContent = `${T("amNewSchoolConfirm")} “${name}”?`;
|
||||||
|
armTimer = setTimeout(disarm, 5000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disarm();
|
||||||
try{
|
try{
|
||||||
const r = await api("/admin/schools", { body:{ name: inp.value.trim() } });
|
const r = await api("/admin/schools", { body:{ name } });
|
||||||
selSchool = r.school.id; inp.value = "";
|
selSchool = r.school.id; inp.value = "";
|
||||||
reload();
|
reload();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
|
|
@ -394,7 +462,10 @@
|
||||||
/* systeemmanagers horen niet bij één school, dus het aanmaken ervan
|
/* systeemmanagers horen niet bij één school, dus het aanmaken ervan
|
||||||
staat hier i.p.v. tussen de school-gebonden gebruikers in de Gebruikers-tab
|
staat hier i.p.v. tussen de school-gebonden gebruikers in de Gebruikers-tab
|
||||||
(de lijst van bestaande supers blijft wel gewoon in Gebruikers staan) */
|
(de lijst van bestaande supers blijft wel gewoon in Gebruikers staan) */
|
||||||
if(creatableRoles().includes("super")) panel.appendChild(addForm("super"));
|
if(creatableRoles().includes("super")){
|
||||||
|
panel.appendChild(h("div","am-h3", T("roleSuper")));
|
||||||
|
panel.appendChild(addForm("super"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* klassen (admin/super binnen een school) */
|
/* klassen (admin/super binnen een school) */
|
||||||
|
|
@ -405,41 +476,46 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const canResetPw = can("schools.resetPasswords");
|
const canResetPw = can("schools.resetPasswords");
|
||||||
const cwrapEl = h("div","am-classes");
|
/* klassen als volwaardige rijen (zelfde maat als de gebruikersvakken),
|
||||||
|
met de gevaarlijke acties als aparte, bevestigde knoppen */
|
||||||
|
const list = h("div","am-list");
|
||||||
CLASSES.forEach(c=>{
|
CLASSES.forEach(c=>{
|
||||||
const chip = h("span","am-chip", c.name);
|
const row = h("div","am-row");
|
||||||
|
const pupilCount = USERS.filter(u=>u.role==="pupil" && Number(u.classId)===Number(c.id)).length;
|
||||||
|
row.appendChild(h("span","am-name", c.name));
|
||||||
|
row.appendChild(h("span","am-role", `${pupilCount} ${(pupilCount===1 ? T("amPupilOne") : T("amPupils")).toLowerCase()}`));
|
||||||
if(canResetPw){
|
if(canResetPw){
|
||||||
const rs = h("button","am-chipx","🔄");
|
const acts = h("div","am-actions");
|
||||||
rs.title = T("amResetClass");
|
acts.appendChild(dangerBtn("🔄 " + T("amResetClass"), "amConfirmReset", async ()=>{
|
||||||
rs.addEventListener("click", async ()=>{
|
|
||||||
if(!confirm(`${T("amConfirmReset")} ${c.name}?`)) return;
|
|
||||||
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();
|
||||||
resetResults = { title: `${T("amResetClass")}: ${c.name}`, entries: r.users };
|
resetResults = { title: `${T("amResetClass")}: ${c.name}`, entries: r.users };
|
||||||
render();
|
render();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
}));
|
||||||
chip.appendChild(rs);
|
acts.appendChild(dangerBtn("🗑 " + T("amDelete"), "amDeleteClassWarn", async ()=>{
|
||||||
const x = h("button","am-chipx","✕");
|
|
||||||
x.addEventListener("click", 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); }
|
||||||
});
|
}));
|
||||||
chip.appendChild(x);
|
row.appendChild(acts);
|
||||||
}
|
}
|
||||||
cwrapEl.appendChild(chip);
|
list.appendChild(row);
|
||||||
});
|
});
|
||||||
panel.appendChild(cwrapEl);
|
panel.appendChild(list);
|
||||||
if(canResetPw){
|
if(canResetPw){
|
||||||
const f = h("div","am-add");
|
const f = h("div","am-add");
|
||||||
const inp = h("input","am-inp");
|
const inp = h("input","am-inp");
|
||||||
inp.placeholder = T("amNamePh");
|
inp.placeholder = T("amClassNamePh");
|
||||||
|
inp.maxLength = 80;
|
||||||
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
inp.addEventListener("keydown", ev=>ev.stopPropagation());
|
||||||
f.appendChild(inp);
|
f.appendChild(field("amFieldName", inp));
|
||||||
const go = h("button","tbtn", T("amNewClass"));
|
const go = h("button","tbtn", T("amNewClass"));
|
||||||
|
go.type = "button";
|
||||||
|
go.disabled = true;
|
||||||
|
inp.addEventListener("input", ()=>{ go.disabled = inp.value.trim().length < 2; });
|
||||||
go.addEventListener("click", async ()=>{
|
go.addEventListener("click", async ()=>{
|
||||||
if(!inp.value.trim()) return;
|
if(inp.value.trim().length < 2) return;
|
||||||
try{
|
try{
|
||||||
await api("/admin/classes", { body:{ name: inp.value.trim(), school: selSchool ? +selSchool : undefined } });
|
await api("/admin/classes", { body:{ name: inp.value.trim(), school: selSchool ? +selSchool : undefined } });
|
||||||
reload();
|
reload();
|
||||||
|
|
@ -449,20 +525,16 @@
|
||||||
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 */
|
||||||
const rsAll = h("button","am-btn","🔄 "+T("amResetSchool"));
|
panel.appendChild(dangerBtn("🔄 " + T("amResetSchool"), "amConfirmResetSchool", async ()=>{
|
||||||
rsAll.type = "button";
|
|
||||||
rsAll.addEventListener("click", async ()=>{
|
|
||||||
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
const schoolId = currentUser.role==="super" ? selSchool : currentUser.schoolId;
|
||||||
if(!schoolId) return;
|
if(!schoolId) return;
|
||||||
if(!confirm(T("amConfirmResetSchool"))) return;
|
|
||||||
try{
|
try{
|
||||||
const r = await api("/admin/schools/"+schoolId+"/reset-passwords", {method:"POST"});
|
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("amResetSchool"), entries: r.users };
|
||||||
render();
|
render();
|
||||||
}catch(e){ msg(e.message); }
|
}catch(e){ msg(e.message); }
|
||||||
});
|
}));
|
||||||
panel.appendChild(rsAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -748,6 +820,10 @@
|
||||||
adminTab = tab;
|
adminTab = tab;
|
||||||
document.querySelectorAll("#adminTabsSlot button").forEach(b=>b.classList.toggle("active", b.dataset.admintab===tab));
|
document.querySelectorAll("#adminTabsSlot button").forEach(b=>b.classList.toggle("active", b.dataset.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
|
||||||
|
alleen maar verwarrend, dus die verbergen we er */
|
||||||
|
const sb = modal.querySelector(".am-schoolbar");
|
||||||
|
if(sb) sb.style.display = tab === "scholen" ? "none" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vaste contextbalk voor de systeemmanager: op elk beheer-tabblad direct
|
/* vaste contextbalk voor de systeemmanager: op elk beheer-tabblad direct
|
||||||
|
|
|
||||||
|
|
@ -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.3.33-beta";
|
const VERSION = "0.3.34-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
@ -182,7 +182,18 @@ const I18N = {
|
||||||
pupilWatching:"meekijken",
|
pupilWatching:"meekijken",
|
||||||
amUsers:"Gebruikers", amClasses:"Klassen", amSchools:"Scholen", amOverarching:"Systeem",
|
amUsers:"Gebruikers", amClasses:"Klassen", amSchools:"Scholen", amOverarching:"Systeem",
|
||||||
amPickSchoolHint:"Kies hierboven eerst een school (🏫).", amPickSchool:"Kies een school",
|
amPickSchoolHint:"Kies hierboven eerst een school (🏫).", amPickSchool:"Kies een school",
|
||||||
amPupils:"Leerlingen", amEdit:"Bewerken",
|
amPupils:"Leerlingen", amPupilOne:"Leerling", amEdit:"Bewerken",
|
||||||
|
amSure:"weet je het zeker?", amDelete:"verwijder",
|
||||||
|
amFieldRole:"Functie", amFieldExtraRoles:"Extra functies", amFieldClass:"Klas",
|
||||||
|
amFieldPw:"Wachtwoord", amFieldActions:"Acties", amPwTypePh:"typ een wachtwoord…",
|
||||||
|
amNewPwWarn:"Maakt een nieuw wachtwoord aan. Het oude wachtwoord werkt daarna niet meer en de gebruiker wordt overal uitgelogd.",
|
||||||
|
amNewCodeWarn:"Maakt een nieuwe koppelcode aan. Het huidige wachtwoord vervalt direct; de gebruiker moet opnieuw activeren.",
|
||||||
|
amDeleteWarn:"Verwijdert dit account definitief, inclusief borden en woorden.",
|
||||||
|
amResultPw:"Nieuw wachtwoord", amResultCode:"Nieuwe koppelcode",
|
||||||
|
amNewSchoolDesc:"De schoolnaam verschijnt op het inlogscherm voor alle gebruikers van die school. Kies de definitieve naam - hernoemen kan later niet via dit scherm.",
|
||||||
|
amSchoolNamePh:"naam van de school…", amNewSchoolConfirm:"Aanmaken:",
|
||||||
|
amClassNamePh:"naam van de klas…",
|
||||||
|
amDeleteClassWarn:"Verwijdert deze klas; leerlingen blijven bestaan maar staan daarna zonder klas.",
|
||||||
amNewSchool:"+ School", amNewClass:"+ Klas", amNewTeacher:"+ Groepsleiding",
|
amNewSchool:"+ School", amNewClass:"+ Klas", amNewTeacher:"+ Groepsleiding",
|
||||||
amNewAdmin:"+ Schoolbeheerder", amNewSuper:"+ Systeemmanager",
|
amNewAdmin:"+ Schoolbeheerder", amNewSuper:"+ Systeemmanager",
|
||||||
amNewPupil:"+ Leerling", amUserPh:"gebruikersnaam…", amNamePh:"naam…",
|
amNewPupil:"+ Leerling", amUserPh:"gebruikersnaam…", amNamePh:"naam…",
|
||||||
|
|
@ -367,7 +378,18 @@ const I18N = {
|
||||||
pupilWatching:"watching",
|
pupilWatching:"watching",
|
||||||
amUsers:"Users", amClasses:"Classes", amSchools:"Schools", amOverarching:"System",
|
amUsers:"Users", amClasses:"Classes", amSchools:"Schools", amOverarching:"System",
|
||||||
amPickSchoolHint:"First pick a school above (🏫).", amPickSchool:"Pick a school",
|
amPickSchoolHint:"First pick a school above (🏫).", amPickSchool:"Pick a school",
|
||||||
amPupils:"Pupils", amEdit:"Edit",
|
amPupils:"Pupils", amPupilOne:"Pupil", amEdit:"Edit",
|
||||||
|
amSure:"are you sure?", amDelete:"delete",
|
||||||
|
amFieldRole:"Role", amFieldExtraRoles:"Extra roles", amFieldClass:"Class",
|
||||||
|
amFieldPw:"Password", amFieldActions:"Actions", amPwTypePh:"type a password…",
|
||||||
|
amNewPwWarn:"Generates a new password. The old one stops working and the user is signed out everywhere.",
|
||||||
|
amNewCodeWarn:"Generates a new link code. The current password stops working immediately; the user must re-activate.",
|
||||||
|
amDeleteWarn:"Deletes this account permanently, including boards and words.",
|
||||||
|
amResultPw:"New password", amResultCode:"New link code",
|
||||||
|
amNewSchoolDesc:"The school name appears on the login screen for all users of that school. Pick the final name - it cannot be renamed from this screen later.",
|
||||||
|
amSchoolNamePh:"name of the school…", amNewSchoolConfirm:"Create:",
|
||||||
|
amClassNamePh:"name of the class…",
|
||||||
|
amDeleteClassWarn:"Deletes this class; pupils remain but end up without a class.",
|
||||||
amNewSchool:"+ School", amNewClass:"+ Class", amNewTeacher:"+ Group leader",
|
amNewSchool:"+ School", amNewClass:"+ Class", amNewTeacher:"+ Group leader",
|
||||||
amNewAdmin:"+ School admin", amNewSuper:"+ System manager",
|
amNewAdmin:"+ School admin", amNewSuper:"+ System manager",
|
||||||
amNewPupil:"+ Pupil", amUserPh:"username…", amNamePh:"name…",
|
amNewPupil:"+ Pupil", amUserPh:"username…", amNamePh:"name…",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue