diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 0000000..2a65afb --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,16 @@ +import { useRef } from "react"; + +export const useDebounce = ( + callback: (args: T) => void, + delay: number +) => { + const debounceRef = useRef(null); + + return (args: T) => { + if (debounceRef.current) clearTimeout(debounceRef.current); + + debounceRef.current = setTimeout(() => { + callback(args); + }, delay); + }; +}; \ No newline at end of file diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index afd27d9..6b13733 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -2,9 +2,11 @@ import { Flex, Grid } from '@mantine/core'; import { IconSearch } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; 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 { useNavigate, useSearchParams } from 'react-router-dom'; import { Context } from '..'; +import { useDebounce } from '../hooks/useDebounce'; import { useRealmUser } from '../hooks/useRealmUser'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema'; @@ -15,7 +17,6 @@ import CameraCard from '../widgets/CameraCard'; import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide'; import { SideBarContext } from '../widgets/sidebars/SideBarContext'; import RetryErrorPage from './RetryErrorPage'; -import { useSearchParams } from 'react-router-dom'; export const mainPageParams = { hostId: 'hostId', @@ -25,12 +26,12 @@ export const mainPageParams = { const MainPage = () => { const { t } = useTranslation() + const navigate = useNavigate() const { mainStore } = useContext(Context) const [searchParams] = useSearchParams() const { setRightChildren } = useContext(SideBarContext) - const { hostId: selectedHostId, selectedTags } = mainStore.filters - const [searchQuery, setSearchQuery] = useState() + const { hostId: selectedHostId, selectedTags, searchQuery } = mainStore.filters const [filteredCameras, setFilteredCameras] = useState() const realmUser = useRealmUser() @@ -42,14 +43,14 @@ const MainPage = () => { }) useEffect(() => { - setRightChildren(); - const serializedTags = searchParams.get(mainPageParams.selectedTags) - const deSerializedTags = serializedTags ? mainStore.getArrayParam(serializedTags) : [] + const deSerializedTags = mainStore.getArrayParam(mainPageParams.selectedTags) mainStore.loadFiltersFromPage({ hostId: searchParams.get(mainPageParams.hostId) || undefined, searchQuery: searchParams.get(mainPageParams.searchQuery) || undefined, selectedTags: deSerializedTags, }) + + setRightChildren(); return () => setRightChildren(null); }, []); @@ -62,7 +63,7 @@ const MainPage = () => { const filterCameras = (camera: GetCameraWHostWConfig) => { const matchesHostId = selectedHostId ? camera.frigateHost?.id === selectedHostId : 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 } @@ -94,6 +95,14 @@ const MainPage = () => { else return [] }, [cameras, filteredCameras]) + const debouncedHandleSearchQuery = useDebounce((value: string) => { + mainStore.setSearchQuery(value, navigate); + }, 600); + + const onInputChange = (event: ChangeEvent) => { + debouncedHandleSearchQuery(event.currentTarget.value) + } + if (isPending) return if (isError) return @@ -111,8 +120,8 @@ const MainPage = () => { style={{ flexGrow: 1 }} placeholder={t('search')} icon={} - value={searchQuery} - onChange={(event) => setSearchQuery(event.currentTarget.value)} + value={searchQuery || undefined} + onChange={onInputChange} /> diff --git a/src/shared/components/filters/UserTagsFilter.tsx b/src/shared/components/filters/UserTagsFilter.tsx index 14fa8a4..0b669ea 100644 --- a/src/shared/components/filters/UserTagsFilter.tsx +++ b/src/shared/components/filters/UserTagsFilter.tsx @@ -14,17 +14,19 @@ import { IconAlertCircle } from '@tabler/icons-react'; interface UserTagsFilterProps { + selectedTags?: string[] onChange?(tagIds: string[]): void } const UserTagsFilter: React.FC = ({ + selectedTags, onChange }) => { const { t } = useTranslation() const queryClient = useQueryClient() - const [selectedList, setSelectedList] = useState([]) + const [selectedList, setSelectedList] = useState(selectedTags || []) useEffect(() => { if (onChange) onChange(selectedList) diff --git a/src/shared/components/inputs/ClearableTextInput.tsx b/src/shared/components/inputs/ClearableTextInput.tsx index 2cb178f..b403a1f 100644 --- a/src/shared/components/inputs/ClearableTextInput.tsx +++ b/src/shared/components/inputs/ClearableTextInput.tsx @@ -15,7 +15,7 @@ const ClearableTextInput: React.FC = ({ const [text, setText] = useState(value) const handleClear = (event: React.MouseEvent) => { - setText(''); // обновляем локальное состояние + setText('') if (onChange) { const fakeEvent = { target: { value: '' }, diff --git a/src/shared/stores/main.store.ts b/src/shared/stores/main.store.ts index 7dc1785..c5d2b59 100644 --- a/src/shared/stores/main.store.ts +++ b/src/shared/stores/main.store.ts @@ -7,18 +7,18 @@ import { isProduction } from "../env.const"; interface Filters { hostId?: string | null searchQuery?: string | null - selectedTags: string[] + selectedTags?: string[] } export class MainStore { - filters: Filters = { selectedTags: []} + filters: Filters = {} constructor() { makeAutoObservable(this) } loadFiltersFromPage(filters: Filters) { - if (!isProduction) console.log('MainPage load filters') + if (!isProduction) console.log('MainPage load filters', filters) this.filters = filters } diff --git a/src/widgets/sidebars/MainFiltersRightSide.tsx b/src/widgets/sidebars/MainFiltersRightSide.tsx index 67b9d7d..3c0ac1f 100644 --- a/src/widgets/sidebars/MainFiltersRightSide.tsx +++ b/src/widgets/sidebars/MainFiltersRightSide.tsx @@ -13,7 +13,7 @@ const MainFiltersRightSide = () => { const navigate = useNavigate() const { mainStore } = useContext(Context) - const { hostId } = mainStore.filters + const { hostId, selectedTags } = mainStore.filters const handleSelectHost = (value: string) => { if (!isProduction) console.log('handleSelectHost value', value) @@ -35,6 +35,7 @@ const MainFiltersRightSide = () => { />