Files
Dashboard/ui/components/KanbanCard/KanbanCard.tsx
T
dejanandClaude Opus 5 b8bf987d0e feat(dashboard): Analytics-Dashboard als Gestaltungsvorlage zur Übergabe
UI/UX-Prototyp der Analytics-Dashboards in HiveGrid, fachlich auf einen
SHK-/Handwerksbetrieb zugeschnitten. Ziel ist die Übergabe an die Entwicklung,
nicht der Produktivbetrieb.

Inhalt: sechs Seiten (Übersicht, Finanzen, Aufträge, Kunden, Team, Komponenten),
26 KPIs, neun Chart-Bausteine, Filtersystem mit seitenabhängiger Relevanz und ein
deterministischer Demo-Datensatz (420 Kunden, 1.900 Aufträge, Stichtag 30.07.2026).

Drei Quellen sind zusammengeführt: die fachliche Planung mit dem Branchenpartner
(Januar 2026), die Subframe-Komponentenbibliothek aus dem HiveGrid-Pitch und
IBM Carbon Data Visualization für die Diagrammregeln. Herkunft und bewusste
Abweichungen stehen in docs/HERKUNFT.md.

Dokumentation: README, HANDOVER (was echte Logik braucht), docs/DESIGN-SYSTEM,
KPI-KATALOG, DATENMODELL, NAVIGATION, HERKUNFT, ZIELSYSTEM (Einbauort in
HiveGrid Cloud) und LEITPLANKEN (welche Cloud-Screens als Vorbild taugen).

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

144 lines
4.6 KiB
TypeScript

"use client";
/*
* Documentation:
* Icon — https://app.subframe.com/f023064a5ca0/library?component=Icon_e24ec5dc-9821-430d-8254-1d72a7d46218
* KanbanCard — https://app.subframe.com/f023064a5ca0/library?component=KanbanCard_27235bcb-e61b-4880-9ae6-3f12d290a434
*/
import React from "react";
import { FeatherFlag } from "@subframe/core";
import { FeatherLock } from "@subframe/core";
import * as SubframeUtils from "../../utils";
import { Icon } from "../Icon";
export interface KanbanCardRootProps
extends React.HTMLAttributes<HTMLDivElement> {
tagsSlot?: React.ReactNode;
hideTags?: boolean;
titleSlot?: React.ReactNode;
valueSlot?: React.ReactNode;
priceLocked?: boolean;
footerLeftSlot?: React.ReactNode;
footerRightSlot?: React.ReactNode;
flagVariant?:
| "primary"
| "secondary"
| "tertiary"
| "destructive"
| "neutral"
| "success"
| "warning"
| "information";
keepTagRowSpace?: boolean;
className?: string;
}
const KanbanCardRoot = React.forwardRef<HTMLDivElement, KanbanCardRootProps>(
function KanbanCardRoot(
{
tagsSlot,
hideTags = false,
titleSlot,
valueSlot,
priceLocked = false,
footerLeftSlot,
footerRightSlot,
flagVariant = "primary",
keepTagRowSpace = false,
className,
...otherProps
}: KanbanCardRootProps,
ref
) {
return (
<div
className={SubframeUtils.twClassNames(
"group/27235bcb flex w-full min-w-[0px] flex-col items-start gap-3 max-w-full",
className
)}
ref={ref}
{...otherProps}
>
<div className="flex w-full min-w-[0px] flex-col items-start gap-4 rounded-lg border border-solid border-border-default bg-surface-neutral-secondary px-3 py-3 shadow-sm relative max-w-full">
<Icon
className="absolute top-3 right-3"
variant={
flagVariant === "information"
? "information"
: flagVariant === "warning"
? "warning"
: flagVariant === "success"
? "success"
: flagVariant === "neutral"
? "neutral"
: flagVariant === "destructive"
? "destructive"
: flagVariant === "tertiary"
? "tertiary"
: flagVariant === "secondary"
? "secondary"
: "primary"
}
icon={<FeatherFlag />}
/>
{tagsSlot ? (
<div
className={SubframeUtils.twClassNames(
"flex w-full min-w-[0px] flex-wrap items-center gap-1.5 pr-7 grow",
{ hidden: hideTags }
)}
>
{tagsSlot}
</div>
) : null}
<div
className={SubframeUtils.twClassNames(
"hidden h-5 w-full min-w-[0px] flex-none flex-wrap items-center gap-1.5 pr-7",
{ flex: keepTagRowSpace }
)}
/>
{titleSlot ? (
<div className="flex w-full min-w-[0px] flex-col items-start justify-center gap-2 pr-7">
{titleSlot}
</div>
) : null}
<div className="flex w-full min-w-[0px] flex-wrap items-center gap-2">
{valueSlot ? (
<div
className={SubframeUtils.twClassNames(
"flex min-w-[0px] flex-col items-start gap-2",
{ hidden: priceLocked }
)}
>
{valueSlot}
</div>
) : null}
<div
className={SubframeUtils.twClassNames(
"hidden min-w-[0px] items-center gap-1.5",
{ flex: priceLocked }
)}
>
<FeatherLock className="text-body-small-medium-0 font-body-small-medium-0 text-icon-neutral-tertiary" />
<span className="break-words text-body-small-regular-0 font-body-small-regular-0 text-text-neutral-tertiary">
Umsatz gesperrt
</span>
</div>
</div>
<div className="flex h-px w-full flex-none flex-col items-center gap-2 bg-border-default" />
<div className="flex w-full min-w-[0px] flex-wrap items-center justify-between gap-x-2 gap-y-2">
{footerLeftSlot ? (
<div className="flex items-center">{footerLeftSlot}</div>
) : null}
{footerRightSlot ? (
<div className="flex items-center">{footerRightSlot}</div>
) : null}
</div>
</div>
</div>
);
}
);
export const KanbanCard = KanbanCardRoot;