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
479 lines
21 KiB
TypeScript
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;
|