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

feat(Search): Add text and categories search #190

Merged
merged 41 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6123ad7
fix(history): Fall back to home in bo back button when no history
vogelino Aug 3, 2023
5159f4f
Merge branch 'staging' into refactor/better-back-button
vogelino Aug 3, 2023
b5b78dd
Merge branch 'staging' into refactor/better-back-button
vogelino Aug 3, 2023
5bfaebf
fix(Links): Encode the back url into the query params to avoid loss o…
vogelino Aug 3, 2023
170eaca
chore(Button): Fix types of query in Button
vogelino Aug 3, 2023
d666aa4
refactor(Map): Scroll to previously clicked facility on back button c…
vogelino Aug 3, 2023
621b267
refactor(map): Cast ScrollBehavior type because typesrcipt does under…
vogelino Aug 3, 2023
de6c8a6
Merge branch 'staging' into fix/avoid-filters-reset
vogelino Aug 7, 2023
b7391cc
fix(UrlStateContext): Use router push with shallow statt window replace
vogelino Aug 7, 2023
e3b3c46
Merge branch 'fix/local-missing-map-markers' into fix/avoid-filters-r…
vogelino Aug 7, 2023
3e7ce23
feat(TextSearch): Add Text Search UI
vogelino Aug 8, 2023
20e4ed0
feat(useActiveIdsBySearchTerm): Filter data by text or category
vogelino Aug 8, 2023
f291cb6
feat(ActiveFiltersList): Show the text search and categories in the l…
vogelino Aug 8, 2023
c17aa3b
refactor(useActiveIdsBySearchTerm): Show all items for first two cate…
vogelino Aug 9, 2023
b12cdc7
refactor(FiltersList): Add loading indication when loading search res…
vogelino Aug 9, 2023
ac96662
feat(TextSearch): Add clear and submit buttons on text search
vogelino Aug 9, 2023
29052af
refactor(UrlStateContext): Limit text search length
vogelino Aug 9, 2023
6e9b1cb
refactor(MapHeader): Change active class and text of sidebar button
vogelino Aug 9, 2023
5f89dbf
Merge branch 'staging' into feat/full-text-search-frontend
vogelino Aug 9, 2023
e6f8764
refactor(MapHeader): Fix styles related to merging the address search…
vogelino Aug 9, 2023
703694c
fix(TextSearch): Remove class that misaligns buttons on text field
vogelino Aug 9, 2023
9cb3cca
fix(UrlStateContext): Make sure that the query string isn't lost when…
vogelino Aug 9, 2023
36962f7
fix(UrlStateContext): use ?? instead of pipe to avoid falsy values to…
vogelino Aug 9, 2023
a118c11
fix(UrlStateContext): Use window pushState instead of router push to …
vogelino Aug 10, 2023
3fc919f
refactor(UrlStateContext): Simplify initial default function
vogelino Aug 10, 2023
58d8920
Merge branch 'refactor/remember-scroll-position' into feat/full-text-…
vogelino Aug 10, 2023
186c89e
Merge branch 'fix/avoid-filters-reset' into feat/full-text-search-fro…
vogelino Aug 10, 2023
75e9cae
fix(UrlStateContext): Ensure the query is sent with every link
vogelino Aug 10, 2023
e3bb85e
fix(UrlStateContext): The text search is now clearable
vogelino Aug 10, 2023
7ad2dbc
fix(UrlStateContext): always update state to avoid old values
vogelino Aug 10, 2023
c2392fa
refactor(sofortige-hilfe): Remove log statement
vogelino Aug 10, 2023
254dbaf
refactor(UrlStateContext): Cleanup the url when empty values
vogelino Aug 10, 2023
4a9f6a6
refactor(UrlStateContext): Enforce cleaning of falsy values from url
vogelino Aug 10, 2023
e461666
fix(UrlStateContext): Avoid resetting everything when no categories s…
vogelino Aug 10, 2023
7d1008c
fix(UrlStateContext): Fix missing filters on page change when no cate…
vogelino Aug 10, 2023
e14a4fd
fix(facilityFilterUtil): Remove encodeURIComponent
vogelino Aug 10, 2023
a071c3d
feat(typos): Fix typos in function names
vogelino Aug 14, 2023
162d555
refactor(UrlStateContext): Attempt to simplify the UrlStateContext
vogelino Aug 14, 2023
115fbd7
fix(TextsContext): Fix text key name case change
vogelino Aug 14, 2023
9710845
fix(TextSearch): Use changed key Text in TextSearch
vogelino Aug 14, 2023
92ecbdb
fix(UrlStateContext): Fix typo in comment
vogelino Aug 15, 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
11 changes: 4 additions & 7 deletions src/components/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@
import { useMapHighlightMarker } from '@lib/hooks/useMapHighlightMarker'
import { useMaplibreMap } from '@lib/hooks/useMaplibreMap'
import { useFiltersWithActiveProp } from '@lib/hooks/useFiltersWithActiveProp'
import {
getActiveLabelGroups,
isFaclilityActive,
} from '@lib/facilityFilterUtil'
import { getActiveLabelGroups, isFacilityActive } from '@lib/facilityFilterUtil'
import { useActiveIdsBySearchTerm } from '@lib/hooks/useActiveIdsBySearchTerm'

interface MapType {
Expand Down Expand Up @@ -184,13 +181,13 @@

const updateFilteredFacilities = useCallback(() => {
if (!map || !markers || !mapLayersLoaded) return
const { activeTopcisLabels, activeTargetLabels } =
const { activeTopicsLabels, activeTargetLabels } =
getActiveLabelGroups(labels)
markers.forEach((marker) => {
const active = isFaclilityActive({
const active = isFacilityActive({
facilityId: marker.id,
facilityLabels: marker.labels,
activeTopcisLabels,
activeTopicsLabels,
activeTargetLabels,
activeIdsBySearchTerm: activeIdsBySearchTerm.ids,
})
Expand Down Expand Up @@ -289,7 +286,7 @@
})
setIsSpiderfied(true)
}
}, [query.id, markers, map])

Check warning on line 289 in src/components/Map/index.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'onMarkerClick'. Either include it or remove the dependency array. If 'onMarkerClick' changes too often, find the parent component that defines it and wrap that definition in useCallback

useEffect(() => {
if (!mapStylesLoaded || !markers || !map) return
Expand Down
4 changes: 2 additions & 2 deletions src/components/TextSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type CategoriesType = Partial<{
categoryAdvising: boolean
categoryClinics: boolean
categoryOnlineOffers: boolean
categoryDisctrictOfficeHelp: boolean
categoryDistrictOfficeHelp: boolean
}>
interface StateType {
text: string
Expand Down Expand Up @@ -152,7 +152,7 @@ export const getCategoriesTexts = (
categoryAdvising: texts.textSearchCategoryAdvising,
categoryClinics: texts.textSearchCategoryClinics,
categoryOnlineOffers: texts.textSearchCategoryOnlineOffers,
categoryDisctrictOfficeHelp: texts.textSearchCategoryDisctrictOfficeHelp,
categoryDistrictOfficeHelp: texts.textSearchcategoryDistrictOfficeHelp,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/TextsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ const defaultValue = {
textSearchCategoryAdvising: '',
textSearchCategoryClinics: '',
textSearchCategoryOnlineOffers: '',
textSearchCategoryDisctrictOfficeHelp: '',
textSearchcategoryDistrictOfficeHelp: '',
vogelino marked this conversation as resolved.
Show resolved Hide resolved
optionalFurtherSearchIntroText: '',
filtersButtonLoading: '',
}
Expand Down
184 changes: 109 additions & 75 deletions src/lib/UrlStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ParsedSearchTermCategoriesType = {
categorySelfHelp: boolean
categoryAdvising: boolean
categoryClinics: boolean
categoryDisctrictOfficeHelp: boolean
categoryDistrictOfficeHelp: boolean
categoryOnlineOffers: boolean
}

Expand All @@ -34,48 +34,28 @@ export const useUrlState = (): [PageQueryType, SetUrlStateHandlerType] =>
export const UrlStateProvider: FC = ({ children }) => {
const { query, pathname } = useRouter()
const mappedQuery = mapRawQueryToState(query)
const [latitude, setLatitude] = useState<number | undefined>(
mappedQuery.latitude
)
const [longitude, setLongitude] = useState<number | undefined>(
mappedQuery.longitude
)
const [zoom, setZoom] = useState<number | undefined>(mappedQuery.zoom)
const [tags, setTags] = useState<number[] | undefined>(mappedQuery.tags)
const [back, setBack] = useState<string | undefined>(mappedQuery.back)
const [q, setQ] = useState<string | undefined>(mappedQuery.q)
const [qCategories, setQCategories] = useState<
PageQueryType['qCategories'] | undefined
>(mappedQuery.qCategories)
const [latitude, setLatitude] = useState(mappedQuery.latitude)
const [longitude, setLongitude] = useState(mappedQuery.longitude)
const [zoom, setZoom] = useState(mappedQuery.zoom)
const [tags, setTags] = useState(mappedQuery.tags)
const [back, setBack] = useState(mappedQuery.back)
const [q, setQ] = useState(mappedQuery.q)
const [qCategories, setQCategories] = useState(mappedQuery.qCategories)

const updateUrlState = useCallback(
(newState: PageQueryType) => {
const path = typeof query.id === 'string' ? `/${query.id}` : pathname
const state = removeFalsyFromQuery({
latitude,
longitude,
zoom,
tags,
qCategories,
back,
q,
...newState,
})
state.q = truncateSearchTerm(state.q)
const paramsString = new URLSearchParams(
state as Record<string, string>
).toString()
const as = [path, paramsString].filter(Boolean).join('?')
window.history.replaceState(
{
...window.history.state,
...state,
as,
url: as,
},
'',
as
)
// GET A COMBINED STATE OBJECT WITH APPLIED DEFAULTS
const state = getFormattedUrlStateWithDefaults({
latitude,
longitude,
zoom,
tags,
back,
q,
qCategories,
})

// UTILITY FUNCTION TO UPDATE ALL REACT STATES
const updateReactUrlState = useCallback(
(state: PageQueryType) => {
setLatitude(state.latitude)
setLongitude(state.longitude)
setZoom(state.zoom)
Expand All @@ -84,51 +64,53 @@ export const UrlStateProvider: FC = ({ children }) => {
setQ(state.q)
setQCategories(state.qCategories)
},
[query.id, pathname, latitude, longitude, zoom, tags, qCategories, back, q]
[setLatitude, setLongitude, setZoom, setTags, setBack, setQ, setQCategories]
)

useEffect(() => {
setLatitude(mappedQuery.latitude)
setLongitude(mappedQuery.longitude)
setZoom(mappedQuery.zoom)
setTags(mappedQuery.tags)
setBack(mappedQuery.back)
setQ(mappedQuery.q)
setQCategories(mappedQuery.qCategories)
// UTILITY FUNCTION TO UPDATE URL STATE (Passed down the context as a setter)
const updateUrlState = useCallback(
(newState: PageQueryType) => {
const path = typeof query.id === 'string' ? `/${query.id}` : pathname

const fullNewState = removeFalsyFromQuery({
...state,
...newState,
})
fullNewState.q = truncateSearchTerm(fullNewState.q)

updateStateWindowLocation(path, fullNewState)
updateReactUrlState(fullNewState)
},
[query.id, pathname, state, updateReactUrlState]
)

// UPDATE REACT STATE ON NEXTJS ROUTER QUERY CHANGE
useEffect(
() => updateReactUrlState(mappedQuery),
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query])
[query]
)

// UPADTE URL WITH DEFAULTS ON INITIAL LOAD AND PAGE CHANGE
vogelino marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (typeof window === 'undefined') return
const urlParams = new URLSearchParams(window.location.search)
if (!urlParams.get('qCategories')) {
const query = mapRawQueryToState({
latitude: urlParams.get('latitude') || undefined,
longitude: urlParams.get('longitude') || undefined,
zoom: urlParams.get('zoom') || undefined,
tags: urlParams.getAll('tags')?.join(',') || undefined,
back: urlParams.get('back') || undefined,
q: urlParams.get('q') || undefined,

const urlStateFromWindowLocation = getUrlStateFromWindowLocation()
const allCatergoriesAreUnselected =
!urlStateFromWindowLocation.qCategories?.length

if (allCatergoriesAreUnselected) {
updateUrlState({
...urlStateFromWindowLocation,
qCategories: stateSearchCategoriesToUrlSearchCategories({
categorySelfHelp: true,
categoryAdvising: true,
})?.join(','),
}),
})
updateUrlState(query)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query.id, pathname])

const state = {
latitude,
longitude,
zoom,
tags: tags || [],
back,
q: q || '',
qCategories: qCategories || [],
}

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

Expand All @@ -139,7 +121,7 @@ export function urlSearchCategoriesToStateSearchCategories(
categorySelfHelp: !!qCategories?.includes(1),
categoryAdvising: !!qCategories?.includes(2),
categoryClinics: !!qCategories?.includes(3),
categoryDisctrictOfficeHelp: !!qCategories?.includes(4),
categoryDistrictOfficeHelp: !!qCategories?.includes(4),
categoryOnlineOffers: !!qCategories?.includes(5),
}
}
Expand All @@ -153,7 +135,59 @@ export function stateSearchCategoriesToUrlSearchCategories(
searchTermCategories.categorySelfHelp && 1,
searchTermCategories.categoryAdvising && 2,
searchTermCategories.categoryClinics && 3,
searchTermCategories.categoryDisctrictOfficeHelp && 4,
searchTermCategories.categoryDistrictOfficeHelp && 4,
searchTermCategories.categoryOnlineOffers && 5,
].filter(Boolean) as PageQueryType['qCategories']
}

function getUrlStateFromWindowLocation(): PageQueryType {
const urlParams = new URLSearchParams(window.location.search)
return mapRawQueryToState({
latitude: urlParams.get('latitude') || undefined,
longitude: urlParams.get('longitude') || undefined,
zoom: urlParams.get('zoom') || undefined,
tags: urlParams.getAll('tags')?.join(',') || undefined,
back: urlParams.get('back') || undefined,
q: urlParams.get('q') || undefined,
qCategories: urlParams.getAll('qCategories')?.join(',') || undefined,
})
}

function getFormattedUrlStateWithDefaults({
latitude,
longitude,
zoom,
tags,
back,
q,
qCategories,
}: PageQueryType): PageQueryType {
return {
latitude,
longitude,
zoom,
tags: tags || [],
back,
q: q || '',
qCategories: qCategories || [],
}
}

function updateStateWindowLocation(path: string, state: PageQueryType): void {
const paramsString = new URLSearchParams(
state as Record<string, string>
).toString()

const as = [path, paramsString].filter(Boolean).join('?')

window.history.replaceState(
{
...window.history.state,
...state,
as,
url: as,
},
'',
as
)
}
32 changes: 16 additions & 16 deletions src/lib/facilityFilterUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ interface GetFilteredFacilitiesPropType {
activeIdsBySearchTerm: MinimalRecordType['id'][] | null
}

interface IsFaclilityActivePropType {
interface isFacilityActivePropType {
activeTargetLabels: GristLabelType[]
activeTopcisLabels: GristLabelType[]
activeTopicsLabels: GristLabelType[]
facilityLabels: GristLabelType['id'][]
facilityId: MinimalRecordType['id']
activeIdsBySearchTerm: MinimalRecordType['id'][] | null
Expand All @@ -18,7 +18,7 @@ interface IsFaclilityActivePropType {
type GetFilterStatusType = (props: {
activeIdsBySearchTerm: MinimalRecordType['id'][] | null
activeTargetLabels: GristLabelType[]
activeTopcisLabels: GristLabelType[]
activeTopicsLabels: GristLabelType[]
}) => {
isFilteredByTopic: boolean
isFilteredByTarget: boolean
Expand All @@ -29,10 +29,10 @@ type GetFilterStatusType = (props: {
export const getFilterStatus: GetFilterStatusType = ({
activeIdsBySearchTerm,
activeTargetLabels,
activeTopcisLabels,
activeTopicsLabels,
}) => {
const isFilteredBySearchTerm = activeIdsBySearchTerm !== null
const isFilteredByTopic = activeTopcisLabels.length > 0
const isFilteredByTopic = activeTopicsLabels.length > 0
const isFilteredByTarget = activeTargetLabels.length > 0
return {
isFilteredByTopic,
Expand All @@ -49,13 +49,13 @@ export function truncateSearchTerm(searchTerm: unknown): string {
return searchTerm.slice(0, MAX_TEXT_SEARCH_STRING_LENGTH)
}

export const isFaclilityActive = ({
export const isFacilityActive = ({
activeTargetLabels,
activeTopcisLabels,
activeTopicsLabels,
facilityLabels,
facilityId,
activeIdsBySearchTerm,
}: IsFaclilityActivePropType): boolean => {
}: isFacilityActivePropType): boolean => {
const {
isNotFilteredAtAll,
isFilteredByTopic,
Expand All @@ -64,11 +64,11 @@ export const isFaclilityActive = ({
} = getFilterStatus({
activeIdsBySearchTerm,
activeTargetLabels,
activeTopcisLabels,
activeTopicsLabels,
})
if (isNotFilteredAtAll) return true
const hasAllOfTheActiveTopics =
activeTopcisLabels?.every((tag) => facilityLabels.includes(tag.id)) || false
activeTopicsLabels?.every((tag) => facilityLabels.includes(tag.id)) || false
const hasAnActiveTarget =
(activeTargetLabels[0] &&
facilityLabels.includes(activeTargetLabels[0].id)) ||
Expand All @@ -94,10 +94,10 @@ export const isFaclilityActive = ({
export const getActiveLabelGroups = (
labels: GristLabelType[]
): {
activeTopcisLabels: GristLabelType[]
activeTopicsLabels: GristLabelType[]
activeTargetLabels: GristLabelType[]
} => ({
activeTopcisLabels: labels.filter(
activeTopicsLabels: labels.filter(
(f) => f.isActive && f.fields.group2 !== 'zielpublikum'
),
activeTargetLabels: labels.filter(
Expand All @@ -110,19 +110,19 @@ export const getFilteredFacilities = ({
labels,
activeIdsBySearchTerm,
}: GetFilteredFacilitiesPropType): MinimalRecordType[] => {
const { activeTopcisLabels, activeTargetLabels } =
const { activeTopicsLabels, activeTargetLabels } =
getActiveLabelGroups(labels)
const { isNotFilteredAtAll } = getFilterStatus({
activeIdsBySearchTerm,
activeTargetLabels,
activeTopcisLabels,
activeTopicsLabels,
})
if (isNotFilteredAtAll) return facilities
return facilities.filter((facility) => {
return isFaclilityActive({
return isFacilityActive({
facilityLabels: facility.labels,
facilityId: facility.id,
activeTopcisLabels,
activeTopicsLabels,
activeTargetLabels,
activeIdsBySearchTerm,
})
Expand Down
Loading
Loading