Skip to content

Commit

Permalink
Merge pull request #42 from NYPL/SCC-3730/styling-etc
Browse files Browse the repository at this point in the history
Functional Filters!
  • Loading branch information
charmingduchess authored Nov 9, 2023
2 parents ea74851 + 8ff149d commit 7b4f25d
Show file tree
Hide file tree
Showing 13 changed files with 1,015 additions and 1 deletion.
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",
},
],
},
]
1 change: 0 additions & 1 deletion pages/search/advanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import type {
SearchFormActionType,
} from "../../src/types/searchTypes"
import { getQueryString } from "../../src/utils/searchUtils"

/**
* 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
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

0 comments on commit 7b4f25d

Please sign in to comment.