467 Stellen. Nicht durch einen anderen Strich ersetzt, sondern durch das
Satzzeichen, das an der jeweiligen Stelle richtig ist: Doppelpunkt, wo eine
Erlaeuterung folgt; Komma, wo der Satz weiterlaeuft; Punkt, wo zwei
Hauptsaetze aneinanderstiessen. Ein Halbgeviertstrich waere nur derselbe
Griff mit kuerzerem Strich gewesen.
Regelbasiert ersetzt und danach nachgeprueft, weil eine Regel allein nicht
reicht. Gefunden und einzeln korrigiert:
- Eingeschobene Saetze mit Strichpaar. Nur der oeffnende Strich passte ins
Muster, der schliessende blieb als "—," stehen. Drei Stellen, jetzt mit
Klammern oder als eigener Satz.
- Doppelte Doppelpunkte, wo beide Striche eines Paars zu Doppelpunkten wurden
("Globale Regeln: Schrift, Groessen, Abstaende: kommen aus HiveGrid").
Sieben Stellen in Doku und Oberflaeche, jetzt in Klammern.
- Pfade, denen ein Komma statt eines Doppelpunkts folgte.
- Ausgerichtete Kommentartabellen, in denen der Strich eine Spalte war.
Nicht angefasst: drei Geviertstriche, die keine Prosa sind, sondern
Tabellenplatzhalter fuer "kein Wert" (Trichter, Auftraege, Kunden). Dort ist
der Strich die Angabe selbst; ihn zu entfernen liesse leere Zellen zurueck.
tsc, lint, build, verify:carbon und verify:layout laufen sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
125 lines
4.9 KiB
TypeScript
125 lines
4.9 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.
|
|
*
|
|
* ────────────────────────────────────────────────────────────────────────────
|
|
* BEIM EINBAU: Dieser Nachbau wird NICHT portiert.
|
|
*
|
|
* Verbindlich ist die Verabredung: Analytics bekommt eine innere Leiste mit
|
|
* den fünf Analyse-Seiten. Gebaut wird sie in HiveGrid mit dem dortigen
|
|
* `SidebarIconsLayout`, das genau diesen Zweck hat (Leads, Termine, HR,
|
|
* Einstellungen nutzen es bereits). Diese Datei beschreibt das Ziel, sie ist
|
|
* nicht das Bauteil. Rangfolge: docs/ZIELSYSTEM.md Abschnitt 0.
|
|
* ────────────────────────────────────────────────────────────────────────────
|
|
*/
|
|
|
|
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.
|
|
Waagerecht scrollend statt umbrechend: Fünf Reiter brauchen mindestens
|
|
425 px, bei 375 px lief die ganze Seite dadurch seitlich über. Über
|
|
zwei Zeilen gebrochen liest sich ein Reiterband als zwei Steuerelemente
|
|
dasselbe Mittel wie beim Zeitraum-Segment in der Filterleiste. */}
|
|
<div className="dv-scroll-x w-full min-w-0 overflow-x-auto border-b border-solid border-border-default bg-white px-4 py-2 sm:hidden">
|
|
<Tabs className="w-max min-w-full">
|
|
{ANALYSE_SEITEN.map((seite) => (
|
|
<Link key={seite.id} href={seite.href} className="contents no-underline">
|
|
<Tabs.Item active={seite.id === aktiv} className="whitespace-nowrap">
|
|
{seite.label}
|
|
</Tabs.Item>
|
|
</Link>
|
|
))}
|
|
</Tabs>
|
|
</div>
|
|
|
|
<div className="w-full min-w-0 overflow-x-clip">{children}</div>
|
|
</div>
|
|
);
|
|
}
|