@haiiro/docuflow 0.0.5 → 0.0.6
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 +10 -10
- package/manifest.json +25 -31
- 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 +90 -300
- package/src/front-components/docflow-processes.front-component.mjs +27 -27
- package/src/front-components/docflow-processes.front-component.tsx +122 -26
- package/src/front-components/docflow-tasks.front-component.mjs +19 -19
- package/src/front-components/process-steps.front-component.mjs +18 -18
- package/src/front-components/task-form.front-component.mjs +26 -26
- package/src/logic-functions/capture-file.logic-function.mjs +15 -1
|
@@ -1,45 +1,37 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
3
|
-
import { AppPath, enqueueSnackbar,
|
|
3
|
+
import { AppPath, enqueueSnackbar, openSidePanelPage } from 'twenty-sdk/front-component';
|
|
4
4
|
import { Button } from 'twenty-ui/primitives/input';
|
|
5
|
-
import { Tag } from 'twenty-ui/primitives/data-display';
|
|
6
5
|
import { useTheme } from 'twenty-ui/theme';
|
|
7
6
|
|
|
8
|
-
import { createRecord, getErrorMessage,
|
|
7
|
+
import { createRecord, getErrorMessage, listRecords } from 'src/front/api';
|
|
9
8
|
import {
|
|
10
|
-
DOCUMENT_ORIGIN_LABELS,
|
|
11
|
-
DOCUMENT_STATUS_LABELS,
|
|
12
9
|
DOCUMENT_TYPE_LABELS,
|
|
13
10
|
formatDate,
|
|
14
|
-
formatMemberName,
|
|
15
11
|
} from 'src/front/format';
|
|
16
12
|
import { useAsync, useDebouncedValue } from 'src/front/hooks/use-async';
|
|
17
13
|
import { type DocflowDocument, type DocumentFolder } from 'src/front/types';
|
|
18
14
|
import {
|
|
19
15
|
DataTable,
|
|
20
|
-
Details,
|
|
21
16
|
DocflowRoot,
|
|
22
17
|
Message,
|
|
23
18
|
SelectField,
|
|
24
|
-
SplitView,
|
|
25
|
-
TextAreaField,
|
|
26
19
|
TextField,
|
|
27
20
|
Toolbar,
|
|
28
21
|
} from 'src/front/ui/components';
|
|
29
|
-
import { getObjectSummaries } from 'src/shared/api/object-summaries';
|
|
30
22
|
import { restAnd, restEq, restIlike, restIn, restOr } from 'src/shared/api/rest-filter.util';
|
|
31
23
|
import { DOCUMENT_OBJECT_NAME, PAGE_SIZE } from 'src/shared/docflow.constants';
|
|
32
24
|
import { logDocflow, logDocflowError } from 'src/shared/log.util';
|
|
33
25
|
import { DOCUMENTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
34
26
|
|
|
27
|
+
const LIST_POLL_INTERVAL_MS = 3000;
|
|
28
|
+
const LIST_POLL_DURATION_MS = 10 * 60 * 1000;
|
|
29
|
+
|
|
35
30
|
type DocumentFilter = 'all' | 'collected' | 'DRAFT' | 'IN_REVIEW' | 'APPROVED' | 'ARCHIVED';
|
|
36
|
-
type Panel = { kind: 'closed' } | { kind: 'upload' } | { kind: 'document'; documentId: string };
|
|
37
31
|
|
|
38
32
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
39
33
|
const FOLDERS_LIMIT = 500;
|
|
40
|
-
const COMPANY_OPTIONS_LIMIT = 8;
|
|
41
34
|
const ALL_FOLDERS = '';
|
|
42
|
-
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
43
35
|
|
|
44
36
|
// The sandbox of a front component gets only the name and size of a chosen
|
|
45
37
|
// file, never its content, so the file goes in through the standard "File"
|
|
@@ -56,7 +48,7 @@ const openDocumentCard = async (documentId: string) => {
|
|
|
56
48
|
} catch (error) {
|
|
57
49
|
logDocflowError('documents: side panel did not open', error, { documentId });
|
|
58
50
|
await enqueueSnackbar({
|
|
59
|
-
message: 'Карточка документа не
|
|
51
|
+
message: 'Карточка документа не открылась, попробуйте ещё раз',
|
|
60
52
|
variant: 'error',
|
|
61
53
|
});
|
|
62
54
|
}
|
|
@@ -87,292 +79,107 @@ const buildFilter = ({
|
|
|
87
79
|
: undefined,
|
|
88
80
|
);
|
|
89
81
|
|
|
90
|
-
const
|
|
91
|
-
value,
|
|
92
|
-
onChange,
|
|
93
|
-
}: {
|
|
94
|
-
value: { id: string; name: string } | null;
|
|
95
|
-
onChange: (company: { id: string; name: string } | null) => void;
|
|
96
|
-
}) => {
|
|
82
|
+
const DocflowDocuments = () => {
|
|
97
83
|
const theme = useTheme();
|
|
98
84
|
const [searchInput, setSearchInput] = useState('');
|
|
99
85
|
const searchQuery = useDebouncedValue(searchInput, SEARCH_DEBOUNCE_MS);
|
|
100
|
-
const
|
|
86
|
+
const [folderId, setFolderId] = useState(ALL_FOLDERS);
|
|
87
|
+
const [documentFilter, setDocumentFilter] = useState<DocumentFilter>('all');
|
|
88
|
+
const [openedDocumentId, setOpenedDocumentId] = useState<string | undefined>();
|
|
89
|
+
const [watchStartedAt, setWatchStartedAt] = useState<number | undefined>();
|
|
90
|
+
const [pages, setPages] = useState(1);
|
|
91
|
+
const [newFolderName, setNewFolderName] = useState('');
|
|
92
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
93
|
+
|
|
94
|
+
const { data: folders, reload: reloadFolders } = useAsync(
|
|
101
95
|
async () =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
).records,
|
|
110
|
-
[searchQuery],
|
|
96
|
+
(
|
|
97
|
+
await listRecords<DocumentFolder>('smbDocumentFolders', {
|
|
98
|
+
order_by: 'name[AscNullsLast]',
|
|
99
|
+
limit: FOLDERS_LIMIT,
|
|
100
|
+
})
|
|
101
|
+
).records,
|
|
102
|
+
[],
|
|
111
103
|
);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</div>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[1] }}>
|
|
126
|
-
<TextField label="Контрагент" placeholder="Начните вводить название" value={searchInput} onChange={setSearchInput} />
|
|
127
|
-
{(data ?? []).map((company) => (
|
|
128
|
-
<Button
|
|
129
|
-
key={company.id}
|
|
130
|
-
variant="ghost"
|
|
131
|
-
size="sm"
|
|
132
|
-
onClick={() => {
|
|
133
|
-
onChange({ id: company.id, name: company.name?.trim() || 'Без названия' });
|
|
134
|
-
setSearchInput('');
|
|
135
|
-
}}
|
|
136
|
-
>
|
|
137
|
-
{company.name?.trim() || 'Без названия'}
|
|
138
|
-
</Button>
|
|
139
|
-
))}
|
|
140
|
-
</div>
|
|
104
|
+
const listQuery = useMemo(
|
|
105
|
+
() => ({
|
|
106
|
+
filter: buildFilter({ folderId, documentFilter, searchQuery }),
|
|
107
|
+
order_by: 'createdAt[DescNullsLast]',
|
|
108
|
+
limit: PAGE_SIZE * pages,
|
|
109
|
+
depth: 1,
|
|
110
|
+
}),
|
|
111
|
+
[folderId, documentFilter, searchQuery, pages],
|
|
141
112
|
);
|
|
142
|
-
}
|
|
113
|
+
const { data, setData, loading, error, reload } = useAsync(
|
|
114
|
+
() => listRecords<DocflowDocument>('smbDocuments', listQuery),
|
|
115
|
+
[listQuery],
|
|
116
|
+
);
|
|
117
|
+
const documents = data?.records ?? [];
|
|
143
118
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
onCancel: () => void;
|
|
152
|
-
}) => {
|
|
153
|
-
const theme = useTheme();
|
|
154
|
-
const [name, setName] = useState('');
|
|
155
|
-
const [documentType, setDocumentType] = useState('OTHER');
|
|
156
|
-
const [status, setStatus] = useState('DRAFT');
|
|
157
|
-
const [number, setNumber] = useState('');
|
|
158
|
-
const [documentDate, setDocumentDate] = useState('');
|
|
159
|
-
const [validUntil, setValidUntil] = useState('');
|
|
160
|
-
const [description, setDescription] = useState('');
|
|
161
|
-
const [counterparty, setCounterparty] = useState<{ id: string; name: string } | null>(null);
|
|
162
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
163
|
-
const [errorMessage, setErrorMessage] = useState<string | undefined>();
|
|
119
|
+
// A document opens in its standard card in the side panel; while people
|
|
120
|
+
// edit there, the list re-reads itself quietly to show the changes.
|
|
121
|
+
const openDocument = async (documentId: string) => {
|
|
122
|
+
setOpenedDocumentId(documentId);
|
|
123
|
+
setWatchStartedAt(Date.now());
|
|
124
|
+
await openDocumentCard(documentId);
|
|
125
|
+
};
|
|
164
126
|
|
|
165
|
-
|
|
166
|
-
if (
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (watchStartedAt === undefined) {
|
|
167
129
|
return;
|
|
168
130
|
}
|
|
169
131
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
132
|
+
const interval = setInterval(() => {
|
|
133
|
+
void listRecords<DocflowDocument>('smbDocuments', listQuery)
|
|
134
|
+
.then((latest) => {
|
|
135
|
+
const signature = (list: DocflowDocument[]) =>
|
|
136
|
+
list.map(({ id, updatedAt }) => `${id}:${updatedAt ?? ''}`).join();
|
|
137
|
+
|
|
138
|
+
if (signature(latest.records) !== signature(data?.records ?? [])) {
|
|
139
|
+
setData(latest);
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
.catch(() => undefined);
|
|
143
|
+
}, LIST_POLL_INTERVAL_MS);
|
|
144
|
+
const stop = setTimeout(() => clearInterval(interval), LIST_POLL_DURATION_MS);
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
clearInterval(interval);
|
|
148
|
+
clearTimeout(stop);
|
|
149
|
+
};
|
|
150
|
+
}, [watchStartedAt, listQuery, data]);
|
|
151
|
+
|
|
152
|
+
// As "Создать" of a standard list: an empty draft in the open folder, filled
|
|
153
|
+
// in its own card in the side panel (the file goes in there too).
|
|
154
|
+
const handleCreateDocument = async () => {
|
|
155
|
+
setIsCreating(true);
|
|
156
|
+
logDocflow('documents: create', { folderId });
|
|
173
157
|
|
|
174
158
|
try {
|
|
175
159
|
const document = await createRecord<DocflowDocument>('smbDocuments', {
|
|
176
|
-
name:
|
|
177
|
-
|
|
178
|
-
status,
|
|
179
|
-
number: number.trim() || null,
|
|
180
|
-
documentDate: DATE_PATTERN.test(documentDate) ? documentDate : null,
|
|
181
|
-
validUntil: DATE_PATTERN.test(validUntil) ? validUntil : null,
|
|
182
|
-
description: description.trim() || null,
|
|
183
|
-
counterpartyId: counterparty?.id ?? null,
|
|
184
|
-
folderId: folderId === ALL_FOLDERS ? null : folderId,
|
|
160
|
+
name: '',
|
|
161
|
+
status: 'DRAFT',
|
|
185
162
|
origin: 'UPLOAD',
|
|
163
|
+
folderId: folderId === ALL_FOLDERS ? null : folderId,
|
|
186
164
|
});
|
|
187
165
|
|
|
188
166
|
if (document === undefined) {
|
|
189
167
|
throw new Error('документ создан, но сервер его не вернул');
|
|
190
168
|
}
|
|
191
169
|
|
|
192
|
-
|
|
193
|
-
await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
170
|
+
void reload();
|
|
171
|
+
await openDocument(document.id);
|
|
172
|
+
} catch (createError) {
|
|
173
|
+
logDocflowError('documents: create failed', createError);
|
|
174
|
+
await enqueueSnackbar({
|
|
175
|
+
message: getErrorMessage(createError, 'Не удалось создать документ'),
|
|
176
|
+
variant: 'error',
|
|
177
|
+
});
|
|
199
178
|
} finally {
|
|
200
|
-
|
|
179
|
+
setIsCreating(false);
|
|
201
180
|
}
|
|
202
181
|
};
|
|
203
182
|
|
|
204
|
-
return (
|
|
205
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[3] }}>
|
|
206
|
-
<TextField label="Название" value={name} onChange={setName} />
|
|
207
|
-
<SelectField
|
|
208
|
-
label="Тип"
|
|
209
|
-
value={documentType}
|
|
210
|
-
options={Object.entries(DOCUMENT_TYPE_LABELS).map(([value, label]) => ({ value, label }))}
|
|
211
|
-
onChange={setDocumentType}
|
|
212
|
-
/>
|
|
213
|
-
<SelectField
|
|
214
|
-
label="Статус"
|
|
215
|
-
value={status}
|
|
216
|
-
options={Object.entries(DOCUMENT_STATUS_LABELS).map(([value, label]) => ({ value, label }))}
|
|
217
|
-
onChange={setStatus}
|
|
218
|
-
/>
|
|
219
|
-
<TextField label="Номер" value={number} onChange={setNumber} />
|
|
220
|
-
<TextField label="Дата документа" type="date" value={documentDate} onChange={setDocumentDate} />
|
|
221
|
-
<TextField label="Действует до" type="date" value={validUntil} onChange={setValidUntil} />
|
|
222
|
-
<CompanyPicker value={counterparty} onChange={setCounterparty} />
|
|
223
|
-
<TextAreaField label="Описание" value={description} onChange={setDescription} />
|
|
224
|
-
<Message tone="hint">Файл прикрепляется в карточке документа: она откроется справа после создания.</Message>
|
|
225
|
-
{errorMessage !== undefined && <Message tone="danger">{errorMessage}</Message>}
|
|
226
|
-
<div style={{ display: 'flex', gap: theme.spacing[2] }}>
|
|
227
|
-
<Button
|
|
228
|
-
variant="solid"
|
|
229
|
-
size="sm"
|
|
230
|
-
loading={isSaving}
|
|
231
|
-
disabled={name.trim().length === 0}
|
|
232
|
-
onClick={() => void handleCreate()}
|
|
233
|
-
>
|
|
234
|
-
Создать и прикрепить файл
|
|
235
|
-
</Button>
|
|
236
|
-
<Button variant="ghost" size="sm" onClick={onCancel}>
|
|
237
|
-
Отмена
|
|
238
|
-
</Button>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const DocumentCard = ({ documentId }: { documentId: string }) => {
|
|
245
|
-
const theme = useTheme();
|
|
246
|
-
const { data: document, loading, error, reload } = useAsync(
|
|
247
|
-
() => getRecord<DocflowDocument>('smbDocuments', documentId),
|
|
248
|
-
[documentId],
|
|
249
|
-
);
|
|
250
|
-
const { data: objectSummaries } = useAsync(() => getObjectSummaries(), []);
|
|
251
|
-
|
|
252
|
-
if (document === undefined) {
|
|
253
|
-
return loading ? (
|
|
254
|
-
<Message>Загрузка…</Message>
|
|
255
|
-
) : (
|
|
256
|
-
<Message tone="danger">{getErrorMessage(error, 'Документ не найден или нет доступа.')}</Message>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const file = document.file?.[0];
|
|
261
|
-
const sourceSummary =
|
|
262
|
-
typeof document.sourceObjectName === 'string'
|
|
263
|
-
? objectSummaries?.get(document.sourceObjectName)
|
|
264
|
-
: undefined;
|
|
265
|
-
const linkStyle = { color: theme.color.blue, cursor: 'pointer' };
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[3] }}>
|
|
269
|
-
<strong style={{ fontSize: theme.font.size.lg }}>{document.name?.trim() || 'Без названия'}</strong>
|
|
270
|
-
<Details
|
|
271
|
-
rows={[
|
|
272
|
-
{ label: 'Тип', value: DOCUMENT_TYPE_LABELS[document.documentType ?? ''] },
|
|
273
|
-
{ label: 'Номер', value: document.number },
|
|
274
|
-
{ label: 'Дата', value: formatDate(document.documentDate) },
|
|
275
|
-
{ label: 'Действует до', value: formatDate(document.validUntil) },
|
|
276
|
-
{ label: 'Контрагент', value: document.counterparty?.name },
|
|
277
|
-
{
|
|
278
|
-
label: 'Статус',
|
|
279
|
-
value: document.status && <Tag color="gray">{DOCUMENT_STATUS_LABELS[document.status] ?? document.status}</Tag>,
|
|
280
|
-
},
|
|
281
|
-
{ label: 'Автор', value: formatMemberName(document.author ?? undefined) },
|
|
282
|
-
{ label: 'Добавлен', value: formatDate(document.createdAt) },
|
|
283
|
-
{ label: 'Источник', value: DOCUMENT_ORIGIN_LABELS[document.origin ?? ''] },
|
|
284
|
-
{
|
|
285
|
-
label: 'Где загружен',
|
|
286
|
-
value: sourceSummary !== undefined && typeof document.sourceRecordId === 'string' && (
|
|
287
|
-
<span
|
|
288
|
-
role="link"
|
|
289
|
-
tabIndex={0}
|
|
290
|
-
style={linkStyle}
|
|
291
|
-
onClick={() =>
|
|
292
|
-
void navigate(AppPath.RecordShowPage, {
|
|
293
|
-
objectNameSingular: sourceSummary.nameSingular,
|
|
294
|
-
objectRecordId: document.sourceRecordId as string,
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
>
|
|
298
|
-
{document.sourceRecordName ?? sourceSummary.labelSingular}
|
|
299
|
-
</span>
|
|
300
|
-
),
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
label: 'Процесс',
|
|
304
|
-
value: typeof document.workflowRunId === 'string' && (
|
|
305
|
-
<span
|
|
306
|
-
role="link"
|
|
307
|
-
tabIndex={0}
|
|
308
|
-
style={linkStyle}
|
|
309
|
-
onClick={() =>
|
|
310
|
-
void navigate(AppPath.RecordShowPage, {
|
|
311
|
-
objectNameSingular: 'workflowRun',
|
|
312
|
-
objectRecordId: document.workflowRunId as string,
|
|
313
|
-
})
|
|
314
|
-
}
|
|
315
|
-
>
|
|
316
|
-
Открыть запуск
|
|
317
|
-
</span>
|
|
318
|
-
),
|
|
319
|
-
},
|
|
320
|
-
]}
|
|
321
|
-
/>
|
|
322
|
-
{typeof document.description === 'string' && document.description.length > 0 && (
|
|
323
|
-
<div style={{ whiteSpace: 'pre-wrap' }}>{document.description}</div>
|
|
324
|
-
)}
|
|
325
|
-
{file?.url !== undefined ? (
|
|
326
|
-
<a href={file.url} target="_blank" rel="noopener noreferrer" download={file.label} style={linkStyle}>
|
|
327
|
-
Скачать {file.label}
|
|
328
|
-
</a>
|
|
329
|
-
) : (
|
|
330
|
-
<Message tone="hint">Файл не прикреплён.</Message>
|
|
331
|
-
)}
|
|
332
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: theme.spacing[2] }}>
|
|
333
|
-
<Button variant="outline" size="sm" onClick={() => void openDocumentCard(documentId)}>
|
|
334
|
-
{file === undefined ? 'Прикрепить файл' : 'Открыть карточку'}
|
|
335
|
-
</Button>
|
|
336
|
-
<Button variant="ghost" size="sm" loading={loading} onClick={() => void reload()}>
|
|
337
|
-
Обновить
|
|
338
|
-
</Button>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const DocflowDocuments = () => {
|
|
345
|
-
const theme = useTheme();
|
|
346
|
-
const [searchInput, setSearchInput] = useState('');
|
|
347
|
-
const searchQuery = useDebouncedValue(searchInput, SEARCH_DEBOUNCE_MS);
|
|
348
|
-
const [folderId, setFolderId] = useState(ALL_FOLDERS);
|
|
349
|
-
const [documentFilter, setDocumentFilter] = useState<DocumentFilter>('all');
|
|
350
|
-
const [panel, setPanel] = useState<Panel>({ kind: 'closed' });
|
|
351
|
-
const [pages, setPages] = useState(1);
|
|
352
|
-
const [newFolderName, setNewFolderName] = useState('');
|
|
353
|
-
|
|
354
|
-
const { data: folders, reload: reloadFolders } = useAsync(
|
|
355
|
-
async () =>
|
|
356
|
-
(
|
|
357
|
-
await listRecords<DocumentFolder>('smbDocumentFolders', {
|
|
358
|
-
order_by: 'name[AscNullsLast]',
|
|
359
|
-
limit: FOLDERS_LIMIT,
|
|
360
|
-
})
|
|
361
|
-
).records,
|
|
362
|
-
[],
|
|
363
|
-
);
|
|
364
|
-
const { data, loading, error, reload } = useAsync(
|
|
365
|
-
() =>
|
|
366
|
-
listRecords<DocflowDocument>('smbDocuments', {
|
|
367
|
-
filter: buildFilter({ folderId, documentFilter, searchQuery }),
|
|
368
|
-
order_by: 'createdAt[DescNullsLast]',
|
|
369
|
-
limit: PAGE_SIZE * pages,
|
|
370
|
-
depth: 1,
|
|
371
|
-
}),
|
|
372
|
-
[folderId, documentFilter, searchQuery, pages],
|
|
373
|
-
);
|
|
374
|
-
const documents = data?.records ?? [];
|
|
375
|
-
|
|
376
183
|
const handleCreateFolder = async () => {
|
|
377
184
|
const folderName = newFolderName.trim();
|
|
378
185
|
|
|
@@ -394,24 +201,7 @@ const DocflowDocuments = () => {
|
|
|
394
201
|
|
|
395
202
|
return (
|
|
396
203
|
<DocflowRoot>
|
|
397
|
-
<
|
|
398
|
-
panelTitle={panel.kind === 'upload' ? 'Новый документ' : 'Документ'}
|
|
399
|
-
onClosePanel={() => setPanel({ kind: 'closed' })}
|
|
400
|
-
panel={
|
|
401
|
-
panel.kind === 'upload' ? (
|
|
402
|
-
<DocumentUploadForm
|
|
403
|
-
folderId={folderId}
|
|
404
|
-
onCancel={() => setPanel({ kind: 'closed' })}
|
|
405
|
-
onUploaded={(documentId) => {
|
|
406
|
-
setPanel({ kind: 'document', documentId });
|
|
407
|
-
void reload();
|
|
408
|
-
}}
|
|
409
|
-
/>
|
|
410
|
-
) : panel.kind === 'document' ? (
|
|
411
|
-
<DocumentCard key={panel.documentId} documentId={panel.documentId} />
|
|
412
|
-
) : undefined
|
|
413
|
-
}
|
|
414
|
-
>
|
|
204
|
+
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0, overflowY: 'auto' }}>
|
|
415
205
|
<div style={{ display: 'flex', flex: 1, flexWrap: 'wrap', minHeight: 0 }}>
|
|
416
206
|
<nav
|
|
417
207
|
aria-label="Папки"
|
|
@@ -422,7 +212,7 @@ const DocflowDocuments = () => {
|
|
|
422
212
|
flexDirection: 'column',
|
|
423
213
|
gap: theme.spacing[1],
|
|
424
214
|
maxWidth: '240px',
|
|
425
|
-
padding: theme.spacing[
|
|
215
|
+
padding: `${theme.spacing[4]} ${theme.spacing[3]} ${theme.spacing[4]} ${theme.spacing[5]}`,
|
|
426
216
|
}}
|
|
427
217
|
>
|
|
428
218
|
{[{ id: ALL_FOLDERS, name: 'Все документы' }, ...(folders ?? [])].map((folder) => (
|
|
@@ -474,8 +264,8 @@ const DocflowDocuments = () => {
|
|
|
474
264
|
}}
|
|
475
265
|
/>
|
|
476
266
|
<div style={{ marginLeft: 'auto' }}>
|
|
477
|
-
<Button variant="solid" size="sm" onClick={() =>
|
|
478
|
-
|
|
267
|
+
<Button variant="solid" size="sm" loading={isCreating} onClick={() => void handleCreateDocument()}>
|
|
268
|
+
Создать
|
|
479
269
|
</Button>
|
|
480
270
|
</div>
|
|
481
271
|
</Toolbar>
|
|
@@ -495,8 +285,8 @@ const DocflowDocuments = () => {
|
|
|
495
285
|
{ key: 'source', label: 'Где загружен', width: '24%' },
|
|
496
286
|
{ key: 'createdAt', label: 'Добавлен' },
|
|
497
287
|
]}
|
|
498
|
-
selectedRowId={
|
|
499
|
-
onRowClick={(documentId) =>
|
|
288
|
+
selectedRowId={openedDocumentId}
|
|
289
|
+
onRowClick={(documentId) => void openDocument(documentId)}
|
|
500
290
|
rows={documents.map((document) => ({
|
|
501
291
|
id: document.id,
|
|
502
292
|
cells: {
|
|
@@ -518,7 +308,7 @@ const DocflowDocuments = () => {
|
|
|
518
308
|
)}
|
|
519
309
|
</div>
|
|
520
310
|
</div>
|
|
521
|
-
</
|
|
311
|
+
</div>
|
|
522
312
|
</DocflowRoot>
|
|
523
313
|
);
|
|
524
314
|
};
|