@ram_28/kf-ai-sdk 2.0.16 → 2.0.18

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 (105) hide show
  1. package/README.md +16 -8
  2. package/dist/{FileField-BWrSHNRq.js → FileField-CZjS2uLh.js} +3 -3
  3. package/dist/{FileField-eDeuzln8.cjs → FileField-DU4UWo_t.cjs} +1 -1
  4. package/dist/api.cjs +1 -1
  5. package/dist/api.mjs +1 -1
  6. package/dist/auth/authConfig.d.ts +1 -1
  7. package/dist/auth/types.d.ts +1 -1
  8. package/dist/auth/types.d.ts.map +1 -1
  9. package/dist/auth.cjs +1 -1
  10. package/dist/auth.mjs +1 -1
  11. package/dist/bdo/core/Item.d.ts +0 -4
  12. package/dist/bdo/core/Item.d.ts.map +1 -1
  13. package/dist/bdo/fields/ReferenceField.d.ts +1 -1
  14. package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
  15. package/dist/bdo/fields/SelectField.d.ts +1 -1
  16. package/dist/bdo/fields/SelectField.d.ts.map +1 -1
  17. package/dist/bdo/fields/UserField.d.ts +1 -1
  18. package/dist/bdo/fields/UserField.d.ts.map +1 -1
  19. package/dist/bdo.cjs +1 -1
  20. package/dist/bdo.mjs +53 -62
  21. package/dist/components/hooks/useActivityForm/types.d.ts +4 -5
  22. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
  23. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
  24. package/dist/components/hooks/useActivityTable/types.d.ts +5 -4
  25. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
  26. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
  27. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +2 -3
  28. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
  29. package/dist/components/hooks/useBDOTable/types.d.ts +20 -12
  30. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
  31. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
  32. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
  33. package/dist/{constants-ConHc1oS.js → constants-Cyi942Yr.js} +5 -5
  34. package/dist/constants-DEmYwKfC.cjs +1 -0
  35. package/dist/filter.cjs +1 -1
  36. package/dist/filter.mjs +1 -1
  37. package/dist/form.cjs +1 -1
  38. package/dist/form.mjs +226 -243
  39. package/dist/table.cjs +1 -1
  40. package/dist/table.mjs +15 -16
  41. package/dist/table.types.d.ts +1 -1
  42. package/dist/table.types.d.ts.map +1 -1
  43. package/dist/types/constants.d.ts +1 -1
  44. package/dist/workflow/Activity.d.ts +8 -5
  45. package/dist/workflow/Activity.d.ts.map +1 -1
  46. package/dist/workflow.cjs +1 -1
  47. package/dist/workflow.mjs +461 -476
  48. package/docs/README.md +57 -0
  49. package/docs/bdo/README.md +161 -0
  50. package/docs/bdo/api_reference.md +281 -0
  51. package/docs/examples/bdo/create-product.md +69 -0
  52. package/docs/examples/bdo/edit-product-dialog.md +95 -0
  53. package/docs/examples/bdo/filtered-product-table.md +100 -0
  54. package/docs/examples/bdo/product-listing.md +73 -0
  55. package/docs/examples/bdo/supplier-dropdown.md +60 -0
  56. package/docs/examples/fields/complex-fields.md +248 -0
  57. package/docs/examples/fields/primitive-fields.md +217 -0
  58. package/docs/examples/workflow/approve-leave-request.md +76 -0
  59. package/docs/examples/workflow/filtered-activity-table.md +101 -0
  60. package/docs/examples/workflow/my-pending-requests.md +90 -0
  61. package/docs/examples/workflow/start-new-workflow.md +47 -0
  62. package/docs/examples/workflow/submit-leave-request.md +72 -0
  63. package/docs/examples/workflow/workflow-progress.md +49 -0
  64. package/docs/fields/README.md +141 -0
  65. package/docs/fields/api_reference.md +134 -0
  66. package/docs/useActivityForm/README.md +244 -0
  67. package/docs/useActivityForm/api_reference.md +279 -0
  68. package/docs/useActivityTable/README.md +263 -0
  69. package/docs/useActivityTable/api_reference.md +294 -0
  70. package/docs/useBDOForm/README.md +175 -0
  71. package/docs/useBDOForm/api_reference.md +244 -0
  72. package/docs/useBDOTable/README.md +242 -0
  73. package/docs/useBDOTable/api_reference.md +253 -0
  74. package/docs/useFilter/README.md +323 -0
  75. package/docs/useFilter/api_reference.md +228 -0
  76. package/docs/workflow/README.md +158 -0
  77. package/docs/workflow/api_reference.md +161 -0
  78. package/package.json +1 -1
  79. package/sdk/auth/authConfig.ts +1 -1
  80. package/sdk/auth/types.ts +1 -1
  81. package/sdk/bdo/core/Item.ts +1 -10
  82. package/sdk/bdo/fields/ReferenceField.ts +1 -1
  83. package/sdk/bdo/fields/SelectField.ts +1 -1
  84. package/sdk/bdo/fields/UserField.ts +1 -1
  85. package/sdk/components/hooks/useActivityForm/types.ts +4 -6
  86. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +10 -73
  87. package/sdk/components/hooks/useActivityTable/types.ts +4 -5
  88. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +10 -8
  89. package/sdk/components/hooks/useBDOForm/createItemProxy.ts +17 -58
  90. package/sdk/components/hooks/useBDOTable/types.ts +20 -10
  91. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +12 -8
  92. package/sdk/table.types.ts +2 -0
  93. package/sdk/types/constants.ts +1 -1
  94. package/sdk/workflow/Activity.ts +39 -7
  95. package/dist/constants-QX2RX-wu.cjs +0 -1
  96. package/docs/api.md +0 -95
  97. package/docs/bdo.md +0 -224
  98. package/docs/gaps.md +0 -360
  99. package/docs/useActivityForm.md +0 -393
  100. package/docs/useActivityTable.md +0 -418
  101. package/docs/useBDOForm.md +0 -376
  102. package/docs/useBDOTable.md +0 -284
  103. package/docs/useFilter.md +0 -188
  104. package/docs/workflow.md +0 -560
  105. /package/docs/{useAuth.md → useAuth/README.md} +0 -0
@@ -0,0 +1,217 @@
1
+ # Primitive Fields
2
+
3
+ > Complete form demonstrating all primitive field types with the correct form binding pattern for each: String, Number, Boolean, Date, DateTime, and Text.
4
+
5
+ ```tsx
6
+ import { useMemo } from "react";
7
+ import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
8
+ import type { UseBDOFormReturnType } from "@ram_28/kf-ai-sdk/form/types";
9
+ import { AdminFieldTest } from "@/bdo/admin/FieldTest";
10
+ import {
11
+ Field,
12
+ FieldContent,
13
+ FieldLabel,
14
+ FieldError,
15
+ } from "@/components/ui/field";
16
+ import { Input } from "@/components/ui/input";
17
+ import { Checkbox } from "@/components/ui/checkbox";
18
+
19
+ export default function PrimitiveFieldsForm({ recordId }: { recordId?: string }) {
20
+ const fieldTest = useMemo(() => new AdminFieldTest(), []);
21
+
22
+ const {
23
+ register,
24
+ handleSubmit,
25
+ errors,
26
+ watch,
27
+ setValue,
28
+ isLoading,
29
+ isSubmitting,
30
+ }: UseBDOFormReturnType<AdminFieldTest> = useBDOForm({
31
+ bdo: fieldTest,
32
+ recordId,
33
+ });
34
+
35
+ if (isLoading) return <p>Loading...</p>;
36
+
37
+ return (
38
+ <form onSubmit={handleSubmit((data) => console.log("Saved", data._id))}>
39
+ {/* ───────────────────────────────────────────────── */}
40
+ {/* StringField — register() for native text inputs */}
41
+ {/* ───────────────────────────────────────────────── */}
42
+ <Field>
43
+ <FieldLabel>
44
+ {fieldTest.Name.label} {fieldTest.Name.required && <span>*</span>}
45
+ </FieldLabel>
46
+ <FieldContent>
47
+ <Input
48
+ {...register(fieldTest.Name.id)}
49
+ maxLength={fieldTest.Name.length}
50
+ placeholder="Enter name"
51
+ />
52
+ {fieldTest.Name.length && (
53
+ <span className="text-xs text-gray-500">
54
+ Max {fieldTest.Name.length} characters
55
+ </span>
56
+ )}
57
+ </FieldContent>
58
+ {errors.Name && <FieldError>{errors.Name.message}</FieldError>}
59
+ </Field>
60
+
61
+ {/* ───────────────────────────────────────────────── */}
62
+ {/* NumberField — register() with type="number" */}
63
+ {/* Derive step from fractionPart */}
64
+ {/* ───────────────────────────────────────────────── */}
65
+ <Field>
66
+ <FieldLabel>
67
+ {fieldTest.Amount.label} {fieldTest.Amount.required && <span>*</span>}
68
+ </FieldLabel>
69
+ <FieldContent>
70
+ <Input
71
+ type="number"
72
+ step={
73
+ fieldTest.Amount.fractionPart
74
+ ? `0.${"0".repeat(fieldTest.Amount.fractionPart - 1)}1`
75
+ : "1"
76
+ }
77
+ {...register(fieldTest.Amount.id)}
78
+ placeholder="0.00"
79
+ />
80
+ <span className="text-xs text-gray-500">
81
+ IntegerPart: {fieldTest.Amount.integerPart}, FractionPart: {fieldTest.Amount.fractionPart ?? "none"}
82
+ </span>
83
+ </FieldContent>
84
+ {errors.Amount && <FieldError>{errors.Amount.message}</FieldError>}
85
+ </Field>
86
+
87
+ {/* Integer-only number (no fractionPart → step="1") */}
88
+ <Field>
89
+ <FieldLabel>{fieldTest.Quantity.label}</FieldLabel>
90
+ <FieldContent>
91
+ <Input
92
+ type="number"
93
+ step="1"
94
+ {...register(fieldTest.Quantity.id)}
95
+ placeholder="0"
96
+ />
97
+ </FieldContent>
98
+ {errors.Quantity && <FieldError>{errors.Quantity.message}</FieldError>}
99
+ </Field>
100
+
101
+ {/* ───────────────────────────────────────────────── */}
102
+ {/* BooleanField — watch() + setValue(), NOT register */}
103
+ {/* Checkbox doesn't fire native change events */}
104
+ {/* ───────────────────────────────────────────────── */}
105
+ <Field>
106
+ <div className="flex items-center gap-3">
107
+ <Checkbox
108
+ id={fieldTest.IsEnabled.id}
109
+ checked={watch(fieldTest.IsEnabled.id) ?? false}
110
+ onCheckedChange={(checked) =>
111
+ setValue(fieldTest.IsEnabled.id, checked === true)
112
+ }
113
+ />
114
+ <FieldLabel htmlFor={fieldTest.IsEnabled.id}>
115
+ {fieldTest.IsEnabled.label}
116
+ </FieldLabel>
117
+ </div>
118
+ </Field>
119
+
120
+ {/* ───────────────────────────────────────────────── */}
121
+ {/* DateField — register() with type="date" */}
122
+ {/* Strict YYYY-MM-DD — never default to "" */}
123
+ {/* ───────────────────────────────────────────────── */}
124
+ <Field>
125
+ <FieldLabel>
126
+ {fieldTest.StartDate.label} {fieldTest.StartDate.required && <span>*</span>}
127
+ </FieldLabel>
128
+ <FieldContent>
129
+ <Input type="date" {...register(fieldTest.StartDate.id)} />
130
+ </FieldContent>
131
+ {errors.StartDate && <FieldError>{errors.StartDate.message}</FieldError>}
132
+ </Field>
133
+
134
+ {/* ───────────────────────────────────────────────── */}
135
+ {/* DateTimeField — register() with datetime-local */}
136
+ {/* Add step for Millisecond precision */}
137
+ {/* ───────────────────────────────────────────────── */}
138
+ <Field>
139
+ <FieldLabel>{fieldTest.CreatedOn.label}</FieldLabel>
140
+ <FieldContent>
141
+ <Input
142
+ type="datetime-local"
143
+ step={fieldTest.CreatedOn.precision === "Millisecond" ? "0.001" : undefined}
144
+ {...register(fieldTest.CreatedOn.id)}
145
+ />
146
+ <span className="text-xs text-gray-500">
147
+ Precision: {fieldTest.CreatedOn.precision}
148
+ </span>
149
+ </FieldContent>
150
+ {errors.CreatedOn && <FieldError>{errors.CreatedOn.message}</FieldError>}
151
+ </Field>
152
+
153
+ {/* ───────────────────────────────────────────────── */}
154
+ {/* TextField — register() with <textarea> */}
155
+ {/* Conditional rendering based on format */}
156
+ {/* ───────────────────────────────────────────────── */}
157
+ <Field>
158
+ <FieldLabel>{fieldTest.Description.label}</FieldLabel>
159
+ <FieldContent>
160
+ <textarea
161
+ {...register(fieldTest.Description.id)}
162
+ rows={4}
163
+ placeholder="Enter plain text description"
164
+ className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm"
165
+ />
166
+ <span className="text-xs text-gray-500">
167
+ Format: {fieldTest.Description.format}
168
+ </span>
169
+ </FieldContent>
170
+ {errors.Description && <FieldError>{errors.Description.message}</FieldError>}
171
+ </Field>
172
+
173
+ {/* Markdown text field — use a markdown editor */}
174
+ <Field>
175
+ <FieldLabel>{fieldTest.RichContent.label}</FieldLabel>
176
+ <FieldContent>
177
+ {fieldTest.RichContent.format === "Markdown" ? (
178
+ <textarea
179
+ {...register(fieldTest.RichContent.id)}
180
+ rows={6}
181
+ placeholder="Enter markdown content..."
182
+ className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-mono"
183
+ />
184
+ ) : (
185
+ <textarea
186
+ {...register(fieldTest.RichContent.id)}
187
+ rows={4}
188
+ className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm"
189
+ />
190
+ )}
191
+ <span className="text-xs text-gray-500">
192
+ Format: {fieldTest.RichContent.format}
193
+ </span>
194
+ </FieldContent>
195
+ {errors.RichContent && <FieldError>{errors.RichContent.message}</FieldError>}
196
+ </Field>
197
+
198
+ <button type="submit" disabled={isSubmitting}>
199
+ {isSubmitting ? "Saving..." : "Save"}
200
+ </button>
201
+ </form>
202
+ );
203
+ }
204
+ ```
205
+
206
+ ## Key Patterns
207
+
208
+ - **`register()` works for** String, Number, Date, DateTime, Text — native HTML inputs that fire change events
209
+ - **`watch()` + `setValue()` needed for** Boolean — Checkbox components don't fire native change events, so `register()` won't pick up value changes
210
+ - **Constraint-derived getters**:
211
+ - `field.length` — max characters (StringField)
212
+ - `field.integerPart` / `field.fractionPart` — numeric precision (NumberField)
213
+ - `field.precision` — `"Second"` or `"Millisecond"` (DateTimeField)
214
+ - `field.format` — `"Plain"` or `"Markdown"` (TextField)
215
+ - **Never default Date/DateTime to empty string** — Use `undefined` for unset dates. Empty strings (`""`) fail DateField validation (`YYYY-MM-DD` regex) and produce confusing errors
216
+ - **Derive `step` from `fractionPart`** — `fractionPart: 2` → `step="0.01"`, no `fractionPart` → `step="1"` (integer only)
217
+ - **Conditional rendering for TextField** — Check `field.format` to switch between `<textarea>` and a markdown editor
@@ -0,0 +1,76 @@
1
+ # Approve Leave Request
2
+
3
+ > Manager reviews readonly employee data and approves/rejects using `useActivityForm`.
4
+
5
+ ```tsx
6
+ import { useMemo } from "react";
7
+ import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
8
+ import type { UseActivityFormReturn } from "@ram_28/kf-ai-sdk/workflow";
9
+ import { ManagerApprovalActivity } from "@/workflow/leave";
10
+
11
+ export default function ApprovalForm({ instanceId, onClose }: { instanceId: string; onClose: () => void }) {
12
+ const activity = useMemo(() => new ManagerApprovalActivity(), []);
13
+
14
+ const { register, handleSubmit, errors, isLoading, isSubmitting, watch, setValue }: UseActivityFormReturn<ManagerApprovalActivity> =
15
+ useActivityForm(activity, { activity_instance_id: instanceId, mode: "onBlur" });
16
+
17
+ if (isLoading) return <p>Loading...</p>;
18
+
19
+ return (
20
+ <div>
21
+ {/* Readonly context fields — auto-disabled by register() */}
22
+ <div>
23
+ <label>{activity.EmployeeName.label}</label>
24
+ <input {...register(activity.EmployeeName.id)} className="bg-gray-100" />
25
+ </div>
26
+ <div>
27
+ <label>{activity.StartDate.label}</label>
28
+ <input type="date" {...register(activity.StartDate.id)} className="bg-gray-100" />
29
+ </div>
30
+ <div>
31
+ <label>{activity.EndDate.label}</label>
32
+ <input type="date" {...register(activity.EndDate.id)} className="bg-gray-100" />
33
+ </div>
34
+ <div>
35
+ <label>{activity.LeaveDays.label}</label>
36
+ <input type="number" {...register(activity.LeaveDays.id)} className="bg-gray-100" />
37
+ </div>
38
+
39
+ <hr />
40
+
41
+ {/* Editable manager decision fields */}
42
+ <div>
43
+ <input
44
+ type="checkbox"
45
+ checked={watch(activity.ManagerApproved.id) ?? false}
46
+ onChange={(e) => setValue(activity.ManagerApproved.id, e.target.checked)}
47
+ />
48
+ <label>
49
+ {activity.ManagerApproved.label}
50
+ {activity.ManagerApproved.required && <span> *</span>}
51
+ </label>
52
+ {errors.ManagerApproved && <p>{errors.ManagerApproved.message}</p>}
53
+ </div>
54
+
55
+ <div>
56
+ <label>{activity.ManagerReason.label}</label>
57
+ <textarea {...register(activity.ManagerReason.id)} rows={3} placeholder="Reason for approval or rejection..." />
58
+ {errors.ManagerReason && <p>{errors.ManagerReason.message}</p>}
59
+ </div>
60
+
61
+ <button onClick={onClose}>Cancel</button>
62
+ <button disabled={isSubmitting} onClick={handleSubmit(() => onClose(), console.error)}>
63
+ {watch(activity.ManagerApproved.id) ? "Approve" : "Reject"} & Submit
64
+ </button>
65
+ </div>
66
+ );
67
+ }
68
+ ```
69
+
70
+ ## Key Patterns
71
+
72
+ - **Readonly context fields** -- fields from the prior employee activity have `ReadOnly: true` and are auto-disabled by `register()`
73
+ - **Editable decision fields** -- `ManagerApproved` and `ManagerReason` are the only editable fields
74
+ - **Checkbox with `watch()` + `setValue()`** -- booleans use `e.target.checked`, not `e.target.value`
75
+ - **Dynamic submit label** -- `watch(activity.ManagerApproved.id)` drives the button text
76
+ - **`handleSubmit` completes the approval** -- validates, syncs remaining fields, then completes the activity
@@ -0,0 +1,101 @@
1
+ # Filtered Activity Table
2
+
3
+ > Filter activity instances by date range using `useActivityTable` with the integrated `table.filter` API.
4
+
5
+ ```tsx
6
+ import { useState, useMemo } from "react";
7
+ import { useActivityTable, ActivityTableStatus } from "@ram_28/kf-ai-sdk/workflow";
8
+ import type { UseActivityTableReturnType } from "@ram_28/kf-ai-sdk/workflow";
9
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
10
+ import { EmployeeInputActivity } from "@/workflow/leave";
11
+
12
+ export default function FilteredActivityTable() {
13
+ const activity = useMemo(() => new EmployeeInputActivity(), []);
14
+ const table: UseActivityTableReturnType<EmployeeInputActivity> = useActivityTable({ activity, status: ActivityTableStatus.InProgress });
15
+
16
+ // ── Date range filter on StartDate ─────────────────────────────
17
+ const [startDateFrom, setStartDateFrom] = useState("");
18
+ const [startDateTo, setStartDateTo] = useState("");
19
+ const [fromConditionId, setFromConditionId] = useState<string | null>(null);
20
+ const [toConditionId, setToConditionId] = useState<string | null>(null);
21
+
22
+ const applyDateFilter = () => {
23
+ if (fromConditionId) { table.filter.removeCondition(fromConditionId); setFromConditionId(null); }
24
+ if (toConditionId) { table.filter.removeCondition(toConditionId); setToConditionId(null); }
25
+
26
+ if (startDateFrom) {
27
+ const id = table.filter.addCondition({
28
+ LHSField: activity.StartDate.id,
29
+ Operator: ConditionOperator.GTE,
30
+ RHSType: RHSType.Constant,
31
+ RHSValue: startDateFrom,
32
+ });
33
+ setFromConditionId(id);
34
+ }
35
+ if (startDateTo) {
36
+ const id = table.filter.addCondition({
37
+ LHSField: activity.StartDate.id,
38
+ Operator: ConditionOperator.LTE,
39
+ RHSType: RHSType.Constant,
40
+ RHSValue: startDateTo,
41
+ });
42
+ setToConditionId(id);
43
+ }
44
+ };
45
+
46
+ const clearDateFilter = () => {
47
+ if (fromConditionId) { table.filter.removeCondition(fromConditionId); setFromConditionId(null); }
48
+ if (toConditionId) { table.filter.removeCondition(toConditionId); setToConditionId(null); }
49
+ setStartDateFrom("");
50
+ setStartDateTo("");
51
+ };
52
+
53
+ if (table.isLoading) return <p>Loading...</p>;
54
+
55
+ return (
56
+ <div>
57
+ {/* Date range filter */}
58
+ <div>
59
+ <label>From</label>
60
+ <input type="date" value={startDateFrom} onChange={(e) => setStartDateFrom(e.target.value)} />
61
+ <label>To</label>
62
+ <input type="date" value={startDateTo} onChange={(e) => setStartDateTo(e.target.value)} />
63
+ <button onClick={applyDateFilter}>Apply</button>
64
+ {table.filter.hasConditions && <button onClick={clearDateFilter}>Clear</button>}
65
+ </div>
66
+
67
+ {/* Table */}
68
+ <table>
69
+ <thead>
70
+ <tr>
71
+ <th>{activity.StartDate.label}</th>
72
+ <th>{activity.EndDate.label}</th>
73
+ <th>{activity.LeaveType.label}</th>
74
+ <th>{activity.LeaveDays.label}</th>
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ {table.rows.map((row) => (
79
+ <tr key={row._id}>
80
+ <td>{row.StartDate.get()}</td>
81
+ <td>{row.EndDate.get()}</td>
82
+ <td>{row.LeaveType.get()}</td>
83
+ <td>{row.LeaveDays.get()}</td>
84
+ </tr>
85
+ ))}
86
+ </tbody>
87
+ </table>
88
+
89
+ <span>Page {table.pagination.pageNo} of {table.pagination.totalPages}</span>
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ## Key Patterns
96
+
97
+ - **Date range with GTE + LTE** -- two separate conditions tracked by their own IDs
98
+ - **`addCondition()` returns an ID** -- store it to later remove the condition with `removeCondition(id)`
99
+ - **`table.filter.hasConditions`** -- show a "Clear" button only when filters are active
100
+ - **Filter changes auto-reset pagination** -- the table resets to page 1 when conditions change
101
+ - **Same filter API as `useBDOTable`** -- `table.filter` works identically in both BDO and Activity tables
@@ -0,0 +1,90 @@
1
+ # My Pending Requests
2
+
3
+ > Employee views leave requests in In Progress / Completed tabs using `useActivityTable`.
4
+
5
+ ```tsx
6
+ import { useState, useMemo } from "react";
7
+ import { useActivityTable, ActivityTableStatus } from "@ram_28/kf-ai-sdk/workflow";
8
+ import type { UseActivityTableReturnType } from "@ram_28/kf-ai-sdk/workflow";
9
+ import { EmployeeInputActivity } from "@/workflow/leave";
10
+
11
+ export default function MyPendingRequests({ onOpenForm }: { onOpenForm: (instanceId: string, bpInstanceId: string) => void }) {
12
+ const [status, setStatus] = useState(ActivityTableStatus.InProgress);
13
+ const activity = useMemo(() => new EmployeeInputActivity(), []);
14
+
15
+ const table: UseActivityTableReturnType<EmployeeInputActivity> = useActivityTable({
16
+ activity,
17
+ status,
18
+ initialState: { sort: [{ StartDate: "DESC" }], pagination: { pageNo: 1, pageSize: 10 } },
19
+ });
20
+
21
+ if (table.isLoading) return <p>Loading...</p>;
22
+ if (table.error) return <p>Error: {table.error.message}</p>;
23
+
24
+ return (
25
+ <div>
26
+ {/* Tab switcher */}
27
+ <div>
28
+ <button onClick={() => setStatus(ActivityTableStatus.InProgress)}
29
+ style={{ fontWeight: status === ActivityTableStatus.InProgress ? "bold" : "normal" }}>
30
+ My Requests
31
+ </button>
32
+ <button onClick={() => setStatus(ActivityTableStatus.Completed)}
33
+ style={{ fontWeight: status === ActivityTableStatus.Completed ? "bold" : "normal" }}>
34
+ Completed
35
+ </button>
36
+ </div>
37
+
38
+ <table>
39
+ <thead>
40
+ <tr>
41
+ <th onClick={() => table.sort.toggle(activity.StartDate.id)} style={{ cursor: "pointer" }}>
42
+ {activity.StartDate.label}
43
+ </th>
44
+ <th>{activity.EndDate.label}</th>
45
+ <th>{activity.LeaveType.label}</th>
46
+ <th>{activity.LeaveDays.label}</th>
47
+ <th>Status</th>
48
+ {status === ActivityTableStatus.Completed && <th>Completed At</th>}
49
+ </tr>
50
+ </thead>
51
+ <tbody>
52
+ {table.rows.map((row) => (
53
+ <tr
54
+ key={row._id}
55
+ style={{ cursor: status === ActivityTableStatus.InProgress ? "pointer" : "default" }}
56
+ onClick={() => {
57
+ if (status === ActivityTableStatus.InProgress) {
58
+ onOpenForm(row._id, row.BPInstanceId.get());
59
+ }
60
+ }}
61
+ >
62
+ <td>{row.StartDate.get()}</td>
63
+ <td>{row.EndDate.get()}</td>
64
+ <td>{row.LeaveType.get()}</td>
65
+ <td>{row.LeaveDays.get()}</td>
66
+ <td>{row.Status.get()}</td>
67
+ {status === ActivityTableStatus.Completed && <td>{row.CompletedAt.get()}</td>}
68
+ </tr>
69
+ ))}
70
+ </tbody>
71
+ </table>
72
+
73
+ {/* Pagination */}
74
+ <div>
75
+ <button onClick={table.pagination.goToPrevious} disabled={!table.pagination.canGoPrevious}>Previous</button>
76
+ <span>Page {table.pagination.pageNo} of {table.pagination.totalPages}</span>
77
+ <button onClick={table.pagination.goToNext} disabled={!table.pagination.canGoNext}>Next</button>
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## Key Patterns
85
+
86
+ - **`ActivityTableStatus.InProgress` / `Completed`** -- changing `status` state triggers an automatic refetch
87
+ - **Activity system fields** -- `row.Status.get()`, `row.CompletedAt.get()`, `row.BPInstanceId.get()` are available on every row
88
+ - **Conditional `CompletedAt` column** -- only rendered in the Completed tab
89
+ - **Clickable rows only in InProgress** -- completed rows are read-only and don't open a form dialog
90
+ - **`row.BPInstanceId.get()`** -- passed along when opening a form, needed for workflow progress tracking
@@ -0,0 +1,47 @@
1
+ # Start New Workflow
2
+
3
+ > "New Request" button calls `workflow.start()` and opens the form with the returned instance ID.
4
+
5
+ ```tsx
6
+ import { useState, useMemo, useCallback } from "react";
7
+ import { Workflow } from "@ram_28/kf-ai-sdk/workflow";
8
+ import type { WorkflowStartResponseType } from "@ram_28/kf-ai-sdk/workflow";
9
+ import type { EmployeeInputEntityType } from "@/workflow/leave";
10
+
11
+ export default function StartNewWorkflow({ onOpenForm }: { onOpenForm: (instanceId: string, bpInstanceId: string) => void }) {
12
+ const [isStarting, setIsStarting] = useState(false);
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ const workflow = useMemo(() => new Workflow<EmployeeInputEntityType>("SimpleLeaveProcess"), []);
16
+
17
+ const handleNewRequest = useCallback(async () => {
18
+ try {
19
+ setIsStarting(true);
20
+ setError(null);
21
+ const result: WorkflowStartResponseType = await workflow.start();
22
+ // result: { BPInstanceId, ActivityId, _id }
23
+ onOpenForm(result._id, result.BPInstanceId);
24
+ } catch (err) {
25
+ setError(err instanceof Error ? err.message : "Failed to start workflow");
26
+ } finally {
27
+ setIsStarting(false);
28
+ }
29
+ }, [workflow, onOpenForm]);
30
+
31
+ return (
32
+ <div>
33
+ <button onClick={handleNewRequest} disabled={isStarting}>
34
+ {isStarting ? "Starting..." : "New Request"}
35
+ </button>
36
+ {error && <p>{error}</p>}
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Key Patterns
43
+
44
+ - **`workflow.start()`** -- creates a new process instance and returns `{ BPInstanceId, ActivityId, _id }`
45
+ - **`BPInstanceId`** -- identifies the workflow instance; used for `workflow.progress()`
46
+ - **`_id`** -- the first activity instance ID; passed to `useActivityForm` as `activity_instance_id`
47
+ - **Workflow constructor** -- `new Workflow<TEntity>("BusinessProcessId")` takes the process ID string
@@ -0,0 +1,72 @@
1
+ # Submit Leave Request
2
+
3
+ > Employee fills in dates, leave type, and reason, then submits using `useActivityForm`.
4
+
5
+ ```tsx
6
+ import { useMemo } from "react";
7
+ import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
8
+ import type { UseActivityFormReturn } from "@ram_28/kf-ai-sdk/workflow";
9
+ import { EmployeeInputActivity } from "@/workflow/leave";
10
+
11
+ export default function LeaveRequestForm({ instanceId, onClose }: { instanceId: string; onClose: () => void }) {
12
+ const activity = useMemo(() => new EmployeeInputActivity(), []);
13
+
14
+ const { register, handleSubmit, errors, isLoading, isSubmitting, watch, setValue }: UseActivityFormReturn<EmployeeInputActivity> =
15
+ useActivityForm(activity, { activity_instance_id: instanceId, mode: "onBlur" });
16
+
17
+ if (isLoading) return <p>Loading...</p>;
18
+
19
+ return (
20
+ <form>
21
+ <div>
22
+ <label>{activity.StartDate.label} {activity.StartDate.required && <span>*</span>}</label>
23
+ <input type="date" {...register(activity.StartDate.id)} />
24
+ {errors.StartDate && <p>{errors.StartDate.message}</p>}
25
+ </div>
26
+
27
+ <div>
28
+ <label>{activity.EndDate.label} {activity.EndDate.required && <span>*</span>}</label>
29
+ <input type="date" {...register(activity.EndDate.id)} />
30
+ {errors.EndDate && <p>{errors.EndDate.message}</p>}
31
+ </div>
32
+
33
+ {/* Select field — watch/setValue, not register */}
34
+ <div>
35
+ <label>{activity.LeaveType.label} {activity.LeaveType.required && <span>*</span>}</label>
36
+ <select value={watch(activity.LeaveType.id) ?? ""} onChange={(e) => setValue(activity.LeaveType.id, e.target.value)}>
37
+ <option value="">Select leave type</option>
38
+ {activity.LeaveType.options.map((opt) => (
39
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
40
+ ))}
41
+ </select>
42
+ {errors.LeaveType && <p>{errors.LeaveType.message}</p>}
43
+ </div>
44
+
45
+ <div>
46
+ <label>{activity.Reason.label}</label>
47
+ <textarea {...register(activity.Reason.id)} rows={3} placeholder="Reason for leave..." />
48
+ {errors.Reason && <p>{errors.Reason.message}</p>}
49
+ </div>
50
+
51
+ {/* Readonly computed field — auto-disabled by register() */}
52
+ <div>
53
+ <label>{activity.LeaveDays.label} (computed)</label>
54
+ <input type="number" {...register(activity.LeaveDays.id)} />
55
+ </div>
56
+
57
+ <button type="button" onClick={onClose}>Cancel</button>
58
+ <button type="button" disabled={isSubmitting} onClick={handleSubmit(() => onClose(), console.error)}>
59
+ Submit Request
60
+ </button>
61
+ </form>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ## Key Patterns
67
+
68
+ - **`useActivityForm(activity, { activity_instance_id })`** -- the instance ID comes from `workflow.start()` or a table row
69
+ - **`handleSubmit` completes the activity** -- validates, sends dirty fields, then completes to advance the workflow
70
+ - **Readonly fields auto-disabled** -- `LeaveDays` has `ReadOnly: true`, so `register()` returns `{ disabled: true }`
71
+ - **`watch()` + `setValue()` for selects** -- custom components that don't fire native change events
72
+ - **Per-field sync** -- changes are auto-saved on blur/change; no manual save button needed
@@ -0,0 +1,49 @@
1
+ # Workflow Progress
2
+
3
+ > Display progress badges for each activity in a workflow instance using `workflow.progress()`.
4
+
5
+ ```tsx
6
+ import { useMemo } from "react";
7
+ import { useQuery } from "@tanstack/react-query";
8
+ import { Workflow } from "@ram_28/kf-ai-sdk/workflow";
9
+ import type { ActivityProgressType } from "@ram_28/kf-ai-sdk/workflow";
10
+ import type { EmployeeInputEntityType } from "@/workflow/leave";
11
+
12
+ export default function WorkflowProgressBadges({ bpInstanceId }: { bpInstanceId: string }) {
13
+ const workflow = useMemo(() => new Workflow<EmployeeInputEntityType>("SimpleLeaveProcess"), []);
14
+
15
+ const { data: progress } = useQuery<ActivityProgressType[]>({
16
+ queryKey: ["workflow-progress", bpInstanceId],
17
+ queryFn: () => workflow.progress(bpInstanceId),
18
+ staleTime: 0,
19
+ });
20
+
21
+ if (!progress) return null;
22
+
23
+ return (
24
+ <div style={{ display: "flex", gap: "8px" }}>
25
+ {progress.map((entry: ActivityProgressType) => (
26
+ <span
27
+ key={entry.ActivityId}
28
+ style={{
29
+ padding: "4px 12px",
30
+ borderRadius: "12px",
31
+ fontSize: "12px",
32
+ backgroundColor: entry.Status === "COMPLETED" ? "#dcfce7" : "#fef9c3",
33
+ color: entry.Status === "COMPLETED" ? "#166534" : "#854d0e",
34
+ }}
35
+ >
36
+ {entry._name} — {entry.Status === "COMPLETED" ? "Done" : "In Progress"}
37
+ </span>
38
+ ))}
39
+ </div>
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## Key Patterns
45
+
46
+ - **`workflow.progress(bpInstanceId)`** -- returns `ActivityProgressType[]`, one entry per activity in the process
47
+ - **`ActivityProgressType`** -- each entry has `ActivityId`, `_name`, `Status` (`"COMPLETED"` or `"IN_PROGRESS"`), `CompletedAt`, `CompletedBy`
48
+ - **`BPInstanceId`** -- comes from `workflow.start()` (new requests) or `row.BPInstanceId.get()` (existing table rows)
49
+ - **`staleTime: 0`** -- always refetch progress when the component mounts (progress changes as activities complete)