Files
Dashboard/features/dashboard/components/cards/ChartCard.tsx
T
dejanandClaude Opus 5 6a0b36ddcc Geviertstriche aus Text, Kommentaren und Doku entfernen
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
2026-08-31 20:51:19 +02:00

150 lines
5.4 KiB
TypeScript

"use client";
import React from "react";
import {
FeatherDownload,
FeatherEyeOff,
FeatherInfo,
FeatherMoreHorizontal,
} from "@subframe/core";
import { useWidgets } from "../../context/WidgetContext";
import { useToast } from "../../context/ToastContext";
/**
* Rahmen für jede Visualisierung (COMP-09).
*
* Enthält die einzige "echte" Interaktionslogik des Prototyps:
* Über das ⋯-Menü lässt sich die Karte ausblenden; sie taucht dann in der
* Filterleiste zum Wiedereinblenden auf. Der Zustand ist persistiert.
*/
export function ChartCard({
id,
titel,
untertitel,
hinweis,
aktion,
legende,
children,
className = "",
}: {
/** Stabile ID: Schlüssel für Ausblenden/Einblenden. */
id: string;
titel: string;
untertitel?: string;
/** Erklärt die Kennzahl; erscheint als Tooltip am (i). */
hinweis?: string;
aktion?: React.ReactNode;
legende?: React.ReactNode;
children: React.ReactNode;
className?: string;
}) {
const { istSichtbar, verstecke } = useWidgets();
const { zeigeToast } = useToast();
const [menuOffen, setMenuOffen] = React.useState(false);
const menu = React.useRef<HTMLDivElement>(null);
const ausloeser = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (!menuOffen) return;
const schliesse = (event: MouseEvent) => {
if (!menu.current?.contains(event.target as Node)) setMenuOffen(false);
};
// Escape schliesst und gibt den Fokus an den Ausloeser zurueck, sonst
// landet die Tastaturbedienung nach dem Schliessen am Seitenanfang.
const taste = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setMenuOffen(false);
ausloeser.current?.focus();
}
};
document.addEventListener("mousedown", schliesse);
document.addEventListener("keydown", taste);
return () => {
document.removeEventListener("mousedown", schliesse);
document.removeEventListener("keydown", taste);
};
}, [menuOffen]);
if (!istSichtbar(id)) return null;
return (
<section
className={`flex flex-col overflow-hidden rounded-lg border border-solid border-border-default bg-white shadow-sm ${className}`}
>
<header className="flex flex-none items-start justify-between gap-3 px-4 pb-3 pt-4">
<div className="flex min-w-0 flex-col">
<div className="flex items-center gap-1.5">
<h2 className="min-w-0 break-words text-[14px] font-semibold leading-tight text-text-neutral-primary">
{titel}
</h2>
{hinweis ? (
<span
title={hinweis}
className="flex h-4 w-4 flex-none cursor-help items-center justify-center text-text-neutral-subtle hover:text-text-neutral-secondary"
>
<FeatherInfo className="text-[13px]" />
</span>
) : null}
</div>
{untertitel ? (
<p className="line-clamp-2 break-words text-[12px] leading-snug text-text-neutral-tertiary">
{untertitel}
</p>
) : null}
</div>
<div className="dv-no-print relative flex flex-none items-center gap-1" ref={menu}>
{aktion}
<button
ref={ausloeser}
onClick={() => setMenuOffen((wert) => !wert)}
aria-label={`Optionen für ${titel}`}
aria-haspopup="menu"
aria-expanded={menuOffen}
className="flex h-8 w-8 items-center justify-center rounded-md text-text-neutral-tertiary hover:bg-neutral-100 hover:text-text-neutral-primary"
>
<FeatherMoreHorizontal className="text-[16px]" />
</button>
{menuOffen ? (
<div
role="menu"
className="absolute right-0 top-9 z-40 flex w-52 flex-col rounded-lg border border-solid border-border-default bg-white py-1 shadow-lg"
>
<button
onClick={() => {
verstecke(id, titel);
setMenuOffen(false);
}}
className="flex items-center gap-2 px-3 py-2 text-left text-[13px] text-text-neutral-primary hover:bg-neutral-50"
>
<FeatherEyeOff className="text-[14px] text-text-neutral-tertiary" />
Widget ausblenden
</button>
<button
onClick={() => {
zeigeToast("Als Bild vorgemerkt", `${titel}: Export folgt in der Umsetzung`);
setMenuOffen(false);
}}
className="flex items-center gap-2 px-3 py-2 text-left text-[13px] text-text-neutral-primary hover:bg-neutral-50"
>
<FeatherDownload className="text-[14px] text-text-neutral-tertiary" />
Als Bild exportieren
</button>
</div>
) : null}
</div>
</header>
{legende ? <div className="flex-none px-4 pb-2">{legende}</div> : null}
{/* justify-center: Karten einer Zeile sind gleich hoch, aber nicht gleich
voll. Eine Karte mit einem Balken neben einer mit einem Diagramm über
400 px bekommt den Überschuss oben und unten je zur Hälfte, statt ihn
unten abzuladen. Der Inhalt behält seine natürliche Höhe, die Karte
wächst weiter mit, wenn mehr hineinkommt. */}
<div className="flex min-h-0 grow flex-col justify-center px-4 pb-4">{children}</div>
</section>
);
}