@inspirer-dev/crm-dashboard 1.0.66 → 1.0.68

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.
@@ -6,6 +6,7 @@ import ReactFlow, {
6
6
  useEdgesState,
7
7
  useReactFlow,
8
8
  useStoreApi,
9
+ useUpdateNodeInternals,
9
10
  addEdge,
10
11
  type Connection,
11
12
  type OnNodesChange,
@@ -69,6 +70,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
69
70
  const history = useFlowHistory();
70
71
  const { fitView, getViewport, setViewport, setCenter } = useReactFlow();
71
72
  const store = useStoreApi();
73
+ const updateNodeInternals = useUpdateNodeInternals();
72
74
 
73
75
  const validation = useMemo(() => {
74
76
  return validateFlow(nodes as FlowNode[], edges as FlowEdge[]);
@@ -298,14 +300,19 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
298
300
  const hasPositions = steps.length > 0 && steps[0].positionX != null;
299
301
  const finalNodes = hasPositions ? n : applyAutoLayout(n, e);
300
302
  setNodes(finalNodes);
301
- setEdges(e);
302
303
 
303
- setTimeout(() => {
304
- fitViewWithOffset();
305
- }, 50);
304
+ requestAnimationFrame(() => {
305
+ finalNodes.forEach((node) => updateNodeInternals(node.id));
306
+ requestAnimationFrame(() => {
307
+ setEdges(e);
308
+ setTimeout(() => {
309
+ fitViewWithOffset();
310
+ }, 50);
311
+ });
312
+ });
306
313
  }
307
314
  isInitialMount.current = false;
308
- }, [steps, setNodes, setEdges, fitViewWithOffset]);
315
+ }, [steps, setNodes, setEdges, fitViewWithOffset, updateNodeInternals]);
309
316
 
310
317
  const syncToParent = useCallback(() => {
311
318
  if (isInitialMount.current) return;
@@ -37,6 +37,7 @@ const EntryNode: React.FC<NodeProps<FlowNodeData>> = ({ selected }) => {
37
37
  style={{
38
38
  width: NODE_DIMENSIONS.width,
39
39
  height: NODE_DIMENSIONS.height,
40
+ boxSizing: 'border-box',
40
41
  background: colors.background,
41
42
  borderRadius: 12,
42
43
  border: getBorderStyle(),
@@ -26,15 +26,15 @@ export const stepsToFlow = (
26
26
  const nodes: FlowNode[] = [];
27
27
  const edges: FlowEdge[] = [];
28
28
 
29
- const firstStep = steps.length > 0
30
- ? [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0]
31
- : null;
32
- const hasPositions = firstStep != null && firstStep.positionX != null;
29
+ const entryStep = steps.find((s) => s.stepKey === ENTRY_NODE_ID);
30
+ const entryPosition = entryStep?.positionX != null && entryStep?.positionY != null
31
+ ? { x: entryStep.positionX, y: entryStep.positionY }
32
+ : { x: 250, y: 0 };
33
33
 
34
34
  const entryNode: FlowNode = {
35
35
  id: ENTRY_NODE_ID,
36
36
  type: 'entry',
37
- position: { x: 250, y: 0 },
37
+ position: entryPosition,
38
38
  data: {
39
39
  stepType: 'entry',
40
40
  name: 'Entry',
@@ -44,7 +44,13 @@ export const stepsToFlow = (
44
44
  };
45
45
  nodes.push(entryNode);
46
46
 
47
- const sortedSteps = [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
47
+ const nonEntrySteps = steps.filter((s) => s.stepKey !== ENTRY_NODE_ID);
48
+ const firstStep = nonEntrySteps.length > 0
49
+ ? [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0]
50
+ : null;
51
+ const hasPositions = firstStep != null && firstStep.positionX != null;
52
+
53
+ const sortedSteps = [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
48
54
 
49
55
  sortedSteps.forEach((step, index) => {
50
56
  const node: FlowNode = {
@@ -111,13 +117,27 @@ export const stepsToFlow = (
111
117
  };
112
118
 
113
119
  export const flowToSteps = (nodes: FlowNode[], edges: FlowEdge[]): FlowStep[] => {
120
+ const entryNode = nodes.find((n) => n.id === ENTRY_NODE_ID);
114
121
  const stepNodes = nodes.filter((n) => n.data.stepType !== 'entry');
115
122
  const nodePositions = new Map(nodes.map((n) => [n.id, n.position.y]));
116
123
  const sorted = [...stepNodes].sort(
117
124
  (a, b) => (nodePositions.get(a.id) ?? 0) - (nodePositions.get(b.id) ?? 0)
118
125
  );
119
126
 
120
- return sorted.map((node, index) => {
127
+ const steps: FlowStep[] = [];
128
+
129
+ if (entryNode) {
130
+ steps.push({
131
+ stepKey: ENTRY_NODE_ID,
132
+ name: 'Entry',
133
+ stepType: 'entry' as any,
134
+ order: -1,
135
+ positionX: Math.round(entryNode.position.x),
136
+ positionY: Math.round(entryNode.position.y),
137
+ });
138
+ }
139
+
140
+ sorted.forEach((node, index) => {
121
141
  const step: FlowStep = {
122
142
  stepKey: node.id,
123
143
  name: node.data.name,
@@ -139,8 +159,10 @@ export const flowToSteps = (nodes: FlowNode[], edges: FlowEdge[]): FlowStep[] =>
139
159
  step.nextStep = outEdges[0]?.target;
140
160
  }
141
161
 
142
- return step;
162
+ steps.push(step);
143
163
  });
164
+
165
+ return steps;
144
166
  };
145
167
 
146
168
  export const applyAutoLayout = (nodes: FlowNode[], edges: FlowEdge[]): FlowNode[] => {
@@ -151,12 +151,12 @@ const parseValue = (value) => {
151
151
  const stepsToFlow = (steps) => {
152
152
  const nodes = [];
153
153
  const edges = [];
154
- const firstStep = steps.length > 0 ? [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0] : null;
155
- const hasPositions = firstStep != null && firstStep.positionX != null;
154
+ const entryStep = steps.find((s) => s.stepKey === ENTRY_NODE_ID);
155
+ const entryPosition = entryStep?.positionX != null && entryStep?.positionY != null ? { x: entryStep.positionX, y: entryStep.positionY } : { x: 250, y: 0 };
156
156
  const entryNode = {
157
157
  id: ENTRY_NODE_ID,
158
158
  type: "entry",
159
- position: { x: 250, y: 0 },
159
+ position: entryPosition,
160
160
  data: {
161
161
  stepType: "entry",
162
162
  name: "Entry",
@@ -165,7 +165,10 @@ const stepsToFlow = (steps) => {
165
165
  deletable: false
166
166
  };
167
167
  nodes.push(entryNode);
168
- const sortedSteps = [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
168
+ const nonEntrySteps = steps.filter((s) => s.stepKey !== ENTRY_NODE_ID);
169
+ const firstStep = nonEntrySteps.length > 0 ? [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0] : null;
170
+ const hasPositions = firstStep != null && firstStep.positionX != null;
171
+ const sortedSteps = [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
169
172
  sortedSteps.forEach((step, index) => {
170
173
  const node = {
171
174
  id: step.stepKey,
@@ -225,12 +228,24 @@ const stepsToFlow = (steps) => {
225
228
  return { nodes, edges };
226
229
  };
227
230
  const flowToSteps = (nodes, edges) => {
231
+ const entryNode = nodes.find((n) => n.id === ENTRY_NODE_ID);
228
232
  const stepNodes = nodes.filter((n) => n.data.stepType !== "entry");
229
233
  const nodePositions = new Map(nodes.map((n) => [n.id, n.position.y]));
230
234
  const sorted = [...stepNodes].sort(
231
235
  (a, b) => (nodePositions.get(a.id) ?? 0) - (nodePositions.get(b.id) ?? 0)
232
236
  );
233
- return sorted.map((node, index) => {
237
+ const steps = [];
238
+ if (entryNode) {
239
+ steps.push({
240
+ stepKey: ENTRY_NODE_ID,
241
+ name: "Entry",
242
+ stepType: "entry",
243
+ order: -1,
244
+ positionX: Math.round(entryNode.position.x),
245
+ positionY: Math.round(entryNode.position.y)
246
+ });
247
+ }
248
+ sorted.forEach((node, index) => {
234
249
  const step = {
235
250
  stepKey: node.id,
236
251
  name: node.data.name,
@@ -249,8 +264,9 @@ const flowToSteps = (nodes, edges) => {
249
264
  } else {
250
265
  step.nextStep = outEdges[0]?.target;
251
266
  }
252
- return step;
267
+ steps.push(step);
253
268
  });
269
+ return steps;
254
270
  };
255
271
  const applyAutoLayout = (nodes, edges) => {
256
272
  const g = new dagre__default.default.graphlib.Graph();
@@ -364,6 +380,7 @@ const EntryNode = ({ selected }) => {
364
380
  style: {
365
381
  width: NODE_DIMENSIONS.width,
366
382
  height: NODE_DIMENSIONS.height,
383
+ boxSizing: "border-box",
367
384
  background: colors.background,
368
385
  borderRadius: 12,
369
386
  border: getBorderStyle(),
@@ -3456,6 +3473,7 @@ const FlowCanvas = ({
3456
3473
  const history = useFlowHistory();
3457
3474
  const { fitView, getViewport, setViewport, setCenter } = ReactFlow.useReactFlow();
3458
3475
  const store = ReactFlow.useStoreApi();
3476
+ const updateNodeInternals = ReactFlow.useUpdateNodeInternals();
3459
3477
  const validation = React.useMemo(() => {
3460
3478
  return validateFlow(nodes, edges);
3461
3479
  }, [nodes, edges]);
@@ -3625,13 +3643,18 @@ const FlowCanvas = ({
3625
3643
  const hasPositions = steps.length > 0 && steps[0].positionX != null;
3626
3644
  const finalNodes = hasPositions ? n : applyAutoLayout(n, e);
3627
3645
  setNodes(finalNodes);
3628
- setEdges(e);
3629
- setTimeout(() => {
3630
- fitViewWithOffset();
3631
- }, 50);
3646
+ requestAnimationFrame(() => {
3647
+ finalNodes.forEach((node) => updateNodeInternals(node.id));
3648
+ requestAnimationFrame(() => {
3649
+ setEdges(e);
3650
+ setTimeout(() => {
3651
+ fitViewWithOffset();
3652
+ }, 50);
3653
+ });
3654
+ });
3632
3655
  }
3633
3656
  isInitialMount.current = false;
3634
- }, [steps, setNodes, setEdges, fitViewWithOffset]);
3657
+ }, [steps, setNodes, setEdges, fitViewWithOffset, updateNodeInternals]);
3635
3658
  const syncToParent = React.useCallback(() => {
3636
3659
  if (isInitialMount.current) return;
3637
3660
  if (syncTimeoutRef.current) {
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { createContext, useContext, memo, useState, useEffect, useCallback, useRef, useMemo, forwardRef } from "react";
3
- import ReactFlow, { Handle, Position, useReactFlow, getSimpleBezierPath, BaseEdge, EdgeLabelRenderer, useNodesState, useEdgesState, useStoreApi, addEdge, Background, MiniMap, ReactFlowProvider } from "reactflow";
3
+ import ReactFlow, { Handle, Position, useReactFlow, getSimpleBezierPath, BaseEdge, EdgeLabelRenderer, useNodesState, useEdgesState, useStoreApi, useUpdateNodeInternals, addEdge, Background, MiniMap, ReactFlowProvider } from "reactflow";
4
4
  import "reactflow/dist/style.css";
5
5
  import { Flex, Box, Typography, SingleSelect, SingleSelectOption, TextInput, NumberInput, Loader, Tooltip, Field, Badge as Badge$1, Modal } from "@strapi/design-system";
6
6
  import { Plus, Trash, Cross, Layout, Pencil } from "@strapi/icons";
@@ -146,12 +146,12 @@ const parseValue = (value) => {
146
146
  const stepsToFlow = (steps) => {
147
147
  const nodes = [];
148
148
  const edges = [];
149
- const firstStep = steps.length > 0 ? [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0] : null;
150
- const hasPositions = firstStep != null && firstStep.positionX != null;
149
+ const entryStep = steps.find((s) => s.stepKey === ENTRY_NODE_ID);
150
+ const entryPosition = entryStep?.positionX != null && entryStep?.positionY != null ? { x: entryStep.positionX, y: entryStep.positionY } : { x: 250, y: 0 };
151
151
  const entryNode = {
152
152
  id: ENTRY_NODE_ID,
153
153
  type: "entry",
154
- position: { x: 250, y: 0 },
154
+ position: entryPosition,
155
155
  data: {
156
156
  stepType: "entry",
157
157
  name: "Entry",
@@ -160,7 +160,10 @@ const stepsToFlow = (steps) => {
160
160
  deletable: false
161
161
  };
162
162
  nodes.push(entryNode);
163
- const sortedSteps = [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
163
+ const nonEntrySteps = steps.filter((s) => s.stepKey !== ENTRY_NODE_ID);
164
+ const firstStep = nonEntrySteps.length > 0 ? [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0] : null;
165
+ const hasPositions = firstStep != null && firstStep.positionX != null;
166
+ const sortedSteps = [...nonEntrySteps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
164
167
  sortedSteps.forEach((step, index) => {
165
168
  const node = {
166
169
  id: step.stepKey,
@@ -220,12 +223,24 @@ const stepsToFlow = (steps) => {
220
223
  return { nodes, edges };
221
224
  };
222
225
  const flowToSteps = (nodes, edges) => {
226
+ const entryNode = nodes.find((n) => n.id === ENTRY_NODE_ID);
223
227
  const stepNodes = nodes.filter((n) => n.data.stepType !== "entry");
224
228
  const nodePositions = new Map(nodes.map((n) => [n.id, n.position.y]));
225
229
  const sorted = [...stepNodes].sort(
226
230
  (a, b) => (nodePositions.get(a.id) ?? 0) - (nodePositions.get(b.id) ?? 0)
227
231
  );
228
- return sorted.map((node, index) => {
232
+ const steps = [];
233
+ if (entryNode) {
234
+ steps.push({
235
+ stepKey: ENTRY_NODE_ID,
236
+ name: "Entry",
237
+ stepType: "entry",
238
+ order: -1,
239
+ positionX: Math.round(entryNode.position.x),
240
+ positionY: Math.round(entryNode.position.y)
241
+ });
242
+ }
243
+ sorted.forEach((node, index) => {
229
244
  const step = {
230
245
  stepKey: node.id,
231
246
  name: node.data.name,
@@ -244,8 +259,9 @@ const flowToSteps = (nodes, edges) => {
244
259
  } else {
245
260
  step.nextStep = outEdges[0]?.target;
246
261
  }
247
- return step;
262
+ steps.push(step);
248
263
  });
264
+ return steps;
249
265
  };
250
266
  const applyAutoLayout = (nodes, edges) => {
251
267
  const g = new dagre.graphlib.Graph();
@@ -359,6 +375,7 @@ const EntryNode = ({ selected }) => {
359
375
  style: {
360
376
  width: NODE_DIMENSIONS.width,
361
377
  height: NODE_DIMENSIONS.height,
378
+ boxSizing: "border-box",
362
379
  background: colors.background,
363
380
  borderRadius: 12,
364
381
  border: getBorderStyle(),
@@ -3451,6 +3468,7 @@ const FlowCanvas = ({
3451
3468
  const history = useFlowHistory();
3452
3469
  const { fitView, getViewport, setViewport, setCenter } = useReactFlow();
3453
3470
  const store = useStoreApi();
3471
+ const updateNodeInternals = useUpdateNodeInternals();
3454
3472
  const validation = useMemo(() => {
3455
3473
  return validateFlow(nodes, edges);
3456
3474
  }, [nodes, edges]);
@@ -3620,13 +3638,18 @@ const FlowCanvas = ({
3620
3638
  const hasPositions = steps.length > 0 && steps[0].positionX != null;
3621
3639
  const finalNodes = hasPositions ? n : applyAutoLayout(n, e);
3622
3640
  setNodes(finalNodes);
3623
- setEdges(e);
3624
- setTimeout(() => {
3625
- fitViewWithOffset();
3626
- }, 50);
3641
+ requestAnimationFrame(() => {
3642
+ finalNodes.forEach((node) => updateNodeInternals(node.id));
3643
+ requestAnimationFrame(() => {
3644
+ setEdges(e);
3645
+ setTimeout(() => {
3646
+ fitViewWithOffset();
3647
+ }, 50);
3648
+ });
3649
+ });
3627
3650
  }
3628
3651
  isInitialMount.current = false;
3629
- }, [steps, setNodes, setEdges, fitViewWithOffset]);
3652
+ }, [steps, setNodes, setEdges, fitViewWithOffset, updateNodeInternals]);
3630
3653
  const syncToParent = useCallback(() => {
3631
3654
  if (isInitialMount.current) return;
3632
3655
  if (syncTimeoutRef.current) {
@@ -131,7 +131,7 @@ const index = {
131
131
  components: {
132
132
  Input: async () => Promise.resolve().then(() => require(
133
133
  /* webpackChunkName: "crm-step-flow-builder" */
134
- "../_chunks/index-PhHmqA3f.js"
134
+ "../_chunks/index-CzxnKN-q.js"
135
135
  ))
136
136
  },
137
137
  options: {
@@ -130,7 +130,7 @@ const index = {
130
130
  components: {
131
131
  Input: async () => import(
132
132
  /* webpackChunkName: "crm-step-flow-builder" */
133
- "../_chunks/index-paSmi6BE.mjs"
133
+ "../_chunks/index-D6i3MTUb.mjs"
134
134
  )
135
135
  },
136
136
  options: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspirer-dev/crm-dashboard",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "CRM Dashboard and Tools",
5
5
  "strapi": {
6
6
  "name": "crm-dashboard",