@@ -7,6 +7,7 @@ import { buildParams } from '@/lib/utils';
77import { PaginationMeta } from '@/types' ;
88import { Link , router } from '@inertiajs/react' ;
99import {
10+ Column ,
1011 ColumnDef ,
1112 flexRender ,
1213 getCoreRowModel ,
@@ -23,17 +24,44 @@ interface DataTableProps<TData, TValue> {
2324 pagination : PaginationMeta < TData [ ] > ;
2425}
2526
27+ function getCommonPinningStyles < TData > ( {
28+ column,
29+ withBorder = false ,
30+ } : {
31+ column : Column < TData > ;
32+ withBorder ?: boolean ;
33+ } ) : React . CSSProperties {
34+ const isPinned = column . getIsPinned ( ) ;
35+ const isLastLeftPinnedColumn = isPinned === 'left' && column . getIsLastColumn ( 'left' ) ;
36+ const isFirstRightPinnedColumn = isPinned === 'right' && column . getIsFirstColumn ( 'right' ) ;
37+
38+ const leftPinnedBoxShadow = '-4px 0 4px -4px var(--border) inset' ;
39+ const rightPinnedBoxShadow = '4px 0 4px -4px var(--border) inset' ;
40+
41+ const rightPinnedShadow = isFirstRightPinnedColumn ? rightPinnedBoxShadow : undefined ;
42+ const pinnedColumnShadow = isLastLeftPinnedColumn ? leftPinnedBoxShadow : rightPinnedShadow ;
43+
44+ return {
45+ boxShadow : withBorder ? pinnedColumnShadow : undefined ,
46+ left : isPinned === 'left' ? `${ column . getStart ( 'left' ) } px` : undefined ,
47+ right : isPinned === 'right' ? `${ column . getAfter ( 'right' ) } px` : undefined ,
48+ opacity : isPinned ? 0.97 : 1 ,
49+ position : isPinned ? 'sticky' : 'relative' ,
50+ background : 'var(--background)' ,
51+ width : column . getSize ( ) ,
52+ zIndex : isPinned ? 1 : undefined ,
53+ } ;
54+ }
55+
2656export function DataTable < TData , TValue > ( { columns, data, pagination } : Readonly < DataTableProps < TData , TValue > > ) {
27- const getInitialSorting = ( ) : SortingState => {
57+ const [ sorting , setSorting ] = useState < SortingState > ( ( ) => {
2858 const params = new URLSearchParams ( globalThis . location . search ) ;
2959 const sort_by = params . get ( 'sort_by' ) ;
3060 const sort_dir = params . get ( 'sort_dir' ) ;
3161 if ( ! sort_by ) return [ ] ;
3262
3363 return [ { id : sort_by , desc : sort_dir === 'desc' } ] ;
34- } ;
35-
36- const [ sorting , setSorting ] = useState < SortingState > ( getInitialSorting ) ;
64+ } ) ;
3765
3866 const [ columnVisibility , setColumnVisibility ] = useState < VisibilityState > ( { } ) ;
3967
@@ -43,39 +71,48 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
4371 } ;
4472
4573 const [ search , setSearch ] = useState < string > ( getInitialSearch ( ) ) ;
74+ const [ path ] = useState < string | undefined > ( pagination . path ) ;
4675 const searchDebounce = useRef < NodeJS . Timeout | undefined > ( undefined ) ;
4776
48- const path = pagination . path ;
77+ const table = useReactTable ( {
78+ columns,
79+ data,
80+ getCoreRowModel : getCoreRowModel ( ) ,
81+ manualPagination : true , // turn off client-side pagination
82+ manualSorting : true , // turn off client-side sorting
83+ pageCount : pagination . last_page ?? Math . ceil ( ( pagination . total ?? 0 ) / ( pagination . per_page ?? 1 ) ) ,
84+ initialState : {
85+ pagination : {
86+ pageIndex : Math . max ( ( pagination . current_page ?? 1 ) - 1 , 0 ) ,
87+ pageSize : pagination . per_page ,
88+ } ,
89+ columnPinning : { left : [ 'id' ] , right : [ 'actions' ] } ,
90+ } ,
91+ onSortingChange : setSorting ,
92+ onColumnVisibilityChange : setColumnVisibility ,
93+ state : { sorting, columnVisibility } ,
94+ } ) ;
4995
96+ // Sync sorting state with server via Inertia
5097 useEffect ( ( ) => {
5198 if ( ! path ) return ;
5299
53100 const params = new URLSearchParams ( globalThis . location . search ) ;
54101 const currentSortBy = params . get ( 'sort_by' ) ;
55102 const currentSortDir = params . get ( 'sort_dir' ) ;
56103
57- if ( ! sorting || sorting . length === 0 ) {
58- if ( ! currentSortBy && ! currentSortDir ) return ;
59-
60- router . get ( path , buildParams ( { sort_by : undefined , sort_dir : undefined } ) , {
61- preserveState : true ,
62- replace : true ,
63- } ) ;
64- return ;
65- }
104+ const sort = sorting ?. [ 0 ] ;
105+ const desiredSortBy = sort ? String ( sort . id ) : undefined ;
106+ let desiredSortDir : string | undefined = undefined ;
66107
67- const sort = sorting [ 0 ] ;
68- const sortBy = String ( sort . id ) ;
69- const sortDir = sort . desc ? 'desc' : 'asc' ;
108+ if ( sort ) desiredSortDir = sort . desc ? 'desc' : 'asc' ;
70109
71- if ( currentSortBy === sortBy && currentSortDir === sortDir ) return ;
110+ if ( currentSortBy === desiredSortBy && currentSortDir === desiredSortDir ) return ;
72111
73- if ( sortBy ) {
74- router . get ( path , buildParams ( { sort_by : sortBy , sort_dir : sortDir } ) , {
75- preserveState : true ,
76- replace : true ,
77- } ) ;
78- }
112+ router . get ( path , buildParams ( { sort_by : desiredSortBy , sort_dir : desiredSortDir } ) , {
113+ preserveState : true ,
114+ replace : true ,
115+ } ) ;
79116 } , [ sorting , path ] ) ;
80117
81118 useEffect ( ( ) => {
@@ -98,18 +135,6 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
98135 return ( ) => globalThis . clearTimeout ( searchDebounce . current ) ;
99136 } , [ search , path ] ) ;
100137
101- const table = useReactTable ( {
102- data,
103- columns,
104- getCoreRowModel : getCoreRowModel ( ) ,
105- onSortingChange : setSorting ,
106- onColumnVisibilityChange : setColumnVisibility ,
107- state : {
108- sorting,
109- columnVisibility,
110- } ,
111- } ) ;
112-
113138 return (
114139 < div className = "mx-auto w-full flex-col space-y-4" >
115140 < div className = "flex items-center py-4" >
@@ -146,7 +171,13 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
146171 < TableRow key = { headerGroup . id } >
147172 { headerGroup . headers . map ( ( header ) => {
148173 return (
149- < TableHead key = { header . id } >
174+ < TableHead
175+ key = { header . id }
176+ colSpan = { header . colSpan }
177+ style = { {
178+ ...getCommonPinningStyles ( { column : header . column } ) ,
179+ } }
180+ >
150181 { header . isPlaceholder
151182 ? null
152183 : flexRender ( header . column . columnDef . header , header . getContext ( ) ) }
@@ -161,7 +192,12 @@ export function DataTable<TData, TValue>({ columns, data, pagination }: Readonly
161192 table . getRowModel ( ) . rows . map ( ( row ) => (
162193 < TableRow key = { row . id } data-state = { row . getIsSelected ( ) && 'selected' } >
163194 { row . getVisibleCells ( ) . map ( ( cell ) => (
164- < TableCell key = { cell . id } >
195+ < TableCell
196+ key = { cell . id }
197+ style = { {
198+ ...getCommonPinningStyles ( { column : cell . column } ) ,
199+ } }
200+ >
165201 { flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) }
166202 </ TableCell >
167203 ) ) }
0 commit comments