Skip to content

Commit eb16f66

Browse files
committed
Frontend: added column sorting to audits table
1 parent 258215c commit eb16f66

3 files changed

Lines changed: 83 additions & 14 deletions

File tree

apps/frontend/src/components/AuditsTable.module.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,23 @@
1010
.audit-name {
1111
font-weight: bold;
1212
}
13+
.header-sort {
14+
display: inline-flex;
15+
align-items: center;
16+
border:0;
17+
background: transparent;
18+
cursor: pointer;
19+
padding:0;
20+
font-weight: bold;
21+
&:hover {
22+
color: variables.$red;
23+
}
24+
}
25+
.header-label {
26+
margin-right: calc(variables.$spacing/2);
27+
}
28+
.arrow-placeholder {
29+
width:1em;
30+
height:1em;
31+
}
1332
}

apps/frontend/src/components/AuditsTable.tsx

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
flexRender,
66
ColumnDef,
77
getPaginationRowModel,
8+
SortingState,
9+
getSortedRowModel,
10+
SortDirection
811
} from "@tanstack/react-table";
912
import { SkeletonTable } from "./Skeleton";
1013
import { StyledButton } from "./StyledButton";
@@ -13,13 +16,19 @@ import { Scan } from "#src/routes/Audits.tsx";
1316
import { StyledLabeledInput } from "./StyledLabeledInput";
1417
import { formatId } from "../utils";
1518
import { Link } from "react-router-dom";
19+
import { FaArrowDown, FaArrowUp } from "react-icons/fa";
20+
import React from "react";
1621

1722
interface 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>

apps/frontend/src/components/StyledButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
66
interface ButtonProps extends React.PropsWithChildren {
77
variant?: string;
88
icon?: ReactNode;
9-
onClick: (e?:any) => Promise<void> | void;
9+
onClick: undefined | ((e?:any) => Promise<void> | void) ;
1010
label: string;
1111
showLabel?: boolean;
1212
disabled?: boolean;

0 commit comments

Comments
 (0)