@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/docs/useTable.md
CHANGED
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
# useTable
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Complete table state management with data fetching, sorting, filtering, search, and pagination.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Integrates with `useFilter` for advanced filtering capabilities with nested condition groups
|
|
7
|
-
- Handles API interactions automatically using React Query for caching and background refetching
|
|
8
|
-
- Returns flattened state accessors (`sort.field`, `pagination.currentPage`) for easy component integration
|
|
9
|
-
|
|
10
|
-
## Type Reference
|
|
5
|
+
## Imports
|
|
11
6
|
|
|
12
7
|
```typescript
|
|
13
8
|
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
14
|
-
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
15
9
|
import type {
|
|
16
10
|
UseTableOptionsType,
|
|
17
11
|
UseTableReturnType,
|
|
18
12
|
ColumnDefinitionType,
|
|
13
|
+
PaginationStateType,
|
|
19
14
|
} from "@ram_28/kf-ai-sdk/table/types";
|
|
20
|
-
|
|
21
|
-
ConditionType,
|
|
22
|
-
ConditionGroupType,
|
|
23
|
-
ConditionOperatorType,
|
|
24
|
-
ConditionGroupOperatorType,
|
|
25
|
-
FilterType,
|
|
26
|
-
UseFilterReturnType,
|
|
27
|
-
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
28
|
-
import type { ListResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
29
|
-
|
|
30
|
-
// Condition operators for comparing field values
|
|
31
|
-
type ConditionOperatorType =
|
|
32
|
-
| "EQ" | "NE" | "GT" | "GTE" | "LT" | "LTE"
|
|
33
|
-
| "Between" | "NotBetween"
|
|
34
|
-
| "IN" | "NIN"
|
|
35
|
-
| "Empty" | "NotEmpty"
|
|
36
|
-
| "Contains" | "NotContains"
|
|
37
|
-
| "MinLength" | "MaxLength";
|
|
38
|
-
|
|
39
|
-
// Group operators for combining conditions
|
|
40
|
-
type ConditionGroupOperatorType = "And" | "Or" | "Not";
|
|
15
|
+
```
|
|
41
16
|
|
|
17
|
+
## Type Definitions
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
42
20
|
// Column configuration
|
|
43
21
|
interface ColumnDefinitionType<T> {
|
|
44
22
|
fieldId: keyof T;
|
|
@@ -48,18 +26,20 @@ interface ColumnDefinitionType<T> {
|
|
|
48
26
|
transform?: (value: any, row: T) => React.ReactNode;
|
|
49
27
|
}
|
|
50
28
|
|
|
29
|
+
// Pagination state
|
|
30
|
+
interface PaginationStateType {
|
|
31
|
+
pageNo: number;
|
|
32
|
+
pageSize: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
51
35
|
// Hook options
|
|
52
36
|
interface UseTableOptionsType<T> {
|
|
53
37
|
source: string;
|
|
54
38
|
columns: ColumnDefinitionType<T>[];
|
|
55
|
-
enableSorting?: boolean;
|
|
56
|
-
enableFiltering?: boolean;
|
|
57
|
-
enablePagination?: boolean;
|
|
58
39
|
initialState?: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
filterOperator?: ConditionGroupOperatorType;
|
|
40
|
+
sort?: Array<Record<string, "ASC" | "DESC">>;
|
|
41
|
+
pagination?: PaginationStateType; // Defaults: { pageNo: 1, pageSize: 10 }
|
|
42
|
+
filter?: UseFilterOptionsType<T>; // { conditions?, operator? }
|
|
63
43
|
};
|
|
64
44
|
onError?: (error: Error) => void;
|
|
65
45
|
onSuccess?: (data: T[]) => void;
|
|
@@ -67,23 +47,16 @@ interface UseTableOptionsType<T> {
|
|
|
67
47
|
|
|
68
48
|
// Hook return type
|
|
69
49
|
interface UseTableReturnType<T> {
|
|
70
|
-
// Data
|
|
71
50
|
rows: T[];
|
|
72
51
|
totalItems: number;
|
|
73
|
-
|
|
74
|
-
// Loading states
|
|
75
52
|
isLoading: boolean;
|
|
76
53
|
isFetching: boolean;
|
|
77
54
|
error: Error | null;
|
|
78
|
-
|
|
79
|
-
// Search
|
|
80
55
|
search: {
|
|
81
56
|
query: string;
|
|
82
57
|
setQuery: (value: string) => void;
|
|
83
58
|
clear: () => void;
|
|
84
59
|
};
|
|
85
|
-
|
|
86
|
-
// Sort
|
|
87
60
|
sort: {
|
|
88
61
|
field: keyof T | null;
|
|
89
62
|
direction: "asc" | "desc" | null;
|
|
@@ -91,13 +64,9 @@ interface UseTableReturnType<T> {
|
|
|
91
64
|
clear: () => void;
|
|
92
65
|
set: (field: keyof T, direction: "asc" | "desc") => void;
|
|
93
66
|
};
|
|
94
|
-
|
|
95
|
-
// Filter (uses useFilter internally)
|
|
96
|
-
filter: UseFilterReturnType;
|
|
97
|
-
|
|
98
|
-
// Pagination
|
|
67
|
+
filter: UseFilterReturnType<T>;
|
|
99
68
|
pagination: {
|
|
100
|
-
|
|
69
|
+
pageNo: number;
|
|
101
70
|
pageSize: number;
|
|
102
71
|
totalPages: number;
|
|
103
72
|
totalItems: number;
|
|
@@ -108,192 +77,133 @@ interface UseTableReturnType<T> {
|
|
|
108
77
|
goToPage: (page: number) => void;
|
|
109
78
|
setPageSize: (size: number) => void;
|
|
110
79
|
};
|
|
111
|
-
|
|
112
|
-
// Operations
|
|
113
80
|
refetch: () => Promise<ListResponseType<T>>;
|
|
114
81
|
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Basic Example
|
|
85
|
+
|
|
86
|
+
A minimal table displaying data with loading and error states.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
90
|
+
import type { ColumnDefinitionType } from "@ram_28/kf-ai-sdk/table/types";
|
|
91
|
+
import { Product, ProductType } from "../sources";
|
|
92
|
+
import { Roles } from "../sources/roles";
|
|
115
93
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
94
|
+
type BuyerProduct = ProductType<typeof Roles.Buyer>;
|
|
95
|
+
|
|
96
|
+
function ProductsTable() {
|
|
97
|
+
const product = new Product(Roles.Buyer);
|
|
98
|
+
|
|
99
|
+
const columns: ColumnDefinitionType<BuyerProduct>[] = [
|
|
100
|
+
{ fieldId: "Title", label: "Name" },
|
|
101
|
+
{ fieldId: "Price", label: "Price" },
|
|
102
|
+
{ fieldId: "Category", label: "Category" },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const table = useTable<BuyerProduct>({
|
|
106
|
+
source: product._id,
|
|
107
|
+
columns,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (table.isLoading) return <div>Loading...</div>;
|
|
111
|
+
if (table.error) return <div>Error: {table.error.message}</div>;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<table>
|
|
115
|
+
<thead>
|
|
116
|
+
<tr>
|
|
117
|
+
{columns.map((col) => (
|
|
118
|
+
<th key={String(col.fieldId)}>{col.label}</th>
|
|
119
|
+
))}
|
|
120
|
+
</tr>
|
|
121
|
+
</thead>
|
|
122
|
+
<tbody>
|
|
123
|
+
{table.rows.map((row) => (
|
|
124
|
+
<tr key={row._id}>
|
|
125
|
+
<td>{row.Title}</td>
|
|
126
|
+
<td>${row.Price}</td>
|
|
127
|
+
<td>{row.Category}</td>
|
|
128
|
+
</tr>
|
|
129
|
+
))}
|
|
130
|
+
</tbody>
|
|
131
|
+
</table>
|
|
132
|
+
);
|
|
119
133
|
}
|
|
120
134
|
```
|
|
121
135
|
|
|
122
|
-
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Initial State
|
|
139
|
+
|
|
140
|
+
### Table with Initial Configuration
|
|
141
|
+
|
|
142
|
+
Set default pagination, sorting, and filters when the table loads.
|
|
123
143
|
|
|
124
144
|
```tsx
|
|
125
145
|
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
126
|
-
import {
|
|
146
|
+
import { useAuth } from "@ram_28/kf-ai-sdk/auth";
|
|
127
147
|
import type {
|
|
128
148
|
UseTableOptionsType,
|
|
129
149
|
UseTableReturnType,
|
|
130
150
|
ColumnDefinitionType,
|
|
131
151
|
} from "@ram_28/kf-ai-sdk/table/types";
|
|
132
|
-
import type {
|
|
133
|
-
ConditionType,
|
|
134
|
-
ConditionGroupType,
|
|
135
|
-
ConditionOperatorType,
|
|
136
|
-
ConditionGroupOperatorType,
|
|
137
|
-
FilterType,
|
|
138
|
-
UseFilterReturnType,
|
|
139
|
-
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
140
|
-
import type { ListResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
141
152
|
import { Product, ProductType } from "../sources";
|
|
142
153
|
import { Roles } from "../sources/roles";
|
|
143
154
|
|
|
144
|
-
// Get the typed product for the Buyer role
|
|
145
155
|
type BuyerProduct = ProductType<typeof Roles.Buyer>;
|
|
146
156
|
|
|
147
|
-
function
|
|
148
|
-
// Instantiate the Product source with role
|
|
157
|
+
function MyItemsTable() {
|
|
149
158
|
const product = new Product(Roles.Buyer);
|
|
159
|
+
const { user } = useAuth();
|
|
150
160
|
|
|
151
|
-
// Column definitions with type safety
|
|
152
161
|
const columns: ColumnDefinitionType<BuyerProduct>[] = [
|
|
153
162
|
{ fieldId: "Title", label: "Name", enableSorting: true },
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
label: "Price",
|
|
157
|
-
enableSorting: true,
|
|
158
|
-
transform: (value) => `$${value.toFixed(2)}`,
|
|
159
|
-
},
|
|
160
|
-
{ fieldId: "Category", label: "Category", enableSorting: true, enableFiltering: true },
|
|
163
|
+
{ fieldId: "Price", label: "Price", enableSorting: true },
|
|
164
|
+
{ fieldId: "Category", label: "Category", enableSorting: true },
|
|
161
165
|
{ fieldId: "Stock", label: "Stock", enableSorting: true },
|
|
162
166
|
];
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
source: product._id, // Use the Business Object ID from the Product class
|
|
168
|
+
const tableOptions: UseTableOptionsType<BuyerProduct> = {
|
|
169
|
+
source: product._id,
|
|
167
170
|
columns,
|
|
168
|
-
enableSorting: true,
|
|
169
|
-
enableFiltering: true,
|
|
170
|
-
enablePagination: true,
|
|
171
171
|
initialState: {
|
|
172
|
+
sort: [{ Title: "ASC" }],
|
|
172
173
|
pagination: { pageNo: 1, pageSize: 10 },
|
|
173
|
-
|
|
174
|
-
|
|
174
|
+
filter: {
|
|
175
|
+
conditions: [
|
|
176
|
+
{
|
|
177
|
+
Operator: "EQ",
|
|
178
|
+
LHSField: "_created_by",
|
|
179
|
+
RHSValue: { _id: user?._id, _name: user?._name },
|
|
180
|
+
RHSType: "Constant",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
operator: "And",
|
|
184
|
+
},
|
|
175
185
|
},
|
|
176
|
-
onError: (error: Error) => console.error("Table error:", error.message),
|
|
177
|
-
onSuccess: (data: BuyerProduct[]) => console.log("Loaded", data.length, "products"),
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const table: UseTableReturnType<BuyerProduct> = useTable<BuyerProduct>(options);
|
|
181
|
-
|
|
182
|
-
// Access filter functionality (UseFilterReturnType)
|
|
183
|
-
const filterState: UseFilterReturnType = table.filter;
|
|
184
|
-
|
|
185
|
-
// Add a filter with dynamic operator selection
|
|
186
|
-
const addFilter = (field: keyof BuyerProduct, operator: ConditionOperatorType, value: any) => {
|
|
187
|
-
table.filter.addCondition({
|
|
188
|
-
Operator: operator,
|
|
189
|
-
LHSField: field as string,
|
|
190
|
-
RHSValue: value,
|
|
191
|
-
});
|
|
192
186
|
};
|
|
193
187
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
table.filter.clearAllConditions();
|
|
197
|
-
addFilter("Category", "EQ", category);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// Add a complex nested filter (Price > 100 OR Stock < 10)
|
|
201
|
-
const addComplexFilter = () => {
|
|
202
|
-
const groupId = table.filter.addConditionGroup("Or");
|
|
203
|
-
table.filter.addCondition({ Operator: "GT", LHSField: "Price", RHSValue: 100 }, groupId);
|
|
204
|
-
table.filter.addCondition({ Operator: "LT", LHSField: "Stock", RHSValue: 10 }, groupId);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Display active filters using type guards
|
|
208
|
-
const renderActiveFilters = () => (
|
|
209
|
-
<div className="active-filters">
|
|
210
|
-
{table.filter.items.map((item) => {
|
|
211
|
-
if (isCondition(item)) {
|
|
212
|
-
return (
|
|
213
|
-
<span key={item.id} className="filter-tag">
|
|
214
|
-
{item.LHSField} {item.Operator} {String(item.RHSValue)}
|
|
215
|
-
<button onClick={() => table.filter.removeCondition(item.id!)}>×</button>
|
|
216
|
-
</span>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
if (isConditionGroup(item)) {
|
|
220
|
-
return (
|
|
221
|
-
<span key={item.id} className="filter-group-tag">
|
|
222
|
-
{item.Operator} Group ({item.Condition.length} conditions)
|
|
223
|
-
<button onClick={() => table.filter.removeCondition(item.id!)}>×</button>
|
|
224
|
-
</span>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
return null;
|
|
228
|
-
})}
|
|
229
|
-
</div>
|
|
230
|
-
);
|
|
188
|
+
const table: UseTableReturnType<BuyerProduct> =
|
|
189
|
+
useTable<BuyerProduct>(tableOptions);
|
|
231
190
|
|
|
232
|
-
|
|
233
|
-
const toggleFilterLogic = () => {
|
|
234
|
-
const next: ConditionGroupOperatorType = table.filter.operator === "And" ? "Or" : "And";
|
|
235
|
-
table.filter.setRootOperator(next);
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// Refetch data manually
|
|
239
|
-
const handleRefresh = async () => {
|
|
240
|
-
const response: ListResponseType<BuyerProduct> = await table.refetch();
|
|
241
|
-
console.log(`Refreshed: ${response.Data.length} items`);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Access filter payload for debugging
|
|
245
|
-
const getFilterPayload = (): FilterType | undefined => {
|
|
246
|
-
return table.filter.payload;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
if (table.isLoading) {
|
|
250
|
-
return <div>Loading products...</div>;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (table.error) {
|
|
254
|
-
return <div>Error: {table.error.message}</div>;
|
|
255
|
-
}
|
|
191
|
+
if (table.isLoading) return <div>Loading...</div>;
|
|
256
192
|
|
|
257
193
|
return (
|
|
258
|
-
<div
|
|
259
|
-
{/* Search */}
|
|
260
|
-
<div className="search-bar">
|
|
261
|
-
<input
|
|
262
|
-
type="text"
|
|
263
|
-
placeholder="Search products..."
|
|
264
|
-
value={table.search.query}
|
|
265
|
-
onChange={(e) => table.search.setQuery(e.target.value)}
|
|
266
|
-
/>
|
|
267
|
-
<button onClick={table.search.clear}>Clear</button>
|
|
268
|
-
</div>
|
|
269
|
-
|
|
270
|
-
{/* Filter Controls */}
|
|
271
|
-
<div className="filter-controls">
|
|
272
|
-
<button onClick={() => filterByCategory("Electronics")}>Electronics</button>
|
|
273
|
-
<button onClick={() => filterByCategory("Books")}>Books</button>
|
|
274
|
-
<button onClick={addComplexFilter}>Add Complex Filter</button>
|
|
275
|
-
<button onClick={toggleFilterLogic}>
|
|
276
|
-
Logic: {table.filter.operator}
|
|
277
|
-
</button>
|
|
278
|
-
{table.filter.hasConditions && (
|
|
279
|
-
<button onClick={() => table.filter.clearAllConditions()}>Clear All Filters</button>
|
|
280
|
-
)}
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
{/* Active Filters */}
|
|
284
|
-
{renderActiveFilters()}
|
|
285
|
-
|
|
286
|
-
{/* Table */}
|
|
194
|
+
<div>
|
|
287
195
|
<table>
|
|
288
196
|
<thead>
|
|
289
197
|
<tr>
|
|
290
198
|
{columns.map((col) => (
|
|
291
199
|
<th
|
|
292
200
|
key={String(col.fieldId)}
|
|
293
|
-
onClick={() =>
|
|
201
|
+
onClick={() =>
|
|
202
|
+
col.enableSorting && table.sort.toggle(col.fieldId)
|
|
203
|
+
}
|
|
294
204
|
style={{ cursor: col.enableSorting ? "pointer" : "default" }}
|
|
295
205
|
>
|
|
296
|
-
{col.label
|
|
206
|
+
{col.label}
|
|
297
207
|
{table.sort.field === col.fieldId && (
|
|
298
208
|
<span>{table.sort.direction === "asc" ? " ↑" : " ↓"}</span>
|
|
299
209
|
)}
|
|
@@ -302,10 +212,10 @@ function ProductsPage() {
|
|
|
302
212
|
</tr>
|
|
303
213
|
</thead>
|
|
304
214
|
<tbody>
|
|
305
|
-
{table.rows.map((row
|
|
215
|
+
{table.rows.map((row) => (
|
|
306
216
|
<tr key={row._id}>
|
|
307
217
|
<td>{row.Title}</td>
|
|
308
|
-
<td>${row.Price
|
|
218
|
+
<td>${row.Price}</td>
|
|
309
219
|
<td>{row.Category}</td>
|
|
310
220
|
<td>{row.Stock}</td>
|
|
311
221
|
</tr>
|
|
@@ -313,7 +223,6 @@ function ProductsPage() {
|
|
|
313
223
|
</tbody>
|
|
314
224
|
</table>
|
|
315
225
|
|
|
316
|
-
{/* Pagination */}
|
|
317
226
|
<div className="pagination">
|
|
318
227
|
<button
|
|
319
228
|
onClick={table.pagination.goToPrevious}
|
|
@@ -322,15 +231,371 @@ function ProductsPage() {
|
|
|
322
231
|
Previous
|
|
323
232
|
</button>
|
|
324
233
|
<span>
|
|
325
|
-
Page {table.pagination.
|
|
326
|
-
|
|
234
|
+
Page {table.pagination.pageNo} of {table.pagination.totalPages}
|
|
235
|
+
</span>
|
|
236
|
+
<button
|
|
237
|
+
onClick={table.pagination.goToNext}
|
|
238
|
+
disabled={!table.pagination.canGoNext}
|
|
239
|
+
>
|
|
240
|
+
Next
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Filter
|
|
251
|
+
|
|
252
|
+
### Status Filter
|
|
253
|
+
|
|
254
|
+
Dropdown-based status filtering.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
function ProductsWithStatusFilter() {
|
|
258
|
+
const product = new Product(Roles.Buyer);
|
|
259
|
+
|
|
260
|
+
const table = useTable<BuyerProduct>({
|
|
261
|
+
source: product._id,
|
|
262
|
+
columns,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
266
|
+
const status = e.target.value;
|
|
267
|
+
table.filter.clearAllConditions();
|
|
268
|
+
|
|
269
|
+
if (status !== "all") {
|
|
270
|
+
table.filter.addCondition({
|
|
271
|
+
Operator: "EQ",
|
|
272
|
+
LHSField: "IsActive",
|
|
273
|
+
RHSValue: status === "active",
|
|
274
|
+
RHSType: "Constant",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div>
|
|
281
|
+
<select onChange={handleStatusChange} defaultValue="all">
|
|
282
|
+
<option value="all">All Products</option>
|
|
283
|
+
<option value="active">Active</option>
|
|
284
|
+
<option value="inactive">Inactive</option>
|
|
285
|
+
</select>
|
|
286
|
+
{/* table rendering */}
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Multiple Filters Combined
|
|
293
|
+
|
|
294
|
+
Apply category and price range filters together.
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
const PRICE_RANGES = [
|
|
298
|
+
{ label: "Under $25", min: 0, max: 25 },
|
|
299
|
+
{ label: "$25 to $50", min: 25, max: 50 },
|
|
300
|
+
{ label: "$50 to $100", min: 50, max: 100 },
|
|
301
|
+
{ label: "$100 to $200", min: 100, max: 200 },
|
|
302
|
+
{ label: "$200 & Above", min: 200, max: null },
|
|
303
|
+
] as const;
|
|
304
|
+
|
|
305
|
+
function ProductsWithMultipleFilters() {
|
|
306
|
+
const product = new Product(Roles.Buyer);
|
|
307
|
+
const [selectedCategory, setSelectedCategory] = useState("all");
|
|
308
|
+
const [selectedPriceRange, setSelectedPriceRange] = useState<string | null>(
|
|
309
|
+
null,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const table = useTable<BuyerProduct>({
|
|
313
|
+
source: product._id,
|
|
314
|
+
columns,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Helper to apply all active filters
|
|
318
|
+
const applyFilters = (category: string, priceRange: string | null) => {
|
|
319
|
+
table.filter.clearAllConditions();
|
|
320
|
+
|
|
321
|
+
// Apply category filter
|
|
322
|
+
if (category !== "all") {
|
|
323
|
+
table.filter.addCondition({
|
|
324
|
+
LHSField: "Category",
|
|
325
|
+
Operator: "EQ",
|
|
326
|
+
RHSValue: category,
|
|
327
|
+
RHSType: "Constant",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Apply price filter
|
|
332
|
+
if (priceRange) {
|
|
333
|
+
const range = PRICE_RANGES.find((r) => r.label === priceRange);
|
|
334
|
+
if (range) {
|
|
335
|
+
if (range.max === null) {
|
|
336
|
+
// "$200 & Above" - use GTE
|
|
337
|
+
table.filter.addCondition({
|
|
338
|
+
Operator: "GTE",
|
|
339
|
+
LHSField: "Price",
|
|
340
|
+
RHSValue: range.min,
|
|
341
|
+
RHSType: "Constant",
|
|
342
|
+
});
|
|
343
|
+
} else if (range.min === 0) {
|
|
344
|
+
// "Under $25" - use LT
|
|
345
|
+
table.filter.addCondition({
|
|
346
|
+
Operator: "LT",
|
|
347
|
+
LHSField: "Price",
|
|
348
|
+
RHSValue: range.max,
|
|
349
|
+
RHSType: "Constant",
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
// Range like "$25 to $50" - use Between
|
|
353
|
+
table.filter.addCondition({
|
|
354
|
+
LHSField: "Price",
|
|
355
|
+
Operator: "Between",
|
|
356
|
+
RHSValue: [range.min, range.max],
|
|
357
|
+
RHSType: "Constant",
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const handleCategoryChange = (category: string) => {
|
|
365
|
+
setSelectedCategory(category);
|
|
366
|
+
applyFilters(category, selectedPriceRange);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const handlePriceChange = (priceRange: string | null) => {
|
|
370
|
+
setSelectedPriceRange(priceRange);
|
|
371
|
+
applyFilters(selectedCategory, priceRange);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<div>
|
|
376
|
+
<select
|
|
377
|
+
value={selectedCategory}
|
|
378
|
+
onChange={(e) => handleCategoryChange(e.target.value)}
|
|
379
|
+
>
|
|
380
|
+
<option value="all">All Categories</option>
|
|
381
|
+
<option value="Electronics">Electronics</option>
|
|
382
|
+
<option value="Books">Books</option>
|
|
383
|
+
</select>
|
|
384
|
+
|
|
385
|
+
<select
|
|
386
|
+
value={selectedPriceRange || ""}
|
|
387
|
+
onChange={(e) => handlePriceChange(e.target.value || null)}
|
|
388
|
+
>
|
|
389
|
+
<option value="">Any Price</option>
|
|
390
|
+
{PRICE_RANGES.map((range) => (
|
|
391
|
+
<option key={range.label} value={range.label}>
|
|
392
|
+
{range.label}
|
|
393
|
+
</option>
|
|
394
|
+
))}
|
|
395
|
+
</select>
|
|
396
|
+
|
|
397
|
+
{/* table rendering */}
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Sort
|
|
406
|
+
|
|
407
|
+
### Column Sort Toggle
|
|
408
|
+
|
|
409
|
+
Click column headers to toggle sort direction.
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
function SortableTable() {
|
|
413
|
+
const product = new Product(Roles.Buyer);
|
|
414
|
+
|
|
415
|
+
const columns: ColumnDefinitionType<BuyerProduct>[] = [
|
|
416
|
+
{ fieldId: "Title", label: "Name", enableSorting: true },
|
|
417
|
+
{ fieldId: "Price", label: "Price", enableSorting: true },
|
|
418
|
+
{ fieldId: "Category", label: "Category", enableSorting: true },
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
const table = useTable<BuyerProduct>({
|
|
422
|
+
source: product._id,
|
|
423
|
+
columns,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<table>
|
|
428
|
+
<thead>
|
|
429
|
+
<tr>
|
|
430
|
+
{columns.map((col) => (
|
|
431
|
+
<th
|
|
432
|
+
key={String(col.fieldId)}
|
|
433
|
+
onClick={() =>
|
|
434
|
+
col.enableSorting && table.sort.toggle(col.fieldId)
|
|
435
|
+
}
|
|
436
|
+
style={{ cursor: col.enableSorting ? "pointer" : "default" }}
|
|
437
|
+
>
|
|
438
|
+
{col.label}
|
|
439
|
+
{table.sort.field === col.fieldId && (
|
|
440
|
+
<span>{table.sort.direction === "asc" ? " ↑" : " ↓"}</span>
|
|
441
|
+
)}
|
|
442
|
+
</th>
|
|
443
|
+
))}
|
|
444
|
+
</tr>
|
|
445
|
+
</thead>
|
|
446
|
+
<tbody>
|
|
447
|
+
{table.rows.map((row) => (
|
|
448
|
+
<tr key={row._id}>
|
|
449
|
+
<td>{row.Title}</td>
|
|
450
|
+
<td>${row.Price}</td>
|
|
451
|
+
<td>{row.Category}</td>
|
|
452
|
+
</tr>
|
|
453
|
+
))}
|
|
454
|
+
</tbody>
|
|
455
|
+
</table>
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Sort Dropdown
|
|
461
|
+
|
|
462
|
+
Allow users to select sort order from a dropdown.
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
function TableWithSortDropdown() {
|
|
466
|
+
const product = new Product(Roles.Buyer);
|
|
467
|
+
const [selectedSort, setSelectedSort] = useState("featured");
|
|
468
|
+
|
|
469
|
+
const table = useTable<BuyerProduct>({
|
|
470
|
+
source: product._id,
|
|
471
|
+
columns,
|
|
472
|
+
initialState: {
|
|
473
|
+
sort: [{ Title: "ASC" }],
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const handleSortChange = (value: string) => {
|
|
478
|
+
setSelectedSort(value);
|
|
479
|
+
switch (value) {
|
|
480
|
+
case "price-asc":
|
|
481
|
+
table.sort.set("Price", "asc");
|
|
482
|
+
break;
|
|
483
|
+
case "price-desc":
|
|
484
|
+
table.sort.set("Price", "desc");
|
|
485
|
+
break;
|
|
486
|
+
case "newest":
|
|
487
|
+
table.sort.set("_created_at", "desc");
|
|
488
|
+
break;
|
|
489
|
+
case "featured":
|
|
490
|
+
default:
|
|
491
|
+
table.sort.set("Title", "asc");
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<div>
|
|
498
|
+
<select
|
|
499
|
+
value={selectedSort}
|
|
500
|
+
onChange={(e) => handleSortChange(e.target.value)}
|
|
501
|
+
>
|
|
502
|
+
<option value="featured">Featured</option>
|
|
503
|
+
<option value="price-asc">Price: Low to High</option>
|
|
504
|
+
<option value="price-desc">Price: High to Low</option>
|
|
505
|
+
<option value="newest">Newest Arrivals</option>
|
|
506
|
+
</select>
|
|
507
|
+
{/* table rendering */}
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Pagination
|
|
516
|
+
|
|
517
|
+
### Basic Pagination Controls
|
|
518
|
+
|
|
519
|
+
Navigate between pages with previous/next buttons.
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
function PaginatedTable() {
|
|
523
|
+
const product = new Product(Roles.Buyer);
|
|
524
|
+
|
|
525
|
+
const table = useTable<BuyerProduct>({
|
|
526
|
+
source: product._id,
|
|
527
|
+
columns,
|
|
528
|
+
initialState: {
|
|
529
|
+
pagination: { pageNo: 1, pageSize: 10 },
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
return (
|
|
534
|
+
<div>
|
|
535
|
+
{/* table rendering */}
|
|
536
|
+
|
|
537
|
+
<div className="pagination">
|
|
538
|
+
<button
|
|
539
|
+
onClick={table.pagination.goToPrevious}
|
|
540
|
+
disabled={!table.pagination.canGoPrevious}
|
|
541
|
+
>
|
|
542
|
+
Previous
|
|
543
|
+
</button>
|
|
544
|
+
|
|
545
|
+
<span>
|
|
546
|
+
Page {table.pagination.pageNo} of {table.pagination.totalPages}
|
|
547
|
+
</span>
|
|
548
|
+
|
|
549
|
+
<button
|
|
550
|
+
onClick={table.pagination.goToNext}
|
|
551
|
+
disabled={!table.pagination.canGoNext}
|
|
552
|
+
>
|
|
553
|
+
Next
|
|
554
|
+
</button>
|
|
555
|
+
</div>
|
|
556
|
+
|
|
557
|
+
<p>{table.pagination.totalItems} total items</p>
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Page Size Selector
|
|
564
|
+
|
|
565
|
+
Allow users to change the number of items per page.
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
function TableWithPageSize() {
|
|
569
|
+
const product = new Product(Roles.Buyer);
|
|
570
|
+
|
|
571
|
+
const table = useTable<BuyerProduct>({
|
|
572
|
+
source: product._id,
|
|
573
|
+
columns,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
return (
|
|
577
|
+
<div>
|
|
578
|
+
{/* table rendering */}
|
|
579
|
+
|
|
580
|
+
<div className="pagination">
|
|
581
|
+
<button
|
|
582
|
+
onClick={table.pagination.goToPrevious}
|
|
583
|
+
disabled={!table.pagination.canGoPrevious}
|
|
584
|
+
>
|
|
585
|
+
Previous
|
|
586
|
+
</button>
|
|
587
|
+
|
|
588
|
+
<span>
|
|
589
|
+
Page {table.pagination.pageNo} of {table.pagination.totalPages}
|
|
327
590
|
</span>
|
|
591
|
+
|
|
328
592
|
<button
|
|
329
593
|
onClick={table.pagination.goToNext}
|
|
330
594
|
disabled={!table.pagination.canGoNext}
|
|
331
595
|
>
|
|
332
596
|
Next
|
|
333
597
|
</button>
|
|
598
|
+
|
|
334
599
|
<select
|
|
335
600
|
value={table.pagination.pageSize}
|
|
336
601
|
onChange={(e) => table.pagination.setPageSize(Number(e.target.value))}
|
|
@@ -338,34 +603,280 @@ function ProductsPage() {
|
|
|
338
603
|
<option value={10}>10 per page</option>
|
|
339
604
|
<option value={25}>25 per page</option>
|
|
340
605
|
<option value={50}>50 per page</option>
|
|
606
|
+
<option value={100}>100 per page</option>
|
|
341
607
|
</select>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Jump to Page
|
|
615
|
+
|
|
616
|
+
Allow users to navigate directly to a specific page.
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
function TableWithPageJump() {
|
|
620
|
+
const product = new Product(Roles.Buyer);
|
|
621
|
+
const [pageInput, setPageInput] = useState("");
|
|
622
|
+
|
|
623
|
+
const table = useTable<BuyerProduct>({
|
|
624
|
+
source: product._id,
|
|
625
|
+
columns,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const handlePageJump = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
629
|
+
if (e.key === "Enter") {
|
|
630
|
+
const page = parseInt(pageInput, 10);
|
|
631
|
+
if (page >= 1 && page <= table.pagination.totalPages) {
|
|
632
|
+
table.pagination.goToPage(page);
|
|
633
|
+
setPageInput("");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<div>
|
|
640
|
+
{/* table rendering */}
|
|
641
|
+
|
|
642
|
+
<div className="pagination">
|
|
643
|
+
<button
|
|
644
|
+
onClick={table.pagination.goToPrevious}
|
|
645
|
+
disabled={!table.pagination.canGoPrevious}
|
|
646
|
+
>
|
|
647
|
+
Previous
|
|
648
|
+
</button>
|
|
649
|
+
|
|
650
|
+
<span>
|
|
651
|
+
Page {table.pagination.pageNo} of {table.pagination.totalPages}
|
|
652
|
+
</span>
|
|
653
|
+
|
|
654
|
+
<button
|
|
655
|
+
onClick={table.pagination.goToNext}
|
|
656
|
+
disabled={!table.pagination.canGoNext}
|
|
657
|
+
>
|
|
658
|
+
Next
|
|
659
|
+
</button>
|
|
660
|
+
|
|
342
661
|
<input
|
|
343
662
|
type="number"
|
|
344
663
|
min={1}
|
|
345
664
|
max={table.pagination.totalPages}
|
|
346
665
|
placeholder="Go to page"
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
}}
|
|
666
|
+
value={pageInput}
|
|
667
|
+
onChange={(e) => setPageInput(e.target.value)}
|
|
668
|
+
onKeyDown={handlePageJump}
|
|
352
669
|
/>
|
|
353
670
|
</div>
|
|
671
|
+
</div>
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
```
|
|
354
675
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Search
|
|
679
|
+
|
|
680
|
+
### Basic Search
|
|
681
|
+
|
|
682
|
+
Add search functionality to filter results by text. The search has built-in debouncing (300ms).
|
|
683
|
+
|
|
684
|
+
```tsx
|
|
685
|
+
function SearchableTable() {
|
|
686
|
+
const product = new Product(Roles.Buyer);
|
|
687
|
+
|
|
688
|
+
const table = useTable<BuyerProduct>({
|
|
689
|
+
source: product._id,
|
|
690
|
+
columns,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
return (
|
|
694
|
+
<div>
|
|
695
|
+
<div className="search-bar">
|
|
696
|
+
<input
|
|
697
|
+
type="text"
|
|
698
|
+
placeholder="Search products..."
|
|
699
|
+
value={table.search.query}
|
|
700
|
+
onChange={(e) => table.search.setQuery(e.target.value)}
|
|
701
|
+
/>
|
|
702
|
+
{table.search.query && (
|
|
703
|
+
<button onClick={table.search.clear}>Clear</button>
|
|
704
|
+
)}
|
|
705
|
+
{table.isFetching && <span>Searching...</span>}
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
{/* table rendering */}
|
|
709
|
+
</div>
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## Complete Example
|
|
717
|
+
|
|
718
|
+
A full-featured product listing page with filters, search, sort, and pagination.
|
|
719
|
+
|
|
720
|
+
```tsx
|
|
721
|
+
import { useState } from "react";
|
|
722
|
+
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
723
|
+
import type {
|
|
724
|
+
UseTableOptionsType,
|
|
725
|
+
UseTableReturnType,
|
|
726
|
+
ColumnDefinitionType,
|
|
727
|
+
} from "@ram_28/kf-ai-sdk/table/types";
|
|
728
|
+
import { Product, ProductType } from "../sources";
|
|
729
|
+
import { Roles } from "../sources/roles";
|
|
730
|
+
|
|
731
|
+
type BuyerProduct = ProductType<typeof Roles.Buyer>;
|
|
732
|
+
|
|
733
|
+
function ProductListPage() {
|
|
734
|
+
const product = new Product(Roles.Buyer);
|
|
735
|
+
const [selectedCategory, setSelectedCategory] = useState("all");
|
|
736
|
+
const [selectedSort, setSelectedSort] = useState("featured");
|
|
737
|
+
|
|
738
|
+
const columns: ColumnDefinitionType<BuyerProduct>[] = [
|
|
739
|
+
{ fieldId: "Title", label: "Name", enableSorting: true },
|
|
740
|
+
{ fieldId: "Price", label: "Price", enableSorting: true },
|
|
741
|
+
{ fieldId: "Category", label: "Category", enableSorting: true },
|
|
742
|
+
{ fieldId: "Stock", label: "Stock", enableSorting: true },
|
|
743
|
+
];
|
|
744
|
+
|
|
745
|
+
const tableOptions: UseTableOptionsType<BuyerProduct> = {
|
|
746
|
+
source: product._id,
|
|
747
|
+
columns,
|
|
748
|
+
initialState: {
|
|
749
|
+
sort: [{ Title: "ASC" }],
|
|
750
|
+
pagination: { pageNo: 1, pageSize: 10 },
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const table: UseTableReturnType<BuyerProduct> =
|
|
755
|
+
useTable<BuyerProduct>(tableOptions);
|
|
756
|
+
|
|
757
|
+
const handleCategoryChange = (category: string) => {
|
|
758
|
+
setSelectedCategory(category);
|
|
759
|
+
table.filter.clearAllConditions();
|
|
760
|
+
if (category !== "all") {
|
|
761
|
+
table.filter.addCondition({
|
|
762
|
+
LHSField: "Category",
|
|
763
|
+
Operator: "EQ",
|
|
764
|
+
RHSValue: category,
|
|
765
|
+
RHSType: "Constant",
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const handleSortChange = (value: string) => {
|
|
771
|
+
setSelectedSort(value);
|
|
772
|
+
switch (value) {
|
|
773
|
+
case "price-asc":
|
|
774
|
+
table.sort.set("Price", "asc");
|
|
775
|
+
break;
|
|
776
|
+
case "price-desc":
|
|
777
|
+
table.sort.set("Price", "desc");
|
|
778
|
+
break;
|
|
779
|
+
case "newest":
|
|
780
|
+
table.sort.set("_created_at", "desc");
|
|
781
|
+
break;
|
|
782
|
+
default:
|
|
783
|
+
table.sort.set("Title", "asc");
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
if (table.error) {
|
|
789
|
+
return (
|
|
790
|
+
<div>
|
|
791
|
+
<p>Error: {table.error.message}</p>
|
|
792
|
+
<button onClick={() => table.refetch()}>Try Again</button>
|
|
793
|
+
</div>
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (table.isLoading) {
|
|
798
|
+
return <div>Loading...</div>;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return (
|
|
802
|
+
<div>
|
|
803
|
+
{/* Controls */}
|
|
804
|
+
<div className="controls">
|
|
805
|
+
<input
|
|
806
|
+
type="text"
|
|
807
|
+
placeholder="Search..."
|
|
808
|
+
value={table.search.query}
|
|
809
|
+
onChange={(e) => table.search.setQuery(e.target.value)}
|
|
810
|
+
/>
|
|
811
|
+
|
|
812
|
+
<select
|
|
813
|
+
value={selectedCategory}
|
|
814
|
+
onChange={(e) => handleCategoryChange(e.target.value)}
|
|
815
|
+
>
|
|
816
|
+
<option value="all">All Categories</option>
|
|
817
|
+
<option value="Electronics">Electronics</option>
|
|
818
|
+
<option value="Books">Books</option>
|
|
819
|
+
</select>
|
|
820
|
+
|
|
821
|
+
<select
|
|
822
|
+
value={selectedSort}
|
|
823
|
+
onChange={(e) => handleSortChange(e.target.value)}
|
|
824
|
+
>
|
|
825
|
+
<option value="featured">Featured</option>
|
|
826
|
+
<option value="price-asc">Price: Low to High</option>
|
|
827
|
+
<option value="price-desc">Price: High to Low</option>
|
|
828
|
+
<option value="newest">Newest</option>
|
|
829
|
+
</select>
|
|
362
830
|
</div>
|
|
363
831
|
|
|
364
|
-
{/*
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
832
|
+
{/* Results count */}
|
|
833
|
+
<p>{table.totalItems} results found</p>
|
|
834
|
+
|
|
835
|
+
{/* Product grid */}
|
|
836
|
+
{table.rows.length === 0 ? (
|
|
837
|
+
<div>
|
|
838
|
+
<p>No products found</p>
|
|
839
|
+
<button
|
|
840
|
+
onClick={() => {
|
|
841
|
+
table.search.clear();
|
|
842
|
+
table.filter.clearAllConditions();
|
|
843
|
+
setSelectedCategory("all");
|
|
844
|
+
}}
|
|
845
|
+
>
|
|
846
|
+
Clear Filters
|
|
847
|
+
</button>
|
|
848
|
+
</div>
|
|
849
|
+
) : (
|
|
850
|
+
<div className="product-grid">
|
|
851
|
+
{table.rows.map((row) => (
|
|
852
|
+
<div key={row._id} className="product-card">
|
|
853
|
+
<h3>{row.Title}</h3>
|
|
854
|
+
<p>${row.Price}</p>
|
|
855
|
+
<p>{row.Category}</p>
|
|
856
|
+
<p>{row.Stock > 0 ? "In Stock" : "Out of Stock"}</p>
|
|
857
|
+
</div>
|
|
858
|
+
))}
|
|
859
|
+
</div>
|
|
860
|
+
)}
|
|
861
|
+
|
|
862
|
+
{/* Pagination */}
|
|
863
|
+
<div className="pagination">
|
|
864
|
+
<button
|
|
865
|
+
onClick={table.pagination.goToPrevious}
|
|
866
|
+
disabled={!table.pagination.canGoPrevious}
|
|
867
|
+
>
|
|
868
|
+
Previous
|
|
869
|
+
</button>
|
|
870
|
+
<span>
|
|
871
|
+
Page {table.pagination.pageNo} of {table.pagination.totalPages}
|
|
872
|
+
</span>
|
|
873
|
+
<button
|
|
874
|
+
onClick={table.pagination.goToNext}
|
|
875
|
+
disabled={!table.pagination.canGoNext}
|
|
876
|
+
>
|
|
877
|
+
Next
|
|
878
|
+
</button>
|
|
879
|
+
</div>
|
|
369
880
|
</div>
|
|
370
881
|
);
|
|
371
882
|
}
|