menu's: gegroepeerde bovenbalk-dropdowns + icoon-tabbalk (inklapbaar)
This commit is contained in:
parent
170a036c4e
commit
168c71d2fb
6 changed files with 83 additions and 14 deletions
|
|
@ -28,10 +28,10 @@ class CoreConfig(AppConfig):
|
|||
core=True,
|
||||
menu_items=(
|
||||
# Hoofdniveau: Rooster = weergave, Roostermaker = aanpasser.
|
||||
MenuItem("Rooster", "/rooster", icon="calendar", order=10),
|
||||
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12),
|
||||
MenuItem("Leerlingrooster", "/leerlingrooster", icon="book", order=14),
|
||||
MenuItem("Ouderrooster", "/ouderrooster", icon="badge", order=16),
|
||||
MenuItem("Rooster", "/rooster", icon="calendar", order=10, group="Rooster"),
|
||||
MenuItem("Roostermaker", "/roostermaker", icon="calendar", order=12, group="Rooster"),
|
||||
MenuItem("Leerlingrooster", "/leerlingrooster", icon="book", order=14, group="Rooster"),
|
||||
MenuItem("Ouderrooster", "/ouderrooster", icon="badge", order=16, group="Rooster"),
|
||||
# Submenu Organisatie: wie hoort waar.
|
||||
MenuItem("Personen", "/personen", icon="users", order=20, group="Organisatie"),
|
||||
MenuItem("Groepsindeling", "/groepsindeling", icon="layers", order=24, group="Organisatie"),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class StageConfig(AppConfig):
|
|||
core=False,
|
||||
default_enabled=False,
|
||||
menu_items=(
|
||||
MenuItem("Stage", "/stage", icon="pin", order=60, group="Groepen"),
|
||||
MenuItem("Stage", "/stage", icon="pin", order=60, group="Organisatie"),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class UrenConfig(AppConfig):
|
|||
core=False,
|
||||
default_enabled=False,
|
||||
menu_items=(
|
||||
MenuItem("Urenoverzicht", "/uren", icon="clock", order=30),
|
||||
MenuItem("Urenoverzicht", "/uren", icon="clock", order=30, group="Overzicht"),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ const ICONS = {
|
|||
away: "🌴", gear: "⚙️", door: "🚪",
|
||||
};
|
||||
|
||||
// Iconen voor de groep-dropdowns in de bovenbalk.
|
||||
const GROEP_ICON = { Rooster: "calendar", Organisatie: "users", Overzicht: "clock", Hulpmiddelen: "puzzle", Toegang: "key" };
|
||||
|
||||
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);
|
||||
|
|
@ -174,6 +177,7 @@ function Shell({ user, onLogout }) {
|
|||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [cogOpen, setCogOpen] = useState(false);
|
||||
const [openMenu, setOpenMenu] = useState(null);
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
|
|
@ -228,6 +232,24 @@ function Shell({ user, onLogout }) {
|
|||
const activePad = normaliseerPad(active);
|
||||
const Page = PAGES[activePad];
|
||||
|
||||
// Groepeer de hoofdtabs: groepen met meerdere items worden een dropdown,
|
||||
// groepen met één item en losse items blijven directe tabs.
|
||||
const navEntries = (() => {
|
||||
const directe = [];
|
||||
const groepMap = new Map();
|
||||
for (const it of tabs) {
|
||||
if (it.group) { if (!groepMap.has(it.group)) groepMap.set(it.group, []); groepMap.get(it.group).push(it); }
|
||||
else directe.push(it);
|
||||
}
|
||||
const entries = directe.map((it) => ({ type: "tab", order: it.order, it }));
|
||||
for (const [groep, items] of groepMap) {
|
||||
if (items.length === 1) entries.push({ type: "tab", order: items[0].order, it: items[0] });
|
||||
else entries.push({ type: "group", order: Math.min(...items.map((i) => i.order)), groep, items });
|
||||
}
|
||||
entries.sort((a, b) => a.order - b.order);
|
||||
return entries;
|
||||
})();
|
||||
|
||||
function TabKnop({ it, compact }) {
|
||||
const pad = normaliseerPad(it.path);
|
||||
return (
|
||||
|
|
@ -243,6 +265,37 @@ function Shell({ user, onLogout }) {
|
|||
);
|
||||
}
|
||||
|
||||
function NavDropdown({ groep, items }) {
|
||||
const open = openMenu === groep;
|
||||
const actief = items.some((i) => normaliseerPad(i.path) === activePad);
|
||||
const icon = GROEP_ICON[groep] || items[0].icon;
|
||||
return (
|
||||
<div className={open ? "nav-menu open" : "nav-menu"} onMouseLeave={() => setOpenMenu(null)}>
|
||||
<button
|
||||
type="button"
|
||||
className={actief ? "top-tab nav-trigger active" : "top-tab nav-trigger"}
|
||||
onClick={() => setOpenMenu((m) => (m === groep ? null : groep))}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open}
|
||||
>
|
||||
<span className="ico">{ICONS[icon] || "•"}</span>
|
||||
<span>{groep}</span>
|
||||
<span className="caret">▾</span>
|
||||
</button>
|
||||
<div className="nav-dropdown" role="menu">
|
||||
{items.map((it) => {
|
||||
const pad = normaliseerPad(it.path);
|
||||
return (
|
||||
<button key={it.path} className={activePad === pad ? "cog-item active" : "cog-item"} role="menuitem" onClick={() => { setActive(pad); setOpenMenu(null); }}>
|
||||
<span className="ico">{ICONS[it.icon] || "•"}</span><span>{it.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Instellingen-cog: staat in de bovenbalk (buiten de scrollende tabbalk,
|
||||
// anders knipt overflow het uitklapmenu weg).
|
||||
const cogMenu = instellingenTabs.length > 0 ? (
|
||||
|
|
@ -290,7 +343,9 @@ function Shell({ user, onLogout }) {
|
|||
</div>
|
||||
</div>
|
||||
<nav className="top-tabs" aria-label="Hoofdnavigatie">
|
||||
{tabs.map((it) => <TabKnop key={it.path} it={it} />)}
|
||||
{navEntries.map((e) => e.type === "group"
|
||||
? <NavDropdown key={"groep:" + e.groep} groep={e.groep} items={e.items} />
|
||||
: <TabKnop key={e.it.path} it={e.it} />)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
|
|
|||
|
|
@ -2429,12 +2429,12 @@ 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 === "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>
|
||||
{blokwizardAan && <button className={tab === "wizard" ? "tab on" : "tab"} title="Vak-wizard" onClick={() => setTab("wizard")}>✨ Wizard</button>}
|
||||
{autoroosterAan && <button className={tab === "auto" ? "tab on" : "tab"} title="Automatisch rooster" onClick={() => setTab("auto")}>🪄 Auto-rooster</button>}
|
||||
<button className={tab === "instellingen" ? "tab on" : "tab"} title="Instellingen" onClick={() => setTab("instellingen")}>⚙ Instellingen</button>
|
||||
<button className={tab === "week" ? "tab on" : "tab"} title="Weekrooster" onClick={() => setTab("week")}><span className="ico">▦</span><span className="tab-tekst">Week</span></button>
|
||||
<button className={tab === "conflicten" ? "tab on" : "tab"} title="Conflicten" onClick={() => setTab("conflicten")}><span className="ico">⚠</span><span className="tab-tekst">Conflicten</span></button>
|
||||
<button className={tab === "schooljaar" ? "tab on" : "tab"} title="Schooljaar" onClick={() => setTab("schooljaar")}><span className="ico">◷</span><span className="tab-tekst">Jaar</span></button>
|
||||
{blokwizardAan && <button className={tab === "wizard" ? "tab on" : "tab"} title="Vak-wizard" onClick={() => setTab("wizard")}><span className="ico">✨</span><span className="tab-tekst">Wizard</span></button>}
|
||||
{autoroosterAan && <button className={tab === "auto" ? "tab on" : "tab"} title="Automatisch rooster" onClick={() => setTab("auto")}><span className="ico">🪄</span><span className="tab-tekst">Auto-rooster</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>
|
||||
)}
|
||||
{edit && tab === "week" && (
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ body { margin: 0; font-family: system-ui, "Segoe UI", Roboto, sans-serif; color:
|
|||
}
|
||||
.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 { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.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) */
|
||||
|
|
@ -492,3 +492,17 @@ input[type="color"] { padding: 2px; width: 44px; min-width: 44px; height: 38px;
|
|||
|
||||
.voorstel-klas { margin-bottom: 18px; }
|
||||
.voorstel-klas h3 { margin: 8px 0 6px; }
|
||||
|
||||
/* Groep-dropdowns in de bovenbalk */
|
||||
.nav-menu { position: relative; }
|
||||
.nav-trigger .caret { font-size: 10px; opacity: .65; margin-left: 1px; }
|
||||
.nav-dropdown { display: none; position: absolute; left: 0; top: 100%; margin-top: 4px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.15); min-width: 210px; padding: 6px; z-index: 60; }
|
||||
.nav-menu:hover .nav-dropdown, .nav-menu.open .nav-dropdown { display: block; }
|
||||
|
||||
/* In-pagina tabbalk: icoon altijd, label inklapbaar op smalle schermen */
|
||||
.tabbar .tab .ico { font-size: 15px; }
|
||||
.tabbar .tab .tab-tekst { margin-left: 6px; }
|
||||
@media (max-width: 720px) {
|
||||
.tabbar .tab .tab-tekst { display: none; }
|
||||
.tabbar .tab { padding-left: 12px; padding-right: 12px; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue