@haiiro/docuflow 0.0.9 → 0.0.11
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 +16 -11
- package/bin/install.mjs +32 -10
- package/manifest.json +82 -24
- 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 +206 -94
- package/src/front-components/docflow-processes.front-component.mjs +42 -42
- package/src/front-components/docflow-processes.front-component.tsx +157 -71
- package/src/front-components/docflow-tasks.front-component.mjs +42 -42
- package/src/front-components/docflow-tasks.front-component.tsx +57 -47
- package/src/front-components/process-steps.front-component.mjs +36 -36
- package/src/front-components/process-steps.front-component.tsx +1 -1
- package/src/front-components/task-form.front-component.mjs +36 -36
|
@@ -1,24 +1,23 @@
|
|
|
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, deleteRecord, 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';
|
|
19
|
+
import { type FolderAppearance, FolderList } from 'src/front/ui/folders';
|
|
20
|
+
import { Button, type MenuAction, PromptDialog, SearchWithFilters } from 'src/front/ui/kit';
|
|
22
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';
|
|
@@ -26,12 +25,15 @@ import { DOCUMENTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-id
|
|
|
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' | 'deleted';
|
|
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,22 +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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
filters.scope === 'deleted' ? restIsNotNull('deletedAt') : undefined,
|
|
72
|
+
filters.status !== ANY ? restEq('status', filters.status) : undefined,
|
|
73
|
+
filters.origin === 'collected'
|
|
72
74
|
? restIn('origin', ['RECORD', 'PROCESS'])
|
|
73
|
-
:
|
|
74
|
-
? restEq('
|
|
75
|
+
: filters.origin === 'UPLOAD'
|
|
76
|
+
? restEq('origin', 'UPLOAD')
|
|
75
77
|
: undefined,
|
|
76
78
|
searchQuery.trim().length > 0
|
|
77
79
|
? restOr(
|
|
@@ -87,11 +89,12 @@ const DocflowDocuments = () => {
|
|
|
87
89
|
const [searchInput, setSearchInput] = useState('');
|
|
88
90
|
const searchQuery = useDebouncedValue(searchInput, SEARCH_DEBOUNCE_MS);
|
|
89
91
|
const [folderId, setFolderId] = useState(ALL_FOLDERS);
|
|
90
|
-
const [
|
|
92
|
+
const [filters, setFilters] = useState<DocumentFilters>(DEFAULT_FILTERS);
|
|
93
|
+
const [sort, setSort] = useState<TableSort>(DEFAULT_SORT);
|
|
91
94
|
const [openedDocumentId, setOpenedDocumentId] = useState<string | undefined>();
|
|
95
|
+
const [renamingDocument, setRenamingDocument] = useState<DocflowDocument | undefined>();
|
|
92
96
|
const [watchStartedAt, setWatchStartedAt] = useState<number | undefined>();
|
|
93
97
|
const [pages, setPages] = useState(1);
|
|
94
|
-
const [newFolderName, setNewFolderName] = useState('');
|
|
95
98
|
const [isCreating, setIsCreating] = useState(false);
|
|
96
99
|
|
|
97
100
|
const { data: folders, reload: reloadFolders } = useAsync(
|
|
@@ -106,18 +109,25 @@ const DocflowDocuments = () => {
|
|
|
106
109
|
);
|
|
107
110
|
const listQuery = useMemo(
|
|
108
111
|
() => ({
|
|
109
|
-
filter: buildFilter({ folderId,
|
|
110
|
-
order_by:
|
|
112
|
+
filter: buildFilter({ folderId, filters, searchQuery }),
|
|
113
|
+
order_by: toRestOrderBy(sort),
|
|
111
114
|
limit: PAGE_SIZE * pages,
|
|
112
115
|
depth: 1,
|
|
113
116
|
}),
|
|
114
|
-
[folderId,
|
|
117
|
+
[folderId, filters, searchQuery, sort, pages],
|
|
115
118
|
);
|
|
116
119
|
const { data, setData, loading, error, reload } = useAsync(
|
|
117
120
|
() => listRecords<DocflowDocument>('smbDocuments', listQuery),
|
|
118
121
|
[listQuery],
|
|
119
122
|
);
|
|
120
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
|
+
};
|
|
121
131
|
|
|
122
132
|
// A document opens in its standard card in the side panel; while people
|
|
123
133
|
// edit there, the list re-reads itself quietly to show the changes.
|
|
@@ -152,6 +162,9 @@ const DocflowDocuments = () => {
|
|
|
152
162
|
};
|
|
153
163
|
}, [watchStartedAt, listQuery, data]);
|
|
154
164
|
|
|
165
|
+
const notifyError = (actionError: unknown, fallback: string) =>
|
|
166
|
+
enqueueSnackbar({ message: getErrorMessage(actionError, fallback), variant: 'error' });
|
|
167
|
+
|
|
155
168
|
// As "Создать" of a standard list: an empty draft in the open folder, filled
|
|
156
169
|
// in its own card in the side panel (the file goes in there too).
|
|
157
170
|
const handleCreateDocument = async () => {
|
|
@@ -174,15 +187,23 @@ const DocflowDocuments = () => {
|
|
|
174
187
|
await openDocument(document.id);
|
|
175
188
|
} catch (createError) {
|
|
176
189
|
logDocflowError('documents: create failed', createError);
|
|
177
|
-
await
|
|
178
|
-
message: getErrorMessage(createError, 'Не удалось создать документ'),
|
|
179
|
-
variant: 'error',
|
|
180
|
-
});
|
|
190
|
+
await notifyError(createError, 'Не удалось создать документ');
|
|
181
191
|
} finally {
|
|
182
192
|
setIsCreating(false);
|
|
183
193
|
}
|
|
184
194
|
};
|
|
185
195
|
|
|
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
|
+
};
|
|
206
|
+
|
|
186
207
|
const handleDeleteDocument = async (documentId: string) => {
|
|
187
208
|
try {
|
|
188
209
|
await deleteRecord('smbDocuments', documentId);
|
|
@@ -190,93 +211,167 @@ const DocflowDocuments = () => {
|
|
|
190
211
|
await reload();
|
|
191
212
|
} catch (deleteError) {
|
|
192
213
|
logDocflowError('documents: delete failed', deleteError, { documentId });
|
|
193
|
-
await
|
|
214
|
+
await notifyError(deleteError, 'Не удалось удалить документ');
|
|
194
215
|
}
|
|
195
216
|
};
|
|
196
217
|
|
|
197
|
-
const handleCreateFolder = async () => {
|
|
198
|
-
|
|
218
|
+
const handleCreateFolder = async (appearance: FolderAppearance) => {
|
|
219
|
+
try {
|
|
220
|
+
const folder = await createRecord<DocumentFolder>('smbDocumentFolders', {
|
|
221
|
+
...appearance,
|
|
222
|
+
parentFolderId: folderId === ALL_FOLDERS ? null : folderId,
|
|
223
|
+
});
|
|
199
224
|
|
|
200
|
-
|
|
201
|
-
|
|
225
|
+
await reloadFolders();
|
|
226
|
+
|
|
227
|
+
if (folder !== undefined) {
|
|
228
|
+
setFolderId(folder.id);
|
|
229
|
+
}
|
|
230
|
+
} catch (folderError) {
|
|
231
|
+
await notifyError(folderError, 'Не удалось создать папку');
|
|
202
232
|
}
|
|
233
|
+
};
|
|
203
234
|
|
|
235
|
+
const handleUpdateFolder = async (id: string, appearance: FolderAppearance) => {
|
|
204
236
|
try {
|
|
205
|
-
await
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
|
|
210
253
|
await reloadFolders();
|
|
254
|
+
await enqueueSnackbar({ message: 'Папка удалена, документы остались в «Все документы»', variant: 'success' });
|
|
211
255
|
} catch (folderError) {
|
|
212
|
-
await
|
|
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
|
+
];
|
|
213
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
|
+
];
|
|
214
304
|
};
|
|
215
305
|
|
|
216
306
|
return (
|
|
217
307
|
<DocflowRoot>
|
|
218
308
|
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto' }}>
|
|
219
309
|
<div style={{ display: 'flex', flex: 1, flexWrap: 'wrap', minHeight: 0 }}>
|
|
220
|
-
<
|
|
221
|
-
aria-label="Папки"
|
|
310
|
+
<div
|
|
222
311
|
style={{
|
|
223
312
|
boxSizing: 'border-box',
|
|
224
|
-
display: 'flex',
|
|
225
313
|
flex: '1 1 200px',
|
|
226
|
-
flexDirection: 'column',
|
|
227
|
-
gap: theme.spacing[1],
|
|
228
314
|
maxWidth: '240px',
|
|
229
315
|
padding: `${theme.spacing[4]} ${theme.spacing[3]} ${theme.spacing[4]} ${theme.spacing[5]}`,
|
|
230
316
|
}}
|
|
231
317
|
>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
))}
|
|
246
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[1], marginTop: theme.spacing[3] }}>
|
|
247
|
-
<TextField placeholder="Новая папка" value={newFolderName} onChange={setNewFolderName} />
|
|
248
|
-
<Button variant="outline" size="sm" disabled={newFolderName.trim().length === 0} onClick={() => void handleCreateFolder()}>
|
|
249
|
-
Создать папку
|
|
250
|
-
</Button>
|
|
251
|
-
</div>
|
|
252
|
-
</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>
|
|
253
331
|
<div style={{ display: 'flex', flex: '999 1 420px', flexDirection: 'column', minWidth: 0 }}>
|
|
254
332
|
<Toolbar>
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
},
|
|
275
374
|
]}
|
|
276
|
-
onChange={(value) => {
|
|
277
|
-
setDocumentFilter(value);
|
|
278
|
-
setPages(1);
|
|
279
|
-
}}
|
|
280
375
|
/>
|
|
281
376
|
<div style={{ marginLeft: 'auto' }}>
|
|
282
377
|
<Button variant="solid" size="sm" loading={isCreating} onClick={() => void handleCreateDocument()}>
|
|
@@ -294,15 +389,20 @@ const DocflowDocuments = () => {
|
|
|
294
389
|
{documents.length > 0 && (
|
|
295
390
|
<DataTable
|
|
296
391
|
columns={[
|
|
297
|
-
{ key: 'name', label: 'Название', width: '30%' },
|
|
298
|
-
{ key: 'type', label: 'Тип', width: '
|
|
299
|
-
{ key: 'number', label: 'Номер', width: '
|
|
300
|
-
{ key: 'source', label: 'Где загружен', width: '
|
|
301
|
-
{ 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' },
|
|
302
397
|
]}
|
|
398
|
+
sort={sort}
|
|
399
|
+
onSortChange={(next) => {
|
|
400
|
+
setSort(next);
|
|
401
|
+
setPages(1);
|
|
402
|
+
}}
|
|
303
403
|
selectedRowId={openedDocumentId}
|
|
304
404
|
onRowClick={(documentId) => void openDocument(documentId)}
|
|
305
|
-
|
|
405
|
+
rowActions={documentActions}
|
|
306
406
|
rows={documents.map((document) => ({
|
|
307
407
|
id: document.id,
|
|
308
408
|
cells: {
|
|
@@ -325,6 +425,18 @@ const DocflowDocuments = () => {
|
|
|
325
425
|
</div>
|
|
326
426
|
</div>
|
|
327
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
|
+
)}
|
|
328
440
|
</DocflowRoot>
|
|
329
441
|
);
|
|
330
442
|
};
|