Skip to content

Commit bcb9abc

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 03c63b8 commit bcb9abc

File tree

2 files changed

+125
-10
lines changed

2 files changed

+125
-10
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: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Head from "next/head";
55
import { weatherTemplate, getWeatherIndex } from "../components/weatherTemplate";
66
import { OverlayPanel } from 'primereact/overlaypanel';
77
import MaintainerMapping from "../maintainers.yml";
8+
import { basePath } from "../next.config.js";
9+
import { SearchForm } from "../components/searchForm";
810

911

1012
export default function Home() {
@@ -13,6 +15,7 @@ export default function Home() {
1315
const [rows, setRows] = useState([]);
1416
const [expandedRows, setExpandedRows] = useState([]);
1517
const [requiredFilter, setRequiredFilter] = useState(false);
18+
const [keepSearch, setKeepSearch] = useState(true);
1619

1720
useEffect(() => {
1821
const fetchData = async () => {
@@ -52,13 +55,51 @@ export default function Home() {
5255
filteredJobs = filteredJobs.filter((job) => job.required);
5356
}
5457
return filteredJobs;
58+
}
59+
60+
// Filters the jobs s.t. all values must be contained in the name.
61+
const matchAll = (filteredJobs, values) => {
62+
return filteredJobs.filter((job) => {
63+
const jobName = job.name.toLowerCase();
64+
return values.every((val) => {
65+
const decodedValue = decodeURIComponent(val).toLowerCase();
66+
return jobName.includes(decodedValue);
67+
});
68+
});
69+
};
70+
71+
// Filters the jobs s.t. at least one value must be contained in the name.
72+
const matchAny = (filteredJobs, values) => {
73+
return filteredJobs.filter((job) => {
74+
const jobName = job.name.toLowerCase();
75+
return values.some((val) => {
76+
const decodedValue = decodeURIComponent(val).toLowerCase();
77+
return jobName.includes(decodedValue);
78+
});
79+
});
80+
};
81+
82+
//Filter based on the URL.
83+
const filterURL = (filteredJobs) => {
84+
const urlParams = new URLSearchParams(window.location.search);
85+
switch(urlParams.get("matchMode")) {
86+
case "and":
87+
filteredJobs = matchAll(filteredJobs, urlParams.getAll("value"));
88+
break;
89+
case "or":
90+
filteredJobs = matchAny(filteredJobs, urlParams.getAll("value"));
91+
break;
92+
default:
93+
break;
94+
}
95+
return filteredJobs;
5596
};
5697

5798
useEffect(() => {
5899
setLoading(true);
59100

60-
// Filter based on required tag.
61101
let filteredJobs = filterRequired(jobs);
102+
filteredJobs = filterURL(filteredJobs);
62103

63104
//Set the rows for the table.
64105
setRows(
@@ -87,8 +128,8 @@ export default function Home() {
87128
setExpandedRows(updatedExpandedRows);
88129
};
89130

90-
const buttonClass = (active) => `tab md:px-4 px-2 py-2 border-2
91-
${active ? "border-blue-500 bg-blue-500 text-white"
131+
const buttonClass = (active) => `tab md:px-4 px-2 py-2 border-2
132+
${active ? "border-blue-500 bg-blue-500 text-white"
92133
: "border-gray-300 bg-white hover:bg-gray-100"}`;
93134

94135

@@ -251,6 +292,39 @@ export default function Home() {
251292
);
252293
};
253294

295+
// Apply search terms to the URL and reload the page.
296+
const handleSearch= (e) => {
297+
// Prevent the default behavior so that we can keep search terms.
298+
e.preventDefault();
299+
const matchMode = e.target.matchMode.value;
300+
const value = e.target.value.value.trimEnd();
301+
if (value) {
302+
// Append the new matchMode regardless of if search terms were kept.
303+
const path = new URLSearchParams();
304+
path.append("matchMode", matchMode);
305+
if (keepSearch) {
306+
// If keepSearch is true, add existing parameters in the URL.
307+
const urlParams = new URLSearchParams(window.location.search);
308+
urlParams.getAll("value").forEach((val) => {
309+
path.append("value", val);
310+
});
311+
}
312+
//Add the search term from the form and redirect.
313+
path.append("value", value);
314+
window.location.assign(`${basePath}/?${path.toString()}`);
315+
}
316+
};
317+
318+
// Clear the search parameters, but only if they exist.
319+
const clearSearch = () => {
320+
const urlParts = window.location.href.split("?");
321+
if(urlParts[1] !== undefined){
322+
window.location.assign(urlParts[0]);
323+
}
324+
}
325+
326+
327+
254328
const renderTable = () => (
255329
<DataTable
256330
value={rows}
@@ -315,14 +389,33 @@ export default function Home() {
315389
"m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
316390
}
317391
>
318-
<button
319-
className={buttonClass(requiredFilter)}
320-
onClick={() => setRequiredFilter(!requiredFilter)}>
321-
Required Jobs Only
322-
</button>
323-
<div className="mt-4 text-lg">Total Rows: {rows.length}</div>
392+
393+
<div className="space-x-2 mx-auto">
394+
<button
395+
className={buttonClass(requiredFilter)}
396+
onClick={() => setRequiredFilter(!requiredFilter)}>
397+
Required Jobs Only
398+
</button>
399+
<button
400+
className={buttonClass()}
401+
onClick={() => clearSearch()}>
402+
Clear Search
403+
</button>
404+
<button
405+
className={buttonClass(keepSearch)}
406+
onClick={() => setKeepSearch(!keepSearch)}>
407+
Keep URL Search Terms
408+
</button>
409+
</div>
410+
411+
<SearchForm handleSearch={handleSearch} />
412+
413+
<div className="mt-1 text-center md:text-lg text-base">
414+
Total Rows: {rows.length}
415+
</div>
416+
324417
<div>{renderTable()}</div>
325418
</main>
326419
</div>
327420
);
328-
}
421+
}

0 commit comments

Comments
 (0)