@superdangerous/app-framework 4.16.35 → 4.16.36
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/package.json +4 -4
- package/ui/dist/data-table.d.mts +457 -0
- package/ui/dist/data-table.d.ts +457 -0
- package/ui/dist/data-table.js +2 -0
- package/ui/dist/data-table.js.map +1 -0
- package/ui/dist/data-table.mjs +2 -0
- package/ui/dist/data-table.mjs.map +1 -0
- package/ui/dist/index.d.mts +2 -454
- package/ui/dist/index.d.ts +2 -454
- package/ui/data-table/components/BatchActionsBar.tsx +0 -53
- package/ui/data-table/components/ColumnVisibility.tsx +0 -111
- package/ui/data-table/components/DataTable.tsx +0 -498
- package/ui/data-table/components/DataTablePage.tsx +0 -244
- package/ui/data-table/components/MultiSelectFilter.tsx +0 -153
- package/ui/data-table/components/Pagination.tsx +0 -203
- package/ui/data-table/components/PaginationControls.tsx +0 -122
- package/ui/data-table/components/TableFilters.tsx +0 -145
- package/ui/data-table/components/index.ts +0 -44
- package/ui/data-table/components/types.ts +0 -181
- package/ui/data-table/hooks/index.ts +0 -17
- package/ui/data-table/hooks/useColumnOrder.ts +0 -237
- package/ui/data-table/hooks/useColumnVisibility.ts +0 -128
- package/ui/data-table/hooks/usePagination.ts +0 -160
- package/ui/data-table/hooks/useResizableColumns.ts +0 -282
- package/ui/data-table/index.ts +0 -87
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
import { useMemo, useCallback, useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Table,
|
|
4
|
-
TableBody,
|
|
5
|
-
TableCell,
|
|
6
|
-
TableHead,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableRow,
|
|
9
|
-
} from '../../components/base/table';
|
|
10
|
-
import { TooltipProvider } from '../../components/base/tooltip';
|
|
11
|
-
import { ChevronUp, ChevronDown, ArrowUpDown, Loader2, Check, Eye, EyeOff } from 'lucide-react';
|
|
12
|
-
import { cn } from '../../src/utils/cn';
|
|
13
|
-
import { useResizableColumns } from '../hooks/useResizableColumns';
|
|
14
|
-
import { useColumnVisibility } from '../hooks/useColumnVisibility';
|
|
15
|
-
import { useColumnOrder, useColumnDragDrop } from '../hooks/useColumnOrder';
|
|
16
|
-
import { usePagination } from '../hooks/usePagination';
|
|
17
|
-
import { Pagination } from './Pagination';
|
|
18
|
-
import type {
|
|
19
|
-
DataTableProps,
|
|
20
|
-
ColumnDef,
|
|
21
|
-
ColumnConfigCompat,
|
|
22
|
-
ColumnSizeConfig,
|
|
23
|
-
} from './types';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* DataTable - Generic data table with full feature set
|
|
27
|
-
*
|
|
28
|
-
* Features:
|
|
29
|
-
* - Column resizing (drag handles)
|
|
30
|
-
* - Column reordering (drag-drop)
|
|
31
|
-
* - Column visibility toggle
|
|
32
|
-
* - Row selection with checkboxes
|
|
33
|
-
* - Sorting
|
|
34
|
-
* - Pagination
|
|
35
|
-
* - Sticky actions column
|
|
36
|
-
* - Context menu support
|
|
37
|
-
* - Header context menu for column visibility
|
|
38
|
-
*/
|
|
39
|
-
export function DataTable<T>({
|
|
40
|
-
data,
|
|
41
|
-
columns,
|
|
42
|
-
storageKey,
|
|
43
|
-
getRowId,
|
|
44
|
-
selectable = false,
|
|
45
|
-
selectedIds,
|
|
46
|
-
onSelectionChange,
|
|
47
|
-
onRowClick,
|
|
48
|
-
onRowContextMenu,
|
|
49
|
-
sortField,
|
|
50
|
-
sortOrder,
|
|
51
|
-
onSort,
|
|
52
|
-
actionsColumn,
|
|
53
|
-
actionsColumnWidth = 80,
|
|
54
|
-
pageSize = 25,
|
|
55
|
-
pagination: externalPagination,
|
|
56
|
-
hidePagination = false,
|
|
57
|
-
className,
|
|
58
|
-
rowClassName,
|
|
59
|
-
enableHeaderContextMenu = true,
|
|
60
|
-
lockedColumns = [],
|
|
61
|
-
defaultColumnOrder,
|
|
62
|
-
loading = false,
|
|
63
|
-
emptyState,
|
|
64
|
-
}: DataTableProps<T>) {
|
|
65
|
-
// Build column configs for hooks
|
|
66
|
-
const columnSizeConfig = useMemo<ColumnSizeConfig[]>(() => {
|
|
67
|
-
const configs: ColumnSizeConfig[] = [];
|
|
68
|
-
|
|
69
|
-
if (selectable) {
|
|
70
|
-
configs.push({ key: 'select', defaultWidth: 40, minWidth: 40 });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
columns.forEach((col) => {
|
|
74
|
-
configs.push({
|
|
75
|
-
key: col.id,
|
|
76
|
-
defaultWidth: col.width?.default ?? 150,
|
|
77
|
-
minWidth: col.width?.min ?? 80,
|
|
78
|
-
maxWidth: col.width?.max,
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (actionsColumn) {
|
|
83
|
-
configs.push({ key: 'actions', defaultWidth: actionsColumnWidth, minWidth: 60 });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return configs;
|
|
87
|
-
}, [columns, selectable, actionsColumn, actionsColumnWidth]);
|
|
88
|
-
|
|
89
|
-
const columnVisibilityConfig = useMemo<ColumnConfigCompat[]>(() => {
|
|
90
|
-
return columns.map((col) => ({
|
|
91
|
-
id: col.id,
|
|
92
|
-
label: typeof col.header === 'string' ? col.header : col.id,
|
|
93
|
-
defaultVisible: col.visibility?.default ?? true,
|
|
94
|
-
locked: col.visibility?.locked,
|
|
95
|
-
}));
|
|
96
|
-
}, [columns]);
|
|
97
|
-
|
|
98
|
-
const defaultOrder = useMemo(() => {
|
|
99
|
-
if (defaultColumnOrder) return defaultColumnOrder;
|
|
100
|
-
const order: string[] = [];
|
|
101
|
-
if (selectable) order.push('select');
|
|
102
|
-
columns.forEach((col) => order.push(col.id));
|
|
103
|
-
if (actionsColumn) order.push('actions');
|
|
104
|
-
return order;
|
|
105
|
-
}, [defaultColumnOrder, columns, selectable, actionsColumn]);
|
|
106
|
-
|
|
107
|
-
// Initialize hooks
|
|
108
|
-
const {
|
|
109
|
-
getResizeHandleProps,
|
|
110
|
-
getColumnStyle,
|
|
111
|
-
getTableStyle,
|
|
112
|
-
} = useResizableColumns({
|
|
113
|
-
tableId: storageKey,
|
|
114
|
-
columns: columnSizeConfig,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const columnVisibility = useColumnVisibility({
|
|
118
|
-
columns: columnVisibilityConfig,
|
|
119
|
-
storageKey,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const { columnOrder, moveColumn } = useColumnOrder({
|
|
123
|
-
storageKey: `${storageKey}-order`,
|
|
124
|
-
defaultOrder,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const { dragState, getDragHandleProps, showDropIndicator } = useColumnDragDrop(
|
|
128
|
-
columnOrder,
|
|
129
|
-
moveColumn,
|
|
130
|
-
[...lockedColumns, 'select', 'actions']
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// Use external pagination if provided, otherwise use internal
|
|
134
|
-
const internalPagination = usePagination({
|
|
135
|
-
data,
|
|
136
|
-
pageSize,
|
|
137
|
-
storageKey,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const pagination = externalPagination || internalPagination;
|
|
141
|
-
|
|
142
|
-
// Header context menu state
|
|
143
|
-
const [headerContextMenu, setHeaderContextMenu] = useState<{ x: number; y: number } | null>(null);
|
|
144
|
-
|
|
145
|
-
const handleHeaderContextMenu = useCallback((e: React.MouseEvent) => {
|
|
146
|
-
if (!enableHeaderContextMenu) return;
|
|
147
|
-
e.preventDefault();
|
|
148
|
-
setHeaderContextMenu({ x: e.clientX, y: e.clientY });
|
|
149
|
-
}, [enableHeaderContextMenu]);
|
|
150
|
-
|
|
151
|
-
// Close header context menu on click outside
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (!headerContextMenu) return;
|
|
154
|
-
const close = () => setHeaderContextMenu(null);
|
|
155
|
-
window.addEventListener('click', close);
|
|
156
|
-
return () => {
|
|
157
|
-
window.removeEventListener('click', close);
|
|
158
|
-
};
|
|
159
|
-
}, [headerContextMenu]);
|
|
160
|
-
|
|
161
|
-
// Selection helpers
|
|
162
|
-
const isAllSelected = useMemo(() => {
|
|
163
|
-
if (!selectable || !selectedIds || pagination.paginatedData.length === 0) return false;
|
|
164
|
-
return pagination.paginatedData.every((item) => selectedIds.has(getRowId(item)));
|
|
165
|
-
}, [selectable, selectedIds, pagination.paginatedData, getRowId]);
|
|
166
|
-
|
|
167
|
-
const isSomeSelected = useMemo(() => {
|
|
168
|
-
if (!selectable || !selectedIds) return false;
|
|
169
|
-
const selected = pagination.paginatedData.filter((item) => selectedIds.has(getRowId(item)));
|
|
170
|
-
return selected.length > 0 && selected.length < pagination.paginatedData.length;
|
|
171
|
-
}, [selectable, selectedIds, pagination.paginatedData, getRowId]);
|
|
172
|
-
|
|
173
|
-
const toggleSelection = useCallback((itemId: string) => {
|
|
174
|
-
if (!onSelectionChange || !selectedIds) return;
|
|
175
|
-
const next = new Set(selectedIds);
|
|
176
|
-
if (next.has(itemId)) {
|
|
177
|
-
next.delete(itemId);
|
|
178
|
-
} else {
|
|
179
|
-
next.add(itemId);
|
|
180
|
-
}
|
|
181
|
-
onSelectionChange(next);
|
|
182
|
-
}, [selectedIds, onSelectionChange]);
|
|
183
|
-
|
|
184
|
-
const selectAll = useCallback(() => {
|
|
185
|
-
if (!onSelectionChange) return;
|
|
186
|
-
const ids = new Set(pagination.paginatedData.map(getRowId));
|
|
187
|
-
onSelectionChange(ids);
|
|
188
|
-
}, [pagination.paginatedData, getRowId, onSelectionChange]);
|
|
189
|
-
|
|
190
|
-
const clearSelection = useCallback(() => {
|
|
191
|
-
if (!onSelectionChange) return;
|
|
192
|
-
onSelectionChange(new Set());
|
|
193
|
-
}, [onSelectionChange]);
|
|
194
|
-
|
|
195
|
-
// Get sort icon
|
|
196
|
-
const getSortIcon = useCallback((field: string) => {
|
|
197
|
-
if (sortField !== field) {
|
|
198
|
-
return <ArrowUpDown className="h-3.5 w-3.5 text-muted-foreground" />;
|
|
199
|
-
}
|
|
200
|
-
return sortOrder === 'asc'
|
|
201
|
-
? <ChevronUp className="h-3.5 w-3.5" />
|
|
202
|
-
: <ChevronDown className="h-3.5 w-3.5" />;
|
|
203
|
-
}, [sortField, sortOrder]);
|
|
204
|
-
|
|
205
|
-
// Find column def by id
|
|
206
|
-
const getColumnDef = useCallback((id: string): ColumnDef<T> | undefined => {
|
|
207
|
-
return columns.find((col) => col.id === id);
|
|
208
|
-
}, [columns]);
|
|
209
|
-
|
|
210
|
-
// Render header cell content
|
|
211
|
-
const renderHeaderContent = useCallback((col: ColumnDef<T>) => {
|
|
212
|
-
const headerProps = {
|
|
213
|
-
columnId: col.id,
|
|
214
|
-
isSorted: sortField === col.sortKey,
|
|
215
|
-
sortDirection: sortField === col.sortKey ? sortOrder : undefined,
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
if (typeof col.header === 'function') {
|
|
219
|
-
return col.header(headerProps);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (col.sortKey && onSort) {
|
|
223
|
-
return (
|
|
224
|
-
<button
|
|
225
|
-
onClick={() => onSort(col.sortKey!)}
|
|
226
|
-
className={cn(
|
|
227
|
-
'flex items-center gap-1 hover:text-foreground transition-colors',
|
|
228
|
-
sortField === col.sortKey && 'text-foreground font-medium'
|
|
229
|
-
)}
|
|
230
|
-
>
|
|
231
|
-
{col.header}
|
|
232
|
-
{getSortIcon(col.sortKey)}
|
|
233
|
-
</button>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return col.header;
|
|
238
|
-
}, [sortField, sortOrder, onSort, getSortIcon]);
|
|
239
|
-
|
|
240
|
-
return (
|
|
241
|
-
<TooltipProvider>
|
|
242
|
-
<>
|
|
243
|
-
{/* Table with scroll container and border - supports both horizontal and vertical scroll with sticky header */}
|
|
244
|
-
<div className="overflow-auto border rounded-lg h-full">
|
|
245
|
-
<Table
|
|
246
|
-
style={getTableStyle()}
|
|
247
|
-
className={cn('resizable-table sticky-actions-table', className)}
|
|
248
|
-
>
|
|
249
|
-
<TableHeader className="sticky top-0 z-20 bg-muted">
|
|
250
|
-
<TableRow onContextMenu={handleHeaderContextMenu} className="border-t-0">
|
|
251
|
-
{columnOrder.map((colKey) => {
|
|
252
|
-
// Select column (sticky on left)
|
|
253
|
-
if (colKey === 'select' && selectable) {
|
|
254
|
-
return (
|
|
255
|
-
<TableHead
|
|
256
|
-
key="select"
|
|
257
|
-
className="sticky-select-header w-10 sticky left-0 z-20 bg-muted relative after:absolute after:right-0 after:top-0 after:bottom-0 after:w-px after:bg-border"
|
|
258
|
-
>
|
|
259
|
-
<input
|
|
260
|
-
type="checkbox"
|
|
261
|
-
checked={isAllSelected}
|
|
262
|
-
ref={(el) => {
|
|
263
|
-
if (el) el.indeterminate = isSomeSelected;
|
|
264
|
-
}}
|
|
265
|
-
onChange={(e) => e.target.checked ? selectAll() : clearSelection()}
|
|
266
|
-
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
|
|
267
|
-
title={isAllSelected ? 'Deselect all' : 'Select all visible'}
|
|
268
|
-
/>
|
|
269
|
-
</TableHead>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Data columns
|
|
274
|
-
const col = getColumnDef(colKey);
|
|
275
|
-
if (!col) return null;
|
|
276
|
-
if (!columnVisibility.isColumnVisible(colKey)) return null;
|
|
277
|
-
|
|
278
|
-
return (
|
|
279
|
-
<TableHead
|
|
280
|
-
key={colKey}
|
|
281
|
-
style={getColumnStyle(colKey)}
|
|
282
|
-
{...getDragHandleProps(colKey)}
|
|
283
|
-
className={cn(
|
|
284
|
-
'cursor-grab relative',
|
|
285
|
-
dragState.draggedId === colKey && 'column-dragging opacity-50',
|
|
286
|
-
showDropIndicator(colKey) && 'drop-indicator'
|
|
287
|
-
)}
|
|
288
|
-
>
|
|
289
|
-
{renderHeaderContent(col)}
|
|
290
|
-
<div {...getResizeHandleProps(colKey)} />
|
|
291
|
-
</TableHead>
|
|
292
|
-
);
|
|
293
|
-
})}
|
|
294
|
-
|
|
295
|
-
{/* Actions column header */}
|
|
296
|
-
{actionsColumn && (
|
|
297
|
-
<TableHead
|
|
298
|
-
key="actions"
|
|
299
|
-
className="sticky right-0 z-20 bg-muted text-center relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border"
|
|
300
|
-
style={{ width: actionsColumnWidth, minWidth: actionsColumnWidth, maxWidth: actionsColumnWidth }}
|
|
301
|
-
>
|
|
302
|
-
Actions
|
|
303
|
-
</TableHead>
|
|
304
|
-
)}
|
|
305
|
-
</TableRow>
|
|
306
|
-
</TableHeader>
|
|
307
|
-
|
|
308
|
-
<TableBody>
|
|
309
|
-
{loading ? (
|
|
310
|
-
<TableRow>
|
|
311
|
-
<TableCell
|
|
312
|
-
colSpan={columnOrder.length + (actionsColumn ? 1 : 0)}
|
|
313
|
-
className="!p-0 h-32"
|
|
314
|
-
>
|
|
315
|
-
<div className="sticky left-0 w-screen max-w-full h-full bg-background flex justify-center items-center">
|
|
316
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
317
|
-
<Loader2 className="h-5 w-5 animate-spin" />
|
|
318
|
-
Loading...
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
</TableCell>
|
|
322
|
-
</TableRow>
|
|
323
|
-
) : pagination.paginatedData.length === 0 ? null : (
|
|
324
|
-
pagination.paginatedData.map((item) => {
|
|
325
|
-
const rowId = getRowId(item);
|
|
326
|
-
const isSelected = selectedIds?.has(rowId) ?? false;
|
|
327
|
-
|
|
328
|
-
return (
|
|
329
|
-
<TableRow
|
|
330
|
-
key={rowId}
|
|
331
|
-
className={cn(
|
|
332
|
-
'group cursor-pointer bg-background hover:bg-muted transition-none',
|
|
333
|
-
isSelected && 'bg-primary/5',
|
|
334
|
-
rowClassName?.(item)
|
|
335
|
-
)}
|
|
336
|
-
onClick={() => onRowClick?.(item)}
|
|
337
|
-
onContextMenu={(e) => {
|
|
338
|
-
if (onRowContextMenu) {
|
|
339
|
-
e.preventDefault();
|
|
340
|
-
onRowContextMenu(item, { x: e.clientX, y: e.clientY });
|
|
341
|
-
}
|
|
342
|
-
}}
|
|
343
|
-
>
|
|
344
|
-
{columnOrder.map((colKey) => {
|
|
345
|
-
// Select cell (sticky on left)
|
|
346
|
-
if (colKey === 'select' && selectable) {
|
|
347
|
-
return (
|
|
348
|
-
<TableCell
|
|
349
|
-
key="select"
|
|
350
|
-
className={cn(
|
|
351
|
-
'sticky-select-cell w-10 sticky left-0 z-10 relative after:absolute after:right-0 after:top-0 after:bottom-0 after:w-px after:bg-border',
|
|
352
|
-
'bg-background group-hover:bg-muted',
|
|
353
|
-
isSelected && 'bg-primary/5 group-hover:bg-primary/10'
|
|
354
|
-
)}
|
|
355
|
-
onClick={(e) => e.stopPropagation()}
|
|
356
|
-
>
|
|
357
|
-
<input
|
|
358
|
-
type="checkbox"
|
|
359
|
-
checked={isSelected}
|
|
360
|
-
onChange={() => toggleSelection(rowId)}
|
|
361
|
-
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
|
|
362
|
-
/>
|
|
363
|
-
</TableCell>
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Data cells
|
|
368
|
-
const col = getColumnDef(colKey);
|
|
369
|
-
if (!col) return null;
|
|
370
|
-
if (!columnVisibility.isColumnVisible(colKey)) return null;
|
|
371
|
-
|
|
372
|
-
const cellProps = {
|
|
373
|
-
columnId: colKey,
|
|
374
|
-
isDragging: dragState.draggedId === colKey,
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
return (
|
|
378
|
-
<TableCell
|
|
379
|
-
key={colKey}
|
|
380
|
-
style={getColumnStyle(colKey)}
|
|
381
|
-
className={cn(
|
|
382
|
-
col.className,
|
|
383
|
-
dragState.draggedId === colKey && 'column-dragging'
|
|
384
|
-
)}
|
|
385
|
-
>
|
|
386
|
-
{col.cell(item, cellProps)}
|
|
387
|
-
</TableCell>
|
|
388
|
-
);
|
|
389
|
-
})}
|
|
390
|
-
|
|
391
|
-
{/* Actions cell */}
|
|
392
|
-
{actionsColumn && (
|
|
393
|
-
<TableCell
|
|
394
|
-
key="actions"
|
|
395
|
-
className={cn(
|
|
396
|
-
'sticky right-0 z-10 text-center relative before:absolute before:left-0 before:top-0 before:bottom-0 before:w-px before:bg-border',
|
|
397
|
-
'bg-background group-hover:bg-muted',
|
|
398
|
-
isSelected && 'bg-primary/5 group-hover:bg-primary/10'
|
|
399
|
-
)}
|
|
400
|
-
style={{ width: actionsColumnWidth, minWidth: actionsColumnWidth, maxWidth: actionsColumnWidth }}
|
|
401
|
-
onClick={(e) => e.stopPropagation()}
|
|
402
|
-
>
|
|
403
|
-
{actionsColumn(item)}
|
|
404
|
-
</TableCell>
|
|
405
|
-
)}
|
|
406
|
-
</TableRow>
|
|
407
|
-
);
|
|
408
|
-
})
|
|
409
|
-
)}
|
|
410
|
-
</TableBody>
|
|
411
|
-
</Table>
|
|
412
|
-
</div>
|
|
413
|
-
|
|
414
|
-
{/* Empty state - rendered outside table for proper positioning */}
|
|
415
|
-
{!loading && pagination.paginatedData.length === 0 && (
|
|
416
|
-
<div className="empty-state-container flex-1 flex items-center justify-center bg-background">
|
|
417
|
-
{emptyState || <span className="block text-center text-muted-foreground py-8">No data</span>}
|
|
418
|
-
</div>
|
|
419
|
-
)}
|
|
420
|
-
|
|
421
|
-
{/* Pagination (hidden when using external pagination controls) */}
|
|
422
|
-
{!hidePagination && !loading && pagination.totalPages > 1 && (
|
|
423
|
-
<Pagination
|
|
424
|
-
page={pagination.page}
|
|
425
|
-
pageSize={pagination.pageSize}
|
|
426
|
-
totalItems={pagination.totalItems}
|
|
427
|
-
totalPages={pagination.totalPages}
|
|
428
|
-
startIndex={pagination.startIndex}
|
|
429
|
-
endIndex={pagination.endIndex}
|
|
430
|
-
canGoPrev={pagination.canGoPrev}
|
|
431
|
-
canGoNext={pagination.canGoNext}
|
|
432
|
-
onPageChange={pagination.setPage}
|
|
433
|
-
onPageSizeChange={pagination.setPageSize}
|
|
434
|
-
onNextPage={pagination.nextPage}
|
|
435
|
-
onPrevPage={pagination.prevPage}
|
|
436
|
-
onFirstPage={'firstPage' in pagination ? (pagination as { firstPage: () => void }).firstPage : () => pagination.setPage(1)}
|
|
437
|
-
onLastPage={'lastPage' in pagination ? (pagination as { lastPage: () => void }).lastPage : () => pagination.setPage(pagination.totalPages)}
|
|
438
|
-
pageSizeOptions={pagination.pageSizeOptions}
|
|
439
|
-
/>
|
|
440
|
-
)}
|
|
441
|
-
|
|
442
|
-
{/* Header Context Menu for Column Visibility */}
|
|
443
|
-
{headerContextMenu && (
|
|
444
|
-
<div
|
|
445
|
-
className="fixed z-50 min-w-[200px] overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95"
|
|
446
|
-
style={{
|
|
447
|
-
top: headerContextMenu.y,
|
|
448
|
-
left: headerContextMenu.x,
|
|
449
|
-
maxHeight: `calc(100vh - ${headerContextMenu.y}px - 20px)`,
|
|
450
|
-
}}
|
|
451
|
-
onClick={(e) => e.stopPropagation()}
|
|
452
|
-
>
|
|
453
|
-
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
|
454
|
-
Toggle columns
|
|
455
|
-
</div>
|
|
456
|
-
<div className="h-px bg-border my-1" />
|
|
457
|
-
{columnVisibility.columns.map(column => {
|
|
458
|
-
const visible = columnVisibility.isColumnVisible(column.id);
|
|
459
|
-
const isLocked = column.locked === true;
|
|
460
|
-
return (
|
|
461
|
-
<button
|
|
462
|
-
key={column.id}
|
|
463
|
-
onClick={() => !isLocked && columnVisibility.toggleColumn(column.id)}
|
|
464
|
-
disabled={isLocked}
|
|
465
|
-
className={cn(
|
|
466
|
-
'flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded-sm hover:bg-accent',
|
|
467
|
-
isLocked && 'opacity-50 cursor-not-allowed'
|
|
468
|
-
)}
|
|
469
|
-
>
|
|
470
|
-
<div className="w-4 h-4 flex items-center justify-center">
|
|
471
|
-
{visible && <Check className="h-3.5 w-3.5 text-primary" />}
|
|
472
|
-
</div>
|
|
473
|
-
<span className="flex-1 text-left">{column.label}</span>
|
|
474
|
-
{isLocked && <span className="text-xs text-muted-foreground">Required</span>}
|
|
475
|
-
</button>
|
|
476
|
-
);
|
|
477
|
-
})}
|
|
478
|
-
<div className="h-px bg-border my-1" />
|
|
479
|
-
<button
|
|
480
|
-
onClick={() => columnVisibility.showAllColumns()}
|
|
481
|
-
className="flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded-sm hover:bg-accent"
|
|
482
|
-
>
|
|
483
|
-
<Eye className="h-4 w-4" />
|
|
484
|
-
Show All
|
|
485
|
-
</button>
|
|
486
|
-
<button
|
|
487
|
-
onClick={() => columnVisibility.hideAllColumns()}
|
|
488
|
-
className="flex items-center gap-2 w-full px-2 py-1.5 text-sm rounded-sm hover:bg-accent"
|
|
489
|
-
>
|
|
490
|
-
<EyeOff className="h-4 w-4" />
|
|
491
|
-
Hide Optional
|
|
492
|
-
</button>
|
|
493
|
-
</div>
|
|
494
|
-
)}
|
|
495
|
-
</>
|
|
496
|
-
</TooltipProvider>
|
|
497
|
-
);
|
|
498
|
-
}
|