Verbeter UI van roostermaker
This commit is contained in:
parent
32da6e79c7
commit
8f62734af3
2 changed files with 219 additions and 64 deletions
|
|
@ -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>
|
||||
{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">👤 {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">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>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue