From 79191498796089a3f983f29dff832a2f4daae5fa Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sat, 12 Sep 2026 18:52:35 +0200 Subject: [PATCH] fix: Navigation aufteilen, statt sie weiter zu dehnen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mit dem dreizehnten Eintrag verschluckte die Leiste erneut Punkte am rechten Rand — nach M6 zum zweiten Mal. Das Problem ist nicht die Breite, sondern die Vermischung: Einrichtung und Verwaltung sucht man nicht beim Arbeiten. Alltag bleibt in der Leiste, alles andere liegt hinter "Mehr". Ein
statt eines Menues aus Skript: Es klappt ohne JavaScript auf und braucht keine Zustandsverwaltung. Zurueckgezogene Tokens stehen jetzt unten. Was noch Zugang hat, ist das, wonach man sucht. Zwei E2E-Tests greifen den neuen Weg ueber "Mehr" auf. Ein dritter wartet das Neuladen ab, bevor er navigiert — die Meldung kommt frueher als die fertige Liste, und Chromium bricht die Navigation sonst ab. Co-Authored-By: Claude Opus 5 --- e2e/anmeldung.spec.ts | 8 ++- e2e/aufgaben.spec.ts | 4 ++ src/app/(app)/verwaltung/api/page.tsx | 4 +- src/components/kopfleiste.tsx | 73 +++++++++++++++++++++++---- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/e2e/anmeldung.spec.ts b/e2e/anmeldung.spec.ts index ed2620f..d3231ef 100644 --- a/e2e/anmeldung.spec.ts +++ b/e2e/anmeldung.spec.ts @@ -50,7 +50,11 @@ test.describe('Anmeldung', () => { await page.getByRole('button', { name: 'Mit Passwort anmelden' }).click() await expect(page.getByRole('heading', { name: /Guten Tag/ })).toBeVisible() - // exact, weil die Startseite zusätzlich einen Link "Bereiche verwalten" hat. + // Alltagspunkte stehen in der Leiste, Einrichtung liegt hinter "Mehr". + await expect(page.getByRole('link', { name: 'Aufgaben', exact: true })).toBeVisible() + await expect(page.getByRole('link', { name: 'Bereiche', exact: true })).toBeHidden() + + await page.getByText('Mehr', { exact: true }).click() await expect(page.getByRole('link', { name: 'Bereiche', exact: true })).toBeVisible() await page.getByTitle('Abmelden').click() @@ -64,6 +68,8 @@ test.describe('Anmeldung', () => { await page.getByRole('button', { name: 'Mit Passwort anmelden' }).click() await expect(page.getByRole('heading', { name: /Guten Tag/ })).toBeVisible() + // Das Protokoll liegt hinter "Mehr" – aufklappen, dann hin. + await page.getByText('Mehr', { exact: true }).click() await page.getByRole('link', { name: 'Protokoll' }).click() await expect(page.getByRole('heading', { name: 'Protokoll' })).toBeVisible() }) diff --git a/e2e/aufgaben.spec.ts b/e2e/aufgaben.spec.ts index e8c5175..d23a35c 100644 --- a/e2e/aufgaben.spec.ts +++ b/e2e/aufgaben.spec.ts @@ -52,6 +52,10 @@ test.describe('Aufgaben', () => { await zeile.getByLabel('Einsortieren nach').selectOption(bereich!.id) await expect(page.getByText(`liegt jetzt in ${bereich!.name}`)).toBeVisible() + // Die Meldung kommt, bevor das Neuladen der Liste fertig ist. Wer jetzt + // navigiert, bricht es ab — und Chromium meldet ERR_ABORTED. + await page.waitForLoadState('networkidle') + // Aus der Inbox ist sie damit verschwunden. await page.goto('/inbox') await expect(page.locator('li').filter({ hasText: titel })).toHaveCount(0) diff --git a/src/app/(app)/verwaltung/api/page.tsx b/src/app/(app)/verwaltung/api/page.tsx index 55864d9..d407941 100644 --- a/src/app/(app)/verwaltung/api/page.tsx +++ b/src/app/(app)/verwaltung/api/page.tsx @@ -20,7 +20,9 @@ export default async function ApiSeite() { await verlangeRolle('ADMIN', '/verwaltung/api') const tokens = await db.apiToken.findMany({ - orderBy: [{ widerrufenAm: 'asc' }, { erstelltAm: 'desc' }], + // Gueltige zuerst, Zurueckgezogenes ans Ende: Was noch Zugang hat, ist + // das, wonach man sucht. + orderBy: [{ widerrufenAm: { sort: 'asc', nulls: 'first' } }, { erstelltAm: 'desc' }], select: { id: true, name: true, diff --git a/src/components/kopfleiste.tsx b/src/components/kopfleiste.tsx index f4d75f2..d2cc5d3 100644 --- a/src/components/kopfleiste.tsx +++ b/src/components/kopfleiste.tsx @@ -3,7 +3,7 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' import { useTheme } from 'next-themes' -import { LogOut, Moon, Shield, Sun } from 'lucide-react' +import { ChevronDown, LogOut, Moon, Shield, Sun } from 'lucide-react' import { signOut } from 'next-auth/react' import type { Rolle } from '@/generated/prisma/enums' import { ROLLEN_NAME, istAdmin } from '@/lib/rollen' @@ -12,6 +12,16 @@ import { Button } from '@/components/ui/button' type Eintrag = { pfad: string; name: string; nurAdmin?: boolean; zaehler?: number } +/** + * Die Leiste traegt nur, was taeglich gebraucht wird. + * + * Mit jedem Meilenstein kam ein Punkt dazu, und zweimal hat die Leiste + * daraufhin still Eintraege am rechten Rand verschluckt. Das Problem ist + * nicht die Breite, sondern die Vermischung: Einrichtung und Verwaltung + * sucht man nicht beim Arbeiten, sondern wenn man sie braucht. Sie liegen + * deshalb hinter "Mehr". + */ + export function Kopfleiste({ name, email, @@ -34,16 +44,21 @@ export function Kopfleiste({ { pfad: '/woche', name: 'Woche' }, { pfad: '/projekte', name: 'Projekte' }, { pfad: '/ideen', name: 'Ideen' }, + ] + + const WEITERE: Eintrag[] = [ { pfad: '/bereiche', name: 'Bereiche' }, { pfad: '/kalenderkonten', name: 'Kalender' }, { pfad: '/integrationen', name: 'Integrationen' }, { pfad: '/einstellungen', name: 'Einstellungen' }, - { pfad: '/verwaltung/api', name: 'API', nurAdmin: true }, + { pfad: '/verwaltung/api', name: 'API-Tokens', nurAdmin: true }, { pfad: '/verwaltung/protokoll', name: 'Protokoll', nurAdmin: true }, ] const pfad = usePathname() const sichtbar = EINTRAEGE.filter((e) => !e.nurAdmin || istAdmin(rolle)) + const weitere = WEITERE.filter((e) => !e.nurAdmin || istAdmin(rolle)) + const imMenue = weitere.some((e) => pfad.startsWith(e.pfad)) return (
@@ -52,13 +67,7 @@ export function Kopfleiste({ Navigation waagerecht, statt umzubrechen und den Namen der Anwendung in die zweite Zeile zu drängen. */} - {/* - Breiter als die Inhaltsspalte (max-w-5xl): Mit dem elften Eintrag - passte die Leiste dort nicht mehr, und "Einstellungen" verschwand - stillschweigend am rechten Rand. Eine Leiste, die Punkte verschluckt, - ist schlimmer als eine, die nicht buendig mit dem Text steht. - */} -
+
Planer @@ -97,6 +106,52 @@ export function Kopfleiste({ ) })} + + {/* + Ein
statt eines Menues aus Skript: Es klappt ohne + JavaScript auf, schliesst sich beim Klick daneben und braucht + keine Zustandsverwaltung. + */} +
+ + Mehr + + + +
+ {weitere.map((eintrag) => { + const aktiv = pfad.startsWith(eintrag.pfad) + return ( + + {eintrag.nurAdmin && ( + + )} + {eintrag.name} + + ) + })} +
+