@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
|
@@ -7,6 +7,7 @@ import { useState, useMemo, useCallback, useRef, useEffect } from "react";
|
|
|
7
7
|
import { useQuery, useMutation, useQueryClient, useQueries, keepPreviousData } from "@tanstack/react-query";
|
|
8
8
|
import { api } from "../../../api";
|
|
9
9
|
import type { ListOptionsType, ListResponseType } from "../../../types/common";
|
|
10
|
+
import { toError } from "../../../utils/error-handling";
|
|
10
11
|
import { useFilter } from "../useFilter";
|
|
11
12
|
|
|
12
13
|
import type {
|
|
@@ -41,8 +42,13 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
41
42
|
|
|
42
43
|
const [search, setSearch] = useState({
|
|
43
44
|
query: initialState?.search || "",
|
|
45
|
+
debouncedQuery: initialState?.search || "",
|
|
44
46
|
});
|
|
45
47
|
|
|
48
|
+
// Debounce timeout ref for search
|
|
49
|
+
const searchDebounceRef = useRef<NodeJS.Timeout | null>(null);
|
|
50
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
51
|
+
|
|
46
52
|
const [sorting] = useState({
|
|
47
53
|
field: initialState?.sorting?.field || null,
|
|
48
54
|
direction: initialState?.sorting?.direction || null,
|
|
@@ -81,8 +87,8 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
81
87
|
// ============================================================
|
|
82
88
|
|
|
83
89
|
const filter = useFilter({
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
conditions: initialState?.filter?.conditions,
|
|
91
|
+
operator: initialState?.filter?.operator || "And",
|
|
86
92
|
});
|
|
87
93
|
|
|
88
94
|
// Helper to generate API options for a specific column
|
|
@@ -139,13 +145,13 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
139
145
|
];
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
// Add Search
|
|
143
|
-
if (search.
|
|
144
|
-
opts.Search = search.
|
|
148
|
+
// Add Search (debounced)
|
|
149
|
+
if (search.debouncedQuery) {
|
|
150
|
+
opts.Search = search.debouncedQuery;
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
return opts;
|
|
148
|
-
}, [filter.payload, columnPagination, sorting, search.
|
|
154
|
+
}, [filter.payload, columnPagination, sorting, search.debouncedQuery]);
|
|
149
155
|
|
|
150
156
|
// ============================================================
|
|
151
157
|
// COLUMN QUERY GENERATION
|
|
@@ -180,10 +186,10 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
180
186
|
const cardApiOptions = useMemo((): ListOptionsType => {
|
|
181
187
|
// This is for the GLOBAL count (ignoring column split)
|
|
182
188
|
const opts: ListOptionsType = {};
|
|
183
|
-
if (search.
|
|
189
|
+
if (search.debouncedQuery) opts.Search = search.debouncedQuery;
|
|
184
190
|
if (filter.payload) opts.Filter = filter.payload;
|
|
185
191
|
return opts;
|
|
186
|
-
}, [search.
|
|
192
|
+
}, [search.debouncedQuery, filter.payload]);
|
|
187
193
|
|
|
188
194
|
const {
|
|
189
195
|
data: countData,
|
|
@@ -196,7 +202,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
196
202
|
return await api<KanbanCardType<T>>(source).count(cardApiOptions);
|
|
197
203
|
} catch (err) {
|
|
198
204
|
if (onErrorRef.current) {
|
|
199
|
-
onErrorRef.current(err
|
|
205
|
+
onErrorRef.current(toError(err));
|
|
200
206
|
}
|
|
201
207
|
throw err;
|
|
202
208
|
}
|
|
@@ -258,7 +264,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
258
264
|
if (context?.previousCards && context?.queryKey) {
|
|
259
265
|
queryClient.setQueryData(context.queryKey, context.previousCards);
|
|
260
266
|
}
|
|
261
|
-
onErrorRef.current?.(error
|
|
267
|
+
onErrorRef.current?.(toError(error));
|
|
262
268
|
},
|
|
263
269
|
onSettled: (_data, _error, variables) => {
|
|
264
270
|
const columnId = variables.columnId;
|
|
@@ -280,7 +286,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
280
286
|
onCardUpdateRef.current?.({ _id: result.id, ...result.updates } as any);
|
|
281
287
|
},
|
|
282
288
|
onError: (error, _variables, _context) => {
|
|
283
|
-
onErrorRef.current?.(error
|
|
289
|
+
onErrorRef.current?.(toError(error));
|
|
284
290
|
},
|
|
285
291
|
onSettled: () => {
|
|
286
292
|
queryClient.invalidateQueries({ queryKey: ["kanban-cards", source] });
|
|
@@ -300,7 +306,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
300
306
|
onCardDeleteRef.current?.(id);
|
|
301
307
|
},
|
|
302
308
|
onError: (error, _id, _context) => {
|
|
303
|
-
onErrorRef.current?.(error
|
|
309
|
+
onErrorRef.current?.(toError(error));
|
|
304
310
|
},
|
|
305
311
|
onSettled: () => {
|
|
306
312
|
queryClient.invalidateQueries({ queryKey: ["kanban-cards", source] });
|
|
@@ -374,7 +380,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
374
380
|
if (context?.previousToData && context?.toQueryKey) {
|
|
375
381
|
queryClient.setQueryData(context.toQueryKey, context.previousToData);
|
|
376
382
|
}
|
|
377
|
-
onErrorRef.current?.(error
|
|
383
|
+
onErrorRef.current?.(toError(error));
|
|
378
384
|
},
|
|
379
385
|
onSettled: (_data, _error, variables) => {
|
|
380
386
|
const fromOpts = getColumnApiOptions(variables.fromColumnId);
|
|
@@ -401,7 +407,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
401
407
|
},
|
|
402
408
|
onSuccess: () => {},
|
|
403
409
|
onError: (error, _variables, _context) => {
|
|
404
|
-
onErrorRef.current?.(error
|
|
410
|
+
onErrorRef.current?.(toError(error));
|
|
405
411
|
},
|
|
406
412
|
onSettled: (_data, _error, variables) => {
|
|
407
413
|
const opts = getColumnApiOptions(variables.columnId);
|
|
@@ -513,11 +519,32 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
513
519
|
// ============================================================
|
|
514
520
|
|
|
515
521
|
const setSearchQuery = useCallback((value: string) => {
|
|
516
|
-
|
|
522
|
+
// Validate search query length to prevent DoS
|
|
523
|
+
if (value.length > 255) {
|
|
524
|
+
console.warn("Search query exceeds maximum length of 255 characters");
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Update immediate value for UI
|
|
529
|
+
setSearch((prev) => ({ ...prev, query: value }));
|
|
530
|
+
|
|
531
|
+
// Clear existing debounce timeout
|
|
532
|
+
if (searchDebounceRef.current) {
|
|
533
|
+
clearTimeout(searchDebounceRef.current);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Debounce the actual API query update
|
|
537
|
+
searchDebounceRef.current = setTimeout(() => {
|
|
538
|
+
setSearch((prev) => ({ ...prev, debouncedQuery: value }));
|
|
539
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
517
540
|
}, []);
|
|
518
541
|
|
|
519
542
|
const clearSearch = useCallback(() => {
|
|
520
|
-
|
|
543
|
+
// Clear debounce timeout
|
|
544
|
+
if (searchDebounceRef.current) {
|
|
545
|
+
clearTimeout(searchDebounceRef.current);
|
|
546
|
+
}
|
|
547
|
+
setSearch({ query: "", debouncedQuery: "" });
|
|
521
548
|
}, []);
|
|
522
549
|
|
|
523
550
|
const totalCards = countData?.Count || 0;
|
|
@@ -555,10 +582,19 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
555
582
|
|
|
556
583
|
useEffect(() => {
|
|
557
584
|
if (error && onErrorRef.current) {
|
|
558
|
-
onErrorRef.current(error
|
|
585
|
+
onErrorRef.current(toError(error));
|
|
559
586
|
}
|
|
560
587
|
}, [error]);
|
|
561
588
|
|
|
589
|
+
// Cleanup debounce timeout on unmount
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
return () => {
|
|
592
|
+
if (searchDebounceRef.current) {
|
|
593
|
+
clearTimeout(searchDebounceRef.current);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}, []);
|
|
597
|
+
|
|
562
598
|
// ============================================================
|
|
563
599
|
// RETURN OBJECT
|
|
564
600
|
// ============================================================
|
|
@@ -574,7 +610,7 @@ export function useKanban<T extends Record<string, any> = Record<string, any>>(
|
|
|
574
610
|
isUpdating,
|
|
575
611
|
|
|
576
612
|
// Error Handling
|
|
577
|
-
error: error
|
|
613
|
+
error: error ? toError(error) : null,
|
|
578
614
|
|
|
579
615
|
// Card Operations (Flat Access)
|
|
580
616
|
createCard: createCardMutation.mutateAsync,
|
|
@@ -1,46 +1,40 @@
|
|
|
1
|
-
import type { ListResponseType,
|
|
2
|
-
|
|
1
|
+
import type { ListResponseType, SortType, ColumnDefinitionType } from "../../../types/common";
|
|
2
|
+
|
|
3
|
+
// Re-export ColumnDefinitionType for backwards compatibility
|
|
4
|
+
export type { ColumnDefinitionType };
|
|
5
|
+
import type { UseFilterReturnType, UseFilterOptionsType } from "../useFilter";
|
|
3
6
|
|
|
4
7
|
// ============================================================
|
|
5
|
-
// TYPE DEFINITIONS
|
|
8
|
+
// STATE TYPE DEFINITIONS
|
|
6
9
|
// ============================================================
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
enableFiltering?: boolean;
|
|
17
|
-
/** Custom transform function (overrides auto-formatting) */
|
|
18
|
-
transform?: (value: any, row: T) => React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Pagination state type
|
|
13
|
+
*/
|
|
14
|
+
export interface PaginationStateType {
|
|
15
|
+
/** Page number (1-indexed) */
|
|
16
|
+
pageNo: number;
|
|
17
|
+
/** Number of items per page */
|
|
18
|
+
pageSize: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// ============================================================
|
|
22
|
+
// TYPE DEFINITIONS
|
|
23
|
+
// ============================================================
|
|
24
|
+
|
|
21
25
|
export interface UseTableOptionsType<T> {
|
|
22
26
|
/** Data source identifier */
|
|
23
27
|
source: string;
|
|
24
28
|
/** Column configurations */
|
|
25
29
|
columns: ColumnDefinitionType<T>[];
|
|
26
|
-
/** Enable sorting functionality */
|
|
27
|
-
enableSorting?: boolean;
|
|
28
|
-
/** Enable filtering functionality */
|
|
29
|
-
enableFiltering?: boolean;
|
|
30
|
-
/** Enable pagination */
|
|
31
|
-
enablePagination?: boolean;
|
|
32
30
|
/** Initial state */
|
|
33
31
|
initialState?: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
direction: "asc" | "desc";
|
|
41
|
-
};
|
|
42
|
-
filters?: Array<ConditionType | ConditionGroupType>;
|
|
43
|
-
filterOperator?: ConditionGroupOperatorType;
|
|
32
|
+
/** Sort configuration: [{ "fieldName": "ASC" }] */
|
|
33
|
+
sort?: SortType;
|
|
34
|
+
/** Pagination state: { pageNo, pageSize } */
|
|
35
|
+
pagination?: PaginationStateType;
|
|
36
|
+
/** Filter state: { conditions, operator } */
|
|
37
|
+
filter?: UseFilterOptionsType<T>;
|
|
44
38
|
};
|
|
45
39
|
/** Error callback */
|
|
46
40
|
onError?: (error: Error) => void;
|
|
@@ -77,11 +71,11 @@ export interface UseTableReturnType<T> {
|
|
|
77
71
|
};
|
|
78
72
|
|
|
79
73
|
// Filter (Simplified chainable API)
|
|
80
|
-
filter: UseFilterReturnType
|
|
74
|
+
filter: UseFilterReturnType<T>;
|
|
81
75
|
|
|
82
76
|
// Pagination (Flat Access)
|
|
83
77
|
pagination: {
|
|
84
|
-
|
|
78
|
+
pageNo: number;
|
|
85
79
|
pageSize: number;
|
|
86
80
|
totalPages: number;
|
|
87
81
|
totalItems: number;
|
|
@@ -28,12 +28,9 @@ const table = useTable<ProductType>({
|
|
|
28
28
|
{ fieldId: "Category", label: "Category", enableSorting: true },
|
|
29
29
|
{ fieldId: "Stock", label: "Stock", enableSorting: true },
|
|
30
30
|
],
|
|
31
|
-
enableSorting: true,
|
|
32
|
-
enableFiltering: true,
|
|
33
|
-
enablePagination: true,
|
|
34
31
|
initialState: {
|
|
35
32
|
pagination: { pageNo: 1, pageSize: 10 },
|
|
36
|
-
|
|
33
|
+
sort: [{ Title: "ASC" }],
|
|
37
34
|
},
|
|
38
35
|
});
|
|
39
36
|
```
|
|
@@ -46,14 +43,9 @@ const table = useTable<ProductType>({
|
|
|
46
43
|
|----------|------|----------|-------------|
|
|
47
44
|
| `source` | `string` | Yes | Data source identifier (Business Data Object name) |
|
|
48
45
|
| `columns` | `ColumnDefinition<T>[]` | Yes | Column configurations |
|
|
49
|
-
| `
|
|
50
|
-
| `enableFiltering` | `boolean` | No | Enable filtering functionality |
|
|
51
|
-
| `enablePagination` | `boolean` | No | Enable pagination |
|
|
52
|
-
| `fieldDefinitions` | `Record<keyof T, FieldDefinition>` | No | Field definitions for filter validation |
|
|
53
|
-
| `initialState` | `object` | No | Initial state for pagination, sorting, filters |
|
|
46
|
+
| `initialState` | `object` | No | Initial state for pagination, sorting, filters. Pagination defaults to `{ pageNo: 1, pageSize: 10 }` |
|
|
54
47
|
| `onError` | `(error: Error) => void` | No | Error callback |
|
|
55
48
|
| `onSuccess` | `(data: T[]) => void` | No | Success callback |
|
|
56
|
-
| `onFilterError` | `(errors: ValidationError[]) => void` | No | Filter error callback |
|
|
57
49
|
|
|
58
50
|
### ColumnDefinition<T>
|
|
59
51
|
|
|
@@ -113,14 +105,14 @@ const table = useTable<ProductType>({
|
|
|
113
105
|
| `filter.addCondition` | `(condition) => string` | Add a filter condition, returns ID |
|
|
114
106
|
| `filter.updateCondition` | `(id, updates) => boolean` | Update a condition |
|
|
115
107
|
| `filter.removeCondition` | `(id) => boolean` | Remove a condition |
|
|
116
|
-
| `filter.
|
|
108
|
+
| `filter.clearAllConditions` | `() => void` | Clear all conditions |
|
|
117
109
|
| `filter.getCondition` | `(id) => FilterConditionWithId` | Get a specific condition |
|
|
118
110
|
| `filter.setLogicalOperator` | `(operator) => void` | Set logical operator |
|
|
119
111
|
|
|
120
112
|
#### Pagination
|
|
121
113
|
| Property | Type | Description |
|
|
122
114
|
|----------|------|-------------|
|
|
123
|
-
| `pagination.
|
|
115
|
+
| `pagination.pageNo` | `number` | Current page (1-indexed) |
|
|
124
116
|
| `pagination.pageSize` | `number` | Items per page |
|
|
125
117
|
| `pagination.totalPages` | `number` | Total number of pages |
|
|
126
118
|
| `pagination.totalItems` | `number` | Total item count |
|
|
@@ -179,12 +171,9 @@ const table = useTable<SellerProduct>({
|
|
|
179
171
|
{ fieldId: "Category", label: "Category", enableSorting: true },
|
|
180
172
|
{ fieldId: "Stock", label: "Stock", enableSorting: true },
|
|
181
173
|
],
|
|
182
|
-
enableSorting: true,
|
|
183
|
-
enableFiltering: true,
|
|
184
|
-
enablePagination: true,
|
|
185
174
|
initialState: {
|
|
186
175
|
pagination: { pageNo: 1, pageSize: 10 },
|
|
187
|
-
|
|
176
|
+
sort: [{ Title: "ASC" }],
|
|
188
177
|
},
|
|
189
178
|
});
|
|
190
179
|
|
|
@@ -220,18 +209,15 @@ const table = useTable<SellerProduct>({
|
|
|
220
209
|
const table = useTable<BuyerProduct>({
|
|
221
210
|
source: "BDO_AmazonProductMaster",
|
|
222
211
|
columns: [...],
|
|
223
|
-
enableSorting: true,
|
|
224
|
-
enableFiltering: true,
|
|
225
|
-
enablePagination: true,
|
|
226
212
|
initialState: {
|
|
227
213
|
pagination: { pageNo: 1, pageSize: 10 },
|
|
228
|
-
|
|
214
|
+
sort: [{ Title: "ASC" }],
|
|
229
215
|
},
|
|
230
216
|
});
|
|
231
217
|
|
|
232
218
|
// Category filter
|
|
233
219
|
const handleCategoryChange = (category: string) => {
|
|
234
|
-
table.filter.
|
|
220
|
+
table.filter.clearAllConditions();
|
|
235
221
|
if (category !== "all") {
|
|
236
222
|
table.filter.addCondition({
|
|
237
223
|
lhsField: "Category",
|
|
@@ -296,7 +282,7 @@ const handleSortChange = (value: string) => {
|
|
|
296
282
|
<Button
|
|
297
283
|
onClick={() => {
|
|
298
284
|
table.search.clear();
|
|
299
|
-
table.filter.
|
|
285
|
+
table.filter.clearAllConditions();
|
|
300
286
|
}}
|
|
301
287
|
>
|
|
302
288
|
Clear All Filters
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useState, useMemo, useCallback } from "react";
|
|
1
|
+
import { useState, useMemo, useCallback, useRef, useEffect } from "react";
|
|
2
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
3
|
import { api } from "../../../api";
|
|
4
4
|
import type { ListResponseType, ListOptionsType } from "../../../types/common";
|
|
5
|
+
import { toError } from "../../../utils/error-handling";
|
|
5
6
|
import { useFilter } from "../useFilter";
|
|
6
7
|
import type { UseTableOptionsType, UseTableReturnType } from "./types";
|
|
7
8
|
|
|
@@ -10,7 +11,8 @@ import type { UseTableOptionsType, UseTableReturnType } from "./types";
|
|
|
10
11
|
// ============================================================
|
|
11
12
|
|
|
12
13
|
interface SearchState {
|
|
13
|
-
query: string;
|
|
14
|
+
query: string; // Immediate UI value
|
|
15
|
+
debouncedQuery: string; // Debounced value for API queries
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
interface SortingState<T> {
|
|
@@ -36,12 +38,26 @@ export function useTable<T = any>(
|
|
|
36
38
|
|
|
37
39
|
const [search, setSearch] = useState<SearchState>({
|
|
38
40
|
query: "",
|
|
41
|
+
debouncedQuery: "",
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// Debounce timeout ref for search
|
|
45
|
+
const searchDebounceRef = useRef<NodeJS.Timeout | null>(null);
|
|
46
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
47
|
+
|
|
48
|
+
// Parse initial sort from API format [{ "fieldName": "ASC" }] to internal state
|
|
49
|
+
const getInitialSortState = (): SortingState<T> => {
|
|
50
|
+
const sortConfig = options.initialState?.sort;
|
|
51
|
+
if (sortConfig && sortConfig.length > 0) {
|
|
52
|
+
const firstSort = sortConfig[0];
|
|
53
|
+
const field = Object.keys(firstSort)[0] as keyof T;
|
|
54
|
+
const direction = firstSort[field as string]?.toLowerCase() as "asc" | "desc";
|
|
55
|
+
return { field, direction };
|
|
56
|
+
}
|
|
57
|
+
return { field: null, direction: null };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const [sorting, setSorting] = useState<SortingState<T>>(getInitialSortState);
|
|
45
61
|
|
|
46
62
|
const [pagination, setPagination] = useState<PaginationState>({
|
|
47
63
|
pageNo: options.initialState?.pagination?.pageNo || 1,
|
|
@@ -52,9 +68,9 @@ export function useTable<T = any>(
|
|
|
52
68
|
// FILTER HOOK INTEGRATION
|
|
53
69
|
// ============================================================
|
|
54
70
|
|
|
55
|
-
const filter = useFilter({
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
const filter = useFilter<T>({
|
|
72
|
+
conditions: options.initialState?.filter?.conditions,
|
|
73
|
+
operator: options.initialState?.filter?.operator || "And",
|
|
58
74
|
});
|
|
59
75
|
|
|
60
76
|
// ============================================================
|
|
@@ -65,9 +81,9 @@ export function useTable<T = any>(
|
|
|
65
81
|
const countApiOptions = useMemo((): ListOptionsType => {
|
|
66
82
|
const opts: ListOptionsType = {};
|
|
67
83
|
|
|
68
|
-
// Add search query (affects count)
|
|
69
|
-
if (search.
|
|
70
|
-
opts.Search = search.
|
|
84
|
+
// Add debounced search query (affects count)
|
|
85
|
+
if (search.debouncedQuery) {
|
|
86
|
+
opts.Search = search.debouncedQuery;
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
// Add filter conditions (affects count)
|
|
@@ -76,7 +92,7 @@ export function useTable<T = any>(
|
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
return opts;
|
|
79
|
-
}, [search.
|
|
95
|
+
}, [search.debouncedQuery, filter.payload]);
|
|
80
96
|
|
|
81
97
|
// Options for list query - includes all options
|
|
82
98
|
const apiOptions = useMemo((): ListOptionsType => {
|
|
@@ -92,13 +108,11 @@ export function useTable<T = any>(
|
|
|
92
108
|
}
|
|
93
109
|
|
|
94
110
|
// Add pagination
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
opts.PageSize = pagination.pageSize;
|
|
98
|
-
}
|
|
111
|
+
opts.Page = pagination.pageNo;
|
|
112
|
+
opts.PageSize = pagination.pageSize;
|
|
99
113
|
|
|
100
114
|
return opts;
|
|
101
|
-
}, [countApiOptions, sorting, pagination
|
|
115
|
+
}, [countApiOptions, sorting, pagination]);
|
|
102
116
|
|
|
103
117
|
// ============================================================
|
|
104
118
|
// DATA FETCHING
|
|
@@ -121,7 +135,7 @@ export function useTable<T = any>(
|
|
|
121
135
|
return response;
|
|
122
136
|
} catch (err) {
|
|
123
137
|
if (options.onError) {
|
|
124
|
-
options.onError(err
|
|
138
|
+
options.onError(toError(err));
|
|
125
139
|
}
|
|
126
140
|
throw err;
|
|
127
141
|
}
|
|
@@ -144,7 +158,7 @@ export function useTable<T = any>(
|
|
|
144
158
|
return await api<T>(options.source).count(countApiOptions);
|
|
145
159
|
} catch (err) {
|
|
146
160
|
if (options.onError) {
|
|
147
|
-
options.onError(err
|
|
161
|
+
options.onError(toError(err));
|
|
148
162
|
}
|
|
149
163
|
throw err;
|
|
150
164
|
}
|
|
@@ -197,12 +211,43 @@ export function useTable<T = any>(
|
|
|
197
211
|
// ============================================================
|
|
198
212
|
|
|
199
213
|
const setSearchQuery = useCallback((value: string) => {
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
// Validate search query length to prevent DoS
|
|
215
|
+
if (value.length > 255) {
|
|
216
|
+
console.warn("Search query exceeds maximum length of 255 characters");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Update immediate value for UI
|
|
221
|
+
setSearch((prev) => ({ ...prev, query: value }));
|
|
222
|
+
|
|
223
|
+
// Clear existing debounce timeout
|
|
224
|
+
if (searchDebounceRef.current) {
|
|
225
|
+
clearTimeout(searchDebounceRef.current);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Debounce the actual API query update
|
|
229
|
+
searchDebounceRef.current = setTimeout(() => {
|
|
230
|
+
setSearch((prev) => ({ ...prev, debouncedQuery: value }));
|
|
231
|
+
setPagination((prev) => ({ ...prev, pageNo: 1 }));
|
|
232
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
202
233
|
}, []);
|
|
203
234
|
|
|
204
235
|
const clearSearch = useCallback(() => {
|
|
205
|
-
|
|
236
|
+
// Clear debounce timeout
|
|
237
|
+
if (searchDebounceRef.current) {
|
|
238
|
+
clearTimeout(searchDebounceRef.current);
|
|
239
|
+
}
|
|
240
|
+
setSearch({ query: "", debouncedQuery: "" });
|
|
241
|
+
setPagination((prev) => ({ ...prev, pageNo: 1 }));
|
|
242
|
+
}, []);
|
|
243
|
+
|
|
244
|
+
// Cleanup debounce timeout on unmount
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
return () => {
|
|
247
|
+
if (searchDebounceRef.current) {
|
|
248
|
+
clearTimeout(searchDebounceRef.current);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
206
251
|
}, []);
|
|
207
252
|
|
|
208
253
|
// ============================================================
|
|
@@ -263,7 +308,7 @@ export function useTable<T = any>(
|
|
|
263
308
|
isFetching: isFetching || isCountFetching,
|
|
264
309
|
|
|
265
310
|
// Error Handling
|
|
266
|
-
error: (error
|
|
311
|
+
error: error ? toError(error) : countError ? toError(countError) : null,
|
|
267
312
|
|
|
268
313
|
// Search (Flat Access)
|
|
269
314
|
search: {
|
|
@@ -286,7 +331,7 @@ export function useTable<T = any>(
|
|
|
286
331
|
|
|
287
332
|
// Pagination (Flat Access)
|
|
288
333
|
pagination: {
|
|
289
|
-
|
|
334
|
+
pageNo: pagination.pageNo,
|
|
290
335
|
pageSize: pagination.pageSize,
|
|
291
336
|
totalPages,
|
|
292
337
|
totalItems,
|