55 flexRender ,
66 ColumnDef ,
77 getPaginationRowModel ,
8+ SortingState ,
9+ getSortedRowModel ,
10+ SortDirection
811} from "@tanstack/react-table" ;
912import { SkeletonTable } from "./Skeleton" ;
1013import { StyledButton } from "./StyledButton" ;
@@ -13,13 +16,19 @@ import { Scan } from "#src/routes/Audits.tsx";
1316import { StyledLabeledInput } from "./StyledLabeledInput" ;
1417import { formatId } from "../utils" ;
1518import { Link } from "react-router-dom" ;
19+ import { FaArrowDown , FaArrowUp } from "react-icons/fa" ;
20+ import React from "react" ;
1621
1722interface Audit {
1823 created_at : string ;
1924 id : string ;
2025 interval : string ;
2126 name : string ;
2227 scans : Scan [ ] ;
28+ user : {
29+ name : string ;
30+ email : string ;
31+ }
2332 urls_aggregate : {
2433 aggregate : {
2534 count : number ;
@@ -39,6 +48,13 @@ export const AuditsTable = ({ audits, isLoading }: auditsTableProps) => {
3948 pageSize : 10 ,
4049 } ) ;
4150
51+ function renderSortingIcon ( val : false | SortDirection ) {
52+ switch ( val ) {
53+ case "asc" : return < FaArrowUp /> ;
54+ case "desc" : return < FaArrowDown /> ;
55+ default : return false ;
56+ }
57+ }
4258 const columns = useMemo < ColumnDef < Audit > [ ] > (
4359 ( ) => [
4460 {
@@ -48,14 +64,19 @@ export const AuditsTable = ({ audits, isLoading }: auditsTableProps) => {
4864 return < Link to = { `/audits/${ formatId ( row . original . id ) } ` } className = { styles [ "audit-name" ] } > { row . original . name } </ Link > ;
4965 } ,
5066 } ,
51-
67+
5268 {
5369 accessorKey : "user" ,
5470 header : "Created By" ,
5571 cell : ( { getValue } ) => {
5672 const user = getValue ( ) as any ; ;
5773 return < Link to = { `mailto:${ user . email } ` } > { user . name } </ Link > ;
5874 } ,
75+ sortingFn : ( rowA , rowB , columnId ) => {
76+ const valA = rowA . original . user . name ;
77+ const valB = rowB . original . user . name ;
78+ return valA . localeCompare ( valB ) ;
79+ }
5980 } ,
6081 {
6182 accessorKey : "interval" ,
@@ -84,34 +105,43 @@ export const AuditsTable = ({ audits, isLoading }: auditsTableProps) => {
84105 } ,
85106 } ,
86107 {
108+ accessorFn : ( row ) => row . urls_aggregate . aggregate . count ,
109+ sortUndefined : 'last' ,
87110 accessorKey : "urls_aggregate" ,
88111 header : "URLs" ,
89112 cell : ( { getValue } ) => {
90- const urls = getValue ( ) as any ;
91- return urls . aggregate . count ;
113+ const urls = getValue ( ) as number ;
114+ if ( ! urls ) return < > N/A</ > ;
115+ return urls ;
92116 } ,
93117 } ,
94118 {
119+ accessorFn : ( row ) => row . scans [ 0 ] ?. blockers_aggregate ?. aggregate ?. count ,
120+ sortUndefined : 'last' ,
95121 accessorKey : "scans" ,
96122 header : "Blockers" ,
97123 cell : ( { getValue } ) => {
98- const scans = getValue ( ) as Scan [ ] ;
99- return < b > { scans [ 0 ] ?. blockers_aggregate ?. aggregate ?. count } </ b > ;
124+ const scans = getValue ( ) as number ;
125+ if ( ! scans ) return < > N/A</ > ;
126+ return < b > { scans } </ b > ;
100127 } ,
101128 } ,
102129 ] ,
103130 [ ]
104131 ) ;
105132
133+ const [ sorting , setSorting ] = React . useState < SortingState > ( [ ] ) ;
106134 const table = useReactTable ( {
107135 data : audits || [ ] ,
108136 columns,
109137 getCoreRowModel : getCoreRowModel ( ) ,
110138 getPaginationRowModel : getPaginationRowModel ( ) ,
111139 state : {
112140 pagination,
141+ sorting
113142 } ,
114- onPaginationChange : setPagination ,
143+ onSortingChange : setSorting ,
144+ getSortedRowModel : getSortedRowModel ( ) ,
115145 } ) ;
116146
117147 return (
@@ -130,13 +160,33 @@ export const AuditsTable = ({ audits, isLoading }: auditsTableProps) => {
130160 { table . getHeaderGroups ( ) . map ( ( headerGroup ) => (
131161 < tr key = { headerGroup . id } className = "bg-gray-100" >
132162 { headerGroup . headers . map ( ( header ) => (
133- < th key = { header . id } scope = "col" >
134- { header . isPlaceholder
135- ? null
136- : flexRender (
137- header . column . columnDef . header ,
138- header . getContext ( )
139- ) }
163+ < th key = { header . id } scope = "col" aria-sort = {
164+ header . column . getIsSorted ( ) === 'asc'
165+ ? 'ascending'
166+ : header . column . getIsSorted ( ) === 'desc'
167+ ? 'descending'
168+ : 'none'
169+ } >
170+ < div
171+ { ...{
172+ onClick : header . column . getToggleSortingHandler ( )
173+ } }
174+ >
175+ < button className = { styles [ "header-sort" ] } aria-label = { `Sort by "${ header . column . columnDef . header } " ${ header . column . getIsSorted ( ) === 'asc' ? 'descending' : 'ascending'
176+ } `} >
177+ < div className = { styles [ "header-label" ] } >
178+ { flexRender (
179+ header . column . columnDef . header ,
180+ header . getContext ( )
181+ ) }
182+ </ div >
183+ { {
184+ asc : renderSortingIcon ( "asc" ) ,
185+ desc : renderSortingIcon ( "desc" ) ,
186+ } [ header . column . getIsSorted ( ) as string ] ?? < div className = { styles [ "arrow-placeholder" ] } /> }
187+
188+ </ button >
189+ </ div >
140190 </ th >
141191 ) ) }
142192 </ tr >
0 commit comments