@haiiro/docuflow 0.0.1 → 0.0.3

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.
Files changed (136) hide show
  1. package/{.twenty/output/manifest.json → manifest.json} +9 -9
  2. package/package.json +15 -3
  3. package/{.twenty/output/src → src}/front-components/docflow-documents.front-component.mjs +24 -24
  4. package/src/front-components/docflow-documents.front-component.tsx +21 -6
  5. package/{.twenty/output/src → src}/front-components/docflow-processes.front-component.mjs +18 -18
  6. package/{.twenty/output/src → src}/front-components/docflow-tasks.front-component.mjs +18 -18
  7. package/{.twenty/output/src → src}/front-components/process-steps.front-component.mjs +19 -19
  8. package/src/front-components/process-steps.front-component.tsx +6 -1
  9. package/{.twenty/output/src → src}/front-components/task-form.front-component.mjs +24 -24
  10. package/{.twenty/output/src → src}/logic-functions/launch-process.logic-function.mjs +14 -0
  11. package/src/logic-functions/launch-process.logic-function.ts +5 -0
  12. package/{.twenty/output/src → src}/logic-functions/save-launch-fields.logic-function.mjs +14 -0
  13. package/src/logic-functions/save-launch-fields.logic-function.ts +5 -0
  14. package/.gitignore +0 -43
  15. package/.nvmrc +0 -1
  16. package/.oxlintrc.json +0 -37
  17. package/.twenty/output/README.md +0 -128
  18. package/.twenty/output/package.json +0 -49
  19. package/.twenty/output/public/cover.svg +0 -15
  20. package/.twenty/output/public/logo.svg +0 -9
  21. package/.twenty/output/src/front-components/docflow-documents.front-component.tsx +0 -516
  22. package/.twenty/output/src/front-components/docflow-processes.front-component.tsx +0 -266
  23. package/.twenty/output/src/front-components/docflow-tasks.front-component.tsx +0 -303
  24. package/.twenty/output/src/front-components/process-steps.front-component.tsx +0 -286
  25. package/.twenty/output/src/front-components/task-form.front-component.tsx +0 -29
  26. package/.twenty/output/src/logic-functions/bitrix-app.logic-function.ts +0 -143
  27. package/.twenty/output/src/logic-functions/bitrix-event.logic-function.ts +0 -54
  28. package/.twenty/output/src/logic-functions/bitrix-install.logic-function.ts +0 -81
  29. package/.twenty/output/src/logic-functions/capture-file.logic-function.ts +0 -32
  30. package/.twenty/output/src/logic-functions/complete-task.logic-function.ts +0 -46
  31. package/.twenty/output/src/logic-functions/hand-out-form-step.logic-function.ts +0 -30
  32. package/.twenty/output/src/logic-functions/health-check.ts +0 -31
  33. package/.twenty/output/src/logic-functions/launch-parameters-step.logic-function.ts +0 -33
  34. package/.twenty/output/src/logic-functions/launch-process.logic-function.ts +0 -96
  35. package/.twenty/output/src/logic-functions/notify-bitrix-assignee.logic-function.ts +0 -85
  36. package/.twenty/output/src/logic-functions/on-attachment-created.logic-function.ts +0 -67
  37. package/.twenty/output/src/logic-functions/on-task-updated.logic-function.ts +0 -71
  38. package/.twenty/output/src/logic-functions/on-workflow-run-updated.logic-function.ts +0 -70
  39. package/.twenty/output/src/logic-functions/save-launch-fields.logic-function.ts +0 -73
  40. package/.twenty/output/src/logic-functions/save-step-settings.logic-function.ts +0 -90
  41. package/.twenty/output/src/logic-functions/save-task-form.logic-function.ts +0 -62
  42. package/.twenty/output/yarn.lock +0 -3292
  43. package/.yarn/install-state.gz +0 -0
  44. package/.yarnrc.yml +0 -1
  45. package/dist/tsconfig.spec.tsbuildinfo +0 -1
  46. package/src/__tests__/api-and-bitrix.test.ts +0 -89
  47. package/src/__tests__/form-fields.test.ts +0 -73
  48. package/src/__tests__/launch-parameters.test.ts +0 -75
  49. package/src/__tests__/step-settings.test.ts +0 -39
  50. package/src/__tests__/workflow-run.test.ts +0 -113
  51. package/src/application-config.ts +0 -58
  52. package/src/fields/company-documents.field.ts +0 -29
  53. package/src/fields/document-author.field.ts +0 -32
  54. package/src/fields/document-counterparty.field.ts +0 -32
  55. package/src/fields/document-folder-child-folders.field.ts +0 -23
  56. package/src/fields/document-folder-documents.field.ts +0 -24
  57. package/src/fields/document-folder-parent-folder.field.ts +0 -31
  58. package/src/fields/document-folder.field.ts +0 -32
  59. package/src/fields/document-responsible.field.ts +0 -32
  60. package/src/fields/member-authored-documents.field.ts +0 -29
  61. package/src/fields/member-responsible-documents.field.ts +0 -29
  62. package/src/fields/task-form-schema.field.ts +0 -21
  63. package/src/fields/task-form-values.field.ts +0 -21
  64. package/src/fields/task-instruction.field.ts +0 -21
  65. package/src/fields/task-section.field.ts +0 -36
  66. package/src/fields/task-workflow-run-id.field.ts +0 -21
  67. package/src/fields/task-workflow-step-id.field.ts +0 -21
  68. package/src/fields/task-workflow-thread-id.field.ts +0 -21
  69. package/src/fields/workflow-launch-fields.field.ts +0 -21
  70. package/src/fields/workflow-section.field.ts +0 -36
  71. package/src/front/api.ts +0 -56
  72. package/src/front/format.ts +0 -60
  73. package/src/front/hooks/use-async.ts +0 -54
  74. package/src/front/hooks/use-workspace-members.ts +0 -22
  75. package/src/front/types.ts +0 -48
  76. package/src/front/ui/components.tsx +0 -436
  77. package/src/front/ui/form-field-inputs.tsx +0 -187
  78. package/src/front/ui/form-fields-builder.tsx +0 -183
  79. package/src/front/ui/task-work-panel.tsx +0 -218
  80. package/src/navigation-menu-items/docflow.navigation-menu-item.ts +0 -15
  81. package/src/objects/document-folder.object.ts +0 -80
  82. package/src/objects/document.object.ts +0 -312
  83. package/src/page-layouts/docflow.page-layout.ts +0 -86
  84. package/src/page-layouts/task-docflow.page-layout-tab.ts +0 -36
  85. package/src/page-layouts/workflow-steps.page-layout-tab.ts +0 -37
  86. package/src/roles/default-role.ts +0 -21
  87. package/src/roles/docflow-member.role.ts +0 -23
  88. package/src/server/bitrix-page.ts +0 -52
  89. package/src/server/bitrix-portal.ts +0 -67
  90. package/src/server/document-capture.ts +0 -102
  91. package/src/server/http-response.ts +0 -18
  92. package/src/server/launch-step.ts +0 -166
  93. package/src/server/process-tasks.ts +0 -116
  94. package/src/server/records.ts +0 -132
  95. package/src/server/task-completion.ts +0 -100
  96. package/src/server/workflow-engine.ts +0 -105
  97. package/src/shared/api/core-graphql.util.ts +0 -98
  98. package/src/shared/api/object-summaries.ts +0 -79
  99. package/src/shared/api/rest-filter.util.ts +0 -37
  100. package/src/shared/api/rest-response.util.ts +0 -43
  101. package/src/shared/bitrix/bitrix-client.ts +0 -114
  102. package/src/shared/bitrix/bitrix.type.ts +0 -25
  103. package/src/shared/bitrix/bitrix.util.ts +0 -139
  104. package/src/shared/docflow.constants.ts +0 -31
  105. package/src/shared/documents/attachment-target.util.ts +0 -64
  106. package/src/shared/engine.ts +0 -11
  107. package/src/shared/form/form-field-builder.util.ts +0 -122
  108. package/src/shared/form/form-field.type.ts +0 -23
  109. package/src/shared/form/form-fields.util.ts +0 -138
  110. package/src/shared/form/step-settings.util.ts +0 -104
  111. package/src/shared/workflow/launch-step.util.ts +0 -56
  112. package/src/shared/workflow/run-target-record.util.ts +0 -70
  113. package/src/shared/workflow/workflow-run.type.ts +0 -35
  114. package/src/shared/workflow/workflow-run.util.ts +0 -174
  115. package/src/universal-identifiers.ts +0 -199
  116. package/src/views/all-document-folders.view.ts +0 -50
  117. package/src/views/all-documents.view.ts +0 -91
  118. package/src/views/documents-by-status.view.ts +0 -76
  119. package/tsconfig.json +0 -37
  120. package/tsconfig.spec.json +0 -9
  121. package/vitest.unit.config.ts +0 -14
  122. /package/{.twenty/output/front-component-shared-dependencies.mjs → front-component-shared-dependencies.mjs} +0 -0
  123. /package/{.twenty/output/src → src}/logic-functions/bitrix-app.logic-function.mjs +0 -0
  124. /package/{.twenty/output/src → src}/logic-functions/bitrix-event.logic-function.mjs +0 -0
  125. /package/{.twenty/output/src → src}/logic-functions/bitrix-install.logic-function.mjs +0 -0
  126. /package/{.twenty/output/src → src}/logic-functions/capture-file.logic-function.mjs +0 -0
  127. /package/{.twenty/output/src → src}/logic-functions/complete-task.logic-function.mjs +0 -0
  128. /package/{.twenty/output/src → src}/logic-functions/hand-out-form-step.logic-function.mjs +0 -0
  129. /package/{.twenty/output/src → src}/logic-functions/health-check.mjs +0 -0
  130. /package/{.twenty/output/src → src}/logic-functions/launch-parameters-step.logic-function.mjs +0 -0
  131. /package/{.twenty/output/src → src}/logic-functions/notify-bitrix-assignee.logic-function.mjs +0 -0
  132. /package/{.twenty/output/src → src}/logic-functions/on-attachment-created.logic-function.mjs +0 -0
  133. /package/{.twenty/output/src → src}/logic-functions/on-task-updated.logic-function.mjs +0 -0
  134. /package/{.twenty/output/src → src}/logic-functions/on-workflow-run-updated.logic-function.mjs +0 -0
  135. /package/{.twenty/output/src → src}/logic-functions/save-step-settings.logic-function.mjs +0 -0
  136. /package/{.twenty/output/src → src}/logic-functions/save-task-form.logic-function.mjs +0 -0
@@ -1,436 +0,0 @@
1
- import { type CSSProperties, type ReactNode, useId } from 'react';
2
- import { Button } from 'twenty-ui/primitives/input';
3
- import { useTheme } from 'twenty-ui/theme';
4
-
5
- // Every style is inline: the host drops <style> elements of front components,
6
- // so class names would style nothing.
7
- export const DocflowRoot = ({ children }: { children: ReactNode }) => {
8
- const theme = useTheme();
9
-
10
- return (
11
- <div
12
- style={{
13
- color: theme.font.color.primary,
14
- display: 'flex',
15
- flexDirection: 'column',
16
- fontFamily: 'inherit',
17
- fontSize: theme.font.size.md,
18
- height: '100%',
19
- minHeight: 0,
20
- }}
21
- >
22
- {children}
23
- </div>
24
- );
25
- };
26
-
27
- export const Toolbar = ({ children }: { children: ReactNode }) => {
28
- const theme = useTheme();
29
-
30
- return (
31
- <div
32
- style={{
33
- alignItems: 'center',
34
- display: 'flex',
35
- flexWrap: 'wrap',
36
- gap: theme.spacing[2],
37
- padding: `${theme.spacing[2]} ${theme.spacing[4]}`,
38
- }}
39
- >
40
- {children}
41
- </div>
42
- );
43
- };
44
-
45
- type InputVariant = 'outlined' | 'bare';
46
-
47
- const useInputStyle = (variant: InputVariant): CSSProperties => {
48
- const theme = useTheme();
49
-
50
- return {
51
- background: theme.background.transparent.lighter,
52
- border: variant === 'bare' ? 'none' : `1px solid ${theme.border.color.medium}`,
53
- borderRadius: theme.border.radius.sm,
54
- boxSizing: 'border-box',
55
- color: theme.font.color.primary,
56
- fontFamily: 'inherit',
57
- fontSize: theme.font.size.md,
58
- minHeight: theme.spacing[8],
59
- outline: 'none',
60
- padding: `0 ${theme.spacing[2]}`,
61
- width: '100%',
62
- };
63
- };
64
-
65
- export const Labeled = ({
66
- label,
67
- children,
68
- htmlFor,
69
- }: {
70
- label: string;
71
- children: ReactNode;
72
- htmlFor?: string;
73
- }) => {
74
- const theme = useTheme();
75
-
76
- return (
77
- <label
78
- htmlFor={htmlFor}
79
- style={{
80
- color: theme.font.color.tertiary,
81
- display: 'flex',
82
- flexDirection: 'column',
83
- fontSize: theme.font.size.xs,
84
- fontWeight: theme.font.weight.semiBold,
85
- gap: theme.spacing[1],
86
- }}
87
- >
88
- {label}
89
- {children}
90
- </label>
91
- );
92
- };
93
-
94
- export const TextField = ({
95
- label,
96
- value,
97
- onChange,
98
- type = 'text',
99
- placeholder,
100
- disabled = false,
101
- variant = 'outlined',
102
- }: {
103
- label?: string;
104
- value: string;
105
- onChange: (value: string) => void;
106
- type?: 'text' | 'number' | 'date' | 'search';
107
- placeholder?: string;
108
- disabled?: boolean;
109
- variant?: InputVariant;
110
- }) => {
111
- const inputId = useId();
112
- const style = useInputStyle(variant);
113
- const input = (
114
- <input
115
- id={inputId}
116
- type={type}
117
- value={value}
118
- placeholder={placeholder}
119
- disabled={disabled}
120
- aria-label={label ?? placeholder}
121
- onChange={(event) => onChange(event.target.value)}
122
- style={style}
123
- />
124
- );
125
-
126
- return label === undefined ? input : <Labeled label={label} htmlFor={inputId}>{input}</Labeled>;
127
- };
128
-
129
- export const TextAreaField = ({
130
- label,
131
- value,
132
- onChange,
133
- rows = 3,
134
- disabled = false,
135
- placeholder,
136
- }: {
137
- label: string;
138
- value: string;
139
- onChange: (value: string) => void;
140
- rows?: number;
141
- disabled?: boolean;
142
- placeholder?: string;
143
- }) => {
144
- const inputId = useId();
145
- const style = useInputStyle('outlined');
146
- const theme = useTheme();
147
-
148
- return (
149
- <Labeled label={label} htmlFor={inputId}>
150
- <textarea
151
- id={inputId}
152
- rows={rows}
153
- value={value}
154
- disabled={disabled}
155
- placeholder={placeholder}
156
- onChange={(event) => onChange(event.target.value)}
157
- style={{ ...style, padding: theme.spacing[2], resize: 'vertical' }}
158
- />
159
- </Labeled>
160
- );
161
- };
162
-
163
- // A native select: the sandbox cannot render popups into the page, so the
164
- // browser's own list is used.
165
- export const SelectField = <TValue extends string>({
166
- label,
167
- value,
168
- options,
169
- onChange,
170
- disabled = false,
171
- variant = 'outlined',
172
- }: {
173
- label?: string;
174
- value: TValue;
175
- options: { value: TValue; label: string }[];
176
- onChange: (value: TValue) => void;
177
- disabled?: boolean;
178
- variant?: InputVariant;
179
- }) => {
180
- const inputId = useId();
181
- const style = useInputStyle(variant);
182
- const select = (
183
- <select
184
- id={inputId}
185
- value={value}
186
- disabled={disabled}
187
- aria-label={label}
188
- onChange={(event) => {
189
- const option = options.find((candidate) => candidate.value === event.target.value);
190
-
191
- if (option !== undefined) {
192
- onChange(option.value);
193
- }
194
- }}
195
- style={{ ...style, width: label === undefined ? 'auto' : '100%' }}
196
- >
197
- {options.map((option) => (
198
- <option key={option.value} value={option.value}>
199
- {option.label}
200
- </option>
201
- ))}
202
- </select>
203
- );
204
-
205
- return label === undefined ? select : <Labeled label={label} htmlFor={inputId}>{select}</Labeled>;
206
- };
207
-
208
- export const CheckboxField = ({
209
- label,
210
- checked,
211
- onChange,
212
- disabled = false,
213
- }: {
214
- label: string;
215
- checked: boolean;
216
- onChange: (checked: boolean) => void;
217
- disabled?: boolean;
218
- }) => {
219
- const theme = useTheme();
220
-
221
- return (
222
- <label
223
- style={{
224
- alignItems: 'center',
225
- color: theme.font.color.secondary,
226
- display: 'flex',
227
- fontSize: theme.font.size.sm,
228
- gap: theme.spacing[2],
229
- }}
230
- >
231
- <input
232
- type="checkbox"
233
- checked={checked}
234
- disabled={disabled}
235
- onChange={(event) => onChange(event.target.checked)}
236
- />
237
- {label}
238
- </label>
239
- );
240
- };
241
-
242
- export const Message = ({ children, tone = 'muted' }: { children: ReactNode; tone?: 'muted' | 'danger' | 'hint' }) => {
243
- const theme = useTheme();
244
-
245
- return (
246
- <div
247
- role={tone === 'danger' ? 'alert' : undefined}
248
- style={{
249
- color: tone === 'danger' ? theme.font.color.danger : theme.font.color.tertiary,
250
- fontSize: theme.font.size.sm,
251
- padding: tone === 'muted' ? theme.spacing[6] : 0,
252
- textAlign: tone === 'muted' ? 'center' : 'left',
253
- }}
254
- >
255
- {children}
256
- </div>
257
- );
258
- };
259
-
260
- export const SectionTitle = ({ children, action }: { children: ReactNode; action?: ReactNode }) => {
261
- const theme = useTheme();
262
-
263
- return (
264
- <div style={{ alignItems: 'center', display: 'flex', gap: theme.spacing[2], justifyContent: 'space-between' }}>
265
- <span style={{ color: theme.font.color.tertiary, fontSize: theme.font.size.sm, fontWeight: theme.font.weight.medium }}>
266
- {children}
267
- </span>
268
- {action}
269
- </div>
270
- );
271
- };
272
-
273
- // A variable a later step of a process writes to read a value.
274
- export const VariableChip = ({ children }: { children: ReactNode }) => {
275
- const theme = useTheme();
276
-
277
- return (
278
- <code
279
- style={{
280
- background: theme.background.transparent.light,
281
- borderRadius: theme.border.radius.sm,
282
- color: theme.font.color.secondary,
283
- fontSize: theme.font.size.xs,
284
- overflowWrap: 'anywhere',
285
- padding: `1px ${theme.spacing[1]}`,
286
- }}
287
- >
288
- {children}
289
- </code>
290
- );
291
- };
292
-
293
- export type TableColumn = { key: string; label: string; width?: string };
294
-
295
- export const DataTable = ({
296
- columns,
297
- rows,
298
- selectedRowId,
299
- onRowClick,
300
- }: {
301
- columns: TableColumn[];
302
- rows: { id: string; cells: Record<string, ReactNode> }[];
303
- selectedRowId?: string;
304
- onRowClick: (rowId: string) => void;
305
- }) => {
306
- const theme = useTheme();
307
- const cellStyle: CSSProperties = {
308
- overflow: 'hidden',
309
- padding: `${theme.spacing[2]} ${theme.spacing[4]}`,
310
- textAlign: 'left',
311
- textOverflow: 'ellipsis',
312
- whiteSpace: 'nowrap',
313
- };
314
-
315
- return (
316
- <table style={{ borderCollapse: 'collapse', tableLayout: 'fixed', width: '100%' }}>
317
- <thead>
318
- <tr>
319
- {columns.map((column) => (
320
- <th
321
- key={column.key}
322
- scope="col"
323
- style={{
324
- ...cellStyle,
325
- color: theme.font.color.tertiary,
326
- fontSize: theme.font.size.sm,
327
- fontWeight: theme.font.weight.medium,
328
- width: column.width,
329
- }}
330
- >
331
- {column.label}
332
- </th>
333
- ))}
334
- </tr>
335
- </thead>
336
- <tbody>
337
- {rows.map((row) => (
338
- <tr
339
- key={row.id}
340
- tabIndex={0}
341
- aria-selected={row.id === selectedRowId}
342
- onClick={() => onRowClick(row.id)}
343
- onKeyDown={(event) => {
344
- if (event.key === 'Enter') {
345
- onRowClick(row.id);
346
- }
347
- }}
348
- style={{
349
- background: row.id === selectedRowId ? theme.background.transparent.light : 'transparent',
350
- borderTop: `1px solid ${theme.border.color.light}`,
351
- cursor: 'pointer',
352
- }}
353
- >
354
- {columns.map((column) => (
355
- <td key={column.key} style={cellStyle}>
356
- {row.cells[column.key]}
357
- </td>
358
- ))}
359
- </tr>
360
- ))}
361
- </tbody>
362
- </table>
363
- );
364
- };
365
-
366
- // The list and its side panel share a row while the widget is wide enough;
367
- // on a narrow one the panel wraps under the list and takes its width.
368
- export const SplitView = ({
369
- children,
370
- panel,
371
- panelTitle,
372
- onClosePanel,
373
- }: {
374
- children: ReactNode;
375
- panel?: ReactNode;
376
- panelTitle?: string;
377
- onClosePanel: () => void;
378
- }) => {
379
- const theme = useTheme();
380
-
381
- return (
382
- <div style={{ display: 'flex', flex: 1, flexWrap: 'wrap', minHeight: 0, overflowY: 'auto' }}>
383
- <div style={{ display: 'flex', flex: '999 1 480px', flexDirection: 'column', minWidth: 0 }}>
384
- {children}
385
- </div>
386
- {panel !== undefined && (
387
- <aside
388
- aria-label={panelTitle}
389
- style={{
390
- background: theme.background.primary,
391
- borderLeft: `1px solid ${theme.border.color.light}`,
392
- boxSizing: 'border-box',
393
- display: 'flex',
394
- flex: '1 1 380px',
395
- flexDirection: 'column',
396
- gap: theme.spacing[3],
397
- minWidth: 0,
398
- padding: `${theme.spacing[3]} ${theme.spacing[4]} ${theme.spacing[4]}`,
399
- }}
400
- >
401
- <div style={{ alignItems: 'center', display: 'flex', gap: theme.spacing[2], justifyContent: 'space-between' }}>
402
- <strong style={{ fontSize: theme.font.size.lg }}>{panelTitle}</strong>
403
- <Button variant="ghost" size="sm" onClick={onClosePanel}>
404
- Закрыть
405
- </Button>
406
- </div>
407
- {panel}
408
- </aside>
409
- )}
410
- </div>
411
- );
412
- };
413
-
414
- export const Details = ({ rows }: { rows: { label: string; value: ReactNode }[] }) => {
415
- const theme = useTheme();
416
-
417
- return (
418
- <dl
419
- style={{
420
- columnGap: theme.spacing[3],
421
- display: 'grid',
422
- fontSize: theme.font.size.sm,
423
- gridTemplateColumns: 'max-content minmax(0, 1fr)',
424
- margin: 0,
425
- rowGap: theme.spacing[1],
426
- }}
427
- >
428
- {rows
429
- .filter(({ value }) => value !== null && value !== undefined && value !== '' && value !== false)
430
- .flatMap(({ label, value }) => [
431
- <dt key={`${label}-term`} style={{ color: theme.font.color.tertiary }}>{label}</dt>,
432
- <dd key={`${label}-value`} style={{ margin: 0, overflowWrap: 'anywhere' }}>{value}</dd>,
433
- ])}
434
- </dl>
435
- );
436
- };
@@ -1,187 +0,0 @@
1
- import { useTheme } from 'twenty-ui/theme';
2
-
3
- import { listRecords } from 'src/front/api';
4
- import { useAsync } from 'src/front/hooks/use-async';
5
- import { Labeled, SelectField, TextField } from 'src/front/ui/components';
6
- import { getFieldOptions, getObjectSummaries } from 'src/shared/api/object-summaries';
7
- import { formatRecordLabel } from 'src/shared/documents/attachment-target.util';
8
- import { type FormField, type FormValues } from 'src/shared/form/form-field.type';
9
- import { getFillableFormFields, isFormFieldRequired } from 'src/shared/form/form-fields.util';
10
-
11
- const RECORD_OPTIONS_LIMIT = 100;
12
- const NO_VALUE = '';
13
-
14
- const readRecordId = (value: unknown): string =>
15
- typeof value === 'object' && value !== null && typeof (value as { id?: unknown }).id === 'string'
16
- ? (value as { id: string }).id
17
- : NO_VALUE;
18
-
19
- const RecordInput = ({
20
- field,
21
- value,
22
- onChange,
23
- disabled,
24
- }: FieldInputProps) => {
25
- const objectName = field.settings?.objectName;
26
- const { data: options, error } = useAsync(async () => {
27
- if (typeof objectName !== 'string') {
28
- return [];
29
- }
30
-
31
- const summary = (await getObjectSummaries()).get(objectName);
32
-
33
- if (summary === undefined) {
34
- return [];
35
- }
36
-
37
- const { records } = await listRecords<Record<string, unknown>>(summary.namePlural, {
38
- limit: RECORD_OPTIONS_LIMIT,
39
- });
40
-
41
- return records.map((record) => ({
42
- value: String(record.id),
43
- label:
44
- formatRecordLabel(
45
- summary.labelFieldName !== undefined ? record[summary.labelFieldName] : undefined,
46
- ) ?? String(record.id),
47
- }));
48
- }, [objectName]);
49
-
50
- return (
51
- <SelectField
52
- label={field.label}
53
- value={readRecordId(value)}
54
- disabled={disabled || error !== undefined}
55
- options={[{ value: NO_VALUE, label: 'Не выбрано' }, ...(options ?? [])]}
56
- onChange={(recordId) => onChange(recordId === NO_VALUE ? null : { id: recordId })}
57
- />
58
- );
59
- };
60
-
61
- const SelectInput = ({ field, value, onChange, disabled }: FieldInputProps) => {
62
- const selectedFieldId = field.settings?.selectedFieldId;
63
- const { data: options } = useAsync(
64
- async () => (typeof selectedFieldId === 'string' ? getFieldOptions(selectedFieldId) : []),
65
- [selectedFieldId],
66
- );
67
- const isMultiple = field.type === 'MULTI_SELECT';
68
- const theme = useTheme();
69
-
70
- if (isMultiple) {
71
- const selected = Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
72
-
73
- return (
74
- <Labeled label={field.label}>
75
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: theme.spacing[2] }}>
76
- {(options ?? []).map((option) => (
77
- <label key={option.value} style={{ alignItems: 'center', display: 'flex', gap: theme.spacing[1], color: theme.font.color.primary, fontWeight: 'normal' }}>
78
- <input
79
- type="checkbox"
80
- disabled={disabled}
81
- checked={selected.includes(option.value)}
82
- onChange={(event) =>
83
- onChange(
84
- event.target.checked
85
- ? [...selected, option.value]
86
- : selected.filter((item) => item !== option.value),
87
- )
88
- }
89
- />
90
- {option.label}
91
- </label>
92
- ))}
93
- </div>
94
- </Labeled>
95
- );
96
- }
97
-
98
- return (
99
- <SelectField
100
- label={field.label}
101
- value={typeof value === 'string' ? value : NO_VALUE}
102
- disabled={disabled}
103
- options={[{ value: NO_VALUE, label: 'Не выбрано' }, ...(options ?? [])]}
104
- onChange={(selected) => onChange(selected === NO_VALUE ? null : selected)}
105
- />
106
- );
107
- };
108
-
109
- type FieldInputProps = {
110
- field: FormField;
111
- value: unknown;
112
- onChange: (value: unknown) => void;
113
- disabled: boolean;
114
- };
115
-
116
- const FieldInput = (props: FieldInputProps) => {
117
- const { field, value, onChange, disabled } = props;
118
-
119
- switch (field.type) {
120
- case 'RECORD':
121
- return <RecordInput {...props} />;
122
- case 'SELECT':
123
- case 'MULTI_SELECT':
124
- return <SelectInput {...props} />;
125
- case 'NUMBER':
126
- return (
127
- <TextField
128
- label={field.label}
129
- type="number"
130
- disabled={disabled}
131
- value={typeof value === 'number' ? String(value) : typeof value === 'string' ? value : ''}
132
- placeholder={field.placeholder}
133
- onChange={(text) => onChange(text.trim() === '' ? null : Number(text))}
134
- />
135
- );
136
- case 'DATE':
137
- return (
138
- <TextField
139
- label={field.label}
140
- type="date"
141
- disabled={disabled}
142
- value={typeof value === 'string' ? value.slice(0, 10) : ''}
143
- onChange={(text) => onChange(text === '' ? null : text)}
144
- />
145
- );
146
- default:
147
- return (
148
- <TextField
149
- label={field.label}
150
- disabled={disabled}
151
- value={typeof value === 'string' ? value : ''}
152
- placeholder={field.placeholder}
153
- onChange={(text) => onChange(text)}
154
- />
155
- );
156
- }
157
- };
158
-
159
- // The fields a person fills, required ones marked; service fields (assignee,
160
- // deadline) are set by the process and never shown.
161
- export const FormFieldInputs = ({
162
- fields,
163
- values,
164
- onChange,
165
- disabled = false,
166
- }: {
167
- fields: FormField[];
168
- values: FormValues;
169
- onChange: (values: FormValues) => void;
170
- disabled?: boolean;
171
- }) => {
172
- const theme = useTheme();
173
-
174
- return (
175
- <div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[3] }}>
176
- {getFillableFormFields(fields).map((field) => (
177
- <FieldInput
178
- key={field.id}
179
- field={{ ...field, label: isFormFieldRequired(field) ? `${field.label} *` : field.label }}
180
- value={field.name in values ? values[field.name] : field.value}
181
- disabled={disabled}
182
- onChange={(value) => onChange({ ...values, [field.name]: value })}
183
- />
184
- ))}
185
- </div>
186
- );
187
- };