Skip to content

Commit fe876d0

Browse files
committed
dashboard: Add search box and link to URL
Adds an input form for searching job names. Searches are appended to the URL. Fixes #4 Signed-off-by: Anna Finn <[email protected]>
1 parent 8a4e4bb commit fe876d0

File tree

2 files changed

+140
-12
lines changed

2 files changed

+140
-12
lines changed

components/searchForm.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const SearchForm = ({ handleSearch }) => {
2+
return (
3+
<div className="flex flex-col items-center md:text-base text-xs">
4+
<div className="flex min-[1126px]:justify-end justify-center w-full">
5+
<form className="p-2 bg-gray-700 rounded-md flex flex-row" onSubmit={(e) => handleSearch(e)}>
6+
<div>
7+
<label className="block text-white">Match Mode:</label>
8+
<select name="matchMode" className="px-1 h-fit rounded-lg">
9+
<option value="or">Match Any</option>
10+
<option value="and">Match All</option>
11+
</select>
12+
</div>
13+
<div className="mx-2">
14+
<label className="block text-white">Search Text:</label>
15+
<input type="text" name="value" required></input>
16+
</div>
17+
<button type="submit" className="bg-blue-500 text-white px-4 rounded-3xl">Submit</button>
18+
</form>
19+
</div>
20+
</div>
21+
);
22+
};

pages/index.js

Lines changed: 118 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import { DataTable } from "primereact/datatable";
33
import { Column } from "primereact/column";
44
import Head from "next/head";
55
import { weatherTemplate, getWeatherIndex } from "../components/weatherTemplate";
6+
import { basePath } from "../next.config.js";
7+
import { SearchForm } from "../components/searchForm";
68

79

810
export default function Home() {
9-
const [loading, setLoading] = useState(true);
10-
const [jobs, setJobs] = useState([]);
11-
const [rows, setRows] = useState([]);
12-
const [expandedRows, setExpandedRows] = useState([]);
11+
const [loading, setLoading] = useState(true);
12+
const [jobs, setJobs] = useState([]);
13+
const [rows, setRows] = useState([]);
14+
const [expandedRows, setExpandedRows] = useState([]);
15+
const [keepSearch, setKeepSearch] = useState(true);
1316

1417
useEffect(() => {
1518
const fetchData = async () => {
@@ -42,15 +45,62 @@ export default function Home() {
4245
fetchData();
4346
}, []);
4447

48+
// Filters the jobs s.t. all values must be contained in the name.
49+
const matchAll = (filteredJobs, values) => {
50+
return filteredJobs.filter((job) => {
51+
const jobName = job.name.toLowerCase();
52+
return values.every((val) => {
53+
const decodedValue = decodeURIComponent(val).toLowerCase();
54+
return jobName.includes(decodedValue);
55+
});
56+
});
57+
};
58+
59+
// Filters the jobs s.t. at least one value must be contained in the name.
60+
const matchAny = (filteredJobs, values) => {
61+
return filteredJobs.filter((job) => {
62+
const jobName = job.name.toLowerCase();
63+
return values.some((val) => {
64+
const decodedValue = decodeURIComponent(val).toLowerCase();
65+
return jobName.includes(decodedValue);
66+
});
67+
});
68+
};
69+
70+
//Filter based on the URL.
71+
const filterURL = (filteredJobs) => {
72+
const urlParams = new URLSearchParams(window.location.search);
73+
switch(urlParams.get("matchMode")) {
74+
case "and":
75+
filteredJobs = matchAll(filteredJobs, urlParams.getAll("value"));
76+
break;
77+
case "or":
78+
filteredJobs = matchAny(filteredJobs, urlParams.getAll("value"));
79+
break;
80+
default:
81+
break;
82+
}
83+
return filteredJobs;
84+
};
85+
4586
useEffect(() => {
4687
setLoading(true);
4788

48-
// Create rows to set into table.
49-
const rows = jobs.map((job) => ({
50-
...job,
51-
weather: getWeatherIndex(job),
52-
}));
53-
setRows(rows);
89+
//Filter based on the URL.
90+
let filteredJobs = filterURL(jobs);
91+
92+
//Set the rows for the table.
93+
setRows(
94+
filteredJobs.map((job) => ({
95+
name : job.name,
96+
runs : job.runs,
97+
fails : job.fails,
98+
skips : job.skips,
99+
required : job.required,
100+
weather : getWeatherIndex(job),
101+
}))
102+
);
103+
54104
setLoading(false);
55105
}, [jobs]);
56106

@@ -67,6 +117,11 @@ export default function Home() {
67117
setExpandedRows(updatedExpandedRows);
68118
};
69119

120+
const buttonClass = (active) => `tab md:px-4 px-2 py-2 border-2
121+
${active ? "border-blue-500 bg-blue-500 text-white"
122+
: "border-gray-300 bg-white hover:bg-gray-100"}`;
123+
124+
70125
// Template for rendering the Name column as a clickable item
71126
const nameTemplate = (rowData) => {
72127
return (
@@ -121,6 +176,39 @@ export default function Home() {
121176
);
122177
};
123178

179+
// Apply search terms to the URL and reload the page.
180+
const handleSearch= (e) => {
181+
// Prevent the default behavior so that we can keep search terms.
182+
e.preventDefault();
183+
const matchMode = e.target.matchMode.value;
184+
const value = e.target.value.value.trimEnd();
185+
if (value) {
186+
// Append the new matchMode regardless of if search terms were kept.
187+
const path = new URLSearchParams();
188+
path.append("matchMode", matchMode);
189+
if (keepSearch) {
190+
// If keepSearch is true, add existing parameters in the URL.
191+
const urlParams = new URLSearchParams(window.location.search);
192+
urlParams.getAll("value").forEach((val) => {
193+
path.append("value", val);
194+
});
195+
}
196+
//Add the search term from the form and redirect.
197+
path.append("value", value);
198+
window.location.assign(`${basePath}/?${path.toString()}`);
199+
}
200+
};
201+
202+
// Clear the search parameters, but only if they exist.
203+
const clearSearch = () => {
204+
const urlParts = window.location.href.split("?");
205+
if(urlParts[1] !== undefined){
206+
window.location.assign(urlParts[0]);
207+
}
208+
}
209+
210+
211+
124212
const renderTable = () => (
125213
<DataTable
126214
value={rows}
@@ -185,9 +273,27 @@ export default function Home() {
185273
"m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
186274
}
187275
>
188-
<div className="mt-4 text-lg">Total Rows: {rows.length}</div>
276+
<div className="space-x-2 mx-auto">
277+
<button
278+
className={buttonClass()}
279+
onClick={() => clearSearch()}>
280+
Clear Search
281+
</button>
282+
<button
283+
className={buttonClass(keepSearch)}
284+
onClick={() => setKeepSearch(!keepSearch)}>
285+
Keep URL Search Terms
286+
</button>
287+
</div>
288+
289+
<SearchForm handleSearch={handleSearch} />
290+
291+
<div className="mt-1 text-center md:text-lg text-base">
292+
Total Rows: {rows.length}
293+
</div>
294+
189295
<div>{renderTable()}</div>
190296
</main>
191297
</div>
192298
);
193-
}
299+
}

0 commit comments

Comments
 (0)