Aus dem Sparring, alle drei auch in Analytics wirksam. 1. Vertriebstrichter: Zeilen-Hervorhebung statt Ausgrauen der uebrigen Balken. Bisher setzte das Ueberfahren einer Zeile alle anderen Balken auf 45 % Deckkraft. Das war aus drei Gruenden falsch: Es liess fuenf von sechs Balken verblassen und las sich dadurch wie eine Auswahl statt wie ein Ueberfahren; es war in sich widerspruechlich, weil die Balken verblassten und die Namen daneben schwarz blieben; und Deckkraft traegt in diesem Diagramm bereits Bedeutung — der Balken kodiert einen Anteil, ihn heller zu machen heisst dort "weniger" und nicht "gerade nicht beruehrt". Jetzt bekommt die beruehrte Zeile einen Untergrund, die uebrigen bleiben unveraendert. Derselbe Untergrund wie in der Handlungsbedarf-Liste und in den Tabellen. Die Spur des aktiven Balkens wird weiss: neutral-50 und neutral-100 liegen nur fuenf Stufen auseinander, grau auf grau haette dem Balken den Bezugsrahmen genommen. 2. KPI-Karten einer Reihe auf gleiche Hoehe. Die Umsatzkarte war hoeher als ihre drei Nachbarn, weil sie als einzige eine Trendzeile mit Verlaufskurve traegt. Auf dem Dashboard steht zwischen Rasterzelle und Karte ein Link, und der reichte die Streckung des Rasters nicht durch. h-full auf der Karte. 3. Handlungsbedarf: Hover-Flaeche greift ueber den Inhalt hinaus. Der Untergrund endete hart am Pfeil — bei zwei Spalten besonders sichtbar, weil daneben 32 px Spaltenabstand ungefaerbt blieben. Jetzt acht Pixel Ueberstand und runde Ecken. Dabei mitgenommen: Der Trennstrich sass unten und brauchte eine Ausnahme fuer "die letzte Zeile" — die es bei zwei Spalten zweimal gibt. Die rechte Spalte behielt dadurch einen Strich, unter dem nichts mehr kam. Oben liegend ist die Ausnahme die erste Zeile jeder Spalte, und die steht immer an derselben Stelle, unabhaengig von der Anzahl der Eintraege. tsc, lint, build und verify:layout laufen sauber; Hover-Zustaende und Kartenhoehen nachgemessen statt begutachtet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
117 lines
4.9 KiB
TypeScript
117 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { KATEGORIE_FARBEN } from "../../utils/chartTheme";
|
|
import { ChartLeer } from "./ChartLeer";
|
|
|
|
/**
|
|
* Vertriebstrichter.
|
|
*
|
|
* Zeigt je Stufe die absolute Zahl, die Balkenlänge als Anteil an der ersten
|
|
* Stufe und die Übergangsquote von der vorherigen Stufe. Genau die
|
|
* Übergangsquote ist die Zahl, nach der Betriebe ihren Vertrieb steuern:
|
|
* Sie zeigt, wo Vorgänge verloren gehen.
|
|
*
|
|
* Einfarbig: Die Stufen sind eine Reihenfolge, keine unabhängigen Kategorien —
|
|
* DESIGN-SYSTEM.md §1 „Rangfolgen einfarbig". Eine Farbabstufung würde nur
|
|
* kodieren, was die Reihenfolge ohnehin sagt.
|
|
*
|
|
* Die Balken sind zentriert, nicht linksbündig: So liest man die Verjüngung als
|
|
* Trichterform, ohne die gemeinsame Skala aufzugeben. Ein echtes Funnel-Chart
|
|
* mit breitenkodierten Segmenten kennt Carbon bewusst nicht — es verzerrt.
|
|
*
|
|
* **Zum Verhalten beim Überfahren:** Die Zeile unter dem Zeiger bekommt einen
|
|
* Untergrund, die übrigen bleiben unverändert. Das war vorher andersherum
|
|
* gelöst — die nicht berührten Balken wurden auf 45 % Deckkraft gesetzt — und
|
|
* das war aus drei Gründen falsch:
|
|
*
|
|
* 1. Es hat mehr bewegt als gemeint war. Ein Zeiger, der über eine Zeile
|
|
* streift, ließ fünf von sechs Balken verblassen; das liest sich wie eine
|
|
* Auswahl, nicht wie ein Überfahren.
|
|
* 2. Es war in sich widersprüchlich. Die Balken verblassten, die Namen und
|
|
* Zahlen daneben blieben schwarz — dieselbe Zeile sagte gleichzeitig
|
|
* „unwichtig" und „unverändert".
|
|
* 3. Deckkraft trägt in diesem Diagramm bereits eine Bedeutung: Der Balken
|
|
* kodiert einen Anteil. Ihn heller zu machen, heißt in der Sprache des
|
|
* Diagramms „weniger" — und nicht „gerade nicht berührt".
|
|
*
|
|
* Der Untergrund ist derselbe wie in der Handlungsbedarf-Liste und in den
|
|
* Tabellen (`bg-neutral-50`): Überfahren sieht überall gleich aus.
|
|
*/
|
|
export function Trichter({
|
|
stufen,
|
|
formatWert,
|
|
}: {
|
|
stufen: { name: string; value: number }[];
|
|
formatWert: (wert: number) => string;
|
|
}) {
|
|
const [aktiv, setAktiv] = React.useState<number | null>(null);
|
|
|
|
if (!stufen.length || stufen[0].value === 0) return <ChartLeer hoehe={220} />;
|
|
|
|
const start = stufen[0].value;
|
|
|
|
return (
|
|
<ol className="flex flex-col gap-2">
|
|
{stufen.map((stufe, index) => {
|
|
const anteilGesamt = (stufe.value / start) * 100;
|
|
const vorher = index > 0 ? stufen[index - 1].value : null;
|
|
const uebergang = vorher && vorher > 0 ? (stufe.value / vorher) * 100 : null;
|
|
const hervorgehoben = aktiv === index;
|
|
|
|
return (
|
|
<li
|
|
key={stufe.name}
|
|
onMouseEnter={() => setAktiv(index)}
|
|
onMouseLeave={() => setAktiv(null)}
|
|
/* -mx-2 px-2: Der Untergrund greift über den Inhalt hinaus, sonst
|
|
endet er hart an der Zahl am rechten Rand. */
|
|
className={`-mx-2 grid grid-cols-[96px_minmax(0,1fr)_52px_44px] items-center gap-2 rounded-md px-2 py-0.5 transition-colors duration-150 ${
|
|
hervorgehoben ? "bg-neutral-50" : ""
|
|
}`}
|
|
>
|
|
<span
|
|
className={`truncate text-[12px] ${
|
|
hervorgehoben ? "text-text-neutral-primary" : "text-text-neutral-secondary"
|
|
}`}
|
|
>
|
|
{stufe.name}
|
|
</span>
|
|
|
|
{/* Die Spur ist im hervorgehobenen Zustand weiß statt grau: Auf dem
|
|
grauen Untergrund der Zeile wäre eine graue Spur nicht mehr von
|
|
ihm zu unterscheiden, und der Balken verlöre seinen Bezugsrahmen. */}
|
|
<span
|
|
className={`flex h-6 w-full items-center justify-center rounded-sm transition-colors duration-150 ${
|
|
hervorgehoben ? "bg-white" : "bg-neutral-100"
|
|
}`}
|
|
>
|
|
<span
|
|
className="block h-full rounded-sm transition-[width] duration-300"
|
|
style={{ width: `${Math.max(3, anteilGesamt)}%`, background: KATEGORIE_FARBEN[0] }}
|
|
/>
|
|
</span>
|
|
|
|
<span className="text-right text-[12px] font-semibold tabular-nums text-text-neutral-primary">
|
|
{formatWert(stufe.value)}
|
|
</span>
|
|
|
|
<span
|
|
className={`text-right text-[12px] tabular-nums ${
|
|
hervorgehoben ? "font-semibold text-text-neutral-primary" : "text-text-neutral-tertiary"
|
|
}`}
|
|
title={uebergang !== null ? "Übergang von der vorherigen Stufe" : "Startwert des Trichters"}
|
|
>
|
|
{uebergang !== null ? `${uebergang.toFixed(0)} %` : "—"}
|
|
</span>
|
|
</li>
|
|
);
|
|
})}
|
|
|
|
<li className="mt-2 flex items-center justify-end gap-1 border-t border-solid border-border-default pt-2 text-[11px] text-text-neutral-tertiary">
|
|
Rechte Spalte: Übergang von der vorherigen Stufe
|
|
</li>
|
|
</ol>
|
|
);
|
|
}
|