@ram_28/kf-ai-sdk 2.0.14 → 2.0.15
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 +2 -1
- package/dist/FileField-BWrSHNRq.js +296 -0
- package/dist/FileField-eDeuzln8.cjs +1 -0
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +228 -472
- package/dist/{client-DnO2KKrw.cjs → client-D5k4SYuw.cjs} +1 -1
- package/dist/{client-iQTqFDNI.js → client-_ayziI1d.js} +33 -32
- package/dist/components/hooks/index.d.ts +9 -3
- package/dist/components/hooks/index.d.ts.map +1 -1
- package/dist/{workflow/components → components/hooks}/useActivityForm/createActivityItemProxy.d.ts +9 -5
- package/dist/components/hooks/useActivityForm/createActivityItemProxy.d.ts.map +1 -0
- package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts +23 -0
- package/dist/components/hooks/useActivityForm/createActivityResolver.d.ts.map +1 -0
- package/dist/components/hooks/useActivityForm/index.d.ts.map +1 -0
- package/dist/{workflow/components → components/hooks}/useActivityForm/types.d.ts +11 -7
- package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -0
- package/dist/{workflow/components → components/hooks}/useActivityForm/useActivityForm.d.ts +2 -2
- package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -0
- package/dist/components/hooks/useActivityTable/index.d.ts +4 -0
- package/dist/components/hooks/useActivityTable/index.d.ts.map +1 -0
- package/dist/components/hooks/useActivityTable/types.d.ts +36 -0
- package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -0
- package/dist/components/hooks/useActivityTable/useActivityTable.d.ts +4 -0
- package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -0
- package/dist/components/hooks/useBDOTable/index.d.ts +3 -0
- package/dist/components/hooks/useBDOTable/index.d.ts.map +1 -0
- package/dist/components/hooks/useBDOTable/types.d.ts +26 -0
- package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -0
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +3 -0
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -0
- package/dist/components/hooks/useTable/index.d.ts +2 -2
- package/dist/components/hooks/useTable/index.d.ts.map +1 -1
- package/dist/components/hooks/useTable/types.d.ts +11 -10
- package/dist/components/hooks/useTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
- package/dist/createResolver-AIgUwoS6.cjs +1 -0
- package/dist/createResolver-ZHXQ7QMa.js +1078 -0
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +252 -314
- package/dist/{metadata-DpfI3zRN.js → metadata-Cc1mBcLS.js} +1 -1
- package/dist/{metadata-DgLSJkF5.cjs → metadata-DWXQPDav.cjs} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.d.ts +1 -0
- package/dist/table.d.ts.map +1 -1
- package/dist/table.mjs +16 -192
- package/dist/table.types.d.ts +2 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/base-fields.d.ts +4 -4
- package/dist/types/base-fields.d.ts.map +1 -1
- package/dist/useTable-CeRklbdT.cjs +1 -0
- package/dist/useTable-DS0-WInw.js +203 -0
- package/dist/workflow/Activity.d.ts +9 -9
- package/dist/workflow/Activity.d.ts.map +1 -1
- package/dist/workflow/client.d.ts.map +1 -1
- package/dist/workflow/createFieldFromMeta.d.ts +29 -0
- package/dist/workflow/createFieldFromMeta.d.ts.map +1 -0
- package/dist/workflow/index.d.ts +1 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/types.d.ts +12 -12
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.d.ts +5 -2
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.mjs +716 -338
- package/dist/workflow.types.d.ts +1 -0
- package/dist/workflow.types.d.ts.map +1 -1
- package/docs/gaps.md +410 -0
- package/docs/useActivityTable.md +481 -0
- package/docs/useBDOTable.md +317 -0
- package/docs/workflow.md +143 -34
- package/package.json +1 -1
- package/sdk/bdo/fields/UserField.ts +1 -1
- package/sdk/components/hooks/index.ts +28 -5
- package/sdk/components/hooks/useActivityForm/createActivityItemProxy.ts +400 -0
- package/sdk/components/hooks/useActivityForm/createActivityResolver.ts +87 -0
- package/sdk/{workflow/components → components/hooks}/useActivityForm/types.ts +21 -8
- package/sdk/components/hooks/useActivityForm/useActivityForm.ts +628 -0
- package/sdk/components/hooks/useActivityTable/index.ts +8 -0
- package/sdk/components/hooks/useActivityTable/types.ts +45 -0
- package/sdk/components/hooks/useActivityTable/useActivityTable.ts +71 -0
- package/sdk/components/hooks/useBDOTable/index.ts +2 -0
- package/sdk/components/hooks/useBDOTable/types.ts +24 -0
- package/sdk/components/hooks/useBDOTable/useBDOTable.ts +15 -0
- package/sdk/components/hooks/useTable/index.ts +3 -3
- package/sdk/components/hooks/useTable/types.ts +16 -12
- package/sdk/components/hooks/useTable/useTable.ts +56 -49
- package/sdk/table.ts +4 -1
- package/sdk/table.types.ts +7 -4
- package/sdk/types/base-fields.ts +4 -4
- package/sdk/workflow/Activity.ts +14 -13
- package/sdk/workflow/client.ts +21 -8
- package/sdk/workflow/createFieldFromMeta.ts +110 -0
- package/sdk/workflow/index.ts +1 -6
- package/sdk/workflow/types.ts +13 -12
- package/sdk/workflow.ts +11 -2
- package/sdk/workflow.types.ts +7 -0
- package/dist/BaseField-B6da88U7.js +0 -40
- package/dist/BaseField-Drp0-OxL.cjs +0 -1
- package/dist/error-handling-CAoD0Kwb.cjs +0 -1
- package/dist/error-handling-CrhTtD88.js +0 -14
- package/dist/index.esm-Cj63v5ny.js +0 -1014
- package/dist/index.esm-DuwT11sx.cjs +0 -1
- package/dist/workflow/components/useActivityForm/createActivityItemProxy.d.ts.map +0 -1
- package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts +0 -22
- package/dist/workflow/components/useActivityForm/createActivityResolver.d.ts.map +0 -1
- package/dist/workflow/components/useActivityForm/index.d.ts.map +0 -1
- package/dist/workflow/components/useActivityForm/types.d.ts.map +0 -1
- package/dist/workflow/components/useActivityForm/useActivityForm.d.ts.map +0 -1
- package/docs/useTable.md +0 -369
- package/sdk/workflow/components/useActivityForm/createActivityItemProxy.ts +0 -130
- package/sdk/workflow/components/useActivityForm/createActivityResolver.ts +0 -61
- package/sdk/workflow/components/useActivityForm/useActivityForm.ts +0 -386
- /package/dist/{workflow/components → components/hooks}/useActivityForm/index.d.ts +0 -0
- /package/sdk/{workflow/components → components/hooks}/useActivityForm/index.ts +0 -0
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
// Table hook
|
|
2
|
-
export { useTable } from
|
|
1
|
+
// Table hook (base)
|
|
2
|
+
export { useTable } from './useTable';
|
|
3
3
|
export type {
|
|
4
4
|
UseTableOptionsType,
|
|
5
5
|
UseTableReturnType,
|
|
6
|
-
|
|
7
|
-
} from
|
|
6
|
+
PaginationStateType,
|
|
7
|
+
} from './useTable';
|
|
8
|
+
|
|
9
|
+
// BDO table hook
|
|
10
|
+
export { useBDOTable } from './useBDOTable';
|
|
11
|
+
export type {
|
|
12
|
+
UseBDOTableOptionsType,
|
|
13
|
+
UseBDOTableReturnType,
|
|
14
|
+
} from './useBDOTable';
|
|
15
|
+
|
|
16
|
+
// Activity table hook
|
|
17
|
+
export { useActivityTable, ActivityTableStatus } from './useActivityTable';
|
|
18
|
+
export type {
|
|
19
|
+
UseActivityTableOptionsType,
|
|
20
|
+
UseActivityTableReturnType,
|
|
21
|
+
ActivityTableStatusType,
|
|
22
|
+
ActivityRowType,
|
|
23
|
+
} from './useActivityTable';
|
|
24
|
+
|
|
25
|
+
// Activity form hook
|
|
26
|
+
export { useActivityForm } from './useActivityForm';
|
|
27
|
+
export type {
|
|
28
|
+
UseActivityFormOptions,
|
|
29
|
+
UseActivityFormReturn,
|
|
30
|
+
} from './useActivityForm';
|
|
8
31
|
|
|
9
32
|
// Filter hook
|
|
10
|
-
export * from
|
|
33
|
+
export * from './useFilter';
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ACTIVITY ITEM PROXY
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Proxy-based item that delegates to RHF for state management.
|
|
5
|
+
// Follows the same pattern as createItemProxy for BaseBdo,
|
|
6
|
+
// adapted for Activity. Attachment methods call
|
|
7
|
+
// createResourceClient() directly (matching BDO's api() pattern).
|
|
8
|
+
|
|
9
|
+
import type { UseFormReturn, Path, FieldValues } from 'react-hook-form';
|
|
10
|
+
import type { BaseFieldMetaType } from '../../../bdo/core/types';
|
|
11
|
+
import type { BaseField } from '../../../bdo/fields/BaseField';
|
|
12
|
+
import type { FileType } from '../../../types/base-fields';
|
|
13
|
+
import type {
|
|
14
|
+
FileDownloadResponseType,
|
|
15
|
+
AttachmentViewType,
|
|
16
|
+
} from '../../../types/common';
|
|
17
|
+
import type {
|
|
18
|
+
FormItemType,
|
|
19
|
+
EditableFormFieldAccessorType,
|
|
20
|
+
ReadonlyFormFieldAccessorType,
|
|
21
|
+
} from '../useForm/types';
|
|
22
|
+
import type { Activity } from '../../../workflow/Activity';
|
|
23
|
+
import type {
|
|
24
|
+
ExtractActivityEditable,
|
|
25
|
+
ExtractActivityReadonly,
|
|
26
|
+
} from './types';
|
|
27
|
+
import { createResourceClient } from '../../../api/client';
|
|
28
|
+
import {
|
|
29
|
+
validateFileExtension,
|
|
30
|
+
extractFileExtension,
|
|
31
|
+
} from '../../../bdo/fields/attachment-constants';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a Proxy-based Item that delegates to RHF for state management.
|
|
35
|
+
*
|
|
36
|
+
* Key principle: Item has NO state. It's a view over RHF's state.
|
|
37
|
+
* Editable fields get set(), readonly fields do not.
|
|
38
|
+
*
|
|
39
|
+
* Activity forms always have an instance ID (no draft creation needed),
|
|
40
|
+
* so attachment operations use the provided instanceId directly.
|
|
41
|
+
*
|
|
42
|
+
* @param activity - The Activity instance for field metadata
|
|
43
|
+
* @param form - The RHF useForm return object
|
|
44
|
+
* @param instanceId - The activity instance ID
|
|
45
|
+
* @returns FormItemType proxy
|
|
46
|
+
*/
|
|
47
|
+
export function createActivityItemProxy<A extends Activity<any, any, any>>(
|
|
48
|
+
activity: A,
|
|
49
|
+
form: UseFormReturn<FieldValues>,
|
|
50
|
+
instanceId: string,
|
|
51
|
+
): FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>> {
|
|
52
|
+
const fields = activity._getFields();
|
|
53
|
+
const accessorCache = new Map<
|
|
54
|
+
string,
|
|
55
|
+
| EditableFormFieldAccessorType<unknown>
|
|
56
|
+
| ReadonlyFormFieldAccessorType<unknown>
|
|
57
|
+
>();
|
|
58
|
+
|
|
59
|
+
// Resource client for attachment operations — same pattern as BDO's api(boId)
|
|
60
|
+
const activityBasePath = `/api/app/process/${activity.meta.businessProcessId}/${activity.meta.activityId}`;
|
|
61
|
+
const activityApi = createResourceClient(activityBasePath);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Defensive helper — throws if no instance ID is available.
|
|
65
|
+
* Activity forms always have an instance, so this is a safety net.
|
|
66
|
+
*/
|
|
67
|
+
function requireInstanceId(): string {
|
|
68
|
+
if (!instanceId) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Cannot perform attachment operation: no activity instance ID',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return instanceId;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new Proxy(
|
|
77
|
+
{} as FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>,
|
|
78
|
+
{
|
|
79
|
+
get(_, prop: string | symbol) {
|
|
80
|
+
// Handle symbol properties (e.g., Symbol.toStringTag)
|
|
81
|
+
if (typeof prop === 'symbol') {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Direct _id access (not an accessor, just the value)
|
|
86
|
+
if (prop === '_id') {
|
|
87
|
+
return instanceId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// toJSON returns all form values as plain object
|
|
91
|
+
if (prop === 'toJSON') {
|
|
92
|
+
return () => form.getValues();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// validate triggers RHF validation for all fields
|
|
96
|
+
if (prop === 'validate') {
|
|
97
|
+
return () => form.trigger();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Return cached accessor if available
|
|
101
|
+
if (accessorCache.has(prop)) {
|
|
102
|
+
return accessorCache.get(prop);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Field accessor
|
|
106
|
+
const bdoField = fields[prop] as BaseField<unknown> | undefined;
|
|
107
|
+
const fieldMeta: BaseFieldMetaType = bdoField?.meta ?? {
|
|
108
|
+
_id: prop,
|
|
109
|
+
Name: prop,
|
|
110
|
+
Type: 'String',
|
|
111
|
+
};
|
|
112
|
+
const isReadOnly = bdoField?.readOnly ?? false;
|
|
113
|
+
|
|
114
|
+
// Base validate function
|
|
115
|
+
const validate = () => {
|
|
116
|
+
if (bdoField) {
|
|
117
|
+
return bdoField.validate(
|
|
118
|
+
form.getValues(prop as Path<FieldValues>),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return { valid: true, errors: [] };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Shared getOrDefault helper
|
|
125
|
+
const getOrDefault = (fallback: unknown) => {
|
|
126
|
+
const value = form.getValues(prop as Path<FieldValues>);
|
|
127
|
+
return value !== undefined && value !== null ? value : fallback;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Only add set() for editable fields
|
|
131
|
+
if (!isReadOnly) {
|
|
132
|
+
// Defensive get(): File fields return [] instead of null/undefined
|
|
133
|
+
const fieldGet = () => {
|
|
134
|
+
const val = form.getValues(prop as Path<FieldValues>);
|
|
135
|
+
if (fieldMeta.Type === 'File') return val ?? [];
|
|
136
|
+
return val;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const accessor: EditableFormFieldAccessorType<unknown> = {
|
|
140
|
+
label: bdoField?.label ?? prop,
|
|
141
|
+
required: bdoField?.required ?? false,
|
|
142
|
+
readOnly: false,
|
|
143
|
+
defaultValue: bdoField?.defaultValue,
|
|
144
|
+
meta: fieldMeta,
|
|
145
|
+
get: fieldGet,
|
|
146
|
+
getOrDefault,
|
|
147
|
+
set: (value: unknown) => {
|
|
148
|
+
form.setValue(prop as Path<FieldValues>, value as any, {
|
|
149
|
+
shouldDirty: true,
|
|
150
|
+
shouldTouch: true,
|
|
151
|
+
shouldValidate: false,
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
validate,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Enrich Image/File field accessors with attachment methods
|
|
158
|
+
if (fieldMeta.Type === 'Image' || fieldMeta.Type === 'File') {
|
|
159
|
+
if (fieldMeta.Type === 'Image') {
|
|
160
|
+
// Image: single file upload
|
|
161
|
+
(accessor as any).upload = async (
|
|
162
|
+
file: File,
|
|
163
|
+
): Promise<FileType> => {
|
|
164
|
+
validateFileExtension(file.name, 'Image');
|
|
165
|
+
const id = requireInstanceId();
|
|
166
|
+
|
|
167
|
+
const [uploadInfo] = await activityApi.getUploadUrl(
|
|
168
|
+
id,
|
|
169
|
+
prop,
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
FileName: file.name,
|
|
173
|
+
Size: file.size,
|
|
174
|
+
FileExtension: extractFileExtension(file.name),
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
);
|
|
178
|
+
await fetch(uploadInfo.UploadUrl.URL, {
|
|
179
|
+
method: 'PUT',
|
|
180
|
+
headers: { 'Content-Type': uploadInfo.ContentType },
|
|
181
|
+
body: file,
|
|
182
|
+
});
|
|
183
|
+
const metadata: FileType = {
|
|
184
|
+
_id: uploadInfo._id,
|
|
185
|
+
_name: uploadInfo._name,
|
|
186
|
+
FileName: uploadInfo.FileName,
|
|
187
|
+
FileExtension: uploadInfo.FileExtension,
|
|
188
|
+
Size: uploadInfo.Size,
|
|
189
|
+
ContentType: uploadInfo.ContentType,
|
|
190
|
+
};
|
|
191
|
+
form.setValue(prop as Path<FieldValues>, metadata as any, {
|
|
192
|
+
shouldDirty: true,
|
|
193
|
+
});
|
|
194
|
+
return metadata;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
(accessor as any).deleteAttachment =
|
|
198
|
+
async (): Promise<void> => {
|
|
199
|
+
const val = form.getValues(
|
|
200
|
+
prop as Path<FieldValues>,
|
|
201
|
+
) as any;
|
|
202
|
+
const id = requireInstanceId();
|
|
203
|
+
if (!val?._id)
|
|
204
|
+
throw new Error(`${prop} has no image to delete`);
|
|
205
|
+
await activityApi.deleteAttachment(id, prop, val._id);
|
|
206
|
+
form.setValue(prop as Path<FieldValues>, null as any, {
|
|
207
|
+
shouldDirty: true,
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
(accessor as any).getDownloadUrl = async (
|
|
212
|
+
viewType?: AttachmentViewType,
|
|
213
|
+
): Promise<FileDownloadResponseType> => {
|
|
214
|
+
const val = form.getValues(
|
|
215
|
+
prop as Path<FieldValues>,
|
|
216
|
+
) as any;
|
|
217
|
+
const id = requireInstanceId();
|
|
218
|
+
if (!val?._id)
|
|
219
|
+
throw new Error(`${prop} has no image`);
|
|
220
|
+
return activityApi.getDownloadUrl(
|
|
221
|
+
id,
|
|
222
|
+
prop,
|
|
223
|
+
val._id,
|
|
224
|
+
viewType,
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
} else {
|
|
228
|
+
// File field — multi-file
|
|
229
|
+
(accessor as any).upload = async (
|
|
230
|
+
files: File[],
|
|
231
|
+
): Promise<FileType[]> => {
|
|
232
|
+
for (const file of files)
|
|
233
|
+
validateFileExtension(file.name, 'File');
|
|
234
|
+
const id = requireInstanceId();
|
|
235
|
+
|
|
236
|
+
const requests = files.map((file) => ({
|
|
237
|
+
FileName: file.name,
|
|
238
|
+
Size: file.size,
|
|
239
|
+
FileExtension: extractFileExtension(file.name),
|
|
240
|
+
}));
|
|
241
|
+
const uploadInfos = await activityApi.getUploadUrl(
|
|
242
|
+
id,
|
|
243
|
+
prop,
|
|
244
|
+
requests,
|
|
245
|
+
);
|
|
246
|
+
const uploaded: FileType[] = await Promise.all(
|
|
247
|
+
files.map(async (file, i) => {
|
|
248
|
+
await fetch(uploadInfos[i].UploadUrl.URL, {
|
|
249
|
+
method: 'PUT',
|
|
250
|
+
headers: {
|
|
251
|
+
'Content-Type': uploadInfos[i].ContentType,
|
|
252
|
+
},
|
|
253
|
+
body: file,
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
_id: uploadInfos[i]._id,
|
|
257
|
+
_name: uploadInfos[i]._name,
|
|
258
|
+
FileName: uploadInfos[i].FileName,
|
|
259
|
+
FileExtension: uploadInfos[i].FileExtension,
|
|
260
|
+
Size: uploadInfos[i].Size,
|
|
261
|
+
ContentType: uploadInfos[i].ContentType,
|
|
262
|
+
};
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
const current =
|
|
266
|
+
(form.getValues(prop as Path<FieldValues>) as
|
|
267
|
+
| FileType[]
|
|
268
|
+
| undefined) ?? [];
|
|
269
|
+
form.setValue(
|
|
270
|
+
prop as Path<FieldValues>,
|
|
271
|
+
[...current, ...uploaded] as any,
|
|
272
|
+
{ shouldDirty: true },
|
|
273
|
+
);
|
|
274
|
+
return uploaded;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
(accessor as any).deleteAttachment = async (
|
|
278
|
+
attachmentId: string,
|
|
279
|
+
): Promise<void> => {
|
|
280
|
+
const current =
|
|
281
|
+
(form.getValues(prop as Path<FieldValues>) as any[]) ?? [];
|
|
282
|
+
const id = requireInstanceId();
|
|
283
|
+
await activityApi.deleteAttachment(id, prop, attachmentId);
|
|
284
|
+
form.setValue(
|
|
285
|
+
prop as Path<FieldValues>,
|
|
286
|
+
current.filter((f) => f._id !== attachmentId) as any,
|
|
287
|
+
{ shouldDirty: true },
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
(accessor as any).getDownloadUrl = async (
|
|
292
|
+
attachmentId: string,
|
|
293
|
+
viewType?: AttachmentViewType,
|
|
294
|
+
): Promise<FileDownloadResponseType> => {
|
|
295
|
+
const id = requireInstanceId();
|
|
296
|
+
return activityApi.getDownloadUrl(
|
|
297
|
+
id,
|
|
298
|
+
prop,
|
|
299
|
+
attachmentId,
|
|
300
|
+
viewType,
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
(accessor as any).getDownloadUrls = async (
|
|
305
|
+
viewType?: AttachmentViewType,
|
|
306
|
+
): Promise<FileDownloadResponseType[]> => {
|
|
307
|
+
const id = requireInstanceId();
|
|
308
|
+
return activityApi.getDownloadUrls(id, prop, viewType);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
accessorCache.set(prop, accessor);
|
|
314
|
+
return accessor;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Defensive get() for readonly accessor too
|
|
318
|
+
const readonlyGet = () => {
|
|
319
|
+
const val = form.getValues(prop as Path<FieldValues>);
|
|
320
|
+
if (fieldMeta.Type === 'File') return val ?? [];
|
|
321
|
+
return val;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const accessor: ReadonlyFormFieldAccessorType<unknown> = {
|
|
325
|
+
label: bdoField?.label ?? prop,
|
|
326
|
+
required: bdoField?.required ?? false,
|
|
327
|
+
readOnly: true,
|
|
328
|
+
defaultValue: bdoField?.defaultValue,
|
|
329
|
+
meta: fieldMeta,
|
|
330
|
+
get: readonlyGet,
|
|
331
|
+
getOrDefault,
|
|
332
|
+
validate,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Enrich readonly Image/File field accessors with download methods
|
|
336
|
+
if (fieldMeta.Type === 'Image' || fieldMeta.Type === 'File') {
|
|
337
|
+
if (fieldMeta.Type === 'Image') {
|
|
338
|
+
(accessor as any).getDownloadUrl = async (
|
|
339
|
+
viewType?: AttachmentViewType,
|
|
340
|
+
): Promise<FileDownloadResponseType> => {
|
|
341
|
+
const val = form.getValues(
|
|
342
|
+
prop as Path<FieldValues>,
|
|
343
|
+
) as any;
|
|
344
|
+
const id = requireInstanceId();
|
|
345
|
+
if (!val?._id)
|
|
346
|
+
throw new Error(`${prop} has no image to download`);
|
|
347
|
+
return activityApi.getDownloadUrl(
|
|
348
|
+
id,
|
|
349
|
+
prop,
|
|
350
|
+
val._id,
|
|
351
|
+
viewType,
|
|
352
|
+
);
|
|
353
|
+
};
|
|
354
|
+
} else {
|
|
355
|
+
(accessor as any).getDownloadUrl = async (
|
|
356
|
+
attachmentId: string,
|
|
357
|
+
viewType?: AttachmentViewType,
|
|
358
|
+
): Promise<FileDownloadResponseType> => {
|
|
359
|
+
const id = requireInstanceId();
|
|
360
|
+
return activityApi.getDownloadUrl(
|
|
361
|
+
id,
|
|
362
|
+
prop,
|
|
363
|
+
attachmentId,
|
|
364
|
+
viewType,
|
|
365
|
+
);
|
|
366
|
+
};
|
|
367
|
+
(accessor as any).getDownloadUrls = async (
|
|
368
|
+
viewType?: AttachmentViewType,
|
|
369
|
+
): Promise<FileDownloadResponseType[]> => {
|
|
370
|
+
const id = requireInstanceId();
|
|
371
|
+
return activityApi.getDownloadUrls(id, prop, viewType);
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
accessorCache.set(prop, accessor);
|
|
377
|
+
return accessor;
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
has(_, prop) {
|
|
381
|
+
if (typeof prop === 'symbol') return false;
|
|
382
|
+
if (prop === '_id' || prop === 'toJSON' || prop === 'validate')
|
|
383
|
+
return true;
|
|
384
|
+
return prop in fields;
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
ownKeys(_) {
|
|
388
|
+
return [...Object.keys(fields), '_id', 'toJSON', 'validate'];
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
392
|
+
if (typeof prop === 'symbol') return undefined;
|
|
393
|
+
return {
|
|
394
|
+
configurable: true,
|
|
395
|
+
enumerable: prop !== 'toJSON' && prop !== 'validate',
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ACTIVITY RESOLVER
|
|
3
|
+
// ============================================================
|
|
4
|
+
// RHF resolver for Activity field validation.
|
|
5
|
+
// Accepts either an Activity instance or a fields map (for
|
|
6
|
+
// metadata-driven forms). Validates using BaseField.validate()
|
|
7
|
+
// plus constraint validation (required, length, number precision).
|
|
8
|
+
|
|
9
|
+
import type { FieldValues } from 'react-hook-form';
|
|
10
|
+
import type { Activity } from '../../../workflow/Activity';
|
|
11
|
+
import type { BaseField } from '../../../bdo/fields/BaseField';
|
|
12
|
+
import type { ValidationResultType } from '../../../bdo/core/types';
|
|
13
|
+
import { validateConstraints } from '../useForm/createResolver';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a React Hook Form resolver for Activity field validation.
|
|
17
|
+
*
|
|
18
|
+
* Validates only editable fields using BaseField.validate() + constraint checks.
|
|
19
|
+
* Readonly fields (readOnly: true) are skipped.
|
|
20
|
+
*
|
|
21
|
+
* @param activityOrFields - An Activity instance or a Record of field name to BaseField
|
|
22
|
+
* @returns RHF Resolver function
|
|
23
|
+
*/
|
|
24
|
+
export function createActivityResolver<A extends Activity<any, any, any>>(
|
|
25
|
+
activityOrFields: A | Record<string, BaseField<unknown>>,
|
|
26
|
+
) {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
return async (values: FieldValues, _context: any, options: any) => {
|
|
29
|
+
const errors: Record<string, { type: string; message: string }> = {};
|
|
30
|
+
|
|
31
|
+
// Accept either an Activity instance or a raw fields map
|
|
32
|
+
const fields: Record<string, BaseField<unknown>> =
|
|
33
|
+
'_getFields' in activityOrFields
|
|
34
|
+
? (activityOrFields as A)._getFields()
|
|
35
|
+
: activityOrFields;
|
|
36
|
+
|
|
37
|
+
// If validating specific fields (blur/change), only validate those
|
|
38
|
+
// If validating all (submit), options.names is undefined
|
|
39
|
+
const fieldsToValidate = options?.names ?? Object.keys(fields);
|
|
40
|
+
|
|
41
|
+
for (const fieldName of fieldsToValidate) {
|
|
42
|
+
const field = fields[fieldName];
|
|
43
|
+
if (!field) continue;
|
|
44
|
+
|
|
45
|
+
// Skip validation for readonly fields
|
|
46
|
+
if (field.readOnly) continue;
|
|
47
|
+
|
|
48
|
+
let value = values[fieldName];
|
|
49
|
+
|
|
50
|
+
// Coerce string values from HTML inputs to the expected type
|
|
51
|
+
if (typeof value === 'string' && field.meta.Type === 'Number') {
|
|
52
|
+
value = value === '' ? undefined : Number(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 1. Type validation
|
|
56
|
+
const typeResult: ValidationResultType = (
|
|
57
|
+
field as BaseField<unknown>
|
|
58
|
+
).validate(value);
|
|
59
|
+
|
|
60
|
+
if (!typeResult.valid && typeResult.errors.length > 0) {
|
|
61
|
+
errors[fieldName] = {
|
|
62
|
+
type: 'validate',
|
|
63
|
+
message: typeResult.errors[0] || `${fieldName} is invalid`,
|
|
64
|
+
};
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Constraint validation (required, length, number precision)
|
|
69
|
+
const constraintResult = validateConstraints(
|
|
70
|
+
field as BaseField<unknown>,
|
|
71
|
+
value,
|
|
72
|
+
);
|
|
73
|
+
if (!constraintResult.valid && constraintResult.errors.length > 0) {
|
|
74
|
+
errors[fieldName] = {
|
|
75
|
+
type: 'constraint',
|
|
76
|
+
message: constraintResult.errors[0],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Return format for RHF resolver
|
|
82
|
+
if (Object.keys(errors).length === 0) {
|
|
83
|
+
return { values, errors: {} };
|
|
84
|
+
}
|
|
85
|
+
return { values: {}, errors };
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -12,9 +12,9 @@ import type {
|
|
|
12
12
|
UseFormTrigger,
|
|
13
13
|
Control,
|
|
14
14
|
FieldErrors,
|
|
15
|
-
} from
|
|
15
|
+
} from 'react-hook-form';
|
|
16
16
|
|
|
17
|
-
import type { Activity } from
|
|
17
|
+
import type { Activity } from '../../../workflow/Activity';
|
|
18
18
|
|
|
19
19
|
// Reuse shared types from useForm — identical interfaces, no duplication
|
|
20
20
|
import type {
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
FormRegisterType,
|
|
24
24
|
EditableFormFieldAccessorType,
|
|
25
25
|
ReadonlyFormFieldAccessorType,
|
|
26
|
-
} from
|
|
26
|
+
} from '../useForm/types';
|
|
27
27
|
|
|
28
28
|
// Re-export for consumers who import from this module
|
|
29
29
|
export type {
|
|
@@ -75,7 +75,7 @@ export interface UseActivityFormOptions<A extends Activity<any, any, any>> {
|
|
|
75
75
|
* Validation mode — controls when validation runs
|
|
76
76
|
* @default "onBlur"
|
|
77
77
|
*/
|
|
78
|
-
mode?:
|
|
78
|
+
mode?: 'onBlur' | 'onChange' | 'onSubmit' | 'onTouched' | 'all';
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Whether to enable activity.read() on mount
|
|
@@ -99,12 +99,15 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
|
|
|
99
99
|
activity: A;
|
|
100
100
|
|
|
101
101
|
/** Smart register with auto-disable for readonly fields */
|
|
102
|
-
register: FormRegisterType<
|
|
102
|
+
register: FormRegisterType<
|
|
103
|
+
ExtractActivityEditable<A>,
|
|
104
|
+
ExtractActivityReadonly<A>
|
|
105
|
+
>;
|
|
103
106
|
|
|
104
|
-
/** Handle form submission — calls activity.update()
|
|
107
|
+
/** Handle form submission — calls activity.update() */
|
|
105
108
|
handleSubmit: HandleSubmitType<AllActivityFields<A>>;
|
|
106
109
|
|
|
107
|
-
/** Handle form completion — calls activity.complete() */
|
|
110
|
+
/** Handle form completion — calls activity.update() + activity.complete() */
|
|
108
111
|
handleComplete: HandleSubmitType<AllActivityFields<A>>;
|
|
109
112
|
|
|
110
113
|
/** Watch field values */
|
|
@@ -148,15 +151,25 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
|
|
|
148
151
|
// LOADING STATES
|
|
149
152
|
// ============================================================
|
|
150
153
|
|
|
151
|
-
/** True during activity.read() */
|
|
154
|
+
/** True during activity.read() or metadata fetch */
|
|
152
155
|
isLoading: boolean;
|
|
153
156
|
|
|
157
|
+
/** True while BP metadata and context schemas are loading */
|
|
158
|
+
isMetadataLoading: boolean;
|
|
159
|
+
|
|
154
160
|
/** Schema/data load error */
|
|
155
161
|
loadError: Error | null;
|
|
156
162
|
|
|
157
163
|
/** Any error active */
|
|
158
164
|
hasError: boolean;
|
|
159
165
|
|
|
166
|
+
// ============================================================
|
|
167
|
+
// METADATA (optional — for consumers that need raw metadata)
|
|
168
|
+
// ============================================================
|
|
169
|
+
|
|
170
|
+
/** Raw BP metadata (BDOBlob) — available after metadata fetch completes */
|
|
171
|
+
bpMetadata: Record<string, unknown> | null;
|
|
172
|
+
|
|
160
173
|
// ============================================================
|
|
161
174
|
// OPERATIONS
|
|
162
175
|
// ============================================================
|