Skip to content

Commit

Permalink
feat: added dynamic filters based on facets and search query
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasGross committed Oct 14, 2024
1 parent a165980 commit afaa4c6
Show file tree
Hide file tree
Showing 12 changed files with 1,400 additions and 1,157 deletions.
44 changes: 39 additions & 5 deletions app/search/fetchSearchResult.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useSearchWithPaginationQuery } from '@/lib/graphql/generated/fbi/graphql'
import {
FacetField,
useSearchFacetsQuery,
useSearchWithPaginationQuery
} from '@/lib/graphql/generated/fbi/graphql'
import getQueryClient from '@/lib/getQueryClient'
import { QueryClient } from '@tanstack/react-query'

const prefetchSearchResult = async (q: string) => {
const queryClient = getQueryClient()

const prefetchSearchResult = async (q: string, queryClient: QueryClient) => {
await queryClient.prefetchQuery({
queryKey: useSearchWithPaginationQuery.getKey({
q: { all: q },
Expand All @@ -20,4 +23,35 @@ const prefetchSearchResult = async (q: string) => {
return queryClient
}

export default prefetchSearchResult
const prefetchSearchFacets = async (q: string, queryClient: QueryClient) => {
await queryClient.prefetchQuery({
queryKey: useSearchFacetsQuery.getKey({
q: { all: q },
facetLimit: 100,
facets: [
'materialTypesGeneral',
'mainLanguages',
'age',
'lix',
'subjects',
'let'
] as FacetField[]
}),
queryFn: useSearchFacetsQuery.fetcher({
q: { all: q },
facetLimit: 100,
facets: [
'materialTypesGeneral',
'mainLanguages',
'age',
'lix',
'subjects',
'let'
] as FacetField[]
})
})

return queryClient
}

export { prefetchSearchFacets, prefetchSearchResult }
10 changes: 8 additions & 2 deletions app/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { dehydrate, HydrationBoundary } from '@tanstack/react-query'

import SearchPageLayout from '@/components/pages/searchPageLayout/SearchPageLayout'

import prefetchSearchResult from './fetchSearchResult.server'
import {
prefetchSearchFacets,
prefetchSearchResult
} from './fetchSearchResult.server'
import { Suspense } from 'react'
import getQueryClient from '@/lib/getQueryClient'

const Page = async ({
searchParams: { q }
}: {
searchParams: { q: string }
}) => {
const queryClient = await prefetchSearchResult(q)
const queryClient = getQueryClient()
await prefetchSearchResult(q, queryClient)
await prefetchSearchFacets(q, queryClient)

return (
<HydrationBoundary state={dehydrate(queryClient)}>
Expand Down
70 changes: 35 additions & 35 deletions codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,40 @@ import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
overwrite: true,
generates: {
// 'lib/graphql/generated/dpl-cms/graphql.tsx': {
// documents: '**/*.dpl-cms.graphql',
// // TODO: Make this configurable
// schema: 'http://dapple-cms.docker/graphql',
// plugins: [
// 'typescript',
// 'typescript-operations',
// 'typescript-react-query'
// ],
// config: {
// defaultScalarType: 'unknown',
// reactQueryVersion: 5,
// exposeFetcher: true,
// exposeQueryKeys: true,
// addSuspenseQuery: true,
// fetcher: {
// // TODO: Make this configurable
// endpoint: 'http://dapple-cms.docker/graphql',
// fetchParams: JSON.stringify({
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// }
// },
// hooks: {
// afterOneFileWrite: ['yarn eslint --fix']
// }
// },
// 'lib/graphql/generated/dpl-cms/graphql.schema.json': {
// // TODO: Make this configurable
// schema: 'http://dapple-cms.docker/graphql',
// plugins: ['introspection']
// },
'lib/graphql/generated/dpl-cms/graphql.tsx': {
documents: '**/*.dpl-cms.graphql',
// TODO: Make this configurable
schema: 'http://dapple-cms.docker/graphql',
plugins: [
'typescript',
'typescript-operations',
'typescript-react-query'
],
config: {
defaultScalarType: 'unknown',
reactQueryVersion: 5,
exposeFetcher: true,
exposeQueryKeys: true,
addSuspenseQuery: true,
fetcher: {
// TODO: Make this configurable
endpoint: 'http://dapple-cms.docker/graphql',
fetchParams: JSON.stringify({
headers: {
'Content-Type': 'application/json'
}
})
}
},
hooks: {
afterOneFileWrite: ['yarn eslint --fix']
}
},
'lib/graphql/generated/dpl-cms/graphql.schema.json': {
// TODO: Make this configurable
schema: 'http://dapple-cms.docker/graphql',
plugins: ['introspection']
},
'lib/graphql/generated/fbi/graphql.tsx': {
documents: '**/*.fbi.graphql',
schema: [
Expand Down Expand Up @@ -66,7 +66,7 @@ const config: CodegenConfig = {
typeNames: 'change-case-all#pascalCase',
transformUnderscore: true
},
fetcher: '@/lib/fetchers/fbi.fetcher#fetchData'
fetcher: '@/lib/graphql/fetchers/fbi.fetcher#fetchData'
},
hooks: {
afterOneFileWrite: ['yarn eslint --fix']
Expand Down
63 changes: 63 additions & 0 deletions components/pages/searchPageLayout/SearchFilterBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import { Badge } from '@/components/ui/badge'
import { SearchFacetsQuery } from '@/lib/graphql/generated/fbi/graphql'
import { cn } from '@/lib/utils'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import React, { useEffect } from 'react'

type facets = SearchFacetsQuery['search']['facets']

const SearchFilterBar = ({ facets }: { facets: facets }) => {
const router = useRouter()
const searchParams = useSearchParams()

const toggleFilter = (filterName: string, value: string) => {
const searchParams = new URLSearchParams(window.location.search)

if (searchParams.has(filterName)) {
if (searchParams.get(filterName) === value) {
searchParams.delete(filterName)
} else {
searchParams.delete(filterName)
searchParams.append(filterName, value)
}
} else {
searchParams.append(filterName, value)
}

const searchParamsString = searchParams.toString()

router.push(
'/search' + searchParamsString ? `?${searchParamsString}` : '',
{ scroll: false }
)
}

return (
<div className="flex flex-wrap gap-4">
{facets.length > 0 &&
facets.map((facet) => (
<div key={facet.name} className="space-y-2">
<h3 className="text-typo-caption uppercase">{facet.name}</h3>
<div className="flex flex-wrap gap-1">
{facet.values.map((value, index) => (
<button
onClick={() => toggleFilter(facet.name, value.term)}
className={cn(
`whitespace-nowrap rounded-lg bg-background-foreground px-4 py-2`,
value.term === searchParams.get(facet.name) && 'bg-primary'
)}
key={index}
>
{value.term}
</button>
))}
</div>
</div>
))}
</div>
)
}

export default SearchFilterBar
123 changes: 111 additions & 12 deletions components/pages/searchPageLayout/SearchPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,138 @@

import { useSearchParams } from 'next/navigation'

import { useSearchWithPaginationQuery } from '@/lib/graphql/generated/fbi/graphql'
import {
FacetField,
FacetValue,
useSearchFacetsQuery,
useSearchWithPaginationQuery
} from '@/lib/graphql/generated/fbi/graphql'
import SearchFilterBar from './SearchFilterBar'
import { useEffect } from 'react'

const facetDefinitions = [
'materialTypesGeneral',
'mainLanguages',
'age',
'lix',
'subjects',
'let'
] as FacetField[]

const branchIds = [
'775120',
'775122',
'775144',
'775167',
'775146',
'775168',
'751010',
'775147',
'751032',
'751031',
'775126',
'751030',
'775149',
'775127',
'775160',
'775162',
'775140',
'751009',
'751029',
'751027',
'751026',
'751025',
'775133',
'751024',
'775100',
'775170',
'775150',
'775130'
] as string[]

export type FilterItemTerm = Omit<FacetValue, '__typename'>

export const formatFacetTerms = (filters: {
[key: string]: { [key: string]: FilterItemTerm }
}) => {
return Object.keys(filters).reduce(
(acc, key) => ({
...acc,
[key]: Object.keys(filters[key])
}),
{}
)
}

const SearchPageLayout = ({ searchQuery }: { searchQuery?: string }) => {
const searchParams = useSearchParams()
const q = searchQuery || searchParams.get('q') || ''

const facetsForSearchRequest = facetDefinitions.reduce(
(acc, facetDefinition) => {
const value = searchParams.get(facetDefinition)
if (value) {
return {
...acc,
[facetDefinition]: [value]
}
}

return acc
},
{} as { [key: string]: string[] }
)

console.log('facetsForSearchRequest', facetsForSearchRequest)

const { data, error, isLoading, isPending, isFetching } =
useSearchWithPaginationQuery({
q: { all: q },
offset: 0,
limit: 10,
filters: {}
filters: {
// TODO: Add filters though endpoint
branchId: branchIds,
...facetsForSearchRequest
}
})

const {
data: facetData,
error: facetError,
isLoading: facetIsLoading,
isPending: facetIsPending,
isFetching: facetIsFetching
} = useSearchFacetsQuery({
q: { all: q },
facetLimit: 100,
facets: facetDefinitions,
filters: {
branchId: branchIds,
...facetsForSearchRequest
}
})

useEffect(() => {
// const queryParams = new URLSearchParams(window.location.search);
}, [searchParams])

if (error) {
return <div>Error: {<pre>{JSON.stringify(error, null, 2)}</pre>}</div>
}

if (!data) return null
const {
search: { hitcount, works }
} = data

return (
<div className="content-container">
<h1 className="text-typo-heading-2">{`Viser resultater for "${q}" (${hitcount})`}</h1>
<div>
<SearchFilterBar facets={facetData?.search?.facets || []} />
<h1 className="text-typo-heading-2">{`Viser resultater for "${q}" ${data?.search.hitcount ? '(' + data?.search.hitcount + ')' : ''}`}</h1>
{isFetching && <p>isFetching...</p>}
{isLoading && <p>isLoading...</p>}
{isPending && <p>isPending...</p>}
{hitcount === 0 && <p>Ingen resultater</p>}
{data?.search.hitcount === 0 && <p>Ingen resultater</p>}
<div className="gap-grid-gap-x grid grid-cols-3">
{hitcount > 0 &&
works.map((work) => (
{data?.search?.hitcount &&
data?.search?.hitcount > 0 &&
data.search.works.map((work) => (
<div key={work.workId} className="bg-background-foreground p-4">
<p>{work.titles.full}</p>

Expand Down
Loading

0 comments on commit afaa4c6

Please sign in to comment.