From 168c71d2fbc3643ac4a23f84e0a564d4af6efc97 Mon Sep 17 00:00:00 2001 From: bes-r <27369668+bes-r@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:21:46 +0200 Subject: [PATCH] menu's: gegroepeerde bovenbalk-dropdowns + icoon-tabbalk (inklapbaar) --- backend/core/apps.py | 8 ++--- backend/modules/stage/apps.py | 2 +- backend/modules/uren/apps.py | 2 +- frontend/src/App.jsx | 57 ++++++++++++++++++++++++++++++++++- frontend/src/pages.jsx | 12 ++++---- frontend/src/styles.css | 16 +++++++++- 6 files changed, 83 insertions(+), 14 deletions(-) diff --git a/backend/core/apps.py b/backend/core/apps.py index 1c51211..3238e81 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -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"), diff --git a/backend/modules/stage/apps.py b/backend/modules/stage/apps.py index f7c27e0..a3ee3c8 100644 --- a/backend/modules/stage/apps.py +++ b/backend/modules/stage/apps.py @@ -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"), ), ) ) diff --git a/backend/modules/uren/apps.py b/backend/modules/uren/apps.py index e88f051..cb3259f 100644 --- a/backend/modules/uren/apps.py +++ b/backend/modules/uren/apps.py @@ -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"), ), ) ) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index aeeff54..a911a8c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -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 ( +
setOpenMenu(null)}> + +
+ {items.map((it) => { + const pad = normaliseerPad(it.path); + return ( + + ); + })} +
+
+ ); + } + // 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 }) { diff --git a/frontend/src/pages.jsx b/frontend/src/pages.jsx index cef9cef..989132a 100644 --- a/frontend/src/pages.jsx +++ b/frontend/src/pages.jsx @@ -2429,12 +2429,12 @@ export function RoosterScherm({ mode }) { {edit && (
- - - - {blokwizardAan && } - {autoroosterAan && } - + + + + {blokwizardAan && } + {autoroosterAan && } +
)} {edit && tab === "week" && ( diff --git a/frontend/src/styles.css b/frontend/src/styles.css index b3ebae6..d01e544 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -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; } +}