Skip to content

Commit

Permalink
Adds Search and Filter components
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbarnes committed Oct 28, 2024
1 parent 1995f87 commit cc2ad6e
Show file tree
Hide file tree
Showing 10 changed files with 1,927 additions and 3,259 deletions.
9 changes: 9 additions & 0 deletions front_end/stylesheets/search.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
// This hides the Django search form when JavaScript is enabled
#orp-django-search {
display: block;
}

.js-enabled #orp-django-search {
display: none;
}

.search-form {
.search-group {
margin-bottom: 30px;
Expand Down
622 changes: 317 additions & 305 deletions orp/orp_search/templates/orp.html

Large diffs are not rendered by default.

4,227 changes: 1,274 additions & 2,953 deletions package-lock.json

Large diffs are not rendered by default.

130 changes: 129 additions & 1 deletion react_front_end/src/App.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,135 @@
import { useState, useEffect } from 'react';
import { useQueryParams } from './hooks/useQueryParams';
import { Search } from './components/Search';
import { CheckboxFilter } from './components/CheckboxFilter';
import { AppliedFilters } from './components/AppliedFilters';

import { documentType, publisher } from './utils/filters';

const generateCheckedState = (checkboxes, queryValues) => checkboxes.map(({ name }) => queryValues.includes(name))

function App() {
const [searchQuery, setSearchQuery] = useQueryParams('search', []);
const [docTypeQuery, setDocTypeQuery] = useQueryParams('document_type', []);
const [publisherQuery, setPublisherQuery] = useQueryParams('publisher', []);

// Set initial checked state as array of booleans for checkboxes based on query params
const [documentTypeCheckedState, setDocumentTypeCheckedState] = useState(generateCheckedState(documentType, docTypeQuery));
const [publisherCheckedState, setPublisherCheckedState] = useState(generateCheckedState(publisher, publisherQuery));

const handleSearchChange = (event) => {
setSearchQuery([event.target.value]);
};

const handleDeleteFilter = (filterName, filter) => {
const updateQueryAndState = (query, setQuery, setCheckedState, data) => {
const updatedQuery = query.filter(item => item !== filter);
setQuery(updatedQuery);
setCheckedState(generateCheckedState(data, updatedQuery));
};

if (filterName === 'docType') {
updateQueryAndState(docTypeQuery, setDocTypeQuery, setDocumentTypeCheckedState, documentType);
} else if (filterName === 'publisher') {
updateQueryAndState(publisherQuery, setPublisherQuery, setPublisherCheckedState, publisher);
}
};

const fetchData = async (queryString) => {
try {
const response = await fetch(`/api/data?${queryString}`);
const data = await response.json();
console.log(data); // Handle the fetched data
} catch (error) {
console.error('Error fetching data:', error);
}
};

useEffect(() => {
const handler = setTimeout(() => {
if (searchQuery.length > 0 || docTypeQuery.length > 0 || publisherQuery.length > 0) {
const queryString = new URLSearchParams({
search: searchQuery.join(','),
document_type: docTypeQuery.join(','),
publisher: publisherQuery.join(',')
}).toString();

// fetchData(queryString);
console.log("Fetching data with query string:", queryString);
}
}, 300); // Adjust the delay as needed

return () => {
clearTimeout(handler);
};
}, [searchQuery, docTypeQuery, publisherQuery]);

return (
<h1>ORP React</h1>
<div className="govuk-grid-row search-form">
<div className="govuk-grid-column-one-third">
<Search handleSearchChange={handleSearchChange} searchQuery={searchQuery} />
<div className="govuk-form-group ">
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
Document type
</h2>
</legend>
<CheckboxFilter
checkboxData={documentType}
checkedState={documentTypeCheckedState}
setCheckedState={setDocumentTypeCheckedState}
setQueryParams={setDocTypeQuery}
/>
</fieldset>
</div>
<div className="govuk-form-group ">
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
Published by
</h2>
</legend>
<CheckboxFilter
checkboxData={publisher}
checkedState={publisherCheckedState}
setCheckedState={setPublisherCheckedState}
setQueryParams={setPublisherQuery}
/>
</fieldset>
</div>
</div>
<div className="govuk-grid-column-two-thirds">
<AppliedFilters
documentTypeCheckedState={documentTypeCheckedState}
publisherCheckedState={publisherCheckedState}
removeFilter={handleDeleteFilter}
/>
<Results
searchQuery={searchQuery}
docTypeQuery={docTypeQuery}
publisherQuery={publisherQuery}
/>
</div>
</div>
);
}


const Results = ({
searchQuery,
docTypeQuery,
publisherQuery
}) => (
<div>
<h2 className="govuk-heading-m">Results</h2>
<pre>
<p>Search query: {searchQuery.join(', ')}</p>
<p>Document type: {docTypeQuery.join(', ')}</p>
<p>Published by: {publisherQuery.join(', ')}</p>
</pre>
</div>
);


export default App;
33 changes: 33 additions & 0 deletions react_front_end/src/components/AppliedFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { documentType, publisher } from '../utils/filters';

const AppliedFilters = ({
documentTypeCheckedState,
publisherCheckedState,
removeFilter,
}) => {

// Combine checked document types and publishers into a single array
const checkedFilters = [
...documentTypeCheckedState
.map((item, index) => item ? { type: 'docType', ...documentType[index] } : null)
.filter(item => item !== null),
...publisherCheckedState
.map((item, index) => item ? { type: 'publisher', ...publisher[index] } : null)
.filter(item => item !== null)
];

return (
<ul className="orp-applied-filters-container">
{checkedFilters.map((filter, index) => (
<li className="orp-applied-filter-tag" key={index}>
<a href="#delete" onClick={() => removeFilter(filter.type, filter.name)}>
<span className="govuk-visually-hidden">Remove filter:</span>
<span className="govuk-body-s">{filter.label}</span>
</a>
</li>
))}
</ul>
);
}

export { AppliedFilters };
38 changes: 38 additions & 0 deletions react_front_end/src/components/CheckboxFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const CheckboxFilter = ({ checkboxData, checkedState, setCheckedState, setQueryParams }) => {

const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);

// Generate an array of the names of all checked checkboxes
const checkedItems = checkboxData
.filter((_, index) => updatedCheckedState[index])
.map(({ name }) => name);
console.log(checkedItems);

setQueryParams(checkedItems);
setCheckedState(updatedCheckedState);
}

return (
<div className="govuk-checkboxes govuk-checkboxes--small" data-module="govuk-checkboxes">
{checkboxData.map(({ name, label }, index) => (
<div className="govuk-checkboxes__item" key={index}>
<input
className="govuk-checkboxes__input"
type="checkbox"
id={`${name}-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={`${name}-${index}`}>{label}</label>
</div>
))}
</div>
)
};

export { CheckboxFilter };
Empty file.
22 changes: 22 additions & 0 deletions react_front_end/src/components/Search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Search = ({ handleSearchChange, searchQuery }) => {
return (
<div className="govuk-form-group search-group">
<label className="govuk-label" htmlFor="search">
Search
</label>
<div className="search-input-button">
<input
id="search"
className="govuk-input"
name="search"
type="search"
onChange={handleSearchChange}
value={searchQuery}
/>
<button type="submit" className="search__button"></button>
</div>
</div>
)
};

export { Search };
42 changes: 42 additions & 0 deletions react_front_end/src/hooks/useQueryParams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState } from 'react';

const getQuery = () => {
if (typeof window !== 'undefined') {
return new URLSearchParams(window.location.search);
}
return new URLSearchParams();
};

const getQueryStringVal = key => {
return getQuery().getAll(key);
};

const useQueryParams = (key, defaultVal = []) => {
const [query, setQuery] = useState(getQueryStringVal(key).length ? getQueryStringVal(key) : defaultVal);

const updateUrl = (newVals) => {
setQuery(newVals);

const query = getQuery();

// Clear existing values for the key
query.delete(key);

// Set new values for the key
newVals.forEach(val => {
if (val.trim() !== '') {
query.append(key, val);
}
});

if (typeof window !== 'undefined') {
const { protocol, pathname, host } = window.location;
const newUrl = `${protocol}//${host}${pathname}?${query.toString()}`;
window.history.pushState({}, '', newUrl);
}
};

return [query, updateUrl];
};

export { useQueryParams };
63 changes: 63 additions & 0 deletions react_front_end/src/utils/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// These should come from the API/Django backend, but for now they are hardcoded

export const documentType = [
{
name: "legislation",
label: "Legislation"
},
{
name: "guidance",
label: "Guidance"
},
{
name: "standards",
label: "British Standards"
},
];

export const publisher = [
{
name: "healthandsafetyexecutive",
label: "Health and Safety Executive"
},
{
name: "civilaviationauthority",
label: "Civil Aviation Authority"
},
{
name: "environmentagency",
label: "Environment Agency"
},
{
name: "defra",
label: "Defra"
},
{
name: "officeofgasandelectricitymarkets",
label: "Office of Gas and Electricity Markets"
},
{
name: "officeofrailandroad",
label: "Office of Rail and Road"
},
{
name: "naturalengland",
label: "Natural England"
},
{
name: "historicengland",
label: "Historic England"
},
{
name: "nationalhighways",
label: "National Highways"
},
{
name: "homesengland",
label: "Homes England"
},
{
name: "departmentfortransport",
label: "Department for Transport"
},
]

0 comments on commit cc2ad6e

Please sign in to comment.