Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Add funding filter and update search filters
Browse files Browse the repository at this point in the history
  • Loading branch information
acouch committed Jun 29, 2024
1 parent 114d9b6 commit 6f71e2f
Show file tree
Hide file tree
Showing 11 changed files with 968 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Accordion } from "@trussworks/react-uswds";
import { QueryParamKey } from "src/types/search/searchResponseTypes";
import SearchFilterCheckbox from "./SearchFilterCheckbox";
import SearchFilterSection from "./SearchFilterSection/SearchFilterSection";
import SearchFilterToggleAll from "./SearchFilterToggleAll";
import { QueryContext } from "src/app/[locale]/look/QueryProvider";
import { useSearchParamUpdater2 } from "src/hooks/useSearchParamUpdater";
import { useContext } from "react";

export interface AccordionItemProps {
title: React.ReactNode | string;
content: React.ReactNode;
expanded: boolean;
id: string;
headingLevel: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
className?: string;
// handleToggle?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

export interface FilterOption {
id: string;
label: string;
value: string;
isChecked?: boolean;
children?: FilterOption[];
}

interface SearchFilterAccordionProps {
options: FilterOption[];
title: string; // Title in header of accordion
query: Set<string>;
queryParamKey: QueryParamKey; // Ex - In query params, search?{key}=first,second,third
}

export function SearchFilterAccordion({
options,
title,
queryParamKey,
query,
}: SearchFilterAccordionProps) {
const { queryTerm } = useContext(QueryContext);
const { updateQueryParams } = useSearchParamUpdater2();
const totalCheckedCount = query.size


const getAccordionTitle = () => (
<>
{title}
{!!totalCheckedCount && (
<span className="usa-tag usa-tag--big radius-pill margin-left-1">
{totalCheckedCount}
</span>
)}
</>
);

// This should just get the current params and push the update like everything else
const toggleSelectAll = (all: boolean) => {
// TODO: need to clear this.
if (all) {

}
else {

}

}

const isSectionAllSelected = (options: FilterOption[], params: Set<string>): boolean => {
return false;
}

const isSectionNoneSelected = (options: FilterOption[], params: Set<string>): boolean => {
return false;
}

const toggleOptionChecked = (value: string, isChecked: boolean) => {
const updated = new Set(query)
isChecked
? updated.add(value)
: updated.delete(value);
const key = queryParamKey;
updateQueryParams(updated, key, queryTerm);
}

const isAllSelected = false;
const isNoneSelected = true;

const getAccordionContent = () => (
<>
<SearchFilterToggleAll
onSelectAll={() => toggleSelectAll(true)}
onClearAll={() => toggleSelectAll(false)}
isAllSelected={isAllSelected}
isNoneSelected={isNoneSelected}
/>

<ul className="usa-list usa-list--unstyled">
{options.map((option) => (
<li key={option.id}>
{/* If we have children, show a "section" dropdown, otherwise show just a checkbox */}
{option.children ? (
// SearchFilterSection will map over all children of this option
<SearchFilterSection
option={option}
query={query}
updateCheckedOption={toggleOptionChecked}
toggleSelectAll={toggleSelectAll}
accordionTitle={title}
// TODO: determine from vars
isSectionAllSelected={isSectionAllSelected(option.children, query)}
isSectionNoneSelected={isSectionNoneSelected(option.children, query)}
/>
) : (
<SearchFilterCheckbox
option={option}
query={query}
updateCheckedOption={toggleOptionChecked}
accordionTitle={title}
/>
)}
</li>
))}
</ul>
</>
);

const accordionOptions: AccordionItemProps[] = [
{
title: getAccordionTitle(),
content: getAccordionContent(),
expanded: false,
id: `funding-instrument-filter-${queryParamKey}`,
headingLevel: "h2",
},
];

return (
<Accordion
bordered={true}
items={accordionOptions}
multiselectable={true}
className="margin-top-4"
/>
);
}

export default SearchFilterAccordion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import FilterCheckbox from "src/components/FilterCheckbox";
import { FilterOption } from "./SearchFilterAccordion";

interface SearchFilterCheckboxProps {
option: FilterOption;
updateCheckedOption: (optionId: string, isChecked: boolean) => void;
accordionTitle: string;
query: Set<string>;

}

const SearchFilterCheckbox: React.FC<SearchFilterCheckboxProps> = ({
option,
updateCheckedOption,
accordionTitle,
query,
}) => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const checked = event.target.checked;
updateCheckedOption(event.target.value, checked);
};

const getNameAttribute = () =>
accordionTitle === "Agency" ? `agency-${option.id}` : option.id;

return (
<FilterCheckbox
id={option.id}
label={option.label}
name={getNameAttribute()} // value passed to server action {name: "{option.label}", value: "on" } (if no value provided)
onChange={handleChange}
checked={query.has(option.value)}
value={option.value}
/>
);
};

export default SearchFilterCheckbox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import { useEffect, useState } from "react";

import { FilterOption } from "../SearchFilterAccordion";
import SearchFilterCheckbox from "../SearchFilterCheckbox";
import SearchFilterToggleAll from "../SearchFilterToggleAll";
import SectionLinkCount from "./SectionLinkCount";
import SectionLinkLabel from "./SectionLinkLabel";

interface SearchFilterSectionProps {
option: FilterOption;
updateCheckedOption: (optionId: string, isChecked: boolean) => void;
toggleSelectAll: (isSelected: boolean, sectionId: string) => void;
accordionTitle: string;
isSectionAllSelected: boolean;
isSectionNoneSelected: boolean;
query: Set<string>
}

const SearchFilterSection: React.FC<SearchFilterSectionProps> = ({
option,
updateCheckedOption,
toggleSelectAll,
accordionTitle,
query,
isSectionAllSelected,
isSectionNoneSelected,
}) => {
const [childrenVisible, setChildrenVisible] = useState<boolean>(false);

// TODO: Set this number per state/query params
const [sectionCount, setSectionCount] = useState<number>(0);

const handleSelectAll = () => {
toggleSelectAll(true, option.id);
};

const handleClearAll = () => {
toggleSelectAll(false, option.id);
};

useEffect(() => {
if (option.children) {
const newCount = option.children.filter(
(child) => child.isChecked,
).length;
setSectionCount(newCount);
}
}, [option.children]);

const getHiddenName = (name: string) =>
accordionTitle === "Agency" ? `agency-${name}` : name;

return (
<div>
<button
className="usa-button usa-button--unstyled width-full border-bottom-2px border-base-lighter"
onClick={(event) => {
event.preventDefault();
setChildrenVisible(!childrenVisible);
}}
>
<span className="grid-row flex-align-center margin-left-neg-1">
<SectionLinkLabel childrenVisible={childrenVisible} option={option} />
<SectionLinkCount sectionCount={sectionCount} />
</span>
</button>
{childrenVisible ? (
<div className="padding-y-1">
<SearchFilterToggleAll
onSelectAll={handleSelectAll}
onClearAll={handleClearAll}
isAllSelected={isSectionAllSelected}
isNoneSelected={isSectionNoneSelected}
/>
<ul className="usa-list usa-list--unstyled margin-left-4">
{option.children?.map((child) => (
<li key={child.id}>
<SearchFilterCheckbox
option={child}
query={query}
updateCheckedOption={updateCheckedOption}
accordionTitle={accordionTitle}
/>
</li>
))}
</ul>
</div>
) : (
// Collapsed sections won't send checked values to the server action.
// So we need hidden inputs.
option.children?.map((child) =>
child.isChecked ? (
<input
key={child.id}
type="hidden"
// name={child.value}
name={getHiddenName(child.id)}
value="on"
/>
) : null,
)
)}
</div>
);
};

export default SearchFilterSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function SectionLinkCount({
sectionCount,
}: {
sectionCount: number;
}) {
return (
<span className="grid-col flex-auto">
{!!sectionCount && (
<span className="usa-tag usa-tag--big radius-pill margin-left-1">
{sectionCount}
</span>
)}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FilterOption } from "../SearchFilterAccordion";
import { Icon } from "@trussworks/react-uswds";

export default function SectionLinkLabel({
childrenVisible,
option,
}: {
childrenVisible: boolean;
option: FilterOption;
}) {
// When the arrow is down, the section is collapsed, and we can expand the section
// When the arrow is up, the section is expanded, and we can collapse the section
const ariaLabel = childrenVisible ? "Collapse section" : "Expand section";

return (
<span className="grid-col flex-fill">
{childrenVisible ? (
<Icon.ArrowDropUp
size={5}
className="text-middle"
aria-label={ariaLabel}
/>
) : (
<Icon.ArrowDropDown
size={5}
className="text-middle"
aria-label={ariaLabel}
/>
)}
{option.label}{" "}
{/* Assuming you want to display the option's label instead of its ID */}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

interface SearchFilterToggleAllProps {
isAllSelected: boolean;
isNoneSelected: boolean;
onSelectAll?: () => void;
onClearAll?: () => void;
}

const SearchFilterToggleAll: React.FC<SearchFilterToggleAllProps> = ({
onSelectAll,
onClearAll,
isAllSelected,
isNoneSelected,
}) => {
return (
<div className="grid-row">
<div className="grid-col-fill">
<button
className="usa-button usa-button--unstyled font-sans-xs"
onClick={(event) => {
// form submission is done in useSearchFilter, so
// prevent the onClick from submitting here.
event.preventDefault();
onSelectAll?.();
}}
disabled={isAllSelected}
>
Select All
</button>
</div>
<div className="grid-col-fill text-right">
<button
className="usa-button usa-button--unstyled font-sans-xs"
onClick={(event) => {
event.preventDefault();
onClearAll?.();
}}
disabled={isNoneSelected}
>
Clear All
</button>
</div>
</div>
);
};

export default SearchFilterToggleAll;
Loading

0 comments on commit 6f71e2f

Please sign in to comment.