@echothink-ui/workflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/components/ApprovalWorkflowEditor.d.ts +2 -0
- package/dist/components/ConditionBuilder.d.ts +2 -0
- package/dist/components/PipelineStage.d.ts +2 -0
- package/dist/components/ProcessDesigner.d.ts +2 -0
- package/dist/components/ProcessTimeline.d.ts +2 -0
- package/dist/components/RuleBuilder.d.ts +2 -0
- package/dist/components/RuleSimulationPanel.d.ts +2 -0
- package/dist/components/WorkflowHandoffPanel.d.ts +2 -0
- package/dist/components/WorkflowPipelineView.d.ts +2 -0
- package/dist/components/types.d.ts +112 -0
- package/dist/index.cjs +499 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +268 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +453 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/src/components/ApprovalWorkflowEditor.tsx +34 -0
- package/src/components/ConditionBuilder.tsx +41 -0
- package/src/components/PipelineStage.tsx +33 -0
- package/src/components/ProcessDesigner.tsx +125 -0
- package/src/components/ProcessTimeline.tsx +23 -0
- package/src/components/RuleBuilder.tsx +66 -0
- package/src/components/RuleSimulationPanel.tsx +47 -0
- package/src/components/WorkflowHandoffPanel.tsx +41 -0
- package/src/components/WorkflowPipelineView.tsx +34 -0
- package/src/components/types.ts +107 -0
- package/src/css.d.ts +1 -0
- package/src/index.tsx +42 -0
- package/src/styles.css +312 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { StatusDot } from "@echothink-ui/core";
|
|
3
|
+
import type { PipelineStageProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function PipelineStage({ stage, active, onSelect, className, ...props }: PipelineStageProps) {
|
|
6
|
+
const content = (
|
|
7
|
+
<>
|
|
8
|
+
<StatusDot status={stage.status} label={stage.status} />
|
|
9
|
+
<strong>{stage.label}</strong>
|
|
10
|
+
{stage.durationMs !== undefined ? <small>{stage.durationMs}ms</small> : null}
|
|
11
|
+
</>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return onSelect ? (
|
|
15
|
+
<button
|
|
16
|
+
{...props}
|
|
17
|
+
type="button"
|
|
18
|
+
className={`eth-workflow-pipeline-stage ${active ? "eth-workflow-pipeline-stage--active" : ""} ${className ?? ""}`}
|
|
19
|
+
data-eth-component="PipelineStage"
|
|
20
|
+
onClick={() => onSelect(stage.id)}
|
|
21
|
+
>
|
|
22
|
+
{content}
|
|
23
|
+
</button>
|
|
24
|
+
) : (
|
|
25
|
+
<article
|
|
26
|
+
{...props}
|
|
27
|
+
className={`eth-workflow-pipeline-stage ${active ? "eth-workflow-pipeline-stage--active" : ""} ${className ?? ""}`}
|
|
28
|
+
data-eth-component="PipelineStage"
|
|
29
|
+
>
|
|
30
|
+
{content}
|
|
31
|
+
</article>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "@echothink-ui/core";
|
|
3
|
+
import type { ProcessDesignerProps, ProcessNode } from "./types";
|
|
4
|
+
|
|
5
|
+
export function ProcessDesigner({
|
|
6
|
+
nodes,
|
|
7
|
+
edges,
|
|
8
|
+
onNodeMove,
|
|
9
|
+
onAddNode,
|
|
10
|
+
onConnect,
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}: ProcessDesignerProps) {
|
|
14
|
+
const svgRef = React.useRef<SVGSVGElement>(null);
|
|
15
|
+
const [positions, setPositions] = React.useState(() => initialPositions(nodes));
|
|
16
|
+
const [draggingId, setDraggingId] = React.useState<string | null>(null);
|
|
17
|
+
const [selectedNodeId, setSelectedNodeId] = React.useState<string | null>(null);
|
|
18
|
+
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
setPositions(initialPositions(nodes));
|
|
21
|
+
}, [nodes]);
|
|
22
|
+
|
|
23
|
+
const pointFromEvent = (event: React.MouseEvent<SVGSVGElement>) => {
|
|
24
|
+
const rect = svgRef.current?.getBoundingClientRect();
|
|
25
|
+
if (!rect) return { x: 0, y: 0 };
|
|
26
|
+
return {
|
|
27
|
+
x: ((event.clientX - rect.left) / rect.width) * 900,
|
|
28
|
+
y: ((event.clientY - rect.top) / rect.height) * 420
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const move = (event: React.MouseEvent<SVGSVGElement>) => {
|
|
33
|
+
if (!draggingId) return;
|
|
34
|
+
const point = pointFromEvent(event);
|
|
35
|
+
setPositions((current) => ({ ...current, [draggingId]: point }));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const stop = () => {
|
|
39
|
+
if (!draggingId) return;
|
|
40
|
+
const point = positions[draggingId];
|
|
41
|
+
if (point) onNodeMove?.(draggingId, point.x, point.y);
|
|
42
|
+
setDraggingId(null);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const clickNode = (id: string) => {
|
|
46
|
+
if (selectedNodeId && selectedNodeId !== id) {
|
|
47
|
+
onConnect?.(selectedNodeId, id);
|
|
48
|
+
setSelectedNodeId(null);
|
|
49
|
+
} else {
|
|
50
|
+
setSelectedNodeId(id);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<section
|
|
56
|
+
{...props}
|
|
57
|
+
className={`eth-workflow-process-designer ${className ?? ""}`}
|
|
58
|
+
data-eth-component="ProcessDesigner"
|
|
59
|
+
>
|
|
60
|
+
<header>
|
|
61
|
+
<h3>Process designer</h3>
|
|
62
|
+
{onAddNode ? <Button intent="secondary" density="compact" onClick={onAddNode}>Add node</Button> : null}
|
|
63
|
+
</header>
|
|
64
|
+
<svg
|
|
65
|
+
ref={svgRef}
|
|
66
|
+
viewBox="0 0 900 420"
|
|
67
|
+
role="img"
|
|
68
|
+
aria-label="Process graph"
|
|
69
|
+
onMouseMove={move}
|
|
70
|
+
onMouseUp={stop}
|
|
71
|
+
onMouseLeave={stop}
|
|
72
|
+
>
|
|
73
|
+
{edges.map((edge, index) => {
|
|
74
|
+
const from = positions[edge.from];
|
|
75
|
+
const to = positions[edge.to];
|
|
76
|
+
if (!from || !to) return null;
|
|
77
|
+
const midX = (from.x + to.x) / 2;
|
|
78
|
+
const midY = (from.y + to.y) / 2;
|
|
79
|
+
return (
|
|
80
|
+
<g key={`${edge.from}-${edge.to}-${index}`}>
|
|
81
|
+
<line x1={from.x} y1={from.y} x2={to.x} y2={to.y} stroke="var(--eth-color-border-strong)" strokeWidth={2} markerEnd="url(#eth-workflow-arrow)" />
|
|
82
|
+
{edge.label ? <text x={midX} y={midY - 6} textAnchor="middle" fontSize={12}>{edge.label}</text> : null}
|
|
83
|
+
</g>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
86
|
+
<defs>
|
|
87
|
+
<marker id="eth-workflow-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
88
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--eth-color-border-strong)" />
|
|
89
|
+
</marker>
|
|
90
|
+
</defs>
|
|
91
|
+
{nodes.map((node) => {
|
|
92
|
+
const point = positions[node.id];
|
|
93
|
+
if (!point) return null;
|
|
94
|
+
return (
|
|
95
|
+
<g
|
|
96
|
+
key={node.id}
|
|
97
|
+
transform={`translate(${point.x}, ${point.y})`}
|
|
98
|
+
className={`eth-workflow-process-designer__node eth-workflow-process-designer__node--${node.type} ${selectedNodeId === node.id ? "eth-workflow-process-designer__node--selected" : ""}`}
|
|
99
|
+
onMouseDown={(event) => { event.preventDefault(); setDraggingId(node.id); }}
|
|
100
|
+
onClick={() => clickNode(node.id)}
|
|
101
|
+
>
|
|
102
|
+
{node.type === "decision" ? (
|
|
103
|
+
<polygon points="0,-32 54,0 0,32 -54,0" fill="var(--eth-color-layer-01)" stroke="var(--eth-color-border-strong)" strokeWidth={2} />
|
|
104
|
+
) : (
|
|
105
|
+
<rect x={-62} y={-28} width={124} height={56} rx={0} fill="var(--eth-color-layer-01)" stroke="var(--eth-color-border-strong)" strokeWidth={2} />
|
|
106
|
+
)}
|
|
107
|
+
<text textAnchor="middle" dominantBaseline="middle" fontSize={13}>{node.label}</text>
|
|
108
|
+
</g>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</svg>
|
|
112
|
+
</section>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function initialPositions(nodes: ProcessNode[]) {
|
|
117
|
+
const positions: Record<string, { x: number; y: number }> = {};
|
|
118
|
+
nodes.forEach((node, index) => {
|
|
119
|
+
positions[node.id] = {
|
|
120
|
+
x: node.x ?? 100 + (index % 5) * 170,
|
|
121
|
+
y: node.y ?? 90 + Math.floor(index / 5) * 120
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
return positions;
|
|
125
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { StatusDot } from "@echothink-ui/core";
|
|
3
|
+
import type { ProcessTimelineProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function ProcessTimeline({ events = [], className, ...props }: ProcessTimelineProps) {
|
|
6
|
+
return (
|
|
7
|
+
<section
|
|
8
|
+
{...props}
|
|
9
|
+
className={`eth-workflow-process-timeline ${className ?? ""}`}
|
|
10
|
+
data-eth-component="ProcessTimeline"
|
|
11
|
+
>
|
|
12
|
+
<ol>
|
|
13
|
+
{events.map((event) => (
|
|
14
|
+
<li key={event.id}>
|
|
15
|
+
{event.status ? <StatusDot status={event.status} /> : null}
|
|
16
|
+
<span>{event.label}</span>
|
|
17
|
+
{event.timestamp ? <time dateTime={event.timestamp}>{event.timestamp}</time> : null}
|
|
18
|
+
</li>
|
|
19
|
+
))}
|
|
20
|
+
</ol>
|
|
21
|
+
</section>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button, FormField, Select } from "@echothink-ui/core";
|
|
3
|
+
import { ConditionBuilder } from "./ConditionBuilder";
|
|
4
|
+
import type { RuleBuilderProps, WorkflowRule } from "./types";
|
|
5
|
+
|
|
6
|
+
const defaultRule: WorkflowRule = {
|
|
7
|
+
combinator: "all",
|
|
8
|
+
conditions: [{ field: "", operator: "equals", value: "" }]
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function RuleBuilder({ value, onChange, variables = [], className, ...props }: RuleBuilderProps) {
|
|
12
|
+
const [internalRule, setInternalRule] = React.useState<WorkflowRule>(value ?? defaultRule);
|
|
13
|
+
const rule = value ?? internalRule;
|
|
14
|
+
const commit = (nextRule: WorkflowRule) => {
|
|
15
|
+
setInternalRule(nextRule);
|
|
16
|
+
onChange?.(nextRule);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<section
|
|
21
|
+
{...props}
|
|
22
|
+
className={`eth-workflow-rule-builder ${className ?? ""}`}
|
|
23
|
+
data-eth-component="RuleBuilder"
|
|
24
|
+
>
|
|
25
|
+
<FormField label="Match">
|
|
26
|
+
<Select
|
|
27
|
+
value={rule.combinator}
|
|
28
|
+
options={[
|
|
29
|
+
{ value: "all", label: "All conditions" },
|
|
30
|
+
{ value: "any", label: "Any condition" }
|
|
31
|
+
]}
|
|
32
|
+
onChange={(event) => commit({ ...rule, combinator: event.currentTarget.value as WorkflowRule["combinator"] })}
|
|
33
|
+
/>
|
|
34
|
+
</FormField>
|
|
35
|
+
<div className="eth-workflow-rule-builder__conditions">
|
|
36
|
+
{rule.conditions.map((condition, index) => (
|
|
37
|
+
<div key={index} className="eth-workflow-rule-builder__condition">
|
|
38
|
+
<ConditionBuilder
|
|
39
|
+
value={condition}
|
|
40
|
+
variables={variables}
|
|
41
|
+
onChange={(nextCondition) => {
|
|
42
|
+
const conditions = [...rule.conditions];
|
|
43
|
+
conditions[index] = nextCondition;
|
|
44
|
+
commit({ ...rule, conditions });
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
<Button
|
|
48
|
+
intent="ghost"
|
|
49
|
+
density="compact"
|
|
50
|
+
onClick={() => commit({ ...rule, conditions: rule.conditions.filter((_, itemIndex) => itemIndex !== index) })}
|
|
51
|
+
>
|
|
52
|
+
Remove
|
|
53
|
+
</Button>
|
|
54
|
+
</div>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
<Button
|
|
58
|
+
intent="secondary"
|
|
59
|
+
density="compact"
|
|
60
|
+
onClick={() => commit({ ...rule, conditions: [...rule.conditions, { field: variables[0] ?? "", operator: "equals", value: "" }] })}
|
|
61
|
+
>
|
|
62
|
+
Add condition
|
|
63
|
+
</Button>
|
|
64
|
+
</section>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "@echothink-ui/core";
|
|
3
|
+
import { DataTable, type DataColumn } from "@echothink-ui/data";
|
|
4
|
+
import type { RuleSimulationPanelProps, RuleSimulationSample } from "./types";
|
|
5
|
+
|
|
6
|
+
type SimulationRow = RuleSimulationSample & {
|
|
7
|
+
actualOutcome?: unknown;
|
|
8
|
+
matched?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function RuleSimulationPanel({
|
|
12
|
+
rule,
|
|
13
|
+
sampleInputs,
|
|
14
|
+
results = [],
|
|
15
|
+
onRun,
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: RuleSimulationPanelProps) {
|
|
19
|
+
const resultMap = new Map(results.map((result) => [result.sampleId, result]));
|
|
20
|
+
const rows: SimulationRow[] = sampleInputs.map((sample) => ({
|
|
21
|
+
...sample,
|
|
22
|
+
actualOutcome: resultMap.get(sample.id)?.actualOutcome,
|
|
23
|
+
matched: resultMap.get(sample.id)?.matched
|
|
24
|
+
}));
|
|
25
|
+
const columns: DataColumn<SimulationRow>[] = [
|
|
26
|
+
{ key: "label", header: "Sample" },
|
|
27
|
+
{ key: "data", header: "Input", render: (row) => <code>{JSON.stringify(row.data)}</code> },
|
|
28
|
+
{ key: "expectedOutcome", header: "Expected", render: (row) => <code>{JSON.stringify(row.expectedOutcome)}</code> },
|
|
29
|
+
{ key: "actualOutcome", header: "Actual", render: (row) => <code>{JSON.stringify(row.actualOutcome)}</code> },
|
|
30
|
+
{ key: "matched", header: "Matched", render: (row) => (row.matched ? "Yes" : row.matched === false ? "No" : "Not run") }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<section
|
|
35
|
+
{...props}
|
|
36
|
+
className={`eth-workflow-rule-simulation ${className ?? ""}`}
|
|
37
|
+
data-eth-component="RuleSimulationPanel"
|
|
38
|
+
>
|
|
39
|
+
<header>
|
|
40
|
+
<h3>Rule simulation</h3>
|
|
41
|
+
{onRun ? <Button onClick={onRun}>Run</Button> : null}
|
|
42
|
+
</header>
|
|
43
|
+
<pre className="eth-workflow-rule-simulation__rule"><code>{JSON.stringify(rule, null, 2)}</code></pre>
|
|
44
|
+
<DataTable rows={rows} columns={columns} rowKey="id" />
|
|
45
|
+
</section>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button, FormField, Select, StatusDot, Textarea } from "@echothink-ui/core";
|
|
3
|
+
import type { WorkflowHandoffPanelProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function WorkflowHandoffPanel({
|
|
6
|
+
item,
|
|
7
|
+
assigneeOptions = [],
|
|
8
|
+
onSubmit,
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: WorkflowHandoffPanelProps) {
|
|
12
|
+
const [assigneeId, setAssigneeId] = React.useState(assigneeOptions[0]?.id ?? "");
|
|
13
|
+
const [reason, setReason] = React.useState("");
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<section
|
|
17
|
+
{...props}
|
|
18
|
+
className={`eth-workflow-handoff-panel ${className ?? ""}`}
|
|
19
|
+
data-eth-component="WorkflowHandoffPanel"
|
|
20
|
+
>
|
|
21
|
+
<header>
|
|
22
|
+
<h3>Workflow handoff</h3>
|
|
23
|
+
{item?.status ? <StatusDot status={item.status} label={item.status} /> : null}
|
|
24
|
+
</header>
|
|
25
|
+
{item ? <p>{item.label}</p> : null}
|
|
26
|
+
<form onSubmit={(event) => { event.preventDefault(); onSubmit?.({ assigneeId, reason }); }}>
|
|
27
|
+
<FormField label="Assignee">
|
|
28
|
+
<Select
|
|
29
|
+
value={assigneeId}
|
|
30
|
+
options={assigneeOptions.map((option) => ({ value: option.id, label: option.kind ? `${option.label} (${option.kind})` : option.label }))}
|
|
31
|
+
onChange={(event) => setAssigneeId(event.currentTarget.value)}
|
|
32
|
+
/>
|
|
33
|
+
</FormField>
|
|
34
|
+
<FormField label="Reason">
|
|
35
|
+
<Textarea value={reason} rows={4} onChange={(event) => setReason(event.currentTarget.value)} />
|
|
36
|
+
</FormField>
|
|
37
|
+
<Button type="submit" disabled={!assigneeId}>Handoff</Button>
|
|
38
|
+
</form>
|
|
39
|
+
</section>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { PipelineStage } from "./PipelineStage";
|
|
3
|
+
import type { WorkflowPipelineViewProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function WorkflowPipelineView({
|
|
6
|
+
stages,
|
|
7
|
+
activeStageId,
|
|
8
|
+
onSelect,
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: WorkflowPipelineViewProps) {
|
|
12
|
+
return (
|
|
13
|
+
<section
|
|
14
|
+
{...props}
|
|
15
|
+
className={`eth-workflow-pipeline-view ${className ?? ""}`}
|
|
16
|
+
data-eth-component="WorkflowPipelineView"
|
|
17
|
+
aria-label="Workflow pipeline"
|
|
18
|
+
>
|
|
19
|
+
<ol className="eth-workflow-pipeline-view__stages">
|
|
20
|
+
{stages.map((stage, index) => (
|
|
21
|
+
<li key={stage.id}>
|
|
22
|
+
<PipelineStage
|
|
23
|
+
stage={stage}
|
|
24
|
+
active={stage.id === activeStageId}
|
|
25
|
+
onSelect={onSelect}
|
|
26
|
+
aria-posinset={index + 1}
|
|
27
|
+
aria-setsize={stages.length}
|
|
28
|
+
/>
|
|
29
|
+
</li>
|
|
30
|
+
))}
|
|
31
|
+
</ol>
|
|
32
|
+
</section>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { EthOperationalStatus } from "@echothink-ui/core";
|
|
3
|
+
|
|
4
|
+
export interface WorkflowStage extends Record<string, unknown> {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
status: EthOperationalStatus;
|
|
8
|
+
startedAt?: string;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WorkflowPipelineViewProps
|
|
13
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> {
|
|
14
|
+
stages: WorkflowStage[];
|
|
15
|
+
activeStageId?: string;
|
|
16
|
+
onSelect?: (id: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PipelineStageProps extends Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> {
|
|
20
|
+
stage: WorkflowStage;
|
|
21
|
+
active?: boolean;
|
|
22
|
+
onSelect?: (id: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProcessNode extends Record<string, unknown> {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
type: "task" | "decision" | "start" | "end";
|
|
29
|
+
x?: number;
|
|
30
|
+
y?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ProcessEdge extends Record<string, unknown> {
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ProcessDesignerProps
|
|
40
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "onSelect"> {
|
|
41
|
+
nodes: ProcessNode[];
|
|
42
|
+
edges: ProcessEdge[];
|
|
43
|
+
onNodeMove?: (id: string, x: number, y: number) => void;
|
|
44
|
+
onAddNode?: () => void;
|
|
45
|
+
onConnect?: (from: string, to: string) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface WorkflowRuleCondition extends Record<string, unknown> {
|
|
49
|
+
field: string;
|
|
50
|
+
operator: "equals" | "not-equals" | "contains" | "gt" | "gte" | "lt" | "lte" | "exists";
|
|
51
|
+
value?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WorkflowRule extends Record<string, unknown> {
|
|
55
|
+
combinator: "all" | "any";
|
|
56
|
+
conditions: WorkflowRuleCondition[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RuleBuilderProps
|
|
60
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "onChange"> {
|
|
61
|
+
value?: WorkflowRule;
|
|
62
|
+
onChange?: (rule: WorkflowRule) => void;
|
|
63
|
+
variables?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ConditionBuilderProps
|
|
67
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "onChange"> {
|
|
68
|
+
value: WorkflowRuleCondition;
|
|
69
|
+
onChange: (condition: WorkflowRuleCondition) => void;
|
|
70
|
+
variables?: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RuleSimulationSample extends Record<string, unknown> {
|
|
74
|
+
id: string;
|
|
75
|
+
label: string;
|
|
76
|
+
data: unknown;
|
|
77
|
+
expectedOutcome?: unknown;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RuleSimulationResult extends Record<string, unknown> {
|
|
81
|
+
sampleId: string;
|
|
82
|
+
actualOutcome: unknown;
|
|
83
|
+
matched: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface RuleSimulationPanelProps extends Omit<React.HTMLAttributes<HTMLElement>, "results"> {
|
|
87
|
+
rule: unknown;
|
|
88
|
+
sampleInputs: RuleSimulationSample[];
|
|
89
|
+
results?: RuleSimulationResult[];
|
|
90
|
+
onRun?: () => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface WorkflowHandoffPanelProps extends Omit<React.HTMLAttributes<HTMLElement>, "onSubmit"> {
|
|
94
|
+
item?: { id: string; label: React.ReactNode; status?: EthOperationalStatus };
|
|
95
|
+
assigneeOptions?: Array<{ id: string; label: string; kind?: "user" | "team" | "agent" }>;
|
|
96
|
+
onSubmit?: (handoff: { assigneeId: string; reason: string }) => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ApprovalWorkflowEditorProps
|
|
100
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "onChange"> {
|
|
101
|
+
steps?: Array<{ id: string; label: string; approver?: string; required?: boolean }>;
|
|
102
|
+
onChange?: (steps: Array<{ id: string; label: string; approver?: string; required?: boolean }>) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ProcessTimelineProps extends React.HTMLAttributes<HTMLElement> {
|
|
106
|
+
events?: Array<{ id: string; label: React.ReactNode; timestamp?: string; status?: EthOperationalStatus }>;
|
|
107
|
+
}
|
package/src/css.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import "./styles.css";
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
ApprovalWorkflowEditorProps,
|
|
5
|
+
ConditionBuilderProps,
|
|
6
|
+
PipelineStageProps,
|
|
7
|
+
ProcessDesignerProps,
|
|
8
|
+
ProcessEdge,
|
|
9
|
+
ProcessNode,
|
|
10
|
+
ProcessTimelineProps,
|
|
11
|
+
RuleBuilderProps,
|
|
12
|
+
RuleSimulationPanelProps,
|
|
13
|
+
RuleSimulationResult,
|
|
14
|
+
RuleSimulationSample,
|
|
15
|
+
WorkflowHandoffPanelProps,
|
|
16
|
+
WorkflowPipelineViewProps,
|
|
17
|
+
WorkflowRule,
|
|
18
|
+
WorkflowRuleCondition,
|
|
19
|
+
WorkflowStage
|
|
20
|
+
} from "./components/types";
|
|
21
|
+
export { ApprovalWorkflowEditor } from "./components/ApprovalWorkflowEditor";
|
|
22
|
+
export { ConditionBuilder } from "./components/ConditionBuilder";
|
|
23
|
+
export { PipelineStage } from "./components/PipelineStage";
|
|
24
|
+
export { ProcessDesigner } from "./components/ProcessDesigner";
|
|
25
|
+
export { ProcessTimeline } from "./components/ProcessTimeline";
|
|
26
|
+
export { RuleBuilder } from "./components/RuleBuilder";
|
|
27
|
+
export { RuleSimulationPanel } from "./components/RuleSimulationPanel";
|
|
28
|
+
export { WorkflowHandoffPanel } from "./components/WorkflowHandoffPanel";
|
|
29
|
+
export { WorkflowPipelineView } from "./components/WorkflowPipelineView";
|
|
30
|
+
|
|
31
|
+
export const WorkflowComponentNames = [
|
|
32
|
+
"WorkflowPipelineView",
|
|
33
|
+
"PipelineStage",
|
|
34
|
+
"ProcessDesigner",
|
|
35
|
+
"RuleBuilder",
|
|
36
|
+
"ConditionBuilder",
|
|
37
|
+
"RuleSimulationPanel",
|
|
38
|
+
"WorkflowHandoffPanel",
|
|
39
|
+
"ApprovalWorkflowEditor",
|
|
40
|
+
"ProcessTimeline"
|
|
41
|
+
] as const;
|
|
42
|
+
export type WorkflowComponentName = (typeof WorkflowComponentNames)[number];
|