Skip to content

Commit 04e6d70

Browse files
committed
Frontend: added audits listing search, filter by user, empty state card
1 parent 830c6f4 commit 04e6d70

5 files changed

Lines changed: 156 additions & 52 deletions

File tree

apps/frontend/src/components/AuditsTable.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,24 @@ import {
99
import { SkeletonTable } from "./Skeleton";
1010
import { StyledButton } from "./StyledButton";
1111
import { useMemo, useState } from "react";
12-
import { Audit, Scan } from "#src/routes/Audits.tsx";
12+
import { Scan } from "#src/routes/Audits.tsx";
1313
import { StyledLabeledInput } from "./StyledLabeledInput";
1414
import { formatId } from "../utils";
1515
import { Link } from "react-router-dom";
1616

17+
interface Audit {
18+
created_at: string;
19+
id: string;
20+
interval: string;
21+
name: string;
22+
scans: Scan[];
23+
urls_aggregate: {
24+
aggregate: {
25+
count: number;
26+
};
27+
};
28+
}
29+
1730
interface auditsTableProps {
1831
audits: Audit[];
1932
isLoading: boolean;

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

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
.card {
55
border-radius: variables.$spacing;
66
padding: calc(variables.$spacing * 3) calc(variables.$spacing * 2);
7-
h1,h2,h3 {
8-
margin:0;
7+
h1,
8+
h2,
9+
h3 {
10+
margin: 0;
911
@include fonts.font-size-normal;
1012
display: flex;
1113
svg {
1214
margin-right: variables.$spacing;
1315
}
14-
}
16+
}
1517
.text-small {
1618
@include fonts.font-size-small;
1719
}
1820
&.blockers-chart {
19-
padding:calc(variables.$spacing*3)
21+
padding: calc(variables.$spacing * 3);
2022
}
2123
&.dark {
2224
background: variables.$black;
@@ -26,13 +28,37 @@
2628
background: variables.$white;
2729
border: 1px solid variables.$gray;
2830
}
31+
&.empty-state-card {
32+
background: variables.$white;
33+
border: 1px solid variables.$gray;
34+
width: 100% !important;
35+
margin-top: calc(variables.$spacing * 2);
36+
display: flex;
37+
padding: calc(variables.$spacing * 8) calc(variables.$spacing * 4);
38+
gap: calc(variables.$spacing);
39+
justify-content: space-evenly;
40+
align-items: flex-start;
41+
h2 {
42+
padding: 0;
43+
max-width: calc(variables.$spacing * 64);
44+
}
45+
p {
46+
margin: 0;
47+
max-width: calc(variables.$spacing * 64);
48+
}
49+
svg {
50+
min-width: calc(variables.$spacing * 8);
51+
min-height: calc(variables.$spacing * 8);
52+
fill: variables.$black-translucent;
53+
}
54+
}
2955
&.inset-light {
30-
margin-top:variables.$spacing;
56+
margin-top: variables.$spacing;
3157
background: variables.$paper;
3258
border: 1px solid variables.$gray;
3359
padding: calc(variables.$spacing * 2) calc(variables.$spacing * 2);
34-
35-
h3{
60+
61+
h3 {
3662
border-bottom: 2px solid variables.$black;
3763
}
3864
select {
@@ -41,34 +67,34 @@
4167
}
4268
&.short-error {
4369
background: variables.$red;
44-
color:variables.$white;
70+
color: variables.$white;
4571
display: inline-flex;
4672
align-items: center;
47-
padding: calc(variables.$spacing*2) calc(variables.$spacing*2);
48-
margin-top: calc(variables.$spacing*2);
73+
padding: calc(variables.$spacing * 2) calc(variables.$spacing * 2);
74+
margin-top: calc(variables.$spacing * 2);
4975
line-height: 1em;
5076
b {
5177
display: block;
5278
}
5379
svg {
54-
margin-right:variables.$spacing;
80+
margin-right: variables.$spacing;
5581
}
5682
}
5783
&.short-success {
5884
background: transparent;
59-
85+
6086
border: 1px solid variables.$green;
61-
color:variables.$black;
87+
color: variables.$black;
6288
display: inline-flex;
6389
align-items: center;
64-
padding: calc(variables.$spacing*2) calc(variables.$spacing*2);
65-
margin-top: calc(variables.$spacing*2);
90+
padding: calc(variables.$spacing * 2) calc(variables.$spacing * 2);
91+
margin-top: calc(variables.$spacing * 2);
6692
line-height: 1em;
6793
b {
6894
display: block;
6995
}
7096
svg {
71-
margin-right:variables.$spacing;
97+
margin-right: variables.$spacing;
7298
}
7399
}
74100
&.red {
@@ -79,10 +105,8 @@
79105
color: variables.$paper;
80106
}
81107
&.short {
82-
height: calc(variables.$spacing*15);
108+
height: calc(variables.$spacing * 15);
83109
align-items: center;
84110
display: flex;
85111
}
86112
}
87-
88-

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@
2020
width: 100%;
2121
}
2222

23+
.filter-controls {
24+
display: flex;
25+
justify-content: space-between;
26+
margin-bottom: calc(variables.$spacing*2);
27+
}
28+
.filter-controls-left {
29+
display: flex;
30+
}
2331
.audits-view-selector {
2432
display: inline-flex;
25-
width: 100%;
2633
align-content: flex-end;
2734
align-items: flex-end;
2835
justify-content: flex-end;

apps/frontend/src/routes/Audits.tsx

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { SkeletonAuditGrid } from "#src/components/Skeleton.tsx";
1212
import { AuditsTable } from "#src/components/AuditsTable.tsx";
1313
import * as Tabs from "@radix-ui/react-tabs";
1414
import { FaTable, FaTableList } from "react-icons/fa6";
15+
import { StyledLabeledInput } from "#src/components/StyledLabeledInput.tsx";
16+
import { ChangeEvent, useState } from "react";
17+
import { useUser } from "../queries";
18+
import { TbHelpCircleFilled } from "react-icons/tb";
1519

1620
export interface Scan {
1721
blockers_aggregate: {
@@ -38,12 +42,19 @@ export interface Audit {
3842

3943
export const Audits = () => {
4044
const navigate = useNavigate();
41-
const {auditsTableView, setAuditsTableView} = useGlobalStore();
45+
const { data: user } = useUser();
46+
const {
47+
auditsTableView,
48+
setAuditsTableView,
49+
auditsTableCreatedByView,
50+
setAuditsTableCreatedByView
51+
} = useGlobalStore();
52+
const [searchString, setSearchString] = useState("");
4253
const { data: audits, isLoading } = useQuery({
4354
queryKey: ["audits"],
4455
queryFn: async () =>
4556
(
46-
await apiClient.graphql({
57+
await (apiClient.graphql({
4758
//query: `{audits(order_by: {created_at: desc}) {id created_at name}}`,
4859
query: `
4960
{
@@ -74,9 +85,25 @@ export const Audits = () => {
7485
}
7586
`,
7687
})
77-
)?.data?.audits,
88+
) as any).data?.audits,
89+
select: (audits) => {
90+
let filtered = audits;
91+
if (auditsTableCreatedByView == "user") {
92+
filtered = audits.filter((audit: any) => audit.user.email === user.email);
93+
}
94+
if (searchString.length >= 3) {
95+
filtered = filtered.filter((audit: any) => audit.name.includes(searchString))
96+
}
97+
return filtered;
98+
}
99+
78100
});
79-
console.log(audits);
101+
//console.log(audits);
102+
103+
const handleCreatedByViewChange = (e: ChangeEvent<HTMLSelectElement>) => {
104+
setAuditsTableCreatedByView(e.target.value);
105+
};
106+
80107
return (
81108
<div className={styles.Audits}>
82109
<div>
@@ -100,31 +127,48 @@ export const Audits = () => {
100127
</div>
101128
</div>
102129
<>
130+
103131
<Tabs.Root
104132
orientation="horizontal"
105133
className="audit-tabs"
106134
value={auditsTableView}
107-
onValueChange={(value)=>setAuditsTableView(value)}
135+
onValueChange={(value) => setAuditsTableView(value)}
108136
activationMode="manual"
109137
>
110-
<Tabs.List aria-label="Select a View" className={styles["audits-view-selector"]}>
111-
<Tabs.Trigger value="cards" className={styles["audits-view-trigger"]} asChild>
112-
<StyledButton
113-
icon={<FaTable />}
114-
onClick={() => {}}
115-
label={"Cards View"}
116-
showLabel={false}
117-
/>
118-
</Tabs.Trigger>
119-
<Tabs.Trigger value="table" className={styles["audits-view-trigger"]} asChild>
120-
<StyledButton
121-
icon={<FaTableList />}
122-
onClick={() => {}}
123-
label={"Cards View"}
124-
showLabel={false}
125-
/>
126-
</Tabs.Trigger>
127-
</Tabs.List>
138+
<div className={styles["filter-controls"]}>
139+
<div className={styles["filter-controls-left"]}>
140+
<StyledLabeledInput>
141+
<label htmlFor="auditsSearch">Search</label>
142+
<input id="auditsSearch" type="text" value={searchString} onChange={(e) => setSearchString(e.target.value)}></input>
143+
</StyledLabeledInput>
144+
<StyledLabeledInput>
145+
<label htmlFor="createdByView">Filter View:</label>
146+
<select onChange={handleCreatedByViewChange} value={auditsTableCreatedByView} id="createdByView">
147+
<option value="user">My Audits</option>
148+
<option value="all">All Audits</option>
149+
</select>
150+
</StyledLabeledInput>
151+
</div>
152+
153+
<Tabs.List aria-label="Select a View" className={styles["audits-view-selector"]}>
154+
<Tabs.Trigger value="cards" className={styles["audits-view-trigger"]} asChild>
155+
<StyledButton
156+
icon={<FaTable />}
157+
onClick={() => { }}
158+
label={"Cards View"}
159+
showLabel={false}
160+
/>
161+
</Tabs.Trigger>
162+
<Tabs.Trigger value="table" className={styles["audits-view-trigger"]} asChild>
163+
<StyledButton
164+
icon={<FaTableList />}
165+
onClick={() => { }}
166+
label={"Cards View"}
167+
showLabel={false}
168+
/>
169+
</Tabs.Trigger>
170+
</Tabs.List>
171+
</div>
128172
<Tabs.Content value="cards">
129173
<div className="cards-33">
130174
{isLoading ? (
@@ -163,8 +207,8 @@ export const Audits = () => {
163207
the_value={
164208
row.scans[0]
165209
? prettyDate(row.scans[0].updated_at) +
166-
" at " +
167-
prettyTime(row.scans[0].updated_at)
210+
" at " +
211+
prettyTime(row.scans[0].updated_at)
168212
: "Not scanned yet"
169213
}
170214
/>
@@ -179,6 +223,15 @@ export const Audits = () => {
179223
</Card>
180224
))
181225
)}
226+
{audits && audits.length == 0 && !isLoading && searchString == "" && <>
227+
<Card variant="empty-state-card">
228+
<TbHelpCircleFilled />
229+
<div className="text">
230+
<h2>It looks like you haven’t created any accessibility audits yet.</h2>
231+
<p>Jump right in by <Link to="build/">creating your first audit</Link> or explore <a style={{ cursor: "pointer" }} onClick={() => setAuditsTableCreatedByView("all")}>existing reports</a> from across your organization to see what’s already being improved.</p>
232+
</div>
233+
</Card>
234+
</>}
182235
</div>
183236
</Tabs.Content>
184237
<Tabs.Content value="table">

apps/frontend/src/utils/useGlobalStore.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,27 @@ interface EqualifyState {
1010
setLoading: (val: string | boolean) => void;
1111
darkMode: boolean;
1212
setDarkMode: (val: boolean) => void;
13-
auditsTableView: string;
14-
setAuditsTableView: (val: string) => void;
15-
blockersTableView: string;
16-
setBlockersTableView: (val: string) => void;
1713
authenticated: boolean;
1814
setAuthenticated: (val: boolean) => void;
1915
ssoAuthenticated: boolean;
2016
setSsoAuthenticated: (val: boolean) => void;
17+
// audits listing screen
18+
auditsTableView: string;
19+
setAuditsTableView: (val: string) => void;
20+
auditsTableCreatedByView: string;
21+
setAuditsTableCreatedByView: (val:string) => void;
22+
// blockers table
23+
blockersTableView: string;
24+
setBlockersTableView: (val: string) => void;
25+
blockerTableColumnVisibility: VisibilityState;
26+
setBlockerTableColumnVisibility: OnChangeFn<VisibilityState>;
27+
// screen reader announcer
2128
announceMessage: string;
2229
setAnnounceMessage: (
2330
val: string,
2431
style?: "normal" | "success" | "error",
2532
screenReaderOnly?: boolean,
2633
) => void;
27-
blockerTableColumnVisibility: VisibilityState;
28-
setBlockerTableColumnVisibility: OnChangeFn<VisibilityState>;
2934
}
3035

3136
export const useGlobalStore = create<EqualifyState>()(
@@ -37,6 +42,8 @@ export const useGlobalStore = create<EqualifyState>()(
3742
setDarkMode: (val) => set(() => ({ darkMode: val })),
3843
auditsTableView: "cards",
3944
setAuditsTableView: (val) => set(() => ({ auditsTableView: val })),
45+
auditsTableCreatedByView: "user",
46+
setAuditsTableCreatedByView: (val) => set(() => ({auditsTableCreatedByView : val})),
4047
blockersTableView: "summary",
4148
setBlockersTableView: (val) => set(() => ({ blockersTableView: val })),
4249
authenticated: false,

0 commit comments

Comments
 (0)