|
|
|
@ -1547,6 +1547,8 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|
|
|
doel: blok.groep || blok.subgroep || blok.leerling || "",
|
|
|
|
doel: blok.groep || blok.subgroep || blok.leerling || "",
|
|
|
|
begeleiders: [...(blok.begeleiders || [])],
|
|
|
|
begeleiders: [...(blok.begeleiders || [])],
|
|
|
|
opmerkingen: blok.opmerkingen || "",
|
|
|
|
opmerkingen: blok.opmerkingen || "",
|
|
|
|
|
|
|
|
geldig_van: blok.geldig_van || "",
|
|
|
|
|
|
|
|
geldig_tot: blok.geldig_tot || "",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const [kopieDoelen, setKopieDoelen] = useState([]);
|
|
|
|
const [kopieDoelen, setKopieDoelen] = useState([]);
|
|
|
|
const [error, setError] = useState(null);
|
|
|
|
const [error, setError] = useState(null);
|
|
|
|
@ -1593,7 +1595,7 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|
|
|
if (splitSubs.length === 0) return;
|
|
|
|
if (splitSubs.length === 0) return;
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
for (const sid of splitSubs) {
|
|
|
|
for (const sid of splitSubs) {
|
|
|
|
await createResource("roosterblokken", { schooljaar: blok.schooljaar, tijdslot: f.tijdslot, activiteit: f.activiteit, locatie: f.locatie || null, lokaal: f.lokaal || 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, lokaal: f.lokaal || null, subgroep: sid, begeleiders: f.begeleiders, opmerkingen: f.opmerkingen , geldig_van: f.geldig_van || null, geldig_tot: f.geldig_tot || null });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (verwijderOrig) await deleteResource("roosterblokken", blok.id);
|
|
|
|
if (verwijderOrig) await deleteResource("roosterblokken", blok.id);
|
|
|
|
await onKlaar();
|
|
|
|
await onKlaar();
|
|
|
|
@ -1607,13 +1609,13 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|
|
|
leerling: f.doel_type === "leerling" ? f.doel : null,
|
|
|
|
leerling: f.doel_type === "leerling" ? f.doel : null,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
async function opslaan() {
|
|
|
|
async function opslaan() {
|
|
|
|
try { await updateResource("roosterblokken", blok.id, { tijdslot: f.tijdslot, activiteit: f.activiteit, locatie: f.locatie || null, lokaal: f.lokaal || 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, lokaal: f.lokaal || null, ...doelPayload(), begeleiders: f.begeleiders, opmerkingen: f.opmerkingen , geldig_van: f.geldig_van || null, geldig_tot: f.geldig_tot || null }); await onKlaar(); }
|
|
|
|
catch (e) { setError(e.message); }
|
|
|
|
catch (e) { setError(e.message); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async function kopieer() {
|
|
|
|
async function kopieer() {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
for (const ts of kopieDoelen) {
|
|
|
|
for (const ts of kopieDoelen) {
|
|
|
|
await createResource("roosterblokken", { schooljaar: blok.schooljaar, tijdslot: ts, activiteit: f.activiteit, locatie: f.locatie || null, lokaal: f.lokaal || null, ...doelPayload(), begeleiders: f.begeleiders, opmerkingen: f.opmerkingen });
|
|
|
|
await createResource("roosterblokken", { schooljaar: blok.schooljaar, tijdslot: ts, activiteit: f.activiteit, locatie: f.locatie || null, lokaal: f.lokaal || null, ...doelPayload(), begeleiders: f.begeleiders, opmerkingen: f.opmerkingen , geldig_van: f.geldig_van || null, geldig_tot: f.geldig_tot || null });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await onKlaar();
|
|
|
|
await onKlaar();
|
|
|
|
} catch (e) { setError(e.message); }
|
|
|
|
} catch (e) { setError(e.message); }
|
|
|
|
@ -1654,6 +1656,8 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|
|
|
{lokalen.map((l) => <option key={l.id} value={l.id}>{l.naam}</option>)}
|
|
|
|
{lokalen.map((l) => <option key={l.id} value={l.id}>{l.naam}</option>)}
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
<input placeholder="Opmerking" value={f.opmerkingen} onChange={(e) => setF({ ...f, opmerkingen: e.target.value })} />
|
|
|
|
<input placeholder="Opmerking" value={f.opmerkingen} onChange={(e) => setF({ ...f, opmerkingen: e.target.value })} />
|
|
|
|
|
|
|
|
<input type="date" title="Geldig vanaf (leeg = altijd)" value={f.geldig_van} onChange={(e) => setF({ ...f, geldig_van: e.target.value })} />
|
|
|
|
|
|
|
|
<input type="date" title="Geldig t/m (leeg = altijd)" value={f.geldig_tot} onChange={(e) => setF({ ...f, geldig_tot: e.target.value })} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<details className="editor-detail">
|
|
|
|
<details className="editor-detail">
|
|
|
|
@ -1883,7 +1887,7 @@ export function RoosterScherm({ mode }) {
|
|
|
|
// Blok bewerken/kopiëren (alleen in de Roostermaker).
|
|
|
|
// Blok bewerken/kopiëren (alleen in de Roostermaker).
|
|
|
|
const [bewerkBlok, setBewerkBlok] = useState(null);
|
|
|
|
const [bewerkBlok, setBewerkBlok] = useState(null);
|
|
|
|
|
|
|
|
|
|
|
|
const leeg = { tijdslot: "", activiteit: "", locatie: "", lokaal: "", doel_type: "groep", doel: "", begeleiders: [], opmerkingen: "" };
|
|
|
|
const leeg = { tijdslot: "", activiteit: "", locatie: "", lokaal: "", doel_type: "groep", doel: "", begeleiders: [], opmerkingen: "", geldig_van: "", geldig_tot: "" };
|
|
|
|
const [form, setForm] = useState(leeg);
|
|
|
|
const [form, setForm] = useState(leeg);
|
|
|
|
const [splitNieuwBlok, setSplitNieuwBlok] = useState(false);
|
|
|
|
const [splitNieuwBlok, setSplitNieuwBlok] = useState(false);
|
|
|
|
const [splitNieuwDoelen, setSplitNieuwDoelen] = useState([]);
|
|
|
|
const [splitNieuwDoelen, setSplitNieuwDoelen] = useState([]);
|
|
|
|
@ -2025,7 +2029,7 @@ export function RoosterScherm({ mode }) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async function opslaan(e) {
|
|
|
|
async function opslaan(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.preventDefault();
|
|
|
|
const basis = { schooljaar, tijdslot: form.tijdslot, activiteit: form.activiteit, locatie: form.locatie || null, lokaal: form.lokaal || null, begeleiders: form.begeleiders, opmerkingen: form.opmerkingen };
|
|
|
|
const basis = { schooljaar, tijdslot: form.tijdslot, activiteit: form.activiteit, locatie: form.locatie || null, lokaal: form.lokaal || null, begeleiders: form.begeleiders, opmerkingen: form.opmerkingen, geldig_van: form.geldig_van || null, geldig_tot: form.geldig_tot || null };
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (form.doel_type === "groep" && splitNieuwBlok && splitNieuwDoelen.length > 0) {
|
|
|
|
if (form.doel_type === "groep" && splitNieuwBlok && splitNieuwDoelen.length > 0) {
|
|
|
|
for (const subgroep of splitNieuwDoelen) {
|
|
|
|
for (const subgroep of splitNieuwDoelen) {
|
|
|
|
@ -2064,6 +2068,37 @@ export function RoosterScherm({ mode }) {
|
|
|
|
catch (err) { setError(err.message); }
|
|
|
|
catch (err) { setError(err.message); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Geldt dit blok op deze datum (periode / eenmalig)?
|
|
|
|
|
|
|
|
function blokGeldigOp(b, iso) {
|
|
|
|
|
|
|
|
if (b.geldig_van && iso < b.geldig_van) return false;
|
|
|
|
|
|
|
|
if (b.geldig_tot && iso > b.geldig_tot) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maak één specifieke dag leeg: laat alle blokken die dag vervallen (alleen
|
|
|
|
|
|
|
|
// deze datum), zonder gevolgen voor andere dagen.
|
|
|
|
|
|
|
|
async function leegDag(iso) {
|
|
|
|
|
|
|
|
const ourWd = (new Date(iso).getDay() + 6) % 7;
|
|
|
|
|
|
|
|
const dagBlokken = blokken.filter((b) => {
|
|
|
|
|
|
|
|
const t = tijdsloten.find((x) => x.id === b.tijdslot);
|
|
|
|
|
|
|
|
if (!t || t.dag !== ourWd || !blokGeldigOp(b, iso)) return false;
|
|
|
|
|
|
|
|
const u = uitzOp(b.id, iso);
|
|
|
|
|
|
|
|
return !(u && u.vervalt);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if (dagBlokken.length === 0) { setError("Geen blokken om te laten vervallen op deze dag."); return; }
|
|
|
|
|
|
|
|
if (!confirm(`Alle ${dagBlokken.length} blok(ken) op ${iso} laten vervallen? Dit geldt alleen voor deze dag.`)) return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
for (const b of dagBlokken) await createResource("blok-uitzonderingen", { roosterblok: b.id, datum: iso, vervalt: true });
|
|
|
|
|
|
|
|
await laadWeek();
|
|
|
|
|
|
|
|
} catch (err) { setError(err.message); }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Voeg een eenmalig (alleen vandaag geldig) blok toe op deze dag.
|
|
|
|
|
|
|
|
function openEenmalig(iso) {
|
|
|
|
|
|
|
|
setBewerkBlok(null);
|
|
|
|
|
|
|
|
setForm({ ...leeg, geldig_van: iso, geldig_tot: iso });
|
|
|
|
|
|
|
|
resetNieuweSplit();
|
|
|
|
|
|
|
|
setToonForm(true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
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);
|
|
|
|
@ -2357,6 +2392,12 @@ export function RoosterScherm({ mode }) {
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
<input placeholder="Opmerking (optioneel)" value={form.opmerkingen} onChange={(e) => setForm({ ...form, opmerkingen: e.target.value })} />
|
|
|
|
<input placeholder="Opmerking (optioneel)" value={form.opmerkingen} onChange={(e) => setForm({ ...form, opmerkingen: e.target.value })} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="form-row periode-row">
|
|
|
|
|
|
|
|
<label className="veld-kol"><span className="veld-kop">Geldig vanaf (optioneel)</span><input type="date" value={form.geldig_van} onChange={(e) => setForm({ ...form, geldig_van: e.target.value })} /></label>
|
|
|
|
|
|
|
|
<label className="veld-kol"><span className="veld-kop">Geldig t/m (optioneel)</span><input type="date" value={form.geldig_tot} onChange={(e) => setForm({ ...form, geldig_tot: e.target.value })} /></label>
|
|
|
|
|
|
|
|
{form.geldig_van && form.geldig_van === form.geldig_tot && <span className="muted small periode-hint">eenmalig op {form.geldig_van}</span>}
|
|
|
|
|
|
|
|
{(form.geldig_van || form.geldig_tot) && <button type="button" className="link small" onClick={() => setForm({ ...form, geldig_van: "", geldig_tot: "" })}>wis periode</button>}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<details className="editor-detail" open>
|
|
|
|
<details className="editor-detail" open>
|
|
|
|
<summary>Doelgroep</summary>
|
|
|
|
<summary>Doelgroep</summary>
|
|
|
|
@ -2430,12 +2471,16 @@ export function RoosterScherm({ mode }) {
|
|
|
|
{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
|
|
|
|
{edit && (kal
|
|
|
|
? <button type="button" className="icon-btn no-print dag-actie" title="Weer lesdag maken" onClick={() => dagHerstel(kal.id)}>↺</button>
|
|
|
|
? <button type="button" className="icon-btn no-print dag-actie" title="Weer lesdag maken" onClick={() => dagHerstel(kal.id)}>↺</button>
|
|
|
|
: <button type="button" className="icon-btn danger no-print dag-actie" title="Dag laten vervallen" onClick={() => dagVervallen(iso)}>⊘</button>
|
|
|
|
: <>
|
|
|
|
|
|
|
|
<button type="button" className="icon-btn danger no-print dag-actie" title="Hele dag laten vervallen (vrije dag)" onClick={() => dagVervallen(iso)}>⊘</button>
|
|
|
|
|
|
|
|
<button type="button" className="icon-btn no-print dag-actie" title="Dag leegmaken (alleen deze dag)" onClick={() => leegDag(iso)}>🧹</button>
|
|
|
|
|
|
|
|
<button type="button" className="icon-btn no-print dag-actie" title="Eenmalig blok op deze dag toevoegen" onClick={() => openEenmalig(iso)}>➕</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) => {
|
|
|
|
const slotBlokken = zichtbareBlokken.filter((b) => b.tijdslot === t.id);
|
|
|
|
const slotBlokken = zichtbareBlokken.filter((b) => b.tijdslot === t.id && blokGeldigOp(b, iso));
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
<div
|
|
|
|
className={hoverSlot === t.id ? "slot-cel hover" : "slot-cel"}
|
|
|
|
className={hoverSlot === t.id ? "slot-cel hover" : "slot-cel"}
|
|
|
|
@ -2496,6 +2541,9 @@ export function UrenPage() {
|
|
|
|
const [data, setData] = useState(null);
|
|
|
|
const [data, setData] = useState(null);
|
|
|
|
const [error, setError] = useState(null);
|
|
|
|
const [error, setError] = useState(null);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
const [groepFilter, setGroepFilter] = useState("");
|
|
|
|
|
|
|
|
const [vakFilter, setVakFilter] = useState("");
|
|
|
|
|
|
|
|
const [leerlingFilter, setLeerlingFilter] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
listResource("schooljaren")
|
|
|
|
listResource("schooljaren")
|
|
|
|
@ -2511,16 +2559,43 @@ export function UrenPage() {
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
}, [schooljaar]);
|
|
|
|
}, [schooljaar]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const r1 = (n) => Math.round(n * 10) / 10;
|
|
|
|
|
|
|
|
const groepNamen = data ? [...new Set(data.per_groep.map((g) => g.groep))].sort() : [];
|
|
|
|
|
|
|
|
const vakNamen = data ? [...new Set(data.per_groep.flatMap((g) => g.vakken.map((v) => v.vak)))].sort() : [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const perGroep = (data ? data.per_groep : [])
|
|
|
|
|
|
|
|
.filter((g) => !groepFilter || g.groep === groepFilter)
|
|
|
|
|
|
|
|
.map((g) => { const vakken = vakFilter ? g.vakken.filter((v) => v.vak === vakFilter) : g.vakken; return { ...g, vakken, totaal: r1(vakken.reduce((a, v) => a + v.uren, 0)) }; })
|
|
|
|
|
|
|
|
.filter((g) => g.vakken.length > 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const perLeerling = (data ? data.per_leerling : [])
|
|
|
|
|
|
|
|
.filter((l) => !leerlingFilter || l.leerling.toLowerCase().includes(leerlingFilter.toLowerCase()))
|
|
|
|
|
|
|
|
.filter((l) => !groepFilter || l.groep === groepFilter)
|
|
|
|
|
|
|
|
.filter((l) => !vakFilter || l.vakken.some((v) => v.vak === vakFilter))
|
|
|
|
|
|
|
|
.map((l) => { const vakken = vakFilter ? l.vakken.filter((v) => v.vak === vakFilter) : l.vakken; return { ...l, vakkenWeergave: vakken }; });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const filterActief = groepFilter || vakFilter || leerlingFilter;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<section>
|
|
|
|
<section>
|
|
|
|
<h1>Urenoverzicht</h1>
|
|
|
|
<h1>Urenoverzicht</h1>
|
|
|
|
<p className="muted">Lesuren per groep en per leerling over het hele schooljaar. Vrije dagen, vakanties en studiedagen tellen niet mee; vervallen blokken zijn afgetrokken. Stage-uren tellen mee bij de leerling.</p>
|
|
|
|
<p className="muted">Lesuren per groep en per leerling over het hele schooljaar. Vrije dagen, vakanties en studiedagen tellen niet mee; vervallen blokken zijn afgetrokken. Stage-uren tellen mee bij de leerling.</p>
|
|
|
|
<Foutmelding msg={error} />
|
|
|
|
<Foutmelding msg={error} />
|
|
|
|
<div className="form-row">
|
|
|
|
<div className="form-row uren-filters">
|
|
|
|
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
|
|
|
|
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
|
|
|
|
<option value="">— schooljaar —</option>
|
|
|
|
<option value="">— schooljaar —</option>
|
|
|
|
{schooljaren.map((j) => <option key={j.id} value={j.id}>{j.naam}</option>)}
|
|
|
|
{schooljaren.map((j) => <option key={j.id} value={j.id}>{j.naam}</option>)}
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
|
|
|
|
<select value={groepFilter} onChange={(e) => setGroepFilter(e.target.value)}>
|
|
|
|
|
|
|
|
<option value="">alle groepen</option>
|
|
|
|
|
|
|
|
{groepNamen.map((n) => <option key={n} value={n}>{n}</option>)}
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
<select value={vakFilter} onChange={(e) => setVakFilter(e.target.value)}>
|
|
|
|
|
|
|
|
<option value="">alle vakken</option>
|
|
|
|
|
|
|
|
{vakNamen.map((n) => <option key={n} value={n}>{n}</option>)}
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
<input placeholder="zoek leerling…" value={leerlingFilter} onChange={(e) => setLeerlingFilter(e.target.value)} />
|
|
|
|
|
|
|
|
{filterActief && <button type="button" className="link small" onClick={() => { setGroepFilter(""); setVakFilter(""); setLeerlingFilter(""); }}>wis filters</button>}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{loading ? <p>Laden…</p> : data && (
|
|
|
|
{loading ? <p>Laden…</p> : data && (
|
|
|
|
<>
|
|
|
|
<>
|
|
|
|
@ -2528,8 +2603,8 @@ export function UrenPage() {
|
|
|
|
<table className="grid">
|
|
|
|
<table className="grid">
|
|
|
|
<thead><tr><th>Groep</th><th>Vak</th><th>Uren (schooljaar)</th></tr></thead>
|
|
|
|
<thead><tr><th>Groep</th><th>Vak</th><th>Uren (schooljaar)</th></tr></thead>
|
|
|
|
<tbody>
|
|
|
|
<tbody>
|
|
|
|
{data.per_groep.length === 0 && <tr><td colSpan="3" className="muted">Geen lesblokken gevonden.</td></tr>}
|
|
|
|
{perGroep.length === 0 && <tr><td colSpan="3" className="muted">Geen resultaten.</td></tr>}
|
|
|
|
{data.per_groep.flatMap((g) => [
|
|
|
|
{perGroep.flatMap((g) => [
|
|
|
|
...g.vakken.map((v, i) => (
|
|
|
|
...g.vakken.map((v, i) => (
|
|
|
|
<tr key={g.groep + "-" + v.vak}>
|
|
|
|
<tr key={g.groep + "-" + v.vak}>
|
|
|
|
<td>{i === 0 ? <strong>{g.groep}</strong> : ""}</td>
|
|
|
|
<td>{i === 0 ? <strong>{g.groep}</strong> : ""}</td>
|
|
|
|
@ -2547,12 +2622,12 @@ export function UrenPage() {
|
|
|
|
<table className="grid">
|
|
|
|
<table className="grid">
|
|
|
|
<thead><tr><th>Leerling</th><th>Groep</th><th>Vakken</th><th>Stage</th><th>Totaal</th></tr></thead>
|
|
|
|
<thead><tr><th>Leerling</th><th>Groep</th><th>Vakken</th><th>Stage</th><th>Totaal</th></tr></thead>
|
|
|
|
<tbody>
|
|
|
|
<tbody>
|
|
|
|
{data.per_leerling.length === 0 && <tr><td colSpan="5" className="muted">Geen leerlingen met uren.</td></tr>}
|
|
|
|
{perLeerling.length === 0 && <tr><td colSpan="5" className="muted">Geen resultaten.</td></tr>}
|
|
|
|
{data.per_leerling.map((l) => (
|
|
|
|
{perLeerling.map((l) => (
|
|
|
|
<tr key={l.leerling}>
|
|
|
|
<tr key={l.leerling}>
|
|
|
|
<td>{l.leerling}</td>
|
|
|
|
<td>{l.leerling}</td>
|
|
|
|
<td className="muted">{l.groep || "—"}</td>
|
|
|
|
<td className="muted">{l.groep || "—"}</td>
|
|
|
|
<td className="muted small">{l.vakken.map((v) => `${v.vak} ${v.uren}u`).join(", ") || "—"}</td>
|
|
|
|
<td className="muted small">{l.vakkenWeergave.map((v) => `${v.vak} ${v.uren}u`).join(", ") || "—"}</td>
|
|
|
|
<td>{l.stage_uren ? `${l.stage_uren} u` : "—"}</td>
|
|
|
|
<td>{l.stage_uren ? `${l.stage_uren} u` : "—"}</td>
|
|
|
|
<td><strong>{l.totaal} u</strong></td>
|
|
|
|
<td><strong>{l.totaal} u</strong></td>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
|