fix: Navigationsstruktur aus der Client-Komponente herausloesen

Das Server-Layout filtert die Navigation danach, welche Module der
angemeldete Benutzer sehen darf. Es importierte NAV_STRUKTUR aber aus
einer Datei mit "use client" - dort kommt auf dem Server kein Wert an,
sondern nur ein Verweis. NAV_STRUKTUR.filter war entsprechend keine
Funktion, und jede Fachseite lief in einen Serverfehler.

Zweiter Fehler in derselben Zeile: die Eintraege trugen die Icons als
React-Komponenten. Die lassen sich nicht ueber die Server/Client-Grenze
reichen.

Beides behoben, indem die Struktur nach src/lib/navigation-struktur.ts
wandert und dort nur noch ein Kuerzel je Icon steht. Die Client-
Komponente uebersetzt das Kuerzel. Die Wiederausfuhr aus navigation.tsx
wurde bewusst weggelassen, sonst stellt sich dieselbe Falle wieder auf.

Dazu scripts/screenshots.mjs: meldet sich mit dem Entwicklungszugang an
und fotografiert Seiten am Schreibtisch und am Handy, hell und dunkel.

Etappe: 1
Phase: Fundament
This commit is contained in:
Florian Wilde
2026-08-22 02:22:36 +02:00
parent 22b3a9b9e9
commit 63352272b8
4 changed files with 234 additions and 30 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ import { redirect } from 'next/navigation'
import { AppHuelle, SchnellerfassungKnopf } from '@/components/app-huelle'
import { BenutzerMenue } from '@/components/benutzer-menue'
import { BetreuteAuswahl } from '@/components/betreute-auswahl'
import { NAV_STRUKTUR, type NavEintrag } from '@/components/navigation'
import { NAV_STRUKTUR, type NavEintrag } from '@/lib/navigation-struktur'
import { aktiveBetreute, erfordereBenutzer, erfordereZweitenFaktor, meineBetreuten } from '@/lib/auth/sitzung'
import { darf } from '@/lib/auth/rollen'
import { ROLLEN_BESCHRIFTUNG } from '@/lib/auth/rollen'
+25 -29
View File
@@ -16,42 +16,32 @@ import {
Shield,
Users,
} from 'lucide-react'
import type { Modul } from '@prisma/client'
import type { IconName, NavEintrag } from '@/lib/navigation-struktur'
import { cn } from '@/lib/utils'
/**
* Hauptnavigation.
*
* Reihenfolge nach Haeufigkeit im Alltag, nicht nach fachlicher Systematik:
* Heute steht oben, weil ich morgens dort anfange. Berichte stehen unten,
* weil ich sie zweimal im Jahr brauche.
* Die Struktur selbst liegt in src/lib/navigation-struktur.ts, damit das
* Server-Layout sie filtern kann. Hier wird nur aus dem Kuerzel das Icon -
* React-Komponenten lassen sich nicht ueber die Server/Client-Grenze
* reichen.
*/
export type NavEintrag = {
titel: string
href: string
icon: React.ComponentType<{ className?: string }>
modul: Modul
/** Zaehler, der als kleine Blase erscheint (z.B. offene Fristen). */
zaehler?: number
/** Faerbt die Blase rot statt neutral. */
dringend?: boolean
const ICONS: Record<IconName, React.ComponentType<{ className?: string }>> = {
heute: Home,
stammdaten: IdCard,
fristen: CalendarClock,
vorgaenge: FolderKanban,
dokumente: FileText,
finanzen: Banknote,
sozialhilfe: Landmark,
immobilie: Building2,
vertraege: Shield,
pflege: HeartPulse,
kontakte: Users,
berichte: ScrollText,
}
export const NAV_STRUKTUR: Omit<NavEintrag, 'zaehler' | 'dringend'>[] = [
{ titel: 'Heute', href: '/heute', icon: Home, modul: 'STAMMDATEN' },
{ titel: 'Stammdaten', href: '/stammdaten', icon: IdCard, modul: 'STAMMDATEN' },
{ titel: 'Fristen', href: '/fristen', icon: CalendarClock, modul: 'FRISTEN' },
{ titel: 'Vorgänge', href: '/vorgaenge', icon: FolderKanban, modul: 'VORGAENGE' },
{ titel: 'Dokumente', href: '/dokumente', icon: FileText, modul: 'DOKUMENTE' },
{ titel: 'Vermögen & Kasse', href: '/finanzen', icon: Banknote, modul: 'FINANZEN' },
{ titel: 'Sozialhilfe', href: '/sozialhilfe', icon: Landmark, modul: 'SOZIALHILFE' },
{ titel: 'Immobilie', href: '/immobilie', icon: Building2, modul: 'IMMOBILIE' },
{ titel: 'Verträge', href: '/vertraege', icon: Shield, modul: 'VERTRAEGE' },
{ titel: 'Heim & Pflege', href: '/pflege', icon: HeartPulse, modul: 'GESUNDHEIT' },
{ titel: 'Kontakte', href: '/kontakte', icon: Users, modul: 'KONTAKTE' },
{ titel: 'Berichte', href: '/berichte', icon: ScrollText, modul: 'BERICHTE' },
]
export function Navigation({
eintraege,
beimKlick,
@@ -68,7 +58,7 @@ export function Navigation({
{eintraege.map((eintrag) => {
// "/fristen/neu" soll "Fristen" ebenfalls hervorheben.
const aktiv = pfad === eintrag.href || pfad.startsWith(`${eintrag.href}/`)
const Icon = eintrag.icon
const Icon = ICONS[eintrag.icon]
return (
<Link
@@ -106,3 +96,9 @@ export function Navigation({
</nav>
)
}
// Bewusst KEINE Wiederausfuhr von NAV_STRUKTUR: Diese Datei ist eine
// Client-Komponente. Was von hier importiert wird, kommt auf dem Server
// nur als Verweis an - nicht als echter Wert. Die Struktur holt sich das
// Layout direkt aus @/lib/navigation-struktur.
export type { NavEintrag } from '@/lib/navigation-struktur'
+58
View File
@@ -0,0 +1,58 @@
import type { Modul } from '@prisma/client'
/**
* Aufbau der Hauptnavigation - bewusst OHNE "use client" und ohne echte
* Icon-Komponenten.
*
* Grund: Diese Liste wird im Server-Layout gefiltert (welche Module darf
* der angemeldete Benutzer sehen?) und danach an eine Client-Komponente
* uebergeben. Ueber diese Grenze laesst sich nur Serialisierbares
* reichen - eine React-Komponente wie ein Icon gehoert nicht dazu.
* Deshalb steht hier nur ein Kuerzel, das die Client-Komponente in das
* passende Icon uebersetzt.
*/
export type IconName =
| 'heute'
| 'stammdaten'
| 'fristen'
| 'vorgaenge'
| 'dokumente'
| 'finanzen'
| 'sozialhilfe'
| 'immobilie'
| 'vertraege'
| 'pflege'
| 'kontakte'
| 'berichte'
export type NavEintrag = {
titel: string
href: string
icon: IconName
modul: Modul
/** Zaehler, der als kleine Blase erscheint (z.B. ueberfaellige Fristen). */
zaehler?: number
/** Faerbt die Blase rot statt neutral. */
dringend?: boolean
}
/**
* Reihenfolge nach Haeufigkeit im Alltag, nicht nach fachlicher Systematik:
* "Heute" steht oben, weil dort der Tag anfaengt. "Berichte" steht unten,
* weil sie zweimal im Jahr gebraucht werden.
*/
export const NAV_STRUKTUR: Omit<NavEintrag, 'zaehler' | 'dringend'>[] = [
{ titel: 'Heute', href: '/heute', icon: 'heute', modul: 'STAMMDATEN' },
{ titel: 'Stammdaten', href: '/stammdaten', icon: 'stammdaten', modul: 'STAMMDATEN' },
{ titel: 'Fristen', href: '/fristen', icon: 'fristen', modul: 'FRISTEN' },
{ titel: 'Vorgänge', href: '/vorgaenge', icon: 'vorgaenge', modul: 'VORGAENGE' },
{ titel: 'Dokumente', href: '/dokumente', icon: 'dokumente', modul: 'DOKUMENTE' },
{ titel: 'Vermögen & Kasse', href: '/finanzen', icon: 'finanzen', modul: 'FINANZEN' },
{ titel: 'Sozialhilfe', href: '/sozialhilfe', icon: 'sozialhilfe', modul: 'SOZIALHILFE' },
{ titel: 'Immobilie', href: '/immobilie', icon: 'immobilie', modul: 'IMMOBILIE' },
{ titel: 'Verträge', href: '/vertraege', icon: 'vertraege', modul: 'VERTRAEGE' },
{ titel: 'Heim & Pflege', href: '/pflege', icon: 'pflege', modul: 'GESUNDHEIT' },
{ titel: 'Kontakte', href: '/kontakte', icon: 'kontakte', modul: 'KONTAKTE' },
{ titel: 'Berichte', href: '/berichte', icon: 'berichte', modul: 'BERICHTE' },
]