Vernieuw navigatie en roostermaker UI
This commit is contained in:
parent
10593e3cfd
commit
18043bc147
5 changed files with 137 additions and 120 deletions
|
|
@ -30,12 +30,10 @@ class CoreConfig(AppConfig):
|
|||
# Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser.
|
||||
MenuItem("Rooster", "/rooster", icon="calendar", order=10),
|
||||
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12),
|
||||
# Submenu Personen (functies zitten als tab in het Personen-scherm)
|
||||
MenuItem("Personen", "/personen", icon="users", order=20, group="Personen"),
|
||||
MenuItem("Afwezigheid", "/afwezigheid", icon="away", order=28, group="Personen"),
|
||||
# Submenu Groepen
|
||||
MenuItem("Groepen", "/groepen", icon="layers", order=30, group="Groepen"),
|
||||
MenuItem("Subgroepen", "/subgroepen", icon="layers", order=40, group="Groepen"),
|
||||
# Submenu Organisatie: wie hoort waar.
|
||||
MenuItem("Personen", "/personen", icon="users", order=20, group="Organisatie"),
|
||||
MenuItem("Structuur", "/structuur", icon="layers", order=24, group="Organisatie"),
|
||||
MenuItem("Afwezigheid", "/afwezigheid", icon="away", order=28, group="Organisatie"),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class LeerpleinConfig(AppConfig):
|
|||
verbose_name = "Module: Leerplein (groepsstructuur)"
|
||||
|
||||
def ready(self):
|
||||
from plugins.registry import MenuItem, ModuleSpec, registry
|
||||
from plugins.registry import ModuleSpec, registry
|
||||
|
||||
registry.register(
|
||||
ModuleSpec(
|
||||
|
|
@ -22,8 +22,6 @@ class LeerpleinConfig(AppConfig):
|
|||
category="Structuur",
|
||||
core=False,
|
||||
default_enabled=False,
|
||||
menu_items=(
|
||||
MenuItem("Leerplein", "/leerplein", icon="layers", order=50, group="Groepen"),
|
||||
),
|
||||
menu_items=(),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
|
|||
import { getMe, login, logout, getModules, setModuleState } from "./api.js";
|
||||
import Logo from "./Logo.jsx";
|
||||
import {
|
||||
GroepenPage, PersonenPage, SubgroepenPage, FunctiesPage,
|
||||
PersonenPage, FunctiesPage,
|
||||
ActiviteitenPage, LocatiesPage, TijdslotenPage, SchooljaarPage,
|
||||
RoosterWeergavePage, RoostermakerPage,
|
||||
AfwezigheidPage, LeerpleinPage, StagePage, GebruikersPage,
|
||||
|
|
@ -14,21 +14,23 @@ const ICONS = {
|
|||
away: "🌴", gear: "⚙️",
|
||||
};
|
||||
|
||||
const STRUCTUUR_PADEN = new Set(["/structuur", "/leerplein", "/groepen", "/subgroepen"]);
|
||||
const STRUCTUUR_ITEM = { label: "Structuur", path: "/structuur", icon: "layers", order: 24, group: "Organisatie" };
|
||||
const normaliseerPad = (path) => (STRUCTUUR_PADEN.has(path) ? "/structuur" : path);
|
||||
|
||||
// Koppel een menu-pad aan een echt scherm. Onbekende paden tonen een
|
||||
// placeholder (worden in een latere fase ingevuld).
|
||||
const PAGES = {
|
||||
"/rooster": RoosterWeergavePage,
|
||||
"/roostermaker": RoostermakerPage,
|
||||
"/structuur": LeerpleinPage,
|
||||
"/personen": PersonenPage,
|
||||
"/functies": FunctiesPage,
|
||||
"/groepen": GroepenPage,
|
||||
"/subgroepen": SubgroepenPage,
|
||||
"/activiteiten": ActiviteitenPage,
|
||||
"/locaties": LocatiesPage,
|
||||
"/tijdsloten": TijdslotenPage,
|
||||
"/schooljaar": SchooljaarPage,
|
||||
"/afwezigheid": AfwezigheidPage,
|
||||
"/leerplein": LeerpleinPage,
|
||||
"/stage": StagePage,
|
||||
"/gebruikers": GebruikersPage,
|
||||
};
|
||||
|
|
@ -134,94 +136,85 @@ function Shell({ user, onLogout }) {
|
|||
}
|
||||
}
|
||||
|
||||
const [openGroups, setOpenGroups] = useState({});
|
||||
|
||||
// Bouw de navigatie op uit de menu-items die ACTIEVE modules aanbieden.
|
||||
// Items met een `group` komen onder een inklapbaar submenu. De groep
|
||||
// "Instellingen" wordt apart, subtiel onderaan, getoond.
|
||||
const { topItems, groups, instellingenItems, alleItems } = useMemo(() => {
|
||||
const items = [];
|
||||
// Verouderde structuurpaden blijven werken, maar verschijnen als één tab.
|
||||
const { tabs, instellingenTabs, alleItems } = useMemo(() => {
|
||||
const perPad = new Map();
|
||||
const voegToe = (raw) => {
|
||||
const pad = normaliseerPad(raw.path);
|
||||
const item = pad === "/structuur"
|
||||
? { ...raw, ...STRUCTUUR_ITEM, module: raw.module }
|
||||
: { ...raw, path: pad };
|
||||
const huidig = perPad.get(item.path);
|
||||
if (!huidig || item.order < huidig.order) perPad.set(item.path, item);
|
||||
};
|
||||
for (const m of modules) {
|
||||
if (!m.enabled) continue;
|
||||
for (const it of m.menu_items) items.push({ ...it, module: m.key });
|
||||
for (const it of m.menu_items) voegToe({ ...it, module: m.key });
|
||||
}
|
||||
if (user.is_staff) {
|
||||
items.push({ 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()];
|
||||
items.sort((a, b) => a.order - b.order);
|
||||
const top = items.filter((i) => !i.group);
|
||||
const grouped = {};
|
||||
const settings = [];
|
||||
for (const i of items) {
|
||||
if (i.group === "Instellingen") settings.push(i);
|
||||
else if (i.group) (grouped[i.group] = grouped[i.group] || []).push(i);
|
||||
}
|
||||
return { topItems: top, groups: grouped, instellingenItems: settings, alleItems: items };
|
||||
return {
|
||||
tabs: items.filter((i) => i.group !== "Instellingen"),
|
||||
instellingenTabs: items.filter((i) => i.group === "Instellingen"),
|
||||
alleItems: items,
|
||||
};
|
||||
}, [modules, user.is_staff]);
|
||||
|
||||
function NavKnop({ it, indent }) {
|
||||
const activePad = normaliseerPad(active);
|
||||
const Page = PAGES[activePad];
|
||||
|
||||
function TabKnop({ it, compact }) {
|
||||
const pad = normaliseerPad(it.path);
|
||||
return (
|
||||
<button
|
||||
className={(active === it.path ? "nav-item active" : "nav-item") + (indent ? " nav-sub" : "")}
|
||||
onClick={() => setActive(it.path)}
|
||||
className={(activePad === pad ? "top-tab active" : "top-tab") + (compact ? " compact" : "")}
|
||||
onClick={() => setActive(pad)}
|
||||
title={it.label}
|
||||
aria-current={activePad === pad ? "page" : undefined}
|
||||
>
|
||||
<span className="ico">{ICONS[it.icon] || "•"}</span>
|
||||
{it.label}
|
||||
<span>{it.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const Page = PAGES[active];
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<aside className="sidebar">
|
||||
<div className="brand">
|
||||
<span className="brand-logo"><Logo size={30} /></span>
|
||||
<span className="brand-name">Roosterwijs</span>
|
||||
</div>
|
||||
<nav>
|
||||
{topItems.map((it) => <NavKnop key={it.path} it={it} />)}
|
||||
{Object.entries(groups).map(([naam, kinderen]) => {
|
||||
const open = openGroups[naam] !== false;
|
||||
return (
|
||||
<div className="nav-group" key={naam}>
|
||||
<button className="nav-group-head" onClick={() => setOpenGroups((o) => ({ ...o, [naam]: !open }))}>
|
||||
<span>{naam}</span>
|
||||
<span className="nav-chev">{open ? "▾" : "▸"}</span>
|
||||
</button>
|
||||
{open && kinderen.map((it) => <NavKnop key={it.path} it={it} indent />)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="sidebar-foot">
|
||||
{instellingenItems.length > 0 && (
|
||||
<div className="nav-group">
|
||||
<button className="nav-group-head subtiel" onClick={() => setOpenGroups((o) => ({ ...o, Instellingen: !o.Instellingen }))}>
|
||||
<span>⚙ Instellingen</span>
|
||||
<span className="nav-chev">{openGroups.Instellingen ? "▾" : "▸"}</span>
|
||||
</button>
|
||||
{openGroups.Instellingen && instellingenItems.map((it) => <NavKnop key={it.path} it={it} indent />)}
|
||||
</div>
|
||||
)}
|
||||
<div className="sidebar-user">
|
||||
<span className="muted small">👤 {user.username}</span>
|
||||
<button className="link" onClick={doLogout}>uitloggen</button>
|
||||
<header className="topbar no-print">
|
||||
<div className="topbar-main">
|
||||
<div className="brand">
|
||||
<span className="brand-logo"><Logo size={30} /></span>
|
||||
<span className="brand-name">Roosterwijs</span>
|
||||
</div>
|
||||
<div className="topbar-user">
|
||||
<span className="muted small">{user.username}</span>
|
||||
<button className="link" onClick={doLogout}>Uitloggen</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<nav className="top-tabs" aria-label="Hoofdnavigatie">
|
||||
{tabs.map((it) => <TabKnop key={it.path} it={it} />)}
|
||||
{instellingenTabs.length > 0 && (
|
||||
<div className="top-tabs-settings" aria-label="Instellingen">
|
||||
{instellingenTabs.map((it) => <TabKnop key={it.path} it={it} compact />)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className="content">
|
||||
<main className={activePad === "/roostermaker" ? "content content-wide" : "content"}>
|
||||
{error && <div className="banner error">⚠ {error}</div>}
|
||||
{loading ? (
|
||||
<p>Laden…</p>
|
||||
) : active === "/modules" && user.is_staff ? (
|
||||
) : activePad === "/modules" && user.is_staff ? (
|
||||
<ModuleManager modules={modules} onChange={load} />
|
||||
) : Page ? (
|
||||
<Page />
|
||||
) : (
|
||||
<Placeholder path={active} menu={alleItems} />
|
||||
<Placeholder path={activePad} menu={alleItems} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1400,11 +1400,20 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|||
try { await deleteResource("roosterblokken", blok.id); await onKlaar(); } catch (e) { setError(e.message); }
|
||||
}
|
||||
return (
|
||||
<div className="blok-editor no-print" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="blok-editor-kop">
|
||||
<strong>Bewerken</strong>
|
||||
<button type="button" className="icon-btn" title="Sluiten" onClick={onSluit}>×</button>
|
||||
</div>
|
||||
<div className="modal-overlay no-print" onClick={onSluit}>
|
||||
<div className="modal rooster-modal" role="dialog" aria-modal="true" aria-label="Roosterblok bewerken" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-kop">
|
||||
<div>
|
||||
<h2>{blok.activiteit_naam || "Roosterblok"}</h2>
|
||||
<div className="muted small">{blok.doel_naam || "Bewerken en splitsen"}</div>
|
||||
</div>
|
||||
<button type="button" className="icon-btn" title="Sluiten" onClick={onSluit}>×</button>
|
||||
</div>
|
||||
<div className="blok-editor modal-editor">
|
||||
<div className="blok-editor-kop">
|
||||
<strong>Vak bewerken</strong>
|
||||
<span className="muted small">Wijzig, splits of kopieer dit blok</span>
|
||||
</div>
|
||||
<Foutmelding msg={error} />
|
||||
<div className="editor-grid">
|
||||
<select value={f.tijdslot} title="Tijdslot" onChange={(e) => setF({ ...f, tijdslot: e.target.value })}>
|
||||
|
|
@ -1488,6 +1497,8 @@ function BlokBewerker({ blok, ctx, onSluit, onKlaar }) {
|
|||
</div>
|
||||
<button type="button" className="btn icon-label" disabled={kopieDoelen.length === 0} onClick={kopieer} title="Kopiëren">⧉ <span>{kopieDoelen.length}</span></button>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1695,6 +1706,10 @@ export function RoosterScherm({ mode }) {
|
|||
setUitzonderingen(await listResource("blok-uitzonderingen", `?schooljaar=${schooljaar}&van=${vanISO}&tot=${totISO}`));
|
||||
} catch (e) { setError(e.message); }
|
||||
}
|
||||
async function herlaadNaBlokWijziging() {
|
||||
try { setSubgroepen(await listResource("subgroepen")); } catch { /* blokken verversen blijft leidend */ }
|
||||
await laadBlokken();
|
||||
}
|
||||
laadRef.current = laadBlokken;
|
||||
useEffect(() => { laadBlokken(); }, [schooljaar]);
|
||||
useEffect(() => { laadWeek(); }, [schooljaar, vanISO]);
|
||||
|
|
@ -1789,14 +1804,6 @@ export function RoosterScherm({ mode }) {
|
|||
const editorCtx = { activiteiten, locaties, groepen, subgroepen, leerlingen, medewerkers, tijdsloten };
|
||||
const isOpenBlok = (b) => edit && bewerkBlok && bewerkBlok.id === b.id;
|
||||
const toggleOpenBlok = (b) => setBewerkBlok((huidig) => (huidig && huidig.id === b.id ? null : b));
|
||||
const inlineBewerker = (b) => isOpenBlok(b) && (
|
||||
<BlokBewerker
|
||||
blok={b}
|
||||
ctx={editorCtx}
|
||||
onSluit={() => setBewerkBlok(null)}
|
||||
onKlaar={async () => { setBewerkBlok(null); await laadBlokken(); }}
|
||||
/>
|
||||
);
|
||||
function renderRoosterBlok(b, iso, kal) {
|
||||
const uit = uitzOp(b.id, iso);
|
||||
const vervallen = !!kal || (uit && uit.vervalt);
|
||||
|
|
@ -1827,7 +1834,6 @@ export function RoosterScherm({ mode }) {
|
|||
{edit && !kal && !uit && (
|
||||
<button type="button" className="icon-btn danger rb-verval no-print" title="Dit vak laten vervallen" onClick={(e) => { e.stopPropagation(); laatVervallen(b, iso); }}>⊘</button>
|
||||
)}
|
||||
{inlineBewerker(b)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1853,7 +1859,6 @@ export function RoosterScherm({ mode }) {
|
|||
{b.begeleiders_namen.length > 0 && <span>{b.begeleiders_namen.map((x) => x.naam).join(", ")}</span>}
|
||||
{vervallen && <span className="rb-status">Vervalt</span>}
|
||||
</div>
|
||||
{inlineBewerker(b)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2009,7 +2014,6 @@ export function RoosterScherm({ mode }) {
|
|||
{edit && (
|
||||
<div className="tabbar no-print">
|
||||
<button className={tab === "week" ? "tab on" : "tab"} title="Weekrooster" onClick={() => setTab("week")}>▦ Week</button>
|
||||
<button className={tab === "indeling" ? "tab on" : "tab"} title="Indeling" onClick={() => setTab("indeling")}>▥ Indeling</button>
|
||||
<button className={tab === "conflicten" ? "tab on" : "tab"} title="Conflicten" onClick={() => setTab("conflicten")}>⚠ Conflicten</button>
|
||||
<button className={tab === "schooljaar" ? "tab on" : "tab"} title="Schooljaar" onClick={() => setTab("schooljaar")}>◷ Jaar</button>
|
||||
<button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}>⚙ Instellingen</button>
|
||||
|
|
@ -2099,20 +2103,6 @@ export function RoosterScherm({ mode }) {
|
|||
|
||||
{schooljaar && tab === "conflicten" && <Conflicten schooljaar={schooljaar} />}
|
||||
|
||||
{edit && schooljaar && tab === "indeling" && (
|
||||
<Groepsindeling
|
||||
groepen={groepen}
|
||||
leerpleinen={leerpleinen}
|
||||
subgroepen={subgroepen}
|
||||
medewerkers={medewerkers}
|
||||
blokken={blokken}
|
||||
onWijzig={async () => {
|
||||
setSubgroepen(await listResource("subgroepen"));
|
||||
await laadBlokken();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tab === "schooljaar" && (
|
||||
<div className="instellingen">
|
||||
<p className="muted">Maak hier snel een schooljaar aan en beheer de kalender (studiedagen, vrije dagen, vakanties).</p>
|
||||
|
|
@ -2129,6 +2119,14 @@ export function RoosterScherm({ mode }) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{edit && bewerkBlok && (
|
||||
<BlokBewerker
|
||||
blok={bewerkBlok}
|
||||
ctx={editorCtx}
|
||||
onSluit={() => setBewerkBlok(null)}
|
||||
onKlaar={async () => { setBewerkBlok(null); await herlaadNaBlokWijziging(); }}
|
||||
/>
|
||||
)}
|
||||
{edit && ghost && <div className="drag-ghost" style={{ left: ghost.x + 10, top: ghost.y + 10 }}>{ghost.label}</div>}
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,19 +12,32 @@
|
|||
* { box-sizing: border-box; }
|
||||
body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color: var(--ink); background: var(--bg); }
|
||||
|
||||
.app { display: grid; grid-template-columns: 250px 1fr; min-height: 100vh; }
|
||||
.app { min-height: 100vh; }
|
||||
|
||||
.sidebar {
|
||||
background: var(--panel);
|
||||
border-right: 1px solid var(--line);
|
||||
padding: 20px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
background: rgba(255,255,255,.96); backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 10px 24px 8px;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 10px; margin: 4px 8px 18px; color: var(--brand); }
|
||||
.topbar-main { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 8px; }
|
||||
.topbar-user { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
|
||||
.top-tabs { display: flex; align-items: center; gap: 6px; 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-tab {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
min-height: 38px; 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;
|
||||
}
|
||||
.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.compact { color: var(--muted); }
|
||||
.top-tab.compact.active { color: #fff; }
|
||||
.brand { display: flex; align-items: center; gap: 10px; margin: 0; color: var(--brand); }
|
||||
.brand-logo { display: inline-flex; color: var(--brand); }
|
||||
.brand-name { font-weight: 800; font-size: 19px; letter-spacing: .2px; }
|
||||
.brand-name { font-weight: 800; font-size: 19px; letter-spacing: 0; }
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
width: 100%; text-align: left;
|
||||
|
|
@ -36,7 +49,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 { padding: 32px 40px; }
|
||||
.content { width: 100%; padding: 22px 24px 32px; }
|
||||
.content-wide { padding-left: 18px; padding-right: 18px; }
|
||||
h1 { margin: 0 0 6px; font-size: 24px; }
|
||||
.muted { color: var(--muted); }
|
||||
|
||||
|
|
@ -114,7 +128,8 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
.tag.dag-vrije_dag { background: #fee2e2; color: #b91c1c; }
|
||||
.tag.dag-vakantie { background: #dcfce7; color: #166534; }
|
||||
|
||||
.rooster-grid { display: grid; grid-template-columns: repeat(5, minmax(220px, 1fr)); gap: 12px; margin-top: 20px; }
|
||||
.rooster-scherm { width: 100%; }
|
||||
.rooster-grid { display: grid; grid-template-columns: repeat(5, minmax(210px, 1fr)); gap: 10px; margin-top: 18px; width: 100%; }
|
||||
.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-blok { border: 1px solid var(--line); border-left: 4px solid var(--brand); border-radius: 8px; padding: 8px; margin-bottom: 8px; background: #fff; }
|
||||
|
|
@ -175,11 +190,11 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
.split-title span { font-size: 11px; color: var(--muted); }
|
||||
.split-count { min-width: 22px; height: 22px; display: inline-grid; place-items: center; border-radius: 999px; background: var(--brand-soft); color: var(--brand); font-size: 12px; font-weight: 800; }
|
||||
.split-groep { font-size: 12px; font-weight: 800; color: var(--brand); margin: 0 2px 7px; }
|
||||
.split-kolommen { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 7px; align-items: start; padding-top: 12px; }
|
||||
.split-kolommen { position: relative; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; align-items: stretch; padding-top: 12px; }
|
||||
.split-kolommen::before { content: ""; position: absolute; top: 0; left: 10%; right: 10%; border-top: 2px solid #bfdbfe; }
|
||||
.split-kolom { position: relative; min-width: 0; border-left: 1px solid var(--line); padding-left: 7px; }
|
||||
.split-kolom { position: relative; min-width: 0; border-left: 1px solid var(--line); padding-left: 7px; height: 100%; }
|
||||
.split-kolom::before { content: ""; position: absolute; top: -12px; left: 50%; width: 2px; height: 12px; background: #bfdbfe; }
|
||||
.split-kolom:first-child { border-left: 0; padding-left: 0; }
|
||||
.split-kolom:nth-child(odd) { border-left: 0; padding-left: 0; }
|
||||
.split-kop { font-size: 11px; font-weight: 700; color: var(--muted); margin: 0 0 4px; }
|
||||
.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); }
|
||||
|
|
@ -225,6 +240,11 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
.modal { background: var(--panel); border-radius: 12px; padding: 18px 20px; width: min(560px, 100%); box-shadow: 0 20px 50px rgba(0,0,0,.3); }
|
||||
.modal-kop { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.modal-kop h2 { margin: 0; font-size: 18px; }
|
||||
.rooster-modal { width: min(920px, calc(100vw - 32px)); padding: 16px; }
|
||||
.modal-editor { margin-top: 0; padding: 12px; background: #f9fafb; }
|
||||
.modal-editor .editor-grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
|
||||
.modal-editor .editor-grid input, .modal-editor .editor-grid select { font-size: 13px; padding: 9px 10px; }
|
||||
.modal-editor .editor-detail { padding-top: 10px; margin-top: 10px; }
|
||||
.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); }
|
||||
|
|
@ -233,7 +253,7 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
/* --- Afdrukken --- */
|
||||
.print-knop { margin-left: auto; background: #fff; }
|
||||
@media print {
|
||||
.sidebar, .no-print, .tabbar, .rooster-balk, .banner,
|
||||
.sidebar, .topbar, .no-print, .tabbar, .rooster-balk, .banner,
|
||||
.rb-greep, .rb-x, .rb-verval, .icon-btn, .print-knop, .drag-ghost { display: none !important; }
|
||||
body { background: #fff; }
|
||||
.app { display: block; }
|
||||
|
|
@ -271,12 +291,22 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
/* Touch-vriendelijk: ruimere tikdoelen op kleinere/aanraakschermen. */
|
||||
@media (pointer: coarse) {
|
||||
.btn, .chip, input, select { min-height: 44px; }
|
||||
.nav-item { padding: 14px 12px; font-size: 15px; }
|
||||
.nav-item, .top-tab { padding: 12px 12px; font-size: 15px; }
|
||||
.link { padding: 8px 6px; display: inline-block; }
|
||||
.rb-greep { font-size: 22px; padding: 4px 10px; }
|
||||
.grid th, .grid td { padding: 14px; }
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.topbar { padding: 10px 12px 8px; }
|
||||
.topbar-main { align-items: flex-start; }
|
||||
.topbar-user { flex-direction: column; align-items: flex-end; gap: 2px; }
|
||||
.content, .content-wide { padding: 18px 12px 28px; }
|
||||
.top-tabs-settings { margin-left: 0; border-left: 0; padding-left: 0; }
|
||||
.split-kolommen { grid-template-columns: 1fr; }
|
||||
.split-kolom { border-left: 0; padding-left: 0; }
|
||||
}
|
||||
|
||||
/* --- Login --- */
|
||||
.center-msg { text-align: center; margin-top: 20vh; color: var(--muted); }
|
||||
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue