Skip to content

Commit

Permalink
refactor(useActiveIdsBySearchTerm): Show all items for first two cate…
Browse files Browse the repository at this point in the history
…gories by default

And remove logic that reactivates all categories when none is selected
anymore as discussed with @Esshahn
  • Loading branch information
vogelino committed Aug 9, 2023
1 parent f291cb6 commit c17aa3b
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 66 deletions.
6 changes: 3 additions & 3 deletions pages/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ const searchData = ({
query: string
filters: string[]
}): TableRowType[] => {
if ((!query && filters.length === 0) || (!query && filters.length === 5))
return data
if (filters.length === 0) return []
if (!query && filters.length === 5) return data
return data.filter((item) => {
// check if item.fields.Typ is in the filters array
if (filters.length > 0 && !filters.includes(item.fields.Typ)) {
Expand Down Expand Up @@ -85,7 +85,7 @@ const handler = async (
const content = await fs.readFile(filePath, 'utf8')
const data = JSON.parse(content) as TableRowType[]
const result = searchData({ data, query: params.query, filters })
return res.status(200).json({ params, result })
return res.status(200).json({ params, result, total: data.length })
} catch (error: unknown) {
if (error instanceof Error) {
return res.status(500).json({ result: null, error: error.message })
Expand Down
3 changes: 1 addition & 2 deletions src/components/ActiveFiltersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ function ActiveFiltersList(): JSX.Element | null {
const [urlState] = useUrlState()
const labels = useFiltersWithActiveProp()
const categoriesTexts = getCategoriesTexts(texts)
const categoriesCount = Object.keys(categoriesTexts).length
const categories = urlSearchCategoriesToStateSearchCategories(
urlState.qCategories
)
Expand All @@ -30,7 +29,7 @@ function ActiveFiltersList(): JSX.Element | null {

const allFilters = [
...(urlState.q ? [{ id: 'search', fields: { text: urlState.q } }] : []),
...(categoryFilters.length < categoriesCount ? categoryFilters : []),
...categoryFilters,
...labels.filter((label) => label.isActive),
] as GristLabelType[]

Expand Down
7 changes: 4 additions & 3 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const Button: FC<ButtonType> = ({
)

const SHARED_CLASSES = classNames(
'text-center',
'text-center group',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-white',
SIZE_CLASSES,
SCHEME_CLASSES,
Expand Down Expand Up @@ -120,9 +120,10 @@ export const Button: FC<ButtonType> = ({
{tooltip && (
<span
className={classNames(
`absolute -top-2 left-1/2 -translate-x-1/2 -translate-y-full`,
`absolute -top-2 left-1/2 -translate-x-1/2 -translate-y-full w-full`,
`px-2 py-1 text-white bg-black opacity-0 pointer-events-none`,
`group-hover:opacity-100 transition-colors text-sm leading-tight`
`group-hover:opacity-100 text-sm leading-tight text-left`,
`transition-opacity motion-reduce:transition-none`
)}
>
{tooltip}
Expand Down
52 changes: 30 additions & 22 deletions src/components/FiltersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
useUrlState,
} from '@lib/UrlStateContext'
import { useUserGeolocation } from '@lib/hooks/useUserGeolocation'
import { useTexts } from '@lib/TextsContext'
import { TextsMapType, useTexts } from '@lib/TextsContext'
import { FC, useEffect, useMemo, useState } from 'react'
import { SwitchButton } from './SwitchButton'
import { useRouter } from 'next/router'
Expand All @@ -19,6 +19,7 @@ import { Listbox } from './Listbox'
import { Button } from './Button'
import { Arrow } from './icons/Arrow'
import TextSearch from './TextSearch'
import { useActiveIdsBySearchTerm } from '@lib/hooks/useActiveIdsBySearchTerm'

export const FiltersList: FC<{
recordsWithOnlyLabels: RecordsWithOnlyLabelsType[]
Expand All @@ -42,6 +43,7 @@ export const FiltersList: FC<{
const filteredFacilitiesCount = useFilteredFacilitiesCount(
recordsWithOnlyLabels
)
const { isLoading: textSearchLoading, total } = useActiveIdsBySearchTerm()

const group1 = labels.filter(({ fields }) => fields.group2 === 'gruppe-1')
const group2 = labels.filter(({ fields }) => fields.group2 === 'gruppe-2')
Expand Down Expand Up @@ -79,25 +81,9 @@ export const FiltersList: FC<{
updateUrlState({ tags: newTags })
}

const getSubmitText = (): string => {
switch (filteredFacilitiesCount) {
case recordsWithOnlyLabels.length:
return texts.filtersButtonTextAllFilters
case 1:
return texts.filtersButtonTextFilteredSingular
case 0:
return texts.filtersButtonTextFilteredNoResults
default:
return texts.filtersButtonTextFilteredPlural.replace(
'#number',
`${filteredFacilitiesCount}`
)
}
}

return (
<div className="pb-20 lg:pb-0 @container">
<div className="md:pt-10 flex flex-wrap gap-x-8 pb-6 md:pb-8">
<div className="@md:pt-10 flex flex-wrap gap-x-8 pb-6 @md:pb-8">
<ul className="flex flex-wrap gap-2 place-content-start mb-5">
<FiltersTagsList
filters={[...group1, ...group2, ...group3]}
Expand Down Expand Up @@ -207,7 +193,7 @@ export const FiltersList: FC<{
<Button
scheme="primary"
size="large"
className={classNames('w-full md:w-max md:min-w-[324px]', 'group')}
className={classNames('w-full @md:w-max @md:min-w-[324px]', 'group')}
onClick={() => {
onSubmit()
void push({
Expand All @@ -225,16 +211,38 @@ export const FiltersList: FC<{
)}
/>
}
disabled={queryTagIds.length > 0 && filteredFacilitiesCount === 0}
disabled={filteredFacilitiesCount === 0}
tooltip={
queryTagIds.length > 0 &&
filteredFacilitiesCount === 0 && (
<span>{texts.filtersButtonTextFilteredNoResultsHint}</span>
)
}
>
{getSubmitText()}
{getSubmitText({
texts,
isLoading: textSearchLoading,
count: filteredFacilitiesCount,
total,
})}
</Button>
</div>
)
}

function getSubmitText({
texts,
isLoading = false,
count,
total,
}: {
texts: TextsMapType
isLoading?: boolean
count: number
total: number
}): string {
if (isLoading) return texts.filtersButtonLoading
if (count === 0) return texts.filtersButtonTextFilteredNoResults
if (count === 1) return texts.filtersButtonTextFilteredSingular
if (count === total) return texts.filtersButtonTextAllFilters
return texts.filtersButtonTextFilteredPlural.replace('#number', `${count}`)
}
19 changes: 6 additions & 13 deletions src/components/TextSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,13 @@ function TextSearch({
id={id}
labelText={labelText}
onChange={(evt) => {
const checked = evt.target.checked
const newCategories = { ...categories, [id]: checked }
const allUnchecked = Object.values(newCategories).every(
(value) => !value
)
const allChecked = Object.keys(categories).reduce(
(acc, key) => ({ ...acc, [key]: true }),
{}
)
const allWhenAllUnchecked = {
categories: allUnchecked ? allChecked : newCategories,
onChange({
text,
}
onChange(allWhenAllUnchecked)
categories: {
...categories,
[id]: evt.target.checked,
},
})
}}
checked={!!categories[id as keyof CategoriesType]}
/>
Expand Down
1 change: 1 addition & 0 deletions src/lib/TextsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const defaultValue = {
textSearchCategoryOnlineOffers: '',
textSearchCategoryDisctrictOfficeHelp: '',
optionalFurtherSearchIntroText: '',
filtersButtonLoading: '',
}

export type TextsMapType = typeof defaultValue
Expand Down
31 changes: 22 additions & 9 deletions src/lib/UrlStateContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'
import { createContext, FC, useCallback, useContext } from 'react'
import { createContext, FC, useCallback, useContext, useEffect } from 'react'
import { mapRawQueryToState, PageQueryType } from './mapRawQueryToState'

type ParsedSearchTermCategoriesType = {
Expand Down Expand Up @@ -44,27 +44,40 @@ export const UrlStateProvider: FC = ({ children }) => {
[query, pathname, push]
)

useEffect(() => {
if (typeof window === 'undefined') return
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('qCategories')) return
updateUrlState({
...query,
qCategories: stateSearchCategoriesToUrlSearchCategories({
categorySelfHelp: true,
categoryAdvising: true,
}),
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return <Provider value={[mappedQuery, updateUrlState]}>{children}</Provider>
}

export function urlSearchCategoriesToStateSearchCategories(
qCategories: PageQueryType['qCategories']
): Partial<ParsedSearchTermCategoriesType> {
const noCategories = !qCategories || qCategories.length === 0
return {
categorySelfHelp: noCategories || qCategories.includes(1),
categoryAdvising: noCategories || qCategories.includes(2),
categoryClinics: noCategories || qCategories.includes(3),
categoryDisctrictOfficeHelp: noCategories || qCategories.includes(4),
categoryOnlineOffers: noCategories || qCategories.includes(5),
categorySelfHelp: !!qCategories?.includes(1),
categoryAdvising: !!qCategories?.includes(2),
categoryClinics: !!qCategories?.includes(3),
categoryDisctrictOfficeHelp: !!qCategories?.includes(4),
categoryOnlineOffers: !!qCategories?.includes(5),
}
}

export function stateSearchCategoriesToUrlSearchCategories(
searchTermCategories: Partial<ParsedSearchTermCategoriesType> | undefined
): PageQueryType['qCategories'] {
if (!searchTermCategories) return undefined
if (Object.entries(searchTermCategories).length === 0) return undefined
if (typeof searchTermCategories === 'undefined') return undefined
if (Object.entries(searchTermCategories).length === 0) return []
return [
searchTermCategories.categorySelfHelp && 1,
searchTermCategories.categoryAdvising && 2,
Expand Down
41 changes: 28 additions & 13 deletions src/lib/hooks/useActiveIdsBySearchTerm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ import { MinimalRecordType } from '@lib/mapRecordToMinimum'
import { useEffect, useState } from 'react'
import useSWR from 'swr'

interface SearchResult {
result?: TableRowType[]
error?: string
interface SearchResultBase {
params: Record<string, string>
}
interface SearchResultSuccess extends SearchResultBase {
result: TableRowType[]
total: number
}

interface SearchResultError extends SearchResultBase {
error: string
}

export type SearchResult = SearchResultSuccess | SearchResultError

interface UseActiveIdsBySearchTermReturnType {
ids: MinimalRecordType['id'][] | null
total: number
isLoading: boolean
key: string
}
Expand All @@ -24,42 +33,48 @@ export const useActiveIdsBySearchTerm =
(): UseActiveIdsBySearchTermReturnType => {
const [urlState] = useUrlState()
const [ids, setIds] = useState<MinimalRecordType['id'][] | null>(null)
const q = urlState.q || null
const qCategories = urlState.qCategories || null
const [total, setTotal] = useState<number>(10000)
const q = urlState.q ?? null
const qCategories = urlState.qCategories ?? null
const key = `search-${q || 'empty'}-${qCategories?.join('-') || 'empty'}`
const { data, isLoading } = useSWR<SearchResult, Error>(key, async () =>
searchRecords(q, qCategories)
searchRecords(q, qCategories, total)
)

useEffect(() => {
if (!data?.result) return
setIds(data.result.map((r) => r.id))
}, [data])
if (isLoading || !data || 'error' in data) return
setIds(data.result?.map((r) => r.id) || [])
setTotal(data.total)
}, [data, isLoading])

return {
ids,
isLoading,
total,
key: ids?.join('-') || 'empty',
}
}

async function searchRecords(
q: string | null,
qCategories: PageQueryType['qCategories'] | null
qCategories: PageQueryType['qCategories'] | null,
total: number
): Promise<SearchResult> {
if (q === null && qCategories === null) return { params: {} }
if (q === null && qCategories === null)
return { params: {}, result: [], total }
const url = new URLSearchParams({
query: q || '',
filters: qCategories ? categoriesToFilterString(qCategories) : '',
filters: categoriesToFilterString(qCategories),
})
const response = await fetch(`/api/search?${url.toString()}`)
if (!response.ok) throw new Error(await response.text())
return (await response.json()) as SearchResult
}

function categoriesToFilterString(
categories: PageQueryType['qCategories']
categories: PageQueryType['qCategories'] | null
): string {
if (categories === null) return ''
const categoriesState = urlSearchCategoriesToStateSearchCategories(categories)
return [
categoriesState.categoryAdvising && 'Beratung',
Expand Down
2 changes: 1 addition & 1 deletion src/lib/hooks/useFilteredFacilitiesCount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const useFilteredFacilitiesCount = (
})

if (isNotFilteredAtAll) {
setFilteredFacilities(facilitiesWithOnlyLabels)
setFilteredFacilities([])
return
}

Expand Down

0 comments on commit c17aa3b

Please sign in to comment.