Skip to content

Commit d3cff6d

Browse files
committed
Frontend: added table view to Audits screen
1 parent 9c5bff1 commit d3cff6d

6 files changed

Lines changed: 364 additions & 39 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@use "../global-styles/variables.module.scss";
2+
@use "../global-styles/fonts.scss";
3+
4+
.AuditsTable {
5+
margin-top:variables.$spacing;
6+
margin-bottom:calc(variables.$spacing*2);
7+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import styles from "./AuditsTable.module.scss";
2+
import {
3+
useReactTable,
4+
getCoreRowModel,
5+
flexRender,
6+
ColumnDef,
7+
getPaginationRowModel,
8+
} from "@tanstack/react-table";
9+
import { SkeletonTable } from "./Skeleton";
10+
import { StyledButton } from "./StyledButton";
11+
import { useMemo, useState } from "react";
12+
import { Audit, Scan } from "#src/routes/Audits.tsx";
13+
import { StyledLabeledInput } from "./StyledLabeledInput";
14+
15+
interface auditsTableProps {
16+
audits: Audit[];
17+
isLoading: boolean;
18+
}
19+
20+
export const AuditsTable = ({ audits, isLoading }: auditsTableProps) => {
21+
//console.log(audits);
22+
const [pagination, setPagination] = useState({
23+
pageIndex: 0,
24+
pageSize: 10,
25+
});
26+
27+
const columns = useMemo<ColumnDef<Audit>[]>(
28+
() => [
29+
{
30+
accessorKey: "name",
31+
header: "Name",
32+
cell: ({ getValue }) => {
33+
const name = getValue() as string;
34+
return name;
35+
},
36+
},
37+
{
38+
accessorKey: "interval",
39+
header: "Runs",
40+
cell: ({ getValue }) => {
41+
const interval = getValue() as any;
42+
return interval;
43+
},
44+
},
45+
{
46+
accessorKey: "created_at",
47+
header: "Created",
48+
cell: ({ getValue }) => {
49+
const created = getValue() as string;
50+
return shortDate(created);
51+
},
52+
},
53+
{
54+
accessorKey: "scans",
55+
id: "lastScan",
56+
header: "Last Scan",
57+
cell: ({ getValue }) => {
58+
const scans = getValue() as Scan[];
59+
if(!scans || scans.length == 0 || !scans[0].updated_at) return "N/A"
60+
return shortDate(scans[0].updated_at);
61+
},
62+
},
63+
{
64+
accessorKey: "urls_aggregate",
65+
header: "URLs",
66+
cell: ({ getValue }) => {
67+
const urls = getValue() as any;
68+
return urls.aggregate.count;
69+
},
70+
},
71+
{
72+
accessorKey: "scans",
73+
header: "Blockers",
74+
cell: ({ getValue }) => {
75+
const scans = getValue() as Scan[];
76+
return <b>{scans[0]?.blockers_aggregate?.aggregate?.count}</b>;
77+
},
78+
},
79+
],
80+
[]
81+
);
82+
83+
const table = useReactTable({
84+
data: audits || [],
85+
columns,
86+
getCoreRowModel: getCoreRowModel(),
87+
getPaginationRowModel: getPaginationRowModel(),
88+
state: {
89+
pagination,
90+
},
91+
onPaginationChange: setPagination,
92+
});
93+
94+
return (
95+
<div className={styles.AuditsTable}>
96+
{isLoading ? (
97+
<SkeletonTable
98+
columns={3}
99+
rows={3}
100+
headers={["Email", "Created At", "Actions"]}
101+
/>
102+
) : (
103+
<>
104+
<div className="table-container">
105+
<table aria-label="Audits table">
106+
<thead>
107+
{table.getHeaderGroups().map((headerGroup) => (
108+
<tr key={headerGroup.id} className="bg-gray-100">
109+
{headerGroup.headers.map((header) => (
110+
<th key={header.id} scope="col">
111+
{header.isPlaceholder
112+
? null
113+
: flexRender(
114+
header.column.columnDef.header,
115+
header.getContext()
116+
)}
117+
</th>
118+
))}
119+
</tr>
120+
))}
121+
</thead>
122+
<tbody>
123+
{table.getRowModel().rows.length === 0 ? (
124+
<tr>
125+
<td colSpan={columns.length}>No audits found</td>
126+
</tr>
127+
) : (
128+
table.getRowModel().rows.map((row, index) => (
129+
<tr
130+
key={row.id}
131+
className={index % 2 === 0 ? "bg-white" : "bg-gray-50"}
132+
>
133+
{row.getVisibleCells().map((cell) => (
134+
<td
135+
key={cell.id}
136+
className="border border-gray-300 px-4 py-2"
137+
>
138+
{flexRender(
139+
cell.column.columnDef.cell,
140+
cell.getContext()
141+
)}
142+
</td>
143+
))}
144+
</tr>
145+
))
146+
)}
147+
</tbody>
148+
</table>
149+
150+
{/* Pagination Controls */}
151+
<div
152+
className="pagination"
153+
role="navigation"
154+
aria-label="Pagination"
155+
>
156+
<div className="pagination-text">
157+
Showing {table.getState().pagination.pageSize} of{" "}
158+
{audits.length} Audits
159+
</div>
160+
<div className="pagination-buttons">
161+
{audits &&
162+
` Page ${table.getState().pagination.pageIndex + 1} of ${table.getPageCount()}`}
163+
164+
<StyledLabeledInput>
165+
<label htmlFor="pageSize">Audits per page:</label>
166+
<select
167+
id="pageSize"
168+
value={table.getState().pagination.pageSize}
169+
onChange={(e) => {
170+
table.setPageSize(Number(e.target.value));
171+
}}
172+
>
173+
<option value="10">10</option>
174+
<option value="50">50</option>
175+
<option value="100">100</option>
176+
</select>
177+
</StyledLabeledInput>
178+
179+
<button
180+
onClick={() => table.firstPage()}
181+
disabled={table.getState().pagination.pageIndex == 0}
182+
aria-label="Go to first page"
183+
>
184+
First
185+
</button>
186+
<button
187+
onClick={() => table.previousPage()}
188+
disabled={!table.getCanPreviousPage()}
189+
aria-label="Go to previous page"
190+
>
191+
Previous
192+
</button>
193+
<button
194+
onClick={() => table.nextPage()}
195+
disabled={!table.getCanNextPage()}
196+
aria-label="Go to next page"
197+
>
198+
Next
199+
</button>
200+
<button
201+
onClick={() => table.lastPage()}
202+
disabled={
203+
table.getState().pagination.pageIndex + 1 >=
204+
table.getPageCount()
205+
}
206+
aria-label="Go to last page"
207+
>
208+
Last
209+
</button>
210+
</div>
211+
</div>
212+
</div>
213+
</>
214+
)}
215+
</div>
216+
);
217+
};
218+
219+
220+
function shortDate(dateTime: string) {
221+
return new Date(dateTime).toLocaleDateString("en-US", {
222+
//weekday: "short",
223+
year: "numeric",
224+
month: "numeric",
225+
day: "numeric",
226+
});
227+
}

apps/frontend/src/components/StyledButton.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export const StyledButton = ({
2828
className = "",
2929
prependText = "",
3030
type = "button",
31-
inline = false
31+
inline = false,
32+
...props
3233
}: ButtonProps) => {
3334
const iconOnly = !showLabel ? styles["icon-only"] + " icon-only":"";
3435
const isDisabled = disabled || loading ? styles["disabled"]: "";
@@ -56,6 +57,7 @@ export const StyledButton = ({
5657
onClick={onClick}
5758
disabled={disabled || loading}
5859
aria-busy={loading}
60+
{...props}
5961
>
6062
{loading ? (
6163
<span className={styles["spinner"]} aria-hidden="true"></span>

apps/frontend/src/routes/Audits.module.scss

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
margin-bottom: calc(variables.$spacing);
77
line-height: 1.1em;
88
svg {
9-
color:variables.$black;
9+
color: variables.$black;
1010
}
1111
}
1212

@@ -20,4 +20,15 @@
2020
width: 100%;
2121
}
2222

23+
.audits-view-selector {
24+
display: inline-flex;
25+
width: 100%;
26+
align-content: flex-end;
27+
align-items: flex-end;
28+
justify-content: flex-end;
29+
30+
button[data-state="active"] {
31+
opacity:1;
32+
}
33+
}
2334
}

0 commit comments

Comments
 (0)