@ram_28/kf-ai-sdk 2.0.15 → 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.
Files changed (130) hide show
  1. package/README.md +22 -14
  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/BaseBdo.d.ts +1 -1
  10. package/dist/bdo.cjs +1 -1
  11. package/dist/bdo.mjs +3 -3
  12. package/dist/components/hooks/useActivityForm/createActivityItemProxy.d.ts +1 -1
  13. package/dist/components/hooks/useActivityForm/createActivityItemProxy.d.ts.map +1 -1
  14. package/dist/components/hooks/useActivityForm/types.d.ts +6 -7
  15. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
  16. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
  17. package/dist/components/hooks/useActivityTable/types.d.ts +7 -6
  18. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
  19. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts +1 -1
  20. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
  21. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -0
  22. package/dist/components/hooks/useBDOForm/createResolver.d.ts.map +1 -0
  23. package/dist/components/hooks/useBDOForm/index.d.ts +6 -0
  24. package/dist/components/hooks/useBDOForm/index.d.ts.map +1 -0
  25. package/dist/components/hooks/useBDOForm/shared.d.ts +50 -0
  26. package/dist/components/hooks/useBDOForm/shared.d.ts.map +1 -0
  27. package/dist/components/hooks/{useForm → useBDOForm}/types.d.ts +6 -6
  28. package/dist/components/hooks/useBDOForm/types.d.ts.map +1 -0
  29. package/dist/components/hooks/{useForm/useForm.d.ts → useBDOForm/useBDOForm.d.ts} +4 -4
  30. package/dist/components/hooks/useBDOForm/useBDOForm.d.ts.map +1 -0
  31. package/dist/components/hooks/useBDOTable/types.d.ts +20 -14
  32. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
  33. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
  34. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
  35. package/dist/{constants-ConHc1oS.js → constants-Cyi942Yr.js} +5 -5
  36. package/dist/constants-DEmYwKfC.cjs +1 -0
  37. package/dist/filter.cjs +1 -1
  38. package/dist/filter.mjs +1 -1
  39. package/dist/form.cjs +1 -1
  40. package/dist/form.d.ts +1 -1
  41. package/dist/form.d.ts.map +1 -1
  42. package/dist/form.mjs +250 -253
  43. package/dist/form.types.d.ts +1 -1
  44. package/dist/form.types.d.ts.map +1 -1
  45. package/dist/shared-5a7UkED1.js +1180 -0
  46. package/dist/shared-nnmlRVs7.cjs +1 -0
  47. package/dist/table.cjs +1 -1
  48. package/dist/table.mjs +14 -14
  49. package/dist/table.types.d.ts +1 -1
  50. package/dist/table.types.d.ts.map +1 -1
  51. package/dist/types/constants.d.ts +4 -4
  52. package/dist/workflow/Activity.d.ts +22 -7
  53. package/dist/workflow/Activity.d.ts.map +1 -1
  54. package/dist/workflow/client.d.ts +2 -2
  55. package/dist/workflow/client.d.ts.map +1 -1
  56. package/dist/workflow/types.d.ts +7 -3
  57. package/dist/workflow/types.d.ts.map +1 -1
  58. package/dist/workflow.cjs +1 -1
  59. package/dist/workflow.mjs +518 -576
  60. package/docs/README.md +51 -0
  61. package/docs/bdo/README.md +161 -0
  62. package/docs/bdo/api_reference.md +281 -0
  63. package/docs/examples/bdo/create-product.md +69 -0
  64. package/docs/examples/bdo/edit-product-dialog.md +95 -0
  65. package/docs/examples/bdo/filtered-product-table.md +100 -0
  66. package/docs/examples/bdo/product-listing.md +73 -0
  67. package/docs/examples/bdo/supplier-dropdown.md +60 -0
  68. package/docs/examples/workflow/approve-leave-request.md +76 -0
  69. package/docs/examples/workflow/filtered-activity-table.md +101 -0
  70. package/docs/examples/workflow/my-pending-requests.md +90 -0
  71. package/docs/examples/workflow/start-new-workflow.md +47 -0
  72. package/docs/examples/workflow/submit-leave-request.md +72 -0
  73. package/docs/examples/workflow/workflow-progress.md +49 -0
  74. package/docs/useActivityForm/README.md +241 -0
  75. package/docs/useActivityForm/api_reference.md +279 -0
  76. package/docs/useActivityTable/README.md +263 -0
  77. package/docs/useActivityTable/api_reference.md +294 -0
  78. package/docs/useBDOForm/README.md +172 -0
  79. package/docs/useBDOForm/api_reference.md +244 -0
  80. package/docs/useBDOTable/README.md +242 -0
  81. package/docs/useBDOTable/api_reference.md +253 -0
  82. package/docs/useFilter/README.md +323 -0
  83. package/docs/useFilter/api_reference.md +228 -0
  84. package/docs/workflow/README.md +158 -0
  85. package/docs/workflow/api_reference.md +161 -0
  86. package/package.json +2 -2
  87. package/sdk/auth/authConfig.ts +1 -1
  88. package/sdk/auth/types.ts +1 -1
  89. package/sdk/bdo/core/BaseBdo.ts +2 -2
  90. package/sdk/components/hooks/useActivityForm/createActivityItemProxy.ts +1 -1
  91. package/sdk/components/hooks/useActivityForm/createActivityResolver.ts +1 -1
  92. package/sdk/components/hooks/useActivityForm/types.ts +8 -10
  93. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +52 -265
  94. package/sdk/components/hooks/useActivityTable/types.ts +6 -5
  95. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +14 -43
  96. package/sdk/components/hooks/{useForm → useBDOForm}/index.ts +4 -3
  97. package/sdk/components/hooks/useBDOForm/shared.ts +250 -0
  98. package/sdk/components/hooks/{useForm → useBDOForm}/types.ts +9 -9
  99. package/sdk/components/hooks/{useForm/useForm.ts → useBDOForm/useBDOForm.ts} +70 -96
  100. package/sdk/components/hooks/useBDOTable/types.ts +20 -12
  101. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +12 -7
  102. package/sdk/form.ts +2 -2
  103. package/sdk/form.types.ts +4 -4
  104. package/sdk/table.types.ts +2 -0
  105. package/sdk/types/constants.ts +4 -4
  106. package/sdk/workflow/Activity.ts +68 -13
  107. package/sdk/workflow/client.ts +65 -25
  108. package/sdk/workflow/types.ts +10 -2
  109. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +0 -1
  110. package/dist/components/hooks/useForm/createResolver.d.ts.map +0 -1
  111. package/dist/components/hooks/useForm/index.d.ts +0 -5
  112. package/dist/components/hooks/useForm/index.d.ts.map +0 -1
  113. package/dist/components/hooks/useForm/types.d.ts.map +0 -1
  114. package/dist/components/hooks/useForm/useForm.d.ts.map +0 -1
  115. package/dist/constants-QX2RX-wu.cjs +0 -1
  116. package/dist/createResolver-AIgUwoS6.cjs +0 -1
  117. package/dist/createResolver-ZHXQ7QMa.js +0 -1078
  118. package/docs/api.md +0 -95
  119. package/docs/bdo.md +0 -224
  120. package/docs/gaps.md +0 -410
  121. package/docs/useActivityTable.md +0 -481
  122. package/docs/useBDOTable.md +0 -317
  123. package/docs/useFilter.md +0 -188
  124. package/docs/useForm.md +0 -376
  125. package/docs/workflow.md +0 -818
  126. /package/dist/components/hooks/{useForm → useBDOForm}/createItemProxy.d.ts +0 -0
  127. /package/dist/components/hooks/{useForm → useBDOForm}/createResolver.d.ts +0 -0
  128. /package/docs/{useAuth.md → useAuth/README.md} +0 -0
  129. /package/sdk/components/hooks/{useForm → useBDOForm}/createItemProxy.ts +0 -0
  130. /package/sdk/components/hooks/{useForm → useBDOForm}/createResolver.ts +0 -0
@@ -0,0 +1,100 @@
1
+ # Filtered Product Table
2
+
3
+ > Filter products by category and price range using the integrated `table.filter` API.
4
+
5
+ ```tsx
6
+ import { useState, useMemo } from "react";
7
+ import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
8
+ import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
9
+ import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
10
+ import { BuyerProduct } from "@/bdo/buyer/Product";
11
+
12
+ export default function FilteredProductTable() {
13
+ const product = useMemo(() => new BuyerProduct(), []);
14
+ const table: UseBDOTableReturnType<BuyerProduct> = useBDOTable({ bdo: product });
15
+
16
+ // ── Category filter (single EQ condition) ─────────────────────
17
+ const [categoryFilter, setCategoryFilter] = useState("");
18
+ const [categoryConditionId, setCategoryConditionId] = useState<string | null>(null);
19
+
20
+ const handleCategoryFilter = (value: string) => {
21
+ if (categoryConditionId) {
22
+ table.filter.removeCondition(categoryConditionId);
23
+ setCategoryConditionId(null);
24
+ }
25
+ setCategoryFilter(value);
26
+ if (value && value !== "all") {
27
+ const id = table.filter.addCondition({
28
+ LHSField: "Category",
29
+ Operator: ConditionOperator.EQ,
30
+ RHSType: RHSType.Constant,
31
+ RHSValue: value,
32
+ });
33
+ setCategoryConditionId(id);
34
+ }
35
+ };
36
+
37
+ // ── Price range filter (GTE + LTE conditions) ─────────────────
38
+ const [minPrice, setMinPrice] = useState("");
39
+ const [maxPrice, setMaxPrice] = useState("");
40
+ const [minPriceConditionId, setMinPriceConditionId] = useState<string | null>(null);
41
+ const [maxPriceConditionId, setMaxPriceConditionId] = useState<string | null>(null);
42
+
43
+ const applyPriceFilter = () => {
44
+ if (minPriceConditionId) { table.filter.removeCondition(minPriceConditionId); setMinPriceConditionId(null); }
45
+ if (maxPriceConditionId) { table.filter.removeCondition(maxPriceConditionId); setMaxPriceConditionId(null); }
46
+
47
+ if (minPrice !== "") {
48
+ const id = table.filter.addCondition({
49
+ LHSField: "Price", Operator: ConditionOperator.GTE, RHSType: RHSType.Constant, RHSValue: Number(minPrice),
50
+ });
51
+ setMinPriceConditionId(id);
52
+ }
53
+ if (maxPrice !== "") {
54
+ const id = table.filter.addCondition({
55
+ LHSField: "Price", Operator: ConditionOperator.LTE, RHSType: RHSType.Constant, RHSValue: Number(maxPrice),
56
+ });
57
+ setMaxPriceConditionId(id);
58
+ }
59
+ };
60
+
61
+ if (table.isLoading) return <p>Loading...</p>;
62
+
63
+ return (
64
+ <div>
65
+ {/* Category dropdown */}
66
+ <select value={categoryFilter} onChange={(e) => handleCategoryFilter(e.target.value)}>
67
+ <option value="all">All categories</option>
68
+ {product.Category.options.map((opt) => (
69
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
70
+ ))}
71
+ </select>
72
+
73
+ {/* Price range */}
74
+ <input type="number" placeholder="Min" value={minPrice} onChange={(e) => setMinPrice(e.target.value)} />
75
+ <input type="number" placeholder="Max" value={maxPrice} onChange={(e) => setMaxPrice(e.target.value)} />
76
+ <button onClick={applyPriceFilter}>Apply</button>
77
+
78
+ {/* Table */}
79
+ <table>
80
+ <tbody>
81
+ {table.rows.map((row) => (
82
+ <tr key={row._id}>
83
+ <td>{row.Title.get()}</td>
84
+ <td>${row.Price.get()?.toFixed(2)}</td>
85
+ <td>{row.Category.get()}</td>
86
+ </tr>
87
+ ))}
88
+ </tbody>
89
+ </table>
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ## Key Patterns
96
+
97
+ - **`addCondition()` returns an ID** -- store it in state to remove the condition later with `removeCondition(id)`
98
+ - **Category filter** -- single `ConditionOperator.EQ` condition; remove-then-add on every change
99
+ - **Price range** -- two separate conditions (`GTE` and `LTE`), each tracked by its own ID
100
+ - **Filter changes auto-reset pagination** -- when conditions change, the table resets to page 1
@@ -0,0 +1,73 @@
1
+ # Product Listing
2
+
3
+ > Browse products with search, sortable columns, and pagination using `useBDOTable`.
4
+
5
+ ```tsx
6
+ import { useMemo } from "react";
7
+ import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
8
+ import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
9
+ import { BuyerProduct } from "@/bdo/buyer/Product";
10
+
11
+ export default function ProductListingPage() {
12
+ const product = useMemo(() => new BuyerProduct(), []);
13
+ const table: UseBDOTableReturnType<BuyerProduct> = useBDOTable({
14
+ bdo: product,
15
+ initialState: { sort: [{ _created_at: "DESC" }], pagination: { pageNo: 1, pageSize: 10 } },
16
+ });
17
+
18
+ if (table.isLoading) return <p>Loading...</p>;
19
+ if (table.error) return <p>Error: {table.error.message}</p>;
20
+
21
+ return (
22
+ <div>
23
+ {/* Search */}
24
+ <input
25
+ placeholder="Search by title..."
26
+ value={table.search.query}
27
+ onChange={(e) => table.search.set("Title", e.target.value)}
28
+ />
29
+ {table.search.query && <button onClick={table.search.clear}>Clear</button>}
30
+
31
+ {/* Table with sortable headers */}
32
+ <table>
33
+ <thead>
34
+ <tr>
35
+ <th onClick={() => table.sort.toggle("Title")} style={{ cursor: "pointer" }}>
36
+ {product.Title.label}
37
+ {table.sort.field === "Title" && (table.sort.direction === "ASC" ? " ↑" : " ↓")}
38
+ </th>
39
+ <th onClick={() => table.sort.toggle("Price")} style={{ cursor: "pointer" }}>
40
+ {product.Price.label}
41
+ {table.sort.field === "Price" && (table.sort.direction === "ASC" ? " ↑" : " ↓")}
42
+ </th>
43
+ <th>{product.Category.label}</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ {table.rows.map((row) => (
48
+ <tr key={row._id}>
49
+ <td>{row.Title.get()}</td>
50
+ <td>${row.Price.get()?.toFixed(2)}</td>
51
+ <td>{row.Category.get()}</td>
52
+ </tr>
53
+ ))}
54
+ </tbody>
55
+ </table>
56
+
57
+ {/* Pagination */}
58
+ <div>
59
+ <button onClick={table.pagination.goToPrevious} disabled={!table.pagination.canGoPrevious}>Previous</button>
60
+ <span>Page {table.pagination.pageNo} of {table.pagination.totalPages}</span>
61
+ <button onClick={table.pagination.goToNext} disabled={!table.pagination.canGoNext}>Next</button>
62
+ </div>
63
+ </div>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Key Patterns
69
+
70
+ - **`row.Field.get()`** -- rows are `ItemType` proxies; always use `.get()` to read values
71
+ - **`search.set(field, query)`** -- updates the input instantly, debounces the API call (300ms)
72
+ - **`sort.toggle(field)`** -- cycles ASC → DESC → cleared on each click
73
+ - **Pagination** -- `canGoNext`/`canGoPrevious` disable buttons at boundaries; `pageNo`, `totalPages` for display
@@ -0,0 +1,60 @@
1
+ # Supplier Dropdown
2
+
3
+ > Lazy-load ReferenceField options when the dropdown opens using `useQuery`.
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 { BuyerProduct } from "@/bdo/buyer/Product";
11
+ import type { ProductSupplierRefType } from "@/bdo/buyer/Product";
12
+
13
+ export default function SupplierDropdown({ recordId }: { recordId?: string }) {
14
+ const [dropdownOpen, setDropdownOpen] = useState(false);
15
+ const product = useMemo(() => new BuyerProduct(), []);
16
+
17
+ const { watch, setValue, item }: UseBDOFormReturnType<BuyerProduct> = useBDOForm({ bdo: product, recordId });
18
+
19
+ // Lazy-load options only when dropdown opens and item has an _id
20
+ const { data: suppliers = [], isFetching } = useQuery<ProductSupplierRefType[]>({
21
+ queryKey: ["supplier-options", item._id],
22
+ queryFn: () => product.SupplierInfo.fetchOptions(item._id!),
23
+ enabled: dropdownOpen && !!item._id,
24
+ staleTime: Infinity,
25
+ });
26
+
27
+ const currentSupplier = watch(product.SupplierInfo.id);
28
+
29
+ return (
30
+ <div>
31
+ <label>{product.SupplierInfo.label}</label>
32
+ <select
33
+ value={currentSupplier?._id ?? ""}
34
+ onFocus={() => setDropdownOpen(true)}
35
+ onChange={(e) => {
36
+ const supplier = suppliers.find((s) => s._id === e.target.value);
37
+ if (supplier) setValue(product.SupplierInfo.id, supplier);
38
+ }}
39
+ >
40
+ <option value="">{currentSupplier?.SupplierName ?? "Select supplier"}</option>
41
+ {isFetching ? (
42
+ <option disabled>Loading...</option>
43
+ ) : (
44
+ suppliers.map((s) => (
45
+ <option key={s._id} value={s._id}>{s.SupplierName}</option>
46
+ ))
47
+ )}
48
+ </select>
49
+ </div>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ## Key Patterns
55
+
56
+ - **`enabled: dropdownOpen && !!item._id`** -- options aren't fetched until the dropdown opens and the draft/record has an ID
57
+ - **`fetchOptions(instanceId)`** -- loads reference options for the current record context
58
+ - **`watch()` + `setValue()`** -- reads/writes the entire reference object (`{ _id, SupplierName }`)
59
+ - **`referenceFields`** -- `product.SupplierInfo.referenceFields` lists the fields fetched from the referenced BDO
60
+ - **`staleTime: Infinity`** -- options are cached and not refetched on re-focus
@@ -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)