save search and filters at main page to path params
This commit is contained in:
parent
ab3bf6f162
commit
88083fba6a
16
src/hooks/useDebounce.ts
Normal file
16
src/hooks/useDebounce.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
export const useDebounce = <T extends any>(
|
||||||
|
callback: (args: T) => void,
|
||||||
|
delay: number
|
||||||
|
) => {
|
||||||
|
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
return (args: T) => {
|
||||||
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||||
|
|
||||||
|
debounceRef.current = setTimeout(() => {
|
||||||
|
callback(args);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -2,9 +2,11 @@ import { Flex, Grid } from '@mantine/core';
|
|||||||
import { IconSearch } from '@tabler/icons-react';
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { ChangeEvent, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import { useRealmUser } from '../hooks/useRealmUser';
|
import { useRealmUser } from '../hooks/useRealmUser';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
||||||
@ -15,7 +17,6 @@ import CameraCard from '../widgets/CameraCard';
|
|||||||
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
||||||
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const mainPageParams = {
|
export const mainPageParams = {
|
||||||
hostId: 'hostId',
|
hostId: 'hostId',
|
||||||
@ -25,12 +26,12 @@ export const mainPageParams = {
|
|||||||
|
|
||||||
const MainPage = () => {
|
const MainPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const navigate = useNavigate()
|
||||||
const { mainStore } = useContext(Context)
|
const { mainStore } = useContext(Context)
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const { setRightChildren } = useContext(SideBarContext)
|
const { setRightChildren } = useContext(SideBarContext)
|
||||||
const { hostId: selectedHostId, selectedTags } = mainStore.filters
|
const { hostId: selectedHostId, selectedTags, searchQuery } = mainStore.filters
|
||||||
const [searchQuery, setSearchQuery] = useState<string>()
|
|
||||||
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
||||||
|
|
||||||
const realmUser = useRealmUser()
|
const realmUser = useRealmUser()
|
||||||
@ -42,14 +43,14 @@ const MainPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRightChildren(<MainFiltersRightSide />);
|
const deSerializedTags = mainStore.getArrayParam(mainPageParams.selectedTags)
|
||||||
const serializedTags = searchParams.get(mainPageParams.selectedTags)
|
|
||||||
const deSerializedTags = serializedTags ? mainStore.getArrayParam(serializedTags) : []
|
|
||||||
mainStore.loadFiltersFromPage({
|
mainStore.loadFiltersFromPage({
|
||||||
hostId: searchParams.get(mainPageParams.hostId) || undefined,
|
hostId: searchParams.get(mainPageParams.hostId) || undefined,
|
||||||
searchQuery: searchParams.get(mainPageParams.searchQuery) || undefined,
|
searchQuery: searchParams.get(mainPageParams.searchQuery) || undefined,
|
||||||
selectedTags: deSerializedTags,
|
selectedTags: deSerializedTags,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setRightChildren(<MainFiltersRightSide />);
|
||||||
return () => setRightChildren(null);
|
return () => setRightChildren(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ const MainPage = () => {
|
|||||||
const filterCameras = (camera: GetCameraWHostWConfig) => {
|
const filterCameras = (camera: GetCameraWHostWConfig) => {
|
||||||
const matchesHostId = selectedHostId ? camera.frigateHost?.id === selectedHostId : true
|
const matchesHostId = selectedHostId ? camera.frigateHost?.id === selectedHostId : true
|
||||||
const matchesSearchQuery = searchQuery ? camera.name.toLowerCase().includes(searchQuery.toLowerCase()) : true
|
const matchesSearchQuery = searchQuery ? camera.name.toLowerCase().includes(searchQuery.toLowerCase()) : true
|
||||||
const matchesTags = selectedTags.length === 0 || camera.tags.some( tag => selectedTags.includes(tag.id))
|
const matchesTags = selectedTags ? selectedTags.length === 0 || camera.tags.some(tag => selectedTags.includes(tag.id)) : true
|
||||||
return matchesHostId && matchesSearchQuery && matchesTags
|
return matchesHostId && matchesSearchQuery && matchesTags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +95,14 @@ const MainPage = () => {
|
|||||||
else return []
|
else return []
|
||||||
}, [cameras, filteredCameras])
|
}, [cameras, filteredCameras])
|
||||||
|
|
||||||
|
const debouncedHandleSearchQuery = useDebounce((value: string) => {
|
||||||
|
mainStore.setSearchQuery(value, navigate);
|
||||||
|
}, 600);
|
||||||
|
|
||||||
|
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
debouncedHandleSearchQuery(event.currentTarget.value)
|
||||||
|
}
|
||||||
|
|
||||||
if (isPending) return <CenterLoader />
|
if (isPending) return <CenterLoader />
|
||||||
|
|
||||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||||
@ -111,8 +120,8 @@ const MainPage = () => {
|
|||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
placeholder={t('search')}
|
placeholder={t('search')}
|
||||||
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
||||||
value={searchQuery}
|
value={searchQuery || undefined}
|
||||||
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||||
|
|||||||
@ -14,17 +14,19 @@ import { IconAlertCircle } from '@tabler/icons-react';
|
|||||||
|
|
||||||
|
|
||||||
interface UserTagsFilterProps {
|
interface UserTagsFilterProps {
|
||||||
|
selectedTags?: string[]
|
||||||
onChange?(tagIds: string[]): void
|
onChange?(tagIds: string[]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const UserTagsFilter: React.FC<UserTagsFilterProps> = ({
|
const UserTagsFilter: React.FC<UserTagsFilterProps> = ({
|
||||||
|
selectedTags,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const [selectedList, setSelectedList] = useState<string[]>([])
|
const [selectedList, setSelectedList] = useState<string[]>(selectedTags || [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onChange) onChange(selectedList)
|
if (onChange) onChange(selectedList)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const ClearableTextInput: React.FC<ClearableTextInputProps> = ({
|
|||||||
const [text, setText] = useState(value)
|
const [text, setText] = useState(value)
|
||||||
|
|
||||||
const handleClear = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleClear = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
setText(''); // обновляем локальное состояние
|
setText('')
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
const fakeEvent = {
|
const fakeEvent = {
|
||||||
target: { value: '' },
|
target: { value: '' },
|
||||||
|
|||||||
@ -7,18 +7,18 @@ import { isProduction } from "../env.const";
|
|||||||
interface Filters {
|
interface Filters {
|
||||||
hostId?: string | null
|
hostId?: string | null
|
||||||
searchQuery?: string | null
|
searchQuery?: string | null
|
||||||
selectedTags: string[]
|
selectedTags?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MainStore {
|
export class MainStore {
|
||||||
filters: Filters = { selectedTags: []}
|
filters: Filters = {}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this)
|
makeAutoObservable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFiltersFromPage(filters: Filters) {
|
loadFiltersFromPage(filters: Filters) {
|
||||||
if (!isProduction) console.log('MainPage load filters')
|
if (!isProduction) console.log('MainPage load filters', filters)
|
||||||
this.filters = filters
|
this.filters = filters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const MainFiltersRightSide = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { mainStore } = useContext(Context)
|
const { mainStore } = useContext(Context)
|
||||||
const { hostId } = mainStore.filters
|
const { hostId, selectedTags } = mainStore.filters
|
||||||
|
|
||||||
const handleSelectHost = (value: string) => {
|
const handleSelectHost = (value: string) => {
|
||||||
if (!isProduction) console.log('handleSelectHost value', value)
|
if (!isProduction) console.log('handleSelectHost value', value)
|
||||||
@ -35,6 +35,7 @@ const MainFiltersRightSide = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UserTagsFilter
|
<UserTagsFilter
|
||||||
|
selectedTags={selectedTags}
|
||||||
onChange={handleSelectTags}
|
onChange={handleSelectTags}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user