skyderby/skyderby

View on GitHub
app/javascript/components/FlightProfiles/usePageParams.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { useCallback, useMemo, startTransition } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import {
  extractParamsFromUrl as extractTrackParamsFromUrl,
  mapParamsToUrl as mapTrackParamsToUrl,
  IndexParams,
  FilterTuple
} from 'api/tracks'
import isEqual from 'lodash.isequal'

interface FlightProfilesURLParams {
  tracksParams: Omit<IndexParams, 'filters'> & {
    filters: FilterTuple[]
  }
  selectedTracks: number[]
  selectedTerrainProfile: number | null
  additionalTerrainProfiles: number[]
}

const extractParamsFromUrl = (search: string): FlightProfilesURLParams => {
  const tracksParams = Object.assign(extractTrackParamsFromUrl(search, 'tracks'), {
    activity: 'base'
  })

  const urlParams = new URLSearchParams(search)

  const selectedTracks = urlParams.getAll('selectedTracks[]').filter(Boolean).map(Number)

  const selectedTerrainProfile = urlParams.get('selectedTerrainProfile')
    ? Number(urlParams.get('selectedTerrainProfile'))
    : null

  const additionalTerrainProfiles = urlParams
    .getAll('additionalTerrainProfiles[]')
    .filter(Boolean)
    .map(Number)

  return {
    tracksParams,
    selectedTracks,
    selectedTerrainProfile,
    additionalTerrainProfiles
  }
}

const mapParamsToUrl = (params: FlightProfilesURLParams): string => {
  const { activity, ...mergedTracksParams } = params.tracksParams
  const tracksParams = mapTrackParamsToUrl(mergedTracksParams, 'tracks')

  const urlParams = new URLSearchParams(tracksParams)

  params.selectedTracks.forEach(id => urlParams.append('selectedTracks[]', String(id)))

  if (params.selectedTerrainProfile) {
    urlParams.set('selectedTerrainProfile', String(params.selectedTerrainProfile))
  }

  Array.from(new Set(params.additionalTerrainProfiles)).forEach(id =>
    urlParams.append('additionalTerrainProfiles[]', String(id))
  )

  return urlParams.toString() === '' ? '' : '?' + urlParams.toString()
}

const usePageParams = () => {
  const navigate = useNavigate()
  const location = useLocation()
  const params = useMemo(() => extractParamsFromUrl(location.search), [location.search])

  const buildUrl = useCallback(
    (newParams: Partial<FlightProfilesURLParams>) => {
      const mergedParams = {
        ...params,
        ...newParams,
        tracksParams: {
          ...params.tracksParams,
          ...newParams.tracksParams
        }
      }

      return mapParamsToUrl(mergedParams)
    },
    [params]
  )

  const updateFilters = useCallback(
    (filters: FilterTuple[]) =>
      startTransition(() => {
        navigate(`${location.pathname}${buildUrl({ tracksParams: { filters } })}`, {
          replace: true
        })
      }),
    [buildUrl, navigate, location.pathname]
  )

  const setSelectedTerrainProfile = useCallback(
    (selectedTerrainProfile: number | null) =>
      navigate(`${location.pathname}${buildUrl({ selectedTerrainProfile })}`, {
        replace: true
      }),
    [buildUrl, navigate, location.pathname]
  )

  const setAdditionalTerrainProfiles = useCallback(
    (ids: number[]) => {
      if (isEqual(ids, params.additionalTerrainProfiles)) return

      navigate(`${location.pathname}${buildUrl({ additionalTerrainProfiles: ids })}`, {
        replace: true
      })
    },
    [buildUrl, navigate, location.pathname, params]
  )

  const deleteAdditionalTerrainProfile = useCallback(
    (idToRemove: number) => {
      navigate(
        `${location.pathname}${buildUrl({
          additionalTerrainProfiles: params.additionalTerrainProfiles.filter(
            id => id !== idToRemove
          )
        })}`,
        { replace: true }
      )
    },
    [params, buildUrl, navigate, location.pathname]
  )

  const toggleTrack = useCallback(
    (trackId: number) => {
      const trackSelected = params.selectedTracks.includes(trackId)
      const selectedTracks = trackSelected
        ? params.selectedTracks.filter(el => el !== trackId)
        : [...params.selectedTracks, trackId]

      navigate(`${location.pathname}${buildUrl({ selectedTracks })}`, { replace: true })
    },
    [params, buildUrl, navigate, location.pathname]
  )

  return {
    params,
    updateFilters,
    setSelectedTerrainProfile,
    setAdditionalTerrainProfiles,
    deleteAdditionalTerrainProfile,
    toggleTrack
  }
}

export default usePageParams