@ram_28/kf-ai-sdk 1.0.19 → 1.0.21
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 +45 -12
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/datetime.d.ts +59 -10
- package/dist/api/datetime.d.ts.map +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.mjs +43 -21
- package/dist/api.types.d.ts +2 -1
- package/dist/api.types.d.ts.map +1 -1
- package/dist/auth/AuthProvider.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +34 -34
- package/dist/base-types.d.ts +1 -1
- package/dist/base-types.d.ts.map +1 -1
- package/dist/client-BIkaIr2y.js +217 -0
- package/dist/client-DxjRcEtN.cjs +1 -0
- package/dist/components/hooks/useFilter/types.d.ts +14 -11
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
- package/dist/components/hooks/useForm/apiClient.d.ts +45 -4
- package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/types.d.ts +5 -22
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
- package/dist/components/hooks/useTable/types.d.ts +19 -31
- package/dist/components/hooks/useTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
- package/dist/error-handling-CAoD0Kwb.cjs +1 -0
- package/dist/error-handling-CrhTtD88.js +14 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +736 -750
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kanban.cjs +2 -2
- package/dist/kanban.mjs +333 -323
- package/dist/{metadata-0lZAfuTP.cjs → metadata-Bz8zJqC1.cjs} +1 -1
- package/dist/{metadata-B88D_pVS.js → metadata-VbQzyD2C.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +113 -96
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/base-fields.d.ts +71 -17
- package/dist/types/base-fields.d.ts.map +1 -1
- package/dist/types/common.d.ts +26 -18
- package/dist/types/common.d.ts.map +1 -1
- package/dist/useFilter-DzpP_ag0.cjs +1 -0
- package/dist/useFilter-H5bgAZQF.js +120 -0
- package/dist/utils/api/buildListOptions.d.ts +43 -0
- package/dist/utils/api/buildListOptions.d.ts.map +1 -0
- package/dist/utils/api/index.d.ts +2 -0
- package/dist/utils/api/index.d.ts.map +1 -0
- package/dist/utils/error-handling.d.ts +41 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/docs/QUICK_REFERENCE.md +142 -420
- package/docs/useAuth.md +52 -340
- package/docs/useFilter.md +858 -162
- package/docs/useForm.md +712 -501
- package/docs/useKanban.md +534 -279
- package/docs/useTable.md +725 -214
- package/package.json +1 -1
- package/sdk/api/client.ts +3 -41
- package/sdk/api/datetime.ts +98 -14
- package/sdk/api/index.ts +12 -6
- package/sdk/api.ts +6 -3
- package/sdk/api.types.ts +3 -4
- package/sdk/auth/AuthProvider.tsx +22 -24
- package/sdk/base-types.ts +2 -0
- package/sdk/components/hooks/useFilter/types.ts +14 -11
- package/sdk/components/hooks/useFilter/useFilter.ts +20 -18
- package/sdk/components/hooks/useForm/apiClient.ts +120 -5
- package/sdk/components/hooks/useForm/useForm.ts +97 -61
- package/sdk/components/hooks/useKanban/types.ts +7 -23
- package/sdk/components/hooks/useKanban/useKanban.ts +54 -18
- package/sdk/components/hooks/useTable/types.ts +26 -32
- package/sdk/components/hooks/useTable/useTable.llm.txt +8 -22
- package/sdk/components/hooks/useTable/useTable.ts +70 -25
- package/sdk/index.ts +157 -10
- package/sdk/table.types.ts +3 -0
- package/sdk/types/base-fields.ts +71 -17
- package/sdk/types/common.ts +33 -19
- package/sdk/utils/api/buildListOptions.ts +120 -0
- package/sdk/utils/api/index.ts +2 -0
- package/sdk/utils/error-handling.ts +150 -0
- package/sdk/utils/index.ts +6 -0
- package/dist/client-DgtkT50N.cjs +0 -1
- package/dist/client-V-WzUb8H.js +0 -237
- package/dist/useFilter-Dofowpr_.cjs +0 -1
- package/dist/useFilter-Dv-mr9QW.js +0 -117
package/package.json
CHANGED
package/sdk/api/client.ts
CHANGED
|
@@ -9,8 +9,6 @@ import type {
|
|
|
9
9
|
CreateUpdateResponseType,
|
|
10
10
|
DeleteResponseType,
|
|
11
11
|
CountResponseType,
|
|
12
|
-
DateTimeEncodedType,
|
|
13
|
-
DateEncodedType,
|
|
14
12
|
MetricOptionsType,
|
|
15
13
|
MetricResponseType,
|
|
16
14
|
PivotOptionsType,
|
|
@@ -157,40 +155,6 @@ export function getApiBaseUrl(): string {
|
|
|
157
155
|
return apiConfig.baseUrl || "";
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
/**
|
|
161
|
-
* Recursively process an object to decode datetime fields
|
|
162
|
-
*/
|
|
163
|
-
function decodeResponseData<T>(data: any): T {
|
|
164
|
-
if (data === null || data === undefined) {
|
|
165
|
-
return data;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (Array.isArray(data)) {
|
|
169
|
-
return data.map((item) => decodeResponseData(item)) as T;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (typeof data === "object") {
|
|
173
|
-
// Check for datetime encoding
|
|
174
|
-
if ("$__dt__" in data) {
|
|
175
|
-
return new Date((data as DateTimeEncodedType).$__dt__ * 1000) as T;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Check for date encoding
|
|
179
|
-
if ("$__d__" in data) {
|
|
180
|
-
return new Date((data as DateEncodedType).$__d__) as T;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Recursively process object properties
|
|
184
|
-
const result: any = {};
|
|
185
|
-
for (const [key, value] of Object.entries(data)) {
|
|
186
|
-
result[key] = decodeResponseData(value);
|
|
187
|
-
}
|
|
188
|
-
return result as T;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return data as T;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
158
|
/**
|
|
195
159
|
* Create a resource client for the specified Business Object
|
|
196
160
|
* @param bo_id - Business Object identifier (e.g., "user", "leave", "vendor")
|
|
@@ -212,7 +176,7 @@ export function api<T = any>(bo_id: string): ResourceClient<T> {
|
|
|
212
176
|
}
|
|
213
177
|
|
|
214
178
|
const responseData: ReadResponseType<T> = await response.json();
|
|
215
|
-
return
|
|
179
|
+
return responseData.Data;
|
|
216
180
|
},
|
|
217
181
|
|
|
218
182
|
async create(
|
|
@@ -278,10 +242,8 @@ export function api<T = any>(bo_id: string): ResourceClient<T> {
|
|
|
278
242
|
throw new Error(`Failed to list ${bo_id}: ${response.statusText}`);
|
|
279
243
|
}
|
|
280
244
|
|
|
281
|
-
const responseData: ListResponseType<
|
|
282
|
-
return
|
|
283
|
-
Data: responseData.Data.map((item) => decodeResponseData<T>(item)),
|
|
284
|
-
};
|
|
245
|
+
const responseData: ListResponseType<T> = await response.json();
|
|
246
|
+
return responseData;
|
|
285
247
|
},
|
|
286
248
|
|
|
287
249
|
async count(options?: ListOptionsType): Promise<CountResponseType> {
|
package/sdk/api/datetime.ts
CHANGED
|
@@ -1,33 +1,117 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DateEncodedType, DateTimeEncodedType } from "../types/base-fields";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Date format constants matching backend DatetimeFormat class
|
|
5
|
+
* Source: kf-ai-base/core/util/datetime_utils.py
|
|
5
6
|
*/
|
|
7
|
+
export const DatetimeFormat = {
|
|
8
|
+
/** Date format: "YYYY-MM-DD" */
|
|
9
|
+
DATE: "%Y-%m-%d",
|
|
10
|
+
/** Time format: "HH:MM:SS" */
|
|
11
|
+
TIME: "%H:%M:%S",
|
|
12
|
+
/** DateTime format: "YYYY-MM-DD HH:MM:SS" */
|
|
13
|
+
DATE_TIME: "%Y-%m-%d %H:%M:%S",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// DECODING FUNCTIONS (API Response → Date object)
|
|
18
|
+
// Use these in components to convert API response to Date
|
|
19
|
+
// ============================================================
|
|
6
20
|
|
|
7
21
|
/**
|
|
8
|
-
*
|
|
22
|
+
* Decode API date response to Date object
|
|
23
|
+
* @param encoded - API response format { "$__d__": "YYYY-MM-DD" }
|
|
24
|
+
* @returns JavaScript Date object
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const apiResponse = { "$__d__": "2025-03-15" };
|
|
28
|
+
* const date = decodeDate(apiResponse); // => Date object
|
|
9
29
|
*/
|
|
10
|
-
export function
|
|
11
|
-
|
|
30
|
+
export function decodeDate(encoded: DateEncodedType): Date {
|
|
31
|
+
const [year, month, day] = encoded.$__d__.split("-").map(Number);
|
|
32
|
+
return new Date(year, month - 1, day);
|
|
12
33
|
}
|
|
13
34
|
|
|
14
35
|
/**
|
|
15
|
-
* Decode API datetime
|
|
36
|
+
* Decode API datetime response to Date object
|
|
37
|
+
* @param encoded - API response format { "$__dt__": unix_timestamp_seconds }
|
|
38
|
+
* @returns JavaScript Date object
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const apiResponse = { "$__dt__": 1769110463 };
|
|
42
|
+
* const date = decodeDateTime(apiResponse); // => Date object
|
|
16
43
|
*/
|
|
17
|
-
export function
|
|
44
|
+
export function decodeDateTime(encoded: DateTimeEncodedType): Date {
|
|
18
45
|
return new Date(encoded.$__dt__ * 1000);
|
|
19
46
|
}
|
|
20
47
|
|
|
48
|
+
// ============================================================
|
|
49
|
+
// FORMATTING FUNCTIONS (Date object → API Request string)
|
|
50
|
+
// Use these when creating/updating records
|
|
51
|
+
// ============================================================
|
|
52
|
+
|
|
21
53
|
/**
|
|
22
|
-
*
|
|
54
|
+
* Format a Date object to API request date string
|
|
55
|
+
* @param date - JavaScript Date object
|
|
56
|
+
* @returns "YYYY-MM-DD" formatted string for API requests
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* formatDate(new Date(2025, 2, 15)) // => "2025-03-15"
|
|
23
60
|
*/
|
|
24
|
-
export function
|
|
25
|
-
|
|
61
|
+
export function formatDate(date: Date): string {
|
|
62
|
+
const year = date.getFullYear();
|
|
63
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
64
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
65
|
+
return `${year}-${month}-${day}`;
|
|
26
66
|
}
|
|
27
67
|
|
|
28
68
|
/**
|
|
29
|
-
*
|
|
69
|
+
* Format a Date object to API request datetime string
|
|
70
|
+
* @param date - JavaScript Date object
|
|
71
|
+
* @returns "YYYY-MM-DD HH:MM:SS" formatted string for API requests
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* formatDateTime(new Date(2025, 2, 15, 10, 30, 45)) // => "2025-03-15 10:30:45"
|
|
30
75
|
*/
|
|
31
|
-
export function
|
|
32
|
-
|
|
33
|
-
|
|
76
|
+
export function formatDateTime(date: Date): string {
|
|
77
|
+
const year = date.getFullYear();
|
|
78
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
79
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
80
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
81
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
82
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
83
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================
|
|
87
|
+
// PARSING FUNCTIONS (String → Date object)
|
|
88
|
+
// Use these when you have a date string and need a Date object
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse date string to Date object
|
|
93
|
+
* @param dateStr - "YYYY-MM-DD" formatted string
|
|
94
|
+
* @returns JavaScript Date object
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* parseDate("2025-03-15") // => Date object for March 15, 2025
|
|
98
|
+
*/
|
|
99
|
+
export function parseDate(dateStr: string): Date {
|
|
100
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
101
|
+
return new Date(year, month - 1, day);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse datetime string to Date object
|
|
106
|
+
* @param dateTimeStr - "YYYY-MM-DD HH:MM:SS" formatted string
|
|
107
|
+
* @returns JavaScript Date object
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* parseDateTime("2025-03-15 10:30:45") // => Date object
|
|
111
|
+
*/
|
|
112
|
+
export function parseDateTime(dateTimeStr: string): Date {
|
|
113
|
+
const [datePart, timePart] = dateTimeStr.split(" ");
|
|
114
|
+
const [year, month, day] = datePart.split("-").map(Number);
|
|
115
|
+
const [hours, minutes, seconds] = timePart.split(":").map(Number);
|
|
116
|
+
return new Date(year, month - 1, day, hours, minutes, seconds);
|
|
117
|
+
}
|
package/sdk/api/index.ts
CHANGED
|
@@ -10,10 +10,13 @@ export type { ResourceClient } from "./client";
|
|
|
10
10
|
|
|
11
11
|
// DateTime utilities
|
|
12
12
|
export {
|
|
13
|
-
|
|
14
|
-
decodeDatetime,
|
|
15
|
-
encodeDate,
|
|
13
|
+
DatetimeFormat,
|
|
16
14
|
decodeDate,
|
|
15
|
+
decodeDateTime,
|
|
16
|
+
formatDate,
|
|
17
|
+
formatDateTime,
|
|
18
|
+
parseDate,
|
|
19
|
+
parseDateTime,
|
|
17
20
|
} from "./datetime";
|
|
18
21
|
|
|
19
22
|
// Metadata API client
|
|
@@ -40,9 +43,6 @@ export type {
|
|
|
40
43
|
CreateUpdateResponseType,
|
|
41
44
|
DeleteResponseType,
|
|
42
45
|
CountResponseType,
|
|
43
|
-
// DateTime types
|
|
44
|
-
DateTimeEncodedType,
|
|
45
|
-
DateEncodedType,
|
|
46
46
|
// Metric types
|
|
47
47
|
MetricTypeType,
|
|
48
48
|
MetricFieldType,
|
|
@@ -58,3 +58,9 @@ export type {
|
|
|
58
58
|
// Fields types
|
|
59
59
|
FieldsResponseType,
|
|
60
60
|
} from "../types/common";
|
|
61
|
+
|
|
62
|
+
// Re-export date types from base-fields
|
|
63
|
+
export type {
|
|
64
|
+
DateEncodedType,
|
|
65
|
+
DateTimeEncodedType,
|
|
66
|
+
} from "../types/base-fields";
|
package/sdk/api.ts
CHANGED
|
@@ -14,10 +14,13 @@ export {
|
|
|
14
14
|
|
|
15
15
|
// DateTime utilities
|
|
16
16
|
export {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
formatDateTime,
|
|
18
|
+
decodeDateTime,
|
|
19
|
+
formatDate,
|
|
20
20
|
decodeDate,
|
|
21
|
+
parseDate,
|
|
22
|
+
parseDateTime,
|
|
23
|
+
DatetimeFormat,
|
|
21
24
|
} from './api/datetime';
|
|
22
25
|
|
|
23
26
|
// Metadata API client
|
package/sdk/api.types.ts
CHANGED
|
@@ -9,6 +9,9 @@ export type { ResourceClient } from './api/client';
|
|
|
9
9
|
// Metadata types
|
|
10
10
|
export type { BackendSchema, MetadataItem, FieldMetadata } from './api/metadata';
|
|
11
11
|
|
|
12
|
+
// DateTime types from base-fields
|
|
13
|
+
export type { DateTimeEncodedType, DateEncodedType } from './types/base-fields';
|
|
14
|
+
|
|
12
15
|
// Common API types
|
|
13
16
|
export type {
|
|
14
17
|
// Sort types
|
|
@@ -32,10 +35,6 @@ export type {
|
|
|
32
35
|
DeleteResponseType,
|
|
33
36
|
CountResponseType,
|
|
34
37
|
|
|
35
|
-
// DateTime types
|
|
36
|
-
DateTimeEncodedType,
|
|
37
|
-
DateEncodedType,
|
|
38
|
-
|
|
39
38
|
// Metric types
|
|
40
39
|
MetricTypeType,
|
|
41
40
|
MetricFieldType,
|
|
@@ -102,7 +102,8 @@ export function AuthProvider({
|
|
|
102
102
|
|
|
103
103
|
const status: AuthStatusType = useMemo(() => {
|
|
104
104
|
if (isLoading || isFetching) return "loading";
|
|
105
|
-
if (sessionData?.userDetails)
|
|
105
|
+
if (Object.keys(sessionData?.userDetails || {}).length > 0)
|
|
106
|
+
return "authenticated";
|
|
106
107
|
return "unauthenticated";
|
|
107
108
|
}, [isLoading, isFetching, sessionData]);
|
|
108
109
|
|
|
@@ -139,11 +140,7 @@ export function AuthProvider({
|
|
|
139
140
|
}, [queryError]);
|
|
140
141
|
|
|
141
142
|
useEffect(() => {
|
|
142
|
-
if (
|
|
143
|
-
status === "unauthenticated" &&
|
|
144
|
-
authConfig.autoRedirect &&
|
|
145
|
-
!isLoading
|
|
146
|
-
) {
|
|
143
|
+
if (status === "unauthenticated" && authConfig.autoRedirect && !isLoading) {
|
|
147
144
|
initiateLogin();
|
|
148
145
|
}
|
|
149
146
|
}, [status, isLoading, authConfig.autoRedirect]);
|
|
@@ -156,7 +153,7 @@ export function AuthProvider({
|
|
|
156
153
|
(provider?: AuthProviderNameType, options?: LoginOptionsType) => {
|
|
157
154
|
initiateLogin(provider, options);
|
|
158
155
|
},
|
|
159
|
-
[]
|
|
156
|
+
[],
|
|
160
157
|
);
|
|
161
158
|
|
|
162
159
|
const logout = useCallback(
|
|
@@ -164,36 +161,37 @@ export function AuthProvider({
|
|
|
164
161
|
queryClient.removeQueries({ queryKey: SESSION_QUERY_KEY });
|
|
165
162
|
await performLogout(options);
|
|
166
163
|
},
|
|
167
|
-
[queryClient]
|
|
164
|
+
[queryClient],
|
|
168
165
|
);
|
|
169
166
|
|
|
170
|
-
const refreshSession =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
const refreshSession =
|
|
168
|
+
useCallback(async (): Promise<SessionResponseType | null> => {
|
|
169
|
+
// Prevent concurrent refreshes - return existing data if already fetching
|
|
170
|
+
if (isFetching) {
|
|
171
|
+
return sessionData || null;
|
|
172
|
+
}
|
|
175
173
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
174
|
+
try {
|
|
175
|
+
const result = await refetch();
|
|
176
|
+
return result.data || null;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
setError(err as Error);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}, [refetch, isFetching, sessionData]);
|
|
184
182
|
|
|
185
183
|
const hasRole = useCallback(
|
|
186
184
|
(role: string): boolean => {
|
|
187
185
|
return user?.Role === role;
|
|
188
186
|
},
|
|
189
|
-
[user]
|
|
187
|
+
[user],
|
|
190
188
|
);
|
|
191
189
|
|
|
192
190
|
const hasAnyRole = useCallback(
|
|
193
191
|
(roles: string[]): boolean => {
|
|
194
192
|
return roles.includes(user?.Role || "");
|
|
195
193
|
},
|
|
196
|
-
[user]
|
|
194
|
+
[user],
|
|
197
195
|
);
|
|
198
196
|
|
|
199
197
|
const clearError = useCallback(() => {
|
|
@@ -240,7 +238,7 @@ export function AuthProvider({
|
|
|
240
238
|
error,
|
|
241
239
|
clearError,
|
|
242
240
|
_forceCheck,
|
|
243
|
-
]
|
|
241
|
+
],
|
|
244
242
|
);
|
|
245
243
|
|
|
246
244
|
// ============================================================
|
package/sdk/base-types.ts
CHANGED
|
@@ -57,18 +57,21 @@ export interface ConditionGroupBuilder {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Hook options (minimal configuration)
|
|
60
|
+
* Used for initializing useFilter, and also for initialState in useTable/useKanban
|
|
61
|
+
* @template T - Data type for type-safe field names (defaults to any)
|
|
60
62
|
*/
|
|
61
|
-
export interface UseFilterOptionsType {
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
63
|
+
export interface UseFilterOptionsType<T = any> {
|
|
64
|
+
/** Filter conditions */
|
|
65
|
+
conditions?: Array<ConditionType<T> | ConditionGroupType<T>>;
|
|
66
|
+
/** Operator for combining conditions (defaults to "And") */
|
|
67
|
+
operator?: ConditionGroupOperatorType;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* Hook return interface with nested filter support
|
|
72
|
+
* @template T - Data type for type-safe field names (defaults to any)
|
|
70
73
|
*/
|
|
71
|
-
export interface UseFilterReturnType {
|
|
74
|
+
export interface UseFilterReturnType<T = any> {
|
|
72
75
|
// ============================================================
|
|
73
76
|
// STATE (read-only)
|
|
74
77
|
// ============================================================
|
|
@@ -77,10 +80,10 @@ export interface UseFilterReturnType {
|
|
|
77
80
|
operator: ConditionGroupOperatorType;
|
|
78
81
|
|
|
79
82
|
/** Current filter items (with id populated) */
|
|
80
|
-
items: Array<ConditionType | ConditionGroupType
|
|
83
|
+
items: Array<ConditionType<T> | ConditionGroupType<T>>;
|
|
81
84
|
|
|
82
85
|
/** Ready-to-use API payload (id stripped, undefined if no conditions) */
|
|
83
|
-
payload: FilterType | undefined;
|
|
86
|
+
payload: FilterType<T> | undefined;
|
|
84
87
|
|
|
85
88
|
/** Whether any conditions exist */
|
|
86
89
|
hasConditions: boolean;
|
|
@@ -95,7 +98,7 @@ export interface UseFilterReturnType {
|
|
|
95
98
|
* @param parentId - Optional id of the parent ConditionGroup. If omitted, adds at root level
|
|
96
99
|
* @returns The id of the created condition
|
|
97
100
|
*/
|
|
98
|
-
addCondition: (condition: Omit<ConditionType
|
|
101
|
+
addCondition: (condition: Omit<ConditionType<T>, "id">, parentId?: string) => string;
|
|
99
102
|
|
|
100
103
|
/**
|
|
101
104
|
* Add a condition group at root level or to a specific parent group
|
|
@@ -114,7 +117,7 @@ export interface UseFilterReturnType {
|
|
|
114
117
|
* @param id - The id of the condition to update
|
|
115
118
|
* @param updates - Partial updates to apply
|
|
116
119
|
*/
|
|
117
|
-
updateCondition: (id: string, updates: Partial<Omit<ConditionType
|
|
120
|
+
updateCondition: (id: string, updates: Partial<Omit<ConditionType<T>, "id">>) => void;
|
|
118
121
|
|
|
119
122
|
/**
|
|
120
123
|
* Update a condition group's operator by id
|
|
@@ -138,7 +141,7 @@ export interface UseFilterReturnType {
|
|
|
138
141
|
* @param id - The id to look up
|
|
139
142
|
* @returns The item or undefined if not found
|
|
140
143
|
*/
|
|
141
|
-
getCondition: (id: string) => ConditionType | ConditionGroupType | undefined;
|
|
144
|
+
getCondition: (id: string) => ConditionType<T> | ConditionGroupType<T> | undefined;
|
|
142
145
|
|
|
143
146
|
// ============================================================
|
|
144
147
|
// UTILITY
|
|
@@ -16,9 +16,11 @@ let idCounter = 0;
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Generate a unique ID for conditions and groups
|
|
19
|
+
* Uses timestamp + random component + counter to prevent collisions
|
|
19
20
|
*/
|
|
20
21
|
const generateId = (): string => {
|
|
21
|
-
|
|
22
|
+
const random = Math.random().toString(36).substring(2, 9);
|
|
23
|
+
return `filter_${Date.now()}_${random}_${++idCounter}`;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -178,22 +180,22 @@ const addToParent = (
|
|
|
178
180
|
// USE FILTER HOOK - Nested Filter Support
|
|
179
181
|
// ============================================================
|
|
180
182
|
|
|
181
|
-
export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnType {
|
|
183
|
+
export function useFilter<T = any>(options: UseFilterOptionsType<T> = {}): UseFilterReturnType<T> {
|
|
182
184
|
// Initialize items with ids
|
|
183
|
-
const [items, setItems] = useState<Array<ConditionType | ConditionGroupType
|
|
184
|
-
cloneWithIds(options.
|
|
185
|
+
const [items, setItems] = useState<Array<ConditionType<T> | ConditionGroupType<T>>>(() =>
|
|
186
|
+
cloneWithIds(options.conditions || []) as Array<ConditionType<T> | ConditionGroupType<T>>
|
|
185
187
|
);
|
|
186
188
|
|
|
187
189
|
const [operator, setOperatorState] = useState<ConditionGroupOperatorType>(
|
|
188
|
-
options.
|
|
190
|
+
options.operator || "And"
|
|
189
191
|
);
|
|
190
192
|
|
|
191
193
|
// Build payload for API (strip ids)
|
|
192
|
-
const payload = useMemo((): FilterType | undefined => {
|
|
194
|
+
const payload = useMemo((): FilterType<T> | undefined => {
|
|
193
195
|
if (items.length === 0) return undefined;
|
|
194
196
|
return {
|
|
195
197
|
Operator: operator,
|
|
196
|
-
Condition: stripIds(items)
|
|
198
|
+
Condition: stripIds(items) as Array<ConditionType<T> | ConditionGroupType<T>>,
|
|
197
199
|
};
|
|
198
200
|
}, [items, operator]);
|
|
199
201
|
|
|
@@ -204,11 +206,11 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
|
|
|
204
206
|
// ============================================================
|
|
205
207
|
|
|
206
208
|
const addCondition = useCallback(
|
|
207
|
-
(condition: Omit<ConditionType
|
|
209
|
+
(condition: Omit<ConditionType<T>, "id">, parentId?: string): string => {
|
|
208
210
|
const id = generateId();
|
|
209
|
-
const newCondition
|
|
211
|
+
const newCondition = { ...condition, id } as ConditionType<T>;
|
|
210
212
|
if (parentId) {
|
|
211
|
-
setItems((prev) => addToParent(prev, parentId, newCondition));
|
|
213
|
+
setItems((prev) => addToParent(prev, parentId, newCondition) as Array<ConditionType<T> | ConditionGroupType<T>>);
|
|
212
214
|
} else {
|
|
213
215
|
setItems((prev) => [...prev, newCondition]);
|
|
214
216
|
}
|
|
@@ -220,13 +222,13 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
|
|
|
220
222
|
const addConditionGroup = useCallback(
|
|
221
223
|
(groupOperator: ConditionGroupOperatorType, parentId?: string): string => {
|
|
222
224
|
const id = generateId();
|
|
223
|
-
const newGroup: ConditionGroupType = {
|
|
225
|
+
const newGroup: ConditionGroupType<T> = {
|
|
224
226
|
id,
|
|
225
227
|
Operator: groupOperator,
|
|
226
228
|
Condition: [],
|
|
227
229
|
};
|
|
228
230
|
if (parentId) {
|
|
229
|
-
setItems((prev) => addToParent(prev, parentId, newGroup));
|
|
231
|
+
setItems((prev) => addToParent(prev, parentId, newGroup) as Array<ConditionType<T> | ConditionGroupType<T>>);
|
|
230
232
|
} else {
|
|
231
233
|
setItems((prev) => [...prev, newGroup]);
|
|
232
234
|
}
|
|
@@ -240,14 +242,14 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
|
|
|
240
242
|
// ============================================================
|
|
241
243
|
|
|
242
244
|
const updateCondition = useCallback(
|
|
243
|
-
(id: string, updates: Partial<Omit<ConditionType
|
|
245
|
+
(id: string, updates: Partial<Omit<ConditionType<T>, "id">>): void => {
|
|
244
246
|
setItems((prev) =>
|
|
245
247
|
updateInTree(prev, id, (item) => {
|
|
246
248
|
if (!isConditionGroup(item)) {
|
|
247
249
|
return { ...item, ...updates };
|
|
248
250
|
}
|
|
249
251
|
return item;
|
|
250
|
-
})
|
|
252
|
+
}) as Array<ConditionType<T> | ConditionGroupType<T>>
|
|
251
253
|
);
|
|
252
254
|
},
|
|
253
255
|
[]
|
|
@@ -261,7 +263,7 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
|
|
|
261
263
|
return { ...item, Operator: newOperator };
|
|
262
264
|
}
|
|
263
265
|
return item;
|
|
264
|
-
})
|
|
266
|
+
}) as Array<ConditionType<T> | ConditionGroupType<T>>
|
|
265
267
|
);
|
|
266
268
|
},
|
|
267
269
|
[]
|
|
@@ -272,12 +274,12 @@ export function useFilter(options: UseFilterOptionsType = {}): UseFilterReturnTy
|
|
|
272
274
|
// ============================================================
|
|
273
275
|
|
|
274
276
|
const removeCondition = useCallback((id: string): void => {
|
|
275
|
-
setItems((prev) => removeFromTree(prev, id));
|
|
277
|
+
setItems((prev) => removeFromTree(prev, id) as Array<ConditionType<T> | ConditionGroupType<T>>);
|
|
276
278
|
}, []);
|
|
277
279
|
|
|
278
280
|
const getCondition = useCallback(
|
|
279
|
-
(id: string): ConditionType | ConditionGroupType | undefined => {
|
|
280
|
-
return findById(items, id);
|
|
281
|
+
(id: string): ConditionType<T> | ConditionGroupType<T> | undefined => {
|
|
282
|
+
return findById(items, id) as ConditionType<T> | ConditionGroupType<T> | undefined;
|
|
281
283
|
},
|
|
282
284
|
[items]
|
|
283
285
|
);
|