@ram_28/kf-ai-sdk 2.0.19 → 2.0.20-beta.1

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 (93) hide show
  1. package/README.md +8 -16
  2. package/dist/api.cjs +1 -1
  3. package/dist/api.mjs +1 -1
  4. package/dist/auth/authConfig.d.ts +1 -1
  5. package/dist/auth/types.d.ts +1 -1
  6. package/dist/auth/types.d.ts.map +1 -1
  7. package/dist/auth.cjs +1 -1
  8. package/dist/auth.mjs +1 -1
  9. package/dist/bdo/core/Item.d.ts.map +1 -1
  10. package/dist/bdo.cjs +1 -1
  11. package/dist/bdo.mjs +32 -32
  12. package/dist/components/hooks/useActivityForm/types.d.ts +5 -4
  13. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
  14. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
  15. package/dist/components/hooks/useActivityTable/types.d.ts +4 -5
  16. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
  17. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
  18. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +2 -2
  19. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
  20. package/dist/components/hooks/useBDOTable/types.d.ts +12 -20
  21. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
  22. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
  23. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
  24. package/dist/{constants-Cyi942Yr.js → constants-ConHc1oS.js} +5 -5
  25. package/dist/constants-QX2RX-wu.cjs +1 -0
  26. package/dist/filter.cjs +1 -1
  27. package/dist/filter.mjs +1 -1
  28. package/dist/form.cjs +1 -1
  29. package/dist/form.mjs +1 -1
  30. package/dist/table.cjs +1 -1
  31. package/dist/table.mjs +16 -15
  32. package/dist/table.types.d.ts +1 -1
  33. package/dist/table.types.d.ts.map +1 -1
  34. package/dist/types/constants.d.ts +1 -1
  35. package/dist/workflow/Activity.d.ts +5 -8
  36. package/dist/workflow/Activity.d.ts.map +1 -1
  37. package/dist/workflow.cjs +1 -1
  38. package/dist/workflow.mjs +476 -461
  39. package/docs/api.md +95 -0
  40. package/docs/bdo.md +224 -0
  41. package/docs/gaps.md +360 -0
  42. package/docs/useActivityForm.md +393 -0
  43. package/docs/useActivityTable.md +418 -0
  44. package/docs/useBDOForm.md +498 -0
  45. package/docs/useBDOTable.md +284 -0
  46. package/docs/useFilter.md +188 -0
  47. package/docs/workflow.md +560 -0
  48. package/package.json +14 -15
  49. package/sdk/auth/authConfig.ts +1 -1
  50. package/sdk/auth/types.ts +1 -1
  51. package/sdk/bdo/core/Item.ts +1 -2
  52. package/sdk/components/hooks/useActivityForm/types.ts +6 -4
  53. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +73 -10
  54. package/sdk/components/hooks/useActivityTable/types.ts +5 -4
  55. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +8 -10
  56. package/sdk/components/hooks/useBDOForm/createItemProxy.ts +5 -9
  57. package/sdk/components/hooks/useBDOTable/types.ts +10 -20
  58. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +8 -12
  59. package/sdk/table.types.ts +0 -2
  60. package/sdk/types/constants.ts +1 -1
  61. package/sdk/workflow/Activity.ts +7 -39
  62. package/dist/constants-DEmYwKfC.cjs +0 -1
  63. package/docs/README.md +0 -57
  64. package/docs/bdo/README.md +0 -161
  65. package/docs/bdo/api_reference.md +0 -281
  66. package/docs/examples/bdo/create-product.md +0 -69
  67. package/docs/examples/bdo/edit-product-dialog.md +0 -95
  68. package/docs/examples/bdo/filtered-product-table.md +0 -100
  69. package/docs/examples/bdo/product-listing.md +0 -73
  70. package/docs/examples/bdo/supplier-dropdown.md +0 -60
  71. package/docs/examples/fields/complex-fields.md +0 -248
  72. package/docs/examples/fields/primitive-fields.md +0 -217
  73. package/docs/examples/workflow/approve-leave-request.md +0 -76
  74. package/docs/examples/workflow/filtered-activity-table.md +0 -101
  75. package/docs/examples/workflow/my-pending-requests.md +0 -90
  76. package/docs/examples/workflow/start-new-workflow.md +0 -47
  77. package/docs/examples/workflow/submit-leave-request.md +0 -72
  78. package/docs/examples/workflow/workflow-progress.md +0 -49
  79. package/docs/fields/README.md +0 -141
  80. package/docs/fields/api_reference.md +0 -134
  81. package/docs/useActivityForm/README.md +0 -244
  82. package/docs/useActivityForm/api_reference.md +0 -279
  83. package/docs/useActivityTable/README.md +0 -263
  84. package/docs/useActivityTable/api_reference.md +0 -294
  85. package/docs/useBDOForm/README.md +0 -175
  86. package/docs/useBDOForm/api_reference.md +0 -244
  87. package/docs/useBDOTable/README.md +0 -242
  88. package/docs/useBDOTable/api_reference.md +0 -253
  89. package/docs/useFilter/README.md +0 -323
  90. package/docs/useFilter/api_reference.md +0 -228
  91. package/docs/workflow/README.md +0 -158
  92. package/docs/workflow/api_reference.md +0 -161
  93. /package/docs/{useAuth/README.md → useAuth.md} +0 -0
@@ -1,248 +0,0 @@
1
- # Complex Fields
2
-
3
- > Complete form demonstrating selection, reference, and attachment fields with async options loading and pre-built UI components: Select, Reference, User, File, and Image.
4
-
5
- ```tsx
6
- import { useState, useMemo } from "react";
7
- import { useQuery } from "@tanstack/react-query";
8
- import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
9
- import type { UseBDOFormReturnType } from "@ram_28/kf-ai-sdk/form/types";
10
- import type { UserFieldType } from "@ram_28/kf-ai-sdk/types";
11
- import { AdminFieldTest } from "@/bdo/admin/FieldTest";
12
- import type { FieldTestSupplierRefType } from "@/bdo/entities/field-test";
13
- import {
14
- Field,
15
- FieldContent,
16
- FieldLabel,
17
- FieldError,
18
- } from "@/components/ui/field";
19
- import {
20
- Select,
21
- SelectContent,
22
- SelectItem,
23
- SelectTrigger,
24
- SelectValue,
25
- } from "@/components/ui/select";
26
- import { ReferenceSelect } from "@/components/ui/reference-select";
27
- import { FileUpload } from "@/components/ui/file-upload";
28
- import { ImageUpload } from "@/components/ui/image-upload";
29
- import { FilePreview } from "@/components/ui/file-preview";
30
- import { ImageThumbnail } from "@/components/ui/image-thumbnail";
31
-
32
- export default function ComplexFieldsForm({ recordId }: { recordId?: string }) {
33
- const fieldTest = useMemo(() => new AdminFieldTest(), []);
34
-
35
- const {
36
- watch,
37
- setValue,
38
- handleSubmit,
39
- errors,
40
- item,
41
- isLoading,
42
- isSubmitting,
43
- }: UseBDOFormReturnType<AdminFieldTest> = useBDOForm({
44
- bdo: fieldTest,
45
- recordId,
46
- });
47
-
48
- // Dropdown open state for lazy-loading
49
- const [userDropdownOpen, setUserDropdownOpen] = useState(false);
50
-
51
- // ─── Async Options ─────────────────────────────────────────
52
-
53
- // User field options — lazy-load when dropdown opens
54
- const { data: users = [], isFetching: isUsersLoading } = useQuery<
55
- UserFieldType[]
56
- >({
57
- queryKey: ["fieldtest-user-options", item._id],
58
- queryFn: () => fieldTest.AssignedUser.fetchOptions(item._id!),
59
- enabled: userDropdownOpen && !!item._id,
60
- staleTime: Infinity,
61
- });
62
-
63
- if (isLoading) return <p>Loading...</p>;
64
-
65
- return (
66
- <form onSubmit={handleSubmit((data) => console.log("Saved", data._id))}>
67
- {/* ───────────────────────────────────────────────── */}
68
- {/* SelectField — static options from Constraint.Enum */}
69
- {/* watch() + setValue(), NOT register() */}
70
- {/* ───────────────────────────────────────────────── */}
71
- <Field>
72
- <FieldLabel>
73
- {fieldTest.Status.label} {fieldTest.Status.required && <span>*</span>}
74
- </FieldLabel>
75
- <FieldContent>
76
- <Select
77
- value={watch(fieldTest.Status.id) ?? ""}
78
- onValueChange={(value) => setValue(fieldTest.Status.id, value)}
79
- >
80
- <SelectTrigger>
81
- <SelectValue placeholder="Select status" />
82
- </SelectTrigger>
83
- <SelectContent>
84
- {fieldTest.Status.options.map((opt) => (
85
- <SelectItem key={opt.value} value={opt.value}>
86
- {opt.label}
87
- </SelectItem>
88
- ))}
89
- </SelectContent>
90
- </Select>
91
- </FieldContent>
92
- {errors.Status && <FieldError>{errors.Status.message}</FieldError>}
93
- </Field>
94
-
95
- {/* ───────────────────────────────────────────────── */}
96
- {/* ReferenceField — pre-built <ReferenceSelect> */}
97
- {/* Abstracts fetchOptions + dropdown UI */}
98
- {/* ───────────────────────────────────────────────── */}
99
- <Field>
100
- <FieldLabel>{fieldTest.SupplierRef.label}</FieldLabel>
101
- <FieldContent>
102
- <ReferenceSelect
103
- bdoField={fieldTest.SupplierRef}
104
- instanceId={item._id}
105
- value={watch(fieldTest.SupplierRef.id)}
106
- onChange={(supplier) =>
107
- setValue(
108
- fieldTest.SupplierRef.id,
109
- supplier as FieldTestSupplierRefType,
110
- )
111
- }
112
- />
113
- </FieldContent>
114
- {errors.SupplierRef && <FieldError>{errors.SupplierRef.message}</FieldError>}
115
- </Field>
116
-
117
- {/* ───────────────────────────────────────────────── */}
118
- {/* UserField — fetchOptions() + manual dropdown */}
119
- {/* Value shape: { _id: string; _name: string } */}
120
- {/* ───────────────────────────────────────────────── */}
121
- <Field>
122
- <FieldLabel>{fieldTest.AssignedUser.label}</FieldLabel>
123
- <FieldContent>
124
- <Select
125
- value={watch(fieldTest.AssignedUser.id)?._id ?? ""}
126
- onValueChange={(value) => {
127
- const user = users.find((u) => u._id === value);
128
- if (user) setValue(fieldTest.AssignedUser.id, user);
129
- }}
130
- open={userDropdownOpen}
131
- onOpenChange={setUserDropdownOpen}
132
- >
133
- <SelectTrigger>
134
- <SelectValue placeholder="Select user">
135
- {watch(fieldTest.AssignedUser.id)?._name ?? "Select user"}
136
- </SelectValue>
137
- </SelectTrigger>
138
- <SelectContent>
139
- {isUsersLoading ? (
140
- <div className="py-4 text-center text-sm text-gray-500">
141
- Loading...
142
- </div>
143
- ) : users.length > 0 ? (
144
- users.map((user) => (
145
- <SelectItem key={user._id} value={user._id}>
146
- {user._name}
147
- </SelectItem>
148
- ))
149
- ) : (
150
- <div className="py-4 text-center text-sm text-gray-500">
151
- No users available
152
- </div>
153
- )}
154
- </SelectContent>
155
- </Select>
156
- </FieldContent>
157
- {errors.AssignedUser && <FieldError>{errors.AssignedUser.message}</FieldError>}
158
- </Field>
159
-
160
- {/* ───────────────────────────────────────────────── */}
161
- {/* FileField — <FileUpload> for edit mode */}
162
- {/* Runtime methods (upload, delete) on item accessor */}
163
- {/* ───────────────────────────────────────────────── */}
164
- <Field>
165
- <FieldLabel>{fieldTest.Documents.label}</FieldLabel>
166
- <FieldContent>
167
- <FileUpload
168
- field={fieldTest.Documents}
169
- value={watch(fieldTest.Documents.id)}
170
- boId={fieldTest.meta._id}
171
- instanceId={item._id}
172
- fieldId={fieldTest.Documents.id}
173
- />
174
- </FieldContent>
175
- </Field>
176
-
177
- {/* ───────────────────────────────────────────────── */}
178
- {/* ImageField — <ImageUpload> for edit mode */}
179
- {/* Single image, nullable */}
180
- {/* ───────────────────────────────────────────────── */}
181
- <Field>
182
- <FieldLabel>{fieldTest.Thumbnail.label}</FieldLabel>
183
- <FieldContent>
184
- <ImageUpload
185
- field={fieldTest.Thumbnail}
186
- value={watch(fieldTest.Thumbnail.id)}
187
- boId={fieldTest.meta._id}
188
- instanceId={item._id}
189
- fieldId={fieldTest.Thumbnail.id}
190
- />
191
- </FieldContent>
192
- </Field>
193
-
194
- <button type="submit" disabled={isSubmitting}>
195
- {isSubmitting ? "Saving..." : "Save"}
196
- </button>
197
- </form>
198
- );
199
- }
200
- ```
201
-
202
- ## Read-Only Display
203
-
204
- For tables and detail pages, use `<FilePreview>` and `<ImageThumbnail>`:
205
-
206
- ```tsx
207
- import { FilePreview } from "@/components/ui/file-preview";
208
- import { ImageThumbnail } from "@/components/ui/image-thumbnail";
209
-
210
- // In a table row or detail page
211
- function RecordRow({ record }: { record: ItemType<...> }) {
212
- return (
213
- <div>
214
- {/* File preview — clickable thumbnails for images, file icons for others */}
215
- <FilePreview
216
- boId={fieldTest.meta._id}
217
- instanceId={record._id}
218
- fieldId="Documents"
219
- value={record.Documents.get()}
220
- />
221
-
222
- {/* Image thumbnail — proxy URL with view_type=thumbnail */}
223
- <ImageThumbnail
224
- boId={fieldTest.meta._id}
225
- instanceId={record._id}
226
- fieldId="Thumbnail"
227
- value={record.Thumbnail.get()}
228
- />
229
- </div>
230
- );
231
- }
232
- ```
233
-
234
- ## Key Patterns
235
-
236
- - **All complex fields use `watch()` + `setValue()`** — never `register()`. Select, Reference, User, File, and Image fields use custom components that don't fire native change events.
237
-
238
- - **`fetchOptions()` lazy-loaded with `useQuery`** — Gate with `enabled: dropdownOpen && !!item._id` to avoid fetching until the dropdown opens. Use `staleTime: Infinity` to cache options.
239
-
240
- - **ReferenceField stores the full object** — Not just an ID. The value is `{ _id, SupplierName, Email, ... }`. The `setValue()` call must pass the complete object.
241
-
242
- - **File/Image runtime methods are on `item.Field`** — The `upload()`, `getDownloadUrl()`, `deleteAttachment()` methods live on the `item` accessor (created by the `Item` proxy), not on the BDO field class. The `<FileUpload>` and `<ImageUpload>` components handle this internally.
243
-
244
- - **`<ReferenceSelect>` abstracts the full pattern** — Handles `fetchOptions()`, dropdown UI, search, and option display. Pass `bdoField`, `instanceId`, `value`, and `onChange`.
245
-
246
- - **`<FileUpload>` / `<ImageUpload>` handle the upload lifecycle** — File selection, upload to storage, progress indication, preview, and delete. They need `boId`, `instanceId`, and `fieldId` for API calls.
247
-
248
- - **`<FilePreview>` / `<ImageThumbnail>` for read-only display** — Use in tables, detail pages, or any context where files should be viewable but not editable. They construct proxy URLs for same-origin access.
@@ -1,217 +0,0 @@
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
@@ -1,76 +0,0 @@
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
@@ -1,101 +0,0 @@
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