Skip to content

Commit

Permalink
Implement 2 styles of text filtering (#1851)
Browse files Browse the repository at this point in the history
  • Loading branch information
entrotech authored Sep 19, 2024
1 parent cb24fc9 commit efef6b4
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 51 deletions.
100 changes: 100 additions & 0 deletions client/src/components/Projects/ColumnHeaderPopups/MultiSelectText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import SearchIcon from "../../../images/search.png";
import { createUseStyles } from "react-jss";

const useStyles = createUseStyles({
searchBarWrapper: {
position: "relative",
alignSelf: "center",
marginBottom: "0.5rem"
},
searchBar: {
maxWidth: "100%",
width: "20em",
padding: "12px 12px 12px 48px",
marginRight: "0.5rem"
},
searchIcon: {
position: "absolute",
left: "16px",
top: "14px"
},
listItem: {
display: "flex",
flexDirection: "row",
alignItems: "center",
height: "2rem",
"&:hover": {
backgroundColor: "lightblue"
}
}
});

const MultiSelectText = ({ options, selectedOptions, setSelectedOptions }) => {
const classes = useStyles();
const [searchString, setSearchString] = useState("");

const filteredOptions = options
.filter(o => !!o)
.filter(opt => opt.includes(searchString));

const onChangeSearchString = e => {
setSearchString(e.target.value);
};

const handleCheckboxChange = o => {
if (selectedOptions.find(so => so.value == o)) {
setSelectedOptions(
selectedOptions.filter(selectedOption => selectedOption.value !== o)
);
} else {
selectedOptions.push({ value: o, label: o });
setSelectedOptions(selectedOptions);
}
};

return (
<>
<div className={classes.searchBarWrapper}>
<input
type="text"
value={searchString}
onChange={onChangeSearchString}
placeholder="Search"
className={classes.searchBar}
/>
<img
className={classes.searchIcon}
src={SearchIcon}
alt="Search Icon"
/>
</div>

<div style={{ overflow: "scroll", maxHeight: "15rem" }}>
{/* <pre>{JSON.stringify(selectedOptions, null, 2)}</pre>
<pre>{JSON.stringify(options, null, 2)}</pre> */}

{filteredOptions.map(o => (
<div key={o} className={classes.listItem}>
<input
style={{ height: "1.5rem" }}
type="checkbox"
checked={selectedOptions.find(option => option.value == o)}
onChange={() => handleCheckboxChange(o)}
/>
<span>{o}</span>
</div>
))}
</div>
</>
);
};

MultiSelectText.propTypes = {
options: PropTypes.any,
selectedOptions: PropTypes.any,
setSelectedOptions: PropTypes.func
};

export default MultiSelectText;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import VisibilityPopup from "./VisibilityPopup";
import StatusPopup from "./StatusPopup";

const ProjectTableColumnHeader = ({
uniqueValues,
projects,
filter,
header,
criteria,
setCriteria,
Expand All @@ -23,6 +24,7 @@ const ProjectTableColumnHeader = ({
<div style={{ width: "100%", height: "100%" }}>
{header.id !== "checkAllProjects" && header.id !== "contextMenu" ? (
<Popup
lockScroll={true}
trigger={
<div
style={{
Expand Down Expand Up @@ -71,7 +73,6 @@ const ProjectTableColumnHeader = ({
/>
) : header.popupType === "text" ? (
<TextPopup
selectOptions={uniqueValues}
close={close}
header={header}
criteria={criteria}
Expand All @@ -81,6 +82,8 @@ const ProjectTableColumnHeader = ({
setSort={setSort}
setCheckedProjectIds={setCheckedProjectIds}
setSelectAllChecked={setSelectAllChecked}
projects={projects}
filter={filter}
/>
) : header.popupType === "visibility" ? (
<VisibilityPopup
Expand Down Expand Up @@ -117,7 +120,8 @@ const ProjectTableColumnHeader = ({
};

ProjectTableColumnHeader.propTypes = {
uniqueValues: PropTypes.any,
projects: PropTypes.any,
filter: PropTypes.func,
header: PropTypes.any,
criteria: PropTypes.any,
setCriteria: PropTypes.func,
Expand Down
88 changes: 52 additions & 36 deletions client/src/components/Projects/ColumnHeaderPopups/TextPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import RadioButton from "../../UI/RadioButton";
import "react-datepicker/dist/react-datepicker.css";
import { MdClose } from "react-icons/md";
import SearchIcon from "../../../images/search.png";
import UniversalSelect from "../../UI/UniversalSelect.jsx";
import Select from "react-select";
import MultiSelectText from "./MultiSelectText";

const TextPopup = ({
selectOptions,
projects,
filter,
close,
header,
criteria,
Expand All @@ -22,10 +24,24 @@ const TextPopup = ({
const [newOrder, setNewOrder] = useState(
header.id !== orderBy ? null : order
);
const [newSearchString, setNewSearchString] = useState(criteria[header.id]);
const [selectedListItems, setSelectedListItems] = useState(
criteria[header.id + "List"].map(s => ({ value: s, label: s }))
);

// To build the drop-down list, we want to apply all the criteria that
// are currently selected EXCEPT the criteria we are currently editing.
const listCriteria = { ...criteria, [header.id + "List"]: [] };
const filteredProjects = projects.filter(p => filter(p, listCriteria));
const property = header.id == "author" ? "fullname" : header.id;
const selectOptions = [...new Set(filteredProjects.map(p => p[property]))]
.filter(value => value !== null)
.sort();

const applyChanges = () => {
setCriteria({ ...criteria, [header.id]: newSearchString });
setCriteria({
...criteria,
[header.id + "List"]: selectedListItems.map(sli => sli.value)
});
if (newOrder) {
setSort(header.id, newOrder);
}
Expand All @@ -35,7 +51,7 @@ const TextPopup = ({
};

const setDefault = () => {
setNewSearchString("");
setSelectedListItems([]);
setCheckedProjectIds([]);
setSelectAllChecked(false);
};
Expand Down Expand Up @@ -81,36 +97,35 @@ const TextPopup = ({
/>
<hr style={{ width: "100%" }} />
</div>
{/* <div>
<ul>
{textAllCurrentProjects.map((text, index) => (
<li key={index}>{text}</li>
))}
</ul>
</div> */}
{/* <input
type="text"
placeholder="Search by Partial Text"
onChange={e => {
setNewSearchString(e.target.value);
}}
value={newSearchString}
/> */}
<UniversalSelect
options={selectOptions.map(text => ({
value: text,
label: text
}))}
name="inputName"
disabled={false}
onChange={e => {
setNewSearchString(e.target.value);
}}
value={newSearchString}
defaultValue={newSearchString}
styles={{ maxHeight: 200 }}
placeholder={placeholderComponent}
></UniversalSelect>
{/* TODO: This is currently implemented differently for the address column than the other text columns,
so PMs, designers and possibly stakeholders can evaluate two different implementations of the TextPopup,
and experiment with the UX, in order to make a decision on how to evolve this filter. */}
{header.id === "address" ? (
<MultiSelectText
options={selectOptions}
selectedOptions={selectedListItems}
setSelectedOptions={e => {
setSelectedListItems(e);
}}
/>
) : (
<Select
options={selectOptions.map(text => ({
value: text,
label: text
}))}
name={property}
disabled={false}
onChange={e => {
setSelectedListItems(e);
}}
value={selectedListItems}
styles={{ maxHeight: "50rem", maxWidth: "50rem" }}
placeholder={placeholderComponent}
isMulti
></Select>
)}

<hr style={{ width: "100%" }} />
<div style={{ display: "flex" }}>
<Button onClick={setDefault} variant="text">
Expand All @@ -129,7 +144,8 @@ const TextPopup = ({
};

TextPopup.propTypes = {
selectOptions: PropTypes.any,
projects: PropTypes.any,
filter: PropTypes.func,
close: PropTypes.func,
header: PropTypes.any,
criteria: PropTypes.any,
Expand Down
6 changes: 5 additions & 1 deletion client/src/components/Projects/FilterDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ const FilterPopup = ({
startDateCreated: null,
endDateCreated: null,
startDateModified: null,
endDateModified: null
endDateModified: null,
nameList: [],
authorList: [],
addressList: [],
alternativeList: []
});
};

Expand Down
47 changes: 36 additions & 11 deletions client/src/components/Projects/ProjectsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ const ProjectsPage = ({ contentContainerRef }) => {
startDateCreated: null,
endDateCreated: null,
startDateModified: null,
endDateModified: null
endDateModified: null,
nameList: [],
addressList: [],
alternativeList: [],
authorList: []
});
const [filterCollapsed, setFilterCollapsed] = useState(true);
const checkedProjectsStatusData = useCheckedProjectsStatusData(
Expand Down Expand Up @@ -506,7 +510,7 @@ const ProjectsPage = ({ contentContainerRef }) => {
return new Date(dateOnly);
};

const filterProjects = p => {
const filter = (p, criteria) => {
if (criteria.type === "draft" && p.dateSnapshotted) return false;
if (criteria.type === "snapshot" && !p.dateSnapshotted) return false;
if (criteria.status === "active" && p.dateTrashed) return false;
Expand Down Expand Up @@ -567,6 +571,33 @@ const ProjectsPage = ({ contentContainerRef }) => {
return false;
}

if (
criteria.nameList.length > 0 &&
!criteria.nameList
.map(n => n.toLowerCase())
.includes(p.name.toLowerCase())
) {
return false;
}

if (
criteria.addressList.length > 0 &&
!criteria.addressList
.map(n => n.toLowerCase())
.includes(p.address.toLowerCase())
) {
return false;
}

if (
criteria.alternativeList.length > 0 &&
!criteria.alternativeList
.map(n => n.toLowerCase())
.includes(p.alternative.toLowerCase())
) {
return false;
}

// Search criteria for filterText - redundant with individual search
// criteria in FilterDrawer, and we could get rid of the search box
// above the grid.
Expand Down Expand Up @@ -640,7 +671,7 @@ const ProjectsPage = ({ contentContainerRef }) => {
const indexOfLastPost = currentPage * projectsPerPage;
const indexOfFirstPost = indexOfLastPost - projectsPerPage;
const sortedProjects = stableSort(
projects.filter(filterProjects),
projects.filter(p => filter(p, criteria)),
getComparator(order, orderBy)
);
const currentProjects = sortedProjects.slice(
Expand Down Expand Up @@ -735,17 +766,11 @@ const ProjectsPage = ({ contentContainerRef }) => {
<thead className={classes.thead}>
<tr className={classes.tr}>
{headerData.map(header => {
//header.id can be used to index the property of the project object except for author
const property =
header.id == "author" ? "fullname" : header.id;
return (
<td key={header.id}>
<ProjectTableColumnHeader
uniqueValues={[
...new Set(projects.map(p => p[property]))
]
.filter(value => value !== null)
.sort()}
projects={projects}
filter={filter}
header={header}
criteria={criteria}
setCriteria={setCriteria}
Expand Down

0 comments on commit efef6b4

Please sign in to comment.