Files
Dashboard/features/dashboard/components/layout/AnalyticsInnerNav.tsx
T
dejanandClaude Opus 5 466c079644 feat(nav): innere Leiste statt Reiter im Analytics-Bereich
Abwägung an der Quelle statt nach Gefühl. Das Zielsystem nutzt beide Muster,
aber für verschiedene Aufgaben:

- Innere Leiste (SidebarIconsLayout, 17 Aufrufstellen): Arbeitsbereiche mit
  Geschwisterseiten — Leads, Termine, HR, Einstellungen.
- Reiter (Tabs): Abschnitte innerhalb EINER Seite — Formular-Editor,
  Chat-Dateien, Projekt-Teilbereiche, Übergabe-Dialog.

Analytics mit fünf Analyse-Seiten ist der erste Fall, strukturell dasselbe wie
Leads mit acht. Drei Gründe:

1. Kohärenz: dieselbe Bedienung an derselben Stelle wie in Leads.
2. Der Platzeinwand trägt nicht. Die Leiste ist eine 32 px schmale Icon-Spalte
   und klappt erst bei Zeigerkontakt oder Tastaturfokus auf 191 px aus,
   verzögert (300 ms auf, 350 ms zu), damit sie beim Überstreichen nicht
   aufspringt. Die Breite brauchen die Analyse-Seiten mit ihren fünfspaltigen
   Kennzahlenreihen.
3. Reiter sind dort der Rückfall, nicht die Wahl: SidebarIconsLayout zeigt
   unterhalb sm selbst eine Reiterleiste. Auf dem Schreibtisch Reiter zu nehmen
   hieße, die Handy-Notlösung zur Hauptform zu machen. Der Nachbau übernimmt
   diesen Rückfall mit.

Nachgemessen: 32 px eingeklappt, 191 px bei Zeigerkontakt, aktiver Eintrag
hervorgehoben, aria-current gesetzt. verify-layout weiterhin grün.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
2026-08-29 20:42:41 +02:00

109 lines
3.7 KiB
TypeScript

"use client";
import React from "react";
import Link from "next/link";
import { SidebarInside } from "@/ui/components/SidebarInside";
import { Tabs } from "@/ui/components/Tabs";
import { FeatherIcon } from "./FeatherIcon";
import { ANALYSE_SEITEN } from "./navigation";
import type { SeitenId } from "../../data/filtering";
/**
* Innere Navigation des Analytics-Bereichs.
*
* Nachbau von `SidebarIconsLayout` aus dem Zielsystem — der Leiste, die dort
* jeder Arbeitsbereich mit Geschwisterseiten benutzt (Leads, Termine, HR,
* Einstellungen; 17 Aufrufstellen). Analytics mit fünf Analyse-Seiten ist
* derselbe Fall.
*
* Verhalten wie dort: eine schmale Icon-Leiste, die bei Zeigerkontakt oder
* Tastaturfokus auf Beschriftungen aufklappt — verzögert, damit sie beim
* bloßen Überstreichen nicht aufspringt. Unterhalb `sm` tritt an ihre Stelle
* eine Reiterleiste; auch das ist aus dem Original übernommen.
*/
const OEFFNEN_MS = 300;
const SCHLIESSEN_MS = 350;
export function AnalyticsInnerNav({
aktiv,
children,
}: {
aktiv: SeitenId;
children: React.ReactNode;
}) {
const [zeigerDrauf, setZeigerDrauf] = React.useState(false);
const [fokusInnen, setFokusInnen] = React.useState(false);
const oeffnen = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const schliessen = React.useRef<ReturnType<typeof setTimeout> | null>(null);
React.useEffect(
() => () => {
if (oeffnen.current) clearTimeout(oeffnen.current);
if (schliessen.current) clearTimeout(schliessen.current);
},
[]
);
const ausgeklappt = zeigerDrauf || fokusInnen;
return (
<div className="sm:flex sm:flex-row">
<div
className="relative hidden sm:sticky sm:top-14 sm:flex sm:self-start"
onMouseEnter={() => {
if (schliessen.current) clearTimeout(schliessen.current);
oeffnen.current = setTimeout(() => setZeigerDrauf(true), OEFFNEN_MS);
}}
onMouseLeave={() => {
if (oeffnen.current) clearTimeout(oeffnen.current);
schliessen.current = setTimeout(() => setZeigerDrauf(false), SCHLIESSEN_MS);
}}
onFocusCapture={() => setFokusInnen(true)}
onBlurCapture={(event) => {
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
setFokusInnen(false);
}
}}
>
<SidebarInside
visible
variant={ausgeklappt ? "open" : "closed"}
className="min-h-[calc(100svh-3.5rem)]"
>
{ANALYSE_SEITEN.map((seite) => (
<Link
key={seite.id}
href={seite.href}
title={seite.label}
aria-current={seite.id === aktiv ? "page" : undefined}
className="flex w-full justify-start no-underline"
>
<SidebarInside.NavItem
icon={<FeatherIcon name={seite.icon} />}
selected={seite.id === aktiv}
variant={ausgeklappt ? "icon-and-text" : "icon-only"}
>
{seite.label}
</SidebarInside.NavItem>
</Link>
))}
</SidebarInside>
</div>
{/* Schmale Fenster: Reiter statt Leiste — wie im Original. */}
<div className="w-full border-b border-solid border-border-default bg-white px-4 py-2 sm:hidden">
<Tabs>
{ANALYSE_SEITEN.map((seite) => (
<Link key={seite.id} href={seite.href} className="contents no-underline">
<Tabs.Item active={seite.id === aktiv}>{seite.label}</Tabs.Item>
</Link>
))}
</Tabs>
</div>
<div className="w-full min-w-0 overflow-x-clip">{children}</div>
</div>
);
}