Skip to content

Commit

Permalink
refactor(FiltersList): Add loading indication when loading search res…
Browse files Browse the repository at this point in the history
…ults
  • Loading branch information
vogelino committed Aug 9, 2023
1 parent c17aa3b commit b12cdc7
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 35 deletions.
3 changes: 2 additions & 1 deletion pages/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const MapPage: Page<MapProps> = ({ records: originalRecords }) => {
[getDistanceToUser, useGeolocation, defaultSort, sortByTagsCount]
)

const tagsKey = urlState.tags?.join('-') || ''
useEffect(() => {
const filteredRecords = getFilteredFacilities({
facilities: originalRecords,
Expand All @@ -111,7 +112,7 @@ const MapPage: Page<MapProps> = ({ records: originalRecords }) => {
})
return setFilteredRecords(sortFacilities(filteredRecords))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlState.tags?.join('-'), activeIdsBySearchTerm.key])
}, [tagsKey, activeIdsBySearchTerm.key, activeIdsBySearchTerm.isLoading])

return (
<>
Expand Down
52 changes: 31 additions & 21 deletions src/components/FiltersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Button } from './Button'
import { Arrow } from './icons/Arrow'
import TextSearch from './TextSearch'
import { useActiveIdsBySearchTerm } from '@lib/hooks/useActiveIdsBySearchTerm'
import { Spinner } from './icons/Spinner'

export const FiltersList: FC<{
recordsWithOnlyLabels: RecordsWithOnlyLabelsType[]
Expand All @@ -44,6 +45,7 @@ export const FiltersList: FC<{
recordsWithOnlyLabels
)
const { isLoading: textSearchLoading, total } = useActiveIdsBySearchTerm()
const fieldsDisabled = textSearchLoading

const group1 = labels.filter(({ fields }) => fields.group2 === 'gruppe-1')
const group2 = labels.filter(({ fields }) => fields.group2 === 'gruppe-2')
Expand All @@ -57,21 +59,17 @@ export const FiltersList: FC<{
.map((label) => label.id)

const [activeTargetGroupId, setActiveTargetGroupId] = useState(
queryTagIds.find((tagId) => {
return targetGroupIds.includes(tagId)
})
queryTagIds.find((tagId) => targetGroupIds.includes(tagId)) || null
)

const tagsKey = queryTagIds.join('-')
useEffect(() => {
if (queryTagIds.length === 0) return

const currentTargetGroupId = queryTagIds.find((tagId) =>
targetGroupIds.includes(tagId)
)
if (currentTargetGroupId) {
setActiveTargetGroupId(currentTargetGroupId)
}
}, [queryTagIds, targetGroupIds])
setActiveTargetGroupId(currentTargetGroupId || null)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tagsKey, targetGroupIds])

const someGroupFiltersActive = labels
.filter(({ fields }) => fields.group2 !== 'zielpublikum')
Expand All @@ -88,10 +86,12 @@ export const FiltersList: FC<{
<FiltersTagsList
filters={[...group1, ...group2, ...group3]}
onLabelClick={updateFilters}
disabled={fieldsDisabled}
/>
</ul>
{someGroupFiltersActive && (
<button
disabled={fieldsDisabled}
onClick={() =>
updateFilters(
queryTagIds.filter((f) => {
Expand All @@ -102,9 +102,13 @@ export const FiltersList: FC<{
}
className={classNames(
`text-lg leading-6 text-left font-normal mb-8`,
`focus:outline-none focus:ring-2 focus:ring-primary`,
`focus:ring-offset-2 focus:ring-offset-white`,
`underline text-gray-80 hover-primary transition-colors`
!fieldsDisabled && [
`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary`,
`focus-visible:ring-offset-2 focus-visible:ring-offset-white`,
`underline text-gray-80 hover-primary`,
],
fieldsDisabled && `text-gray-40`,
`transition motion-reduce:transition-none`
)}
>
{texts.reset}
Expand All @@ -126,10 +130,12 @@ export const FiltersList: FC<{
categories={urlSearchCategoriesToStateSearchCategories(
urlState.qCategories
)}
disabled={fieldsDisabled}
/>
<div className="block w-full @md:w-[324px] z-10">
<Listbox
label={texts.filtersSearchTargetLabel}
disabled={fieldsDisabled}
options={targetGroups
.sort((a, b) => {
if (!a.fields.order) return 1
Expand Down Expand Up @@ -164,14 +170,14 @@ export const FiltersList: FC<{
break
case targetGroupAlreadyInUrl && !hasValidTargetGroup:
updateFilters([...tagsWithoutOldTargetGroup])
setActiveTargetGroupId(undefined)
setActiveTargetGroupId(null)
break
case !targetGroupAlreadyInUrl && hasValidTargetGroup:
updateFilters([...queryTagIds, selectedValue as number])
break
case !targetGroupAlreadyInUrl && !hasValidTargetGroup:
updateFilters([...queryTagIds])
setActiveTargetGroupId(undefined)
setActiveTargetGroupId(null)
break
default:
break
Expand All @@ -185,7 +191,7 @@ export const FiltersList: FC<{
<SwitchButton
value={useGeolocation}
onToggle={setGeolocationUsage}
disabled={geolocationIsForbidden}
disabled={geolocationIsForbidden || fieldsDisabled}
tooltip={geolocationIsForbidden ? texts.geolocationForbidden : ``}
>
{texts.filtersGeoSearchLabel}
Expand All @@ -205,13 +211,17 @@ export const FiltersList: FC<{
})
}}
icon={
<Arrow
className={classNames(
'transition-transform group-hover:translate-x-0.5 group-disabled:group-hover:translate-x-0'
)}
/>
textSearchLoading ? (
<Spinner className={classNames('animate-spin')} />
) : (
<Arrow
className={classNames(
'transition-transform group-hover:translate-x-0.5 group-disabled:group-hover:translate-x-0'
)}
/>
)
}
disabled={filteredFacilitiesCount === 0}
disabled={filteredFacilitiesCount === 0 || fieldsDisabled}
tooltip={
filteredFacilitiesCount === 0 && (
<span>{texts.filtersButtonTextFilteredNoResultsHint}</span>
Expand Down
3 changes: 3 additions & 0 deletions src/components/FiltersTagsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ interface FiltersTagsListPropType {
onLabelClick?: (activeFilters: number[]) => void
filters: FiltersWithActivePropType[]
className?: string
disabled?: boolean
}

export const FiltersTagsList: FC<FiltersTagsListPropType> = ({
onLabelClick,
filters,
className,
disabled = false,
}) => {
const [urlState] = useUrlState()
const activeFilters = urlState.tags || []
Expand All @@ -21,6 +23,7 @@ export const FiltersTagsList: FC<FiltersTagsListPropType> = ({
<>
{filters.map((filter) => (
<Label
disabled={disabled}
label={filter}
key={filter.id}
isActive={filter.isActive}
Expand Down
20 changes: 13 additions & 7 deletions src/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export const Label: FC<{
className?: string
onClick?: (id: number) => void
isInteractive?: boolean
disabled?: boolean
}> = ({
label,
onClick = () => undefined,
isActive,
className = '',
isInteractive = true,
disabled = false,
}) => {
const Icon = icons[label.fields.icon as keyof typeof icons] || Fragment
return (
Expand All @@ -24,20 +26,24 @@ export const Label: FC<{
className={classNames(
className,
`py-1.5 text-lg flex gap-2 text-left leading-6 pl-2 pr-3 rounded`,
`transition motion-reduce:transition-none`,
isActive &&
isInteractive &&
isActive &&
!disabled &&
`bg-primary border-primary text-white hover:bg-purple-400`,
(!isActive || !isInteractive) && ` border-gray-20`,
(!isInteractive || disabled) && `ring-1 ring-inset ring-gray-20`,
!isActive &&
!disabled &&
isInteractive &&
'bg-purple-50 hover:bg-purple-200 text-purple-700',
isInteractive && [
`focus:outline-none focus:ring-2 focus:ring-primary`,
`focus:ring-offset-2 focus:ring-offset-white`,
],
isInteractive &&
!disabled && [
`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary`,
`focus-visible:ring-offset-2 focus-visible:ring-offset-white`,
],
!isInteractive && `cursor-default`
)}
disabled={!isInteractive}
disabled={!isInteractive || disabled}
>
{label.fields.icon && (
<Icon
Expand Down
16 changes: 11 additions & 5 deletions src/components/Listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ListboxType {
onChange?: (selectedValue: number | null) => void
nullSelectionLabel?: string
className?: string
disabled?: boolean
}

export const Listbox: FC<ListboxType> = ({
Expand All @@ -25,18 +26,23 @@ export const Listbox: FC<ListboxType> = ({
onChange = () => undefined,
nullSelectionLabel = 'Keine Präferenz',
className = '',
disabled = false,
}) => {
const handleChange = (selectedOption: number): void =>
onChange(selectedOption)

const mergedOptions = [{ label: nullSelectionLabel, value: null }, ...options]
const selectedOption = mergedOptions.find(
(option) => option.value === activeValue
)
const nullSection = { label: nullSelectionLabel, value: null }
const mergedOptions = [nullSection, ...options]
const selectedOption =
mergedOptions.find((option) => option.value === activeValue) || nullSection

return (
<div className={classNames(className, 'w-full')}>
<HeadlessListbox value={activeValue} onChange={handleChange}>
<HeadlessListbox
value={activeValue}
onChange={handleChange}
disabled={disabled}
>
<HeadlessListbox.Label
className={classNames('w-full flex justify-between', 'text-lg')}
>
Expand Down
5 changes: 5 additions & 0 deletions src/components/TextSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface StateType {

interface TextSearchProps extends StateType {
onChange: (state: Partial<StateType>) => void
disabled?: boolean
}

type CategoriesTextMapType = Record<keyof CategoriesType, string>
Expand All @@ -25,6 +26,7 @@ function TextSearch({
text: initialText,
categories,
onChange,
disabled = false,
}: TextSearchProps): JSX.Element {
const texts = useTexts()
const [text, setText] = useState(initialText || '')
Expand All @@ -43,6 +45,7 @@ function TextSearch({
<fieldset
className="w-full @md:w-[324px]"
aria-labelledby="textSearchLabel"
disabled={disabled}
>
<TextInput
id="textSearch"
Expand All @@ -58,12 +61,14 @@ function TextSearch({
}
}}
value={text}
disabled={disabled}
/>
{checkboxes.map(({ id, labelText }) => (
<Checkbox
key={id}
id={id}
labelText={labelText}
disabled={disabled}
onChange={(evt) => {
onChange({
text,
Expand Down
27 changes: 27 additions & 0 deletions src/components/icons/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC, SVGAttributes } from 'react'

export const Spinner: FC<{
className?: SVGAttributes<SVGSVGElement>['className']
}> = ({ className = '' }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<line x1="12" y1="2" x2="12" y2="6"></line>
<line x1="12" y1="18" x2="12" y2="22"></line>
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
<line x1="2" y1="12" x2="6" y2="12"></line>
<line x1="18" y1="12" x2="22" y2="12"></line>
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
</svg>
)
11 changes: 10 additions & 1 deletion src/lib/UrlStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ export const UrlStateProvider: FC = ({ children }) => {
if (typeof window === 'undefined') return
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('qCategories')) return
const parsedQuery = mapRawQueryToState({
q: urlParams.get('q') || undefined,
qCategories: urlParams.getAll('qCategories') || undefined,
tags: urlParams.getAll('tags') || undefined,
back: urlParams.get('back') || undefined,
latitude: urlParams.get('latitude') || undefined,
longitude: urlParams.get('longitude') || undefined,
zoom: urlParams.get('zoom') || undefined,
})
updateUrlState({
...query,
...parsedQuery,
qCategories: stateSearchCategoriesToUrlSearchCategories({
categorySelfHelp: true,
categoryAdvising: true,
Expand Down

0 comments on commit b12cdc7

Please sign in to comment.