@haiiro/docuflow 0.0.16 → 0.0.17
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 +8 -2
- package/manifest.json +34 -19
- package/package.json +1 -1
- package/public/app/docflow-app.js +5 -5
- package/src/front-components/docflow-documents.front-component.mjs +1 -1
- package/src/front-components/docflow-processes.front-component.mjs +35 -35
- package/src/front-components/docflow-processes.front-component.tsx +187 -85
- package/src/front-components/docflow-tasks.front-component.mjs +24 -24
- package/src/front-components/launch-process.front-component.mjs +19 -19
- package/src/front-components/launch-process.front-component.tsx +36 -60
- package/src/front-components/process-steps.front-component.mjs +17 -17
- package/src/front-components/task-form.front-component.mjs +10 -10
- package/src/logic-functions/bitrix-app.logic-function.mjs +1 -1
- package/src/logic-functions/complete-task.logic-function.mjs +39 -8
- package/src/logic-functions/docflow-app.logic-function.mjs +1 -1
- package/src/logic-functions/hand-out-form-step.logic-function.mjs +71278 -11217
- package/src/logic-functions/hand-out-form-step.logic-function.ts +1 -1
- package/src/logic-functions/launch-process.logic-function.mjs +269 -33
- package/src/logic-functions/launch-process.logic-function.ts +42 -37
- package/src/logic-functions/on-task-updated.logic-function.mjs +39 -8
- package/src/logic-functions/on-workflow-run-updated.logic-function.mjs +35 -9
- package/src/logic-functions/on-workflow-run-updated.logic-function.ts +6 -3
- package/src/logic-functions/process-catalog.logic-function.mjs +119726 -0
- package/src/logic-functions/process-catalog.logic-function.ts +84 -0
- package/src/logic-functions/publish-process.logic-function.mjs +218 -16
- package/src/logic-functions/publish-process.logic-function.ts +13 -5
- package/src/logic-functions/save-launch-fields.logic-function.mjs +5 -2
- package/src/logic-functions/save-step-settings.logic-function.mjs +5 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
3
3
|
import { AppPath, enqueueSnackbar, navigate } from 'twenty-sdk/front-component';
|
|
4
|
-
import { IconPencil, IconPlayerPlay, IconPlayerStop, IconRocket, IconSettings, IconTrash } from 'twenty-ui/icon';
|
|
4
|
+
import { IconPencil, IconPlayerPlay, IconPlayerStop, IconPlus, IconRocket, IconSettings, IconTrash, IconX } from 'twenty-ui/icon';
|
|
5
5
|
import { useTheme } from 'twenty-ui/theme';
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -40,27 +40,24 @@ import {
|
|
|
40
40
|
useRowSelection,
|
|
41
41
|
} from 'src/front/ui/components';
|
|
42
42
|
import { FormFieldInputs } from 'src/front/ui/form-field-inputs';
|
|
43
|
-
import { Button, type MenuAction, MenuButton, PromptDialog, QuickViews, StatusChip } from 'src/front/ui/kit';
|
|
43
|
+
import { Button, type MenuAction, MenuButton, PromptDialog, QuickViews, StatusChip, ValueDialog } from 'src/front/ui/kit';
|
|
44
44
|
import { Timeline } from 'src/front/ui/timeline';
|
|
45
|
-
import { restAnd, restEq
|
|
45
|
+
import { restAnd, restEq } from 'src/shared/api/rest-filter.util';
|
|
46
46
|
import { DOCFLOW_SECTION } from 'src/shared/docflow.constants';
|
|
47
47
|
import { type FormField, type FormValues } from 'src/shared/form/form-field.type';
|
|
48
48
|
import { findMissingRequiredFields } from 'src/shared/form/form-fields.util';
|
|
49
49
|
import { logDocflow, logDocflowError } from 'src/shared/log.util';
|
|
50
|
+
import { buildLaunchParameters, isLaunchableTrigger, isLaunchParametersStep } from 'src/shared/workflow/launch-step.util';
|
|
50
51
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
parseLaunchFields,
|
|
56
|
-
parseLaunchObject,
|
|
57
|
-
} from 'src/shared/workflow/launch-step.util';
|
|
52
|
+
type CatalogProcess,
|
|
53
|
+
type CatalogVersion,
|
|
54
|
+
type OtherProcess,
|
|
55
|
+
} from 'src/shared/workflow/process-catalog.type';
|
|
58
56
|
import { orderRunSteps } from 'src/shared/workflow/run-timeline.util';
|
|
59
|
-
import { type WorkflowStep
|
|
57
|
+
import { type WorkflowStep } from 'src/shared/workflow/workflow-run.type';
|
|
60
58
|
import { getFormStepAssignment, getFormStepFields, isFormStep } from 'src/shared/workflow/workflow-run.util';
|
|
61
59
|
import { PROCESSES_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
62
60
|
|
|
63
|
-
const PROCESSES_LIMIT = 200;
|
|
64
61
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
65
62
|
const DRAFT_WAIT_ATTEMPTS = 10;
|
|
66
63
|
const DRAFT_WAIT_MS = 400;
|
|
@@ -71,23 +68,14 @@ type Panel = { kind: 'closed' } | { kind: 'process'; workflowId: string };
|
|
|
71
68
|
type ProcessView = 'all' | 'active' | 'draft';
|
|
72
69
|
type ParametersFilter = 'any' | 'with' | 'without';
|
|
73
70
|
|
|
74
|
-
type WorkflowRecord = {
|
|
75
|
-
id: string;
|
|
76
|
-
name?: string | null;
|
|
77
|
-
smbLaunchFields?: unknown;
|
|
78
|
-
smbLaunchObject?: string | null;
|
|
79
|
-
updatedAt?: string | null;
|
|
80
|
-
};
|
|
81
|
-
type WorkflowVersionRecord = {
|
|
82
|
-
id: string;
|
|
83
|
-
workflowId: string;
|
|
84
|
-
status?: string | null;
|
|
85
|
-
trigger?: WorkflowTrigger | null;
|
|
86
|
-
steps?: WorkflowStep[] | null;
|
|
87
|
-
};
|
|
71
|
+
type WorkflowRecord = { id: string };
|
|
88
72
|
|
|
89
73
|
type DocflowProcess = {
|
|
74
|
+
// The process engine's id: the editor page, runs and launching take it
|
|
90
75
|
workflowId: string;
|
|
76
|
+
// The process record of Twenty 2.45: renaming and deleting go through it.
|
|
77
|
+
// Twenty 2.50 has none, those are done in its editor.
|
|
78
|
+
workspaceWorkflowId: string | null;
|
|
91
79
|
// The published version people launch; none while the process is a draft
|
|
92
80
|
workflowVersionId: string | undefined;
|
|
93
81
|
name: string;
|
|
@@ -124,66 +112,51 @@ const VIEWS: { value: ProcessView; label: string }[] = [
|
|
|
124
112
|
{ value: 'draft', label: 'Черновики' },
|
|
125
113
|
];
|
|
126
114
|
|
|
127
|
-
const stagesOf = (version:
|
|
115
|
+
const stagesOf = (version: CatalogVersion | undefined) =>
|
|
128
116
|
orderRunSteps({ flow: { trigger: version?.trigger, steps: version?.steps } }).filter((step) => !isLaunchParametersStep(step));
|
|
129
117
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
118
|
+
const toDocflowProcess = (process: CatalogProcess): DocflowProcess => {
|
|
119
|
+
const { active, draft, deactivated } = process;
|
|
120
|
+
const isLaunchable = active !== undefined && isLaunchableTrigger(active.trigger);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
workflowId: process.workflowId,
|
|
124
|
+
workspaceWorkflowId: process.workspaceWorkflowId,
|
|
125
|
+
workflowVersionId: isLaunchable ? active.id : undefined,
|
|
126
|
+
name: process.name,
|
|
127
|
+
isPublished: isLaunchable,
|
|
128
|
+
isActive: active !== undefined,
|
|
129
|
+
hasUnpublishedChanges: active !== undefined && draft !== undefined,
|
|
130
|
+
launchFields: process.launchFields,
|
|
131
|
+
launchObject: process.launchObject,
|
|
132
|
+
stages: stagesOf(active ?? draft ?? deactivated),
|
|
133
|
+
updatedAt: process.updatedAt,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
146
136
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
order_by: 'createdAt[DescNullsLast]',
|
|
154
|
-
limit: PROCESSES_LIMIT * 5,
|
|
137
|
+
// Docflow processes come from the app's route, which reads them through the
|
|
138
|
+
// Twenty process engine (2.50 keeps no process records) and knows which are
|
|
139
|
+
// docflow ones. The list is short: searched here.
|
|
140
|
+
const loadProcesses = async (searchQuery: string): Promise<DocflowProcess[]> => {
|
|
141
|
+
const { processes } = await callAppRoute<{ processes: CatalogProcess[] }>('/docflow/processes/catalog', {
|
|
142
|
+
action: 'list',
|
|
155
143
|
});
|
|
144
|
+
const query = searchQuery.trim().toLowerCase();
|
|
156
145
|
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Taken off publishing: its steps still show
|
|
161
|
-
const deactivated = versions.find((version) => version.workflowId === workflow.id && version.status === 'DEACTIVATED');
|
|
162
|
-
const isLaunchable = active !== undefined && isLaunchableTrigger(active.trigger);
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
workflowId: workflow.id,
|
|
166
|
-
workflowVersionId: isLaunchable ? active.id : undefined,
|
|
167
|
-
name: workflow.name?.trim() || 'Без названия',
|
|
168
|
-
isPublished: isLaunchable,
|
|
169
|
-
isActive: active !== undefined,
|
|
170
|
-
hasUnpublishedChanges: active !== undefined && draft !== undefined,
|
|
171
|
-
launchFields: parseLaunchFields(workflow.smbLaunchFields),
|
|
172
|
-
launchObject: isLaunchTrigger(active?.trigger) ? parseLaunchObject(workflow.smbLaunchObject) : undefined,
|
|
173
|
-
stages: stagesOf(active ?? draft ?? deactivated),
|
|
174
|
-
updatedAt: workflow.updatedAt,
|
|
175
|
-
};
|
|
176
|
-
});
|
|
146
|
+
return processes
|
|
147
|
+
.map(toDocflowProcess)
|
|
148
|
+
.filter((process) => query.length === 0 || process.name.toLowerCase().includes(query));
|
|
177
149
|
};
|
|
178
150
|
|
|
179
|
-
// Runs per process: all of them and the ones still going
|
|
151
|
+
// Runs per process: all of them and the ones still going. A run names its
|
|
152
|
+
// process by the engine's id on every Twenty version.
|
|
180
153
|
const loadRunCounts = async (workflowIds: string[]): Promise<Record<string, RunCounts>> =>
|
|
181
154
|
Object.fromEntries(
|
|
182
155
|
await Promise.all(
|
|
183
156
|
workflowIds.map(async (workflowId) => {
|
|
184
157
|
const [total, running] = await Promise.all([
|
|
185
|
-
countRecords('workflowRuns', restEq('
|
|
186
|
-
countRecords('workflowRuns', restAnd(restEq('
|
|
158
|
+
countRecords('workflowRuns', restEq('coreWorkflowId', workflowId)),
|
|
159
|
+
countRecords('workflowRuns', restAnd(restEq('coreWorkflowId', workflowId), restEq('status', 'RUNNING'))),
|
|
187
160
|
]);
|
|
188
161
|
|
|
189
162
|
return [workflowId, { total, running }];
|
|
@@ -191,8 +164,10 @@ const loadRunCounts = async (workflowIds: string[]): Promise<Record<string, RunC
|
|
|
191
164
|
),
|
|
192
165
|
);
|
|
193
166
|
|
|
194
|
-
const openEditor = (workflowId: string) =>
|
|
195
|
-
|
|
167
|
+
const openEditor = (workflowId: string) => navigate(AppPath.WorkflowCoreShowPage, { coreWorkflowId: workflowId });
|
|
168
|
+
|
|
169
|
+
// What Twenty 2.50 does only in its own editor
|
|
170
|
+
const EDITOR_ONLY_MESSAGE = 'В этой версии Twenty это делается в редакторе процесса: «Открыть редактор»';
|
|
196
171
|
|
|
197
172
|
const openRun = (runId: string) =>
|
|
198
173
|
navigate(AppPath.RecordShowPage, { objectNameSingular: 'workflowRun', objectRecordId: runId });
|
|
@@ -234,7 +209,7 @@ const RecentRuns = ({ workflowId }: { workflowId: string }) => {
|
|
|
234
209
|
async () =>
|
|
235
210
|
(
|
|
236
211
|
await listRecords<DocflowWorkflowRun>('workflowRuns', {
|
|
237
|
-
filter: restEq('
|
|
212
|
+
filter: restEq('coreWorkflowId', workflowId),
|
|
238
213
|
order_by: 'createdAt[DescNullsLast]',
|
|
239
214
|
limit: RECENT_RUNS_LIMIT,
|
|
240
215
|
})
|
|
@@ -387,7 +362,7 @@ const ProcessPanel = ({
|
|
|
387
362
|
|
|
388
363
|
try {
|
|
389
364
|
await callAppRoute('/docflow/processes/launch', {
|
|
390
|
-
|
|
365
|
+
workflowId: process.workflowId,
|
|
391
366
|
values: parameters,
|
|
392
367
|
});
|
|
393
368
|
await enqueueSnackbar({ message: 'Процесс запущен', variant: 'success' });
|
|
@@ -460,6 +435,70 @@ const ProcessPanel = ({
|
|
|
460
435
|
);
|
|
461
436
|
};
|
|
462
437
|
|
|
438
|
+
// Processes of the workspace made in the Twenty editor, added to docflow:
|
|
439
|
+
// on Twenty 2.50 that is how a process gets here, the app cannot create one.
|
|
440
|
+
const AddFromTwentyDialog = ({ onClose, onAdded }: { onClose: () => void; onAdded: () => void }) => {
|
|
441
|
+
const theme = useTheme();
|
|
442
|
+
const [checkedIds, setCheckedIds] = useState<string[]>([]);
|
|
443
|
+
const [errorMessage, setErrorMessage] = useState<string | undefined>();
|
|
444
|
+
const { data: others, loading, error } = useAsync(
|
|
445
|
+
async () =>
|
|
446
|
+
(await callAppRoute<{ processes: OtherProcess[] }>('/docflow/processes/catalog', { action: 'others' })).processes,
|
|
447
|
+
[],
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const toggle = (workflowId: string) =>
|
|
451
|
+
setCheckedIds((current) =>
|
|
452
|
+
current.includes(workflowId) ? current.filter((id) => id !== workflowId) : [...current, workflowId],
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
return (
|
|
456
|
+
<ValueDialog
|
|
457
|
+
title="Добавить процессы Twenty"
|
|
458
|
+
hint="Процессы из «Рабочих процессов» Twenty станут процессами документооборота: их можно будет запускать отсюда, а их задания попадут в «Задания»."
|
|
459
|
+
confirmLabel="Добавить"
|
|
460
|
+
canSubmit={checkedIds.length > 0}
|
|
461
|
+
onClose={onClose}
|
|
462
|
+
onSubmit={async () => {
|
|
463
|
+
try {
|
|
464
|
+
await callAppRoute('/docflow/processes/catalog', { action: 'add', workflowIds: checkedIds });
|
|
465
|
+
await enqueueSnackbar({ message: `Добавлено: ${checkedIds.length}`, variant: 'success' });
|
|
466
|
+
onAdded();
|
|
467
|
+
} catch (addError) {
|
|
468
|
+
setErrorMessage(getErrorMessage(addError, 'Не удалось добавить'));
|
|
469
|
+
}
|
|
470
|
+
}}
|
|
471
|
+
>
|
|
472
|
+
{loading && others === undefined && <ListSkeleton rows={3} />}
|
|
473
|
+
{error !== undefined && <Message tone="danger">{getErrorMessage(error, 'Не удалось загрузить процессы')}</Message>}
|
|
474
|
+
{others !== undefined && others.length === 0 && <Message tone="hint">Других процессов нет.</Message>}
|
|
475
|
+
<div style={{ display: 'flex', flexDirection: 'column', maxHeight: '320px', overflowY: 'auto' }}>
|
|
476
|
+
{(others ?? []).map((other) => (
|
|
477
|
+
<label
|
|
478
|
+
key={other.workflowId}
|
|
479
|
+
style={{
|
|
480
|
+
alignItems: 'center',
|
|
481
|
+
cursor: 'pointer',
|
|
482
|
+
display: 'flex',
|
|
483
|
+
fontSize: theme.font.size.md,
|
|
484
|
+
gap: theme.spacing[2],
|
|
485
|
+
padding: `${theme.spacing[1]} 0`,
|
|
486
|
+
}}
|
|
487
|
+
>
|
|
488
|
+
<input
|
|
489
|
+
type="checkbox"
|
|
490
|
+
checked={checkedIds.includes(other.workflowId)}
|
|
491
|
+
onChange={() => toggle(other.workflowId)}
|
|
492
|
+
/>
|
|
493
|
+
{other.name}
|
|
494
|
+
</label>
|
|
495
|
+
))}
|
|
496
|
+
</div>
|
|
497
|
+
{errorMessage !== undefined && <Message tone="danger">{errorMessage}</Message>}
|
|
498
|
+
</ValueDialog>
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
|
|
463
502
|
// The server gives a new workflow its first draft version right after the
|
|
464
503
|
// record; the launch step can only be put into an existing draft.
|
|
465
504
|
const waitForDraft = async (workflowId: string) => {
|
|
@@ -488,6 +527,7 @@ const DocflowProcesses = () => {
|
|
|
488
527
|
const [panel, setPanel] = useState<Panel>({ kind: 'closed' });
|
|
489
528
|
const [renaming, setRenaming] = useState<DocflowProcess | undefined>();
|
|
490
529
|
const [isCreating, setIsCreating] = useState(false);
|
|
530
|
+
const [isAdding, setIsAdding] = useState(false);
|
|
491
531
|
const [hiddenColumns, setHiddenColumns] = useHiddenColumns(['updatedAt']);
|
|
492
532
|
const { data: loaded, loading, error, reload } = useAsync(() => loadProcesses(searchQuery), [searchQuery]);
|
|
493
533
|
const workflowIdsKey = (loaded ?? []).map(({ workflowId }) => workflowId).join();
|
|
@@ -537,18 +577,43 @@ const DocflowProcesses = () => {
|
|
|
537
577
|
},
|
|
538
578
|
});
|
|
539
579
|
|
|
580
|
+
// The 2.45 process records of the given processes; none on Twenty 2.50,
|
|
581
|
+
// which renames and deletes processes only in its editor
|
|
582
|
+
const recordIdsOf = (ids: string[]): string[] | undefined => {
|
|
583
|
+
const recordIds = ids.map((id) => processes.find(({ workflowId }) => workflowId === id)?.workspaceWorkflowId);
|
|
584
|
+
|
|
585
|
+
return recordIds.every((recordId): recordId is string => typeof recordId === 'string') ? recordIds : undefined;
|
|
586
|
+
};
|
|
587
|
+
|
|
540
588
|
// The processes go to the Twenty trash ("Рабочие процессы → Удалённые"),
|
|
541
589
|
// their runs and tasks stay.
|
|
542
590
|
const removeProcesses = (ids: string[]) =>
|
|
543
591
|
bulk.run(
|
|
544
592
|
async () => {
|
|
545
|
-
|
|
593
|
+
const recordIds = recordIdsOf(ids);
|
|
594
|
+
|
|
595
|
+
if (recordIds === undefined) {
|
|
596
|
+
throw new Error(EDITOR_ONLY_MESSAGE);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
await deleteRecords('workflows', recordIds);
|
|
546
600
|
setPanel((current) => (current.kind === 'process' && ids.includes(current.workflowId) ? { kind: 'closed' } : current));
|
|
547
601
|
},
|
|
548
602
|
ids.length === 1 ? 'Процесс удалён' : `Удалено процессов: ${ids.length}`,
|
|
549
603
|
'Не удалось удалить',
|
|
550
604
|
);
|
|
551
605
|
|
|
606
|
+
// Only the mark goes: the process stays in Twenty with its runs
|
|
607
|
+
const takeOutOfDocflow = (ids: string[]) =>
|
|
608
|
+
bulk.run(
|
|
609
|
+
async () => {
|
|
610
|
+
await callAppRoute('/docflow/processes/catalog', { action: 'remove', workflowIds: ids });
|
|
611
|
+
setPanel((current) => (current.kind === 'process' && ids.includes(current.workflowId) ? { kind: 'closed' } : current));
|
|
612
|
+
},
|
|
613
|
+
ids.length === 1 ? 'Процесс убран из документооборота' : `Убрано из документооборота: ${ids.length}`,
|
|
614
|
+
'Не удалось убрать',
|
|
615
|
+
);
|
|
616
|
+
|
|
552
617
|
// Publishing goes through the app's route, one process at a time: the
|
|
553
618
|
// workflow engine takes a version per call.
|
|
554
619
|
const publishProcesses = (ids: string[], publish: boolean) =>
|
|
@@ -559,7 +624,11 @@ const DocflowProcesses = () => {
|
|
|
559
624
|
|
|
560
625
|
const handleRenameProcess = async (process: DocflowProcess, name: string) => {
|
|
561
626
|
try {
|
|
562
|
-
|
|
627
|
+
if (process.workspaceWorkflowId === null) {
|
|
628
|
+
throw new Error(EDITOR_ONLY_MESSAGE);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
await updateRecord('workflows', process.workspaceWorkflowId, { name });
|
|
563
632
|
await enqueueSnackbar({ message: 'Процесс переименован', variant: 'success' });
|
|
564
633
|
setRenaming(undefined);
|
|
565
634
|
await reload();
|
|
@@ -593,10 +662,21 @@ const DocflowProcesses = () => {
|
|
|
593
662
|
});
|
|
594
663
|
} catch (createError) {
|
|
595
664
|
logDocflowError('process: create failed', createError);
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
665
|
+
// Twenty 2.50 lets no app create a process: it is made in the Twenty
|
|
666
|
+
// editor and added here
|
|
667
|
+
const message = getErrorMessage(createError, 'Не удалось создать процесс');
|
|
668
|
+
|
|
669
|
+
if (/Method not allowed/i.test(message)) {
|
|
670
|
+
await enqueueSnackbar({
|
|
671
|
+
message: 'В этой версии Twenty процесс создаётся в «Рабочих процессах» Twenty, затем — «Добавить из Twenty»',
|
|
672
|
+
variant: 'info',
|
|
673
|
+
});
|
|
674
|
+
await navigate(AppPath.RecordIndexPage, { objectNamePlural: 'workflows' });
|
|
675
|
+
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
await enqueueSnackbar({ message, variant: 'error' });
|
|
600
680
|
} finally {
|
|
601
681
|
setIsCreating(false);
|
|
602
682
|
}
|
|
@@ -628,6 +708,12 @@ const DocflowProcesses = () => {
|
|
|
628
708
|
: []),
|
|
629
709
|
{ key: 'edit', label: 'Открыть редактор', icon: IconSettings, onSelect: () => void openEditor(workflowId) },
|
|
630
710
|
{ key: 'rename', label: 'Переименовать', icon: IconPencil, onSelect: () => setRenaming(process) },
|
|
711
|
+
{
|
|
712
|
+
key: 'take-out',
|
|
713
|
+
label: 'Убрать из документооборота',
|
|
714
|
+
icon: IconX,
|
|
715
|
+
onSelect: () => void takeOutOfDocflow([workflowId]),
|
|
716
|
+
},
|
|
631
717
|
{
|
|
632
718
|
key: 'delete',
|
|
633
719
|
label: 'Удалить',
|
|
@@ -692,6 +778,11 @@ const DocflowProcesses = () => {
|
|
|
692
778
|
}))}
|
|
693
779
|
/>
|
|
694
780
|
}
|
|
781
|
+
actions={
|
|
782
|
+
<Button size="md" icon={IconPlus} onClick={() => setIsAdding(true)}>
|
|
783
|
+
Добавить из Twenty
|
|
784
|
+
</Button>
|
|
785
|
+
}
|
|
695
786
|
onCreate={() => void handleCreate()}
|
|
696
787
|
isCreating={isCreating}
|
|
697
788
|
/>
|
|
@@ -702,7 +793,9 @@ const DocflowProcesses = () => {
|
|
|
702
793
|
)}
|
|
703
794
|
{loading && loaded === undefined && <TableSkeleton />}
|
|
704
795
|
{!loading && error === undefined && processes.length === 0 && (
|
|
705
|
-
<Message
|
|
796
|
+
<Message>
|
|
797
|
+
Процессов нет. «Создать» добавит новый процесс, «Добавить из Twenty» — уже настроенный в «Рабочих процессах».
|
|
798
|
+
</Message>
|
|
706
799
|
)}
|
|
707
800
|
{processes.length > 0 && (
|
|
708
801
|
<DataTable
|
|
@@ -779,6 +872,15 @@ const DocflowProcesses = () => {
|
|
|
779
872
|
/>
|
|
780
873
|
</SelectionBar>
|
|
781
874
|
</SplitView>
|
|
875
|
+
{isAdding && (
|
|
876
|
+
<AddFromTwentyDialog
|
|
877
|
+
onClose={() => setIsAdding(false)}
|
|
878
|
+
onAdded={() => {
|
|
879
|
+
setIsAdding(false);
|
|
880
|
+
void reloadAll();
|
|
881
|
+
}}
|
|
882
|
+
/>
|
|
883
|
+
)}
|
|
782
884
|
{renaming !== undefined && (
|
|
783
885
|
<PromptDialog
|
|
784
886
|
title="Переименовать процесс"
|