@ram_28/kf-ai-sdk 2.0.16 → 2.0.17
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 -8
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +1 -1
- package/dist/auth/authConfig.d.ts +1 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +1 -1
- package/dist/components/hooks/useActivityForm/types.d.ts +4 -5
- package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/types.d.ts +5 -4
- package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/types.d.ts +20 -12
- package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
- package/dist/{constants-ConHc1oS.js → constants-Cyi942Yr.js} +5 -5
- package/dist/constants-DEmYwKfC.cjs +1 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +15 -16
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/workflow/Activity.d.ts +8 -5
- package/dist/workflow/Activity.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.mjs +461 -476
- package/docs/README.md +51 -0
- package/docs/bdo/README.md +161 -0
- package/docs/bdo/api_reference.md +281 -0
- package/docs/examples/bdo/create-product.md +69 -0
- package/docs/examples/bdo/edit-product-dialog.md +95 -0
- package/docs/examples/bdo/filtered-product-table.md +100 -0
- package/docs/examples/bdo/product-listing.md +73 -0
- package/docs/examples/bdo/supplier-dropdown.md +60 -0
- package/docs/examples/workflow/approve-leave-request.md +76 -0
- package/docs/examples/workflow/filtered-activity-table.md +101 -0
- package/docs/examples/workflow/my-pending-requests.md +90 -0
- package/docs/examples/workflow/start-new-workflow.md +47 -0
- package/docs/examples/workflow/submit-leave-request.md +72 -0
- package/docs/examples/workflow/workflow-progress.md +49 -0
- package/docs/useActivityForm/README.md +241 -0
- package/docs/useActivityForm/api_reference.md +279 -0
- package/docs/useActivityTable/README.md +263 -0
- package/docs/useActivityTable/api_reference.md +294 -0
- package/docs/useBDOForm/README.md +172 -0
- package/docs/useBDOForm/api_reference.md +244 -0
- package/docs/useBDOTable/README.md +242 -0
- package/docs/useBDOTable/api_reference.md +253 -0
- package/docs/useFilter/README.md +323 -0
- package/docs/useFilter/api_reference.md +228 -0
- package/docs/workflow/README.md +158 -0
- package/docs/workflow/api_reference.md +161 -0
- package/package.json +1 -1
- package/sdk/auth/authConfig.ts +1 -1
- package/sdk/auth/types.ts +1 -1
- package/sdk/components/hooks/useActivityForm/types.ts +4 -6
- package/sdk/components/hooks/useActivityForm/useActivityForm.ts +10 -73
- package/sdk/components/hooks/useActivityTable/types.ts +4 -5
- package/sdk/components/hooks/useActivityTable/useActivityTable.ts +10 -8
- package/sdk/components/hooks/useBDOTable/types.ts +20 -10
- package/sdk/components/hooks/useBDOTable/useBDOTable.ts +12 -8
- package/sdk/table.types.ts +2 -0
- package/sdk/types/constants.ts +1 -1
- package/sdk/workflow/Activity.ts +39 -7
- package/dist/constants-QX2RX-wu.cjs +0 -1
- package/docs/api.md +0 -95
- package/docs/bdo.md +0 -224
- package/docs/gaps.md +0 -360
- package/docs/useActivityForm.md +0 -393
- package/docs/useActivityTable.md +0 -418
- package/docs/useBDOForm.md +0 -376
- package/docs/useBDOTable.md +0 -284
- package/docs/useFilter.md +0 -188
- package/docs/workflow.md +0 -560
- /package/docs/{useAuth.md → useAuth/README.md} +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# useActivityForm
|
|
2
|
+
|
|
3
|
+
Form hook for workflow activities with automatic per-field sync and a single `handleSubmit` to complete the activity and advance the workflow.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
**Use `useActivityForm` when:**
|
|
8
|
+
|
|
9
|
+
- Building a form for a workflow activity (employee leave request, manager approval, etc.)
|
|
10
|
+
- You need `handleSubmit` to complete the activity and advance the workflow
|
|
11
|
+
- Fields from prior activities should appear as readonly context
|
|
12
|
+
|
|
13
|
+
**Use something else when:**
|
|
14
|
+
|
|
15
|
+
- Building a BDO record form — use [`useBDOForm`](../useBDOForm/README.md) instead
|
|
16
|
+
- Listing activity instances in a table — use [`useActivityTable`](../useActivityTable/README.md) instead
|
|
17
|
+
|
|
18
|
+
## Imports
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useMemo } from "react";
|
|
28
|
+
import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
|
|
29
|
+
import { EmployeeInputActivity } from "@/workflow/leave";
|
|
30
|
+
|
|
31
|
+
function LeaveRequestForm({ instanceId }: { instanceId: string }) {
|
|
32
|
+
const activity = useMemo(() => new EmployeeInputActivity(), []);
|
|
33
|
+
|
|
34
|
+
const { register, handleSubmit, errors, isLoading, isSubmitting } =
|
|
35
|
+
useActivityForm(activity, { activity_instance_id: instanceId });
|
|
36
|
+
|
|
37
|
+
if (isLoading) return <p>Loading...</p>;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<form>
|
|
41
|
+
<label>{activity.StartDate.label}</label>
|
|
42
|
+
<input type="date" {...register(activity.StartDate.id)} />
|
|
43
|
+
{errors.StartDate && <p>{errors.StartDate.message}</p>}
|
|
44
|
+
|
|
45
|
+
<label>{activity.EndDate.label}</label>
|
|
46
|
+
<input type="date" {...register(activity.EndDate.id)} />
|
|
47
|
+
{errors.EndDate && <p>{errors.EndDate.message}</p>}
|
|
48
|
+
|
|
49
|
+
<button type="button" disabled={isSubmitting}
|
|
50
|
+
onClick={handleSubmit(() => {}, console.error)}>
|
|
51
|
+
Submit Request
|
|
52
|
+
</button>
|
|
53
|
+
</form>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage Guide
|
|
59
|
+
|
|
60
|
+
### Registering Fields
|
|
61
|
+
|
|
62
|
+
Use the Activity instance's field to get the field ID, label, and metadata — never hardcode field names as strings.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<label>{activity.StartDate.label} {activity.StartDate.required && <span>*</span>}</label>
|
|
66
|
+
<input type="date" {...register(activity.StartDate.id)} />
|
|
67
|
+
{errors.StartDate && <p>{errors.StartDate.message}</p>}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For **select, reference, boolean, and other custom components** that don't fire native change events, use `watch()` + `setValue()` instead of `register()`:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<Select
|
|
74
|
+
value={watch(activity.LeaveType.id) ?? ""}
|
|
75
|
+
onValueChange={(v) => setValue(activity.LeaveType.id, v)}
|
|
76
|
+
>
|
|
77
|
+
<SelectTrigger>
|
|
78
|
+
<SelectValue placeholder="Select leave type" />
|
|
79
|
+
</SelectTrigger>
|
|
80
|
+
<SelectContent>
|
|
81
|
+
{activity.LeaveType.options.map((opt) => (
|
|
82
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
83
|
+
{opt.label}
|
|
84
|
+
</SelectItem>
|
|
85
|
+
))}
|
|
86
|
+
</SelectContent>
|
|
87
|
+
</Select>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Readonly fields are auto-disabled by `register()` — no manual disable needed.
|
|
91
|
+
|
|
92
|
+
### Context-Derived Readonly Fields
|
|
93
|
+
|
|
94
|
+
When a workflow has multiple activities, fields from prior activities appear as readonly context. The hook discovers them automatically from BP metadata, and `register()` returns `{ disabled: true }` for them.
|
|
95
|
+
|
|
96
|
+
In a manager approval form, the employee's StartDate, EndDate, LeaveType, and LeaveDays are readonly context, while ManagerApproved and ManagerReason are editable:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { useMemo } from "react";
|
|
100
|
+
import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
|
|
101
|
+
import { ManagerApprovalActivity } from "@/workflow/leave";
|
|
102
|
+
|
|
103
|
+
function ManagerApprovalForm({ instanceId }: { instanceId: string }) {
|
|
104
|
+
const activity = useMemo(() => new ManagerApprovalActivity(), []);
|
|
105
|
+
|
|
106
|
+
const { register, handleSubmit, errors, isLoading, isSubmitting, watch, setValue } =
|
|
107
|
+
useActivityForm(activity, { activity_instance_id: instanceId });
|
|
108
|
+
|
|
109
|
+
if (isLoading) return <p>Loading...</p>;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
{/* Readonly context from employee's activity — auto-disabled */}
|
|
114
|
+
<label>{activity.StartDate.label}</label>
|
|
115
|
+
<input type="date" {...register(activity.StartDate.id)} className="bg-gray-100" />
|
|
116
|
+
|
|
117
|
+
<label>{activity.EndDate.label}</label>
|
|
118
|
+
<input type="date" {...register(activity.EndDate.id)} className="bg-gray-100" />
|
|
119
|
+
|
|
120
|
+
<label>{activity.LeaveDays.label}</label>
|
|
121
|
+
<input type="number" {...register(activity.LeaveDays.id)} className="bg-gray-100" />
|
|
122
|
+
|
|
123
|
+
{/* Editable manager decision fields */}
|
|
124
|
+
<div>
|
|
125
|
+
<input
|
|
126
|
+
type="checkbox"
|
|
127
|
+
checked={watch(activity.ManagerApproved.id) ?? false}
|
|
128
|
+
onChange={(e) => setValue(activity.ManagerApproved.id, e.target.checked)}
|
|
129
|
+
/>
|
|
130
|
+
<label>
|
|
131
|
+
{activity.ManagerApproved.label}
|
|
132
|
+
{activity.ManagerApproved.required && <span> *</span>}
|
|
133
|
+
</label>
|
|
134
|
+
{errors.ManagerApproved && <p>{errors.ManagerApproved.message}</p>}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<label>{activity.ManagerReason.label}</label>
|
|
138
|
+
<textarea {...register(activity.ManagerReason.id)} rows={3} />
|
|
139
|
+
{errors.ManagerReason && <p>{errors.ManagerReason.message}</p>}
|
|
140
|
+
|
|
141
|
+
<button type="button" disabled={isSubmitting}
|
|
142
|
+
onClick={handleSubmit(() => {}, console.error)}>
|
|
143
|
+
Complete Review
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Validation
|
|
151
|
+
|
|
152
|
+
Validation happens automatically. The hook validates field types, constraints (required, length, integerPart/fractionPart), and backend expression rules — all without any manual configuration.
|
|
153
|
+
|
|
154
|
+
Errors appear in `errors.FieldName.message`:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
{errors.StartDate && <p>{errors.StartDate.message}</p>}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Handling Submit
|
|
161
|
+
|
|
162
|
+
Per-field sync automatically saves changes on blur/change via `activity.update()`, so there's no need for a manual save button. When the user is ready, `handleSubmit` validates the form, sends any remaining dirty fields, then completes the activity to advance the workflow.
|
|
163
|
+
|
|
164
|
+
`handleSubmit` is curried: `(onSuccess?, onError?) => (event?) => Promise<void>`.
|
|
165
|
+
|
|
166
|
+
- **`onSuccess(result)`** — Called with `CreateUpdateResponseType` (`{ _id: string }`) after the activity is completed.
|
|
167
|
+
- **`onError(error)`** — Called with `FieldErrors` (validation failure) or `Error` (API failure).
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<button type="button" disabled={isSubmitting}
|
|
171
|
+
onClick={handleSubmit(() => { toast.success("Submitted"); onClose(); }, console.error)}>
|
|
172
|
+
Submit Request
|
|
173
|
+
</button>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
To validate before showing a confirmation dialog, use `trigger()`:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
const handleSubmitWithConfirm = async () => {
|
|
180
|
+
const valid = await trigger();
|
|
181
|
+
if (!valid) return;
|
|
182
|
+
// Show confirmation UI, then invoke:
|
|
183
|
+
handleSubmit(onSuccess, onError)(); // note the double invocation — curried
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
> **Don't** call `activity.update()` or `activity.complete()` manually — `handleSubmit` does it for you.
|
|
188
|
+
|
|
189
|
+
### Working with `item`
|
|
190
|
+
|
|
191
|
+
`item` represents the current activity instance. Each field on `item` is an accessor with methods to read, write, and validate that field's value.
|
|
192
|
+
|
|
193
|
+
**Instance-level members:**
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const { item } = useActivityForm(activity, { activity_instance_id: instanceId });
|
|
197
|
+
|
|
198
|
+
item._id; // current activity instance ID
|
|
199
|
+
item.toJSON(); // all form values as plain object
|
|
200
|
+
await item.validate(); // trigger validation for all fields
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Field-level accessors** (`item.FieldName`):
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// Read/write
|
|
207
|
+
item.StartDate.get(); // current value
|
|
208
|
+
item.LeaveDays.getOrDefault(0); // value or fallback
|
|
209
|
+
item.StartDate.set("2026-03-01"); // update value
|
|
210
|
+
|
|
211
|
+
// Validate
|
|
212
|
+
item.StartDate.validate(); // { valid: boolean, errors: string[] }
|
|
213
|
+
|
|
214
|
+
// Metadata
|
|
215
|
+
item.StartDate.label; // display label
|
|
216
|
+
item.StartDate.required; // is required?
|
|
217
|
+
item.StartDate.readOnly; // is readonly?
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**When to use `item` vs `register`/`watch`/`setValue`:**
|
|
221
|
+
|
|
222
|
+
- Use `register()` for standard HTML inputs (text, number, date)
|
|
223
|
+
- Use `watch()` + `setValue()` for custom components (select, checkbox, reference)
|
|
224
|
+
- Use `item.Field.get()` / `item.Field.set()` for programmatic read/write (event handlers, computed logic)
|
|
225
|
+
|
|
226
|
+
## Further Reading
|
|
227
|
+
|
|
228
|
+
- [API Reference](./api_reference.md) — All options, return values, and type definitions
|
|
229
|
+
- [Submit Leave Request](../examples/workflow/submit-leave-request.md) — Employee form submission
|
|
230
|
+
- [Approve Leave Request](../examples/workflow/approve-leave-request.md) — Manager approval with readonly context
|
|
231
|
+
- [Start New Workflow](../examples/workflow/start-new-workflow.md) — `workflow.start()` flow
|
|
232
|
+
|
|
233
|
+
## Common Mistakes
|
|
234
|
+
|
|
235
|
+
- **Don't forget `useMemo` on the Activity.** `new ActivityClass()` must be wrapped in `useMemo(() => ..., [])`. Re-creating the instance on every render breaks the hook.
|
|
236
|
+
- **Don't create a manual save button.** Per-field sync handles auto-saving on blur/change. `handleSubmit` is for completing the activity, not saving drafts.
|
|
237
|
+
- **Don't set date `defaultValues` to empty string.** Use `undefined` instead. Empty strings cause type validation errors for Date and DateTime fields.
|
|
238
|
+
- **Don't use `register()` for select, checkbox, or reference components.** They don't fire native change events. Use `watch()` + `setValue()`.
|
|
239
|
+
- **Don't call `activity.update()` or `activity.complete()` manually.** `handleSubmit` handles the API calls. Calling them yourself will double-submit.
|
|
240
|
+
- **Don't render the form before `isLoading` is false.** The activity data and metadata are being fetched. Guard with `if (isLoading) return <Loading />`.
|
|
241
|
+
- **Don't forget `activity_instance_id` is required.** This is the activity instance ID (from `workflow.start()` or `getInProgressList()`), not a BDO record ID.
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# useActivityForm API Reference
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
|
|
5
|
+
import type {
|
|
6
|
+
UseActivityFormOptions,
|
|
7
|
+
UseActivityFormReturn,
|
|
8
|
+
} from "@ram_28/kf-ai-sdk/workflow";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
const form: UseActivityFormReturn<EmployeeInputActivity> = useActivityForm(
|
|
15
|
+
activity: EmployeeInputActivity,
|
|
16
|
+
options: UseActivityFormOptions<EmployeeInputActivity>,
|
|
17
|
+
);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
First argument is the Activity instance (positional). Second argument is the options object.
|
|
21
|
+
|
|
22
|
+
## Options
|
|
23
|
+
|
|
24
|
+
`UseActivityFormOptions<A>`
|
|
25
|
+
|
|
26
|
+
- `activity_instance_id: string`
|
|
27
|
+
- **Required**
|
|
28
|
+
- Activity instance ID. From `workflow.start()` response or `getInProgressList()` rows.
|
|
29
|
+
- `defaultValues?: Partial<ExtractActivityEditable<A>>`
|
|
30
|
+
- **Optional** · Defaults to `{}`
|
|
31
|
+
- Initial form values before server data loads. Server data wins on merge.
|
|
32
|
+
- `mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all"`
|
|
33
|
+
- **Optional** · Defaults to `"onBlur"`
|
|
34
|
+
- RHF validation mode. Also controls when per-field server sync fires.
|
|
35
|
+
- `enabled?: boolean`
|
|
36
|
+
- **Optional** · Defaults to `true`
|
|
37
|
+
- Set `false` to defer `activity.read()` until the instance ID is available.
|
|
38
|
+
|
|
39
|
+
## Return Value
|
|
40
|
+
|
|
41
|
+
`UseActivityFormReturn<A>`
|
|
42
|
+
|
|
43
|
+
### Instance
|
|
44
|
+
|
|
45
|
+
- `item: FormItemType<TEditable, TReadonly>`
|
|
46
|
+
- The current activity instance. Each field is an accessor with methods to read, write, and validate. See [Instance and Field Accessors](#instance-and-field-accessors).
|
|
47
|
+
- `activity: A`
|
|
48
|
+
- The Activity instance passed to the hook.
|
|
49
|
+
|
|
50
|
+
### Form Methods
|
|
51
|
+
|
|
52
|
+
- `register: FormRegisterType<TEditable, TReadonly>`
|
|
53
|
+
- Registers an input field. Auto-disables readonly fields (`{ disabled: true }`).
|
|
54
|
+
- `handleSubmit: HandleSubmitType<CreateUpdateResponseType>`
|
|
55
|
+
- Validates, updates any remaining dirty fields, then completes the activity. See [handleSubmit](#handlesubmit).
|
|
56
|
+
- `watch: UseFormWatch<AllActivityFields<A>>`
|
|
57
|
+
- Standard RHF `watch`. Typed to all fields (editable + readonly).
|
|
58
|
+
- `setValue: UseFormSetValue<ExtractActivityEditable<A>>`
|
|
59
|
+
- Standard RHF `setValue`. Typed to editable fields only.
|
|
60
|
+
- `getValues: UseFormGetValues<AllActivityFields<A>>`
|
|
61
|
+
- Returns all field values.
|
|
62
|
+
- `reset: UseFormReset<AllActivityFields<A>>`
|
|
63
|
+
- Resets form to given values or defaults.
|
|
64
|
+
- `trigger: UseFormTrigger<AllActivityFields<A>>`
|
|
65
|
+
- Manually triggers validation for specific or all fields.
|
|
66
|
+
- `control: Control<AllActivityFields<A>>`
|
|
67
|
+
- RHF control for `Controller` components.
|
|
68
|
+
|
|
69
|
+
### Form State
|
|
70
|
+
|
|
71
|
+
- `errors: FieldErrors<AllActivityFields<A>>`
|
|
72
|
+
- Validation errors by field name. Each entry has `type` and `message`.
|
|
73
|
+
- `isValid: boolean`
|
|
74
|
+
- `true` if all fields pass validation.
|
|
75
|
+
- `isDirty: boolean`
|
|
76
|
+
- `true` if any field has been modified.
|
|
77
|
+
- `isSubmitting: boolean`
|
|
78
|
+
- `true` during `handleSubmit` (from button click to API response).
|
|
79
|
+
- `isSubmitSuccessful: boolean`
|
|
80
|
+
- `true` after the last submission succeeded.
|
|
81
|
+
|
|
82
|
+
### Loading & Error
|
|
83
|
+
|
|
84
|
+
- `isLoading: boolean`
|
|
85
|
+
- `true` during initial data + metadata loading. Guard form render on this.
|
|
86
|
+
- `isMetadataLoading: boolean`
|
|
87
|
+
- `true` while BP metadata is being fetched.
|
|
88
|
+
- `loadError: Error | null`
|
|
89
|
+
- Error from `activity.read()`.
|
|
90
|
+
- `hasError: boolean`
|
|
91
|
+
- `true` when `loadError` is non-null.
|
|
92
|
+
|
|
93
|
+
### Other
|
|
94
|
+
|
|
95
|
+
- `bpMetadata: Record<string, unknown> | null`
|
|
96
|
+
- Raw BP metadata blob. `null` while loading.
|
|
97
|
+
- `clearErrors: () => void`
|
|
98
|
+
- Clear all form validation errors.
|
|
99
|
+
|
|
100
|
+
## handleSubmit
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
type HandleSubmitType<TRead> = (
|
|
104
|
+
onSuccess?: (data: TRead, e?: React.BaseSyntheticEvent) => void | Promise<void>,
|
|
105
|
+
onError?: (error: FieldErrors | Error, e?: React.BaseSyntheticEvent) => void | Promise<void>,
|
|
106
|
+
) => (e?: React.BaseSyntheticEvent) => Promise<void>;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Curried function. Pass to an `onClick` handler: `onClick={handleSubmit(onSuccess, onError)}`.
|
|
110
|
+
|
|
111
|
+
**Behavior:**
|
|
112
|
+
1. Validates all fields via the resolver (type + constraint).
|
|
113
|
+
2. Collects dirty, non-readonly fields and sends them via `activity.update()`.
|
|
114
|
+
3. Calls `activity.complete()` to advance the workflow.
|
|
115
|
+
4. Invokes `onSuccess` with the complete response.
|
|
116
|
+
|
|
117
|
+
- `onSuccess(result, e?)`
|
|
118
|
+
- Called with `CreateUpdateResponseType` (`{ _id: string }`) after the activity is completed and the workflow advances.
|
|
119
|
+
- `onError(error, e?)`
|
|
120
|
+
- Called with `FieldErrors` if validation fails, or `Error` if the API call fails.
|
|
121
|
+
|
|
122
|
+
If no dirty fields exist, the update call is skipped but `complete()` is still called.
|
|
123
|
+
|
|
124
|
+
> Per-field sync handles auto-saving on blur/change. `handleSubmit` is for completing the activity, not saving drafts.
|
|
125
|
+
|
|
126
|
+
## Instance and Field Accessors
|
|
127
|
+
|
|
128
|
+
`item` represents the current activity instance. Each field on `item` is an accessor object.
|
|
129
|
+
|
|
130
|
+
### Instance Members (`item`)
|
|
131
|
+
|
|
132
|
+
- `_id: string | undefined`
|
|
133
|
+
- Current activity instance ID.
|
|
134
|
+
- `toJSON(): Partial<TEditable & TReadonly>`
|
|
135
|
+
- All form values as a plain object.
|
|
136
|
+
- `validate(): Promise<boolean>`
|
|
137
|
+
- Triggers validation for all fields. Returns `true` if valid.
|
|
138
|
+
|
|
139
|
+
### Editable Field (`item.FieldName`)
|
|
140
|
+
|
|
141
|
+
- `get(): T | undefined`
|
|
142
|
+
- Current value from form state.
|
|
143
|
+
- `getOrDefault(fallback: T): T`
|
|
144
|
+
- Value or fallback if null/undefined.
|
|
145
|
+
- `set(value: T): void`
|
|
146
|
+
- Sets the field value.
|
|
147
|
+
- `validate(): ValidationResultType`
|
|
148
|
+
- Validates the current value. Returns `{ valid: boolean, errors: string[] }`.
|
|
149
|
+
- `label: string`
|
|
150
|
+
- Display label from field metadata.
|
|
151
|
+
- `required: boolean`
|
|
152
|
+
- Whether the field is required.
|
|
153
|
+
- `readOnly: boolean`
|
|
154
|
+
- Always `false` for editable fields.
|
|
155
|
+
- `defaultValue: unknown`
|
|
156
|
+
- Default value from metadata.
|
|
157
|
+
- `meta: BaseFieldMetaType`
|
|
158
|
+
- Raw field metadata.
|
|
159
|
+
|
|
160
|
+
### Readonly Field (`item.FieldName`)
|
|
161
|
+
|
|
162
|
+
Same as editable except: no `set()` method, `readOnly` is always `true`.
|
|
163
|
+
|
|
164
|
+
## Types
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// ---- Generic extraction helpers ----
|
|
168
|
+
|
|
169
|
+
type ExtractActivityEntity<A> =
|
|
170
|
+
A extends Activity<infer E, any, any> ? E : never;
|
|
171
|
+
|
|
172
|
+
type ExtractActivityEditable<A> =
|
|
173
|
+
A extends Activity<any, infer E, any> ? E : never;
|
|
174
|
+
|
|
175
|
+
type ExtractActivityReadonly<A> =
|
|
176
|
+
A extends Activity<any, any, infer R> ? R : never;
|
|
177
|
+
|
|
178
|
+
type AllActivityFields<A> =
|
|
179
|
+
ExtractActivityEditable<A> & ExtractActivityReadonly<A>;
|
|
180
|
+
|
|
181
|
+
// ---- Hook options ----
|
|
182
|
+
|
|
183
|
+
interface UseActivityFormOptions<A extends Activity<any, any, any>> {
|
|
184
|
+
activity_instance_id: string;
|
|
185
|
+
defaultValues?: Partial<ExtractActivityEditable<A>>;
|
|
186
|
+
mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
|
|
187
|
+
enabled?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---- Hook return type ----
|
|
191
|
+
|
|
192
|
+
interface UseActivityFormReturn<A extends Activity<any, any, any>> {
|
|
193
|
+
item: FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>;
|
|
194
|
+
activity: A;
|
|
195
|
+
|
|
196
|
+
register: FormRegisterType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>;
|
|
197
|
+
handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
|
|
198
|
+
watch: UseFormWatch<AllActivityFields<A>>;
|
|
199
|
+
setValue: UseFormSetValue<ExtractActivityEditable<A>>;
|
|
200
|
+
getValues: UseFormGetValues<AllActivityFields<A>>;
|
|
201
|
+
reset: UseFormReset<AllActivityFields<A>>;
|
|
202
|
+
trigger: UseFormTrigger<AllActivityFields<A>>;
|
|
203
|
+
control: Control<AllActivityFields<A>>;
|
|
204
|
+
|
|
205
|
+
errors: FieldErrors<AllActivityFields<A>>;
|
|
206
|
+
isValid: boolean;
|
|
207
|
+
isDirty: boolean;
|
|
208
|
+
isSubmitting: boolean;
|
|
209
|
+
isSubmitSuccessful: boolean;
|
|
210
|
+
|
|
211
|
+
isLoading: boolean;
|
|
212
|
+
isMetadataLoading: boolean;
|
|
213
|
+
loadError: Error | null;
|
|
214
|
+
hasError: boolean;
|
|
215
|
+
|
|
216
|
+
bpMetadata: Record<string, unknown> | null;
|
|
217
|
+
clearErrors: () => void;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- Shared types ----
|
|
221
|
+
|
|
222
|
+
type HandleSubmitType<TRead = unknown> = (
|
|
223
|
+
onSuccess?: (
|
|
224
|
+
data: TRead,
|
|
225
|
+
e?: React.BaseSyntheticEvent,
|
|
226
|
+
) => void | Promise<void>,
|
|
227
|
+
onError?: (
|
|
228
|
+
error: FieldErrors | Error,
|
|
229
|
+
e?: React.BaseSyntheticEvent,
|
|
230
|
+
) => void | Promise<void>,
|
|
231
|
+
) => (e?: React.BaseSyntheticEvent) => Promise<void>;
|
|
232
|
+
|
|
233
|
+
type FormRegisterType<TEditable, TReadonly> = <
|
|
234
|
+
K extends keyof TEditable | keyof TReadonly | string
|
|
235
|
+
>(
|
|
236
|
+
name: K & string,
|
|
237
|
+
options?: RegisterOptions,
|
|
238
|
+
) => K extends keyof TReadonly
|
|
239
|
+
? UseFormRegisterReturn & { disabled: true }
|
|
240
|
+
: UseFormRegisterReturn;
|
|
241
|
+
|
|
242
|
+
type FormItemType<TEditable, TReadonly> = {
|
|
243
|
+
[K in keyof TEditable]: EditableFormFieldAccessorType<TEditable[K]>;
|
|
244
|
+
} & {
|
|
245
|
+
[K in keyof TReadonly]: ReadonlyFormFieldAccessorType<TReadonly[K]>;
|
|
246
|
+
} & {
|
|
247
|
+
readonly _id: string | undefined;
|
|
248
|
+
toJSON(): Partial<TEditable & TReadonly>;
|
|
249
|
+
validate(): Promise<boolean>;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
interface EditableFormFieldAccessorType<T> {
|
|
253
|
+
readonly label: string;
|
|
254
|
+
readonly required: boolean;
|
|
255
|
+
readonly readOnly: false;
|
|
256
|
+
readonly defaultValue: unknown;
|
|
257
|
+
readonly meta: BaseFieldMetaType;
|
|
258
|
+
get(): T | undefined;
|
|
259
|
+
getOrDefault(fallback: T): T;
|
|
260
|
+
set(value: T): void;
|
|
261
|
+
validate(): ValidationResultType;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface ReadonlyFormFieldAccessorType<T> {
|
|
265
|
+
readonly label: string;
|
|
266
|
+
readonly required: boolean;
|
|
267
|
+
readonly readOnly: true;
|
|
268
|
+
readonly defaultValue: unknown;
|
|
269
|
+
readonly meta: BaseFieldMetaType;
|
|
270
|
+
get(): T | undefined;
|
|
271
|
+
getOrDefault(fallback: T): T;
|
|
272
|
+
validate(): ValidationResultType;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface ValidationResultType {
|
|
276
|
+
valid: boolean;
|
|
277
|
+
errors: string[];
|
|
278
|
+
}
|
|
279
|
+
```
|