Skip to content

Commit

Permalink
feat: filter by search input and sync with url params
Browse files Browse the repository at this point in the history
  • Loading branch information
amalcaraz committed Apr 16, 2024
1 parent 6b2206a commit d5b2652
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 26 deletions.
3 changes: 3 additions & 0 deletions src/components/common/Viewport/cmp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { useRequestRewards } from '@/hooks/common/useRequestEntity/useRequestRew
import { useRequestCCNsFeed } from '@/hooks/common/useRequestEntity/useRequestCCNsFeed'
import { useRequestRewardsFeed } from '@/hooks/common/useRequestEntity/useRequestRewardsFeed'
import { useRequestAlephNodes } from '@/hooks/common/useRequestEntity/useRequestAlephNodes'
import { useFilters } from '@/hooks/common/useFilters'

export const Viewport = ({ children }: ViewportProps) => {
useFilters({ syncUrl: true })

useRequestAlephNodes({})
useRequestRewards({})

Expand Down
43 changes: 30 additions & 13 deletions src/hooks/common/useComputeResourceNodes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useAppState } from '@/contexts/appState'
import { CRN, NodeLastVersions } from '@/domain/node'
import { useDebounceState } from '@aleph-front/core'
import { Account } from 'aleph-sdk-ts/dist/accounts/account'
import { ChangeEvent, useCallback, useMemo, useState } from 'react'
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import { UseSortedListReturn, useSortedList } from './useSortedList'
import { UseFiltersReturn } from './useFilters'
import { useFilter } from './useFilter'

export type UseComputeResourceNodesProps = {
nodes?: CRN[]
Expand All @@ -14,11 +15,11 @@ export type UseComputeResourceNodesReturn = {
accountBalance?: number
nodes?: CRN[]
filteredNodes?: CRN[]
filter: string
filter?: string
lastVersion?: NodeLastVersions
handleSortItems: UseSortedListReturn<CRN>['handleSortItems']
handleFilterChange: (e: ChangeEvent<HTMLInputElement>) => void
}
} & Pick<UseFiltersReturn, 'filters'>

export function useComputeResourceNodes({
nodes: prefetchNodes,
Expand All @@ -27,22 +28,37 @@ export function useComputeResourceNodes({
const { account, balance: accountBalance = 0 } = state.account
const { data: lastVersion } = state.lastCRNVersion
const { entities: data } = state.crns
const filters = state.filter

// -----------------------------

const [filter, setFilter] = useState('')
const [crnqFilter, setCrnqFilter] = useFilter({
key: 'crnq',
debounced: 200,
})

const [filter, setFilter] = useState<string>()

const handleFilterChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const filter = e.target.value
setFilter(filter)
setCrnqFilter(filter)
},
[setCrnqFilter],
)

const debouncedFilter = useDebounceState(filter, 200)
useEffect(() => {
if (filter !== undefined) return
if (!crnqFilter) return

const handleFilterChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const filter = e.target.value
setFilter(filter)
}, [])
setFilter(crnqFilter)
}, [crnqFilter, filter])

// -----------------------------

const filterNodes = useCallback(
(query: string, nodes?: CRN[]): CRN[] | undefined => {
(query?: string, nodes?: CRN[]): CRN[] | undefined => {
if (!nodes) return
if (!query) return nodes

Expand All @@ -65,8 +81,8 @@ export function useComputeResourceNodes({
}, [prefetchNodes, data])

const filteredNodes = useMemo(
() => filterNodes(debouncedFilter, nodes),
[filterNodes, debouncedFilter, nodes],
() => filterNodes(crnqFilter, nodes),
[filterNodes, crnqFilter, nodes],
)

// -----------------------------
Expand All @@ -84,6 +100,7 @@ export function useComputeResourceNodes({
filteredNodes: sortedFilteredNodes,
filter,
lastVersion,
filters,
handleSortItems,
handleFilterChange,
}
Expand Down
43 changes: 30 additions & 13 deletions src/hooks/common/useCoreChannelNodes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useAppState } from '@/contexts/appState'
import { CCN, NodeLastVersions } from '@/domain/node'
import { useDebounceState } from '@aleph-front/core'
import { Account } from 'aleph-sdk-ts/dist/accounts/account'
import { ChangeEvent, useCallback, useMemo, useState } from 'react'
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import { UseSortedListReturn, useSortedList } from './useSortedList'
import { useFilter } from './useFilter'
import { UseFiltersReturn } from './useFilters'

export type UseCoreChannelNodesProps = {
nodes?: CCN[]
Expand All @@ -14,11 +15,11 @@ export type UseCoreChannelNodesReturn = {
accountBalance?: number
nodes?: CCN[]
filteredNodes?: CCN[]
filter: string
filter?: string
lastVersion?: NodeLastVersions
handleSortItems: UseSortedListReturn<CCN>['handleSortItems']
handleFilterChange: (e: ChangeEvent<HTMLInputElement>) => void
}
} & Pick<UseFiltersReturn, 'filters'>

export function useCoreChannelNodes({
nodes: prefetchNodes,
Expand All @@ -27,24 +28,39 @@ export function useCoreChannelNodes({
const { account, balance: accountBalance = 0 } = state.account
const { data: lastVersion } = state.lastCCNVersion
const { entities: data } = state.ccns
const filters = state.filter

const nodes = prefetchNodes || data

// -----------------------------

const [filter, setFilter] = useState('')
const [ccnqFilter, setCcnqFilter] = useFilter({
key: 'ccnq',
debounced: 200,
})

const [filter, setFilter] = useState<string>()

const handleFilterChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const filter = e.target.value
setFilter(filter)
setCcnqFilter(filter)
},
[setCcnqFilter],
)

const debouncedFilter = useDebounceState(filter, 200)
useEffect(() => {
if (filter !== undefined) return
if (!ccnqFilter) return

const handleFilterChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const filter = e.target.value
setFilter(filter)
}, [])
setFilter(ccnqFilter)
}, [ccnqFilter, filter])

// -----------------------------

const filterNodes = useCallback(
(query: string, nodes?: CCN[]): CCN[] | undefined => {
(query?: string, nodes?: CCN[]): CCN[] | undefined => {
if (!nodes) return
if (!query) return nodes

Expand All @@ -56,8 +72,8 @@ export function useCoreChannelNodes({
)

const filteredNodes = useMemo(
() => filterNodes(debouncedFilter, nodes),
[filterNodes, debouncedFilter, nodes],
() => filterNodes(ccnqFilter, nodes),
[filterNodes, ccnqFilter, nodes],
)

const presortedFilteredNodes = useMemo(() => {
Expand All @@ -80,6 +96,7 @@ export function useCoreChannelNodes({
filteredNodes: sortedFilteredNodes,
filter,
lastVersion,
filters,
handleSortItems,
handleFilterChange,
}
Expand Down
43 changes: 43 additions & 0 deletions src/hooks/common/useFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMemo } from 'react'
import { useAppState } from '@/contexts/appState'
import { FilterAddAction, FilterDelAction } from '@/store/filter'

export type UseFilterProps = {
key: string
debounced?: number
}

export type UseFilterReturn = [string | undefined, (value: string) => void]

// @todo: move it to core package utils
export function debounce(
cb: (...args: any[]) => void,
delay: number,
): (...args: any) => void {
let id: NodeJS.Timeout

return (args) => {
clearTimeout(id)
id = setTimeout(() => cb(args), delay)
}
}

export function useFilter({
key,
debounced = 0,
}: UseFilterProps): UseFilterReturn {
const [state, dispatch] = useAppState()
const filters = state.filter

const filter = filters[key]?.value

const handleChange = useMemo(() => {
return debounce((value) => {
return value
? dispatch(new FilterAddAction({ key, value }))
: dispatch(new FilterDelAction({ key }))
}, debounced)
}, [debounced, key, dispatch])

return [filter, handleChange]
}
109 changes: 109 additions & 0 deletions src/hooks/common/useFilters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
import { useAppState } from '@/contexts/appState'
import {
FilterAddAction,
FilterDelAction,
FilterItem,
FilterSetAction,
} from '@/store/filter'

export type UseFiltersProps = {
syncUrl?: boolean
}

export type UseFiltersReturn = {
filters: Record<string, FilterItem | null>
handleAddFilter: (key: string, value: string) => void
handleDelFilter: (key: string) => void
}

export function useFilters({
syncUrl = false,
}: UseFiltersProps): UseFiltersReturn {
const [state, dispatch] = useAppState()
const filters = state.filter

const router = useRouter()

// -----------------------------

useEffect(() => {
if (!syncUrl) return
if (!router.isReady) return

const urlArgs = router.asPath.split('?')[1]
const urlParams = Object.fromEntries(new URLSearchParams(urlArgs).entries())
const stateParams = Object.fromEntries(
Object.entries(filters).map(([key, val]) => [key, val ? val.value : val]),
)

const newFilters: Record<string, string | null> = {
...urlParams,
...stateParams,
}

const addToState = Object.keys(newFilters).filter((key) => {
const fValue = stateParams[key] || null
return fValue !== newFilters[key]
})

const addToUrl = Object.keys(newFilters).filter((key) => {
const qValue = urlParams[key] || null
return qValue !== newFilters[key]
})

if (addToState.length) {
const state = addToState.reduce(
(ac, key) => {
const value = newFilters[key]
ac[key] = value ? { key, value } : null
return ac
},
{ ...filters },
)

console.log('SYNC FILTER STATE', state)

dispatch(new FilterSetAction({ state }))
}

if (addToUrl.length) {
const query = addToUrl.reduce(
(ac, key) => {
const value = newFilters[key]

if (value) {
ac[key] = value
} else {
delete ac[key]
}

return ac
},
{ ...router.query },
)

console.log('SYNC FILTER QUERY', query)

router.push({ query })
}
}, [syncUrl, filters, router, dispatch])

const handleAddFilter = useCallback(
(key: string, value: string) =>
dispatch(new FilterAddAction({ key, value })),
[dispatch],
)

const handleDelFilter = useCallback(
(key: string) => dispatch(new FilterDelAction({ key })),
[dispatch],
)

return {
filters,
handleAddFilter,
handleDelFilter,
}
}
Loading

0 comments on commit d5b2652

Please sign in to comment.