@haiiro/docuflow 0.0.8 → 0.0.10
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 +13 -1
- package/manifest.json +108 -25
- package/package.json +1 -1
- package/src/front-components/docflow-documents.front-component.mjs +35 -35
- package/src/front-components/docflow-documents.front-component.tsx +216 -88
- package/src/front-components/docflow-processes.front-component.mjs +42 -42
- package/src/front-components/docflow-processes.front-component.tsx +170 -61
- package/src/front-components/docflow-tasks.front-component.mjs +42 -42
- package/src/front-components/docflow-tasks.front-component.tsx +69 -47
- package/src/front-components/process-steps.front-component.mjs +36 -36
- package/src/front-components/process-steps.front-component.tsx +28 -2
- package/src/front-components/task-form.front-component.mjs +36 -36
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
3
3
|
import { AppPath, enqueueSnackbar, openSidePanelPage } from 'twenty-sdk/front-component';
|
|
4
|
-
import {
|
|
4
|
+
import { IconExternalLink, IconFolderSymlink, IconPencil, IconRestore, IconTrash } from 'twenty-ui/icon';
|
|
5
5
|
import { useTheme } from 'twenty-ui/theme';
|
|
6
6
|
|
|
7
|
-
import { createRecord, getErrorMessage, listRecords } from 'src/front/api';
|
|
8
|
-
import {
|
|
9
|
-
DOCUMENT_TYPE_LABELS,
|
|
10
|
-
formatDate,
|
|
11
|
-
} from 'src/front/format';
|
|
7
|
+
import { createRecord, deleteRecord, getErrorMessage, listRecords, updateRecord } from 'src/front/api';
|
|
8
|
+
import { DOCUMENT_TYPE_LABELS, formatDate } from 'src/front/format';
|
|
12
9
|
import { useAsync, useDebouncedValue } from 'src/front/hooks/use-async';
|
|
13
10
|
import { type DocflowDocument, type DocumentFolder } from 'src/front/types';
|
|
14
11
|
import {
|
|
15
12
|
DataTable,
|
|
16
13
|
DocflowRoot,
|
|
17
14
|
Message,
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
type TableSort,
|
|
16
|
+
toRestOrderBy,
|
|
20
17
|
Toolbar,
|
|
21
18
|
} from 'src/front/ui/components';
|
|
22
|
-
import {
|
|
19
|
+
import { type FolderAppearance, FolderList } from 'src/front/ui/folders';
|
|
20
|
+
import { Button, type MenuAction, PromptDialog, SearchWithFilters } from 'src/front/ui/kit';
|
|
21
|
+
import { restAnd, restEq, restIlike, restIn, restIsNotNull, restOr } from 'src/shared/api/rest-filter.util';
|
|
23
22
|
import { DOCUMENT_OBJECT_NAME, PAGE_SIZE } from 'src/shared/docflow.constants';
|
|
24
23
|
import { logDocflow, logDocflowError } from 'src/shared/log.util';
|
|
25
24
|
import { DOCUMENTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
26
25
|
|
|
27
26
|
const LIST_POLL_INTERVAL_MS = 3000;
|
|
28
27
|
const LIST_POLL_DURATION_MS = 10 * 60 * 1000;
|
|
29
|
-
|
|
30
|
-
type DocumentFilter = 'all' | 'collected' | 'DRAFT' | 'IN_REVIEW' | 'APPROVED' | 'ARCHIVED';
|
|
31
|
-
|
|
32
28
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
33
29
|
const FOLDERS_LIMIT = 500;
|
|
34
30
|
const ALL_FOLDERS = '';
|
|
31
|
+
const ANY = 'any';
|
|
32
|
+
|
|
33
|
+
type DocumentFilters = { status: string; origin: string; scope: 'active' | 'deleted' };
|
|
34
|
+
|
|
35
|
+
const DEFAULT_FILTERS: DocumentFilters = { status: ANY, origin: ANY, scope: 'active' };
|
|
36
|
+
const DEFAULT_SORT: TableSort = { key: 'createdAt', direction: 'desc' };
|
|
35
37
|
|
|
36
38
|
// The sandbox of a front component gets only the name and size of a chosen
|
|
37
39
|
// file, never its content, so the file goes in through the standard "File"
|
|
@@ -56,19 +58,22 @@ const openDocumentCard = async (documentId: string) => {
|
|
|
56
58
|
|
|
57
59
|
const buildFilter = ({
|
|
58
60
|
folderId,
|
|
59
|
-
|
|
61
|
+
filters,
|
|
60
62
|
searchQuery,
|
|
61
63
|
}: {
|
|
62
64
|
folderId: string;
|
|
63
|
-
|
|
65
|
+
filters: DocumentFilters;
|
|
64
66
|
searchQuery: string;
|
|
65
67
|
}) =>
|
|
66
68
|
restAnd(
|
|
67
69
|
folderId !== ALL_FOLDERS ? restEq('folderId', folderId) : undefined,
|
|
68
|
-
|
|
70
|
+
// Deleted = in the Twenty trash, restored from the document's own card
|
|
71
|
+
filters.scope === 'deleted' ? restIsNotNull('deletedAt') : undefined,
|
|
72
|
+
filters.status !== ANY ? restEq('status', filters.status) : undefined,
|
|
73
|
+
filters.origin === 'collected'
|
|
69
74
|
? restIn('origin', ['RECORD', 'PROCESS'])
|
|
70
|
-
:
|
|
71
|
-
? restEq('
|
|
75
|
+
: filters.origin === 'UPLOAD'
|
|
76
|
+
? restEq('origin', 'UPLOAD')
|
|
72
77
|
: undefined,
|
|
73
78
|
searchQuery.trim().length > 0
|
|
74
79
|
? restOr(
|
|
@@ -84,11 +89,12 @@ const DocflowDocuments = () => {
|
|
|
84
89
|
const [searchInput, setSearchInput] = useState('');
|
|
85
90
|
const searchQuery = useDebouncedValue(searchInput, SEARCH_DEBOUNCE_MS);
|
|
86
91
|
const [folderId, setFolderId] = useState(ALL_FOLDERS);
|
|
87
|
-
const [
|
|
92
|
+
const [filters, setFilters] = useState<DocumentFilters>(DEFAULT_FILTERS);
|
|
93
|
+
const [sort, setSort] = useState<TableSort>(DEFAULT_SORT);
|
|
88
94
|
const [openedDocumentId, setOpenedDocumentId] = useState<string | undefined>();
|
|
95
|
+
const [renamingDocument, setRenamingDocument] = useState<DocflowDocument | undefined>();
|
|
89
96
|
const [watchStartedAt, setWatchStartedAt] = useState<number | undefined>();
|
|
90
97
|
const [pages, setPages] = useState(1);
|
|
91
|
-
const [newFolderName, setNewFolderName] = useState('');
|
|
92
98
|
const [isCreating, setIsCreating] = useState(false);
|
|
93
99
|
|
|
94
100
|
const { data: folders, reload: reloadFolders } = useAsync(
|
|
@@ -103,18 +109,25 @@ const DocflowDocuments = () => {
|
|
|
103
109
|
);
|
|
104
110
|
const listQuery = useMemo(
|
|
105
111
|
() => ({
|
|
106
|
-
filter: buildFilter({ folderId,
|
|
107
|
-
order_by:
|
|
112
|
+
filter: buildFilter({ folderId, filters, searchQuery }),
|
|
113
|
+
order_by: toRestOrderBy(sort),
|
|
108
114
|
limit: PAGE_SIZE * pages,
|
|
109
115
|
depth: 1,
|
|
110
116
|
}),
|
|
111
|
-
[folderId,
|
|
117
|
+
[folderId, filters, searchQuery, sort, pages],
|
|
112
118
|
);
|
|
113
119
|
const { data, setData, loading, error, reload } = useAsync(
|
|
114
120
|
() => listRecords<DocflowDocument>('smbDocuments', listQuery),
|
|
115
121
|
[listQuery],
|
|
116
122
|
);
|
|
117
123
|
const documents = data?.records ?? [];
|
|
124
|
+
const activeFilterCount =
|
|
125
|
+
(filters.status !== ANY ? 1 : 0) + (filters.origin !== ANY ? 1 : 0) + (filters.scope !== 'active' ? 1 : 0);
|
|
126
|
+
|
|
127
|
+
const changeFilters = (patch: Partial<DocumentFilters>) => {
|
|
128
|
+
setFilters((current) => ({ ...current, ...patch }));
|
|
129
|
+
setPages(1);
|
|
130
|
+
};
|
|
118
131
|
|
|
119
132
|
// A document opens in its standard card in the side panel; while people
|
|
120
133
|
// edit there, the list re-reads itself quietly to show the changes.
|
|
@@ -149,6 +162,9 @@ const DocflowDocuments = () => {
|
|
|
149
162
|
};
|
|
150
163
|
}, [watchStartedAt, listQuery, data]);
|
|
151
164
|
|
|
165
|
+
const notifyError = (actionError: unknown, fallback: string) =>
|
|
166
|
+
enqueueSnackbar({ message: getErrorMessage(actionError, fallback), variant: 'error' });
|
|
167
|
+
|
|
152
168
|
// As "Создать" of a standard list: an empty draft in the open folder, filled
|
|
153
169
|
// in its own card in the side panel (the file goes in there too).
|
|
154
170
|
const handleCreateDocument = async () => {
|
|
@@ -171,97 +187,191 @@ const DocflowDocuments = () => {
|
|
|
171
187
|
await openDocument(document.id);
|
|
172
188
|
} catch (createError) {
|
|
173
189
|
logDocflowError('documents: create failed', createError);
|
|
174
|
-
await
|
|
175
|
-
message: getErrorMessage(createError, 'Не удалось создать документ'),
|
|
176
|
-
variant: 'error',
|
|
177
|
-
});
|
|
190
|
+
await notifyError(createError, 'Не удалось создать документ');
|
|
178
191
|
} finally {
|
|
179
192
|
setIsCreating(false);
|
|
180
193
|
}
|
|
181
194
|
};
|
|
182
195
|
|
|
183
|
-
const
|
|
184
|
-
|
|
196
|
+
const handleUpdateDocument = async (documentId: string, patch: Record<string, unknown>, done: string) => {
|
|
197
|
+
try {
|
|
198
|
+
await updateRecord('smbDocuments', documentId, patch);
|
|
199
|
+
await enqueueSnackbar({ message: done, variant: 'success' });
|
|
200
|
+
await reload();
|
|
201
|
+
} catch (updateError) {
|
|
202
|
+
logDocflowError('documents: update failed', updateError, { documentId });
|
|
203
|
+
await notifyError(updateError, 'Не удалось изменить документ');
|
|
204
|
+
}
|
|
205
|
+
};
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
|
|
207
|
+
const handleDeleteDocument = async (documentId: string) => {
|
|
208
|
+
try {
|
|
209
|
+
await deleteRecord('smbDocuments', documentId);
|
|
210
|
+
await enqueueSnackbar({ message: 'Документ удалён: он в фильтре «Удалённые»', variant: 'success' });
|
|
211
|
+
await reload();
|
|
212
|
+
} catch (deleteError) {
|
|
213
|
+
logDocflowError('documents: delete failed', deleteError, { documentId });
|
|
214
|
+
await notifyError(deleteError, 'Не удалось удалить документ');
|
|
188
215
|
}
|
|
216
|
+
};
|
|
189
217
|
|
|
218
|
+
const handleCreateFolder = async (appearance: FolderAppearance) => {
|
|
190
219
|
try {
|
|
191
|
-
await createRecord('smbDocumentFolders', {
|
|
192
|
-
|
|
220
|
+
const folder = await createRecord<DocumentFolder>('smbDocumentFolders', {
|
|
221
|
+
...appearance,
|
|
193
222
|
parentFolderId: folderId === ALL_FOLDERS ? null : folderId,
|
|
194
223
|
});
|
|
195
|
-
|
|
224
|
+
|
|
196
225
|
await reloadFolders();
|
|
226
|
+
|
|
227
|
+
if (folder !== undefined) {
|
|
228
|
+
setFolderId(folder.id);
|
|
229
|
+
}
|
|
197
230
|
} catch (folderError) {
|
|
198
|
-
await
|
|
231
|
+
await notifyError(folderError, 'Не удалось создать папку');
|
|
199
232
|
}
|
|
200
233
|
};
|
|
201
234
|
|
|
235
|
+
const handleUpdateFolder = async (id: string, appearance: FolderAppearance) => {
|
|
236
|
+
try {
|
|
237
|
+
await updateRecord('smbDocumentFolders', id, { ...appearance });
|
|
238
|
+
await reloadFolders();
|
|
239
|
+
} catch (folderError) {
|
|
240
|
+
await notifyError(folderError, 'Не удалось изменить папку');
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// The documents stay: they show under "Все документы"
|
|
245
|
+
const handleDeleteFolder = async (id: string) => {
|
|
246
|
+
try {
|
|
247
|
+
await deleteRecord('smbDocumentFolders', id);
|
|
248
|
+
|
|
249
|
+
if (folderId === id) {
|
|
250
|
+
setFolderId(ALL_FOLDERS);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await reloadFolders();
|
|
254
|
+
await enqueueSnackbar({ message: 'Папка удалена, документы остались в «Все документы»', variant: 'success' });
|
|
255
|
+
} catch (folderError) {
|
|
256
|
+
await notifyError(folderError, 'Не удалось удалить папку');
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const documentActions = (documentId: string): MenuAction[] => {
|
|
261
|
+
const document = documents.find(({ id }) => id === documentId);
|
|
262
|
+
|
|
263
|
+
if (document === undefined) {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (filters.scope === 'deleted') {
|
|
268
|
+
return [
|
|
269
|
+
{ key: 'restore', label: 'Открыть и восстановить', icon: IconRestore, onSelect: () => void openDocument(documentId) },
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return [
|
|
274
|
+
{ key: 'open', label: 'Открыть карточку', icon: IconExternalLink, onSelect: () => void openDocument(documentId) },
|
|
275
|
+
{ key: 'rename', label: 'Переименовать', icon: IconPencil, onSelect: () => setRenamingDocument(document) },
|
|
276
|
+
...(folders ?? [])
|
|
277
|
+
.filter((folder) => folder.id !== document.folderId)
|
|
278
|
+
.map((folder) => ({
|
|
279
|
+
key: `move-${folder.id}`,
|
|
280
|
+
label: `В папку «${folder.name?.trim() || 'Без названия'}»`,
|
|
281
|
+
icon: IconFolderSymlink,
|
|
282
|
+
onSelect: () =>
|
|
283
|
+
void handleUpdateDocument(documentId, { folderId: folder.id }, `Документ перемещён в «${folder.name ?? ''}»`),
|
|
284
|
+
})),
|
|
285
|
+
...(typeof document.folderId === 'string' && document.folderId.length > 0
|
|
286
|
+
? [
|
|
287
|
+
{
|
|
288
|
+
key: 'unfile',
|
|
289
|
+
label: 'Убрать из папки',
|
|
290
|
+
icon: IconFolderSymlink,
|
|
291
|
+
onSelect: () => void handleUpdateDocument(documentId, { folderId: null }, 'Документ убран из папки'),
|
|
292
|
+
},
|
|
293
|
+
]
|
|
294
|
+
: []),
|
|
295
|
+
{
|
|
296
|
+
key: 'delete',
|
|
297
|
+
label: 'Удалить',
|
|
298
|
+
confirmLabel: 'Точно удалить?',
|
|
299
|
+
icon: IconTrash,
|
|
300
|
+
tone: 'danger' as const,
|
|
301
|
+
onSelect: () => void handleDeleteDocument(documentId),
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
};
|
|
305
|
+
|
|
202
306
|
return (
|
|
203
307
|
<DocflowRoot>
|
|
204
308
|
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto' }}>
|
|
205
309
|
<div style={{ display: 'flex', flex: 1, flexWrap: 'wrap', minHeight: 0 }}>
|
|
206
|
-
<
|
|
207
|
-
aria-label="Папки"
|
|
310
|
+
<div
|
|
208
311
|
style={{
|
|
209
312
|
boxSizing: 'border-box',
|
|
210
|
-
display: 'flex',
|
|
211
313
|
flex: '1 1 200px',
|
|
212
|
-
flexDirection: 'column',
|
|
213
|
-
gap: theme.spacing[1],
|
|
214
314
|
maxWidth: '240px',
|
|
215
315
|
padding: `${theme.spacing[4]} ${theme.spacing[3]} ${theme.spacing[4]} ${theme.spacing[5]}`,
|
|
216
316
|
}}
|
|
217
317
|
>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
))}
|
|
232
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[1], marginTop: theme.spacing[3] }}>
|
|
233
|
-
<TextField placeholder="Новая папка" value={newFolderName} onChange={setNewFolderName} />
|
|
234
|
-
<Button variant="outline" size="sm" disabled={newFolderName.trim().length === 0} onClick={() => void handleCreateFolder()}>
|
|
235
|
-
Создать папку
|
|
236
|
-
</Button>
|
|
237
|
-
</div>
|
|
238
|
-
</nav>
|
|
318
|
+
<FolderList
|
|
319
|
+
folders={folders ?? []}
|
|
320
|
+
selectedFolderId={folderId}
|
|
321
|
+
allFoldersId={ALL_FOLDERS}
|
|
322
|
+
onSelect={(id) => {
|
|
323
|
+
setFolderId(id);
|
|
324
|
+
setPages(1);
|
|
325
|
+
}}
|
|
326
|
+
onCreate={handleCreateFolder}
|
|
327
|
+
onUpdate={handleUpdateFolder}
|
|
328
|
+
onDelete={handleDeleteFolder}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
239
331
|
<div style={{ display: 'flex', flex: '999 1 420px', flexDirection: 'column', minWidth: 0 }}>
|
|
240
332
|
<Toolbar>
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
333
|
+
<SearchWithFilters
|
|
334
|
+
value={searchInput}
|
|
335
|
+
onChange={setSearchInput}
|
|
336
|
+
placeholder="Название, номер, где загружен"
|
|
337
|
+
activeFilterCount={activeFilterCount}
|
|
338
|
+
onResetFilters={() => changeFilters(DEFAULT_FILTERS)}
|
|
339
|
+
filters={[
|
|
340
|
+
{
|
|
341
|
+
key: 'status',
|
|
342
|
+
title: 'Статус',
|
|
343
|
+
value: filters.status,
|
|
344
|
+
options: [
|
|
345
|
+
{ value: ANY, label: 'Любой' },
|
|
346
|
+
{ value: 'DRAFT', label: 'Черновики' },
|
|
347
|
+
{ value: 'IN_REVIEW', label: 'На согласовании' },
|
|
348
|
+
{ value: 'APPROVED', label: 'Согласованные' },
|
|
349
|
+
{ value: 'ARCHIVED', label: 'В архиве' },
|
|
350
|
+
],
|
|
351
|
+
onChange: (value) => changeFilters({ status: value }),
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
key: 'origin',
|
|
355
|
+
title: 'Откуда',
|
|
356
|
+
value: filters.origin,
|
|
357
|
+
options: [
|
|
358
|
+
{ value: ANY, label: 'Отовсюду' },
|
|
359
|
+
{ value: 'UPLOAD', label: 'Загружены в документы' },
|
|
360
|
+
{ value: 'collected', label: 'Из записей и процессов' },
|
|
361
|
+
],
|
|
362
|
+
onChange: (value) => changeFilters({ origin: value }),
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
key: 'scope',
|
|
366
|
+
title: 'Корзина',
|
|
367
|
+
value: filters.scope,
|
|
368
|
+
options: [
|
|
369
|
+
{ value: 'active', label: 'Действующие' },
|
|
370
|
+
{ value: 'deleted', label: 'Удалённые' },
|
|
371
|
+
],
|
|
372
|
+
onChange: (value) => changeFilters({ scope: value as DocumentFilters['scope'] }),
|
|
373
|
+
},
|
|
260
374
|
]}
|
|
261
|
-
onChange={(value) => {
|
|
262
|
-
setDocumentFilter(value);
|
|
263
|
-
setPages(1);
|
|
264
|
-
}}
|
|
265
375
|
/>
|
|
266
376
|
<div style={{ marginLeft: 'auto' }}>
|
|
267
377
|
<Button variant="solid" size="sm" loading={isCreating} onClick={() => void handleCreateDocument()}>
|
|
@@ -279,14 +389,20 @@ const DocflowDocuments = () => {
|
|
|
279
389
|
{documents.length > 0 && (
|
|
280
390
|
<DataTable
|
|
281
391
|
columns={[
|
|
282
|
-
{ key: 'name', label: 'Название', width: '
|
|
283
|
-
{ key: 'type', label: 'Тип', width: '14%' },
|
|
284
|
-
{ key: 'number', label: 'Номер', width: '12%' },
|
|
285
|
-
{ key: 'source', label: 'Где загружен', width: '24%' },
|
|
286
|
-
{ key: 'createdAt', label: 'Добавлен' },
|
|
392
|
+
{ key: 'name', label: 'Название', width: '30%', sortKey: 'name' },
|
|
393
|
+
{ key: 'type', label: 'Тип', width: '14%', sortKey: 'documentType' },
|
|
394
|
+
{ key: 'number', label: 'Номер', width: '12%', sortKey: 'number' },
|
|
395
|
+
{ key: 'source', label: 'Где загружен', width: '24%', sortKey: 'sourceRecordName' },
|
|
396
|
+
{ key: 'createdAt', label: 'Добавлен', sortKey: 'createdAt' },
|
|
287
397
|
]}
|
|
398
|
+
sort={sort}
|
|
399
|
+
onSortChange={(next) => {
|
|
400
|
+
setSort(next);
|
|
401
|
+
setPages(1);
|
|
402
|
+
}}
|
|
288
403
|
selectedRowId={openedDocumentId}
|
|
289
404
|
onRowClick={(documentId) => void openDocument(documentId)}
|
|
405
|
+
rowActions={documentActions}
|
|
290
406
|
rows={documents.map((document) => ({
|
|
291
407
|
id: document.id,
|
|
292
408
|
cells: {
|
|
@@ -309,6 +425,18 @@ const DocflowDocuments = () => {
|
|
|
309
425
|
</div>
|
|
310
426
|
</div>
|
|
311
427
|
</div>
|
|
428
|
+
{renamingDocument !== undefined && (
|
|
429
|
+
<PromptDialog
|
|
430
|
+
title="Переименовать документ"
|
|
431
|
+
label="Название"
|
|
432
|
+
initialValue={renamingDocument.name ?? ''}
|
|
433
|
+
onClose={() => setRenamingDocument(undefined)}
|
|
434
|
+
onSubmit={async (name) => {
|
|
435
|
+
await handleUpdateDocument(renamingDocument.id, { name }, 'Документ переименован');
|
|
436
|
+
setRenamingDocument(undefined);
|
|
437
|
+
}}
|
|
438
|
+
/>
|
|
439
|
+
)}
|
|
312
440
|
</DocflowRoot>
|
|
313
441
|
);
|
|
314
442
|
};
|