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
902 lines
32 KiB
TypeScript
902 lines
32 KiB
TypeScript
// @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,
|
|
});
|