refactoring
add host filter to main page add timer to loader decompose host filter filter admin menu from drawer fix cameratransferlist buttons
This commit is contained in:
parent
5629db199c
commit
acaf99c878
@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
# Build commands:
|
# Build commands:
|
||||||
# - $VERSION=0.5
|
# - $VERSION=0.6
|
||||||
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
|
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
|
||||||
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
||||||
# - docker image push --all-tags oncharterliz/multi-frigate
|
# - docker image push --all-tags oncharterliz/multi-frigate
|
||||||
|
|||||||
@ -20,19 +20,12 @@ export const keycloakConfig: AuthProviderProps = {
|
|||||||
onSigninCallback: () => {
|
onSigninCallback: () => {
|
||||||
const currentUrl = new URL(window.location.href);
|
const currentUrl = new URL(window.location.href);
|
||||||
const params = currentUrl.searchParams;
|
const params = currentUrl.searchParams;
|
||||||
console.log('params', params.toString())
|
|
||||||
|
|
||||||
params.delete('state');
|
params.delete('state');
|
||||||
params.delete('session_state');
|
params.delete('session_state');
|
||||||
params.delete('code');
|
params.delete('code');
|
||||||
|
|
||||||
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`
|
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`
|
||||||
console.log('newUrl', newUrl)
|
|
||||||
|
|
||||||
window.history.replaceState({}, document.title, newUrl)
|
window.history.replaceState({}, document.title, newUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootStore = new RootStore()
|
const rootStore = new RootStore()
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.
|
|||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import { Flex, Group, Select, Text } from '@mantine/core';
|
import { Flex, Group, Select, Text } from '@mantine/core';
|
||||||
import { OneSelectItem } from '../shared/components/filters.aps/OneSelectFilter';
|
import { OneSelectItem } from '../shared/components/filters/OneSelectFilter';
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { dimensions } from '../shared/dimensions/dimensions';
|
import { dimensions } from '../shared/dimensions/dimensions';
|
||||||
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
||||||
@ -6,7 +6,9 @@ import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
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';
|
||||||
|
import HostSelect from '../shared/components/filters/HostSelect';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
|
import { strings } from '../shared/strings/strings';
|
||||||
import CameraCard from '../widgets/CameraCard';
|
import CameraCard from '../widgets/CameraCard';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
|
||||||
@ -14,6 +16,7 @@ const MainPage = () => {
|
|||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
const [searchQuery, setSearchQuery] = useState<string>()
|
const [searchQuery, setSearchQuery] = useState<string>()
|
||||||
|
const [selectedHostId, setSelectedHostId] = useState<string>()
|
||||||
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
||||||
|
|
||||||
const { data: cameras, isPending, isError, refetch } = useQuery({
|
const { data: cameras, isPending, isError, refetch } = useQuery({
|
||||||
@ -22,12 +25,19 @@ const MainPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchQuery && cameras) {
|
if (!cameras) {
|
||||||
setFilteredCameras(cameras.filter(camera => camera.name.toLowerCase().includes(searchQuery.toLowerCase())))
|
|
||||||
} else {
|
|
||||||
setFilteredCameras(undefined)
|
setFilteredCameras(undefined)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}, [searchQuery, cameras])
|
|
||||||
|
const filterCameras = (camera: GetCameraWHostWConfig) => {
|
||||||
|
const matchesHostId = selectedHostId ? camera.frigateHost?.id === selectedHostId : true
|
||||||
|
const matchesSearchQuery = searchQuery ? camera.name.toLowerCase().includes(searchQuery.toLowerCase()) : true
|
||||||
|
return matchesHostId && matchesSearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilteredCameras(cameras.filter(filterCameras))
|
||||||
|
}, [searchQuery, cameras, selectedHostId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!executed.current) {
|
if (!executed.current) {
|
||||||
@ -66,6 +76,10 @@ const MainPage = () => {
|
|||||||
|
|
||||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
|
||||||
|
const handleSelectHost = (hostId: string) => {
|
||||||
|
setSelectedHostId(hostId)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction='column' h='100%' w='100%' >
|
<Flex direction='column' h='100%' w='100%' >
|
||||||
<Flex justify='space-between' align='center' w='100%'>
|
<Flex justify='space-between' align='center' w='100%'>
|
||||||
@ -77,11 +91,18 @@ const MainPage = () => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
maw={400}
|
maw={400}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
placeholder="Search..."
|
placeholder={strings.search}
|
||||||
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
icon={<IconSearch size="0.9rem" stroke={1.5} />}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
<HostSelect
|
||||||
|
valueId={selectedHostId}
|
||||||
|
onChange={handleSelectHost}
|
||||||
|
ml='1rem'
|
||||||
|
spaceBetween='0px'
|
||||||
|
placeholder={strings.selectHost}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||||
|
|||||||
@ -100,9 +100,9 @@ const SettingsPage = () => {
|
|||||||
mutation.mutate(configsToUpdate);
|
mutation.mutate(configsToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
if (configPending || adminLoading) return <CenterLoader />
|
if (configPending || adminLoading) return <CenterLoader />
|
||||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||||
if (!isAdmin) return <Forbidden />
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex h='100%'>
|
<Flex h='100%'>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import HostSystemPage from "../pages/HostSystemPage";
|
|||||||
import HostStoragePage from "../pages/HostStoragePage";
|
import HostStoragePage from "../pages/HostStoragePage";
|
||||||
import LiveCameraPage from "../pages/LiveCameraPage";
|
import LiveCameraPage from "../pages/LiveCameraPage";
|
||||||
import RecordingsPage from "../pages/RecordingsPage";
|
import RecordingsPage from "../pages/RecordingsPage";
|
||||||
import AccessSettings from "../pages/AccessSettings";
|
import AccessSettings from "../pages/AccessSettingsPage";
|
||||||
import PlayRecordPage from "../pages/PlayRecordPage";
|
import PlayRecordPage from "../pages/PlayRecordPage";
|
||||||
|
|
||||||
interface IRoute {
|
interface IRoute {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { frigateApi, frigateQueryKeys } from '../../services/frigate.proxy/friga
|
|||||||
import CogwheelLoader from './loaders/CogwheelLoader';
|
import CogwheelLoader from './loaders/CogwheelLoader';
|
||||||
import RetryError from './RetryError';
|
import RetryError from './RetryError';
|
||||||
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
|
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
|
||||||
import { OneSelectItem } from './filters.aps/OneSelectFilter';
|
import { OneSelectItem } from './filters/OneSelectFilter';
|
||||||
import { strings } from '../strings/strings';
|
import { strings } from '../strings/strings';
|
||||||
import { isProduction } from '../env.const';
|
import { isProduction } from '../env.const';
|
||||||
|
|
||||||
@ -65,8 +65,8 @@ const CamerasTransferList = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex w='100%' justify='center'>
|
<Flex w='100%' justify='center'>
|
||||||
<Button mt='1rem' w='10%' miw='6rem' mr='1rem' onClick={handleDiscard}>{strings.discard}</Button>
|
<Button mt='1rem' miw='6rem' mr='1rem' onClick={handleDiscard}>{strings.discard}</Button>
|
||||||
<Button mt='1rem' w='10%' miw='5rem' onClick={handleSave}>{strings.save}</Button>
|
<Button mt='1rem' miw='5rem' onClick={handleSave}>{strings.save}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<TransferList
|
<TransferList
|
||||||
transferAllMatchingFilter
|
transferAllMatchingFilter
|
||||||
|
|||||||
@ -30,13 +30,19 @@ const useStyles = createStyles((theme,
|
|||||||
|
|
||||||
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
||||||
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||||
const [visible, { open, close }] = useDisclosure(window.innerWidth > hideSizePx);
|
const initialVisible = () => {
|
||||||
const manualVisible: React.MutableRefObject<null | boolean> = useRef(null)
|
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||||
|
if (savedVisibility === null) {
|
||||||
|
return window.innerWidth < hideSizePx;
|
||||||
|
}
|
||||||
|
return savedVisibility === 'true';
|
||||||
|
}
|
||||||
|
const [visible, { open, close }] = useDisclosure(initialVisible());
|
||||||
|
|
||||||
const { classes } = useStyles({ visible })
|
const { classes } = useStyles({ visible })
|
||||||
|
|
||||||
const handleClickVisible = (state: boolean) => {
|
const handleClickVisible = (state: boolean) => {
|
||||||
manualVisible.current = state
|
localStorage.setItem(`sidebarVisible_${side}`, String(state))
|
||||||
if (state) open()
|
if (state) open()
|
||||||
else close()
|
else close()
|
||||||
}
|
}
|
||||||
@ -73,23 +79,14 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
|||||||
isHidden(!visible)
|
isHidden(!visible)
|
||||||
}, [visible])
|
}, [visible])
|
||||||
|
|
||||||
// resize controller
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkWindowSize = () => {
|
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||||
if (window.innerWidth <= hideSizePx && visible) {
|
if (savedVisibility === null && window.innerWidth < hideSizePx) {
|
||||||
close()
|
|
||||||
}
|
|
||||||
if (window.innerWidth > hideSizePx && !visible && manualVisible.current === null) {
|
|
||||||
open()
|
open()
|
||||||
|
} else if (savedVisibility) {
|
||||||
|
savedVisibility === 'true' ? open() : close()
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
window.addEventListener('resize', checkWindowSize);
|
|
||||||
|
|
||||||
// Cleanup function to remove event listener
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', checkWindowSize);
|
|
||||||
}
|
|
||||||
}, [visible])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -112,7 +109,6 @@ const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
|||||||
{rightChildren}
|
{rightChildren}
|
||||||
</Aside>
|
</Aside>
|
||||||
}
|
}
|
||||||
|
|
||||||
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
import { Center, Text } from '@mantine/core';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
import React, { useContext, useEffect } from 'react';
|
|
||||||
import { Context } from '../../..';
|
|
||||||
import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api';
|
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import CogwheelLoader from '../loaders/CogwheelLoader';
|
|
||||||
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
|
||||||
import RetryError from '../RetryError';
|
|
||||||
|
|
||||||
const HostSelectFilter = () => {
|
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
|
||||||
|
|
||||||
const { data: hosts, isError, isPending, isSuccess, refetch } = useQuery({
|
|
||||||
queryKey: [frigateQueryKeys.getFrigateHosts],
|
|
||||||
queryFn: frigateApi.getHosts
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hosts) return
|
|
||||||
if (recStore.hostIdParam) {
|
|
||||||
recStore.filteredHost = hosts.find(host => host.id === recStore.hostIdParam)
|
|
||||||
recStore.hostIdParam = undefined
|
|
||||||
}
|
|
||||||
}, [isSuccess])
|
|
||||||
|
|
||||||
if (isPending) return <CogwheelLoader />
|
|
||||||
if (isError) return <RetryError onRetry={refetch}/>
|
|
||||||
|
|
||||||
if (!hosts || hosts.length < 1) return null
|
|
||||||
|
|
||||||
const hostItems: OneSelectItem[] = hosts
|
|
||||||
.filter(host => host.enabled)
|
|
||||||
.map(host => ({ value: host.id, label: host.name }))
|
|
||||||
|
|
||||||
const handleSelect = (value: string) => {
|
|
||||||
const host = hosts?.find(host => host.id === value)
|
|
||||||
if (!host) {
|
|
||||||
recStore.filteredHost = undefined
|
|
||||||
recStore.filteredCamera = undefined
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (recStore.filteredHost?.id !== host.id) {
|
|
||||||
recStore.filteredCamera = undefined
|
|
||||||
}
|
|
||||||
recStore.filteredHost = host
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OneSelectFilter
|
|
||||||
id='frigate-hosts'
|
|
||||||
label={strings.selectHost}
|
|
||||||
spaceBetween='1rem'
|
|
||||||
value={recStore.filteredHost?.id || ''}
|
|
||||||
defaultValue={recStore.filteredHost?.id || ''}
|
|
||||||
data={hostItems}
|
|
||||||
onChange={handleSelect}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default observer(HostSelectFilter);
|
|
||||||
@ -1,11 +1,10 @@
|
|||||||
|
import { Box, Flex, Indicator, Text } from '@mantine/core';
|
||||||
import { DatePickerInput } from '@mantine/dates';
|
import { DatePickerInput } from '@mantine/dates';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import React, { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { strings } from '../../strings/strings';
|
|
||||||
import { Box, Flex, Indicator, Text } from '@mantine/core';
|
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
|
||||||
import { Context } from '../../..';
|
import { Context } from '../../..';
|
||||||
import { isProduction } from '../../env.const';
|
import { isProduction } from '../../env.const';
|
||||||
|
import { strings } from '../../strings/strings';
|
||||||
|
|
||||||
interface DateRangeSelectFilterProps {}
|
interface DateRangeSelectFilterProps {}
|
||||||
|
|
||||||
67
src/shared/components/filters/HostSelect.tsx
Normal file
67
src/shared/components/filters/HostSelect.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { frigateQueryKeys, frigateApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import { strings } from '../../strings/strings';
|
||||||
|
import RetryError from '../RetryError';
|
||||||
|
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||||
|
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
||||||
|
import { SystemProp, SpacingValue, MantineStyleSystemProps, Loader, Center } from '@mantine/core';
|
||||||
|
|
||||||
|
interface HostSelectProps extends MantineStyleSystemProps {
|
||||||
|
label?: string
|
||||||
|
valueId?: string
|
||||||
|
defaultId?: string
|
||||||
|
spaceBetween?: SystemProp<SpacingValue>
|
||||||
|
placeholder?: string
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
onSuccess?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const HostSelect = ({
|
||||||
|
label,
|
||||||
|
valueId,
|
||||||
|
defaultId,
|
||||||
|
spaceBetween,
|
||||||
|
placeholder,
|
||||||
|
onChange,
|
||||||
|
onSuccess,
|
||||||
|
...styleProps
|
||||||
|
}: HostSelectProps) => {
|
||||||
|
const { data: hosts, isError, isPending, isSuccess, refetch } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getFrigateHosts],
|
||||||
|
queryFn: frigateApi.getHosts
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onSuccess) onSuccess()
|
||||||
|
}, [isSuccess])
|
||||||
|
|
||||||
|
if (isPending) return <Center><Loader /></Center>
|
||||||
|
if (isError) return <RetryError onRetry={refetch} />
|
||||||
|
|
||||||
|
if (!hosts || hosts.length < 1) return null
|
||||||
|
|
||||||
|
const hostItems: OneSelectItem[] = hosts
|
||||||
|
.filter(host => host.enabled)
|
||||||
|
.map(host => ({ value: host.id, label: host.name }))
|
||||||
|
|
||||||
|
const handleSelect = (value: string) => {
|
||||||
|
if (onChange) onChange(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OneSelectFilter
|
||||||
|
id='frigate-hosts'
|
||||||
|
label={label}
|
||||||
|
placeholder={placeholder}
|
||||||
|
spaceBetween={spaceBetween ? spaceBetween : '1rem'}
|
||||||
|
value={valueId || ''}
|
||||||
|
defaultValue={defaultId || ''}
|
||||||
|
data={hostItems}
|
||||||
|
onChange={handleSelect}
|
||||||
|
{...styleProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HostSelect;
|
||||||
@ -11,15 +11,15 @@ export interface OneSelectItem {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OneSelectFilterProps extends SelectProps {
|
export interface OneSelectFilterProps extends SelectProps {
|
||||||
id?: string
|
id?: string
|
||||||
data: OneSelectItem[]
|
data: OneSelectItem[]
|
||||||
spaceBetween?: SystemProp<SpacingValue>
|
spaceBetween?: SystemProp<SpacingValue>
|
||||||
label?: string
|
label?: string
|
||||||
defaultValue?: string
|
defaultValue?: string | null
|
||||||
textClassName?: string
|
textClassName?: string
|
||||||
showClose?: boolean,
|
showClose?: boolean,
|
||||||
value?: string,
|
value?: string | null,
|
||||||
onChange?: (value: string, id?: string,) => void
|
onChange?: (value: string, id?: string,) => void
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
@ -41,11 +41,13 @@ const OneSelectFilter = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mt={spaceBetween}>
|
<Box mt={spaceBetween}>
|
||||||
|
{!label ? null :
|
||||||
<Flex justify='space-between'>
|
<Flex justify='space-between'>
|
||||||
<Text className={textClassName}>{label}</Text>
|
<Text className={textClassName}>{label}</Text>
|
||||||
{showClose ? <CloseWithTooltip label={strings.hide} onClose={handleOnClose} />
|
{showClose ? <CloseWithTooltip label={strings.hide} onClose={handleOnClose} />
|
||||||
: null}
|
: null}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
}
|
||||||
<Select
|
<Select
|
||||||
mt={spaceBetween}
|
mt={spaceBetween}
|
||||||
data={data}
|
data={data}
|
||||||
50
src/shared/components/filters/RecordingsHostFilter.tsx
Normal file
50
src/shared/components/filters/RecordingsHostFilter.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Context } from '../../..';
|
||||||
|
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import { strings } from '../../strings/strings';
|
||||||
|
import HostSelect from './HostSelect';
|
||||||
|
|
||||||
|
const RecordingsHostFilter = () => {
|
||||||
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
|
const { data: hosts } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getFrigateHosts],
|
||||||
|
queryFn: frigateApi.getHosts
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const handleSelect = (value: string) => {
|
||||||
|
const host = hosts?.find(host => host.id === value)
|
||||||
|
if (!host) {
|
||||||
|
recStore.filteredHost = undefined
|
||||||
|
recStore.filteredCamera = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (recStore.filteredHost?.id !== host.id) {
|
||||||
|
recStore.filteredCamera = undefined
|
||||||
|
}
|
||||||
|
recStore.filteredHost = host
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSuccess = () => {
|
||||||
|
if (!hosts) return
|
||||||
|
if (recStore.hostIdParam) {
|
||||||
|
recStore.filteredHost = hosts.find(host => host.id === recStore.hostIdParam)
|
||||||
|
recStore.hostIdParam = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HostSelect
|
||||||
|
label={strings.selectHost}
|
||||||
|
valueId={recStore.filteredHost?.id}
|
||||||
|
defaultId={recStore.filteredHost?.id}
|
||||||
|
onChange={handleSelect}
|
||||||
|
onSuccess={handleSuccess}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(RecordingsHostFilter);
|
||||||
@ -1,9 +1,29 @@
|
|||||||
import React from 'react';
|
import { Center } from '@mantine/core';
|
||||||
import { Center, DEFAULT_THEME } from '@mantine/core';
|
|
||||||
import CogwheelSVG from '../svg/CogwheelSVG';
|
import CogwheelSVG from '../svg/CogwheelSVG';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface CogwheelLoaderProps {
|
||||||
|
duration?: number;
|
||||||
|
onTimeout?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CogwheelLoader: React.FC<CogwheelLoaderProps> = ({
|
||||||
|
duration,
|
||||||
|
onTimeout,
|
||||||
|
}) => {
|
||||||
|
const [isTimeout, setIsTimeout] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (duration && onTimeout) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setIsTimeout(true);
|
||||||
|
onTimeout();
|
||||||
|
}, duration);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [duration, onTimeout])
|
||||||
|
|
||||||
const CogwheelLoader = () => {
|
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
{CogwheelSVG}
|
{CogwheelSVG}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useDisclosure } from '@mantine/hooks';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LinkItem } from '../../../widgets/header/HeaderAction';
|
import { LinkItem } from '../../../widgets/header/HeaderAction';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAdminRole } from '../../../hooks/useAdminRole';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
burger: {
|
burger: {
|
||||||
@ -38,6 +39,8 @@ const DrawerMenu = ({
|
|||||||
links
|
links
|
||||||
}: DrawerMenuProps) => {
|
}: DrawerMenuProps) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { isAdmin } = useAdminRole()
|
||||||
|
|
||||||
|
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] = useDisclosure(false)
|
const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] = useDisclosure(false)
|
||||||
@ -47,7 +50,7 @@ const DrawerMenu = ({
|
|||||||
closeDrawer()
|
closeDrawer()
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = links.map(item => (
|
const items = links.filter(link => !(link.admin && !isAdmin)).map(item => (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
className={classes.drawerButton}
|
className={classes.drawerButton}
|
||||||
key={item.link}
|
key={item.link}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import CameraSelectFilter from '../shared/components/filters.aps/CameraSelectFilter';
|
import CameraSelectFilter from '../shared/components/filters/CameraSelectFilter';
|
||||||
import DateRangeSelectFilter from '../shared/components/filters.aps/DateRangeSelectFilter';
|
import DateRangeSelectFilter from '../shared/components/filters/DateRangeSelectFilter';
|
||||||
import HostSelectFilter from '../shared/components/filters.aps/HostSelectFilter';
|
import RecordingsHostFilter from '../shared/components/filters/RecordingsHostFilter';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
const RecordingsFiltersRightSide = () => {
|
const RecordingsFiltersRightSide = () => {
|
||||||
@ -12,7 +12,7 @@ const RecordingsFiltersRightSide = () => {
|
|||||||
if (!isProduction) console.log('RecordingsFiltersRightSide rendered')
|
if (!isProduction) console.log('RecordingsFiltersRightSide rendered')
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HostSelectFilter />
|
<RecordingsHostFilter />
|
||||||
{recStore.filteredHost ?
|
{recStore.filteredHost ?
|
||||||
<CameraSelectFilter
|
<CameraSelectFilter
|
||||||
selectedHostId={recStore.filteredHost.id} />
|
selectedHostId={recStore.filteredHost.id} />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user