feat(frontend): group dashboard sidebar navigation into sections

Add section-based grouping to the sidebar navigation with headers and
dividers. Menu items are now organized into 4 sections:
- Allgemein (Übersicht, Zählpunkte, Mein Profil)
- Community (Community beitreten, Meine Mitgliedschaften, Verbrauch)
- Tarife & Einladungen (Meine Tarife, Einladungen)
- Verwaltung (Admin-only: Mitgliedschaften, Benutzerverwaltung, etc.)

Only sections with visible items for the current role are rendered.
All 107 frontend tests passing.
This commit is contained in:
Bernhard Müller 2026-07-24 14:12:26 +02:00
parent fb53b169a2
commit b1f38e752b
3 changed files with 54 additions and 26 deletions

View File

@ -21,15 +21,25 @@
</a> </a>
</div> </div>
<nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto"> <nav class="flex-1 px-4 py-6 space-y-4 overflow-y-auto">
@for (item of visibleNavItems(); track item.path) { @for (group of navSections(); track group.section; let last = $last) {
<a [routerLink]="item.path" <div>
routerLinkActive="bg-emerald-50 text-emerald-700 font-medium" <p class="px-4 mb-1 text-xs font-semibold uppercase tracking-wider text-slate-400">
[routerLinkActiveOptions]="{exact: item.path === '/dashboard'}" {{ group.section }}
(click)="closeMobileMenu()" </p>
class="block px-4 py-2.5 rounded-lg text-slate-600 hover:bg-slate-50 hover:text-slate-900 transition-colors"> @for (item of group.items; track item.path) {
{{ item.label }} <a [routerLink]="item.path"
</a> routerLinkActive="bg-emerald-50 text-emerald-700 font-medium"
[routerLinkActiveOptions]="{exact: item.path === '/dashboard'}"
(click)="closeMobileMenu()"
class="block px-4 py-2 rounded-lg text-slate-600 hover:bg-slate-50 hover:text-slate-900 transition-colors">
{{ item.label }}
</a>
}
</div>
@if (!last) {
<div class="border-t border-slate-100 mx-4"></div>
}
} }
</nav> </nav>
</aside> </aside>

View File

@ -1,7 +1,7 @@
import {Component, computed, inject, signal} from '@angular/core'; import {Component, computed, inject, signal} from '@angular/core';
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
import {AuthService} from '../../services/auth'; import {AuthService} from '../../services/auth';
import {NAV_ITEMS} from './dashboard-nav-items'; import {NAV_ITEMS, NAV_SECTIONS, NavItem} from './dashboard-nav-items';
import {NotificationComponent} from '../../components/notification/notification'; import {NotificationComponent} from '../../components/notification/notification';
@Component({ @Component({
@ -17,9 +17,17 @@ export class DashboardLayout {
isMobileMenuOpen = signal(false); isMobileMenuOpen = signal(false);
visibleNavItems = computed(() => { navSections = computed(() => {
const role = this.authService.currentUserRole(); const role = this.authService.currentUserRole();
return NAV_ITEMS.filter(item => role !== null && item.roles.includes(role)); if (role === null) return [];
const visible = NAV_ITEMS.filter(item => item.roles.includes(role));
return NAV_SECTIONS
.map(section => ({
section,
items: visible.filter(item => item.section === section)
}))
.filter(group => group.items.length > 0);
}); });
toggleMobileMenu() { toggleMobileMenu() {

View File

@ -2,21 +2,31 @@ export interface NavItem {
label: string; label: string;
path: string; path: string;
roles: string[]; roles: string[];
section: string;
} }
export const NAV_SECTIONS = ['Allgemein', 'Community', 'Tarife & Einladungen', 'Verwaltung'] as const;
export const NAV_ITEMS: NavItem[] = [ export const NAV_ITEMS: NavItem[] = [
{label: 'Übersicht', path: '/dashboard', roles: ['ADMIN', 'MEMBER']}, // Allgemein
{label: 'Zählpunkte', path: '/dashboard/metering-points', roles: ['ADMIN', 'MEMBER']}, {label: 'Übersicht', path: '/dashboard', roles: ['ADMIN', 'MEMBER'], section: 'Allgemein'},
{label: 'Community beitreten', path: '/dashboard/join-community', roles: ['MEMBER']}, {label: 'Zählpunkte', path: '/dashboard/metering-points', roles: ['ADMIN', 'MEMBER'], section: 'Allgemein'},
{label: 'Meine Mitgliedschaften', path: '/dashboard/my-memberships', roles: ['MEMBER']}, {label: 'Mein Profil', path: '/dashboard/profile', roles: ['ADMIN', 'MEMBER'], section: 'Allgemein'},
{label: 'Verbrauch', path: '/dashboard/consumption', roles: ['MEMBER']},
{label: 'Mitgliedschaften', path: '/dashboard/admin-memberships', roles: ['ADMIN']}, // Community
{label: 'Mein Profil', path: '/dashboard/profile', roles: ['ADMIN', 'MEMBER']}, {label: 'Community beitreten', path: '/dashboard/join-community', roles: ['MEMBER'], section: 'Community'},
{label: 'Benutzerverwaltung', path: '/dashboard/approvals', roles: ['ADMIN']}, {label: 'Meine Mitgliedschaften', path: '/dashboard/my-memberships', roles: ['MEMBER'], section: 'Community'},
{label: 'Energiegemeinschaften', path: '/dashboard/energy-communities', roles: ['ADMIN']}, {label: 'Verbrauch', path: '/dashboard/consumption', roles: ['MEMBER'], section: 'Community'},
{label: 'Tarife verwalten', path: '/dashboard/tariffs', roles: ['ADMIN']},
{label: 'Messdaten-Upload', path: '/dashboard/upload-metering-data', roles: ['ADMIN']}, // Tarife & Einladungen
{label: 'Meine Tarife', path: '/dashboard/my-tariffs', roles: ['MEMBER']}, {label: 'Meine Tarife', path: '/dashboard/my-tariffs', roles: ['MEMBER'], section: 'Tarife & Einladungen'},
{label: 'Meine Einladungen', path: '/dashboard/producer-invites', roles: ['MEMBER']}, {label: 'Meine Einladungen', path: '/dashboard/producer-invites', roles: ['MEMBER'], section: 'Tarife & Einladungen'},
{label: 'Offene Einladungen', path: '/dashboard/consumer-invites', roles: ['MEMBER']} {label: 'Offene Einladungen', path: '/dashboard/consumer-invites', roles: ['MEMBER'], section: 'Tarife & Einladungen'},
// Verwaltung
{label: 'Mitgliedschaften', path: '/dashboard/admin-memberships', roles: ['ADMIN'], section: 'Verwaltung'},
{label: 'Benutzerverwaltung', path: '/dashboard/approvals', roles: ['ADMIN'], section: 'Verwaltung'},
{label: 'Energiegemeinschaften', path: '/dashboard/energy-communities', roles: ['ADMIN'], section: 'Verwaltung'},
{label: 'Tarife verwalten', path: '/dashboard/tariffs', roles: ['ADMIN'], section: 'Verwaltung'},
{label: 'Messdaten-Upload', path: '/dashboard/upload-metering-data', roles: ['ADMIN'], section: 'Verwaltung'},
]; ];