diff --git a/frontend/src/components/search/SearchFilterAccordion/SearchFilterAccordion.tsx b/frontend/src/components/search/SearchFilterAccordion/SearchFilterAccordion.tsx index e719de3d9..4fb845cdf 100644 --- a/frontend/src/components/search/SearchFilterAccordion/SearchFilterAccordion.tsx +++ b/frontend/src/components/search/SearchFilterAccordion/SearchFilterAccordion.tsx @@ -1,5 +1,6 @@ "use client"; +import { camelCase } from "lodash"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; import { QueryParamKey } from "src/types/search/searchResponseTypes"; @@ -43,6 +44,20 @@ export interface FilterOptionWithChildren { children: FilterOption[]; } +const isSectionAllSelected = ( + allSelected: Set, + query: Set, +): boolean => { + return areSetsEqual(allSelected, query); +}; + +const isSectionNoneSelected = (query: Set): boolean => { + return query.size === 0; +}; + +const areSetsEqual = (a: Set, b: Set) => + a.size === b.size && [...a].every((value) => b.has(value)); + export function SearchFilterAccordion({ filterOptions, title, @@ -50,13 +65,21 @@ export function SearchFilterAccordion({ query, }: SearchFilterAccordionProps) { const { queryTerm } = useContext(QueryContext); - const { updateQueryParams } = useSearchParamUpdater(); + const { updateQueryParams, searchParams } = useSearchParamUpdater(); + const totalCheckedCount = query.size; - // These are all of the available selectedable options. - const allOptionValues = filterOptions.map((options) => options.value); - // This is the setting if all are selected. + // all top level selectable filter options + const allOptionValues = filterOptions.reduce((values: string[], option) => { + if (option.children) { + return values; + } + values.push(option.value); + return values; + }, []); + const allSelected = new Set(allOptionValues); + // SPLIT ME INTO MY OWN COMPONENT const getAccordionTitle = () => ( <> {title} @@ -68,29 +91,25 @@ export function SearchFilterAccordion({ ); - const toggleSelectAll = (all: boolean, allSelected: Set): void => { - if (all) { - updateQueryParams(allSelected, queryParamKey, queryTerm); + // need to add any existing relevant search params to the passed in set + const toggleSelectAll = (all: boolean, newSelections?: Set): void => { + if (all && newSelections) { + // get existing current selected options for this accordion from url + const currentSelections = new Set( + searchParams.get(camelCase(title))?.split(","), + ); + // add existing to newly selected section + const sectionPlusCurrent = new Set([ + ...currentSelections, + ...newSelections, + ]); + updateQueryParams(sectionPlusCurrent, queryParamKey, queryTerm); } else { - const noneSelected = new Set(); - updateQueryParams(noneSelected, queryParamKey, queryTerm); + const clearedSelections = newSelections || new Set(); + updateQueryParams(clearedSelections, queryParamKey, queryTerm); } }; - const isSectionAllSelected = ( - allSelected: Set, - query: Set, - ): boolean => { - return areSetsEqual(allSelected, query); - }; - - const isSectionNoneSelected = (query: Set): boolean => { - return query.size === 0; - }; - - const areSetsEqual = (a: Set, b: Set) => - a.size === b.size && [...a].every((value) => b.has(value)); - const toggleOptionChecked = (value: string, isChecked: boolean) => { const updated = new Set(query); isChecked ? updated.add(value) : updated.delete(value); @@ -99,11 +118,12 @@ export function SearchFilterAccordion({ const isExpanded = !!query.size; + // SPLIT ME INTO MY OWN COMPONENT const getAccordionContent = () => ( <> toggleSelectAll(true, allSelected)} - onClearAll={() => toggleSelectAll(false, allSelected)} + onClearAll={() => toggleSelectAll(false)} isAllSelected={isSectionAllSelected(allSelected, query)} isNoneSelected={isSectionNoneSelected(query)} /> @@ -138,12 +158,13 @@ export function SearchFilterAccordion({ ); + // MEMOIZE ME const accordionOptions: AccordionItemProps[] = [ { title: getAccordionTitle(), content: getAccordionContent(), expanded: isExpanded, - id: `funding-instrument-filter-${queryParamKey}`, + id: `opportunity-filter-${queryParamKey}`, headingLevel: "h2", }, ]; diff --git a/frontend/src/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.tsx b/frontend/src/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.tsx index 87ed677bb..1a3385247 100644 --- a/frontend/src/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.tsx +++ b/frontend/src/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.tsx @@ -1,6 +1,9 @@ "use client"; -import { useState } from "react"; +import { camelCase } from "lodash"; + +import { useSearchParams } from "next/navigation"; +import { useCallback, useMemo, useState } from "react"; import { FilterOptionWithChildren } from "src/components/search/SearchFilterAccordion/SearchFilterAccordion"; import SearchFilterCheckbox from "src/components/search/SearchFilterAccordion/SearchFilterCheckbox"; @@ -11,7 +14,7 @@ import SearchFilterToggleAll from "src/components/search/SearchFilterAccordion/S interface SearchFilterSectionProps { option: FilterOptionWithChildren; updateCheckedOption: (optionId: string, isChecked: boolean) => void; - toggleSelectAll: (all: boolean, allSelected: Set) => void; + toggleSelectAll: (all: boolean, allSelected?: Set) => void; accordionTitle: string; isSectionAllSelected: ( allSelected: Set, @@ -33,6 +36,7 @@ const SearchFilterSection: React.FC = ({ value, }) => { const [childrenVisible, setChildrenVisible] = useState(false); + const searchParams = useSearchParams(); const sectionQuery = new Set(); query.forEach((queryValue) => { @@ -42,16 +46,26 @@ const SearchFilterSection: React.FC = ({ sectionQuery.add(queryValue); } }); - const allSectionOptionValues = option.children.map( - (options) => options.value, + const allSectionOptions = useMemo( + () => new Set(option.children.map((options) => options.value)), + [option], ); - const sectionAllSelected = new Set(allSectionOptionValues); const sectionCount = sectionQuery.size; const getHiddenName = (name: string) => accordionTitle === "Agency" ? `agency-${name}` : name; + const clearSection = useCallback(() => { + const currentSelections = new Set( + searchParams.get(camelCase(accordionTitle))?.split(","), + ); + allSectionOptions.forEach((option) => { + currentSelections.delete(option); + }); + toggleSelectAll(false, currentSelections); + }, [toggleSelectAll, accordionTitle, searchParams, allSectionOptions]); + return (