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
This commit is contained in:
commit
b8bf987d0e
250 files changed
+33153
No files matched your search
@@ -0,0 +1,901 @@
|
||||
// @subframe/sync-disable
|
||||
// @ts-nocheck
|
||||
"use client";
|
||||
/*
|
||||
* Documentation:
|
||||
* Node Workflow Engine — https://app.subframe.com/f023064a5ca0/library?component=Node+Workflow+Engine_e75d1b96-38eb-4380-aef0-a320a2d99e5c
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { FeatherAlertCircle } from "@subframe/core";
|
||||
import { FeatherArrowRightLeft } from "@subframe/core";
|
||||
import { FeatherBellRing } from "@subframe/core";
|
||||
import { FeatherCalendar } from "@subframe/core";
|
||||
import { FeatherCalendarClock } from "@subframe/core";
|
||||
import { FeatherCalendarPlus } from "@subframe/core";
|
||||
import { FeatherCalendarX } from "@subframe/core";
|
||||
import { FeatherCheck } from "@subframe/core";
|
||||
import { FeatherClipboardCheck } from "@subframe/core";
|
||||
import { FeatherClipboardList } from "@subframe/core";
|
||||
import { FeatherClock } from "@subframe/core";
|
||||
import { FeatherFilter } from "@subframe/core";
|
||||
import { FeatherFolderPlus } from "@subframe/core";
|
||||
import { FeatherGitBranch } from "@subframe/core";
|
||||
import { FeatherGitMerge } from "@subframe/core";
|
||||
import { FeatherGitPullRequest } from "@subframe/core";
|
||||
import { FeatherGlobe } from "@subframe/core";
|
||||
import { FeatherInbox } from "@subframe/core";
|
||||
import { FeatherMail } from "@subframe/core";
|
||||
import { FeatherMailX } from "@subframe/core";
|
||||
import { FeatherPlus } from "@subframe/core";
|
||||
import { FeatherRepeat } from "@subframe/core";
|
||||
import { FeatherSignpost } from "@subframe/core";
|
||||
import { FeatherSquareCheck } from "@subframe/core";
|
||||
import { FeatherStickyNote } from "@subframe/core";
|
||||
import { FeatherTimer } from "@subframe/core";
|
||||
import { FeatherTrophy } from "@subframe/core";
|
||||
import { FeatherUserCheck } from "@subframe/core";
|
||||
import { FeatherUserCog } from "@subframe/core";
|
||||
import { FeatherUserPen } from "@subframe/core";
|
||||
import { FeatherUserPlus } from "@subframe/core";
|
||||
import { FeatherVariable } from "@subframe/core";
|
||||
import { FeatherWebhook } from "@subframe/core";
|
||||
import { FeatherZap } from "@subframe/core";
|
||||
import * as SubframeCore from "@subframe/core";
|
||||
import * as SubframeUtils from "../../utils";
|
||||
|
||||
export interface KatalogProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Katalog = React.forwardRef<HTMLDivElement, KatalogProps>(function Katalog(
|
||||
{ className, ...otherProps }: KatalogProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"flex w-full flex-col items-start gap-12 px-8 py-8",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className="flex w-full flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Auslöser
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="neuer-kontakt" />
|
||||
<NodeWorkflowEngine art="kontakt-geaendert" />
|
||||
<NodeWorkflowEngine art="neue-anfrage" />
|
||||
<NodeWorkflowEngine art="lead-qualifiziert" />
|
||||
<NodeWorkflowEngine art="projekt-gewonnen" />
|
||||
<NodeWorkflowEngine art="status-gewechselt" />
|
||||
<NodeWorkflowEngine art="projekt-angelegt" />
|
||||
<NodeWorkflowEngine art="deadline-naht" />
|
||||
<NodeWorkflowEngine art="email-eingegangen" />
|
||||
<NodeWorkflowEngine art="email-unzustellbar" />
|
||||
<NodeWorkflowEngine art="formular-abgeschickt" />
|
||||
<NodeWorkflowEngine art="termin-gebucht" />
|
||||
<NodeWorkflowEngine art="termin-storniert" />
|
||||
<NodeWorkflowEngine art="ereignis-erstellt" />
|
||||
<NodeWorkflowEngine art="zeitplan" />
|
||||
<NodeWorkflowEngine art="webhook-empfangen" />
|
||||
</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 flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Aktionen: Daten & CRM
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="kontakt-aendern" />
|
||||
<NodeWorkflowEngine art="notiz-anlegen" />
|
||||
</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 flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Aktionen: Kommunikation
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="benachrichtigung-senden" />
|
||||
</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 flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Aktionen: Aufgaben
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="aufgabe-anlegen" />
|
||||
<NodeWorkflowEngine art="aufgabe-zuweisen" />
|
||||
</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 flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Logik
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="wenn-dann" />
|
||||
<NodeWorkflowEngine art="warten" />
|
||||
<NodeWorkflowEngine art="schleife" />
|
||||
<NodeWorkflowEngine art="oder" />
|
||||
<NodeWorkflowEngine art="und" />
|
||||
<NodeWorkflowEngine art="switch" />
|
||||
<NodeWorkflowEngine art="wert-setzen" />
|
||||
<NodeWorkflowEngine art="filter" />
|
||||
</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 flex-col items-start gap-6">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-tertiary">
|
||||
Integration
|
||||
</span>
|
||||
<div className="w-full items-start grid grid-cols-6 gap-x-8 gap-y-10 mobile:grid mobile:gap-x-8 mobile:gap-y-10 mobile:grid-cols-3">
|
||||
<NodeWorkflowEngine art="http-anfrage" />
|
||||
<div className="flex flex-col items-center gap-1 self-stretch">
|
||||
<NodeWorkflowEngine art="pds-synchronisieren" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export interface NodeItemProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
||||
art?:
|
||||
| "ausloeser"
|
||||
| "aktion"
|
||||
| "logik"
|
||||
| "multi-logik-3"
|
||||
| "multi-logik-4"
|
||||
| "ereignis";
|
||||
farbe?: "gruen" | "violett" | "blau" | "rot" | "amber" | "grau";
|
||||
status?: "standard" | "laeuft" | "fehler" | "erfolgreich";
|
||||
icon?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
beschreibung?: React.ReactNode;
|
||||
ausgewaehlt?: boolean;
|
||||
inaktiv?: boolean;
|
||||
portLabel1?: React.ReactNode;
|
||||
portLabel2?: React.ReactNode;
|
||||
connectedOutputPorts?: number[];
|
||||
onInputPortClick?: () => void;
|
||||
onOutputPortClick?: (port: number) => void;
|
||||
onAddAfter?: (port: number) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const NodeItem = React.forwardRef<HTMLDivElement, NodeItemProps>(
|
||||
function NodeItem(
|
||||
{
|
||||
art = "aktion",
|
||||
farbe = "blau",
|
||||
status = "standard",
|
||||
icon = <FeatherZap />,
|
||||
title,
|
||||
beschreibung,
|
||||
ausgewaehlt = false,
|
||||
inaktiv = false,
|
||||
portLabel1,
|
||||
portLabel2,
|
||||
connectedOutputPorts = [],
|
||||
onInputPortClick,
|
||||
onOutputPortClick,
|
||||
onAddAfter,
|
||||
className,
|
||||
...otherProps
|
||||
}: NodeItemProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"group/3535de66 flex cursor-pointer flex-col items-center gap-1.5 group/node-root",
|
||||
{ "opacity-50 pointer-events-none": inaktiv },
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"hidden items-center justify-center -mb-4 z-10",
|
||||
{ flex: art === "ereignis" }
|
||||
)}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
label="3"
|
||||
richtung={art === "ereignis" ? "oben" : "unten"}
|
||||
portType="input"
|
||||
portIndex={1}
|
||||
onPortClick={() => onInputPortClick?.()}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"flex items-start -mr-[11px] z-10",
|
||||
{ hidden: art === "ereignis" || art === "ausloeser" }
|
||||
)}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
className="h-auto w-auto flex-none self-stretch"
|
||||
label="3"
|
||||
portType="input"
|
||||
portIndex={1}
|
||||
onPortClick={() => onInputPortClick?.()}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"flex h-32 w-32 flex-none items-center justify-center rounded-lg border-[6px] border-solid border-border-default bg-surface-ui-subtle-blue-default relative group-hover/3535de66:shadow-lg",
|
||||
{
|
||||
"border-[6px] border-solid border-[#0791f066] group-hover/3535de66:shadow-none":
|
||||
ausgewaehlt,
|
||||
"border-2 border-solid border-border-negative-solid":
|
||||
status === "fehler",
|
||||
"bg-surface-neutral-secondary": farbe === "grau",
|
||||
"bg-surface-ui-subtle-yellow-default": farbe === "amber",
|
||||
"bg-surface-ui-subtle-red-default": farbe === "rot",
|
||||
"bg-surface-ui-subtle-purple-default": farbe === "violett",
|
||||
"bg-surface-ui-subtle-green-default": farbe === "gruen",
|
||||
"rounded-round": art === "ereignis",
|
||||
"rounded-s-round": art === "ausloeser",
|
||||
}
|
||||
)}
|
||||
>
|
||||
{icon ? (
|
||||
<SubframeCore.IconWrapper
|
||||
className={SubframeUtils.twClassNames(
|
||||
"text-title-page font-title-page text-icon-ui-blue",
|
||||
{
|
||||
"text-icon-neutral-tertiary": farbe === "grau",
|
||||
"text-icon-ui-yellow": farbe === "amber",
|
||||
"text-icon-ui-red": farbe === "rot",
|
||||
"text-icon-ui-purple": farbe === "violett",
|
||||
"text-icon-feedback-positive": farbe === "gruen",
|
||||
}
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</SubframeCore.IconWrapper>
|
||||
) : null}
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"hidden items-start absolute -top-2 -right-2",
|
||||
{
|
||||
flex:
|
||||
status === "erfolgreich" ||
|
||||
status === "fehler" ||
|
||||
status === "laeuft",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<NodeWorkflowEngine.StatusBadge
|
||||
status={
|
||||
status === "fehler"
|
||||
? "fehler"
|
||||
: status === "laeuft"
|
||||
? "laeuft"
|
||||
: "erfolgreich"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"flex flex-col items-start gap-5 -ml-[11px] z-10",
|
||||
{
|
||||
hidden: art === "ereignis",
|
||||
"gap-1": art === "multi-logik-4",
|
||||
"gap-2.5": art === "multi-logik-3",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
label={portLabel1}
|
||||
richtung="rechts"
|
||||
portType="output"
|
||||
portIndex={1}
|
||||
connected={connectedOutputPorts.includes(1)}
|
||||
onPortClick={() => onOutputPortClick?.(1)}
|
||||
onAddClick={() => onAddAfter?.(1)}
|
||||
/>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden items-start", {
|
||||
flex:
|
||||
art === "multi-logik-4" ||
|
||||
art === "multi-logik-3" ||
|
||||
art === "logik",
|
||||
})}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
className="h-auto w-auto flex-none self-stretch"
|
||||
label={portLabel2}
|
||||
richtung="rechts"
|
||||
portType="output"
|
||||
portIndex={2}
|
||||
connected={connectedOutputPorts.includes(2)}
|
||||
onPortClick={() => onOutputPortClick?.(2)}
|
||||
onAddClick={() => onAddAfter?.(2)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden items-start", {
|
||||
flex: art === "multi-logik-4" || art === "multi-logik-3",
|
||||
})}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
className="h-auto w-auto flex-none self-stretch"
|
||||
label="3"
|
||||
richtung="rechts"
|
||||
portType="output"
|
||||
portIndex={3}
|
||||
connected={connectedOutputPorts.includes(3)}
|
||||
onPortClick={() => onOutputPortClick?.(3)}
|
||||
onAddClick={() => onAddAfter?.(3)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden items-start", {
|
||||
flex: art === "multi-logik-4",
|
||||
})}
|
||||
>
|
||||
<NodeWorkflowEngine.Port
|
||||
className="h-auto w-auto flex-none self-stretch"
|
||||
label="4"
|
||||
richtung="rechts"
|
||||
portType="output"
|
||||
portIndex={4}
|
||||
connected={connectedOutputPorts.includes(4)}
|
||||
onPortClick={() => onOutputPortClick?.(4)}
|
||||
onAddClick={() => onAddAfter?.(4)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span className="text-body-small-medium-150 font-body-small-medium-150 text-text-neutral-primary text-center">
|
||||
{title}
|
||||
</span>
|
||||
<span className="text-body-extra-small-regular-150 font-body-extra-small-regular-150 text-text-neutral-tertiary text-center">
|
||||
{beschreibung}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export interface PortProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
label?: React.ReactNode;
|
||||
richtung?: "keine" | "rechts" | "oben" | "unten";
|
||||
portType?: "input" | "output";
|
||||
portIndex?: number;
|
||||
connected?: boolean;
|
||||
onPortClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onAddClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Port = React.forwardRef<HTMLDivElement, PortProps>(function Port(
|
||||
{
|
||||
label,
|
||||
richtung = "keine",
|
||||
portType,
|
||||
portIndex,
|
||||
connected = false,
|
||||
onPortClick,
|
||||
onAddClick,
|
||||
className,
|
||||
...otherProps
|
||||
}: PortProps,
|
||||
ref
|
||||
) {
|
||||
const handlePortClick: React.MouseEventHandler<HTMLDivElement> = (event) => {
|
||||
event.stopPropagation();
|
||||
onPortClick?.(event);
|
||||
};
|
||||
|
||||
const handleAddClick: React.MouseEventHandler<HTMLDivElement> = (event) => {
|
||||
event.stopPropagation();
|
||||
onAddClick?.(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"group/21cef75b flex cursor-pointer items-center",
|
||||
{
|
||||
"flex-col": richtung === "unten" || richtung === "oben",
|
||||
"h-8": richtung === "rechts",
|
||||
},
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
data-workflow-port
|
||||
{...otherProps}
|
||||
>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden flex-col items-center", {
|
||||
flex: richtung === "oben" && !connected,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="flex h-8 w-8 flex-none items-center justify-center rounded-sm border border-solid border-border-strong bg-surface-neutral-secondary cursor-pointer hover:bg-surface-neutral-tertiary"
|
||||
data-workflow-port-add
|
||||
onClick={handleAddClick}
|
||||
>
|
||||
<FeatherPlus className="text-body-extra-small-semibold-0 font-body-extra-small-semibold-0 text-icon-neutral-secondary" />
|
||||
</div>
|
||||
<div className="flex h-12 w-6 flex-none items-start justify-center">
|
||||
<div className="flex w-px flex-none items-start self-stretch bg-border-strong" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex h-4 w-4 flex-none items-start rounded-round border-2 border-solid border-border-strong bg-surface-neutral-secondary z-10 group-hover/21cef75b:border-2 group-hover/21cef75b:border-solid group-hover/21cef75b:border-border-information-solid group-hover/21cef75b:shadow-focus"
|
||||
data-workflow-port-dot
|
||||
data-workflow-port-type={portType}
|
||||
data-workflow-port-index={portIndex}
|
||||
onClick={handlePortClick}
|
||||
/>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden items-center", {
|
||||
flex: richtung === "rechts" && !connected,
|
||||
})}
|
||||
>
|
||||
<div className="flex h-px w-16 flex-none items-start bg-border-strong -ml-2 -mt-0.5" />
|
||||
<div
|
||||
className="flex h-8 w-8 flex-none items-center justify-center rounded-sm border border-solid border-border-strong bg-surface-neutral-secondary cursor-pointer hover:bg-surface-neutral-tertiary"
|
||||
data-workflow-port-add
|
||||
onClick={handleAddClick}
|
||||
>
|
||||
<FeatherPlus className="text-body-extra-small-semibold-0 font-body-extra-small-semibold-0 text-icon-neutral-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={SubframeUtils.twClassNames("hidden flex-col items-center", {
|
||||
flex: richtung === "unten" && !connected,
|
||||
})}
|
||||
>
|
||||
<div className="flex h-12 w-4 flex-none items-start justify-center -mt-2">
|
||||
<div className="flex w-px flex-none items-start self-stretch bg-border-strong" />
|
||||
</div>
|
||||
<div
|
||||
className="flex h-8 w-8 flex-none items-center justify-center rounded-sm border border-solid border-border-strong bg-surface-neutral-secondary cursor-pointer hover:bg-surface-neutral-tertiary"
|
||||
data-workflow-port-add
|
||||
onClick={handleAddClick}
|
||||
>
|
||||
<FeatherPlus className="text-body-extra-small-semibold-0 font-body-extra-small-semibold-0 text-icon-neutral-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export interface StatusBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
status?: "fehler" | "laeuft" | "erfolgreich";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const StatusBadge = React.forwardRef<HTMLDivElement, StatusBadgeProps>(
|
||||
function StatusBadge(
|
||||
{ status = "erfolgreich", className, ...otherProps }: StatusBadgeProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"group/e60ff70b flex h-5 w-5 items-center justify-center rounded-sm bg-surface-feedback-solid-positive-default",
|
||||
{
|
||||
"border border-solid border-border-default bg-surface-neutral-secondary":
|
||||
status === "laeuft",
|
||||
"bg-surface-feedback-solid-negative-default": status === "fehler",
|
||||
},
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
{status === "fehler" ? (
|
||||
<FeatherAlertCircle
|
||||
className={SubframeUtils.twClassNames(
|
||||
"text-body-extra-small-semibold-0 font-body-extra-small-semibold-0 text-icon-neutral-on-solid",
|
||||
{ "hidden text-icon-neutral-primary": status === "laeuft" }
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<FeatherCheck
|
||||
className={SubframeUtils.twClassNames(
|
||||
"text-body-extra-small-semibold-0 font-body-extra-small-semibold-0 text-icon-neutral-on-solid",
|
||||
{ "hidden text-icon-neutral-primary": status === "laeuft" }
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<SubframeCore.Loader
|
||||
className={SubframeUtils.twClassNames(
|
||||
"hidden text-body-extra-small-regular-0 font-body-extra-small-regular-0 text-icon-neutral-tertiary",
|
||||
{ "inline-flex": status === "laeuft" }
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export interface NodeWorkflowEngineRootProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
||||
art?:
|
||||
| "neuer-kontakt"
|
||||
| "kontakt-geaendert"
|
||||
| "neue-anfrage"
|
||||
| "lead-qualifiziert"
|
||||
| "projekt-gewonnen"
|
||||
| "status-gewechselt"
|
||||
| "projekt-angelegt"
|
||||
| "deadline-naht"
|
||||
| "email-eingegangen"
|
||||
| "email-unzustellbar"
|
||||
| "formular-abgeschickt"
|
||||
| "termin-gebucht"
|
||||
| "termin-storniert"
|
||||
| "ereignis-erstellt"
|
||||
| "zeitplan"
|
||||
| "webhook-empfangen"
|
||||
| "kontakt-aendern"
|
||||
| "notiz-anlegen"
|
||||
| "benachrichtigung-senden"
|
||||
| "aufgabe-anlegen"
|
||||
| "aufgabe-zuweisen"
|
||||
| "wenn-dann"
|
||||
| "warten"
|
||||
| "schleife"
|
||||
| "oder"
|
||||
| "und"
|
||||
| "switch"
|
||||
| "wert-setzen"
|
||||
| "filter"
|
||||
| "http-anfrage"
|
||||
| "pds-synchronisieren";
|
||||
onInputPortClick?: () => void;
|
||||
onOutputPortClick?: (port: number) => void;
|
||||
onAddAfter?: (port: number) => void;
|
||||
connectedOutputPorts?: number[];
|
||||
title?: React.ReactNode;
|
||||
beschreibung?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const NodeWorkflowEngineRoot = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
NodeWorkflowEngineRootProps
|
||||
>(function NodeWorkflowEngineRoot(
|
||||
{
|
||||
art = "neuer-kontakt",
|
||||
onInputPortClick,
|
||||
onOutputPortClick,
|
||||
onAddAfter,
|
||||
connectedOutputPorts = [],
|
||||
title,
|
||||
beschreibung,
|
||||
className,
|
||||
...otherProps
|
||||
}: NodeWorkflowEngineRootProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className={SubframeUtils.twClassNames(
|
||||
"group/e75d1b96 flex flex-col items-center",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
<NodeItem
|
||||
art={
|
||||
art === "pds-synchronisieren"
|
||||
? "aktion"
|
||||
: art === "http-anfrage"
|
||||
? "aktion"
|
||||
: art === "filter"
|
||||
? "aktion"
|
||||
: art === "wert-setzen"
|
||||
? "aktion"
|
||||
: art === "switch"
|
||||
? "multi-logik-3"
|
||||
: art === "und"
|
||||
? "logik"
|
||||
: art === "oder"
|
||||
? "logik"
|
||||
: art === "schleife"
|
||||
? "logik"
|
||||
: art === "warten"
|
||||
? "aktion"
|
||||
: art === "wenn-dann"
|
||||
? "logik"
|
||||
: art === "aufgabe-zuweisen"
|
||||
? "aktion"
|
||||
: art === "aufgabe-anlegen"
|
||||
? "aktion"
|
||||
: art === "benachrichtigung-senden"
|
||||
? "aktion"
|
||||
: art === "notiz-anlegen"
|
||||
? "aktion"
|
||||
: art === "kontakt-aendern"
|
||||
? "aktion"
|
||||
: "ausloeser"
|
||||
}
|
||||
farbe={
|
||||
art === "pds-synchronisieren"
|
||||
? "rot"
|
||||
: art === "http-anfrage"
|
||||
? "grau"
|
||||
: art === "filter"
|
||||
? "violett"
|
||||
: art === "wert-setzen"
|
||||
? "violett"
|
||||
: art === "switch"
|
||||
? "violett"
|
||||
: art === "und"
|
||||
? "violett"
|
||||
: art === "oder"
|
||||
? "violett"
|
||||
: art === "schleife"
|
||||
? "violett"
|
||||
: art === "warten"
|
||||
? "violett"
|
||||
: art === "wenn-dann"
|
||||
? "violett"
|
||||
: art === "aufgabe-zuweisen"
|
||||
? "amber"
|
||||
: art === "aufgabe-anlegen"
|
||||
? "amber"
|
||||
: art === "benachrichtigung-senden"
|
||||
? "blau"
|
||||
: art === "notiz-anlegen"
|
||||
? "rot"
|
||||
: art === "kontakt-aendern"
|
||||
? "rot"
|
||||
: "gruen"
|
||||
}
|
||||
icon={
|
||||
art === "pds-synchronisieren" ? (
|
||||
<img
|
||||
src="/logos/pds.svg"
|
||||
alt="PDS"
|
||||
className="h-9 w-[74px] object-contain"
|
||||
/>
|
||||
) : art === "http-anfrage" ? (
|
||||
<FeatherGlobe />
|
||||
) : art === "filter" ? (
|
||||
<FeatherFilter />
|
||||
) : art === "wert-setzen" ? (
|
||||
<FeatherVariable />
|
||||
) : art === "switch" ? (
|
||||
<FeatherSignpost />
|
||||
) : art === "und" ? (
|
||||
<FeatherGitPullRequest />
|
||||
) : art === "oder" ? (
|
||||
<FeatherGitMerge />
|
||||
) : art === "schleife" ? (
|
||||
<FeatherRepeat />
|
||||
) : art === "warten" ? (
|
||||
<FeatherTimer />
|
||||
) : art === "wenn-dann" ? (
|
||||
<FeatherGitBranch />
|
||||
) : art === "aufgabe-zuweisen" ? (
|
||||
<FeatherClipboardList />
|
||||
) : art === "aufgabe-anlegen" ? (
|
||||
<FeatherSquareCheck />
|
||||
) : art === "benachrichtigung-senden" ? (
|
||||
<FeatherBellRing />
|
||||
) : art === "notiz-anlegen" ? (
|
||||
<FeatherStickyNote />
|
||||
) : art === "kontakt-aendern" ? (
|
||||
<FeatherUserPen />
|
||||
) : art === "webhook-empfangen" ? (
|
||||
<FeatherWebhook />
|
||||
) : art === "zeitplan" ? (
|
||||
<FeatherClock />
|
||||
) : art === "ereignis-erstellt" ? (
|
||||
<FeatherCalendar />
|
||||
) : art === "termin-storniert" ? (
|
||||
<FeatherCalendarX />
|
||||
) : art === "termin-gebucht" ? (
|
||||
<FeatherCalendarPlus />
|
||||
) : art === "formular-abgeschickt" ? (
|
||||
<FeatherClipboardCheck />
|
||||
) : art === "email-unzustellbar" ? (
|
||||
<FeatherMailX />
|
||||
) : art === "email-eingegangen" ? (
|
||||
<FeatherMail />
|
||||
) : art === "deadline-naht" ? (
|
||||
<FeatherCalendarClock />
|
||||
) : art === "projekt-angelegt" ? (
|
||||
<FeatherFolderPlus />
|
||||
) : art === "status-gewechselt" ? (
|
||||
<FeatherArrowRightLeft />
|
||||
) : art === "projekt-gewonnen" ? (
|
||||
<FeatherTrophy />
|
||||
) : art === "lead-qualifiziert" ? (
|
||||
<FeatherUserCheck />
|
||||
) : art === "neue-anfrage" ? (
|
||||
<FeatherInbox />
|
||||
) : art === "kontakt-geaendert" ? (
|
||||
<FeatherUserCog />
|
||||
) : (
|
||||
<FeatherUserPlus />
|
||||
)
|
||||
}
|
||||
title={
|
||||
title ??
|
||||
(art === "pds-synchronisieren"
|
||||
? "PDS synchronisieren"
|
||||
: art === "http-anfrage"
|
||||
? "HTTP-Anfrage"
|
||||
: art === "filter"
|
||||
? "Filter"
|
||||
: art === "wert-setzen"
|
||||
? "Wert setzen"
|
||||
: art === "switch"
|
||||
? "Switch"
|
||||
: art === "und"
|
||||
? "Und"
|
||||
: art === "oder"
|
||||
? "Oder"
|
||||
: art === "schleife"
|
||||
? "Schleife"
|
||||
: art === "warten"
|
||||
? "Warten"
|
||||
: art === "wenn-dann"
|
||||
? "Wenn-Dann"
|
||||
: art === "aufgabe-zuweisen"
|
||||
? "Aufgabe zuweisen"
|
||||
: art === "aufgabe-anlegen"
|
||||
? "Aufgabe anlegen"
|
||||
: art === "benachrichtigung-senden"
|
||||
? "Benachrichtigung senden"
|
||||
: art === "notiz-anlegen"
|
||||
? "Notiz anlegen"
|
||||
: art === "kontakt-aendern"
|
||||
? "Kontakt ändern"
|
||||
: art === "webhook-empfangen"
|
||||
? "Webhook empfangen"
|
||||
: art === "zeitplan"
|
||||
? "Zeitplan"
|
||||
: art === "ereignis-erstellt"
|
||||
? "Ereignis erstellt"
|
||||
: art === "termin-storniert"
|
||||
? "Termin storniert"
|
||||
: art === "termin-gebucht"
|
||||
? "Termin gebucht"
|
||||
: art === "formular-abgeschickt"
|
||||
? "Formular abgeschickt"
|
||||
: art === "email-unzustellbar"
|
||||
? "E-Mail unzustellbar"
|
||||
: art === "email-eingegangen"
|
||||
? "E-Mail eingegangen"
|
||||
: art === "deadline-naht"
|
||||
? "Deadline naht"
|
||||
: art === "projekt-angelegt"
|
||||
? "Projekt angelegt"
|
||||
: art === "status-gewechselt"
|
||||
? "Status gewechselt"
|
||||
: art === "projekt-gewonnen"
|
||||
? "Projekt gewonnen"
|
||||
: art === "lead-qualifiziert"
|
||||
? "Lead qualifiziert"
|
||||
: art === "neue-anfrage"
|
||||
? "Neue Anfrage"
|
||||
: art === "kontakt-geaendert"
|
||||
? "Kontakt geändert"
|
||||
: "Neuer Kontakt")
|
||||
}
|
||||
beschreibung={
|
||||
beschreibung ??
|
||||
(art === "pds-synchronisieren"
|
||||
? "Daten mit PDS austauschen"
|
||||
: art === "http-anfrage"
|
||||
? "Externen Dienst aufrufen"
|
||||
: art === "filter"
|
||||
? "Datensätze filtern"
|
||||
: art === "wert-setzen"
|
||||
? "Variable setzen oder lesen"
|
||||
: art === "switch"
|
||||
? "Mehrere Pfade verzweigen"
|
||||
: art === "und"
|
||||
? "Wenn alle Bedingungen zutreffen"
|
||||
: art === "oder"
|
||||
? "Wenn mind. eine Bedingung zutrifft"
|
||||
: art === "schleife"
|
||||
? "Für jedes Element wiederholen"
|
||||
: art === "warten"
|
||||
? "Pausiert, z. B. 3 Tage"
|
||||
: art === "wenn-dann"
|
||||
? "Verzweigt nach Bedingung"
|
||||
: art === "aufgabe-zuweisen"
|
||||
? "Person oder Team zuordnen"
|
||||
: art === "aufgabe-anlegen"
|
||||
? "Neue Aufgabe erstellen"
|
||||
: art === "benachrichtigung-senden"
|
||||
? "E-Mail, Push oder intern"
|
||||
: art === "notiz-anlegen"
|
||||
? "Notiz am Datensatz speichern"
|
||||
: art === "kontakt-aendern"
|
||||
? "Datensatz aktualisieren"
|
||||
: art === "webhook-empfangen"
|
||||
? "Wenn ein externes System ruft"
|
||||
: art === "zeitplan"
|
||||
? "Zu festen Zeiten, z. B. montags 8 Uhr"
|
||||
: art === "ereignis-erstellt"
|
||||
? "Wenn ein Kalender-Eintrag entsteht"
|
||||
: art === "termin-storniert"
|
||||
? "Wenn ein Termin abgesagt wird"
|
||||
: art === "termin-gebucht"
|
||||
? "Wenn ein Termin gebucht wird"
|
||||
: art === "formular-abgeschickt"
|
||||
? "Wenn ein Formular eingeht"
|
||||
: art === "email-unzustellbar"
|
||||
? "Wenn eine Kampagnen-Mail bounct"
|
||||
: art === "email-eingegangen"
|
||||
? "Wenn eine E-Mail ankommt"
|
||||
: art === "deadline-naht"
|
||||
? "Wenn eine Frist bald abläuft"
|
||||
: art === "projekt-angelegt"
|
||||
? "Wenn ein Projekt erstellt wird"
|
||||
: art === "status-gewechselt"
|
||||
? "Wenn eine Karte die Spalte wechselt"
|
||||
: art === "projekt-gewonnen"
|
||||
? "Wenn ein Projekt gewonnen wird"
|
||||
: art === "lead-qualifiziert"
|
||||
? "Wenn ein Lead qualifiziert wird"
|
||||
: art === "neue-anfrage"
|
||||
? "Wenn eine Anfrage eingeht"
|
||||
: art === "kontakt-geaendert"
|
||||
? "Wenn sich Kontaktdaten ändern"
|
||||
: "Wenn ein Kontakt angelegt wird")
|
||||
}
|
||||
inaktiv={false}
|
||||
portLabel1={
|
||||
art === "und"
|
||||
? "Ja"
|
||||
: art === "oder"
|
||||
? "Ja"
|
||||
: art === "schleife"
|
||||
? "Weiter"
|
||||
: art === "wenn-dann"
|
||||
? "Ja"
|
||||
: "1"
|
||||
}
|
||||
portLabel2={
|
||||
art === "und"
|
||||
? "Nein"
|
||||
: art === "oder"
|
||||
? "Nein"
|
||||
: art === "schleife"
|
||||
? "Fertig"
|
||||
: art === "wenn-dann"
|
||||
? "Nein"
|
||||
: "2"
|
||||
}
|
||||
onInputPortClick={onInputPortClick}
|
||||
onOutputPortClick={onOutputPortClick}
|
||||
onAddAfter={onAddAfter}
|
||||
connectedOutputPorts={connectedOutputPorts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const NodeWorkflowEngine = Object.assign(NodeWorkflowEngineRoot, {
|
||||
Katalog,
|
||||
NodeItem,
|
||||
Port,
|
||||
StatusBadge,
|
||||
});
|
||||
Reference in new issue
Block a user