@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,7 +1,7 @@
|
|
|
1
1
|
import { 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, IconEye, IconRestore, IconTrash } from 'twenty-ui/icon';
|
|
5
5
|
import { Tag } from 'twenty-ui/primitives/data-display';
|
|
6
6
|
import { useTheme } from 'twenty-ui/theme';
|
|
7
7
|
|
|
@@ -19,9 +19,12 @@ import {
|
|
|
19
19
|
SplitView,
|
|
20
20
|
TextAreaField,
|
|
21
21
|
TextField,
|
|
22
|
+
type TableSort,
|
|
23
|
+
toRestOrderBy,
|
|
22
24
|
Toolbar,
|
|
23
25
|
} from 'src/front/ui/components';
|
|
24
26
|
import { FormFieldsBuilder } from 'src/front/ui/form-fields-builder';
|
|
27
|
+
import { Button, SearchWithFilters } from 'src/front/ui/kit';
|
|
25
28
|
import { TaskWorkPanel } from 'src/front/ui/task-work-panel';
|
|
26
29
|
import {
|
|
27
30
|
restAnd,
|
|
@@ -39,23 +42,26 @@ import { getFormFieldsError } from 'src/shared/form/form-fields.util';
|
|
|
39
42
|
import { TASKS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
40
43
|
|
|
41
44
|
type TaskFilter = 'open' | 'mine' | 'overdue' | 'done' | 'archived';
|
|
42
|
-
type TaskSort = 'newest' | 'oldest' | 'dueSoon' | 'nameAsc';
|
|
43
45
|
type Panel = { kind: 'closed' } | { kind: 'create' } | { kind: 'task'; taskId: string };
|
|
44
46
|
|
|
45
47
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
46
48
|
const NO_ASSIGNEE = '';
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
50
|
+
const TASK_FILTER_OPTIONS: { value: TaskFilter; label: string }[] = [
|
|
51
|
+
{ value: 'open', label: 'Открытые' },
|
|
52
|
+
{ value: 'mine', label: 'Мои' },
|
|
53
|
+
{ value: 'overdue', label: 'Просроченные' },
|
|
54
|
+
{ value: 'done', label: 'Выполненные' },
|
|
55
|
+
{ value: 'archived', label: 'Архив (удалённые)' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const DEFAULT_SORT: TableSort = { key: 'createdAt', direction: 'desc' };
|
|
54
59
|
|
|
55
60
|
const OPEN_FILTER = restOr(restNeq('status', TASK_DONE_STATUS), restIsNull('status'));
|
|
56
61
|
|
|
57
62
|
// An archived task opens in its standard card: Twenty shows it as deleted
|
|
58
63
|
// there and offers to restore it.
|
|
64
|
+
// A task opens in its standard card too; a deleted one shows "Восстановить" there
|
|
59
65
|
const openArchivedTask = (taskId: string) =>
|
|
60
66
|
openSidePanelPage({
|
|
61
67
|
to: AppPath.RecordShowPage,
|
|
@@ -184,7 +190,7 @@ const DocflowTasks = () => {
|
|
|
184
190
|
const [searchInput, setSearchInput] = useState('');
|
|
185
191
|
const searchQuery = useDebouncedValue(searchInput, SEARCH_DEBOUNCE_MS);
|
|
186
192
|
const [taskFilter, setTaskFilter] = useState<TaskFilter>('open');
|
|
187
|
-
const [sort, setSort] = useState<
|
|
193
|
+
const [sort, setSort] = useState<TableSort>(DEFAULT_SORT);
|
|
188
194
|
const [panel, setPanel] = useState<Panel>({ kind: 'closed' });
|
|
189
195
|
const [pages, setPages] = useState(1);
|
|
190
196
|
// Fixed for the life of the list, so "overdue" does not refetch by itself.
|
|
@@ -194,7 +200,7 @@ const DocflowTasks = () => {
|
|
|
194
200
|
() =>
|
|
195
201
|
listRecords<DocflowTask>('tasks', {
|
|
196
202
|
filter: buildFilter({ taskFilter, searchQuery, currentMemberId: currentMember?.id, now }),
|
|
197
|
-
order_by:
|
|
203
|
+
order_by: toRestOrderBy(sort),
|
|
198
204
|
limit: PAGE_SIZE * pages,
|
|
199
205
|
}),
|
|
200
206
|
[taskFilter, searchQuery, currentMember?.id, sort, pages, now],
|
|
@@ -233,43 +239,30 @@ const DocflowTasks = () => {
|
|
|
233
239
|
}
|
|
234
240
|
>
|
|
235
241
|
<Toolbar>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/>
|
|
244
|
-
</div>
|
|
245
|
-
<SelectField<TaskFilter>
|
|
246
|
-
variant="bare"
|
|
247
|
-
value={taskFilter}
|
|
248
|
-
options={[
|
|
249
|
-
{ value: 'open', label: 'Открытые' },
|
|
250
|
-
{ value: 'mine', label: 'Мои' },
|
|
251
|
-
{ value: 'overdue', label: 'Просроченные' },
|
|
252
|
-
{ value: 'done', label: 'Выполненные' },
|
|
253
|
-
{ value: 'archived', label: 'Архив' },
|
|
254
|
-
]}
|
|
255
|
-
onChange={(value) => {
|
|
256
|
-
setTaskFilter(value);
|
|
242
|
+
<SearchWithFilters
|
|
243
|
+
value={searchInput}
|
|
244
|
+
onChange={setSearchInput}
|
|
245
|
+
placeholder="Поиск заданий"
|
|
246
|
+
activeFilterCount={taskFilter === 'open' ? 0 : 1}
|
|
247
|
+
onResetFilters={() => {
|
|
248
|
+
setTaskFilter('open');
|
|
257
249
|
setPages(1);
|
|
258
250
|
}}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
251
|
+
filters={[
|
|
252
|
+
{
|
|
253
|
+
key: 'tasks',
|
|
254
|
+
title: 'Показать',
|
|
255
|
+
value: taskFilter,
|
|
256
|
+
options: TASK_FILTER_OPTIONS,
|
|
257
|
+
onChange: (value) => {
|
|
258
|
+
setTaskFilter(value as TaskFilter);
|
|
259
|
+
setPages(1);
|
|
260
|
+
},
|
|
261
|
+
},
|
|
268
262
|
]}
|
|
269
|
-
onChange={setSort}
|
|
270
263
|
/>
|
|
271
264
|
<div style={{ marginLeft: 'auto' }}>
|
|
272
|
-
<Button variant="
|
|
265
|
+
<Button variant="solid" size="sm" onClick={() => setPanel({ kind: 'create' })}>
|
|
273
266
|
Создать
|
|
274
267
|
</Button>
|
|
275
268
|
</div>
|
|
@@ -284,13 +277,30 @@ const DocflowTasks = () => {
|
|
|
284
277
|
{tasks.length > 0 && (
|
|
285
278
|
<DataTable
|
|
286
279
|
columns={[
|
|
287
|
-
{ key: 'title', label: 'Задание', width: '
|
|
288
|
-
{ key: 'assignee', label: 'Исполнитель', width: '
|
|
289
|
-
{ key: 'dueAt', label: 'Срок', width: '
|
|
290
|
-
{ key: 'status', label: 'Статус' },
|
|
280
|
+
{ key: 'title', label: 'Задание', width: '38%', sortKey: 'title' },
|
|
281
|
+
{ key: 'assignee', label: 'Исполнитель', width: '22%' },
|
|
282
|
+
{ key: 'dueAt', label: 'Срок', width: '16%', sortKey: 'dueAt' },
|
|
283
|
+
{ key: 'status', label: 'Статус', sortKey: 'status' },
|
|
291
284
|
]}
|
|
285
|
+
sort={sort}
|
|
286
|
+
onSortChange={setSort}
|
|
292
287
|
selectedRowId={panel.kind === 'task' ? panel.taskId : undefined}
|
|
293
|
-
|
|
288
|
+
rowActions={(taskId) =>
|
|
289
|
+
taskFilter === 'archived'
|
|
290
|
+
? [{ key: 'open', label: 'Открыть и восстановить', icon: IconRestore, onSelect: () => void openArchivedTask(taskId) }]
|
|
291
|
+
: [
|
|
292
|
+
{ key: 'open', label: 'Открыть', icon: IconEye, onSelect: () => setPanel({ kind: 'task', taskId }) },
|
|
293
|
+
{ key: 'card', label: 'Карточка задачи', icon: IconExternalLink, onSelect: () => void openArchivedTask(taskId) },
|
|
294
|
+
{
|
|
295
|
+
key: 'delete',
|
|
296
|
+
label: 'Удалить',
|
|
297
|
+
confirmLabel: 'Точно удалить?',
|
|
298
|
+
icon: IconTrash,
|
|
299
|
+
tone: 'danger',
|
|
300
|
+
onSelect: () => void handleDeleteTask(taskId),
|
|
301
|
+
},
|
|
302
|
+
]
|
|
303
|
+
}
|
|
294
304
|
onRowClick={(taskId) =>
|
|
295
305
|
taskFilter === 'archived'
|
|
296
306
|
? void openArchivedTask(taskId)
|