kmod-cli 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ListProps<T> {
|
|
4
|
+
items: T[];
|
|
5
|
+
keyExtractor: (item: T) => React.Key;
|
|
6
|
+
renderItem: (item: T, index: number) => React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function List<T>({ items, keyExtractor, renderItem }: ListProps<T>) {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
{items.map((item, index) => (
|
|
13
|
+
<ListItem
|
|
14
|
+
key={keyExtractor(item)}
|
|
15
|
+
item={item}
|
|
16
|
+
index={index}
|
|
17
|
+
renderItem={renderItem}
|
|
18
|
+
/>
|
|
19
|
+
))}
|
|
20
|
+
</>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ListItemProps<T> {
|
|
25
|
+
item: T;
|
|
26
|
+
index: number;
|
|
27
|
+
renderItem: (item: T, index: number) => React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Generic memo component
|
|
31
|
+
function _ListItem<T>({ item, index, renderItem }: ListItemProps<T>) {
|
|
32
|
+
return <>{renderItem(item, index)}</>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ListItem = React.memo(_ListItem) as <T>(
|
|
36
|
+
props: ListItemProps<T>
|
|
37
|
+
) => React.ReactNode;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// Example usage of the List component
|
|
41
|
+
// interface User {
|
|
42
|
+
// id: number;
|
|
43
|
+
// name: string;
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
// const users: User[] = [
|
|
47
|
+
// { id: 1, name: 'Alice' },
|
|
48
|
+
// { id: 2, name: 'Bob' },
|
|
49
|
+
// { id: 3, name: 'Charlie' },
|
|
50
|
+
// ];
|
|
51
|
+
|
|
52
|
+
// export function UserList() {
|
|
53
|
+
// return (
|
|
54
|
+
// <List
|
|
55
|
+
// items={users}
|
|
56
|
+
// keyExtractor={(user) => user.id}
|
|
57
|
+
// renderItem={(user) => <div>{user.name}</div>}
|
|
58
|
+
// />
|
|
59
|
+
// );
|
|
60
|
+
// }
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
|
|
11
|
-
// Nếu bạn cần alias cho ITable type, dùng:
|
|
12
11
|
import type { Table as ITable } from '@tanstack/react-table';
|
|
13
12
|
import {
|
|
14
13
|
Cell,
|
|
@@ -55,7 +54,7 @@ export type TableClassNames = {
|
|
|
55
54
|
};
|
|
56
55
|
export type TableHeaderProps<TData> =
|
|
57
56
|
HTMLAttributes<HTMLTableSectionElement> & {
|
|
58
|
-
handleClick
|
|
57
|
+
handleClick?: ({
|
|
59
58
|
e,
|
|
60
59
|
table,
|
|
61
60
|
}: {
|
|
@@ -64,7 +63,7 @@ export type TableHeaderProps<TData> =
|
|
|
64
63
|
}) => void;
|
|
65
64
|
};
|
|
66
65
|
export type TableBodyProps<TData> = HTMLAttributes<HTMLTableSectionElement> & {
|
|
67
|
-
handleClick
|
|
66
|
+
handleClick?: ({
|
|
68
67
|
e,
|
|
69
68
|
table,
|
|
70
69
|
}: {
|
|
@@ -73,7 +72,8 @@ export type TableBodyProps<TData> = HTMLAttributes<HTMLTableSectionElement> & {
|
|
|
73
72
|
}) => void;
|
|
74
73
|
};
|
|
75
74
|
export type TableHeadProps<TData> = HTMLAttributes<HTMLTableCellElement> & {
|
|
76
|
-
|
|
75
|
+
classNameCondition?: | (({cell, table}: {cell?: Header<TData, unknown>; table?: ITable<TData>}) => string) | string;
|
|
76
|
+
handleClick?: ({
|
|
77
77
|
e,
|
|
78
78
|
table,
|
|
79
79
|
cell,
|
|
@@ -85,7 +85,8 @@ export type TableHeadProps<TData> = HTMLAttributes<HTMLTableCellElement> & {
|
|
|
85
85
|
};
|
|
86
86
|
export type TableCellProps<TData, TValue> =
|
|
87
87
|
HTMLAttributes<HTMLTableCellElement> & {
|
|
88
|
-
|
|
88
|
+
classNameCondition?: | (({cell, table}: {cell?: Cell<TData, unknown>; table?: ITable<TData>}) => string) | string;
|
|
89
|
+
handleClick?: ({
|
|
89
90
|
e,
|
|
90
91
|
table,
|
|
91
92
|
cell,
|
|
@@ -96,7 +97,7 @@ export type TableCellProps<TData, TValue> =
|
|
|
96
97
|
}) => void;
|
|
97
98
|
};
|
|
98
99
|
export type TableRowHeadProps<TData> = HTMLAttributes<HTMLTableRowElement> & {
|
|
99
|
-
handleClick
|
|
100
|
+
handleClick?: ({
|
|
100
101
|
e,
|
|
101
102
|
table,
|
|
102
103
|
row,
|
|
@@ -107,7 +108,8 @@ export type TableRowHeadProps<TData> = HTMLAttributes<HTMLTableRowElement> & {
|
|
|
107
108
|
}) => void;
|
|
108
109
|
};
|
|
109
110
|
export type TableRowBodyProps<TData> = HTMLAttributes<HTMLTableRowElement> & {
|
|
110
|
-
|
|
111
|
+
classNameCondition?: | (({row, table}: {row?: Row<TData>; table?: ITable<TData>}) => string) | string;
|
|
112
|
+
handleClick?: ({
|
|
111
113
|
e,
|
|
112
114
|
table,
|
|
113
115
|
row,
|
|
@@ -118,7 +120,7 @@ export type TableRowBodyProps<TData> = HTMLAttributes<HTMLTableRowElement> & {
|
|
|
118
120
|
}) => void;
|
|
119
121
|
};
|
|
120
122
|
export type TableProps<TData> = HTMLAttributes<HTMLTableElement> & {
|
|
121
|
-
handleClick
|
|
123
|
+
handleClick?: ({
|
|
122
124
|
e,
|
|
123
125
|
table,
|
|
124
126
|
}: {
|
|
@@ -173,6 +175,15 @@ export type DataTableProps<TData, TValue> = {
|
|
|
173
175
|
// handles?: Handles
|
|
174
176
|
};
|
|
175
177
|
|
|
178
|
+
export interface UseTablePropsFn<TData, TValue> {
|
|
179
|
+
table: ITable<TData>;
|
|
180
|
+
row?: Row<TData>;
|
|
181
|
+
cell?: Cell<TData, TValue>;
|
|
182
|
+
header?: Header<TData, TValue>;
|
|
183
|
+
headerGroup?: HeaderGroup<TData>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
176
187
|
export type DataTableToolbarFns<TData> = {
|
|
177
188
|
globalFilter: string;
|
|
178
189
|
setGlobalFilter: (value: string) => void;
|
|
@@ -204,7 +215,7 @@ export function DataTable<TData, TValue>({
|
|
|
204
215
|
useTableProps,
|
|
205
216
|
initialState,
|
|
206
217
|
alternate = "even",
|
|
207
|
-
alternateColor = "#
|
|
218
|
+
alternateColor = "#fbfbfb",
|
|
208
219
|
// handles
|
|
209
220
|
}: DataTableProps<TData, TValue>) {
|
|
210
221
|
const table = useReactTable({
|
|
@@ -241,47 +252,74 @@ export function DataTable<TData, TValue>({
|
|
|
241
252
|
};
|
|
242
253
|
|
|
243
254
|
const {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
} = useTableProps?.tableProps || {};
|
|
255
|
+
handleClick: tableHandleClick,
|
|
256
|
+
onClick: tableOnClick,
|
|
257
|
+
...tableDomProps
|
|
258
|
+
} = useTableProps?.tableProps || {};
|
|
248
259
|
|
|
249
|
-
const {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} = useTableProps?.headerProps || {};
|
|
254
|
-
const {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
} = useTableProps?.rowHeadProps || {};
|
|
259
|
-
const {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
} = useTableProps?.
|
|
270
|
-
const {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
260
|
+
const {
|
|
261
|
+
handleClick: headerHandleClick,
|
|
262
|
+
onClick: headerOnClick,
|
|
263
|
+
...headerDomProps
|
|
264
|
+
} = useTableProps?.headerProps || {};
|
|
265
|
+
const {
|
|
266
|
+
handleClick: rowHeadHandleClick,
|
|
267
|
+
onClick: rowHeadOnClick,
|
|
268
|
+
...rowHeadDomProps
|
|
269
|
+
} = useTableProps?.rowHeadProps || {};
|
|
270
|
+
const {
|
|
271
|
+
handleClick: cellHeadHandleClick,
|
|
272
|
+
onClick: cellHeadOnClick,
|
|
273
|
+
classNameCondition: cellHeadClassNameCondition,
|
|
274
|
+
...cellHeadDomProps
|
|
275
|
+
} = useTableProps?.cellHeadProps || {};
|
|
276
|
+
const {
|
|
277
|
+
handleClick: bodyHandleClick,
|
|
278
|
+
onClick: bodyOnClick,
|
|
279
|
+
...bodyDomProps
|
|
280
|
+
} = useTableProps?.bodyProps || {};
|
|
281
|
+
const {
|
|
282
|
+
handleClick: rowBodyHandleClick,
|
|
283
|
+
onClick: rowBodyOnClick,
|
|
284
|
+
style: rowBodyStyle,
|
|
285
|
+
classNameCondition: rowBodyClassNameCondition,
|
|
286
|
+
...rowBodyDomProps
|
|
287
|
+
} = useTableProps?.rowBodyProps || {};
|
|
288
|
+
const {
|
|
289
|
+
handleClick: cellBodyHandleClick,
|
|
290
|
+
onClick: cellBodyOnClick,
|
|
291
|
+
classNameCondition: cellBodyClassNameCondition,
|
|
292
|
+
...cellBodyDomProps
|
|
293
|
+
} = useTableProps?.cellBodyProps || {};
|
|
275
294
|
|
|
276
|
-
const {
|
|
277
|
-
|
|
278
|
-
...skRowDomProps
|
|
279
|
-
} = useTableProps?.rowBodyProps || {};
|
|
295
|
+
const { handleClick: skRowHandleClick, classNameCondition: skRowClassNameCondition, ...skRowDomProps } =
|
|
296
|
+
useTableProps?.rowBodyProps || {};
|
|
280
297
|
|
|
281
|
-
const {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
298
|
+
const { handleClick: skCellHandleClick, classNameCondition: skCellClassNameCondition, ...skCellDomProps } =
|
|
299
|
+
useTableProps?.cellBodyProps || {};
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
function getCellHeadClassNameByCondition({cell,table}: {cell?: Header<TData, unknown>; table?: ITable<TData>}) {
|
|
303
|
+
if(!cell || !table) return "";
|
|
304
|
+
const classNameCondition = useTableProps?.cellHeadProps?.classNameCondition;
|
|
305
|
+
if(!classNameCondition) return "";
|
|
306
|
+
if(typeof classNameCondition === "string") return classNameCondition;
|
|
307
|
+
return classNameCondition({cell,table});
|
|
308
|
+
}
|
|
309
|
+
function getCellBodyClassNameByCondition({cell,table}: {cell?: Cell<TData, unknown>; table?: ITable<TData>}) {
|
|
310
|
+
if(!cell || !table) return "";
|
|
311
|
+
const classNameCondition = useTableProps?.cellBodyProps?.classNameCondition;
|
|
312
|
+
if(!classNameCondition) return "";
|
|
313
|
+
if(typeof classNameCondition === "string") return classNameCondition;
|
|
314
|
+
return classNameCondition({cell,table});
|
|
315
|
+
}
|
|
316
|
+
function getRowBodyClassNameByCondition({row,table}: {row?: Row<TData>; table?: ITable<TData>}) {
|
|
317
|
+
if(!row || !table) return "";
|
|
318
|
+
const classNameCondition = useTableProps?.rowBodyProps?.classNameCondition;
|
|
319
|
+
if(!classNameCondition) return "";
|
|
320
|
+
if(typeof classNameCondition === "string") return classNameCondition;
|
|
321
|
+
return classNameCondition({row,table});
|
|
322
|
+
}
|
|
285
323
|
|
|
286
324
|
|
|
287
325
|
return (
|
|
@@ -321,11 +359,7 @@ const {
|
|
|
321
359
|
{headerGroup.headers.map((header) => (
|
|
322
360
|
<TableHead
|
|
323
361
|
key={header.id}
|
|
324
|
-
{...
|
|
325
|
-
const { handleClick, onClick, ...rest } =
|
|
326
|
-
useTableProps?.cellHeadProps || {};
|
|
327
|
-
return rest;
|
|
328
|
-
})()}
|
|
362
|
+
{...cellHeadDomProps}
|
|
329
363
|
className={cn(
|
|
330
364
|
"cursor-pointer select-none",
|
|
331
365
|
classNames?.header?.head
|
|
@@ -334,22 +368,11 @@ const {
|
|
|
334
368
|
width: header.getSize() ? `${header.getSize()}px !important` : "auto",
|
|
335
369
|
}}
|
|
336
370
|
onClick={(e) => {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
useTableProps.cellHeadProps.onClick(e);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Just call the parent's handleClick if provided
|
|
343
|
-
if (useTableProps?.cellHeadProps?.handleClick) {
|
|
344
|
-
useTableProps.cellHeadProps.handleClick({
|
|
345
|
-
e,
|
|
346
|
-
cell: header,
|
|
347
|
-
table,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
371
|
+
cellHeadOnClick?.(e);
|
|
372
|
+
cellHeadHandleClick?.({ e, table, cell: header });
|
|
350
373
|
}}
|
|
351
374
|
>
|
|
352
|
-
<div className={cn("flex items-center gap-1 w-fit", classNames?.header?.content)}>
|
|
375
|
+
<div className={cn("flex items-center gap-1 w-fit", classNames?.header?.content, getCellHeadClassNameByCondition({cell: header, table}))}>
|
|
353
376
|
{flexRender(
|
|
354
377
|
header.column.columnDef.header,
|
|
355
378
|
header.getContext()
|
|
@@ -384,15 +407,13 @@ const {
|
|
|
384
407
|
{!isLoading &&
|
|
385
408
|
table.getRowModel().rows.length > 0 &&
|
|
386
409
|
table.getRowModel().rows.map((row, index) => {
|
|
387
|
-
const { handleClick, onClick, ...rest } =
|
|
388
|
-
useTableProps?.rowBodyProps || {};
|
|
389
410
|
|
|
390
411
|
return (
|
|
391
412
|
<TableRow
|
|
392
413
|
{...rowBodyDomProps}
|
|
393
414
|
key={row.id}
|
|
394
415
|
style={{...rowBodyStyle, backgroundColor: getAlternateColor(index)}}
|
|
395
|
-
className={cn(classNames?.body?.row)}
|
|
416
|
+
className={cn(classNames?.body?.row, getRowBodyClassNameByCondition({row,table}))}
|
|
396
417
|
data-state={row.getIsSelected() && "selected"}
|
|
397
418
|
onClick={(e) => {
|
|
398
419
|
rowBodyOnClick?.(e);
|
|
@@ -403,7 +424,7 @@ const {
|
|
|
403
424
|
<TableCell
|
|
404
425
|
{...cellBodyDomProps}
|
|
405
426
|
key={cell.id}
|
|
406
|
-
className={cn(classNames?.body?.cell)}
|
|
427
|
+
className={cn(classNames?.body?.cell, getCellBodyClassNameByCondition({cell, table}))}
|
|
407
428
|
onClick={(e) => {
|
|
408
429
|
cellBodyOnClick?.(e);
|
|
409
430
|
cellBodyHandleClick?.({ e, cell, table });
|
|
@@ -480,26 +501,27 @@ export const TableSkeleton = <TData, TValue>({
|
|
|
480
501
|
const {
|
|
481
502
|
handleClick: _rowHandleClick,
|
|
482
503
|
onClick: _rowOnClick,
|
|
504
|
+
classNameCondition: rowClassNameCondition,
|
|
483
505
|
...rowDomProps
|
|
484
506
|
} = props?.rowBodyProps || {};
|
|
485
507
|
|
|
486
508
|
const {
|
|
487
509
|
handleClick: _cellHandleClick,
|
|
488
510
|
onClick: _cellOnClick,
|
|
511
|
+
classNameCondition: cellClassNameCondition,
|
|
489
512
|
...cellDomProps
|
|
490
513
|
} = props?.cellBodyProps || {};
|
|
491
514
|
|
|
492
|
-
|
|
493
515
|
if (showNoData) {
|
|
494
516
|
return (
|
|
495
517
|
<TableRow
|
|
496
518
|
key="no-data-skeleton"
|
|
497
|
-
className={cn(classNames?.body?.row)}
|
|
519
|
+
className={cn(classNames?.body?.row, )}
|
|
498
520
|
{...rowDomProps}
|
|
499
521
|
>
|
|
500
522
|
<TableCell
|
|
501
523
|
colSpan={columns.length}
|
|
502
|
-
className={cn("h-24 text-center"
|
|
524
|
+
className={cn("h-24 text-center")}
|
|
503
525
|
{...cellDomProps}
|
|
504
526
|
>
|
|
505
527
|
{emptyLabel}
|
|
@@ -1,95 +1,200 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import React from 'react';
|
|
4
4
|
|
|
5
|
-
import { ColumnDef } from
|
|
5
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { cn } from '../../lib/utils';
|
|
8
|
+
import {
|
|
9
|
+
DataTable,
|
|
10
|
+
UseTableProps,
|
|
11
|
+
} from './data-table'; // chỉnh path cho đúng
|
|
8
12
|
|
|
13
|
+
/* ======================================================
|
|
14
|
+
* 1. Data type
|
|
15
|
+
* ====================================================== */
|
|
9
16
|
type User = {
|
|
10
17
|
id: string;
|
|
11
18
|
name: string;
|
|
12
19
|
email: string;
|
|
20
|
+
role: "admin" | "user";
|
|
21
|
+
status: "active" | "inactive";
|
|
13
22
|
};
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
/* ======================================================
|
|
25
|
+
* 2. Mock data
|
|
26
|
+
* ====================================================== */
|
|
27
|
+
const USERS: User[] = [
|
|
28
|
+
{
|
|
29
|
+
id: "1",
|
|
30
|
+
name: "John Doe",
|
|
31
|
+
email: "john@example.com",
|
|
32
|
+
role: "admin",
|
|
33
|
+
status: "active",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "2",
|
|
37
|
+
name: "Jane Smith",
|
|
38
|
+
email: "jane@example.com",
|
|
39
|
+
role: "user",
|
|
40
|
+
status: "inactive",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "3",
|
|
44
|
+
name: "Alex Johnson",
|
|
45
|
+
email: "alex@example.com",
|
|
46
|
+
role: "user",
|
|
47
|
+
status: "active",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/* ======================================================
|
|
52
|
+
* 3. Columns
|
|
53
|
+
* ====================================================== */
|
|
16
54
|
const columns: ColumnDef<User>[] = [
|
|
17
55
|
{
|
|
18
56
|
accessorKey: "name",
|
|
19
57
|
header: "Name",
|
|
20
|
-
cell: ({ row }) =>
|
|
21
|
-
|
|
58
|
+
cell: ({ row }) => (
|
|
59
|
+
<span className="font-medium">{row.original.name}</span>
|
|
60
|
+
),
|
|
22
61
|
},
|
|
23
62
|
{
|
|
24
63
|
accessorKey: "email",
|
|
25
64
|
header: "Email",
|
|
26
|
-
|
|
65
|
+
cell: ({ getValue }) => (
|
|
66
|
+
<span className="text-blue-600">{getValue<string>()}</span>
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
accessorKey: "role",
|
|
71
|
+
header: "Role",
|
|
72
|
+
cell: ({ getValue }) => (
|
|
73
|
+
<span className="capitalize">{getValue<string>()}</span>
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
accessorKey: "status",
|
|
78
|
+
header: "Status",
|
|
79
|
+
cell: ({ getValue }) => {
|
|
80
|
+
const value = getValue<string>();
|
|
81
|
+
return (
|
|
82
|
+
<span
|
|
83
|
+
className={cn(
|
|
84
|
+
"px-2 py-0.5 rounded text-xs font-medium",
|
|
85
|
+
value === "active"
|
|
86
|
+
? "bg-green-100 text-green-700"
|
|
87
|
+
: "bg-red-100 text-red-700"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{value}
|
|
91
|
+
</span>
|
|
92
|
+
);
|
|
93
|
+
},
|
|
27
94
|
},
|
|
28
95
|
];
|
|
29
96
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
97
|
+
/* ======================================================
|
|
98
|
+
* 4. Toolbar
|
|
99
|
+
* ====================================================== */
|
|
100
|
+
const Toolbar = ({ fns }: any) => {
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex items-center gap-2">
|
|
103
|
+
<input
|
|
104
|
+
value={fns.globalFilter ?? ""}
|
|
105
|
+
onChange={(e) => fns.setGlobalFilter(e.target.value)}
|
|
106
|
+
placeholder="Search..."
|
|
107
|
+
className="border rounded px-2 py-1 text-sm"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
37
112
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
113
|
+
/* ======================================================
|
|
114
|
+
* 5. Pagination
|
|
115
|
+
* ====================================================== */
|
|
116
|
+
const Pagination = ({ fns }: any) => {
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex items-center justify-end gap-2">
|
|
119
|
+
<button
|
|
120
|
+
onClick={fns.previousPage}
|
|
121
|
+
disabled={!fns.getCanPreviousPage()}
|
|
122
|
+
className="px-3 py-1 border rounded disabled:opacity-50"
|
|
123
|
+
>
|
|
124
|
+
Prev
|
|
125
|
+
</button>
|
|
126
|
+
|
|
127
|
+
<span className="text-sm">
|
|
128
|
+
Page {fns.pageIndex + 1}
|
|
129
|
+
</span>
|
|
130
|
+
|
|
131
|
+
<button
|
|
132
|
+
onClick={fns.nextPage}
|
|
133
|
+
disabled={!fns.getCanNextPage()}
|
|
134
|
+
className="px-3 py-1 border rounded disabled:opacity-50"
|
|
135
|
+
>
|
|
136
|
+
Next
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
42
141
|
|
|
142
|
+
/* ======================================================
|
|
143
|
+
* 6. useTableProps (row / cell behavior)
|
|
144
|
+
* ====================================================== */
|
|
145
|
+
const useTableProps: UseTableProps<User, unknown> = {
|
|
146
|
+
rowBodyProps: {
|
|
147
|
+
classNameCondition: ({ row }) =>
|
|
148
|
+
row?.original.status === "inactive"
|
|
149
|
+
? "opacity-60"
|
|
150
|
+
: "",
|
|
151
|
+
handleClick: ({ row }) => {
|
|
152
|
+
console.log("Row clicked:", row.original);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
cellBodyProps: {
|
|
156
|
+
classNameCondition: ({ cell }) =>
|
|
157
|
+
cell?.column.id === "email"
|
|
158
|
+
? "underline"
|
|
159
|
+
: "",
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/* ======================================================
|
|
164
|
+
* 7. Final Component
|
|
165
|
+
* ====================================================== */
|
|
166
|
+
export default function UserTableExample() {
|
|
43
167
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<div className="flex items-center justify-end gap-4">
|
|
76
|
-
<button
|
|
77
|
-
onClick={fns.previousPage}
|
|
78
|
-
disabled={!fns.getCanPreviousPage()}
|
|
79
|
-
className="px-3 py-1 rounded-md bg-gray-200 disabled:opacity-50 dark:bg-gray-700 dark:text-white"
|
|
80
|
-
>
|
|
81
|
-
Trang trước
|
|
82
|
-
</button>
|
|
83
|
-
<span>Trang {fns.pageIndex + 1}</span>
|
|
84
|
-
<button
|
|
85
|
-
onClick={fns.nextPage}
|
|
86
|
-
disabled={!fns.getCanNextPage()}
|
|
87
|
-
className="px-3 py-1 rounded-md bg-gray-200 disabled:opacity-50 dark:bg-gray-700 dark:text-white"
|
|
88
|
-
>
|
|
89
|
-
Trang sau
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
/>
|
|
168
|
+
<div className="p-4 space-y-4">
|
|
169
|
+
<h1 className="text-lg font-semibold">User Table</h1>
|
|
170
|
+
|
|
171
|
+
<DataTable<User, unknown>
|
|
172
|
+
data={USERS}
|
|
173
|
+
columns={columns}
|
|
174
|
+
enableSort
|
|
175
|
+
alternate="even"
|
|
176
|
+
alternateColor="#f9fafb"
|
|
177
|
+
emptyLabel="No users found"
|
|
178
|
+
initialState={{
|
|
179
|
+
pagination: {
|
|
180
|
+
pageSize: 5,
|
|
181
|
+
pageIndex: 0,
|
|
182
|
+
},
|
|
183
|
+
}}
|
|
184
|
+
toolbarTable={({ fns }) => <Toolbar fns={fns} />}
|
|
185
|
+
paginationTable={({ fns }) => <Pagination fns={fns} />}
|
|
186
|
+
useTableProps={useTableProps}
|
|
187
|
+
classNames={{
|
|
188
|
+
table: "border rounded-md",
|
|
189
|
+
header: {
|
|
190
|
+
head: "bg-gray-50 text-sm font-semibold",
|
|
191
|
+
},
|
|
192
|
+
body: {
|
|
193
|
+
row: "hover:bg-gray-50 cursor-pointer",
|
|
194
|
+
cell: "text-sm",
|
|
195
|
+
},
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
94
199
|
);
|
|
95
200
|
}
|