Koppelingen: personeel aan subgroep/leerplein, subgroep->groep, leerplein-medewerkers; inzetregel begeleider-vak (klassenondersteuner overal, leerkracht ook theorievakken)

This commit is contained in:
bes-r 2026-06-14 13:58:06 +02:00
parent e39f65706e
commit 4e461a9471
9 changed files with 211 additions and 46 deletions

View file

@ -50,15 +50,16 @@ volgt.
## Koppelingen & inzetregels
- [ ] **Personeel koppelen aan leerplein en subgroep** (nu alleen stamgroep
mogelijk in het personeelsformulier).
- [ ] **Theorievakken automatisch beschikbaar voor leerkrachten** (een leerkracht
mag altijd op een theorievak ingezet worden).
- [ ] **Inzetregel begeleider ↔ vak:** een leerkracht of praktijkondersteuner kan
alleen aan een vak/blok gekoppeld worden als hij/zij aan dat vak gekoppeld
is; een **klassenondersteuner kan overal** ingezet worden.
- [ ] **Hiërarchie vastleggen:** subgroepen verbonden aan groepen, en groepen aan
leerpleinen (subgroep → groep → leerplein).
- [x] **Personeel koppelen aan leerplein en subgroep** (personeelsformulier:
stamgroep, subgroep-begeleiding én leerpleinen).
- [x] **Theorievakken automatisch beschikbaar voor leerkrachten** (een leerkracht
mag altijd op een theorievak ingezet worden; daarnaast worden theorievakken
bij het aanmaken automatisch gekoppeld).
- [x] **Inzetregel begeleider ↔ vak:** leerkracht/praktijkondersteuner alleen op
gekoppelde vakken (leerkracht bovendien altijd op theorievakken);
**klassenondersteuner overal**.
- [x] **Hiërarchie vastgelegd:** subgroep → groep (Subgroep.groep) en
groep → leerplein (Leerplein.groepen).
## Module: Urenoverzicht

View file

@ -31,10 +31,11 @@ class PersoonAdmin(admin.ModelAdmin):
@admin.register(Subgroep)
class SubgroepAdmin(admin.ModelAdmin):
list_display = ("naam", "omschrijving", "actief")
list_filter = ("actief",)
list_display = ("naam", "groep", "omschrijving", "actief")
list_filter = ("actief", "groep")
search_fields = ("naam",)
filter_horizontal = ("leden",)
autocomplete_fields = ("groep",)
filter_horizontal = ("leden", "begeleiders")
@admin.register(Schooljaar)

View file

@ -0,0 +1,24 @@
# Generated by Django 5.2.15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0004_activiteit_theorievak_persoon_geboortedatum_and_more'),
]
operations = [
migrations.AddField(
model_name='subgroep',
name='begeleiders',
field=models.ManyToManyField(blank=True, help_text='Medewerkers die aan deze subgroep gekoppeld zijn.', related_name='begeleide_subgroepen', to='core.persoon', verbose_name='begeleiders'),
),
migrations.AddField(
model_name='subgroep',
name='groep',
field=models.ForeignKey(blank=True, help_text='De groep waar deze subgroep onder valt.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subgroepen', to='core.groep', verbose_name='groep'),
),
]

View file

@ -38,6 +38,10 @@ class PersoonSerializer(serializers.ModelSerializer):
many=True, queryset=Activiteit.objects.all(), required=False
)
vakken_namen = serializers.SerializerMethodField()
begeleide_subgroepen = serializers.PrimaryKeyRelatedField(
many=True, queryset=Subgroep.objects.all(), required=False
)
begeleide_subgroepen_namen = serializers.SerializerMethodField()
class Meta:
model = Persoon
@ -46,6 +50,7 @@ class PersoonSerializer(serializers.ModelSerializer):
"rol", "rol_label", "groep", "groep_naam",
"stamgroep", "stamgroep_naam",
"functies", "functies_namen", "vakken", "vakken_namen",
"begeleide_subgroepen", "begeleide_subgroepen_namen",
"geboortedatum", "actief", "opmerkingen", "aangemaakt_op",
]
read_only_fields = ["aangemaakt_op"]
@ -56,13 +61,17 @@ class PersoonSerializer(serializers.ModelSerializer):
def get_vakken_namen(self, obj):
return [{"id": a.id, "naam": a.naam} for a in obj.vakken.all()]
def get_begeleide_subgroepen_namen(self, obj):
return [{"id": s.id, "naam": s.naam} for s in obj.begeleide_subgroepen.all()]
def validate(self, data):
# Functies/vakken horen alleen bij medewerkers; een leerling krijgt er geen.
# Functies/vakken/subgroep-begeleiding horen alleen bij medewerkers.
# (De M2M wordt door DRF ná save() gezet, dus hier afdwingen.)
rol = data.get("rol", getattr(self.instance, "rol", None))
if rol == "leerling":
data["functies"] = []
data["vakken"] = []
data["begeleide_subgroepen"] = []
return data
@staticmethod
@ -73,17 +82,24 @@ class PersoonSerializer(serializers.ModelSerializer):
return gekozen + [a for a in theorie if a.id not in ids]
def create(self, validated_data):
# Een leerkracht krijgt de theorievakken standaard direct gekoppeld.
# begeleide_subgroepen is een reverse-M2M: apart afhandelen na save.
subg = validated_data.pop("begeleide_subgroepen", None)
if validated_data.get("rol") == "leerkracht":
validated_data["vakken"] = self._met_theorievakken(validated_data.get("vakken", []))
return super().create(validated_data)
instance = super().create(validated_data)
if subg is not None:
instance.begeleide_subgroepen.set(subg)
return instance
def update(self, instance, validated_data):
# Ook bij bewerken houdt een leerkracht altijd de theorievakken.
subg = validated_data.pop("begeleide_subgroepen", "__keep__")
rol = validated_data.get("rol", instance.rol)
if rol == "leerkracht" and "vakken" in validated_data:
validated_data["vakken"] = self._met_theorievakken(validated_data["vakken"])
return super().update(instance, validated_data)
instance = super().update(instance, validated_data)
if subg != "__keep__":
instance.begeleide_subgroepen.set(subg)
return instance
class SubgroepSerializer(serializers.ModelSerializer):
@ -92,12 +108,19 @@ class SubgroepSerializer(serializers.ModelSerializer):
)
leden_namen = serializers.SerializerMethodField()
aantal_leden = serializers.SerializerMethodField()
groep_naam = serializers.CharField(source="groep.naam", read_only=True, default=None)
begeleiders = serializers.PrimaryKeyRelatedField(
many=True, queryset=Persoon.objects.exclude(rol="leerling"), required=False
)
begeleiders_namen = serializers.SerializerMethodField()
class Meta:
model = Subgroep
fields = [
"id", "naam", "omschrijving", "actief",
"leden", "leden_namen", "aantal_leden", "aangemaakt_op",
"groep", "groep_naam",
"leden", "leden_namen", "aantal_leden",
"begeleiders", "begeleiders_namen", "aangemaakt_op",
]
read_only_fields = ["aangemaakt_op"]
@ -107,6 +130,9 @@ class SubgroepSerializer(serializers.ModelSerializer):
def get_aantal_leden(self, obj):
return obj.leden.count()
def get_begeleiders_namen(self, obj):
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.begeleiders.all()]
# --- Fase 2 ---------------------------------------------------------------
@ -207,13 +233,31 @@ class RoosterblokSerializer(serializers.ModelSerializer):
raise serializers.ValidationError(
"Kies precies één doelgroep: groep, subgroep of leerling."
)
# Begeleiders zijn medewerkers; een leerling kan geen begeleider zijn.
# (Het Roosterblok-model kent geen rolbeperking, dus hier afdwingen.)
for p in data.get("begeleiders", []):
# Inzetregels voor begeleiders t.o.v. het vak (de activiteit):
# - een leerling kan geen begeleider zijn;
# - een klassenondersteuner kan overal worden ingezet;
# - een leerkracht mag op theorievakken én op de aan hem/haar gekoppelde
# vakken; een praktijkondersteuner alleen op de gekoppelde vakken.
activiteit = data.get("activiteit", getattr(self.instance, "activiteit", None))
begeleiders = data.get("begeleiders", None)
if begeleiders is None and self.instance is not None:
begeleiders = list(self.instance.begeleiders.all())
for p in (begeleiders or []):
if p.rol == "leerling":
raise serializers.ValidationError(
f"'{p.volledige_naam}' is een leerling en kan geen begeleider zijn."
)
if p.rol == "klassenondersteuner":
continue
if activiteit is None:
continue
mag = p.vakken.filter(pk=activiteit.pk).exists()
if p.rol == "leerkracht" and getattr(activiteit, "theorievak", False):
mag = True
if not mag:
raise serializers.ValidationError(
f"'{p.volledige_naam}' is niet gekoppeld aan het vak '{activiteit.naam}'."
)
return data

View file

@ -8,7 +8,7 @@ class LeerpleinAdmin(admin.ModelAdmin):
list_display = ("naam", "omschrijving", "actief")
list_filter = ("actief",)
search_fields = ("naam",)
filter_horizontal = ("groepen", "subgroepen", "leerlingen")
filter_horizontal = ("groepen", "subgroepen", "leerlingen", "medewerkers")
@admin.register(SubgroepGroep)

View file

@ -0,0 +1,19 @@
# Generated by Django 5.2.15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_subgroep_begeleiders_subgroep_groep'),
('module_leerplein', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='leerplein',
name='medewerkers',
field=models.ManyToManyField(blank=True, related_name='leerpleinen_medewerker', to='core.persoon', verbose_name='medewerkers'),
),
]

View file

@ -26,6 +26,10 @@ class Leerplein(models.Model):
"core.Persoon", verbose_name="losse leerlingen", related_name="leerpleinen",
blank=True, limit_choices_to={"rol": "leerling"},
)
medewerkers = models.ManyToManyField(
"core.Persoon", verbose_name="medewerkers", related_name="leerpleinen_medewerker",
blank=True,
)
aangemaakt_op = models.DateTimeField(auto_now_add=True)
class Meta:

View file

@ -15,16 +15,20 @@ class LeerpleinSerializer(serializers.ModelSerializer):
leerlingen = serializers.PrimaryKeyRelatedField(
many=True, queryset=Persoon.objects.filter(rol="leerling"), required=False
)
medewerkers = serializers.PrimaryKeyRelatedField(
many=True, queryset=Persoon.objects.exclude(rol="leerling"), required=False
)
groepen_namen = serializers.SerializerMethodField()
subgroepen_namen = serializers.SerializerMethodField()
leerlingen_namen = serializers.SerializerMethodField()
medewerkers_namen = serializers.SerializerMethodField()
class Meta:
model = Leerplein
fields = [
"id", "naam", "omschrijving", "actief",
"groepen", "subgroepen", "leerlingen",
"groepen_namen", "subgroepen_namen", "leerlingen_namen",
"groepen", "subgroepen", "leerlingen", "medewerkers",
"groepen_namen", "subgroepen_namen", "leerlingen_namen", "medewerkers_namen",
"aangemaakt_op",
]
read_only_fields = ["aangemaakt_op"]
@ -38,6 +42,9 @@ class LeerpleinSerializer(serializers.ModelSerializer):
def get_leerlingen_namen(self, obj):
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.leerlingen.all()]
def get_medewerkers_namen(self, obj):
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.medewerkers.all()]
class SubgroepGroepSerializer(serializers.ModelSerializer):
groep_naam = serializers.CharField(source="groep.naam", read_only=True)

View file

@ -262,19 +262,36 @@ export function PersonenPage() {
const [groepen, setGroepen] = useState([]);
const [functies, setFuncties] = useState([]);
const [vakken, setVakken] = useState([]);
const [subgroepen, setSubgroepen] = useState([]);
const [leerpleinen, setLeerpleinen] = useState([]);
// Twee losse flows: medewerker en leerling.
const [modus, setModus] = useState("leerling");
const leegMedewerker = { voornaam: "", achternaam: "", rol: "leerkracht", stamgroep: "", functies: [], vakken: [] };
const leegMedewerker = { voornaam: "", achternaam: "", rol: "leerkracht", stamgroep: "", functies: [], vakken: [], begeleide_subgroepen: [], leerpleinen: [] };
const leegLeerling = { voornaam: "", achternaam: "", groep: "", geboortedatum: "" };
const [form, setForm] = useState(leegLeerling);
const [editId, setEditId] = useState(null);
const [filter, setFilter] = useState("");
useEffect(() => {
function laadHulplijsten() {
listResource("groepen").then(setGroepen).catch(() => {});
listResource("functies").then(setFuncties).catch(() => {});
listResource("activiteiten").then(setVakken).catch(() => {});
}, []);
listResource("subgroepen").then(setSubgroepen).catch(() => {});
listResource("leerpleinen").then(setLeerpleinen).catch(() => setLeerpleinen([]));
}
useEffect(() => { laadHulplijsten(); }, []);
// Leerplein-koppeling loopt via de leerplein-API (de module bezit die relatie).
async function syncLeerpleinen(persoonId, gekozenIds) {
const huidige = await listResource("leerpleinen").catch(() => []);
for (const lp of huidige) {
const inLijst = (lp.medewerkers || []).includes(persoonId);
const moet = gekozenIds.includes(lp.id);
if (moet && !inLijst) await updateResource("leerpleinen", lp.id, { medewerkers: [...lp.medewerkers, persoonId] });
else if (!moet && inLijst) await updateResource("leerpleinen", lp.id, { medewerkers: lp.medewerkers.filter((x) => x !== persoonId) });
}
listResource("leerpleinen").then(setLeerpleinen).catch(() => {});
}
function kiesModus(m) {
setModus(m);
@ -291,14 +308,19 @@ export function PersonenPage() {
? {
voornaam: form.voornaam, achternaam: form.achternaam, rol: form.rol,
stamgroep: form.stamgroep || null, functies: form.functies, vakken: form.vakken,
begeleide_subgroepen: form.begeleide_subgroepen,
}
: {
voornaam: form.voornaam, achternaam: form.achternaam, rol: "leerling",
groep: form.groep || null, geboortedatum: form.geboortedatum || null,
};
try {
if (editId) await updateResource("personen", editId, payload);
else await createResource("personen", payload);
const opgeslagen = editId
? await updateResource("personen", editId, payload)
: await createResource("personen", payload);
if (modus === "medewerker" && opgeslagen && opgeslagen.id) {
await syncLeerpleinen(opgeslagen.id, form.leerpleinen || []);
}
setForm(modus === "medewerker" ? leegMedewerker : leegLeerling);
setEditId(null);
await reload();
@ -322,6 +344,8 @@ export function PersonenPage() {
stamgroep: p.stamgroep || "",
functies: p.functies_namen.map((x) => x.id),
vakken: p.vakken_namen.map((x) => x.id),
begeleide_subgroepen: (p.begeleide_subgroepen_namen || []).map((x) => x.id),
leerpleinen: leerpleinen.filter((lp) => (lp.medewerkers || []).includes(p.id)).map((lp) => lp.id),
});
}
}
@ -382,6 +406,21 @@ export function PersonenPage() {
{vakken.length === 0 && <span className="muted small">Nog geen vakken/activiteiten.</span>}
</div>
</div>
<div className="leden-keuze">
<div className="muted small">Subgroepen (begeleiding):</div>
<div className="chips">
{subgroepen.map((s) => <button type="button" key={s.id} className={form.begeleide_subgroepen.includes(s.id) ? "chip on" : "chip"} onClick={() => toggle("begeleide_subgroepen", s.id)}>{s.naam}</button>)}
{subgroepen.length === 0 && <span className="muted small">Nog geen subgroepen.</span>}
</div>
</div>
{leerpleinen.length > 0 && (
<div className="leden-keuze">
<div className="muted small">Leerpleinen:</div>
<div className="chips">
{leerpleinen.map((lp) => <button type="button" key={lp.id} className={form.leerpleinen.includes(lp.id) ? "chip on" : "chip"} onClick={() => toggle("leerpleinen", lp.id)}>{lp.naam}</button>)}
</div>
</div>
)}
</>
)}
@ -440,27 +479,28 @@ export function PersonenPage() {
export function SubgroepenPage() {
const { items, error, loading, reload, setError } = useList("subgroepen");
const [leerlingen, setLeerlingen] = useState([]);
const leeg = { naam: "", omschrijving: "", leden: [] };
const [groepen, setGroepen] = useState([]);
const [medewerkers, setMedewerkers] = useState([]);
const leeg = { naam: "", omschrijving: "", groep: "", leden: [], begeleiders: [] };
const [form, setForm] = useState(leeg);
const [editId, setEditId] = useState(null);
useEffect(() => {
listResource("personen", "?rol=leerling").then(setLeerlingen).catch(() => {});
listResource("groepen").then(setGroepen).catch(() => {});
listResource("personen").then((ps) => setMedewerkers(ps.filter((p) => p.rol !== "leerling"))).catch(() => {});
}, []);
function toggleLid(id) {
setForm((f) =>
f.leden.includes(id)
? { ...f, leden: f.leden.filter((x) => x !== id) }
: { ...f, leden: [...f.leden, id] }
);
function toggle(veld, id) {
setForm((f) => f[veld].includes(id) ? { ...f, [veld]: f[veld].filter((x) => x !== id) } : { ...f, [veld]: [...f[veld], id] });
}
async function opslaan(e) {
e.preventDefault();
const payload = { ...form, groep: form.groep || null };
try {
if (editId) await updateResource("subgroepen", editId, form);
else await createResource("subgroepen", form);
if (editId) await updateResource("subgroepen", editId, payload);
else await createResource("subgroepen", payload);
setForm(leeg);
setEditId(null);
await reload();
@ -481,22 +521,35 @@ export function SubgroepenPage() {
return (
<section>
<h1>Subgroepen</h1>
<p className="muted">Vrije, groep-overstijgende groepjes leerlingen (bv. niveaugroep rekenen).</p>
<p className="muted">Groepjes leerlingen binnen een groep (bv. niveaugroep rekenen), met eigen begeleiders.</p>
<Foutmelding msg={error} />
<form className="form-card" onSubmit={opslaan}>
<div className="form-row">
<input placeholder="Naam" value={form.naam} required onChange={(e) => setForm({ ...form, naam: e.target.value })} />
<input placeholder="Omschrijving (optioneel)" value={form.omschrijving} onChange={(e) => setForm({ ...form, omschrijving: e.target.value })} />
<select value={form.groep || ""} onChange={(e) => setForm({ ...form, groep: e.target.value })}>
<option value=""> groep </option>
{groepen.map((g) => <option key={g.id} value={g.id}>{g.naam}</option>)}
</select>
</div>
<div className="leden-keuze">
<div className="muted small">Leden (leerlingen):</div>
<div className="chips">
{leerlingen.map((l) => (
<button type="button" key={l.id} className={form.leden.includes(l.id) ? "chip on" : "chip"} onClick={() => toggleLid(l.id)}>{l.volledige_naam}</button>
<button type="button" key={l.id} className={form.leden.includes(l.id) ? "chip on" : "chip"} onClick={() => toggle("leden", l.id)}>{l.volledige_naam}</button>
))}
{leerlingen.length === 0 && <span className="muted small">Nog geen leerlingen ingevoerd.</span>}
</div>
</div>
<div className="leden-keuze">
<div className="muted small">Begeleiders:</div>
<div className="chips">
{medewerkers.map((m) => (
<button type="button" key={m.id} className={form.begeleiders.includes(m.id) ? "chip on" : "chip"} onClick={() => toggle("begeleiders", m.id)}>{m.volledige_naam}</button>
))}
{medewerkers.length === 0 && <span className="muted small">Nog geen medewerkers.</span>}
</div>
</div>
<div className="form-row">
<button className="btn primary" type="submit">{editId ? "Opslaan" : "Toevoegen"}</button>
{editId && <button type="button" className="btn" onClick={() => { setEditId(null); setForm(leeg); }}>Annuleren</button>}
@ -504,19 +557,21 @@ export function SubgroepenPage() {
</form>
{loading ? <p>Laden</p> : (
<table className="grid">
<thead><tr><th>Naam</th><th>Leden</th><th></th></tr></thead>
<thead><tr><th>Naam</th><th>Groep</th><th>Leden</th><th>Begeleiders</th><th></th></tr></thead>
<tbody>
{items.map((s) => (
<tr key={s.id}>
<td>{s.naam}{s.omschrijving && <div className="muted small">{s.omschrijving}</div>}</td>
<td className="muted">{s.groep_naam || "—"}</td>
<td><span className="badge">{s.aantal_leden}</span> <span className="muted small">{s.leden_namen.map((x) => x.naam).join(", ")}</span></td>
<td className="muted small">{s.begeleiders_namen.map((x) => x.naam).join(", ") || "—"}</td>
<td className="row-actions">
<button className="link" onClick={() => { setEditId(s.id); setForm({ naam: s.naam, omschrijving: s.omschrijving, leden: s.leden_namen.map((x) => x.id) }); }}>bewerken</button>
<button className="link" onClick={() => { setEditId(s.id); setForm({ naam: s.naam, omschrijving: s.omschrijving, groep: s.groep || "", leden: s.leden_namen.map((x) => x.id), begeleiders: s.begeleiders_namen.map((x) => x.id) }); }}>bewerken</button>
<button className="link danger" onClick={() => verwijder(s.id)}>verwijderen</button>
</td>
</tr>
))}
{items.length === 0 && <tr><td colSpan="3" className="muted">Nog geen subgroepen.</td></tr>}
{items.length === 0 && <tr><td colSpan="5" className="muted">Nog geen subgroepen.</td></tr>}
</tbody>
</table>
)}
@ -784,7 +839,8 @@ export function LeerpleinPage() {
const [groepen, setGroepen] = useState([]);
const [subgroepen, setSubgroepen] = useState([]);
const [leerlingen, setLeerlingen] = useState([]);
const leeg = { naam: "", omschrijving: "", groepen: [], subgroepen: [], leerlingen: [] };
const [medewerkers, setMedewerkers] = useState([]);
const leeg = { naam: "", omschrijving: "", groepen: [], subgroepen: [], leerlingen: [], medewerkers: [] };
const [form, setForm] = useState(leeg);
const [editId, setEditId] = useState(null);
@ -792,6 +848,7 @@ export function LeerpleinPage() {
listResource("groepen").then(setGroepen).catch(() => {});
listResource("subgroepen").then(setSubgroepen).catch(() => {});
listResource("personen", "?rol=leerling").then(setLeerlingen).catch(() => {});
listResource("personen").then((ps) => setMedewerkers(ps.filter((p) => p.rol !== "leerling"))).catch(() => {});
}, []);
function toggle(veld, id) {
@ -841,6 +898,13 @@ export function LeerpleinPage() {
{leerlingen.length === 0 && <span className="muted small">Geen leerlingen.</span>}
</div>
</div>
<div className="leden-keuze">
<div className="muted small">Medewerkers:</div>
<div className="chips">
{medewerkers.map((m) => <button type="button" key={m.id} className={form.medewerkers.includes(m.id) ? "chip on" : "chip"} onClick={() => toggle("medewerkers", m.id)}>{m.volledige_naam}</button>)}
{medewerkers.length === 0 && <span className="muted small">Geen medewerkers.</span>}
</div>
</div>
<div className="form-row">
<button className="btn primary" type="submit">{editId ? "Opslaan" : "Toevoegen"}</button>
{editId && <button type="button" className="btn" onClick={() => { setEditId(null); setForm(leeg); }}>Annuleren</button>}
@ -857,10 +921,11 @@ export function LeerpleinPage() {
{lp.groepen_namen.length > 0 && <div>🗂 {lp.groepen_namen.map((x) => x.naam).join(", ")}</div>}
{lp.subgroepen_namen.length > 0 && <div>🔹 {lp.subgroepen_namen.map((x) => x.naam).join(", ")}</div>}
{lp.leerlingen_namen.length > 0 && <div>👤 {lp.leerlingen_namen.map((x) => x.naam).join(", ")}</div>}
{lp.groepen_namen.length + lp.subgroepen_namen.length + lp.leerlingen_namen.length === 0 && <span className="muted">leeg</span>}
{(lp.medewerkers_namen || []).length > 0 && <div>🧑🏫 {lp.medewerkers_namen.map((x) => x.naam).join(", ")}</div>}
{lp.groepen_namen.length + lp.subgroepen_namen.length + lp.leerlingen_namen.length + (lp.medewerkers_namen || []).length === 0 && <span className="muted">leeg</span>}
</td>
<td className="row-actions">
<button className="link" onClick={() => { setEditId(lp.id); setForm({ naam: lp.naam, omschrijving: lp.omschrijving, groepen: lp.groepen_namen.map((x) => x.id), subgroepen: lp.subgroepen_namen.map((x) => x.id), leerlingen: lp.leerlingen_namen.map((x) => x.id) }); }}>bewerken</button>
<button className="link" onClick={() => { setEditId(lp.id); setForm({ naam: lp.naam, omschrijving: lp.omschrijving, groepen: lp.groepen_namen.map((x) => x.id), subgroepen: lp.subgroepen_namen.map((x) => x.id), leerlingen: lp.leerlingen_namen.map((x) => x.id), medewerkers: (lp.medewerkers_namen || []).map((x) => x.id) }); }}>bewerken</button>
<button className="link danger" onClick={() => verwijder(lp.id)}>verwijderen</button>
</td>
</tr>