Verbeter UI van roostermaker

This commit is contained in:
bes-r 2026-06-14 15:57:12 +02:00
parent 32da6e79c7
commit 8f62734af3
2 changed files with 219 additions and 64 deletions

View file

@ -1235,10 +1235,22 @@ export function blokInScope(blok, scope, ctx) {
return leerlingenVanBlok(blok, ctx).some((id) => set.has(id));
}
function groepVanSubgroep(subgroepen, subgroepId) {
const sub = subgroepen.find((s) => s.id === subgroepId);
return sub ? sub.groep : null;
}
function bovenliggendeGroep(blok, subgroepen) {
if (blok.groep) return blok.groep;
if (blok.subgroep) return groepVanSubgroep(subgroepen, blok.subgroep);
return null;
}
/* ----------------------------- Blok bewerken/kopiëren --------------------- */
function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
const { activiteiten, locaties, groepen, subgroepen, leerlingen, medewerkers, tijdsloten } = ctx;
const [f, setF] = useState({
tijdslot: blok.tijdslot,
activiteit: blok.activiteit,
locatie: blok.locatie || "",
doel_type: blok.doel_type,
@ -1253,15 +1265,26 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
const [splitSubs, setSplitSubs] = useState([]);
const [splitNieuw, setSplitNieuw] = useState("");
const [verwijderOrig, setVerwijderOrig] = useState(true);
const doelOpties = f.doel_type === "groep" ? groepen : f.doel_type === "subgroep" ? subs : leerlingen;
// Groep waaronder nieuwe split-subgroepen vallen: de groep van het blok zelf.
const splitGroep = blok.groep || null;
const [subgroepGroepFilter, setSubgroepGroepFilter] = useState(() => (
blok.subgroep ? groepVanSubgroep(subgroepen, blok.subgroep) || "" : ""
));
const gekozenGroep = f.doel_type === "groep"
? (f.doel ? Number(f.doel) : null)
: f.doel_type === "subgroep"
? (subgroepGroepFilter || groepVanSubgroep(subs, Number(f.doel)))
: bovenliggendeGroep(blok, subs);
const subgroepOpties = gekozenGroep ? subs.filter((s) => s.groep === gekozenGroep) : subs;
const doelOpties = f.doel_type === "groep" ? groepen : f.doel_type === "subgroep" ? subgroepOpties : leerlingen;
const splitGroep = gekozenGroep || blok.groep || groepVanSubgroep(subs, blok.subgroep) || null;
const splitOpties = splitGroep ? subs.filter((s) => s.groep === splitGroep) : subs;
const splitGroepNaam = splitGroep ? (groepen.find((g) => g.id === splitGroep)?.naam || "groep") : "groep";
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 });
const s = await createResource("subgroepen", { naam: nieuweSub.trim(), groep: gekozenGroep || null });
setSubs([...subs, s]);
setSubgroepGroepFilter(s.groep || "");
setF({ ...f, doel_type: "subgroep", doel: s.id });
setNieuweSub("");
} catch (e) { setError(e.message); }
@ -1280,7 +1303,7 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
if (splitSubs.length === 0) return;
try {
for (const sid of splitSubs) {
await createResource("roosterblokken", { schooljaar: blok.schooljaar, tijdslot: blok.tijdslot, activiteit: f.activiteit, locatie: f.locatie || null, subgroep: sid, begeleiders: f.begeleiders, opmerkingen: f.opmerkingen });
await createResource("roosterblokken", { schooljaar: blok.schooljaar, tijdslot: f.tijdslot, activiteit: f.activiteit, locatie: f.locatie || null, subgroep: sid, begeleiders: f.begeleiders, opmerkingen: f.opmerkingen });
}
if (verwijderOrig) await deleteResource("roosterblokken", blok.id);
await onKlaar();
@ -1294,7 +1317,7 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
leerling: f.doel_type === "leerling" ? f.doel : null,
});
async function opslaan() {
try { await updateResource("roosterblokken", blok.id, { activiteit: f.activiteit, locatie: f.locatie || null, ...doelPayload(), begeleiders: f.begeleiders, opmerkingen: f.opmerkingen }); await onKlaar(); }
try { await updateResource("roosterblokken", blok.id, { tijdslot: f.tijdslot, activiteit: f.activiteit, locatie: f.locatie || null, ...doelPayload(), begeleiders: f.begeleiders, opmerkingen: f.opmerkingen }); await onKlaar(); }
catch (e) { setError(e.message); }
}
async function kopieer() {
@ -1315,6 +1338,9 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
<div className="modal-kop"><h2>Blok bewerken</h2><button className="link" onClick={onSluit}>sluiten </button></div>
<Foutmelding msg={error} />
<div className="form-row">
<select value={f.tijdslot} onChange={(e) => setF({ ...f, tijdslot: e.target.value })}>
{tijdsloten.map((t) => <option key={t.id} value={t.id}>{t.dag_label} {t.start_tijd?.slice(0, 5)} {t.naam}</option>)}
</select>
<select value={f.activiteit} onChange={(e) => setF({ ...f, activiteit: e.target.value })}>
{activiteiten.map((a) => <option key={a.id} value={a.id}>{a.naam}</option>)}
</select>
@ -1324,7 +1350,10 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
</select>
</div>
<div className="form-row">
<select value={f.doel_type} onChange={(e) => setF({ ...f, doel_type: e.target.value, doel: "" })}>
<select value={f.doel_type} onChange={(e) => {
setF({ ...f, doel_type: e.target.value, doel: "" });
if (e.target.value !== "subgroep") setSubgroepGroepFilter("");
}}>
<option value="groep">Groep</option>
<option value="subgroep">Subgroep</option>
<option value="leerling">Individuele leerling</option>
@ -1336,6 +1365,10 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
</div>
{f.doel_type === "subgroep" && (
<div className="form-row">
<select value={subgroepGroepFilter || ""} onChange={(e) => { setSubgroepGroepFilter(e.target.value ? Number(e.target.value) : ""); setF({ ...f, doel: "", doel_type: "subgroep" }); }}>
<option value=""> alle groepen </option>
{groepen.map((g) => <option key={g.id} value={g.id}>{g.naam}</option>)}
</select>
<input placeholder="Nieuwe subgroep" value={nieuweSub} onChange={(e) => setNieuweSub(e.target.value)} />
<button type="button" className="btn" onClick={maakSubgroep}>+ Subgroep aanmaken</button>
</div>
@ -1352,10 +1385,10 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
<button className="btn" onClick={verwijderen}>Verwijderen</button>
</div>
<hr className="modal-hr" />
<div className="muted small">Dit vak opdelen in subgroepen (zelfde tijd):</div>
<div className="muted small">Groep subgroep: deel dit vak op onder {splitGroepNaam} (zelfde tijd):</div>
<div className="chips">
{subs.map((s) => <button type="button" key={s.id} className={splitSubs.includes(s.id) ? "chip on" : "chip"} onClick={() => toggleSplit(s.id)}>{s.naam}</button>)}
{subs.length === 0 && <span className="muted small">Nog geen subgroepen.</span>}
{splitOpties.map((s) => <button type="button" key={s.id} className={splitSubs.includes(s.id) ? "chip on" : "chip"} onClick={() => toggleSplit(s.id)}>{s.naam}</button>)}
{splitOpties.length === 0 && <span className="muted small">Nog geen subgroepen onder deze groep.</span>}
</div>
<div className="form-row">
<input placeholder="Nieuwe subgroep" value={splitNieuw} onChange={(e) => setSplitNieuw(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); maakSplitSub(); } }} />
@ -1385,7 +1418,7 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken, onWijzig }) {
const [type, setType] = useState("groep");
const [hoofdId, setHoofdId] = useState("");
const [nieuw, setNieuw] = useState({ naam: "", begeleiders: [] });
const [nieuw, setNieuw] = useState({ naam: "", groep: "", begeleiders: [] });
const [error, setError] = useState(null);
const opties = type === "groep" ? groepen : leerpleinen;
@ -1395,6 +1428,12 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
let subs = [];
if (hid && type === "groep") subs = subgroepen.filter((s) => s.groep === hid);
if (hid && type === "leerplein" && hoofd) subs = subgroepen.filter((s) => (hoofd.subgroepen || []).includes(s.id));
const leerpleinGroepen = type === "leerplein" && hoofd
? groepen.filter((g) => (hoofd.groepen || []).includes(g.id))
: [];
const groepenMetSubgroepen = type === "leerplein"
? leerpleinGroepen.map((g) => ({ groep: g, subs: subgroepen.filter((s) => s.groep === g.id) }))
: [];
const vakkenVan = (subId) => [...new Set(blokken.filter((b) => b.subgroep === subId).map((b) => b.activiteit_naam))];
const toggleBeg = (id) => setNieuw((n) => n.begeleiders.includes(id) ? { ...n, begeleiders: n.begeleiders.filter((x) => x !== id) } : { ...n, begeleiders: [...n.begeleiders, id] });
@ -1402,8 +1441,8 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
async function voegToe(e) {
e.preventDefault();
try {
await createResource("subgroepen", { naam: nieuw.naam, groep: hid, begeleiders: nieuw.begeleiders });
setNieuw({ naam: "", begeleiders: [] });
await createResource("subgroepen", { naam: nieuw.naam, groep: type === "groep" ? hid : nieuw.groep, begeleiders: nieuw.begeleiders });
setNieuw({ naam: "", groep: "", begeleiders: [] });
await onWijzig();
} catch (err) { setError(err.message); }
}
@ -1418,7 +1457,7 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
<Foutmelding msg={error} />
<div className="rooster-balk">
<label className="veld">Type
<select value={type} onChange={(e) => { setType(e.target.value); setHoofdId(""); }}>
<select value={type} onChange={(e) => { setType(e.target.value); setHoofdId(""); setNieuw({ naam: "", groep: "", begeleiders: [] }); }}>
<option value="groep">Groep</option>
<option value="leerplein">Leerplein</option>
</select>
@ -1432,23 +1471,51 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
{hid && hoofd && (
<div className="indeling">
<div className="indeling-hoofd">{hoofd.naam} <span className="muted small"> {type}</span></div>
<div className="indeling-kolommen">
{subs.map((s) => (
<div className="indeling-kolom" key={s.id}>
<div className="indeling-kop">{s.naam}</div>
<div className="small">👤 {s.begeleiders_namen && s.begeleiders_namen.length ? s.begeleiders_namen.map((x) => x.naam).join(", ") : "geen verantwoordelijke"}</div>
<div className="small">📚 {vakkenVan(s.id).join(", ") || "nog geen vakken"}</div>
<div className="small muted">{s.aantal_leden} leerling(en)</div>
<button className="link danger small" onClick={() => verwijderSub(s.id)}>verwijderen</button>
</div>
))}
{subs.length === 0 && <div className="muted">Nog geen subgroepen onder {hoofd.naam}.</div>}
</div>
{type === "leerplein" ? (
<div className="indeling-groepen">
{groepenMetSubgroepen.map(({ groep, subs: groepSubs }) => (
<div className="indeling-groep" key={groep.id}>
<div className="indeling-groep-kop">{groep.naam}</div>
<div className="indeling-kolommen">
{groepSubs.map((s) => (
<div className="indeling-kolom" key={s.id}>
<div className="indeling-kop">{s.naam}</div>
<div className="small">Verantwoordelijke: {s.begeleiders_namen && s.begeleiders_namen.length ? s.begeleiders_namen.map((x) => x.naam).join(", ") : "geen"}</div>
<div className="small">Vakken: {vakkenVan(s.id).join(", ") || "nog geen vakken"}</div>
<div className="small muted">{s.aantal_leden} leerling(en)</div>
<button className="link danger small" onClick={() => verwijderSub(s.id)}>verwijderen</button>
</div>
))}
{groepSubs.length === 0 && <div className="muted small">Nog geen subgroepen onder {groep.naam}.</div>}
</div>
</div>
))}
{leerpleinGroepen.length === 0 && <div className="muted">Nog geen groepen onder {hoofd.naam}.</div>}
</div>
) : (
<div className="indeling-kolommen">
{subs.map((s) => (
<div className="indeling-kolom" key={s.id}>
<div className="indeling-kop">{s.naam}</div>
<div className="small">Verantwoordelijke: {s.begeleiders_namen && s.begeleiders_namen.length ? s.begeleiders_namen.map((x) => x.naam).join(", ") : "geen"}</div>
<div className="small">Vakken: {vakkenVan(s.id).join(", ") || "nog geen vakken"}</div>
<div className="small muted">{s.aantal_leden} leerling(en)</div>
<button className="link danger small" onClick={() => verwijderSub(s.id)}>verwijderen</button>
</div>
))}
{subs.length === 0 && <div className="muted">Nog geen subgroepen onder {hoofd.naam}.</div>}
</div>
)}
{type === "groep" ? (
<form className="form-card" onSubmit={voegToe}>
<div className="muted small">Subgroep toevoegen onder {hoofd.naam}:</div>
<form className="form-card" onSubmit={voegToe}>
<div className="muted small">Subgroep toevoegen onder {type === "groep" ? hoofd.naam : "een groep binnen dit leerplein"}:</div>
<div className="form-row">
{type === "leerplein" && (
<select value={nieuw.groep || ""} required onChange={(e) => setNieuw({ ...nieuw, groep: e.target.value })}>
<option value=""> groep </option>
{leerpleinGroepen.map((g) => <option key={g.id} value={g.id}>{g.naam}</option>)}
</select>
)}
<input placeholder="Naam subgroep" value={nieuw.naam} required onChange={(e) => setNieuw({ ...nieuw, naam: e.target.value })} />
<button className="btn primary" type="submit">+ Subgroep</button>
</div>
@ -1459,9 +1526,6 @@ function Groepsindeling({ groepen, leerpleinen, subgroepen, medewerkers, blokken
</div>
</div>
</form>
) : (
<p className="muted small">Subgroepen toevoegen kan onder een groep (een leerplein bundelt bestaande subgroepen).</p>
)}
</div>
)}
</div>
@ -1642,6 +1706,103 @@ export function RoosterScherm({ mode }) {
// 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 rolAfk = (id) => { const m = medewerkers.find((x) => x.id === id); return m ? (ROL_AFK[m.rol] || "") : ""; };
function renderRoosterBlok(b, iso, kal) {
const uit = uitzOp(b.id, iso);
const vervallen = !!kal || (uit && uit.vervalt);
return (
<div
className={(vervallen ? "rooster-blok vervallen" : "rooster-blok") + (edit ? " klikbaar" : "")}
key={b.id}
style={{ borderLeftColor: b.activiteit_kleur }}
onClick={edit ? () => setBewerkBlok(b) : undefined}
title={edit ? "Klik om te bewerken" : undefined}
>
<div className="rb-top">
{edit && <span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} onClick={(e) => e.stopPropagation()} title="Sleep naar ander tijdslot"></span>}
<strong>{b.activiteit_naam}</strong>
{edit && <button className="link danger rb-x" onClick={(e) => { e.stopPropagation(); verwijder(b.id); }} title="Blok verwijderen">×</button>}
</div>
<div className="small">Doel: {b.doel_naam} <span className="muted">({b.doel_type})</span></div>
{b.locatie_naam && <div className="small">Locatie: {b.locatie_naam}</div>}
{b.begeleiders_namen.length > 0 && (
<div className="small muted">Begeleiding: {b.begeleiders_namen.map((x, idx) => (
<span key={x.id} className={persoonAfwezig(x.id, iso) ? "afwezig" : ""}>{idx > 0 ? ", " : ""}{x.naam}{rolAfk(x.id) ? ` (${rolAfk(x.id)})` : ""}</span>
))}</div>
)}
{kal && <div className="small rb-status">Vervalt - {kal.type_label}</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>}
{edit && !kal && !uit && (
<button className="link danger small rb-verval no-print" onClick={(e) => { e.stopPropagation(); laatVervallen(b, iso); }}>dit vak laten vervallen</button>
)}
</div>
);
}
function renderSlotBlokken(slotBlokken, iso, kal) {
const losse = [];
const perGroep = new Map();
slotBlokken.forEach((b) => {
const gid = bovenliggendeGroep(b, subgroepen);
if (!gid) { losse.push(b); return; }
if (!perGroep.has(gid)) perGroep.set(gid, []);
perGroep.get(gid).push(b);
});
const out = losse.map((b) => renderRoosterBlok(b, iso, kal));
if (scopeType === "leerplein" && scopeId && perGroep.size > 0) {
const leerplein = leerpleinen.find((l) => l.id === Number(scopeId));
out.push(
<div className="blok-splitsing leerplein-splitsing" key={`leerplein-${scopeId}`}>
<div className="split-groep">{leerplein ? leerplein.naam : "Leerplein"}</div>
<div className="split-kolommen">
{[...perGroep.entries()].map(([gid, groepBlokken]) => {
const groep = groepen.find((g) => g.id === gid);
return (
<div className="split-kolom" key={gid}>
<div className="split-kop">{groep ? groep.naam : "Groep"}</div>
{groepBlokken.map((b) => renderRoosterBlok(b, iso, kal))}
</div>
);
})}
</div>
</div>
);
return out;
}
perGroep.forEach((groepBlokken, gid) => {
const heeftSplitsing = groepBlokken.some((b) => b.subgroep);
if (!heeftSplitsing) {
groepBlokken.forEach((b) => out.push(renderRoosterBlok(b, iso, kal)));
return;
}
const groep = groepen.find((g) => g.id === gid);
const heleGroep = groepBlokken.filter((b) => b.groep === gid);
const subIds = [...new Set(groepBlokken.filter((b) => b.subgroep).map((b) => b.subgroep))];
out.push(
<div className="blok-splitsing" key={`split-${gid}`}>
<div className="split-groep">{groep ? groep.naam : "Groep"}</div>
<div className="split-kolommen">
{heleGroep.length > 0 && (
<div className="split-kolom">
<div className="split-kop">Hele groep</div>
{heleGroep.map((b) => renderRoosterBlok(b, iso, kal))}
</div>
)}
{subIds.map((sid) => {
const sub = subgroepen.find((s) => s.id === sid);
const blocks = groepBlokken.filter((b) => b.subgroep === sid);
return (
<div className="split-kolom" key={sid}>
<div className="split-kop">{sub ? sub.naam : "Subgroep"}</div>
{blocks.map((b) => renderRoosterBlok(b, iso, kal))}
</div>
);
})}
</div>
</div>
);
});
return out;
}
return (
<section className="rooster-scherm">
@ -1698,6 +1859,7 @@ export function RoosterScherm({ mode }) {
{edit && (
<div className="tabbar no-print">
<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 === "schooljaar" ? "tab on" : "tab"} onClick={() => setTab("schooljaar")}>Schooljaar</button>
<button className={tab === "instellingen" ? "tab on" : "tab"} onClick={() => setTab("instellingen")}>Instellingen</button>
@ -1775,38 +1937,7 @@ export function RoosterScherm({ mode }) {
key={t.id}
>
<div className="slot-tijd">{t.start_tijd?.slice(0, 5)} {t.naam}</div>
{slotBlokken.map((b) => {
const uit = uitzOp(b.id, iso);
const vervallen = !!kal || (uit && uit.vervalt);
return (
<div
className={(vervallen ? "rooster-blok vervallen" : "rooster-blok") + (edit ? " klikbaar" : "")}
key={b.id}
style={{ borderLeftColor: b.activiteit_kleur }}
onClick={edit ? () => setBewerkBlok(b) : undefined}
title={edit ? "Klik om te bewerken" : undefined}
>
<div className="rb-top">
{edit && <span className="rb-greep" onPointerDown={(e) => startDrag(e, b)} onClick={(e) => e.stopPropagation()} title="Sleep naar ander tijdslot"></span>}
<strong>{b.activiteit_naam}</strong>
{edit && <button className="link danger rb-x" onClick={(e) => { e.stopPropagation(); verwijder(b.id); }} title="Blok verwijderen">×</button>}
</div>
<div className="small">🎯 {b.doel_naam} <span className="muted">({b.doel_type})</span></div>
{b.locatie_naam && <div className="small">📍 {b.locatie_naam}</div>}
{b.begeleiders_namen.length > 0 && (
<div className="small muted">👤 {b.begeleiders_namen.map((x, idx) => (
<span key={x.id} className={persoonAfwezig(x.id, iso) ? "afwezig" : ""}>{idx > 0 ? ", " : ""}{x.naam}{rolAfk(x.id) ? ` (${rolAfk(x.id)})` : ""}</span>
))}</div>
)}
{kal && <div className="small rb-status">Vervalt {kal.type_label}</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>}
{edit && !kal && !uit && (
<button className="link danger small rb-verval no-print" onClick={(e) => { e.stopPropagation(); laatVervallen(b, iso); }}>dit vak laten vervallen</button>
)}
</div>
);
})}
{renderSlotBlokken(slotBlokken, iso, kal)}
</div>
);
})}
@ -1818,6 +1949,20 @@ export function RoosterScherm({ mode }) {
{schooljaar && tab === "conflicten" && <Conflicten schooljaar={schooljaar} />}
{edit && schooljaar && tab === "indeling" && (
<Groepsindeling
groepen={groepen}
leerpleinen={leerpleinen}
subgroepen={subgroepen}
medewerkers={medewerkers}
blokken={blokken}
onWijzig={async () => {
setSubgroepen(await listResource("subgroepen"));
await laadBlokken();
}}
/>
)}
{tab === "schooljaar" && (
<div className="instellingen">
<p className="muted">Maak hier snel een schooljaar aan en beheer de kalender (studiedagen, vrije dagen, vakanties).</p>

View file

@ -153,11 +153,21 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
/* --- Groepsindeling --- */
.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-groepen { display: grid; gap: 14px; margin: 12px 0 16px; }
.indeling-groep { border: 1px solid var(--line); border-radius: 8px; background: #fafbff; padding: 10px; }
.indeling-groep-kop { font-weight: 700; margin-bottom: 8px; color: var(--brand); }
.indeling-kolommen { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin: 12px 0 16px; }
.indeling-kolom { border: 1px solid var(--line); border-top: 3px solid var(--brand); border-radius: 10px; padding: 10px 12px; background: var(--panel); display: flex; flex-direction: column; gap: 4px; }
.indeling-kop { font-weight: 600; margin-bottom: 2px; }
@media (max-width: 700px) { .indeling-kolommen { grid-template-columns: 1fr; } }
.blok-splitsing { border: 1px solid var(--line); border-radius: 8px; background: #fafbff; padding: 6px; margin-bottom: 8px; }
.split-groep { font-size: 12px; font-weight: 700; color: var(--brand); margin: 0 2px 6px; }
.split-kolommen { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 6px; align-items: stretch; }
.split-kolom { min-width: 0; }
.split-kop { font-size: 11px; font-weight: 700; color: var(--muted); margin: 0 0 4px; }
.split-kolom .rooster-blok { margin-bottom: 0; }
/* --- Blok-bewerker (modal) --- */
.modal-overlay { position: fixed; inset: 0; background: rgba(15,23,42,.45); display: flex; align-items: flex-start; justify-content: center; padding: 40px 16px; z-index: 900; overflow: auto; }
.modal { background: var(--panel); border-radius: 12px; padding: 18px 20px; width: min(560px, 100%); box-shadow: 0 20px 50px rgba(0,0,0,.3); }