@open-mercato/core 0.6.3-develop.3876.1.d40fe4ec2d → 0.6.3-develop.3881.1.0b590ac4eb
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/attachments/api/file/[id]/route.js +7 -2
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -4
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/audit_logs/services/accessLogService.js +127 -8
- package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
- package/dist/modules/auth/di.js +17 -3
- package/dist/modules/auth/di.js.map +2 -2
- package/dist/modules/auth/services/rbacDefaultCache.js +110 -0
- package/dist/modules/auth/services/rbacDefaultCache.js.map +7 -0
- package/dist/modules/currencies/api/currencies/route.js +3 -4
- package/dist/modules/currencies/api/currencies/route.js.map +2 -2
- package/dist/modules/currencies/api/exchange-rates/route.js +3 -4
- package/dist/modules/currencies/api/exchange-rates/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +26 -24
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +26 -0
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +7 -0
- package/dist/modules/directory/utils/organizationScope.js +85 -0
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +2 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +4 -2
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +20 -3
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/ActivitiesEditor.js +34 -1
- package/dist/modules/workflows/components/ActivitiesEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +153 -17
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/StepsEditor.js +31 -0
- package/dist/modules/workflows/components/StepsEditor.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraph.js +3 -2
- package/dist/modules/workflows/components/WorkflowGraph.js.map +2 -2
- package/dist/modules/workflows/components/nodes/WaitForTimerNode.js +54 -0
- package/dist/modules/workflows/components/nodes/WaitForTimerNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/index.js +3 -1
- package/dist/modules/workflows/components/nodes/index.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +117 -0
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/di.js +5 -1
- package/dist/modules/workflows/di.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +42 -1
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
- package/dist/modules/workflows/lib/activity-worker-handler.js +24 -0
- package/dist/modules/workflows/lib/activity-worker-handler.js.map +2 -2
- package/dist/modules/workflows/lib/duration.js +32 -0
- package/dist/modules/workflows/lib/duration.js.map +7 -0
- package/dist/modules/workflows/lib/event-logger.js +1 -0
- package/dist/modules/workflows/lib/event-logger.js.map +2 -2
- package/dist/modules/workflows/lib/format-validation-error.js +12 -0
- package/dist/modules/workflows/lib/format-validation-error.js.map +7 -0
- package/dist/modules/workflows/lib/graph-utils.js +6 -3
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/dist/modules/workflows/lib/node-type-icons.js +9 -5
- package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
- package/dist/modules/workflows/lib/signal-handler.js +55 -23
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +79 -29
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +159 -0
- package/dist/modules/workflows/lib/timer-handler.js.map +7 -0
- package/dist/modules/workflows/lib/workflow-executor.js +1 -1
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/workers/workflow-activities.worker.js +20 -4
- package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/api/file/[id]/route.ts +7 -2
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -4
- package/src/modules/audit_logs/services/accessLogService.ts +179 -15
- package/src/modules/auth/di.ts +26 -3
- package/src/modules/auth/services/rbacDefaultCache.ts +145 -0
- package/src/modules/currencies/api/currencies/route.ts +3 -4
- package/src/modules/currencies/api/exchange-rates/route.ts +3 -4
- package/src/modules/customers/api/people/route.ts +27 -25
- package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +39 -0
- package/src/modules/directory/utils/organizationScope.ts +121 -0
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +3 -2
- package/src/modules/workflows/backend/definitions/create/page.tsx +4 -2
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +18 -1
- package/src/modules/workflows/components/ActivitiesEditor.tsx +40 -0
- package/src/modules/workflows/components/NodeEditDialog.tsx +218 -30
- package/src/modules/workflows/components/StepsEditor.tsx +36 -0
- package/src/modules/workflows/components/WorkflowGraph.tsx +2 -1
- package/src/modules/workflows/components/nodes/WaitForTimerNode.tsx +70 -0
- package/src/modules/workflows/components/nodes/index.ts +3 -0
- package/src/modules/workflows/data/validators.ts +121 -0
- package/src/modules/workflows/di.ts +4 -0
- package/src/modules/workflows/i18n/de.json +10 -1
- package/src/modules/workflows/i18n/en.json +10 -1
- package/src/modules/workflows/i18n/es.json +10 -1
- package/src/modules/workflows/i18n/pl.json +10 -1
- package/src/modules/workflows/lib/activity-executor.ts +86 -2
- package/src/modules/workflows/lib/activity-queue-types.ts +18 -11
- package/src/modules/workflows/lib/activity-worker-handler.ts +29 -0
- package/src/modules/workflows/lib/duration.ts +51 -0
- package/src/modules/workflows/lib/event-logger.ts +1 -0
- package/src/modules/workflows/lib/format-validation-error.ts +30 -0
- package/src/modules/workflows/lib/graph-utils.ts +3 -0
- package/src/modules/workflows/lib/node-type-icons.ts +6 -2
- package/src/modules/workflows/lib/signal-handler.ts +62 -24
- package/src/modules/workflows/lib/step-handler.ts +107 -50
- package/src/modules/workflows/lib/timer-handler.ts +213 -0
- package/src/modules/workflows/lib/workflow-executor.ts +1 -1
- package/src/modules/workflows/workers/workflow-activities.worker.ts +33 -7
|
@@ -21,6 +21,7 @@ import {JsonBuilder} from '@open-mercato/ui/backend/JsonBuilder'
|
|
|
21
21
|
import {StartPreConditionsEditor, type StartPreCondition} from './fields/StartPreConditionsEditor'
|
|
22
22
|
import {useT} from '@open-mercato/shared/lib/i18n/context'
|
|
23
23
|
import {useConfirmDialog} from '@open-mercato/ui/backend/confirm-dialog'
|
|
24
|
+
import {isFutureIsoDateString, isValidDurationString} from '../data/validators'
|
|
24
25
|
|
|
25
26
|
export interface NodeEditDialogProps {
|
|
26
27
|
node: Node | null
|
|
@@ -81,6 +82,10 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
81
82
|
const [signalName, setSignalName] = useState('')
|
|
82
83
|
const [signalTimeout, setSignalTimeout] = useState('')
|
|
83
84
|
|
|
85
|
+
// Wait for timer configuration fields
|
|
86
|
+
const [timerDuration, setTimerDuration] = useState('')
|
|
87
|
+
const [timerUntil, setTimerUntil] = useState('')
|
|
88
|
+
|
|
84
89
|
// Step activities state (for AUTOMATED steps)
|
|
85
90
|
const [stepActivities, setStepActivities] = useState<any[]>([])
|
|
86
91
|
const [expandedStepActivities, setExpandedStepActivities] = useState<Set<number>>(new Set())
|
|
@@ -88,6 +93,9 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
88
93
|
// Pre-conditions state (for START steps)
|
|
89
94
|
const [preConditions, setPreConditions] = useState<StartPreCondition[]>([])
|
|
90
95
|
|
|
96
|
+
// Inline validation errors keyed by field name
|
|
97
|
+
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
|
|
98
|
+
|
|
91
99
|
// Convert JSON Schema to our custom format
|
|
92
100
|
const convertJsonSchemaToFields = (schema: any): FormField[] => {
|
|
93
101
|
if (!schema || !schema.properties) return []
|
|
@@ -211,6 +219,15 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
211
219
|
setSignalTimeout('')
|
|
212
220
|
}
|
|
213
221
|
|
|
222
|
+
// Load timer configuration
|
|
223
|
+
if (node.type === 'waitForTimer') {
|
|
224
|
+
setTimerDuration(nodeData?.config?.duration || '')
|
|
225
|
+
setTimerUntil(nodeData?.config?.until || '')
|
|
226
|
+
} else {
|
|
227
|
+
setTimerDuration('')
|
|
228
|
+
setTimerUntil('')
|
|
229
|
+
}
|
|
230
|
+
|
|
214
231
|
// Load step activities (for AUTOMATED steps)
|
|
215
232
|
if (node.type === 'automated' && nodeData?.activities) {
|
|
216
233
|
setStepActivities(nodeData.activities)
|
|
@@ -257,6 +274,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
257
274
|
}
|
|
258
275
|
setAdvancedConfig(advancedFields)
|
|
259
276
|
setExpandedFields(new Set())
|
|
277
|
+
setFieldErrors({})
|
|
260
278
|
}
|
|
261
279
|
}, [node, isOpen])
|
|
262
280
|
|
|
@@ -313,6 +331,30 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
313
331
|
const handleSave = () => {
|
|
314
332
|
if (!node) return
|
|
315
333
|
|
|
334
|
+
// Pre-save validation for wait-related fields. Surface inline errors instead
|
|
335
|
+
// of silently saving an invalid value that will only blow up later when the
|
|
336
|
+
// whole workflow is serialized through the API zod schema.
|
|
337
|
+
const errors: Record<string, string> = {}
|
|
338
|
+
|
|
339
|
+
if (node.type === 'waitForTimer') {
|
|
340
|
+
if (timerDuration && !isValidDurationString(timerDuration)) {
|
|
341
|
+
errors.timerDuration = t('workflows.validation.invalidDuration')
|
|
342
|
+
}
|
|
343
|
+
if (timerUntil && !isFutureIsoDateString(timerUntil)) {
|
|
344
|
+
errors.timerUntil = t('workflows.validation.untilMustBeFuture')
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (node.type === 'waitForSignal' && signalTimeout && !isValidDurationString(signalTimeout)) {
|
|
349
|
+
errors.signalTimeout = t('workflows.validation.invalidDuration')
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (Object.keys(errors).length > 0) {
|
|
353
|
+
setFieldErrors(errors)
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
setFieldErrors({})
|
|
357
|
+
|
|
316
358
|
// Validate and sanitize step ID
|
|
317
359
|
const sanitizedId = sanitizeId(node.id)
|
|
318
360
|
if (sanitizedId !== node.id) {
|
|
@@ -407,6 +449,17 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
407
449
|
}
|
|
408
450
|
}
|
|
409
451
|
|
|
452
|
+
// Wait for timer specific fields (duration XOR until)
|
|
453
|
+
if (node.type === 'waitForTimer') {
|
|
454
|
+
const config: any = {}
|
|
455
|
+
if (timerDuration) {
|
|
456
|
+
config.duration = timerDuration
|
|
457
|
+
} else if (timerUntil) {
|
|
458
|
+
config.until = timerUntil
|
|
459
|
+
}
|
|
460
|
+
updates.config = Object.keys(config).length > 0 ? config : undefined
|
|
461
|
+
}
|
|
462
|
+
|
|
410
463
|
// Step activities (for AUTOMATED steps)
|
|
411
464
|
if (node.type === 'automated' && stepActivities.length > 0) {
|
|
412
465
|
updates.activities = stepActivities
|
|
@@ -449,6 +502,9 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
449
502
|
userTask: t('workflows.nodeTypes.userTask'),
|
|
450
503
|
automated: t('workflows.nodeTypes.automated'),
|
|
451
504
|
decision: t('workflows.nodeTypes.decision'),
|
|
505
|
+
waitForSignal: t('workflows.nodeTypes.waitForSignal'),
|
|
506
|
+
waitForTimer: t('workflows.nodeTypes.waitForTimer'),
|
|
507
|
+
subWorkflow: t('workflows.nodeTypes.subWorkflow'),
|
|
452
508
|
}[node.type || 'automated']
|
|
453
509
|
|
|
454
510
|
// START nodes are partially editable (pre-conditions only), END nodes are not editable
|
|
@@ -544,27 +600,30 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
544
600
|
</p>
|
|
545
601
|
</div>
|
|
546
602
|
|
|
547
|
-
{/* Timeout
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
603
|
+
{/* Timeout — hidden for wait nodes that already expose their own time-bound config
|
|
604
|
+
(waitForTimer uses duration/until, waitForSignal uses signalConfig.timeout). */}
|
|
605
|
+
{node.type !== 'waitForSignal' && node.type !== 'waitForTimer' && (
|
|
606
|
+
<div>
|
|
607
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
608
|
+
{t('workflows.form.timeout')}
|
|
609
|
+
</label>
|
|
610
|
+
<Input
|
|
611
|
+
type="text"
|
|
612
|
+
value={timeout}
|
|
613
|
+
onChange={(e) => setTimeout(e.target.value)}
|
|
614
|
+
placeholder={t('workflows.form.placeholders.timeout')}
|
|
615
|
+
/>
|
|
616
|
+
<p className="text-xs text-gray-500 mt-1">
|
|
617
|
+
{t('workflows.form.descriptions.timeout')}
|
|
618
|
+
</p>
|
|
619
|
+
</div>
|
|
620
|
+
)}
|
|
562
621
|
|
|
563
622
|
{/* User Task Configuration */}
|
|
564
623
|
{node.type === 'userTask' && (
|
|
565
624
|
<>
|
|
566
625
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
567
|
-
<h3 className="text-sm font-semibold text-
|
|
626
|
+
<h3 className="text-sm font-semibold text-foreground mb-3">
|
|
568
627
|
{t('workflows.nodeEditor.userTaskConfig')}
|
|
569
628
|
</h3>
|
|
570
629
|
</div>
|
|
@@ -618,7 +677,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
618
677
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
619
678
|
<div className="flex items-center justify-between mb-3">
|
|
620
679
|
<div>
|
|
621
|
-
<h3 className="text-sm font-semibold text-
|
|
680
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
622
681
|
{t('workflows.form.formFields', { count: formFields.length })}
|
|
623
682
|
</h3>
|
|
624
683
|
<p className="text-xs text-gray-500 mt-0.5">
|
|
@@ -663,7 +722,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
663
722
|
>
|
|
664
723
|
<div className="flex-1">
|
|
665
724
|
<div className="flex items-center gap-2">
|
|
666
|
-
<span className="text-sm font-semibold text-
|
|
725
|
+
<span className="text-sm font-semibold text-foreground">
|
|
667
726
|
{field.label || field.name}
|
|
668
727
|
</span>
|
|
669
728
|
<Badge variant="secondary" className="text-xs">
|
|
@@ -819,7 +878,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
819
878
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
820
879
|
<div className="flex items-center justify-between mb-3">
|
|
821
880
|
<div>
|
|
822
|
-
<h3 className="text-sm font-semibold text-
|
|
881
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
823
882
|
{t('workflows.form.stepActivities', { count: stepActivities.length })}
|
|
824
883
|
</h3>
|
|
825
884
|
<p className="text-xs text-gray-500 mt-0.5">
|
|
@@ -874,7 +933,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
874
933
|
>
|
|
875
934
|
<div className="flex-1">
|
|
876
935
|
<div className="flex items-center gap-2">
|
|
877
|
-
<span className="text-sm font-semibold text-
|
|
936
|
+
<span className="text-sm font-semibold text-foreground">
|
|
878
937
|
{activity.activityName || activity.activityId || `Activity ${index + 1}`}
|
|
879
938
|
</span>
|
|
880
939
|
<Badge variant="secondary" className="text-xs">
|
|
@@ -957,6 +1016,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
957
1016
|
<SelectItem value="EMIT_EVENT">{t('workflows.activities.types.EMIT_EVENT')}</SelectItem>
|
|
958
1017
|
<SelectItem value="CALL_WEBHOOK">{t('workflows.activities.types.CALL_WEBHOOK')}</SelectItem>
|
|
959
1018
|
<SelectItem value="EXECUTE_FUNCTION">{t('workflows.activities.types.EXECUTE_FUNCTION')}</SelectItem>
|
|
1019
|
+
<SelectItem value="WAIT">{t('workflows.activities.types.WAIT')}</SelectItem>
|
|
960
1020
|
</SelectContent>
|
|
961
1021
|
</Select>
|
|
962
1022
|
</div>
|
|
@@ -1065,7 +1125,48 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1065
1125
|
</label>
|
|
1066
1126
|
</div>
|
|
1067
1127
|
|
|
1068
|
-
{/* Activity
|
|
1128
|
+
{/* WAIT Activity: Duration / Until fields */}
|
|
1129
|
+
{activity.activityType === 'WAIT' && (
|
|
1130
|
+
<div className="space-y-3">
|
|
1131
|
+
<div>
|
|
1132
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1133
|
+
{t('workflows.activities.waitDuration')}
|
|
1134
|
+
</label>
|
|
1135
|
+
<Input
|
|
1136
|
+
size="sm"
|
|
1137
|
+
type="text"
|
|
1138
|
+
value={activity.config?.duration || ''}
|
|
1139
|
+
onChange={(e) => {
|
|
1140
|
+
const updated = [...stepActivities]
|
|
1141
|
+
updated[index].config = { ...updated[index].config, duration: e.target.value, until: undefined }
|
|
1142
|
+
setStepActivities(updated)
|
|
1143
|
+
}}
|
|
1144
|
+
placeholder={t('workflows.activities.waitDurationPlaceholder')}
|
|
1145
|
+
/>
|
|
1146
|
+
<p className="text-xs text-muted-foreground mt-1">{t('workflows.activities.waitDurationDescription')}</p>
|
|
1147
|
+
</div>
|
|
1148
|
+
<div className="text-xs text-center text-muted-foreground">{t('workflows.activities.waitOr')}</div>
|
|
1149
|
+
<div>
|
|
1150
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1151
|
+
{t('workflows.activities.waitUntil')}
|
|
1152
|
+
</label>
|
|
1153
|
+
<Input
|
|
1154
|
+
size="sm"
|
|
1155
|
+
type="datetime-local"
|
|
1156
|
+
value={activity.config?.until ? activity.config.until.slice(0, 16) : ''}
|
|
1157
|
+
onChange={(e) => {
|
|
1158
|
+
const updated = [...stepActivities]
|
|
1159
|
+
updated[index].config = { ...updated[index].config, until: e.target.value ? new Date(e.target.value).toISOString() : undefined, duration: undefined }
|
|
1160
|
+
setStepActivities(updated)
|
|
1161
|
+
}}
|
|
1162
|
+
/>
|
|
1163
|
+
<p className="text-xs text-muted-foreground mt-1">{t('workflows.activities.waitUntilDescription')}</p>
|
|
1164
|
+
</div>
|
|
1165
|
+
</div>
|
|
1166
|
+
)}
|
|
1167
|
+
|
|
1168
|
+
{/* Activity Config JSON (hidden for WAIT) */}
|
|
1169
|
+
{activity.activityType !== 'WAIT' && (
|
|
1069
1170
|
<div>
|
|
1070
1171
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1071
1172
|
{t('workflows.form.configuration')}
|
|
@@ -1082,6 +1183,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1082
1183
|
{t('workflows.form.descriptions.activityConfig')}
|
|
1083
1184
|
</p>
|
|
1084
1185
|
</div>
|
|
1186
|
+
)}
|
|
1085
1187
|
|
|
1086
1188
|
{/* Delete Button */}
|
|
1087
1189
|
<div className="pt-3 border-t border-gray-100">
|
|
@@ -1114,7 +1216,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1114
1216
|
{node.type === 'subWorkflow' && (
|
|
1115
1217
|
<>
|
|
1116
1218
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
1117
|
-
<h3 className="text-sm font-semibold text-
|
|
1219
|
+
<h3 className="text-sm font-semibold text-foreground mb-3">
|
|
1118
1220
|
{t('workflows.form.subWorkflowConfig')}
|
|
1119
1221
|
</h3>
|
|
1120
1222
|
</div>
|
|
@@ -1164,7 +1266,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1164
1266
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
1165
1267
|
<div className="flex items-center justify-between mb-3">
|
|
1166
1268
|
<div>
|
|
1167
|
-
<h4 className="text-sm font-semibold text-
|
|
1269
|
+
<h4 className="text-sm font-semibold text-foreground">
|
|
1168
1270
|
{t('workflows.form.inputMapping', { count: inputMappings.length })}
|
|
1169
1271
|
</h4>
|
|
1170
1272
|
<p className="text-xs text-gray-500 mt-0.5">
|
|
@@ -1237,7 +1339,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1237
1339
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
1238
1340
|
<div className="flex items-center justify-between mb-3">
|
|
1239
1341
|
<div>
|
|
1240
|
-
<h4 className="text-sm font-semibold text-
|
|
1342
|
+
<h4 className="text-sm font-semibold text-foreground">
|
|
1241
1343
|
{t('workflows.form.outputMapping', { count: outputMappings.length })}
|
|
1242
1344
|
</h4>
|
|
1243
1345
|
<p className="text-xs text-gray-500 mt-0.5">
|
|
@@ -1312,7 +1414,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1312
1414
|
{node.type === 'waitForSignal' && (
|
|
1313
1415
|
<>
|
|
1314
1416
|
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
1315
|
-
<h3 className="text-sm font-semibold text-
|
|
1417
|
+
<h3 className="text-sm font-semibold text-foreground mb-3">
|
|
1316
1418
|
{t('workflows.form.signalConfig')}
|
|
1317
1419
|
</h3>
|
|
1318
1420
|
</div>
|
|
@@ -1339,12 +1441,98 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1339
1441
|
<Input
|
|
1340
1442
|
type="text"
|
|
1341
1443
|
value={signalTimeout}
|
|
1342
|
-
onChange={(e) =>
|
|
1444
|
+
onChange={(e) => {
|
|
1445
|
+
setSignalTimeout(e.target.value)
|
|
1446
|
+
if (fieldErrors.signalTimeout) {
|
|
1447
|
+
const next = { ...fieldErrors }
|
|
1448
|
+
delete next.signalTimeout
|
|
1449
|
+
setFieldErrors(next)
|
|
1450
|
+
}
|
|
1451
|
+
}}
|
|
1343
1452
|
placeholder={t('workflows.form.placeholders.signalTimeout')}
|
|
1453
|
+
aria-invalid={fieldErrors.signalTimeout ? true : undefined}
|
|
1344
1454
|
/>
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1455
|
+
{fieldErrors.signalTimeout ? (
|
|
1456
|
+
<p className="text-xs text-destructive mt-1">
|
|
1457
|
+
{fieldErrors.signalTimeout}
|
|
1458
|
+
</p>
|
|
1459
|
+
) : (
|
|
1460
|
+
<p className="text-xs text-gray-500 mt-1">
|
|
1461
|
+
{t('workflows.form.descriptions.signalTimeout')}
|
|
1462
|
+
</p>
|
|
1463
|
+
)}
|
|
1464
|
+
</div>
|
|
1465
|
+
</>
|
|
1466
|
+
)}
|
|
1467
|
+
|
|
1468
|
+
{/* Wait for Timer Configuration */}
|
|
1469
|
+
{node.type === 'waitForTimer' && (
|
|
1470
|
+
<>
|
|
1471
|
+
<div className="border-t border-gray-200 pt-4 mt-4">
|
|
1472
|
+
<h3 className="text-sm font-semibold text-foreground mb-3">
|
|
1473
|
+
{t('workflows.steps.types.WAIT_FOR_TIMER')}
|
|
1474
|
+
</h3>
|
|
1475
|
+
</div>
|
|
1476
|
+
|
|
1477
|
+
<div>
|
|
1478
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1479
|
+
{t('workflows.activities.waitDuration')}
|
|
1480
|
+
</label>
|
|
1481
|
+
<Input
|
|
1482
|
+
type="text"
|
|
1483
|
+
value={timerDuration}
|
|
1484
|
+
onChange={(e) => {
|
|
1485
|
+
setTimerDuration(e.target.value)
|
|
1486
|
+
if (e.target.value) setTimerUntil('')
|
|
1487
|
+
if (fieldErrors.timerDuration) {
|
|
1488
|
+
const next = { ...fieldErrors }
|
|
1489
|
+
delete next.timerDuration
|
|
1490
|
+
setFieldErrors(next)
|
|
1491
|
+
}
|
|
1492
|
+
}}
|
|
1493
|
+
placeholder={t('workflows.activities.waitDurationPlaceholder')}
|
|
1494
|
+
aria-invalid={fieldErrors.timerDuration ? true : undefined}
|
|
1495
|
+
/>
|
|
1496
|
+
{fieldErrors.timerDuration ? (
|
|
1497
|
+
<p className="text-xs text-destructive mt-1">
|
|
1498
|
+
{fieldErrors.timerDuration}
|
|
1499
|
+
</p>
|
|
1500
|
+
) : (
|
|
1501
|
+
<p className="text-xs text-gray-500 mt-1">
|
|
1502
|
+
{t('workflows.activities.waitDurationDescription')}
|
|
1503
|
+
</p>
|
|
1504
|
+
)}
|
|
1505
|
+
</div>
|
|
1506
|
+
|
|
1507
|
+
<div>
|
|
1508
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
1509
|
+
{t('workflows.activities.waitUntil')}
|
|
1510
|
+
</label>
|
|
1511
|
+
<Input
|
|
1512
|
+
type="datetime-local"
|
|
1513
|
+
value={timerUntil ? timerUntil.slice(0, 16) : ''}
|
|
1514
|
+
min={new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16)}
|
|
1515
|
+
onChange={(e) => {
|
|
1516
|
+
const next = e.target.value ? new Date(e.target.value).toISOString() : ''
|
|
1517
|
+
setTimerUntil(next)
|
|
1518
|
+
if (next) setTimerDuration('')
|
|
1519
|
+
if (fieldErrors.timerUntil) {
|
|
1520
|
+
const nextErrors = { ...fieldErrors }
|
|
1521
|
+
delete nextErrors.timerUntil
|
|
1522
|
+
setFieldErrors(nextErrors)
|
|
1523
|
+
}
|
|
1524
|
+
}}
|
|
1525
|
+
aria-invalid={fieldErrors.timerUntil ? true : undefined}
|
|
1526
|
+
/>
|
|
1527
|
+
{fieldErrors.timerUntil ? (
|
|
1528
|
+
<p className="text-xs text-destructive mt-1">
|
|
1529
|
+
{fieldErrors.timerUntil}
|
|
1530
|
+
</p>
|
|
1531
|
+
) : (
|
|
1532
|
+
<p className="text-xs text-gray-500 mt-1">
|
|
1533
|
+
{t('workflows.activities.waitUntilDescription')}
|
|
1534
|
+
</p>
|
|
1535
|
+
)}
|
|
1348
1536
|
</div>
|
|
1349
1537
|
</>
|
|
1350
1538
|
)}
|
|
@@ -1356,7 +1544,7 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
1356
1544
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
|
1357
1545
|
className="flex items-center justify-between w-full text-left"
|
|
1358
1546
|
>
|
|
1359
|
-
<h3 className="text-sm font-semibold text-
|
|
1547
|
+
<h3 className="text-sm font-semibold text-foreground">
|
|
1360
1548
|
{t('workflows.form.advancedConfiguration')}
|
|
1361
1549
|
</h3>
|
|
1362
1550
|
<svg
|
|
@@ -203,6 +203,42 @@ export function StepsEditor({ value = [], onChange, error }: StepsEditorProps) {
|
|
|
203
203
|
</div>
|
|
204
204
|
</div>
|
|
205
205
|
|
|
206
|
+
{step.stepType === 'WAIT_FOR_TIMER' && (
|
|
207
|
+
<div className="grid grid-cols-1 sm:grid-cols-[1fr_auto_1fr] gap-3 items-end">
|
|
208
|
+
<div>
|
|
209
|
+
<Label htmlFor={`step-${index}-duration`} className="text-xs">
|
|
210
|
+
{t('workflows.activities.waitDuration')}
|
|
211
|
+
</Label>
|
|
212
|
+
<Input
|
|
213
|
+
id={`step-${index}-duration`}
|
|
214
|
+
value={step.config?.duration || ''}
|
|
215
|
+
onChange={(e) => updateStep(index, 'config', { ...step.config, duration: e.target.value, until: undefined })}
|
|
216
|
+
placeholder={t('workflows.activities.waitDurationPlaceholder')}
|
|
217
|
+
className="mt-1"
|
|
218
|
+
/>
|
|
219
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
220
|
+
{t('workflows.activities.waitDurationDescription')}
|
|
221
|
+
</p>
|
|
222
|
+
</div>
|
|
223
|
+
<span className="text-xs text-muted-foreground pb-6">{t('workflows.activities.waitOr')}</span>
|
|
224
|
+
<div>
|
|
225
|
+
<Label htmlFor={`step-${index}-until`} className="text-xs">
|
|
226
|
+
{t('workflows.activities.waitUntil')}
|
|
227
|
+
</Label>
|
|
228
|
+
<Input
|
|
229
|
+
id={`step-${index}-until`}
|
|
230
|
+
type="datetime-local"
|
|
231
|
+
value={step.config?.until ? step.config.until.slice(0, 16) : ''}
|
|
232
|
+
onChange={(e) => updateStep(index, 'config', { ...step.config, until: e.target.value ? new Date(e.target.value).toISOString() : undefined, duration: undefined })}
|
|
233
|
+
className="mt-1"
|
|
234
|
+
/>
|
|
235
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
236
|
+
{t('workflows.activities.waitUntilDescription')}
|
|
237
|
+
</p>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
|
|
206
242
|
<div>
|
|
207
243
|
<Label htmlFor={`step-${index}-description`} className="text-xs">
|
|
208
244
|
{t('workflows.steps.singular')} {t('workflows.definitions.description')}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
ConnectionMode,
|
|
18
18
|
MarkerType,
|
|
19
19
|
} from '@xyflow/react'
|
|
20
|
-
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode} from './nodes'
|
|
20
|
+
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
|
|
21
21
|
import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
|
|
22
22
|
import { STATUS_COLORS } from '../lib/status-colors'
|
|
23
23
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -151,6 +151,7 @@ export function WorkflowGraph({
|
|
|
151
151
|
automated: AutomatedNode,
|
|
152
152
|
subWorkflow: SubWorkflowNode,
|
|
153
153
|
waitForSignal: WaitForSignalNode,
|
|
154
|
+
waitForTimer: WaitForTimerNode,
|
|
154
155
|
}),
|
|
155
156
|
[]
|
|
156
157
|
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Handle, Position, NodeProps } from '@xyflow/react'
|
|
4
|
+
import { WorkflowNodeCard } from '../WorkflowNodeCard'
|
|
5
|
+
import { WorkflowStatus } from '../../lib/status-colors'
|
|
6
|
+
|
|
7
|
+
export interface WaitForTimerNodeData {
|
|
8
|
+
label: string
|
|
9
|
+
description?: string
|
|
10
|
+
duration?: string
|
|
11
|
+
until?: string
|
|
12
|
+
config?: { duration?: string; until?: string }
|
|
13
|
+
version?: number
|
|
14
|
+
status?: 'pending' | 'running' | 'completed' | 'error' | 'not_started' | 'in_progress'
|
|
15
|
+
stepNumber?: number
|
|
16
|
+
badge?: string
|
|
17
|
+
tooltip?: string
|
|
18
|
+
executionStatus?: 'completed' | 'active' | 'pending' | 'failed' | 'skipped'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* WaitForTimerNode - Pauses workflow for a duration or until a specific datetime
|
|
23
|
+
* Uses WorkflowNodeCard for consistent styling
|
|
24
|
+
*/
|
|
25
|
+
export function WaitForTimerNode({ data, isConnectable, selected }: NodeProps) {
|
|
26
|
+
const nodeData = data as unknown as WaitForTimerNodeData
|
|
27
|
+
|
|
28
|
+
const mapStatus = (status?: string): WorkflowStatus => {
|
|
29
|
+
if (!status || status === 'pending') return 'not_started'
|
|
30
|
+
if (status === 'running' || status === 'in_progress') return 'in_progress'
|
|
31
|
+
if (status === 'completed') return 'completed'
|
|
32
|
+
if (status === 'error') return 'not_started'
|
|
33
|
+
return 'not_started'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const workflowStatus = mapStatus(nodeData.status)
|
|
37
|
+
|
|
38
|
+
const duration = nodeData.duration || nodeData.config?.duration
|
|
39
|
+
const until = nodeData.until || nodeData.config?.until
|
|
40
|
+
const description = nodeData.description ||
|
|
41
|
+
(duration ? `Wait for ${duration}` : until ? `Wait until ${until}` : 'Timer-based pause')
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="wait-for-timer-node" title={nodeData.tooltip}>
|
|
45
|
+
<Handle
|
|
46
|
+
type="target"
|
|
47
|
+
position={Position.Top}
|
|
48
|
+
id="target"
|
|
49
|
+
isConnectable={isConnectable}
|
|
50
|
+
className="!w-3 !h-3 !bg-[#0080FE] !border-2 !border-white"
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<WorkflowNodeCard
|
|
54
|
+
title={nodeData.label}
|
|
55
|
+
description={description}
|
|
56
|
+
status={workflowStatus}
|
|
57
|
+
nodeType="waitForTimer"
|
|
58
|
+
selected={selected}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<Handle
|
|
62
|
+
type="source"
|
|
63
|
+
position={Position.Bottom}
|
|
64
|
+
id="source"
|
|
65
|
+
isConnectable={isConnectable}
|
|
66
|
+
className="!w-3 !h-3 !bg-[#0080FE] !border-2 !border-white"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -15,3 +15,6 @@ export type { SubWorkflowNodeData } from './SubWorkflowNode'
|
|
|
15
15
|
|
|
16
16
|
export { WaitForSignalNode } from './WaitForSignalNode'
|
|
17
17
|
export type { WaitForSignalNodeData } from './WaitForSignalNode'
|
|
18
|
+
|
|
19
|
+
export { WaitForTimerNode } from './WaitForTimerNode'
|
|
20
|
+
export type { WaitForTimerNodeData } from './WaitForTimerNode'
|