Roostermaker: rol begeleider zichtbaar (LK/PO/KO) en sneltoetsen (N/pijltjes/T/Esc)

This commit is contained in:
bes-r 2026-06-14 14:42:56 +02:00
parent e1ada6d702
commit 5adddb03c1
3 changed files with 32 additions and 7 deletions

View file

@ -15,10 +15,10 @@ volgt.
- [x] Vanuit de Roostermaker (tab **Indeling**) direct **subgroepen aanmaken** - [x] Vanuit de Roostermaker (tab **Indeling**) direct **subgroepen aanmaken**
onder een hoofdgroep, met verantwoordelijke(n) — kolomsgewijs overzicht onder een hoofdgroep, met verantwoordelijke(n) — kolomsgewijs overzicht
van subgroepen, vakken en wie verantwoordelijk is. van subgroepen, vakken en wie verantwoordelijk is.
- [ ] **Sneltoetsen** voor veelgebruikte acties (bv. nieuw blok, kopiëren, - [x] **Sneltoetsen** in de Roostermaker: N (nieuw blok), ←/→ (week terug/vooruit),
verwijderen, week vooruit/terug, opslaan). T (deze week), Esc (paneel/sluiten); met hint.
- [ ] **Rol van een begeleider beter zichtbaar** in de roostermaker (bv. functie/ - [x] **Rol van een begeleider zichtbaar** in de Roostermaker: afkorting (LK/PO/KO)
rol tonen bij de begeleider in een blok en bij het toewijzen). bij de begeleider in een blok en de volledige rol bij het toewijzen.
- [ ] **Vereenvoudigd vak/blok toevoegen** — bijvoorbeeld een **stap-voor-stap - [ ] **Vereenvoudigd vak/blok toevoegen** — bijvoorbeeld een **stap-voor-stap
wizard** die meeschuift (activiteit → doelgroep → personeel → tijd), in wizard** die meeschuift (activiteit → doelgroep → personeel → tijd), in
plaats van één groot formulier. plaats van één groot formulier.

View file

@ -14,6 +14,9 @@ const ROLLEN = [
{ value: "klassenondersteuner", label: "Klassenondersteuner" }, { value: "klassenondersteuner", label: "Klassenondersteuner" },
]; ];
// Korte aanduiding van een medewerkersrol (compact in de roostervakjes).
const ROL_AFK = { leerkracht: "LK", praktijkondersteuner: "PO", klassenondersteuner: "KO" };
const WEEKDAGEN = [ const WEEKDAGEN = [
{ value: 0, label: "Maandag" }, { value: 0, label: "Maandag" },
{ value: 1, label: "Dinsdag" }, { value: 1, label: "Dinsdag" },
@ -1315,7 +1318,7 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
<div className="leden-keuze"> <div className="leden-keuze">
<div className="muted small">Begeleiders:</div> <div className="muted small">Begeleiders:</div>
<div className="chips"> <div className="chips">
{medewerkers.map((m) => <button type="button" key={m.id} className={f.begeleiders.includes(m.id) ? "chip on" : "chip"} onClick={() => toggleBeg(m.id)}>{m.volledige_naam}</button>)} {medewerkers.map((m) => <button type="button" key={m.id} className={f.begeleiders.includes(m.id) ? "chip on" : "chip"} onClick={() => toggleBeg(m.id)}>{m.volledige_naam}{m.rol_label ? ` · ${m.rol_label}` : ""}</button>)}
</div> </div>
</div> </div>
<div className="form-row"><input placeholder="Opmerking" value={f.opmerkingen} onChange={(e) => setF({ ...f, opmerkingen: e.target.value })} /></div> <div className="form-row"><input placeholder="Opmerking" value={f.opmerkingen} onChange={(e) => setF({ ...f, opmerkingen: e.target.value })} /></div>
@ -1540,6 +1543,23 @@ export function RoosterScherm({ mode }) {
return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); }; return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); };
}, []); }, []);
// Sneltoetsen (alleen in de Roostermaker).
useEffect(() => {
if (!edit) return;
function onKey(e) {
const t = e.target;
const typing = t && (t.tagName === "INPUT" || t.tagName === "SELECT" || t.tagName === "TEXTAREA" || t.isContentEditable);
if (e.key === "Escape") { setBewerkBlok(null); setToonForm(false); return; }
if (typing) return;
if (e.key === "n" || e.key === "N") { setTab("week"); setToonForm((v) => !v); }
else if (e.key === "ArrowLeft") { setMaandag((m) => plusDagen(m, -7)); }
else if (e.key === "ArrowRight") { setMaandag((m) => plusDagen(m, 7)); }
else if (e.key === "t" || e.key === "T") { setMaandag(maandagVan(new Date())); }
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [edit]);
function startDrag(e, blok) { function startDrag(e, blok) {
e.preventDefault(); e.preventDefault();
dragRef.current = blok; dragRef.current = blok;
@ -1581,6 +1601,7 @@ export function RoosterScherm({ mode }) {
const uitzOp = (blokId, iso) => uitzonderingen.find((u) => u.roosterblok === blokId && u.datum === iso); const uitzOp = (blokId, iso) => uitzonderingen.find((u) => u.roosterblok === blokId && u.datum === iso);
// Op id vergelijken, niet op naam: twee personen kunnen dezelfde naam hebben. // Op id vergelijken, niet op naam: twee personen kunnen dezelfde naam hebben.
const persoonAfwezig = (id, iso) => afwezig.some((a) => a.persoon === id && a.start_datum <= iso && a.eind_datum >= iso); const persoonAfwezig = (id, iso) => afwezig.some((a) => a.persoon === id && a.start_datum <= iso && a.eind_datum >= iso);
const rolAfk = (id) => { const m = medewerkers.find((x) => x.id === id); return m ? (ROL_AFK[m.rol] || "") : ""; };
return ( return (
<section className="rooster-scherm"> <section className="rooster-scherm">
@ -1642,6 +1663,9 @@ export function RoosterScherm({ mode }) {
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button> <button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
</div> </div>
)} )}
{edit && tab === "week" && (
<p className="muted small no-print sneltoets-hint">Sneltoetsen: <b>N</b> nieuw blok · <b></b>/<b></b> week · <b>T</b> deze week · <b>Esc</b> sluiten</p>
)}
{edit && schooljaar && tab === "week" && toonForm && ( {edit && schooljaar && tab === "week" && toonForm && (
<form className="form-card no-print" onSubmit={opslaan}> <form className="form-card no-print" onSubmit={opslaan}>
@ -1673,7 +1697,7 @@ export function RoosterScherm({ mode }) {
<div className="leden-keuze"> <div className="leden-keuze">
<div className="muted small">Begeleiders:</div> <div className="muted small">Begeleiders:</div>
<div className="chips"> <div className="chips">
{medewerkers.map((m) => <button type="button" key={m.id} className={form.begeleiders.includes(m.id) ? "chip on" : "chip"} onClick={() => toggleBegeleider(m.id)}>{m.volledige_naam}</button>)} {medewerkers.map((m) => <button type="button" key={m.id} className={form.begeleiders.includes(m.id) ? "chip on" : "chip"} onClick={() => toggleBegeleider(m.id)}>{m.volledige_naam}{m.rol_label ? ` · ${m.rol_label}` : ""}</button>)}
{medewerkers.length === 0 && <span className="muted small">Nog geen medewerkers.</span>} {medewerkers.length === 0 && <span className="muted small">Nog geen medewerkers.</span>}
</div> </div>
</div> </div>
@ -1731,7 +1755,7 @@ export function RoosterScherm({ mode }) {
{b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>} {b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>}
{b.begeleiders_namen.length > 0 && ( {b.begeleiders_namen.length > 0 && (
<div className="small muted">👤 {b.begeleiders_namen.map((x, idx) => ( <div className="small muted">👤 {b.begeleiders_namen.map((x, idx) => (
<span key={x.id} className={persoonAfwezig(x.id, iso) ? "afwezig" : ""}>{idx > 0 ? ", " : ""}{x.naam}</span> <span key={x.id} className={persoonAfwezig(x.id, iso) ? "afwezig" : ""}>{idx > 0 ? ", " : ""}{x.naam}{rolAfk(x.id) ? ` (${rolAfk(x.id)})` : ""}</span>
))}</div> ))}</div>
)} )}
{kal && <div className="small rb-status">Vervalt {kal.type_label}</div>} {kal && <div className="small rb-status">Vervalt {kal.type_label}</div>}

View file

@ -148,6 +148,7 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); } .drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
.dag-actie { display: block; margin-top: 4px; font-size: 11px; } .dag-actie { display: block; margin-top: 4px; font-size: 11px; }
.sneltoets-hint { margin: -4px 0 12px; }
/* --- Groepsindeling --- */ /* --- Groepsindeling --- */
.indeling { margin-top: 12px; } .indeling { margin-top: 12px; }