diff --git a/ROADMAP.md b/ROADMAP.md
index 45e9f81..6aa9ca6 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -40,6 +40,12 @@ volgt.
## Fase 6 — Weergave & beheer
+- [x] **Printopties-dialog** (🖨 in Rooster/Roostermaker): kies wát (huidige
+ selectie / alle klassen) en hóé je afdrukt — indeling (kolommen / dagen
+ onder elkaar), vakken in kleur, begeleiding/lokaal tonen, optionele
+ koptekst — plus **papierformaat** (A4/A3/A5/Letter) en oriëntatie
+ (liggend/staand). Toegepast via body-classes + dynamische `@page`.
+
- [x] **Vakkleur als vlak** (toggle 🎨, onthouden per browser): vak volledig in de
activiteitskleur i.p.v. een streepje, met automatisch leesbare tekstkleur
(zwart/wit op basis van helderheid). Werkt in Rooster/Roostermaker en
diff --git a/frontend/src/pages.jsx b/frontend/src/pages.jsx
index 019db4a..b57d748 100644
--- a/frontend/src/pages.jsx
+++ b/frontend/src/pages.jsx
@@ -2072,6 +2072,12 @@ export function RoosterScherm({ mode }) {
const setWeergave = (v) => { setWeergaveState(v); try { localStorage.setItem("rw_weergave", v); } catch { /* niet beschikbaar */ } };
const [vakkleurVlak, setVakkleurState] = useState(() => { try { return localStorage.getItem("rw_vakkleur") === "vlak"; } catch { return false; } });
const setVakkleur = (v) => { setVakkleurState(v); try { localStorage.setItem("rw_vakkleur", v ? "vlak" : "streep"); } catch { /* niet beschikbaar */ } };
+ // Printopties
+ const [printOpen, setPrintOpen] = useState(false);
+ const [printPending, setPrintPending] = useState(false);
+ const [printAllesActief, setPrintAllesActief] = useState(false);
+ const [printKop, setPrintKop] = useState("");
+ const [printOpts, setPrintOpts] = useState({ bereik: "huidig", indeling: "kolom", kleur: false, begeleiding: true, lokaal: true, papier: "A4", orientatie: "landscape" });
useEffect(() => {
listResource("schooljaren").then((j) => { setSchooljaren(j); if (j.length) setSchooljaar((s) => s || String(j[0].id)); }).catch((e) => setError(e.message));
@@ -2096,7 +2102,9 @@ export function RoosterScherm({ mode }) {
// Scope-context + gefilterde blokken (voor weergave/afdrukken).
const scopeCtx = { leerlingen, subgroepen, leerpleinen };
const scope = { type: scopeType, id: scopeId ? Number(scopeId) : "" };
- const zichtbareBlokken = blokken.filter((b) => blokInScope(b, scope, scopeCtx));
+ // Bij printen "alle klassen" tijdelijk de scope negeren.
+ const effScope = printAllesActief ? { type: "alles", id: "" } : scope;
+ const zichtbareBlokken = blokken.filter((b) => blokInScope(b, effScope, scopeCtx));
// Conflicten over álle blokken (niet alleen de zichtbare scope), zodat de
// melding compleet is en de betrokken blokken in het rooster omlijnd worden.
const conflictInfo = berekenRoosterConflicten(blokken, scopeCtx, tijdsloten);
@@ -2194,6 +2202,34 @@ export function RoosterScherm({ mode }) {
setBewerkBlok(null);
setToonForm(true);
}
+ // Print: pas de gekozen opties toe via body-classes + @page, dan afdrukken.
+ function zetPaginaStijl(papier, orientatie) {
+ let el = document.getElementById("rw-print-page");
+ if (!el) { el = document.createElement("style"); el.id = "rw-print-page"; document.head.appendChild(el); }
+ el.textContent = `@page { size: ${papier} ${orientatie}; margin: 10mm; }`;
+ }
+ function startPrint() {
+ const b = document.body.classList;
+ b.toggle("print-lijst", printOpts.indeling === "lijst");
+ b.toggle("print-kolom", printOpts.indeling === "kolom");
+ b.toggle("print-kleur", !!printOpts.kleur);
+ b.toggle("print-geen-begeleiding", !printOpts.begeleiding);
+ b.toggle("print-geen-lokaal", !printOpts.lokaal);
+ zetPaginaStijl(printOpts.papier, printOpts.orientatie);
+ setPrintKop(printOpts.kop || "");
+ if (printOpts.bereik === "alles") setPrintAllesActief(true);
+ setPrintOpen(false);
+ setPrintPending(true);
+ }
+ useEffect(() => {
+ if (!printPending) return;
+ const id = setTimeout(() => {
+ window.print();
+ setPrintPending(false);
+ setPrintAllesActief(false);
+ }, 120);
+ return () => clearTimeout(id);
+ }, [printPending]);
// Direct vanuit een tijdvak in het rooster een vak toevoegen: tijdslot vast,
// en het doel alvast op de actieve scope (klas/leerling) zetten.
function openNieuwBlokVoorSlot(tijdslotId) {
@@ -2338,7 +2374,7 @@ export function RoosterScherm({ mode }) {
toggleOpenBlok(b) : undefined}
title={edit ? "Klik om te bewerken" : undefined}
>
@@ -2351,10 +2387,10 @@ export function RoosterScherm({ mode }) {
{(b.locatie_naam || b.lokaal_naam || b.begeleiders_namen.length > 0) && (
- {b.locatie_naam &&
📍 {b.locatie_naam}}
- {b.lokaal_naam &&
🚪 {b.lokaal_naam}}
+ {b.locatie_naam &&
📍 {b.locatie_naam}}
+ {b.lokaal_naam &&
🚪 {b.lokaal_naam}}
{b.begeleiders_namen.length > 0 && (
-
👤 {b.begeleiders_namen.map((x, idx) => (
+ 👤 {b.begeleiders_namen.map((x, idx) => (
{idx > 0 ? ", " : ""}{x.naam}{rolAfk(x.id) ? ` (${rolAfk(x.id)})` : ""}
))}
)}
@@ -2474,6 +2510,7 @@ export function RoosterScherm({ mode }) {
return (
+ {printKop && {printKop}
}
{edit ? "Roostermaker" : "Rooster"}
{edit
@@ -2501,7 +2538,7 @@ export function RoosterScherm({ mode }) {
- {printAan && }
+ {printAan && }
)}
@@ -2772,6 +2809,55 @@ export function RoosterScherm({ mode }) {
/>
)}
{edit && ghost && {ghost.label}
}
+
+ {printOpen && (
+ setPrintOpen(false)}>
+
e.stopPropagation()}>
+
+
Afdrukken
Kies wat en hoe je afdrukt.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
);
}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 91f68c0..9669b71 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -326,10 +326,26 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
body { background: #fff; }
.app { display: block; }
.content { padding: 0; }
+ .print-only { display: block; }
.rooster-scherm h1 { margin: 0 0 8px; }
.rooster-grid { grid-template-columns: repeat(5, 1fr); gap: 4px; }
.rooster-kolom { border-color: #999; }
- .rooster-blok { break-inside: avoid; }
+ .rooster-blok, .vak-kaart, .blok-splitsing { break-inside: avoid; }
+
+ /* Indeling-keuze uit de printdialog */
+ body.print-kolom .rooster-grid { display: grid !important; grid-template-columns: repeat(5, 1fr) !important; }
+ body.print-lijst .rooster-grid { display: flex !important; flex-direction: column !important; }
+ body.print-lijst .rooster-kolom { display: flex; flex-wrap: wrap; gap: 6px; }
+ body.print-lijst .rooster-dag { flex: 0 0 100%; }
+ body.print-lijst .slot-cel { flex: 1 1 200px; max-width: 320px; }
+
+ /* Details verbergen volgens keuze */
+ body.print-geen-begeleiding .vk-tag.t-beg { display: none; }
+ body.print-geen-lokaal .vk-tag.t-lok, body.print-geen-lokaal .vk-tag.t-loc { display: none; }
+
+ /* Vakken in kleur afdrukken */
+ body.print-kleur .vak-kaart { background: var(--vk-kleur) !important; color: var(--vk-tekst) !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
+ body.print-kleur .vak-kaart .vk-naam, body.print-kleur .vak-kaart .muted { color: inherit !important; }
}
/* --- Onderkant zijbalk: subtiel Instellingen-blok --- */
@@ -560,8 +576,11 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.wm-add { width: 100%; border: 1px dashed var(--line); background: transparent; color: var(--muted); border-radius: 7px; padding: 4px; cursor: pointer; font: inherit; font-size: 13px; opacity: .6; }
.wm-cel:hover .wm-add { opacity: 1; }
.wm-add:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-soft); }
-/* Vak volledig in de vakkleur (tekstkleur wordt inline op contrast gezet) */
+/* Vak volledig in de vakkleur (kleur + leesbare tekstkleur via CSS-variabelen) */
+.vak-kaart.kleur-vlak { background: var(--vk-kleur); color: var(--vk-tekst); }
.vak-kaart.kleur-vlak .vk-naam { color: inherit; }
+.print-only { display: none; }
+.print-kop { font-size: 16px; font-weight: 700; margin: 0 0 8px; }
.vak-kaart.kleur-vlak .rb-greep, .vak-kaart.kleur-vlak .icon-btn { color: inherit; opacity: .85; }
.vak-kaart.kleur-vlak .rb-status, .vak-kaart.kleur-vlak .rb-verv { color: inherit; }
.wm-vak.kleur-vlak .wm-vak-naam { color: inherit; }