@ram_28/kf-ai-sdk 2.0.18 → 2.0.20-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -16
- package/dist/{FileField-CZjS2uLh.js → FileField-BWrSHNRq.js} +3 -3
- package/dist/{FileField-DU4UWo_t.cjs → FileField-eDeuzln8.cjs} +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +1 -1
- package/dist/auth/authClient.d.ts.map +1 -1
- package/dist/auth/authConfig.d.ts +1 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +102 -106
- package/dist/bdo/core/Item.d.ts +4 -0
- package/dist/bdo/core/Item.d.ts.map +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
- package/dist/bdo/fields/SelectField.d.ts +1 -1
- package/dist/bdo/fields/SelectField.d.ts.map +1 -1
- package/dist/bdo/fields/UserField.d.ts +1 -1
- package/dist/bdo/fields/UserField.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +62 -53
- package/dist/components/hooks/useActivityForm/types.d.ts +5 -4
- package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/types.d.ts +4 -5
- package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
- package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +3 -2
- package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/types.d.ts +12 -20
- package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
- package/dist/{constants-Cyi942Yr.js → constants-ConHc1oS.js} +5 -5
- package/dist/constants-QX2RX-wu.cjs +1 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +243 -226
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +16 -15
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/workflow/Activity.d.ts +5 -8
- package/dist/workflow/Activity.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.mjs +476 -461
- package/docs/api.md +95 -0
- package/docs/bdo.md +224 -0
- package/docs/gaps.md +360 -0
- package/docs/useActivityForm.md +393 -0
- package/docs/useActivityTable.md +418 -0
- package/docs/useBDOForm.md +498 -0
- package/docs/useBDOTable.md +284 -0
- package/docs/useFilter.md +188 -0
- package/docs/workflow.md +560 -0
- package/package.json +14 -15
- package/sdk/auth/authClient.ts +15 -21
- package/sdk/auth/authConfig.ts +1 -1
- package/sdk/auth/types.ts +1 -1
- package/sdk/bdo/core/Item.ts +10 -1
- package/sdk/bdo/fields/ReferenceField.ts +1 -1
- package/sdk/bdo/fields/SelectField.ts +1 -1
- package/sdk/bdo/fields/UserField.ts +1 -1
- package/sdk/components/hooks/useActivityForm/types.ts +6 -4
- package/sdk/components/hooks/useActivityForm/useActivityForm.ts +73 -10
- package/sdk/components/hooks/useActivityTable/types.ts +5 -4
- package/sdk/components/hooks/useActivityTable/useActivityTable.ts +8 -10
- package/sdk/components/hooks/useBDOForm/createItemProxy.ts +58 -17
- package/sdk/components/hooks/useBDOTable/types.ts +10 -20
- package/sdk/components/hooks/useBDOTable/useBDOTable.ts +8 -12
- package/sdk/table.types.ts +0 -2
- package/sdk/types/constants.ts +1 -1
- package/sdk/workflow/Activity.ts +7 -39
- package/dist/constants-DEmYwKfC.cjs +0 -1
- package/docs/README.md +0 -57
- package/docs/bdo/README.md +0 -161
- package/docs/bdo/api_reference.md +0 -281
- package/docs/examples/bdo/create-product.md +0 -69
- package/docs/examples/bdo/edit-product-dialog.md +0 -95
- package/docs/examples/bdo/filtered-product-table.md +0 -100
- package/docs/examples/bdo/product-listing.md +0 -73
- package/docs/examples/bdo/supplier-dropdown.md +0 -60
- package/docs/examples/fields/complex-fields.md +0 -248
- package/docs/examples/fields/primitive-fields.md +0 -217
- package/docs/examples/workflow/approve-leave-request.md +0 -76
- package/docs/examples/workflow/filtered-activity-table.md +0 -101
- package/docs/examples/workflow/my-pending-requests.md +0 -90
- package/docs/examples/workflow/start-new-workflow.md +0 -47
- package/docs/examples/workflow/submit-leave-request.md +0 -72
- package/docs/examples/workflow/workflow-progress.md +0 -49
- package/docs/fields/README.md +0 -141
- package/docs/fields/api_reference.md +0 -134
- package/docs/useActivityForm/README.md +0 -244
- package/docs/useActivityForm/api_reference.md +0 -279
- package/docs/useActivityTable/README.md +0 -263
- package/docs/useActivityTable/api_reference.md +0 -294
- package/docs/useBDOForm/README.md +0 -175
- package/docs/useBDOForm/api_reference.md +0 -244
- package/docs/useBDOTable/README.md +0 -242
- package/docs/useBDOTable/api_reference.md +0 -253
- package/docs/useFilter/README.md +0 -323
- package/docs/useFilter/api_reference.md +0 -228
- package/docs/workflow/README.md +0 -158
- package/docs/workflow/api_reference.md +0 -161
- /package/docs/{useAuth/README.md → useAuth.md} +0 -0
package/sdk/auth/authClient.ts
CHANGED
|
@@ -9,11 +9,7 @@ import type {
|
|
|
9
9
|
LoginOptionsType,
|
|
10
10
|
LogoutOptionsType,
|
|
11
11
|
} from "./types";
|
|
12
|
-
import {
|
|
13
|
-
getAuthBaseUrl,
|
|
14
|
-
getAuthConfig,
|
|
15
|
-
getProviderConfig,
|
|
16
|
-
} from "./authConfig";
|
|
12
|
+
import { getAuthBaseUrl, getAuthConfig, getProviderConfig } from "./authConfig";
|
|
17
13
|
import { getDefaultHeaders } from "../api/client";
|
|
18
14
|
|
|
19
15
|
/**
|
|
@@ -52,7 +48,7 @@ export async function fetchSession(): Promise<SessionResponseType> {
|
|
|
52
48
|
}
|
|
53
49
|
throw new AuthenticationError(
|
|
54
50
|
`Session check failed: ${response.statusText}`,
|
|
55
|
-
response.status
|
|
51
|
+
response.status,
|
|
56
52
|
);
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -82,7 +78,7 @@ export async function fetchSession(): Promise<SessionResponseType> {
|
|
|
82
78
|
*/
|
|
83
79
|
export function initiateLogin(
|
|
84
80
|
provider?: AuthProviderNameType,
|
|
85
|
-
options?: LoginOptionsType
|
|
81
|
+
options?: LoginOptionsType,
|
|
86
82
|
): Promise<never> {
|
|
87
83
|
return new Promise(() => {
|
|
88
84
|
const config = getAuthConfig();
|
|
@@ -91,7 +87,7 @@ export function initiateLogin(
|
|
|
91
87
|
// Validate base URL
|
|
92
88
|
if (!baseUrl) {
|
|
93
89
|
throw new Error(
|
|
94
|
-
'Auth base URL is not configured. Call setApiBaseUrl("https://...") or configureAuth({ baseUrl: "https://..." }) first.'
|
|
90
|
+
'Auth base URL is not configured. Call setApiBaseUrl("https://...") or configureAuth({ baseUrl: "https://..." }) first.',
|
|
95
91
|
);
|
|
96
92
|
}
|
|
97
93
|
|
|
@@ -100,9 +96,10 @@ export function initiateLogin(
|
|
|
100
96
|
|
|
101
97
|
// Validate provider config
|
|
102
98
|
if (!providerConfig) {
|
|
103
|
-
const availableProviders =
|
|
99
|
+
const availableProviders =
|
|
100
|
+
Object.keys(config.providers || {}).join(", ") || "none";
|
|
104
101
|
throw new Error(
|
|
105
|
-
`Auth provider "${selectedProvider}" is not configured. Available providers: ${availableProviders}
|
|
102
|
+
`Auth provider "${selectedProvider}" is not configured. Available providers: ${availableProviders}`,
|
|
106
103
|
);
|
|
107
104
|
}
|
|
108
105
|
|
|
@@ -110,7 +107,7 @@ export function initiateLogin(
|
|
|
110
107
|
if (!providerConfig.loginPath) {
|
|
111
108
|
throw new Error(
|
|
112
109
|
`Login path not configured for provider "${selectedProvider}". ` +
|
|
113
|
-
|
|
110
|
+
`Configure it with: configureAuth({ providers: { ${selectedProvider}: { loginPath: '/api/auth/...' } } })`,
|
|
114
111
|
);
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -121,14 +118,14 @@ export function initiateLogin(
|
|
|
121
118
|
} catch {
|
|
122
119
|
throw new Error(
|
|
123
120
|
`Failed to construct login URL. Base URL: "${baseUrl}", Login path: "${providerConfig.loginPath}". ` +
|
|
124
|
-
|
|
121
|
+
`Ensure baseUrl is a valid URL (e.g., "https://example.com").`,
|
|
125
122
|
);
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
if (options?.callbackUrl || config.callbackUrl) {
|
|
129
126
|
loginUrl.searchParams.set(
|
|
130
127
|
"callbackUrl",
|
|
131
|
-
options?.callbackUrl || config.callbackUrl || window.location.href
|
|
128
|
+
options?.callbackUrl || config.callbackUrl || window.location.href,
|
|
132
129
|
);
|
|
133
130
|
}
|
|
134
131
|
|
|
@@ -138,7 +135,7 @@ export function initiateLogin(
|
|
|
138
135
|
});
|
|
139
136
|
}
|
|
140
137
|
|
|
141
|
-
window.open(loginUrl.toString(),
|
|
138
|
+
window.open(loginUrl.toString(), "_blank");
|
|
142
139
|
// Promise never resolves - login opens in new tab
|
|
143
140
|
});
|
|
144
141
|
}
|
|
@@ -147,21 +144,18 @@ export function initiateLogin(
|
|
|
147
144
|
* Logout the current user
|
|
148
145
|
* Optionally calls the logout endpoint before clearing client state
|
|
149
146
|
*/
|
|
150
|
-
export async function performLogout(
|
|
147
|
+
export async function performLogout(
|
|
148
|
+
options?: LogoutOptionsType,
|
|
149
|
+
): Promise<void> {
|
|
151
150
|
const config = getAuthConfig();
|
|
152
151
|
const baseUrl = getAuthBaseUrl();
|
|
153
|
-
const headers = getDefaultHeaders();
|
|
154
152
|
|
|
155
153
|
const providerConfig = getProviderConfig(config.defaultProvider);
|
|
156
154
|
const logoutPath = providerConfig?.logoutPath;
|
|
157
155
|
|
|
158
156
|
if (logoutPath && options?.callLogoutEndpoint !== false) {
|
|
159
157
|
try {
|
|
160
|
-
await fetch(`${baseUrl}${logoutPath}
|
|
161
|
-
method: "POST",
|
|
162
|
-
headers,
|
|
163
|
-
credentials: "include",
|
|
164
|
-
});
|
|
158
|
+
await fetch(`${baseUrl}${logoutPath}`);
|
|
165
159
|
} catch (error) {
|
|
166
160
|
console.warn("Logout endpoint call failed:", error);
|
|
167
161
|
}
|
package/sdk/auth/authConfig.ts
CHANGED
|
@@ -43,7 +43,7 @@ let authConfig: AuthConfigType = { ...defaultAuthConfig };
|
|
|
43
43
|
* autoRedirect: true,
|
|
44
44
|
* providers: {
|
|
45
45
|
* google: { loginPath: "/api/auth/google/login" },
|
|
46
|
-
*
|
|
46
|
+
* microsoft: { loginPath: "/api/auth/microsoft/login" },
|
|
47
47
|
* },
|
|
48
48
|
* });
|
|
49
49
|
* ```
|
package/sdk/auth/types.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type AuthStatusType = "loading" | "authenticated" | "unauthenticated";
|
|
|
29
29
|
/**
|
|
30
30
|
* Authentication provider type (extensible for multiple OAuth providers)
|
|
31
31
|
*/
|
|
32
|
-
export type AuthProviderNameType = "google" | "
|
|
32
|
+
export type AuthProviderNameType = "google" | "microsoft" | "github" | "azure" | "custom";
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Auth endpoint configuration for a specific provider
|
package/sdk/bdo/core/Item.ts
CHANGED
|
@@ -196,9 +196,18 @@ export class Item<T extends Record<string, unknown>> {
|
|
|
196
196
|
}) as Item<T>;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Require instanceId or throw.
|
|
201
|
+
* TODO: Support create flow via draftInteraction to get temp _id
|
|
202
|
+
*/
|
|
199
203
|
private _requireInstanceId(): string {
|
|
200
204
|
const id = this._data._id as string | undefined;
|
|
201
|
-
|
|
205
|
+
if (!id) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Cannot perform attachment operation: item has no _id. Save the item first.",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return id;
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
/**
|
|
@@ -71,7 +71,7 @@ export class ReferenceField<TRef = unknown> extends BaseField<
|
|
|
71
71
|
* Fetch referenced records from the backend via the fetchField API.
|
|
72
72
|
* Requires the field to be bound to a parent BDO.
|
|
73
73
|
*/
|
|
74
|
-
async fetchOptions(instanceId: string
|
|
74
|
+
async fetchOptions(instanceId: string): Promise<TRef[]> {
|
|
75
75
|
if (!this._parentBoId) {
|
|
76
76
|
throw new Error(
|
|
77
77
|
`Field ${this.id} not bound to a BDO. Cannot fetch options.`
|
|
@@ -57,7 +57,7 @@ export class SelectField<T extends string | number = string> extends BaseField<T
|
|
|
57
57
|
/**
|
|
58
58
|
* Fetch dynamic options from the backend, returned as typed SelectOption[]
|
|
59
59
|
*/
|
|
60
|
-
async fetchOptions(instanceId: string
|
|
60
|
+
async fetchOptions(instanceId: string): Promise<SelectOptionType<T>[]> {
|
|
61
61
|
if (!this._parentBoId) {
|
|
62
62
|
throw new Error(
|
|
63
63
|
`Field ${this.id} not bound to a BDO. Cannot fetch options.`
|
|
@@ -32,7 +32,7 @@ export class UserField extends BaseField<UserFieldType> {
|
|
|
32
32
|
* Fetch user records from the backend via the fetchField API.
|
|
33
33
|
* Requires the field to be bound to a parent BDO.
|
|
34
34
|
*/
|
|
35
|
-
async fetchOptions(instanceId: string
|
|
35
|
+
async fetchOptions(instanceId: string): Promise<UserFieldType[]> {
|
|
36
36
|
if (!this._parentBoId) {
|
|
37
37
|
throw new Error(
|
|
38
38
|
`Field ${this.id} not bound to a BDO. Cannot fetch options.`,
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
} from 'react-hook-form';
|
|
16
16
|
|
|
17
17
|
import type { Activity } from '../../../workflow/Activity';
|
|
18
|
-
import type { CreateUpdateResponseType } from '../../../types/common';
|
|
19
18
|
|
|
20
19
|
// Reuse shared types from useBDOForm — identical interfaces, no duplication
|
|
21
20
|
import type {
|
|
@@ -105,8 +104,11 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
|
|
|
105
104
|
ExtractActivityReadonly<A>
|
|
106
105
|
>;
|
|
107
106
|
|
|
108
|
-
/** Handle form submission —
|
|
109
|
-
handleSubmit: HandleSubmitType<
|
|
107
|
+
/** Handle form submission — calls activity.update() */
|
|
108
|
+
handleSubmit: HandleSubmitType<AllActivityFields<A>>;
|
|
109
|
+
|
|
110
|
+
/** Handle form completion — calls activity.update() + activity.complete() */
|
|
111
|
+
handleComplete: HandleSubmitType<AllActivityFields<A>>;
|
|
110
112
|
|
|
111
113
|
/** Watch field values */
|
|
112
114
|
watch: UseFormWatch<AllActivityFields<A>>;
|
|
@@ -139,7 +141,7 @@ export interface UseActivityFormReturn<A extends Activity<any, any, any>> {
|
|
|
139
141
|
/** Form has been modified */
|
|
140
142
|
isDirty: boolean;
|
|
141
143
|
|
|
142
|
-
/** Form is currently submitting */
|
|
144
|
+
/** Form is currently submitting (save or complete) */
|
|
143
145
|
isSubmitting: boolean;
|
|
144
146
|
|
|
145
147
|
/** Form submission was successful */
|
|
@@ -10,9 +10,12 @@ import { useForm as useReactHookForm } from 'react-hook-form';
|
|
|
10
10
|
import { useQuery } from '@tanstack/react-query';
|
|
11
11
|
|
|
12
12
|
import type { Activity } from '../../../workflow/Activity';
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
UseActivityFormOptions,
|
|
15
|
+
UseActivityFormReturn,
|
|
16
|
+
AllActivityFields,
|
|
17
|
+
} from './types';
|
|
14
18
|
|
|
15
|
-
import type { CreateUpdateResponseType } from '../../../types/common';
|
|
16
19
|
import { createActivityResolver } from './createActivityResolver';
|
|
17
20
|
import { createActivityItemProxy } from './createActivityItemProxy';
|
|
18
21
|
import {
|
|
@@ -297,13 +300,13 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
297
300
|
);
|
|
298
301
|
|
|
299
302
|
// ============================================================
|
|
300
|
-
// HANDLE SUBMIT — activity.update()
|
|
303
|
+
// HANDLE SUBMIT — activity.update()
|
|
301
304
|
// ============================================================
|
|
302
305
|
|
|
303
306
|
const handleSubmit = useCallback(
|
|
304
307
|
(
|
|
305
308
|
onSuccess?: (
|
|
306
|
-
data:
|
|
309
|
+
data: AllActivityFields<A>,
|
|
307
310
|
e?: React.BaseSyntheticEvent,
|
|
308
311
|
) => void | Promise<void>,
|
|
309
312
|
onError?: (
|
|
@@ -316,7 +319,7 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
316
319
|
setIsSubmitting(true);
|
|
317
320
|
|
|
318
321
|
try {
|
|
319
|
-
// Only send dirty (changed) fields — matches useBDOForm behavior
|
|
322
|
+
// Only send dirty (changed) fields — matches useBDOForm update behavior
|
|
320
323
|
// Use getValues() to capture Image/File values set via setValue()
|
|
321
324
|
// that RHF resolver doesn't include in `data`
|
|
322
325
|
const cleanedData: Record<string, unknown> = {};
|
|
@@ -336,7 +339,7 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
336
339
|
: value;
|
|
337
340
|
}
|
|
338
341
|
|
|
339
|
-
//
|
|
342
|
+
// Save via activity.update()
|
|
340
343
|
if (Object.keys(cleanedData).length > 0) {
|
|
341
344
|
await activityRef.update(
|
|
342
345
|
activity_instance_id,
|
|
@@ -344,10 +347,7 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
344
347
|
);
|
|
345
348
|
}
|
|
346
349
|
|
|
347
|
-
|
|
348
|
-
const result = await activityRef.complete(activity_instance_id);
|
|
349
|
-
|
|
350
|
-
await onSuccess?.(result, event);
|
|
350
|
+
await onSuccess?.(data as AllActivityFields<A>, event);
|
|
351
351
|
} catch (error) {
|
|
352
352
|
onError?.(toError(error), event);
|
|
353
353
|
} finally {
|
|
@@ -362,6 +362,68 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
362
362
|
[rhf, activityRef, readonlyFieldNames, allFields, activity_instance_id],
|
|
363
363
|
) as UseActivityFormReturn<A>['handleSubmit'];
|
|
364
364
|
|
|
365
|
+
// ============================================================
|
|
366
|
+
// HANDLE COMPLETE — activity.update() + activity.complete()
|
|
367
|
+
// ============================================================
|
|
368
|
+
|
|
369
|
+
const handleComplete = useCallback(
|
|
370
|
+
(
|
|
371
|
+
onSuccess?: (
|
|
372
|
+
data: AllActivityFields<A>,
|
|
373
|
+
e?: React.BaseSyntheticEvent,
|
|
374
|
+
) => void | Promise<void>,
|
|
375
|
+
onError?: (
|
|
376
|
+
error: any,
|
|
377
|
+
e?: React.BaseSyntheticEvent,
|
|
378
|
+
) => void | Promise<void>,
|
|
379
|
+
) => {
|
|
380
|
+
return rhf.handleSubmit(
|
|
381
|
+
async (data, event) => {
|
|
382
|
+
setIsSubmitting(true);
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// Only send dirty (changed) fields — matches useBDOForm update behavior
|
|
386
|
+
// Use getValues() to capture Image/File values set via setValue()
|
|
387
|
+
// that RHF resolver doesn't include in `data`
|
|
388
|
+
const cleanedData: Record<string, unknown> = {};
|
|
389
|
+
const readonlySet = new Set(readonlyFieldNames);
|
|
390
|
+
const dirtyFields = rhf.formState.dirtyFields;
|
|
391
|
+
const allValues = rhf.getValues() as Record<string, unknown>;
|
|
392
|
+
|
|
393
|
+
for (const key of Object.keys(allValues)) {
|
|
394
|
+
if (readonlySet.has(key) || !dirtyFields[key]) continue;
|
|
395
|
+
const value =
|
|
396
|
+
allValues[key] !== undefined
|
|
397
|
+
? allValues[key]
|
|
398
|
+
: (data as Record<string, unknown>)[key];
|
|
399
|
+
const field = allFields[key];
|
|
400
|
+
cleanedData[key] = field
|
|
401
|
+
? coerceFieldValue(field, value)
|
|
402
|
+
: value;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (Object.keys(cleanedData).length > 0) {
|
|
406
|
+
await activityRef.update(
|
|
407
|
+
activity_instance_id,
|
|
408
|
+
cleanedData as any,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
await activityRef.complete(activity_instance_id);
|
|
412
|
+
await onSuccess?.(data as AllActivityFields<A>, event);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
onError?.(toError(error), event);
|
|
415
|
+
} finally {
|
|
416
|
+
setIsSubmitting(false);
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
(errors, event) => {
|
|
420
|
+
onError?.(errors, event);
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
},
|
|
424
|
+
[rhf, activityRef, readonlyFieldNames, allFields, activity_instance_id],
|
|
425
|
+
) as UseActivityFormReturn<A>['handleComplete'];
|
|
426
|
+
|
|
365
427
|
// ============================================================
|
|
366
428
|
// CLEAR ERRORS
|
|
367
429
|
// ============================================================
|
|
@@ -386,6 +448,7 @@ export function useActivityForm<A extends Activity<any, any, any>>(
|
|
|
386
448
|
// Form methods
|
|
387
449
|
register,
|
|
388
450
|
handleSubmit,
|
|
451
|
+
handleComplete,
|
|
389
452
|
watch: rhf.watch as any,
|
|
390
453
|
setValue: rhf.setValue as any,
|
|
391
454
|
getValues: rhf.getValues as any,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Activity } from '../../../workflow/Activity';
|
|
2
|
+
import type { ActivityInstanceFieldsType } from '../../../workflow/types';
|
|
2
3
|
import type { UseTableReturnType, PaginationStateType } from '../useTable/types';
|
|
3
4
|
import type { UseFilterOptionsType } from '../useFilter/types';
|
|
4
5
|
import type { SortType } from '../../../types/common';
|
|
@@ -15,12 +16,12 @@ export type ActivityTableStatusType =
|
|
|
15
16
|
(typeof ActivityTableStatus)[keyof typeof ActivityTableStatus];
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Row type
|
|
19
|
-
*
|
|
19
|
+
* Row type for activity table data.
|
|
20
|
+
* System fields and entity fields are flat at the top level.
|
|
20
21
|
*/
|
|
21
22
|
export type ActivityRowType<A extends Activity<any, any, any>> =
|
|
22
|
-
A extends
|
|
23
|
-
?
|
|
23
|
+
A extends Activity<infer E, any, any>
|
|
24
|
+
? ActivityInstanceFieldsType & E
|
|
24
25
|
: never;
|
|
25
26
|
|
|
26
27
|
export interface UseActivityTableOptionsType<
|
|
@@ -13,24 +13,22 @@ export function useActivityTable<A extends Activity<any, any, any>>(
|
|
|
13
13
|
const { activity, status, ...rest } = options;
|
|
14
14
|
const { businessProcessId, activityId } = activity.meta;
|
|
15
15
|
|
|
16
|
+
const ops = useMemo(() => activity._getOps(), [activity]);
|
|
17
|
+
|
|
16
18
|
const listFn = useMemo(
|
|
17
19
|
() =>
|
|
18
20
|
status === 'inprogress'
|
|
19
|
-
?
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
: async (opts: any) => ({
|
|
23
|
-
Data: (await activity.getCompletedList(opts)) as ActivityRowType<A>[],
|
|
24
|
-
}),
|
|
25
|
-
[activity, status],
|
|
21
|
+
? (opts: any) => ops.inProgressList(opts)
|
|
22
|
+
: (opts: any) => ops.completedList(opts),
|
|
23
|
+
[ops, status],
|
|
26
24
|
);
|
|
27
25
|
|
|
28
26
|
const countFn = useMemo(
|
|
29
27
|
() =>
|
|
30
28
|
status === 'inprogress'
|
|
31
|
-
?
|
|
32
|
-
:
|
|
33
|
-
[
|
|
29
|
+
? (opts: any) => ops.inProgressCount(opts)
|
|
30
|
+
: (opts: any) => ops.completedCount(opts),
|
|
31
|
+
[ops, status],
|
|
34
32
|
);
|
|
35
33
|
|
|
36
34
|
return useTable<ActivityRowType<A>>({
|
|
@@ -21,8 +21,9 @@ import type {
|
|
|
21
21
|
* Key principle: Item has NO state. It's a view over RHF's state.
|
|
22
22
|
* Editable fields get set(), readonly fields do not.
|
|
23
23
|
*
|
|
24
|
-
* In create mode (no _id),
|
|
25
|
-
*
|
|
24
|
+
* Draft-based upload: In create mode (no _id), upload() automatically creates
|
|
25
|
+
* a draft record via draftInteraction() to get an _id, then uploads immediately.
|
|
26
|
+
* On form submit, if a draft _id exists, update() is used instead of create().
|
|
26
27
|
*
|
|
27
28
|
* @param bdo - The BDO instance for field metadata
|
|
28
29
|
* @param form - The RHF useForm return object
|
|
@@ -35,11 +36,34 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
35
36
|
const fields = bdo.getFields();
|
|
36
37
|
const accessorCache = new Map<string, EditableFormFieldAccessorType<unknown> | ReadonlyFormFieldAccessorType<unknown>>();
|
|
37
38
|
|
|
39
|
+
// Draft tracking for create mode — shared across all attachment fields in this form
|
|
38
40
|
const boIdShared = bdo.getBoId();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
let draftId: string | null = null;
|
|
42
|
+
let draftPromise: Promise<string> | null = null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Ensures a record _id exists for attachment uploads.
|
|
46
|
+
* In edit mode, returns the existing _id.
|
|
47
|
+
* In create mode, creates a draft record via draftInteraction() to get an _id.
|
|
48
|
+
* The draft _id is shared across all attachment fields and only created once.
|
|
49
|
+
*/
|
|
50
|
+
async function ensureDraft(): Promise<string> {
|
|
51
|
+
// If form already has an _id (edit mode or previous draft), use it
|
|
52
|
+
const existing = form.getValues("_id" as Path<FieldValues>) as string | undefined;
|
|
53
|
+
if (existing) return existing;
|
|
54
|
+
if (draftId) return draftId;
|
|
55
|
+
if (!draftPromise) {
|
|
56
|
+
draftPromise = api(boIdShared).draftInteraction({}).then((d: any) => {
|
|
57
|
+
draftId = d._id;
|
|
58
|
+
form.setValue("_id" as Path<FieldValues>, draftId as any, { shouldDirty: false });
|
|
59
|
+
return draftId!;
|
|
60
|
+
}).catch((err: Error) => {
|
|
61
|
+
draftPromise = null;
|
|
62
|
+
throw err;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return draftPromise;
|
|
66
|
+
}
|
|
43
67
|
|
|
44
68
|
return new Proxy({} as FormItemType<ExtractEditableType<B>, ExtractReadonlyType<B>>, {
|
|
45
69
|
get(_, prop: string | symbol) {
|
|
@@ -63,6 +87,11 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
63
87
|
return () => form.trigger();
|
|
64
88
|
}
|
|
65
89
|
|
|
90
|
+
// Internal: check if a draft was created (used by handleSubmit)
|
|
91
|
+
if (prop === "_hasDraft") {
|
|
92
|
+
return () => !!draftId;
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
// Return cached accessor if available
|
|
67
96
|
if (accessorCache.has(prop)) {
|
|
68
97
|
return accessorCache.get(prop);
|
|
@@ -153,12 +182,17 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
153
182
|
// Enrich Image/File field accessors with attachment methods (draft-based upload)
|
|
154
183
|
if (fieldMeta.Type === "Image" || fieldMeta.Type === "File") {
|
|
155
184
|
const boId = boIdShared;
|
|
185
|
+
const requireInstanceId = (): string => {
|
|
186
|
+
const id = form.getValues("_id" as Path<FieldValues>) as string | undefined;
|
|
187
|
+
if (!id) throw new Error("Save the record before attachment operations");
|
|
188
|
+
return id;
|
|
189
|
+
};
|
|
156
190
|
|
|
157
191
|
if (fieldMeta.Type === "Image") {
|
|
158
192
|
// Image: single file upload — always uploads immediately (draft in create mode)
|
|
159
193
|
(accessor as any).upload = async (file: File): Promise<FileType> => {
|
|
160
194
|
validateFileExtension(file.name, "Image");
|
|
161
|
-
const id =
|
|
195
|
+
const id = await ensureDraft();
|
|
162
196
|
|
|
163
197
|
const [uploadInfo] = await api(boId).getUploadUrl(id, prop, [
|
|
164
198
|
{ FileName: file.name, Size: file.size, FileExtension: extractFileExtension(file.name) },
|
|
@@ -182,7 +216,7 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
182
216
|
|
|
183
217
|
(accessor as any).deleteAttachment = async (): Promise<void> => {
|
|
184
218
|
const val = form.getValues(prop as Path<FieldValues>) as any;
|
|
185
|
-
const instanceId =
|
|
219
|
+
const instanceId = requireInstanceId();
|
|
186
220
|
if (!(val?._id)) throw new Error(`${prop} has no image to delete`);
|
|
187
221
|
await api(boId).deleteAttachment(instanceId, prop, val._id);
|
|
188
222
|
form.setValue(prop as Path<FieldValues>, null as any, { shouldDirty: true });
|
|
@@ -190,7 +224,7 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
190
224
|
|
|
191
225
|
(accessor as any).getDownloadUrl = async (viewType?: AttachmentViewType): Promise<FileDownloadResponseType> => {
|
|
192
226
|
const val = form.getValues(prop as Path<FieldValues>) as any;
|
|
193
|
-
const instanceId =
|
|
227
|
+
const instanceId = requireInstanceId();
|
|
194
228
|
if (!(val?._id)) throw new Error(`${prop} has no image`);
|
|
195
229
|
return api(boId).getDownloadUrl(instanceId, prop, val._id, viewType);
|
|
196
230
|
};
|
|
@@ -198,7 +232,7 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
198
232
|
// File field — multi-file, always uploads immediately (draft in create mode)
|
|
199
233
|
(accessor as any).upload = async (files: File[]): Promise<FileType[]> => {
|
|
200
234
|
for (const file of files) validateFileExtension(file.name, "File");
|
|
201
|
-
const id =
|
|
235
|
+
const id = await ensureDraft();
|
|
202
236
|
|
|
203
237
|
const requests = files.map((file) => ({
|
|
204
238
|
FileName: file.name,
|
|
@@ -230,7 +264,7 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
230
264
|
|
|
231
265
|
(accessor as any).deleteAttachment = async (attachmentId: string): Promise<void> => {
|
|
232
266
|
const current = (form.getValues(prop as Path<FieldValues>) as any[]) ?? [];
|
|
233
|
-
const instanceId =
|
|
267
|
+
const instanceId = requireInstanceId();
|
|
234
268
|
await api(boId).deleteAttachment(instanceId, prop, attachmentId);
|
|
235
269
|
form.setValue(
|
|
236
270
|
prop as Path<FieldValues>,
|
|
@@ -243,13 +277,13 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
243
277
|
attachmentId: string,
|
|
244
278
|
viewType?: AttachmentViewType,
|
|
245
279
|
): Promise<FileDownloadResponseType> => {
|
|
246
|
-
const instanceId =
|
|
280
|
+
const instanceId = requireInstanceId();
|
|
247
281
|
return api(boId).getDownloadUrl(instanceId, prop, attachmentId, viewType);
|
|
248
282
|
};
|
|
249
283
|
(accessor as any).getDownloadUrls = async (
|
|
250
284
|
viewType?: AttachmentViewType,
|
|
251
285
|
): Promise<FileDownloadResponseType[]> => {
|
|
252
|
-
const instanceId =
|
|
286
|
+
const instanceId = requireInstanceId();
|
|
253
287
|
return api(boId).getDownloadUrls(instanceId, prop, viewType);
|
|
254
288
|
};
|
|
255
289
|
}
|
|
@@ -280,11 +314,16 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
280
314
|
// Enrich readonly Image/File field accessors with download methods
|
|
281
315
|
if (fieldMeta.Type === "Image" || fieldMeta.Type === "File") {
|
|
282
316
|
const boId = boIdShared;
|
|
317
|
+
const requireInstanceId = (): string => {
|
|
318
|
+
const id = form.getValues("_id" as Path<FieldValues>) as string | undefined;
|
|
319
|
+
if (!id) throw new Error("Cannot perform attachment operation: item has no _id. Save the item first.");
|
|
320
|
+
return id;
|
|
321
|
+
};
|
|
283
322
|
|
|
284
323
|
if (fieldMeta.Type === "Image") {
|
|
285
324
|
(accessor as any).getDownloadUrl = async (viewType?: AttachmentViewType): Promise<FileDownloadResponseType> => {
|
|
286
325
|
const val = form.getValues(prop as Path<FieldValues>) as any;
|
|
287
|
-
const instanceId =
|
|
326
|
+
const instanceId = requireInstanceId();
|
|
288
327
|
if (!(val?._id)) throw new Error(`${prop} has no image to download`);
|
|
289
328
|
return api(boId).getDownloadUrl(instanceId, prop, val._id, viewType);
|
|
290
329
|
};
|
|
@@ -293,13 +332,13 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
293
332
|
attachmentId: string,
|
|
294
333
|
viewType?: AttachmentViewType,
|
|
295
334
|
): Promise<FileDownloadResponseType> => {
|
|
296
|
-
const instanceId =
|
|
335
|
+
const instanceId = requireInstanceId();
|
|
297
336
|
return api(boId).getDownloadUrl(instanceId, prop, attachmentId, viewType);
|
|
298
337
|
};
|
|
299
338
|
(accessor as any).getDownloadUrls = async (
|
|
300
339
|
viewType?: AttachmentViewType,
|
|
301
340
|
): Promise<FileDownloadResponseType[]> => {
|
|
302
|
-
const instanceId =
|
|
341
|
+
const instanceId = requireInstanceId();
|
|
303
342
|
return api(boId).getDownloadUrls(instanceId, prop, viewType);
|
|
304
343
|
};
|
|
305
344
|
}
|
|
@@ -313,6 +352,8 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
313
352
|
if (typeof prop === "symbol") return false;
|
|
314
353
|
if (prop === "_id" || prop === "toJSON" || prop === "validate")
|
|
315
354
|
return true;
|
|
355
|
+
if (prop === "_hasDraft")
|
|
356
|
+
return true;
|
|
316
357
|
return prop in fields;
|
|
317
358
|
},
|
|
318
359
|
|
|
@@ -324,7 +365,7 @@ export function createItemProxy<B extends BaseBdo<any, any, any>>(
|
|
|
324
365
|
if (typeof prop === "symbol") return undefined;
|
|
325
366
|
return {
|
|
326
367
|
configurable: true,
|
|
327
|
-
enumerable: prop !== "toJSON" && prop !== "validate",
|
|
368
|
+
enumerable: prop !== "toJSON" && prop !== "validate" && prop !== "_hasDraft",
|
|
328
369
|
};
|
|
329
370
|
},
|
|
330
371
|
});
|
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
import type { ListOptionsType, SortType } from '../../../types/common';
|
|
2
1
|
import type { UseTableReturnType, PaginationStateType } from '../useTable/types';
|
|
3
2
|
import type { UseFilterOptionsType } from '../useFilter/types';
|
|
3
|
+
import type { SortType } from '../../../types/common';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Infer row type from BDO's list() return type */
|
|
13
|
-
export type BDORowType<B extends BDOTableSourceType> =
|
|
14
|
-
B extends { list(opts?: any): Promise<(infer R)[]> } ? R : never;
|
|
15
|
-
|
|
16
|
-
export interface UseBDOTableOptionsType<B extends BDOTableSourceType> {
|
|
17
|
-
/** BDO instance — list() and count() are called for data */
|
|
18
|
-
bdo: B;
|
|
5
|
+
export interface UseBDOTableOptionsType<T> {
|
|
6
|
+
/** BDO instance — only meta._id is used (for API routing) */
|
|
7
|
+
bdo: {
|
|
8
|
+
meta: { readonly _id: string; readonly name: string };
|
|
9
|
+
};
|
|
19
10
|
/** Initial state */
|
|
20
11
|
initialState?: {
|
|
21
12
|
sort?: SortType;
|
|
22
13
|
pagination?: PaginationStateType;
|
|
23
|
-
filter?: UseFilterOptionsType<
|
|
14
|
+
filter?: UseFilterOptionsType<T>;
|
|
24
15
|
};
|
|
25
16
|
/** Error callback */
|
|
26
17
|
onError?: (error: Error) => void;
|
|
27
|
-
/** Success callback — receives
|
|
28
|
-
onSuccess?: (data:
|
|
18
|
+
/** Success callback — receives rows from current page */
|
|
19
|
+
onSuccess?: (data: T[]) => void;
|
|
29
20
|
}
|
|
30
21
|
|
|
31
|
-
export type UseBDOTableReturnType<
|
|
32
|
-
UseTableReturnType<BDORowType<B>>;
|
|
22
|
+
export type UseBDOTableReturnType<T> = UseTableReturnType<T>;
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
+
import { api } from '../../../api/client';
|
|
1
2
|
import { useTable } from '../useTable';
|
|
2
|
-
import type {
|
|
3
|
-
BDOTableSourceType,
|
|
4
|
-
BDORowType,
|
|
5
|
-
UseBDOTableOptionsType,
|
|
6
|
-
UseBDOTableReturnType,
|
|
7
|
-
} from './types';
|
|
3
|
+
import type { UseBDOTableOptionsType, UseBDOTableReturnType } from './types';
|
|
8
4
|
|
|
9
|
-
export function useBDOTable<
|
|
10
|
-
options: UseBDOTableOptionsType<
|
|
11
|
-
): UseBDOTableReturnType<
|
|
5
|
+
export function useBDOTable<T = any>(
|
|
6
|
+
options: UseBDOTableOptionsType<T>,
|
|
7
|
+
): UseBDOTableReturnType<T> {
|
|
12
8
|
const { bdo, ...rest } = options;
|
|
13
9
|
|
|
14
|
-
return useTable<
|
|
10
|
+
return useTable<T>({
|
|
15
11
|
queryKey: ['table', bdo.meta._id],
|
|
16
|
-
listFn:
|
|
17
|
-
countFn:
|
|
12
|
+
listFn: (opts) => api<T>(bdo.meta._id).list(opts),
|
|
13
|
+
countFn: (opts) => api<T>(bdo.meta._id).count(opts),
|
|
18
14
|
...rest,
|
|
19
15
|
});
|
|
20
16
|
}
|