Files
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

479 lines
21 KiB
TypeScript

// @subframe/sync-disable
// @ts-nocheck
"use client";
/*
* Documentation:
* Address Dialog — https://app.subframe.com/f023064a5ca0/library?component=Address+Dialog_b3a6c4d9-a908-4acc-881c-132a867b5ef6
* Avatar — https://app.subframe.com/f023064a5ca0/library?component=Avatar_bec25ae6-5010-4485-b46b-cf79e3943ab2
* Button — https://app.subframe.com/f023064a5ca0/library?component=Button_3b777358-b86b-40af-9327-891efc6826fe
* Checkbox — https://app.subframe.com/f023064a5ca0/library?component=Checkbox_3816e3b5-c48c-499b-b45e-0777c6972523
* Dialog — https://app.subframe.com/f023064a5ca0/library?component=Dialog_ca59db17-43fb-4247-8094-3c55162e902d
* Icon Button — https://app.subframe.com/f023064a5ca0/library?component=Icon+Button_af9405b1-8c54-4e01-9786-5aad308224f6
* Icon with Background — https://app.subframe.com/f023064a5ca0/library?component=Icon+with+Background_4a18e9fc-cea3-46ca-8c53-2412462342ad
* Search Input — https://app.subframe.com/f023064a5ca0/library?component=Search+Input_75a35ed3-52c0-4435-8586-e390eb77d723
* Select — https://app.subframe.com/f023064a5ca0/library?component=Select_bb88f90b-8c43-4b73-9c2f-3558ce7838f3
* Text Area — https://app.subframe.com/f023064a5ca0/library?component=Text+Area_4ec05ee8-8f1c-46b2-b863-5419aa7f5cce
* Text Input — https://app.subframe.com/f023064a5ca0/library?component=Text+Input_be48ca43-f8e7-4c0e-8870-d219ea11abfe
*/
import React from "react";
import { FeatherArrowRight } from "@subframe/core";
import { FeatherCheck } from "@subframe/core";
import { FeatherChevronDown } from "@subframe/core";
import { FeatherMapPin } from "@subframe/core";
import { FeatherUserSearch } from "@subframe/core";
import { FeatherX } from "@subframe/core";
import * as SubframeCore from "@subframe/core";
import * as SubframeUtils from "../../utils";
import { Avatar } from "../Avatar";
import { Button } from "../Button";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { IconButton } from "../IconButton";
import { IconWithBackground } from "../IconWithBackground";
import { SearchInput } from "../SearchInput";
import { Select } from "../Select";
import { TextArea } from "../TextArea";
import { TextInput } from "../TextInput";
export interface AddressDialogRootProps
extends React.ComponentProps<typeof Dialog> {
hideAddressType?: boolean;
hideNote?: boolean;
hideCompany?: boolean;
hideDefaultAddress?: boolean;
hideImport?: boolean;
title?: React.ReactNode;
subtitle?: React.ReactNode;
className?: string;
}
const AddressDialogRoot = React.forwardRef<
React.ElementRef<typeof Dialog>,
AddressDialogRootProps
>(function AddressDialogRoot(
{
hideAddressType = false,
hideNote = false,
hideCompany = false,
hideDefaultAddress = false,
hideImport = false,
title,
subtitle,
className,
...otherProps
}: AddressDialogRootProps,
ref
) {
return (
<Dialog
className={SubframeUtils.twClassNames("group/b3a6c4d9", className)}
ref={ref}
{...otherProps}
>
<Dialog.Content
className="mobile:w-full mobile:max-w-[calc(100vw-32px)]"
size="medium"
hideCloseIcon={true}
>
<div className="flex w-full flex-col items-start gap-4">
<div className="flex w-full items-start justify-between gap-3">
<div className="flex items-start gap-3">
<IconWithBackground
variant="primary"
size="medium"
icon={<FeatherMapPin />}
/>
<div className="flex flex-col items-start gap-1">
{title ? (
<span className="text-heading-0 font-heading-0 text-text-neutral-primary">
{title}
</span>
) : null}
{subtitle ? (
<span className="text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
{subtitle}
</span>
) : null}
</div>
</div>
<IconButton variant="tertiary" size="small" icon={<FeatherX />} />
</div>
<div className="flex w-full flex-col items-start gap-3">
<div
className={SubframeUtils.twClassNames(
"flex w-full flex-col items-start gap-3",
{ hidden: hideImport }
)}
>
<div className="flex w-full items-center justify-between rounded-md border border-solid border-border-default bg-surface-neutral-secondary px-3 py-3 gap-3 mobile:flex-wrap">
<div className="flex flex-col items-start gap-1">
<span className="text-body-default-medium-0 font-body-default-medium-0 text-text-neutral-primary">
Aus Kontakt übernehmen
</span>
<span className="text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
Adressdaten aus einem bestehenden Kontakt vorbefüllen
</span>
</div>
<SubframeCore.DropdownMenu.Root>
<SubframeCore.DropdownMenu.Trigger asChild={true}>
<Button
variant="secondary"
size="small"
icon={<FeatherUserSearch />}
iconRight={<FeatherChevronDown />}
>
Kontakt suchen
</Button>
</SubframeCore.DropdownMenu.Trigger>
<SubframeCore.DropdownMenu.Portal>
<SubframeCore.DropdownMenu.Content
side="bottom"
align="end"
sideOffset={4}
asChild={true}
>
<div className="flex w-80 flex-none flex-col items-start overflow-hidden rounded-md border border-solid border-border-default bg-surface-neutral-secondary shadow-lg">
<div className="flex w-full flex-col items-start px-2 py-2">
<SearchInput
className="h-9 w-full flex-none"
size="small"
fillContainer={true}
label="Label"
helpText="Help text"
message="Hilfetext"
hideShortcut={true}
>
<SearchInput.Input placeholder="Kontakt oder Firma suchen" />
</SearchInput>
</div>
<div className="flex h-px w-full flex-none flex-col items-start bg-border-default" />
<div className="flex w-full flex-col items-start px-1 py-1">
<div className="flex w-full items-center gap-3 rounded-md px-2 py-2 cursor-pointer hover:bg-surface-neutral-tertiary">
<Avatar size="small">MB</Avatar>
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<span className="line-clamp-1 w-full text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-primary">
Markus Bauer
</span>
<span className="line-clamp-1 w-full text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
Bauer Elektrotechnik GmbH · Berlin
</span>
</div>
</div>
<div className="flex w-full items-center gap-3 rounded-md px-2 py-2 cursor-pointer hover:bg-surface-neutral-tertiary">
<Avatar size="small">SK</Avatar>
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<span className="line-clamp-1 w-full text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-primary">
Sandra Keller
</span>
<span className="line-clamp-1 w-full text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
Keller Immobilien · Potsdam
</span>
</div>
</div>
<div className="flex w-full items-center gap-3 rounded-md px-2 py-2 cursor-pointer hover:bg-surface-neutral-tertiary">
<Avatar size="small">JR</Avatar>
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<span className="line-clamp-1 w-full text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-primary">
Jonas Richter
</span>
<span className="line-clamp-1 w-full text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
Richter Bau AG · Hamburg
</span>
</div>
</div>
<div className="flex w-full items-center gap-3 rounded-md px-2 py-2 cursor-pointer hover:bg-surface-neutral-tertiary">
<Avatar size="small">EV</Avatar>
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<span className="line-clamp-1 w-full text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-primary">
Elena Vogt
</span>
<span className="line-clamp-1 w-full text-body-small-regular-150 font-body-small-regular-150 text-text-neutral-tertiary">
— · München
</span>
</div>
</div>
</div>
<div className="flex h-px w-full flex-none flex-col items-start bg-border-default" />
<div className="flex w-full items-center gap-2 px-3 py-2.5 cursor-pointer hover:bg-surface-neutral-tertiary">
<span className="grow shrink-0 basis-0 text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-secondary">
Alle Kontakte durchsuchen
</span>
<FeatherArrowRight className="text-body-small-regular-150 font-body-small-regular-150 text-icon-neutral-tertiary" />
</div>
</div>
</SubframeCore.DropdownMenu.Content>
</SubframeCore.DropdownMenu.Portal>
</SubframeCore.DropdownMenu.Root>
</div>
<div className="flex h-px w-full flex-none flex-col items-start bg-border-default" />
</div>
<Select
className={SubframeUtils.twClassNames({
hidden: hideAddressType,
})}
variant="outline"
fillContainer={true}
showLabel={true}
label="Adresstyp"
placeholder="Lieferadresse"
helpText=""
errorText=""
message=""
>
<Select.Item
value="Lieferadresse"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Lieferadresse
</Select.Item>
<Select.Item
value="Baustelle"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Baustelle
</Select.Item>
<Select.Item
value="Rechnungsadresse"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Rechnungsadresse
</Select.Item>
<Select.Item
value="Büro"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Büro
</Select.Item>
<Select.Item
value="Privat"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Privat
</Select.Item>
</Select>
<TextInput
className={SubframeUtils.twClassNames("h-auto w-full flex-none", {
hidden: hideCompany,
})}
fillContainer={true}
showLabel={true}
label="Firma"
helpText=""
message=""
>
<TextInput.Input placeholder="Firmenname (optional)" value="" />
</TextInput>
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="Name / Ansprechpartner"
helpText=""
message=""
>
<TextInput.Input placeholder="Vor- und Nachname" value="" />
</TextInput>
<div className="flex w-full items-end gap-3 mobile:flex-wrap">
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="Straße"
helpText=""
message=""
>
<TextInput.Input placeholder="Straßenname" value="" />
</TextInput>
</div>
<div className="flex w-48 flex-none flex-col items-start">
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="Nr."
helpText=""
message=""
>
<TextInput.Input placeholder="12a" value="" />
</TextInput>
</div>
</div>
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="Adresszusatz"
helpText=""
message=""
>
<TextInput.Input
placeholder="Etage, Gebäude, c/o (optional)"
value=""
/>
</TextInput>
<div className="flex w-full items-end gap-3 mobile:flex-wrap">
<div className="flex w-48 flex-none flex-col items-start">
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="PLZ"
helpText=""
message=""
>
<TextInput.Input placeholder="10115" value="" />
</TextInput>
</div>
<div className="flex grow shrink-0 basis-0 flex-col items-start">
<TextInput
className="h-auto w-full flex-none"
fillContainer={true}
showLabel={true}
label="Ort"
helpText=""
message=""
>
<TextInput.Input placeholder="Berlin" value="" />
</TextInput>
</div>
</div>
<Select
variant="outline"
fillContainer={true}
showLabel={true}
label="Land"
placeholder="Deutschland"
helpText=""
errorText=""
message=""
>
<Select.Item
value="Deutschland"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Deutschland
</Select.Item>
<Select.Item
value="Österreich"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Österreich
</Select.Item>
<Select.Item
value="Schweiz"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Schweiz
</Select.Item>
<Select.Item
value="Niederlande"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Niederlande
</Select.Item>
<Select.Item
value="Polen"
showItemText={true}
content={
<span className="grow shrink-0 basis-0 text-body-default-regular-0 font-body-default-regular-0 text-text-neutral-tertiary">
Option
</span>
}
>
Polen
</Select.Item>
</Select>
<div
className={SubframeUtils.twClassNames(
"flex w-full flex-col items-start gap-3",
{ hidden: hideNote }
)}
>
<div className="flex h-px w-full flex-none flex-col items-start bg-border-default" />
<TextArea
className="h-auto w-full flex-none max-w-[9999px]"
error={false}
fillContainer={false}
showLabel={true}
label="Notiz zur Adresse"
helpText="Nur intern sichtbar, wird nicht auf Versandlabels gedruckt"
errorText=""
message=""
>
<TextArea.Input
className="h-16 w-full flex-none min-h-[64px]"
placeholder="z. B. Zufahrt über den Hinterhof, Schlüssel beim Nachbarn, Parkmöglichkeit"
value=""
/>
</TextArea>
</div>
<Checkbox
className={SubframeUtils.twClassNames({
hidden: hideDefaultAddress,
})}
label="Als Standardadresse verwenden"
description=""
helpText=""
/>
</div>
<div className="flex w-full items-center justify-end gap-2 bg-surface-neutral-secondary pt-2 sticky bottom-0">
<Button variant="tertiary">Abbrechen</Button>
<Button variant="primary" icon={<FeatherCheck />}>
Adresse speichern
</Button>
</div>
</div>
</Dialog.Content>
</Dialog>
);
});
export const AddressDialog = AddressDialogRoot;