diff --git a/frontend/cypress/e2e/canList.cy.js b/frontend/cypress/e2e/canList.cy.js index fbdaf457bb..2843b4a349 100644 --- a/frontend/cypress/e2e/canList.cy.js +++ b/frontend/cypress/e2e/canList.cy.js @@ -65,6 +65,16 @@ describe("CAN List", () => { // click the button that has text Apply cy.get("button").contains("Apply").click(); + // check that the correct tags are displayed + cy.get("div").contains("Filters Applied:").should("exist"); + cy.get("svg[id='filter-tag-activePeriod']").should("exist"); + cy.get("svg[id='filter-tag-transfer']").should("exist"); + cy.get("svg[id='filter-tag-portfolio']").should("exist"); + + cy.get("span").contains("1 Year").should("exist"); + cy.get("span").contains("Direct").should("exist"); + cy.get("span").contains("HMRF").should("exist"); + // check that the table is filtered correctly // table should contain 6 rows @@ -77,6 +87,11 @@ describe("CAN List", () => { // check that the table is filtered correctly // table should have more than 5 rows + /// check that the correct tags are displayed + cy.get("div").contains("Filters Applied:").should("not.exist"); + cy.get("svg[id='filter-tag-activePeriod']").should("not.exist"); + cy.get("svg[id='filter-tag-transfer']").should("not.exist"); + cy.get("svg[id='filter-tag-portfolio']").should("not.exist"); cy.get("tbody").find("tr").should("have.length.greaterThan", 3); }); diff --git a/frontend/src/components/UI/FilterTags/FilterTags.jsx b/frontend/src/components/UI/FilterTags/FilterTags.jsx index 97e0f0b1af..73d4a9cda4 100644 --- a/frontend/src/components/UI/FilterTags/FilterTags.jsx +++ b/frontend/src/components/UI/FilterTags/FilterTags.jsx @@ -5,8 +5,8 @@ import Tag from "../Tag"; * A filter tags. * @param {Object} props - The component props. * @param {Function} props.removeFilter - A function to call to remove a filter/tag. - * @param {Array} props.tagsList - An array of tags to display. - * @returns {React.JSX.Element} - The procurement shop select element. + * @param {string[]} props.tagsList - An array of tags to display. + * @returns {JSX.Element} - The filter tags component. (Pills with an 'x' to remove them) */ export const FilterTags = ({ removeFilter, tagsList }) => { const FilterTag = ({ tag }) => ( diff --git a/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.hooks.js b/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.hooks.js new file mode 100644 index 0000000000..d7432c1119 --- /dev/null +++ b/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.hooks.js @@ -0,0 +1,89 @@ +import { useState, useEffect, useCallback } from "react"; +/** + * @typedef {Object} FilterItem + * @property {string} title + */ + +/** + * @typedef {Object} Filters + * @property {FilterItem[]} activePeriod + * @property {FilterItem[]} portfolio + * @property {FilterItem[]} transfer + */ + +/** + * @typedef {Object} Tag + * @property {string} tagText + * @property {string} filter + */ + +/** + * Custom hook for managing tags list + * @param {Filters} filters + * @returns {Tag[]} + */ +export const useTagsList = (filters) => { + const [tagsList, setTagsList] = useState([]); + + /** + * @param {keyof Filters} filterKey + * @param {string} filterName + */ + const updateTags = useCallback( + (filterKey, filterName) => { + if (!Array.isArray(filters[filterKey])) return; + + const selectedTags = filters[filterKey].map((item) => ({ + tagText: item.title, + filter: filterName + })); + + setTagsList((prevState) => [...prevState.filter((t) => t.filter !== filterName), ...selectedTags]); + }, + [filters] + ); + + useEffect(() => { + updateTags("activePeriod", "activePeriod"); + }, [filters.activePeriod, updateTags]); + + useEffect(() => { + updateTags("portfolio", "portfolio"); + }, [filters.portfolio, updateTags]); + + useEffect(() => { + updateTags("transfer", "transfer"); + }, [filters.transfer, updateTags]); + + return tagsList; +}; + +/** + * Removes a filter tag + * @param {Tag} tag - The tag to remove + * @param {function(function(Filters): Filters): void} setFilters - Function to update filters + */ +export const removeFilter = (tag, setFilters) => { + switch (tag.filter) { + case "activePeriod": + setFilters((prevState) => ({ + ...prevState, + activePeriod: prevState.activePeriod.filter((period) => period.title !== tag.tagText) + })); + break; + case "portfolio": + setFilters((prevState) => ({ + ...prevState, + portfolio: prevState.portfolio.filter((portfolio) => portfolio.title !== tag.tagText) + })); + break; + case "transfer": + setFilters((prevState) => ({ + ...prevState, + transfer: prevState.transfer.filter((transfer) => transfer.title !== tag.tagText) + })); + break; + default: + console.warn(`Unknown filter type: ${tag.filter}`); + } +}; diff --git a/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.jsx b/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.jsx new file mode 100644 index 0000000000..821bfcb849 --- /dev/null +++ b/frontend/src/pages/cans/list/CANFilterTags/CANFilterTags.jsx @@ -0,0 +1,35 @@ +import _ from "lodash"; +import FilterTags from "../../../../components/UI/FilterTags"; +import FilterTagsWrapper from "../../../../components/UI/FilterTags/FilterTagsWrapper"; +import { useTagsList, removeFilter } from "./CANFilterTags.hooks"; + +/** + * A filter tags component. + * @param {Object} props - The component props. + * @param {import('./CANFilterTags.hooks').Filters} props.filters - The current filters. + * @param {() => void} props.setFilters - A function to call to set the filters. + * @returns {JSX.Element|null} The filter tags component or null if no tags. + */ +export const CANFilterTags = ({ filters, setFilters }) => { + const tagsList = useTagsList(filters); + + const tagsListByFilter = _.groupBy(tagsList, "filter"); + const tagsListByFilterMerged = Object.values(tagsListByFilter) + .flat() + .sort((a, b) => a.tagText.localeCompare(b.tagText)); + + if (tagsList.length === 0) { + return null; + } + + return ( + + removeFilter(tag, setFilters)} + tagsList={tagsListByFilterMerged} + /> + + ); +}; + +export default CANFilterTags; diff --git a/frontend/src/pages/cans/list/CANFilterTags/index.js b/frontend/src/pages/cans/list/CANFilterTags/index.js new file mode 100644 index 0000000000..821729e846 --- /dev/null +++ b/frontend/src/pages/cans/list/CANFilterTags/index.js @@ -0,0 +1 @@ +export { default } from "./CANFilterTags"; diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index ccae3eaf95..f12e1e5964 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -10,7 +10,8 @@ import FiscalYear from "../../../components/UI/FiscalYear"; import { setSelectedFiscalYear } from "../../../pages/cans/detail/canDetailSlice"; import ErrorPage from "../../ErrorPage"; import CANFilterButton from "./CANFilterButton"; -import { sortAndFilterCANs, getPortfolioOptions } from "./CanList.helpers"; +import CANFilterTags from "./CANFilterTags"; +import { getPortfolioOptions, sortAndFilterCANs } from "./CanList.helpers"; /** * Page for the CAN List. @@ -79,6 +80,12 @@ const CanList = () => { /> } FYSelect={} + FilterTags={ + + } /> )