Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5
4 changed files with 130 additions and 47 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.3.32-beta
|
||||
0.3.33-beta
|
||||
|
|
|
|||
|
|
@ -1129,6 +1129,13 @@
|
|||
min-height:20px; font-size:13.5px; font-weight:700; color:var(--green); margin:4px 0;
|
||||
display:flex; align-items:center; gap:8px; flex-wrap:wrap;
|
||||
}
|
||||
/* vaste schoolkiezer voor de systeemmanager, boven alle beheer-tabs */
|
||||
.am-schoolbar{
|
||||
display:flex; align-items:center; gap:8px; margin:0 0 10px;
|
||||
padding:8px 10px; background:var(--surface-2); border-radius:12px;
|
||||
}
|
||||
.am-schoolbar-lbl{font-size:16px;}
|
||||
.am-schoolbar .am-sel{flex:1; min-width:0;}
|
||||
.am-add{display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:8px 0;}
|
||||
.am-inp,.am-sel,.am-pw{
|
||||
border:1px solid var(--line); border-radius:var(--radius); height:var(--control-h);
|
||||
|
|
@ -1143,6 +1150,20 @@
|
|||
.am-pw{width:120px;}
|
||||
.am-list{display:flex; flex-direction:column; gap:5px;}
|
||||
.am-group{font-weight:700; font-size:13px; margin-top:10px; color:var(--muted); text-transform:uppercase; letter-spacing:.4px;}
|
||||
/* inklapbare groepskop (leerlingen per klas): klikbaar, pijltje in de tekst */
|
||||
.am-group-toggle{cursor:pointer; user-select:none;}
|
||||
.am-group-toggle:hover{color:var(--ink);}
|
||||
/* uitklapbaar gereedschapsblok per gebruikersrij (achter het ✏-knopje) */
|
||||
.am-row-tools{
|
||||
display:flex; gap:8px; align-items:center; flex-wrap:wrap; width:100%;
|
||||
margin-top:6px; padding-top:8px; border-top:1px dashed var(--line);
|
||||
}
|
||||
.am-edit{
|
||||
border:none; background:transparent; border-radius:8px; cursor:pointer;
|
||||
font-size:14px; padding:4px 8px; opacity:.6;
|
||||
}
|
||||
.am-edit:hover{opacity:1; background:var(--surface-3);}
|
||||
.am-edit.on{opacity:1; background:var(--accent-soft);}
|
||||
.am-row{
|
||||
display:flex; gap:8px; align-items:center; background:var(--surface-2);
|
||||
border-radius:var(--radius); padding:7px 10px; flex-wrap:wrap;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@
|
|||
let adminTab = "gebruikers";
|
||||
/* live zoekfilter voor de gebruikerslijst, blijft ook behouden over her-renders */
|
||||
let userFilter = "";
|
||||
/* uitgeklapte leerling-groepen en gebruikersrijen (standaard alles dicht
|
||||
voor een rustig overzicht), blijven behouden over her-renders */
|
||||
const openGroups = new Set();
|
||||
const openRows = new Set();
|
||||
|
||||
/* welke rollen de ingelogde gebruiker mag aanmaken: unie over hoofdrol + extra rollen */
|
||||
const creatableRoles = () =>
|
||||
|
|
@ -127,11 +131,17 @@
|
|||
|
||||
function userRow(u){
|
||||
const row = h("div","am-row");
|
||||
/* actieknoppen (kopieer/genereer/verwijder) samen in één groep, zodat
|
||||
ze op smalle schermen als blok rechts blijven staan i.p.v. los
|
||||
tussen de andere rij-elementen te wrappen */
|
||||
/* rustig standaardbeeld: alleen naam + rol/klas; alle knoppen en velden
|
||||
zitten achter het ✏-knopje in een uitklapbaar gereedschapsblok */
|
||||
const tools = h("div","am-row-tools");
|
||||
const actions = h("div","am-actions");
|
||||
row.appendChild(h("span","am-name", u.displayName + (u.displayName!==u.username ? ` (${u.username})` : "")));
|
||||
if(u.role==="pupil"){
|
||||
const cls = CLASSES.find(c=>Number(c.id)===Number(u.classId));
|
||||
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (cls ? ` · ${cls.name}` : "")));
|
||||
}else{
|
||||
row.appendChild(h("span","am-role", T(roleKey(u.role)) + (u.pending ? ` · ${T("amPending")}` : "")));
|
||||
}
|
||||
if(u.role!=="pupil" && can("users.role.change") && u.id!==currentUser.id){
|
||||
/* super mag de rol van andere stafaccounts wijzigen - leerlingen blijven altijd leerling */
|
||||
const rs = h("select","am-sel");
|
||||
|
|
@ -141,7 +151,7 @@
|
|||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{role: rs.value}}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
});
|
||||
row.appendChild(rs);
|
||||
tools.appendChild(rs);
|
||||
|
||||
/* extra functies naast de hoofdrol (bv. een teacher die ook beheerdersrechten
|
||||
krijgt) - los van de hoofdrol hierboven, direct toggelbaar per rol */
|
||||
|
|
@ -158,9 +168,7 @@
|
|||
});
|
||||
extraWrap.appendChild(chip);
|
||||
});
|
||||
row.appendChild(extraWrap);
|
||||
}else{
|
||||
row.appendChild(h("span","am-role", T(roleKey(u.role))));
|
||||
tools.appendChild(extraWrap);
|
||||
}
|
||||
if(u.role==="pupil"){
|
||||
/* klas (uitwisselen met andere klassen) */
|
||||
|
|
@ -169,7 +177,7 @@
|
|||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{classId: cs.value ? +cs.value : null}}); }
|
||||
catch(e){ msg(e.message); }
|
||||
});
|
||||
row.appendChild(cs);
|
||||
tools.appendChild(cs);
|
||||
/* wachtwoord inzien en aanpassen - alleen zichtbaar zolang de leerling
|
||||
nog niet is ingelogd; daarna kan alleen een nieuw wachtwoord gezet worden */
|
||||
const pw = h("input","am-pw");
|
||||
|
|
@ -182,7 +190,7 @@
|
|||
try{ await api("/admin/users/"+u.id, {method:"PATCH", body:{password: pw.value}}); reload(); }
|
||||
catch(e){ msg(e.message); }
|
||||
});
|
||||
row.appendChild(pw);
|
||||
tools.appendChild(pw);
|
||||
const pwCopy = h("button","am-copy","📋");
|
||||
pwCopy.type = "button";
|
||||
pwCopy.title = T("amCopy");
|
||||
|
|
@ -203,9 +211,7 @@
|
|||
}
|
||||
}else{
|
||||
if(u.pending && u.linkCode){
|
||||
row.appendChild(h("span","am-code", T("amCode")+": "+u.linkCode));
|
||||
}else if(u.pending){
|
||||
row.appendChild(h("span","am-code", T("amPending")));
|
||||
tools.appendChild(h("span","am-code", T("amCode")+": "+u.linkCode));
|
||||
}
|
||||
if(can("users.staffCredentials") && u.role!=="pupil" && u.id!==currentUser.id){
|
||||
const nc = h("button","am-btn", T("amNewCode"));
|
||||
|
|
@ -239,7 +245,24 @@
|
|||
});
|
||||
actions.appendChild(del);
|
||||
}
|
||||
if(actions.childNodes.length) row.appendChild(actions);
|
||||
if(actions.childNodes.length) tools.appendChild(actions);
|
||||
if(tools.childNodes.length){
|
||||
const edit = h("button","am-edit","✏");
|
||||
edit.type = "button";
|
||||
edit.title = T("amEdit");
|
||||
const sync = ()=>{
|
||||
const open = openRows.has(u.id);
|
||||
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);
|
||||
sync();
|
||||
});
|
||||
sync();
|
||||
row.appendChild(edit);
|
||||
row.appendChild(tools);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
|
|
@ -346,16 +369,12 @@
|
|||
return box;
|
||||
}
|
||||
|
||||
/* systeem: schoolkiezer + nieuwe school + systeemmanagers beheren.
|
||||
Dit tabblad is waar een super zaken regelt die niet aan één school vastzitten. */
|
||||
/* systeem: nieuwe school + systeemmanagers beheren. Dit tabblad is waar een
|
||||
super zaken regelt die niet aan één school vastzitten; de schoolkiezer
|
||||
zelf staat als vaste contextbalk boven álle beheer-tabs (schoolContextBar),
|
||||
zodat geen enkel tabblad meer afhankelijk is van een ander tabblad. */
|
||||
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());
|
||||
|
|
@ -614,27 +633,37 @@
|
|||
});
|
||||
}
|
||||
|
||||
/* zoekfilter voor de gebruikerslijst: verbergt niet-matchende rijen en lege groepen
|
||||
via style.display i.p.v. een her-render, zodat het zoekveld focus houdt tijdens
|
||||
het typen (een volledige render() zou het veld elke toets opnieuw aanmaken) */
|
||||
/* zoekfilter + inklapbare groepen voor de gebruikerslijst: één plek die de
|
||||
zichtbaarheid bepaalt (style.display i.p.v. her-render, zodat het zoekveld
|
||||
focus houdt). Ingeklapte groepen verbergen hun rijen; zodra er gezocht
|
||||
wordt tellen alle groepen mee zodat een treffer nooit verstopt zit. */
|
||||
function applyUserFilter(panel){
|
||||
const q = userFilter.trim().toLowerCase();
|
||||
const list = panel.querySelector(".am-list");
|
||||
if(!list) return;
|
||||
let pendingGroup = null, groupVisible = false;
|
||||
let curGroup = null, groupVisible = false;
|
||||
const closeGroup = ()=>{
|
||||
if(curGroup) curGroup.style.display = (groupVisible || curGroup.dataset.collapsible) ? "" : "none";
|
||||
};
|
||||
[...list.children].forEach(el=>{
|
||||
if(el.classList.contains("am-group")){
|
||||
if(pendingGroup) pendingGroup.style.display = groupVisible ? "" : "none";
|
||||
pendingGroup = el; groupVisible = false;
|
||||
}else if(el.classList.contains("am-row")){
|
||||
closeGroup();
|
||||
curGroup = el; groupVisible = false;
|
||||
return;
|
||||
}
|
||||
const collapsed = curGroup && curGroup.dataset.collapsible
|
||||
&& !openGroups.has(curGroup.dataset.gkey) && !q;
|
||||
if(el.classList.contains("am-row")){
|
||||
const name = el.querySelector(".am-name");
|
||||
const match = !q || (name && name.textContent.toLowerCase().includes(q));
|
||||
el.style.display = match ? "" : "none";
|
||||
el.style.display = (match && !collapsed) ? "" : "none";
|
||||
if(match) groupVisible = true;
|
||||
}else{
|
||||
/* toevoegrij (am-add-wrap): volgt de ingeklapte staat van zijn groep */
|
||||
el.style.display = collapsed ? "none" : "";
|
||||
}
|
||||
/* .am-add-wrap (toevoegrij) blijft altijd zichtbaar, ongeacht het filter */
|
||||
});
|
||||
if(pendingGroup) pendingGroup.style.display = groupVisible ? "" : "none";
|
||||
closeGroup();
|
||||
}
|
||||
|
||||
/* gebruikers: lijst (altijd, ook zonder gekozen school) + aanmaak-formulieren.
|
||||
|
|
@ -657,7 +686,7 @@
|
|||
staffGroups.forEach(([role, label])=>{
|
||||
const us = USERS.filter(u=>u.role===role);
|
||||
if(!us.length) return;
|
||||
list.appendChild(h("div","am-group", label));
|
||||
list.appendChild(h("div","am-group", `${label} (${us.length})`));
|
||||
us.forEach(u=>list.appendChild(userRow(u)));
|
||||
});
|
||||
|
||||
|
|
@ -666,29 +695,42 @@
|
|||
const creatable = creatableRoles();
|
||||
const canAddPupil = schoolChosen && creatable.includes("pupil");
|
||||
|
||||
/* 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, formEl)=>{
|
||||
const g = h("div","am-group am-group-toggle");
|
||||
g.dataset.collapsible = "1";
|
||||
g.dataset.gkey = key;
|
||||
const sync = ()=>{ g.textContent = `${openGroups.has(key) ? "▾" : "▸"} ${label} (${us.length})`; };
|
||||
sync();
|
||||
g.addEventListener("click", ()=>{
|
||||
if(openGroups.has(key)) openGroups.delete(key); else openGroups.add(key);
|
||||
sync();
|
||||
applyUserFilter(panel);
|
||||
});
|
||||
list.appendChild(g);
|
||||
us.forEach(u=>list.appendChild(userRow(u)));
|
||||
if(formEl) list.appendChild(formEl);
|
||||
};
|
||||
|
||||
/* per-klas indeling heeft alleen zin met één gekozen school (CLASSES is dan
|
||||
betrouwbaar); zonder gekozen school (super, alle scholen) of voor groepsleiding
|
||||
gewoon een platte lijst - 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 */
|
||||
if(currentUser.role==="teacher" || !schoolChosen){
|
||||
if(pupils.length){
|
||||
list.appendChild(h("div","am-group", T("rolePupil")));
|
||||
pupils.forEach(u=>list.appendChild(userRow(u)));
|
||||
if(pupils.length || canAddPupil){
|
||||
pupilGroup("pupils", T("amPupils"), pupils, canAddPupil ? addForm("pupil") : null);
|
||||
}
|
||||
if(canAddPupil) list.appendChild(addForm("pupil"));
|
||||
}else{
|
||||
CLASSES.forEach(c=>{
|
||||
const us = pupils.filter(u=>u.classId===c.id);
|
||||
if(!us.length && !canAddPupil) return;
|
||||
list.appendChild(h("div","am-group", c.name));
|
||||
us.forEach(u=>list.appendChild(userRow(u)));
|
||||
if(canAddPupil) list.appendChild(addForm("pupil", c.id));
|
||||
pupilGroup("class:"+c.id, c.name, us, canAddPupil ? addForm("pupil", c.id) : null);
|
||||
});
|
||||
const noClass = pupils.filter(u=>!u.classId);
|
||||
if(noClass.length || canAddPupil){
|
||||
list.appendChild(h("div","am-group", T("amNoClass")));
|
||||
noClass.forEach(u=>list.appendChild(userRow(u)));
|
||||
if(canAddPupil) list.appendChild(addForm("pupil"));
|
||||
pupilGroup("class:none", T("amNoClass"), noClass, canAddPupil ? addForm("pupil") : null);
|
||||
}
|
||||
}
|
||||
panel.appendChild(list);
|
||||
|
|
@ -708,8 +750,26 @@
|
|||
modal.querySelectorAll(".am-tabpanel").forEach(p=>{ p.style.display = p.dataset.panel===tab ? "block" : "none"; });
|
||||
}
|
||||
|
||||
/* vaste contextbalk voor de systeemmanager: op elk beheer-tabblad direct
|
||||
de school kiezen, zonder eerst naar het Systeem-tabblad te moeten */
|
||||
function schoolContextBar(){
|
||||
if(currentUser.role !== "super") return null;
|
||||
const bar = h("div","am-schoolbar");
|
||||
bar.appendChild(h("span","am-schoolbar-lbl","🏫"));
|
||||
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.setAttribute("aria-label", T("amPickSchool"));
|
||||
sel.addEventListener("change", ()=>{ selSchool = sel.value; reload(); });
|
||||
bar.appendChild(sel);
|
||||
return bar;
|
||||
}
|
||||
|
||||
function render(){
|
||||
modal.innerHTML = "";
|
||||
const sb = schoolContextBar();
|
||||
if(sb) modal.appendChild(sb);
|
||||
modal.appendChild(h("div","am-msg"));
|
||||
const rp = resetPanel();
|
||||
if(rp) modal.appendChild(rp);
|
||||
|
|
|
|||
|
|
@ -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.32-beta";
|
||||
const VERSION = "0.3.33-beta";
|
||||
(function(){
|
||||
const tag = document.getElementById("verTag");
|
||||
tag.textContent = "v"+VERSION;
|
||||
|
|
@ -181,7 +181,8 @@ const I18N = {
|
|||
pupilEmpty:"Je leerkracht heeft nog niets voor je klaargezet.",
|
||||
pupilWatching:"meekijken",
|
||||
amUsers:"Gebruikers", amClasses:"Klassen", amSchools:"Scholen", amOverarching:"Systeem",
|
||||
amPickSchoolHint:"Kies eerst een school bij het tabblad “Systeem”.",
|
||||
amPickSchoolHint:"Kies hierboven eerst een school (🏫).", amPickSchool:"Kies een school",
|
||||
amPupils:"Leerlingen", amEdit:"Bewerken",
|
||||
amNewSchool:"+ School", amNewClass:"+ Klas", amNewTeacher:"+ Groepsleiding",
|
||||
amNewAdmin:"+ Schoolbeheerder", amNewSuper:"+ Systeemmanager",
|
||||
amNewPupil:"+ Leerling", amUserPh:"gebruikersnaam…", amNamePh:"naam…",
|
||||
|
|
@ -365,7 +366,8 @@ const I18N = {
|
|||
pupilEmpty:"Your teacher hasn't set up anything for you yet.",
|
||||
pupilWatching:"watching",
|
||||
amUsers:"Users", amClasses:"Classes", amSchools:"Schools", amOverarching:"System",
|
||||
amPickSchoolHint:"First pick a school in the “System” tab.",
|
||||
amPickSchoolHint:"First pick a school above (🏫).", amPickSchool:"Pick a school",
|
||||
amPupils:"Pupils", amEdit:"Edit",
|
||||
amNewSchool:"+ School", amNewClass:"+ Class", amNewTeacher:"+ Group leader",
|
||||
amNewAdmin:"+ School admin", amNewSuper:"+ System manager",
|
||||
amNewPupil:"+ Pupil", amUserPh:"username…", amNamePh:"name…",
|
||||
|
|
|
|||
Loading…
Reference in a new issue