Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional Filters! #42

Merged
merged 28 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c4ae7b3
wip
charmingduchess Oct 30, 2023
ffa9c99
offsite is sorted, checkboxgroup bug
charmingduchess Oct 31, 2023
26fef8f
fix checkboxgroup bug
charmingduchess Oct 31, 2023
0ae2baa
checkbox group value controlled by selected field state
charmingduchess Oct 31, 2023
1dd53f1
basic filter ui implemented
charmingduchess Oct 31, 2023
423bee1
filter skeleton and tests
charmingduchess Nov 1, 2023
490bb87
more tests
charmingduchess Nov 1, 2023
875be15
replace filters in adv search
charmingduchess Nov 1, 2023
d2a2404
build out open close behavior
charmingduchess Nov 2, 2023
b8c2aac
show/hide works
charmingduchess Nov 2, 2023
64cf726
add comments from PR
charmingduchess Nov 2, 2023
9920278
merge main
charmingduchess Nov 2, 2023
e7f84b0
start utils tests
charmingduchess Nov 3, 2023
415bf42
wip
charmingduchess Nov 3, 2023
0e5372f
merge
charmingduchess Nov 3, 2023
500f445
update tests and add button disabled
charmingduchess Nov 3, 2023
88ca49c
clear local state on close
charmingduchess Nov 3, 2023
7d0a6c0
rm unsused dependency
charmingduchess Nov 3, 2023
cee2a2f
utils test
charmingduchess Nov 6, 2023
481c4eb
stylin' B-)
charmingduchess Nov 6, 2023
4482dd0
out of focus closes filters
charmingduchess Nov 7, 2023
f0223bf
fix var name
charmingduchess Nov 7, 2023
42bb608
add applied filter string func
charmingduchess Nov 8, 2023
e2f18cc
style fixes
charmingduchess Nov 8, 2023
c2662ca
remove empty describe block
charmingduchess Nov 8, 2023
f20568c
remove filters from advanced search
charmingduchess Nov 8, 2023
8504d15
fix query join
charmingduchess Nov 9, 2023
8ff149d
rm filters from advanced
charmingduchess Nov 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions __test__/fixtures/testAggregations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
export const normalAggs = [
{
"@type": "nypl:Aggregation",
"@id": "res:location",
id: "location",
field: "location",
values: [
{
value: "loc:mal82",
count: 572,
label: "Schwarzman Building - Main Reading Room 315",
},
{
value: "loc:makk3",
count: 133,
label: "Schwarzman Building - Dewitt Wallace Reference Desk Room 108",
},
{
value: "loc:rc2ma",
count: 66,
label: "Offsite",
},
{
value: "loc:rcma2",
count: 66,
label: "Offsite",
},
],
},
{
"@type": "nypl:Aggregation",
"@id": "res:format",
id: "format",
field: "format",
values: [
{
value: "Text",
count: 704,
label: "Text",
},
{
value: "AUG. 23, 2021-CURRENT",
count: 109,
label: "AUG. 23, 2021-CURRENT",
},
{
value: "FEB. 15/22, 2021 - AUG. 16, 2021",
count: 24,
label: "FEB. 15/22, 2021 - AUG. 16, 2021",
},
],
},
{
"@type": "nypl:Aggregation",
"@id": "res:status",
id: "status",
field: "status",
values: [
{
value: "status:a",
count: 826,
label: "Available",
},
{
value: "status:na",
count: 6,
label: "Not available",
},
{
value: "status:co",
count: 4,
label: "Loaned",
},
{
value: "status:oh",
count: 1,
label: "On Holdshelf",
},
],
},
]

export const aggsWithRepeatedValues = [
{
"@type": "nypl:Aggregation",
"@id": "res:location",
id: "location",
field: "location",
values: [
{
value: "loc:mym32",
count: 8,
label: "Performing Arts Research Collections - Music",
},
],
},
{
"@type": "nypl:Aggregation",
"@id": "res:format",
id: "format",
field: "format",
values: [],
},
{
"@type": "nypl:Aggregation",
"@id": "res:status",
id: "status",
field: "status",
values: [
{
value: "status:a",
count: 4,
label: "Available",
},
{
value: "status:a",
count: 4,
label: "Available ",
},
],
},
]

export const aggsWithMissingProperties = [
{
"@id": "res:location",
"@type": "nypl:Aggregation",
field: "location",
id: "location",
values: [
{
count: 4,
value: "loc:maj03",
label: "SASB M1 - General Research - Room 315",
},
{
count: 12,
label: "Offsite",
value: "loc:rc2ma",
},
{
count: 12,
label: "Off site",
value: "loc:rc2ma",
},
{
count: 12,
label: "Off-site",
value: "loc:rc2ma",
},
{
count: 12,
label: "off-site",
value: "loc:rc2ma",
},
{
count: 12,
label: "off site",
value: "loc:rc2ma",
},
{
count: 2,
value: "offsite",
label: "Offsite",
},
{
count: 2,
value: "blank",
label: "",
},
{
count: 2,
value: "blaaaank",
},
],
},
{
"@id": "res:format",
"@type": "nypl:Aggregation",
field: "format",
id: "format",
values: [
{
count: 12,
label: "Text",
value: "Text",
},
],
},
{
"@id": "res:status",
"@type": "nypl:Aggregation",
field: "status",
id: "status",
values: [
{
count: 12,
label: "Available",
value: "status:a",
},
{
count: 12,
label: "Not Available (ReCAP",
value: "status:na",
},
],
},
]
4 changes: 4 additions & 0 deletions pages/search/advanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import type {
} from "../../src/types/searchTypes"
import { getQueryString } from "../../src/utils/searchUtils"

import ItemFilterContainer from "../../src/components/ItemFilters/FiltersContainer"
import { normalAggs } from "../../__test__/fixtures/testAggregations"

/**
* The Advanced Search page is responsible for displaying the Advanced Search form fields and
* buttons that clear the fields and submit a search request.
Expand Down Expand Up @@ -122,6 +125,7 @@ export default function AdvancedSearch() {
}
/>
)}
<ItemFilterContainer itemAggs={normalAggs} />
<Heading level="two">Advanced Search</Heading>
<Form
id="advancedSearchForm"
Expand Down
79 changes: 79 additions & 0 deletions src/components/ItemFilters/FiltersContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useEffect, useRef, useState } from "react"
import { useRouter } from "next/router"
import React from "react"
import { Text, useCloseDropDown } from "@nypl/design-system-react-components"

import styles from "../../../styles/components/ItemFilters.module.scss"
import type { ItemAggregation } from "../../types/filterTypes"
import { ItemFilterData, LocationFilterData } from "../../models/itemFilterData"
import ItemFilter from "./ItemFilter"
import {
buildItemFilterQueryParams,
parseItemFilterQueryParams,
} from "../../utils/itemFilterUtils"

interface ItemFilterContainerProps {
itemAggs: ItemAggregation[]
}

const ItemFilterContainer = ({ itemAggs }: ItemFilterContainerProps) => {
const { query } = useRouter()
const filterData = itemAggs.map((agg: ItemAggregation) => {
if (agg.field === "location") return new LocationFilterData(agg)
else return new ItemFilterData(agg)
})
const [activeFilters, setAppliedFilters] = useState({
location: [],
format: [],
status: [],
})

const ref = useRef<HTMLDivElement>(null)
useCloseDropDown(() => setWhichFilterIsOpen(""), ref)

const [whichFilterIsOpen, setWhichFilterIsOpen] = useState("")

const [tempQueryDisplay, setTempQueryDisplay] = useState("")

const tempSubmitFilters = () => {
const locationFilterData = filterData.find(
(filter) => filter.field === "location"
) as LocationFilterData
setTempQueryDisplay(
buildItemFilterQueryParams(
activeFilters,
locationFilterData.recapLocations()
)
)
}

useEffect(() => {
setAppliedFilters(parseItemFilterQueryParams(query))
}, [query])

useEffect(tempSubmitFilters, [activeFilters, tempSubmitFilters])

return (
<div className={styles.filtersContainer}>
<Text data-testid="filter-text" size="body2" isBold={true}>
Filter by
</Text>
<div className={styles.filterGroup} ref={ref}>
{filterData.map((field: ItemFilterData) => (
<ItemFilter
isOpen={whichFilterIsOpen === field.field}
setWhichFilterIsOpen={setWhichFilterIsOpen}
key={field.field}
itemFilterData={field}
setAppliedFilters={setAppliedFilters}
activeFilters={activeFilters}
submitFilters={tempSubmitFilters}
/>
))}
</div>
<p>{tempQueryDisplay}</p>
</div>
)
}

export default ItemFilterContainer
92 changes: 92 additions & 0 deletions src/components/ItemFilters/ItemFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { CheckboxGroup, Checkbox } from "@nypl/design-system-react-components"
import type { Dispatch } from "react"
import { useCallback, useEffect, useState } from "react"

import type { ItemFilterData } from "../../models/itemFilterData"
import type { Option, AppliedFilters } from "../../types/filterTypes"
import styles from "../../../styles/components/ItemFilters.module.scss"

import ItemFilterButtons from "./ItemFilterButtons"
import ItemFilterLabel from "./ItemFilterLabel"

interface ItemFilterProps {
itemFilterData: ItemFilterData
setAppliedFilters: Dispatch<React.SetStateAction<AppliedFilters>>
activeFilters: AppliedFilters
// this type is temporary for dev use only. could end up being different.
submitFilters: Dispatch<React.SetStateAction<AppliedFilters>>
isOpen: boolean
setWhichFilterIsOpen: Dispatch<React.SetStateAction<string>>
}

const ItemFilter = ({
itemFilterData,
setAppliedFilters,
activeFilters,
isOpen,
setWhichFilterIsOpen,
}: ItemFilterProps) => {
const field = itemFilterData.field
const [selectedOptions, setSelectedOptions] = useState(activeFilters[field])

const resetToAppliedOptions = useCallback(() => {
updateCheckboxGroupValue(activeFilters[field])
}, [activeFilters, field])

const updateCheckboxGroupValue = (data: string[]) => {
setSelectedOptions(data)
}

// When the filter is close with unapplied options, those options are not
// persisted. Instead, reset to the options that were last queried for.
useEffect(() => {
if (!isOpen) resetToAppliedOptions()
}, [isOpen, resetToAppliedOptions])

return (
<div className={styles.itemFilter}>
<ItemFilterLabel
field={field}
selectedOptions={selectedOptions}
setWhichFilterIsOpen={setWhichFilterIsOpen}
isOpen={isOpen}
/>
{isOpen && (
<div className={styles.itemFilterOptionsContainer}>
<CheckboxGroup
labelText={field}
showLabel={false}
key={field}
name={field}
id={field}
onChange={updateCheckboxGroupValue}
// isSelected of the children checkboxes is controlled by this value
// attribute. The options whose value attribute match those present in
// the CheckboxGroup value array are selected.
value={selectedOptions}
>
{itemFilterData.displayOptions().map(({ value, label }: Option) => {
return (
<Checkbox
className={styles.filterOption}
id={value}
key={value}
value={value}
labelText={label}
/>
)
})}
</CheckboxGroup>
<ItemFilterButtons
field={field}
selectedOptions={selectedOptions}
setSelectedOptions={setSelectedOptions}
setAppliedFilters={setAppliedFilters}
/>
</div>
)}
</div>
)
}

export default ItemFilter
Loading
Loading