Personen+Functies samengevoegd (tab), Indeling-tab weg + subgroep aanmaken vanuit blok, 'dit vak laten vervallen' + hele dag laten vervallen in weekrooster

This commit is contained in:
bes-r 2026-06-14 14:27:56 +02:00
parent 8841fd5698
commit 95a04f3ca1
4 changed files with 57 additions and 20 deletions

View file

@ -74,9 +74,9 @@ volgt.
- [ ] **Vrije/vakantie-/studiedagen kunnen niet gebruikt worden** in de - [ ] **Vrije/vakantie-/studiedagen kunnen niet gebruikt worden** in de
Roostermaker (geen blokken plannen op die dagen). Roostermaker (geen blokken plannen op die dagen).
- [ ] De actie **"Deze dag laten vervallen"** subtieler wegwerken **en de tekst - [x] De actie heet nu **"dit vak laten vervallen"** (per blok, juiste tekst).
corrigeren** — het is per *blok*, niet per dag. - [x] Een **hele dag laten vervallen** kan direct in het weekrooster (knop in de
- [ ] Mogelijkheid om **een hele dag op vervallen** te zetten (i.p.v. per blok). dagkop; maakt een vrije dag aan, met "weer lesdag" om te herstellen).
## Later ## Later

View file

@ -30,9 +30,8 @@ class CoreConfig(AppConfig):
# Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser. # Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser.
MenuItem("Rooster", "/rooster", icon="calendar", order=10), MenuItem("Rooster", "/rooster", icon="calendar", order=10),
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12), MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12),
# Submenu Personen # Submenu Personen (functies zitten als tab in het Personen-scherm)
MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"), MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"),
MenuItem("Functies", "/functies", icon="badge", order=25, group="Personen"),
MenuItem("Afwezigheid", "/afwezigheid", icon="away", order=28, group="Personen"), MenuItem("Afwezigheid", "/afwezigheid", icon="away", order=28, group="Personen"),
# Submenu Groepen # Submenu Groepen
MenuItem("Groepen", "/groepen", icon="layers", order=30, group="Groepen"), MenuItem("Groepen", "/groepen", icon="layers", order=30, group="Groepen"),

View file

@ -271,6 +271,7 @@ export function PersonenPage() {
const [form, setForm] = useState(leegLeerling); const [form, setForm] = useState(leegLeerling);
const [editId, setEditId] = useState(null); const [editId, setEditId] = useState(null);
const [filter, setFilter] = useState(""); const [filter, setFilter] = useState("");
const [hoofdtab, setHoofdtab] = useState("personen");
function laadHulplijsten() { function laadHulplijsten() {
listResource("groepen").then(setGroepen).catch(() => {}); listResource("groepen").then(setGroepen).catch(() => {});
@ -354,9 +355,26 @@ export function PersonenPage() {
: filter === "medewerker" ? items.filter((p) => p.rol !== "leerling") : filter === "medewerker" ? items.filter((p) => p.rol !== "leerling")
: items; : items;
if (hoofdtab === "functies") {
return (
<section>
<h1>Personen &amp; functies</h1>
<div className="tabbar">
<button className="tab" onClick={() => setHoofdtab("personen")}>Personen</button>
<button className="tab on" onClick={() => setHoofdtab("functies")}>Functies</button>
</div>
<FunctiesPage />
</section>
);
}
return ( return (
<section> <section>
<h1>Personen</h1> <h1>Personen &amp; functies</h1>
<div className="tabbar">
<button className="tab on" onClick={() => setHoofdtab("personen")}>Personen</button>
<button className="tab" onClick={() => setHoofdtab("functies")}>Functies</button>
</div>
<p className="muted">Personeel en leerlingen staan los van elkaar. Kies wat je wilt toevoegen.</p> <p className="muted">Personeel en leerlingen staan los van elkaar. Kies wat je wilt toevoegen.</p>
<Foutmelding msg={error} /> <Foutmelding msg={error} />
@ -1224,7 +1242,19 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
}); });
const [kopieDoelen, setKopieDoelen] = useState([]); const [kopieDoelen, setKopieDoelen] = useState([]);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const doelOpties = f.doel_type === "groep" ? groepen : f.doel_type === "subgroep" ? subgroepen : leerlingen; const [subs, setSubs] = useState(subgroepen);
const [nieuweSub, setNieuweSub] = useState("");
const doelOpties = f.doel_type === "groep" ? groepen : f.doel_type === "subgroep" ? subs : leerlingen;
async function maakSubgroep() {
if (!nieuweSub.trim()) return;
try {
const s = await createResource("subgroepen", { naam: nieuweSub.trim(), groep: f.doel_type === "groep" && f.doel ? f.doel : null });
setSubs([...subs, s]);
setF({ ...f, doel_type: "subgroep", doel: s.id });
setNieuweSub("");
} catch (e) { setError(e.message); }
}
const toggleBeg = (id) => setF((s) => s.begeleiders.includes(id) ? { ...s, begeleiders: s.begeleiders.filter((x) => x !== id) } : { ...s, begeleiders: [...s.begeleiders, id] }); const toggleBeg = (id) => setF((s) => s.begeleiders.includes(id) ? { ...s, begeleiders: s.begeleiders.filter((x) => x !== id) } : { ...s, begeleiders: [...s.begeleiders, id] });
const toggleKopie = (id) => setKopieDoelen((d) => d.includes(id) ? d.filter((x) => x !== id) : [...d, id]); const toggleKopie = (id) => setKopieDoelen((d) => d.includes(id) ? d.filter((x) => x !== id) : [...d, id]);
const doelPayload = () => ({ const doelPayload = () => ({
@ -1273,6 +1303,12 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
{doelOpties.map((o) => <option key={o.id} value={o.id}>{o.naam || o.volledige_naam}</option>)} {doelOpties.map((o) => <option key={o.id} value={o.id}>{o.naam || o.volledige_naam}</option>)}
</select> </select>
</div> </div>
{f.doel_type === "subgroep" && (
<div className="form-row">
<input placeholder="Nieuwe subgroep" value={nieuweSub} onChange={(e) => setNieuweSub(e.target.value)} />
<button type="button" className="btn" onClick={maakSubgroep}>+ Subgroep aanmaken</button>
</div>
)}
<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">
@ -1528,6 +1564,14 @@ export function RoosterScherm({ mode }) {
async function herstel(uitId) { async function herstel(uitId) {
try { await deleteResource("blok-uitzonderingen", uitId); await laadWeek(); } catch (err) { setError(err.message); } try { await deleteResource("blok-uitzonderingen", uitId); await laadWeek(); } catch (err) { setError(err.message); }
} }
async function dagVervallen(iso) {
try { await createResource("kalenderdagen", { schooljaar, datum: iso, type: "vrije_dag", omschrijving: "vervallen" }); await laadBlokken(); }
catch (err) { setError(err.message); }
}
async function dagHerstel(kalId) {
try { await deleteResource("kalenderdagen", kalId); await laadBlokken(); }
catch (err) { setError(err.message); }
}
// Hulpfuncties voor het effectieve rooster. // Hulpfuncties voor het effectieve rooster.
const kalenderOp = (iso) => kalender.find((k) => k.datum === iso); const kalenderOp = (iso) => kalender.find((k) => k.datum === iso);
@ -1590,7 +1634,6 @@ export function RoosterScherm({ mode }) {
{edit && ( {edit && (
<div className="tabbar no-print"> <div className="tabbar no-print">
<button className={tab === "week" ? "tab on" : "tab"} onClick={() => setTab("week")}>Weekrooster</button> <button className={tab === "week" ? "tab on" : "tab"} onClick={() => setTab("week")}>Weekrooster</button>
<button className={tab === "indeling" ? "tab on" : "tab"} onClick={() => setTab("indeling")}>Indeling</button>
<button className={tab === "conflicten" ? "tab on" : "tab"} onClick={() => setTab("conflicten")}>Conflicten</button> <button className={tab === "conflicten" ? "tab on" : "tab"} onClick={() => setTab("conflicten")}>Conflicten</button>
<button className={tab === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button> <button className={tab === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button>
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button> <button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
@ -1650,6 +1693,10 @@ export function RoosterScherm({ mode }) {
<div className="rooster-dag"> <div className="rooster-dag">
{d.label} <span className="muted">{fmtDM(datum)}</span> {d.label} <span className="muted">{fmtDM(datum)}</span>
{kal && <div className={`tag dag-${kal.type}`}>{kal.type_label}{kal.omschrijving ? `: ${kal.omschrijving}` : ""}</div>} {kal && <div className={`tag dag-${kal.type}`}>{kal.type_label}{kal.omschrijving ? `: ${kal.omschrijving}` : ""}</div>}
{edit && (kal
? <button className="link small no-print dag-actie" onClick={() => dagHerstel(kal.id)}>weer lesdag</button>
: <button className="link danger small no-print dag-actie" onClick={() => dagVervallen(iso)}>dag laten vervallen</button>
)}
</div> </div>
{slots.length === 0 && <div className="muted small"></div>} {slots.length === 0 && <div className="muted small"></div>}
{slots.map((t) => { {slots.map((t) => {
@ -1688,7 +1735,7 @@ export function RoosterScherm({ mode }) {
{!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""}{edit && <> · <button className="link" onClick={(e) => { e.stopPropagation(); herstel(uit.id); }}>herstel</button></>}</div>} {!kal && uit && uit.vervalt && <div className="small rb-status">Vervalt {uit.opmerking ? `(${uit.opmerking})` : ""}{edit && <> · <button className="link" onClick={(e) => { e.stopPropagation(); herstel(uit.id); }}>herstel</button></>}</div>}
{!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv"> Vervanger: {uit.vervanger_naam}</div>} {!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv"> Vervanger: {uit.vervanger_naam}</div>}
{edit && !kal && !uit && ( {edit && !kal && !uit && (
<button className="link danger small rb-verval no-print" onClick={(e) => { e.stopPropagation(); laatVervallen(b, iso); }}>deze dag laten vervallen</button> <button className="link danger small rb-verval no-print" onClick={(e) => { e.stopPropagation(); laatVervallen(b, iso); }}>dit vak laten vervallen</button>
)} )}
</div> </div>
); );
@ -1702,17 +1749,6 @@ export function RoosterScherm({ mode }) {
</div> </div>
)} )}
{edit && tab === "indeling" && (
<Groepsindeling
groepen={groepen}
leerpleinen={leerpleinen}
subgroepen={subgroepen}
medewerkers={medewerkers}
blokken={blokken}
onWijzig={async () => { try { setSubgroepen(await listResource("subgroepen")); } catch (e) { setError(e.message); } }}
/>
)}
{schooljaar && tab === "conflicten" && <Conflicten schooljaar={schooljaar} />} {schooljaar && tab === "conflicten" && <Conflicten schooljaar={schooljaar} />}
{tab === "schooljaar" && ( {tab === "schooljaar" && (

View file

@ -147,6 +147,8 @@ 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; }
/* --- Groepsindeling --- */ /* --- Groepsindeling --- */
.indeling { margin-top: 12px; } .indeling { margin-top: 12px; }
.indeling-hoofd { background: var(--brand); color: #fff; border-radius: 10px; padding: 12px 16px; font-weight: 700; font-size: 16px; text-align: center; } .indeling-hoofd { background: var(--brand); color: #fff; border-radius: 10px; padding: 12px 16px; font-weight: 700; font-size: 16px; text-align: center; }