Compare commits

..

2 commits

Author SHA1 Message Date
bes-r
4459f60019 Improve schedule duplicate blocks and conflict styling
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
2026-07-06 20:55:18 +02:00
bes-r
958ace1536 Improve schedule UI clarity 2026-07-04 23:23:58 +02:00
3 changed files with 122 additions and 59 deletions

View file

@ -23,6 +23,22 @@ const GROEP_ICON = { Rooster: "calendar", Organisatie: "users", Overzicht: "cloc
const GROEPSINDELING_PADEN = new Set(["/groepsindeling", "/structuur", "/leerplein", "/groepen", "/subgroepen"]);
const GROEPSINDELING_ITEM = { label: "Groepsindeling", path: "/groepsindeling", icon: "layers", order: 24, group: "Organisatie" };
const normaliseerPad = (path) => (GROEPSINDELING_PADEN.has(path) ? "/groepsindeling" : path);
const ROLE_NAV = {
roostermaker: new Set(["/rooster", "/perioderooster", "/roostermaker", "/vervanging", "/afwezigheid", "/auto-rooster", "/vak-wizard"]),
directie: null,
zorgcoordinator: new Set(["/rooster", "/groepsindeling", "/personen", "/afwezigheid", "/stage", "/uren", "/portaal"]),
stagebegeleider: new Set(["/rooster", "/stage", "/afwezigheid", "/portaal"]),
medewerker: new Set(["/rooster", "/groepsindeling", "/afwezigheid", "/stage", "/uren", "/portaal"]),
kijker: new Set(["/rooster", "/uren", "/portaal"]),
ouder: new Set(["/portaal", "/ouderrooster"]),
leerling: new Set(["/portaal", "/leerlingrooster"]),
};
function magMenuZien(user, item) {
if (user.is_staff || user.role === "beheerder" || user.role === "directie") return true;
const allowed = ROLE_NAV[user.role];
if (!allowed) return true;
return allowed.has(normaliseerPad(item.path));
}
// Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
// placeholder (worden in een latere fase ingevuld).
@ -261,16 +277,19 @@ function Shell({ user, onLogout }) {
if (isBeheerder) {
voegToe({ label: "Modulebeheer", path: "/modules", icon: "gear", order: 9999, group: "Instellingen" });
}
const items = [...perPad.values()];
const items = [...perPad.values()].filter((item) => magMenuZien(user, item));
items.sort((a, b) => a.order - b.order);
return {
tabs: items.filter((i) => i.group !== "Instellingen"),
instellingenTabs: items.filter((i) => i.group === "Instellingen"),
alleItems: items,
};
}, [modules, user.is_staff, user.role]);
}, [modules, user]);
const activePad = normaliseerPad(active);
const eerstePad = alleItems[0]?.path || "/rooster";
const activePad = normaliseerPad(
[...alleItems].some((item) => normaliseerPad(item.path) === normaliseerPad(active)) ? active : eerstePad
);
const Page = PAGES[activePad];
// Groepeer de hoofdtabs: groepen met meerdere items worden een dropdown,
@ -391,7 +410,7 @@ function Shell({ user, onLogout }) {
</nav>
</header>
<main className={activePad === "/roostermaker" ? "content content-wide" : "content"}>
<main className={activePad === "/roostermaker" || activePad === "/rooster" ? "content content-wide" : "content"}>
{error && <div className="banner error"> {error}</div>}
{loading ? (
<p>Laden</p>

View file

@ -2087,6 +2087,8 @@ 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 */ } };
const [detailsOpen, setDetailsOpenState] = useState(() => { try { return localStorage.getItem("rw_rooster_details") === "1"; } catch { return false; } });
const setDetailsOpen = (v) => { setDetailsOpenState(v); try { localStorage.setItem("rw_rooster_details", v ? "1" : "0"); } catch { /* niet beschikbaar */ } };
// Printopties
const [printOpen, setPrintOpen] = useState(false);
const [printPending, setPrintPending] = useState(false);
@ -2409,7 +2411,7 @@ export function RoosterScherm({ mode }) {
{edit && <button type="button" className="icon-btn" onClick={(e) => { e.stopPropagation(); toggleOpenBlok(b); }} title="Bewerken"></button>}
{edit && <button type="button" className="icon-btn danger rb-x" onClick={(e) => { e.stopPropagation(); verwijder(b.id); }} title="Blok verwijderen">×</button>}
</div>
{(b.locatie_naam || b.lokaal_naam || b.begeleiders_namen.length > 0) && (
{(edit || detailsOpen) && (b.locatie_naam || b.lokaal_naam || b.begeleiders_namen.length > 0) && (
<div className="vk-meta">
{b.locatie_naam && <span className="vk-tag t-loc">📍 {b.locatie_naam}</span>}
{b.lokaal_naam && <span className="vk-tag t-lok">🚪 {b.lokaal_naam}</span>}
@ -2420,7 +2422,7 @@ export function RoosterScherm({ mode }) {
)}
</div>
)}
{stageNoot(b)}
{(edit || detailsOpen) && stageNoot(b)}
{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 type="button" className="icon-btn" title="Herstellen" onClick={(e) => { e.stopPropagation(); herstel(uit.id); }}></button>}</div>}
{!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv">Vervanger: {uit.vervanger_naam}</div>}
@ -2462,7 +2464,7 @@ export function RoosterScherm({ mode }) {
out.push(
<div className="blok-splitsing enkel" key={`los-${k}`}>
{klasKop(eerste.doel_naam, eerste.doel_type, blks.length > 1 ? blks.length : null, doelIcoon(eerste.doel_type))}
<div className="split-vakken">{blks.map((b) => renderVakKaart(b, iso, kal))}</div>
<div className={blks.length > 1 ? "split-vakken meerdere-vakken" : "split-vakken"}>{blks.map((b) => renderVakKaart(b, iso, kal))}</div>
</div>
);
});
@ -2479,7 +2481,7 @@ export function RoosterScherm({ mode }) {
return (
<div className="split-kolom" key={gid}>
<div className="split-kop split-node-kop">{groep ? groep.naam : "Groep"}</div>
<div className="split-vakken">{groepBlokken.map((b) => renderVakKaart(b, iso, kal))}</div>
<div className={groepBlokken.length > 1 ? "split-vakken meerdere-vakken" : "split-vakken"}>{groepBlokken.map((b) => renderVakKaart(b, iso, kal))}</div>
</div>
);
})}
@ -2497,7 +2499,7 @@ export function RoosterScherm({ mode }) {
out.push(
<div className="blok-splitsing enkel" key={`groep-${gid}`}>
{klasKop(groepNaam, "groep", groepBlokken.length > 1 ? groepBlokken.length : null, "👥")}
<div className="split-vakken">{groepBlokken.map((b) => renderVakKaart(b, iso, kal))}</div>
<div className={groepBlokken.length > 1 ? "split-vakken meerdere-vakken" : "split-vakken"}>{groepBlokken.map((b) => renderVakKaart(b, iso, kal))}</div>
</div>
);
return;
@ -2512,7 +2514,7 @@ export function RoosterScherm({ mode }) {
{heleGroep.length > 0 && (
<div className="split-kolom">
<div className="split-kop split-node-kop">Hele groep</div>
<div className="split-vakken">{heleGroep.map((b) => renderVakKaart(b, iso, kal))}</div>
<div className={heleGroep.length > 1 ? "split-vakken meerdere-vakken" : "split-vakken"}>{heleGroep.map((b) => renderVakKaart(b, iso, kal))}</div>
</div>
)}
{subIds.map((sid) => {
@ -2521,7 +2523,7 @@ export function RoosterScherm({ mode }) {
return (
<div className="split-kolom" key={sid}>
<div className="split-kop split-node-kop">{sub ? sub.naam : "Subgroep"}</div>
<div className="split-vakken">{blocks.map((b) => renderVakKaart(b, iso, kal))}</div>
<div className={blocks.length > 1 ? "split-vakken meerdere-vakken" : "split-vakken"}>{blocks.map((b) => renderVakKaart(b, iso, kal))}</div>
</div>
);
})}
@ -2533,7 +2535,7 @@ export function RoosterScherm({ mode }) {
}
return (
<section className="rooster-scherm">
<section className={"rooster-scherm " + (edit ? "rooster-edit " : "rooster-view ") + (detailsOpen ? "details-open" : "details-compact")}>
<div className="print-only print-kop">
<RoosterLogo print />
<div className="print-kop-tekst">
@ -2541,18 +2543,19 @@ export function RoosterScherm({ mode }) {
<span>{(schooljaren.find((j) => String(j.id) === String(schooljaar)) || {}).naam || ""}{schooljaar ? " · " : ""}{schooljaar ? weekTitel(maandag) : ""} · afgedrukt {fmtDM(new Date())}</span>
</div>
</div>
<div className="rooster-kop">
<h1>{edit ? "Roostermaker" : "Rooster"}</h1>
<div className="rooster-kop app-page-kop">
<div>
<h1>{edit ? "Roostermaker" : "Rooster"}</h1>
<div className="page-subtitle no-print">
{schooljaar ? `${weekTitel(maandag)} · ${zichtbareBlokken.length} blokken zichtbaar` : "Kies een schooljaar"}
{conflictInfo.meldingen.length > 0 ? ` · ${conflictInfo.meldingen.length} conflicten` : ""}
</div>
</div>
<RoosterLogo />
</div>
<p className="muted no-print">
{edit
? "Sleep een blok naar een ander tijdslot om het te verplaatsen. Vrije dagen, afwezigheid en uitzonderingen worden meegenomen."
: "Bekijk het weekrooster. Vrije dagen, afwezigheid en uitzonderingen zijn verwerkt."}
</p>
<Foutmelding msg={error} />
<div className="rooster-balk no-print">
<div className="rooster-balk rooster-toolbar no-print">
<label className="veld">Schooljaar
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
<option value=""> kies </option>
@ -2571,6 +2574,7 @@ export function RoosterScherm({ mode }) {
<div className="weergave-opties no-print">
<button type="button" className={weergave === "lijst" ? "icon-toggle on" : "icon-toggle"} aria-pressed={weergave === "lijst"} title={weergave === "kolom" ? "Dagen onder elkaar tonen" : "Dagen als kolommen tonen"} onClick={() => setWeergave(weergave === "kolom" ? "lijst" : "kolom")}>{weergave === "kolom" ? "☰" : "▦"}</button>
<button type="button" className={vakkleurVlak ? "icon-toggle on" : "icon-toggle"} aria-pressed={vakkleurVlak} title={vakkleurVlak ? "Vakkleur als streepje tonen" : "Vak volledig in vakkleur tonen"} onClick={() => setVakkleur(!vakkleurVlak)}>🎨</button>
<button type="button" className={detailsOpen ? "icon-toggle on" : "icon-toggle"} aria-pressed={detailsOpen} title={detailsOpen ? "Minder details tonen" : "Meer details tonen"} onClick={() => setDetailsOpen(!detailsOpen)}></button>
{printAan && <button type="button" className="icon-toggle" title="Afdrukken (met opties)" onClick={() => setPrintOpen(true)}>🖨</button>}
</div>
)}
@ -2638,9 +2642,7 @@ export function RoosterScherm({ mode }) {
<button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}><span className="ico"></span><span className="tab-tekst">Instellingen</span></button>
</div>
)}
{edit && tab === "week" && (
<p className="muted small no-print sneltoets-hint">Sneltoetsen: <b>N</b> nieuw blok · <b></b>/<b></b> week · <b>T</b> deze week · <b>Esc</b> sluiten</p>
)}
{edit && tab === "week" && <div className="muted small no-print sneltoets-hint">N nieuw · pijltjes week · T vandaag · Esc sluiten</div>}
{edit && schooljaar && tab === "week" && toonForm && (
<div className="modal-overlay no-print" onClick={sluitNieuwBlok}>
@ -2770,13 +2772,18 @@ export function RoosterScherm({ mode }) {
<div className="rooster-dag">
{d.label} <span className="muted">{fmtDM(datum)}</span>
{kal && <div className={`tag dag-${kal.type}`}>{kal.type_label}{kal.omschrijving ? `: ${kal.omschrijving}` : ""}</div>}
{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 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>
</>
{edit && (
<span className="dag-acties no-print">
{kal ? (
<button type="button" className="icon-btn" title="Weer lesdag maken" onClick={() => dagHerstel(kal.id)}></button>
) : (
<>
<button type="button" className="icon-btn danger" title="Hele dag laten vervallen" onClick={() => dagVervallen(iso)}></button>
<button type="button" className="icon-btn" title="Dag leegmaken" onClick={() => leegDag(iso)}></button>
<button type="button" className="icon-btn" title="Eenmalig blok toevoegen" onClick={() => openEenmalig(iso)}></button>
</>
)}
</span>
)}
</div>
{slots.length === 0 && <div className="muted small"></div>}

View file

@ -16,13 +16,13 @@ body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color:
.topbar {
position: sticky; top: 0; z-index: 50;
background: rgba(255,255,255,.96); backdrop-filter: blur(10px);
background: rgba(255,255,255,.97); backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
padding: 10px 24px 8px;
padding: 8px 24px 7px;
}
.topbar-main { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 8px; }
.topbar-main { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 6px; }
.topbar-user { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
.top-tabs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.top-tabs { display: flex; align-items: center; gap: 4px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; }
.top-tabs-settings { display: flex; align-items: center; gap: 6px; margin-left: auto; padding-left: 10px; border-left: 1px solid var(--line); }
/* Instellingen als cog-menu (popover via hover of klik) */
@ -36,10 +36,10 @@ body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color:
.cog-item.active { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.top-tab {
display: inline-flex; align-items: center; gap: 8px;
min-height: 38px; flex: 0 0 auto;
min-height: 34px; flex: 0 0 auto;
border: 1px solid transparent; border-radius: 8px;
background: transparent; color: var(--ink);
padding: 8px 12px; font: inherit; font-size: 14px; cursor: pointer;
padding: 6px 10px; font: inherit; font-size: 13px; cursor: pointer;
}
.top-tab:hover { background: var(--bg); border-color: var(--line); }
.top-tab.active { background: var(--brand); color: #fff; border-color: var(--brand); font-weight: 700; }
@ -59,8 +59,8 @@ body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color:
.ico { width: 18px; text-align: center; }
.hint { margin-top: auto; font-size: 12px; color: var(--muted); padding: 12px 8px 0; }
.content { width: 100%; padding: 22px 24px 32px; }
.content-wide { padding-left: 18px; padding-right: 18px; }
.content { width: 100%; padding: 20px 24px 32px; }
.content-wide { padding: 14px 14px 28px; }
h1 { margin: 0 0 6px; font-size: 24px; }
.muted { color: var(--muted); }
@ -147,14 +147,14 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.tag.dag-vakantie { background: #dcfce7; color: #166534; }
.rooster-scherm { width: 100%; }
.rooster-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; margin-top: 18px; width: 100%; }
.rooster-grid { display: grid; grid-template-columns: repeat(5, minmax(180px, 1fr)); gap: 8px; margin-top: 10px; width: 100%; }
/* "Dagen onder elkaar": elke dag een volledige band, tijdvakken naast elkaar; de week scrollt verticaal mee. */
.rooster-grid.lijst { display: flex; flex-direction: column; gap: 12px; }
.rooster-grid.lijst .rooster-kolom { display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start; }
.rooster-grid.lijst .rooster-dag { flex: 0 0 100%; text-align: left; padding-right: 32px; margin-bottom: 4px; }
.rooster-grid.lijst .slot-cel { flex: 1 1 210px; max-width: 320px; margin-bottom: 0; }
.rooster-kolom { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 8px; min-height: 80px; }
.rooster-dag { position: relative; font-weight: 600; font-size: 13px; text-align: center; padding: 6px 32px 6px 0; border-bottom: 1px solid var(--line); margin-bottom: 8px; min-height: 36px; }
.rooster-kolom { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 6px; min-height: 80px; }
.rooster-dag { position: sticky; top: 92px; z-index: 3; background: var(--panel); font-weight: 700; font-size: 12px; text-align: left; padding: 5px 76px 7px 4px; border-bottom: 1px solid var(--line); margin-bottom: 6px; min-height: 34px; }
.rooster-blok { border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 8px; padding: 8px; margin-bottom: 8px; background: #fff; }
.rooster-blok.open { box-shadow: 0 0 0 2px var(--brand-soft); }
.rb-top { display: flex; justify-content: space-between; align-items: center; gap: 6px; }
@ -163,17 +163,18 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
@media (max-width: 900px) { .rooster-grid { grid-template-columns: 1fr; } }
/* --- Fase 3: weekweergave, drag-and-drop, touch --- */
.rooster-balk { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 16px 0; }
.rooster-balk { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 10px 0; }
.rooster-toolbar { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
.week-nav { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.week-label { font-weight: 700; min-width: 150px; text-align: center; }
.week-label { font-weight: 700; min-width: 138px; text-align: center; font-size: 13px; }
.btn.icon { padding: 6px 12px; font-size: 18px; line-height: 1; }
.rooster-kolom.vrij { background: repeating-linear-gradient(45deg, #fafafa, #fafafa 8px, #f1f1f4 8px, #f1f1f4 16px); }
.rooster-dag .tag { display: inline-block; margin-top: 4px; }
.slot-cel { border: 1px dashed transparent; border-radius: 8px; padding: 4px; margin-bottom: 8px; transition: background .1s, border-color .1s; }
.slot-cel { border: 1px dashed transparent; border-radius: 7px; padding: 3px; margin-bottom: 6px; transition: background .1s, border-color .1s; }
.slot-cel.hover { border-color: var(--brand); background: var(--brand-soft); }
.slot-tijd { font-size: 11px; color: var(--muted); padding: 2px 2px 4px; }
.slot-tijd { font-size: 10px; color: var(--muted); padding: 1px 2px 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Snel een vak toevoegen direct in een tijdvak */
.slot-add { width: 100%; margin-top: 4px; border: 1px dashed var(--line); background: transparent; color: var(--muted); border-radius: 8px; padding: 5px; font: inherit; font-size: 12px; cursor: pointer; opacity: .55; transition: opacity .1s, border-color .1s, color .1s, background .1s; }
.slot-cel:hover .slot-add, .slot-add:focus-visible { opacity: 1; }
@ -204,7 +205,8 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.drag-ghost { position: fixed; z-index: 1000; pointer-events: none; background: var(--brand); color: #fff; padding: 6px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
.dag-actie { position: absolute; right: 0; top: 3px; margin: 0; }
.dag-acties { position: absolute; right: 2px; top: 3px; display: inline-flex; gap: 2px; background: rgba(255,255,255,.88); border-radius: 6px; }
.dag-acties .icon-btn { width: 22px; height: 22px; font-size: 12px; }
.sneltoets-hint { margin: -4px 0 12px; }
/* --- Groepsindeling --- */
@ -233,6 +235,14 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.split-kop { font-size: 11px; font-weight: 700; color: var(--muted); margin: 0 0 4px; }
.split-node-kop { display: inline-flex; max-width: 100%; align-items: center; border: 1px solid #bfdbfe; border-radius: 6px; background: #fff; color: var(--brand); padding: 4px 7px; margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-vakken { display: grid; gap: 6px; }
.split-vakken.meerdere-vakken {
gap: 5px;
padding-left: 7px;
border-left: 2px solid var(--line);
}
.split-vakken.meerdere-vakken .vak-kaart {
border-radius: 6px;
}
.split-vak { position: relative; border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 6px; background: #fff; padding: 6px 6px 5px; margin-bottom: 6px; cursor: pointer; box-shadow: 0 1px 0 rgba(31,41,55,.03); }
.split-vak.open { box-shadow: 0 0 0 2px var(--brand-soft); }
.split-vak.vervallen { opacity: .55; }
@ -243,15 +253,15 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
/* Eén uniform vak-kaartje (overal gelijk) + klas-kop-container */
.blok-splitsing.enkel { background: linear-gradient(180deg, #eff6ff 0, #f8fbff 40px, #fff 100%); }
.blok-splitsing.enkel .split-header { margin-bottom: 8px; }
.vak-kaart { position: relative; border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 7px; background: #fff; padding: 7px 8px; cursor: default; box-shadow: 0 1px 0 rgba(31,41,55,.03); touch-action: none; }
.vak-kaart { position: relative; border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 7px; background: #fff; padding: 6px 7px; cursor: default; box-shadow: 0 1px 0 rgba(31,41,55,.03); touch-action: none; }
.vak-kaart.klikbaar { cursor: pointer; }
.vak-kaart.klikbaar:hover, .vak-kaart.open { box-shadow: 0 0 0 2px var(--brand-soft); }
.vak-kaart.vervallen { opacity: .55; }
.vak-kaart.vervallen .vk-naam { text-decoration: line-through; }
.vk-kop { display: flex; align-items: center; gap: 5px; }
.vk-naam { min-width: 0; flex: 1; font-size: 13px; overflow: hidden; text-overflow: ellipsis; }
.vk-meta { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-top: 5px; }
.vk-tag { display: inline-flex; align-items: center; gap: 3px; background: var(--bg); color: var(--muted); border-radius: 999px; padding: 2px 8px; font-size: 11px; line-height: 1.5; max-width: 100%; }
.vk-naam { min-width: 0; flex: 1; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vk-meta { display: flex; flex-wrap: wrap; gap: 3px 5px; margin-top: 5px; }
.vk-tag { display: inline-flex; align-items: center; gap: 3px; background: var(--bg); color: var(--muted); border-radius: 999px; padding: 1px 6px; font-size: 10px; line-height: 1.5; max-width: 100%; }
@media print { .blok-splitsing.enkel { background: #fff; } }
.klas-strip { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; min-width: 0; }
@ -309,14 +319,8 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.modal-hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0 12px; }
.rooster-blok.klikbaar { cursor: pointer; }
.rooster-blok.klikbaar:hover { box-shadow: 0 0 0 2px var(--brand-soft); }
.scope-balk { margin-top: -6px; flex-direction: column; align-items: stretch; gap: 10px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
.scope-rij { display: flex; align-items: center; gap: 10px; }
.scope-label { color: var(--muted); font-size: 13px; font-weight: 600; flex: 0 0 auto; width: 104px; text-align: right; white-space: nowrap; }
.klas-strip .chip { white-space: nowrap; flex: 0 0 auto; min-width: 62px; display: inline-flex; align-items: center; justify-content: center; min-height: 34px; }
.klas-strip .chip.chip-plein { min-width: 96px; }
.scope-persoon { flex: 0 0 auto; max-width: 300px; min-height: 34px; }
.scope-persoon { flex: 0 0 auto; min-height: 34px; }
.scope-persoon.on { border-color: var(--brand); color: var(--brand); font-weight: 600; }
.scope-aantal { flex: 0 0 auto; white-space: nowrap; }
/* --- Afdrukken --- */
.print-knop { margin-left: auto; background: #fff; }
@ -576,7 +580,10 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.wm-vak-kop { display: flex; align-items: center; gap: 4px; }
.wm-vak-naam { flex: 1; min-width: 0; font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.wm-vak-meta { color: var(--muted); font-size: 11px; margin-top: 2px; }
.wm-vak.in-conflict { outline: 2px solid #ef4444; outline-offset: 1px; box-shadow: 0 0 0 4px rgba(239,68,68,.15); }
.wm-vak.in-conflict {
outline: 0;
box-shadow: inset 0 0 0 2px #ef4444, 0 1px 0 rgba(31,41,55,.03);
}
.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); }
@ -593,6 +600,31 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
/* Schoollogo in de roosterweergave */
.rooster-kop { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rooster-logo { max-height: 40px; max-width: 150px; object-fit: contain; }
.app-page-kop { margin-bottom: 8px; }
.page-subtitle { color: var(--muted); font-size: 13px; margin-top: 2px; }
.rooster-scherm h1 { margin-bottom: 0; }
.rooster-scherm .veld { gap: 8px; }
.rooster-scherm select { min-width: 0; }
.scope-balk { align-items: stretch; background: transparent; border: 0; padding: 0; margin-top: 6px; }
.scope-rij { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 360px; }
.scope-label { flex: none; font-size: 11px; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.scope-balk .klas-strip { display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto; padding: 2px 2px 7px; scrollbar-width: thin; }
.scope-balk .chip { flex: 0 0 auto; padding: 4px 9px; font-size: 12px; }
.scope-persoon { max-width: 320px; }
.scope-aantal { white-space: nowrap; }
.rooster-edit .rooster-grid { grid-template-columns: repeat(5, minmax(210px, 1fr)); }
.rooster-view.details-compact .vak-kaart { padding-top: 7px; padding-bottom: 7px; }
.rooster-view.details-compact .blok-splitsing { background: #fff; border-color: var(--line); }
.rooster-view.details-compact .split-header { background: #fafbff; margin-bottom: 6px; }
.details-compact .split-kolommen { gap: 6px; }
.details-compact .split-vakken { gap: 5px; }
.details-compact .rb-picto { height: 18px; width: 18px; }
.details-compact .split-title span { display: none; }
.rooster-edit .vak-kaart { background: #fff; }
.rooster-edit .slot-add { opacity: .35; padding: 4px; }
.rooster-edit .slot-cel:hover .slot-add { opacity: 1; }
.rooster-edit .rooster-kolom { background: #fbfcff; }
.sneltoets-hint { margin: -2px 0 8px; text-align: right; }
/* Huisstijl-pagina: logo-upload + voorbeeld op geruite achtergrond */
.huisstijl .logo-vak { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin: 10px 0; }
@ -657,4 +689,9 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
.conflict-lijst li { padding: 4px 0 4px 16px; position: relative; color: #7f1d1d; font-size: 14px; }
.conflict-lijst li::before { content: "•"; position: absolute; left: 2px; }
.conflict-lijst li.waarschuwing { color: #92400e; }
.rooster-blok.in-conflict, .split-vak.in-conflict, .vak-kaart.in-conflict { outline: 2px solid #ef4444; outline-offset: 1px; box-shadow: 0 0 0 4px rgba(239,68,68,.15); }
.rooster-blok.in-conflict,
.split-vak.in-conflict,
.vak-kaart.in-conflict {
outline: 0;
box-shadow: inset 0 0 0 2px #ef4444, 0 1px 0 rgba(31,41,55,.03);
}