Improve schedule UI clarity

This commit is contained in:
bes-r 2026-07-04 23:23:58 +02:00
parent c845588e5e
commit 958ace1536
3 changed files with 100 additions and 52 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_PADEN = new Set(["/groepsindeling", "/structuur", "/leerplein", "/groepen", "/subgroepen"]);
const GROEPSINDELING_ITEM = { label: "Groepsindeling", path: "/groepsindeling", icon: "layers", order: 24, group: "Organisatie" }; const GROEPSINDELING_ITEM = { label: "Groepsindeling", path: "/groepsindeling", icon: "layers", order: 24, group: "Organisatie" };
const normaliseerPad = (path) => (GROEPSINDELING_PADEN.has(path) ? "/groepsindeling" : path); 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 // Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
// placeholder (worden in een latere fase ingevuld). // placeholder (worden in een latere fase ingevuld).
@ -261,16 +277,19 @@ function Shell({ user, onLogout }) {
if (isBeheerder) { if (isBeheerder) {
voegToe({ label: "Modulebeheer", path: "/modules", icon: "gear", order: 9999, group: "Instellingen" }); 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); items.sort((a, b) => a.order - b.order);
return { return {
tabs: items.filter((i) => i.group !== "Instellingen"), tabs: items.filter((i) => i.group !== "Instellingen"),
instellingenTabs: items.filter((i) => i.group === "Instellingen"), instellingenTabs: items.filter((i) => i.group === "Instellingen"),
alleItems: items, 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]; const Page = PAGES[activePad];
// Groepeer de hoofdtabs: groepen met meerdere items worden een dropdown, // Groepeer de hoofdtabs: groepen met meerdere items worden een dropdown,
@ -391,7 +410,7 @@ function Shell({ user, onLogout }) {
</nav> </nav>
</header> </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>} {error && <div className="banner error"> {error}</div>}
{loading ? ( {loading ? (
<p>Laden</p> <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 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 [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 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 // Printopties
const [printOpen, setPrintOpen] = useState(false); const [printOpen, setPrintOpen] = useState(false);
const [printPending, setPrintPending] = 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" 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>} {edit && <button type="button" className="icon-btn danger rb-x" onClick={(e) => { e.stopPropagation(); verwijder(b.id); }} title="Blok verwijderen">×</button>}
</div> </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"> <div className="vk-meta">
{b.locatie_naam && <span className="vk-tag t-loc">📍 {b.locatie_naam}</span>} {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>} {b.lokaal_naam && <span className="vk-tag t-lok">🚪 {b.lokaal_naam}</span>}
@ -2420,7 +2422,7 @@ export function RoosterScherm({ mode }) {
)} )}
</div> </div>
)} )}
{stageNoot(b)} {(edit || detailsOpen) && stageNoot(b)}
{kal && <div className="small rb-status">Vervalt {kal.type_label}</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 type="button" className="icon-btn" title="Herstellen" onClick={(e) => { e.stopPropagation(); herstel(uit.id); }}></button>}</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>} {!kal && uit && !uit.vervalt && uit.vervanger_naam && <div className="small rb-verv">Vervanger: {uit.vervanger_naam}</div>}
@ -2533,7 +2535,7 @@ export function RoosterScherm({ mode }) {
} }
return ( 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"> <div className="print-only print-kop">
<RoosterLogo print /> <RoosterLogo print />
<div className="print-kop-tekst"> <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> <span>{(schooljaren.find((j) => String(j.id) === String(schooljaar)) || {}).naam || ""}{schooljaar ? " · " : ""}{schooljaar ? weekTitel(maandag) : ""} · afgedrukt {fmtDM(new Date())}</span>
</div> </div>
</div> </div>
<div className="rooster-kop"> <div className="rooster-kop app-page-kop">
<div>
<h1>{edit ? "Roostermaker" : "Rooster"}</h1> <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 /> <RoosterLogo />
</div> </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} /> <Foutmelding msg={error} />
<div className="rooster-balk no-print"> <div className="rooster-balk rooster-toolbar no-print">
<label className="veld">Schooljaar <label className="veld">Schooljaar
<select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}> <select value={schooljaar} onChange={(e) => setSchooljaar(e.target.value)}>
<option value=""> kies </option> <option value=""> kies </option>
@ -2571,6 +2574,7 @@ export function RoosterScherm({ mode }) {
<div className="weergave-opties no-print"> <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={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={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>} {printAan && <button type="button" className="icon-toggle" title="Afdrukken (met opties)" onClick={() => setPrintOpen(true)}>🖨</button>}
</div> </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> <button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}><span className="ico"></span><span className="tab-tekst">Instellingen</span></button>
</div> </div>
)} )}
{edit && tab === "week" && ( {edit && tab === "week" && <div className="muted small no-print sneltoets-hint">N nieuw · pijltjes week · T vandaag · Esc sluiten</div>}
<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 && schooljaar && tab === "week" && toonForm && ( {edit && schooljaar && tab === "week" && toonForm && (
<div className="modal-overlay no-print" onClick={sluitNieuwBlok}> <div className="modal-overlay no-print" onClick={sluitNieuwBlok}>
@ -2770,14 +2772,19 @@ export function RoosterScherm({ mode }) {
<div className="rooster-dag"> <div className="rooster-dag">
{d.label} <span className="muted">{fmtDM(datum)}</span> {d.label} <span className="muted">{fmtDM(datum)}</span>
{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 && (
? <button type="button" className="icon-btn no-print dag-actie" title="Weer lesdag maken" onClick={() => dagHerstel(kal.id)}></button> <span className="dag-acties no-print">
: <> {kal ? (
<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" title="Weer lesdag maken" onClick={() => dagHerstel(kal.id)}></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> <>
<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> </div>
{slots.length === 0 && <div className="muted small"></div>} {slots.length === 0 && <div className="muted small"></div>}
{slots.map((t) => { {slots.map((t) => {

View file

@ -16,13 +16,13 @@ body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color:
.topbar { .topbar {
position: sticky; top: 0; z-index: 50; 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); 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; } .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); } .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) */ /* 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; } .cog-item.active { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.top-tab { .top-tab {
display: inline-flex; align-items: center; gap: 8px; 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; border: 1px solid transparent; border-radius: 8px;
background: transparent; color: var(--ink); 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:hover { background: var(--bg); border-color: var(--line); }
.top-tab.active { background: var(--brand); color: #fff; border-color: var(--brand); font-weight: 700; } .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; } .ico { width: 18px; text-align: center; }
.hint { margin-top: auto; font-size: 12px; color: var(--muted); padding: 12px 8px 0; } .hint { margin-top: auto; font-size: 12px; color: var(--muted); padding: 12px 8px 0; }
.content { width: 100%; padding: 22px 24px 32px; } .content { width: 100%; padding: 20px 24px 32px; }
.content-wide { padding-left: 18px; padding-right: 18px; } .content-wide { padding: 14px 14px 28px; }
h1 { margin: 0 0 6px; font-size: 24px; } h1 { margin: 0 0 6px; font-size: 24px; }
.muted { color: var(--muted); } .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; } .tag.dag-vakantie { background: #dcfce7; color: #166534; }
.rooster-scherm { width: 100%; } .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. */ /* "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 { 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-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 .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-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-kolom { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 6px; 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-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 { 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); } .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; } .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; } } @media (max-width: 900px) { .rooster-grid { grid-template-columns: 1fr; } }
/* --- Fase 3: weekweergave, drag-and-drop, touch --- */ /* --- 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-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; } .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-kolom.vrij { background: repeating-linear-gradient(45deg, #fafafa, #fafafa 8px, #f1f1f4 8px, #f1f1f4 16px); }
.rooster-dag .tag { display: inline-block; margin-top: 4px; } .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-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 */ /* 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-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; } .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); } .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; } .sneltoets-hint { margin: -4px 0 12px; }
/* --- Groepsindeling --- */ /* --- Groepsindeling --- */
@ -243,15 +245,15 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
/* Eén uniform vak-kaartje (overal gelijk) + klas-kop-container */ /* 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 { background: linear-gradient(180deg, #eff6ff 0, #f8fbff 40px, #fff 100%); }
.blok-splitsing.enkel .split-header { margin-bottom: 8px; } .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 { cursor: pointer; }
.vak-kaart.klikbaar:hover, .vak-kaart.open { box-shadow: 0 0 0 2px var(--brand-soft); } .vak-kaart.klikbaar:hover, .vak-kaart.open { box-shadow: 0 0 0 2px var(--brand-soft); }
.vak-kaart.vervallen { opacity: .55; } .vak-kaart.vervallen { opacity: .55; }
.vak-kaart.vervallen .vk-naam { text-decoration: line-through; } .vak-kaart.vervallen .vk-naam { text-decoration: line-through; }
.vk-kop { display: flex; align-items: center; gap: 5px; } .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-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: 4px 6px; margin-top: 5px; } .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: 2px 8px; font-size: 11px; line-height: 1.5; max-width: 100%; } .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; } } @media print { .blok-splitsing.enkel { background: #fff; } }
.klas-strip { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; min-width: 0; } .klas-strip { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; min-width: 0; }
@ -309,14 +311,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; } .modal-hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0 12px; }
.rooster-blok.klikbaar { cursor: pointer; } .rooster-blok.klikbaar { cursor: pointer; }
.rooster-blok.klikbaar:hover { box-shadow: 0 0 0 2px var(--brand-soft); } .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-persoon { flex: 0 0 auto; min-height: 34px; }
.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.on { border-color: var(--brand); color: var(--brand); font-weight: 600; } .scope-persoon.on { border-color: var(--brand); color: var(--brand); font-weight: 600; }
.scope-aantal { flex: 0 0 auto; white-space: nowrap; }
/* --- Afdrukken --- */ /* --- Afdrukken --- */
.print-knop { margin-left: auto; background: #fff; } .print-knop { margin-left: auto; background: #fff; }
@ -593,6 +589,32 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
/* Schoollogo in de roosterweergave */ /* Schoollogo in de roosterweergave */
.rooster-kop { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .rooster-kop { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rooster-logo { max-height: 40px; max-width: 150px; object-fit: contain; } .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,
.details-compact .split-count { 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-pagina: logo-upload + voorbeeld op geruite achtergrond */
.huisstijl .logo-vak { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin: 10px 0; } .huisstijl .logo-vak { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin: 10px 0; }