Add user tags filter at main page
This commit is contained in:
parent
d39e976053
commit
90114ac7a6
@ -8,6 +8,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import AppBody from './AppBody';
|
import AppBody from './AppBody';
|
||||||
import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal';
|
import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal';
|
||||||
import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal';
|
import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal';
|
||||||
|
import { SideBarProvider } from './widgets/sidebars/SideBarContext';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@ -67,8 +68,10 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalsProvider modals={modals}>
|
<ModalsProvider modals={modals}>
|
||||||
<Notifications />
|
<SideBarProvider>
|
||||||
<AppBody />
|
<Notifications />
|
||||||
|
<AppBody />
|
||||||
|
</SideBarProvider>
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</MantineProvider >
|
</MantineProvider >
|
||||||
</ColorSchemeProvider>
|
</ColorSchemeProvider>
|
||||||
|
|||||||
@ -5,9 +5,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Context } from '.';
|
import { Context } from '.';
|
||||||
import AppRouter from './router/AppRouter';
|
import AppRouter from './router/AppRouter';
|
||||||
import { routesPath } from './router/routes.path';
|
import { routesPath } from './router/routes.path';
|
||||||
import SideBar from './shared/components/SideBar';
|
|
||||||
import { isProduction } from './shared/env.const';
|
import { isProduction } from './shared/env.const';
|
||||||
import { HeaderAction } from './widgets/header/HeaderAction';
|
import { HeaderAction } from './widgets/header/HeaderAction';
|
||||||
|
import RightSideBar from "./shared/components/RightSideBar";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const AppBody = () => {
|
const AppBody = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -20,20 +21,22 @@ const AppBody = () => {
|
|||||||
{ link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true },
|
{ link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
|
|
||||||
const [leftSideBar, setLeftSidebar] = useState(false)
|
const location = useLocation()
|
||||||
const [rightSideBar, setRightSidebar] = useState(false)
|
|
||||||
|
|
||||||
const leftSideBarIsHidden = (isHidden: boolean) => {
|
const pathsWithLeftSidebar: string[] = []
|
||||||
setLeftSidebar(!isHidden)
|
const pathsWithRightSidebar: string[] = [routesPath.MAIN_PATH, routesPath.RECORDINGS_PATH]
|
||||||
}
|
|
||||||
const rightSideBarIsHidden = (isHidden: boolean) => {
|
const [leftSideBar, setLeftSidebar] = useState(pathsWithLeftSidebar.includes(location.pathname))
|
||||||
setRightSidebar(!isHidden)
|
const [rightSideBar, setRightSidebar] = useState(pathsWithRightSidebar.includes(location.pathname))
|
||||||
}
|
|
||||||
|
const handleRightSidebarChange = (isVisible: boolean) => {
|
||||||
|
setRightSidebar(isVisible);
|
||||||
|
};
|
||||||
|
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
|
|
||||||
if (!isProduction) console.log("render Main")
|
if (!isProduction) console.log("render Main")
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
@ -50,9 +53,10 @@ const AppBody = () => {
|
|||||||
header={
|
header={
|
||||||
<HeaderAction links={headerLinks} />
|
<HeaderAction links={headerLinks} />
|
||||||
}
|
}
|
||||||
|
|
||||||
aside={
|
aside={
|
||||||
!sideBarsStore.rightVisible ? <></> :
|
!pathsWithRightSidebar.includes(location.pathname) ? <></> :
|
||||||
<SideBar isHidden={rightSideBarIsHidden} side="right" />
|
<RightSideBar onChangeHidden={handleRightSidebarChange}/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { error } from "console"
|
|||||||
|
|
||||||
const en = {
|
const en = {
|
||||||
mainPage: {
|
mainPage: {
|
||||||
createSelectTags: 'Create\\Select tags'
|
createSelectTags: 'Create\\Select tags',
|
||||||
|
tagsError: 'Cannot be more than 10 symbols',
|
||||||
},
|
},
|
||||||
editCameraPage: {
|
editCameraPage: {
|
||||||
notFrigateCamera: 'Not frigate camera',
|
notFrigateCamera: 'Not frigate camera',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const ru = {
|
const ru = {
|
||||||
mainPage: {
|
mainPage: {
|
||||||
createSelectTags: 'Создать\\Выбрать тэги'
|
createSelectTags: 'Создай\\Выбери тэги',
|
||||||
|
tagsError: 'Не может быть более 10 символов',
|
||||||
},
|
},
|
||||||
editCameraPage: {
|
editCameraPage: {
|
||||||
notFrigateCamera: 'Не камера Фригата',
|
notFrigateCamera: 'Не камера Фригата',
|
||||||
|
|||||||
@ -10,17 +10,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
const Forbidden = () => {
|
const Forbidden = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const executed = useRef(false)
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const handleGoToMain = () => {
|
const handleGoToMain = () => {
|
||||||
navigate(routesPath.MAIN_PATH)
|
navigate(routesPath.MAIN_PATH)
|
||||||
|
|||||||
@ -1,24 +1,11 @@
|
|||||||
import { Button, Flex, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
|
||||||
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
|
||||||
import { routesPath } from '../router/routes.path';
|
|
||||||
import { Context } from '..';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { routesPath } from '../router/routes.path';
|
||||||
|
import CogWheelWithText from '../shared/components/loaders/CogWheelWithText';
|
||||||
|
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const handleGoToMain = () => {
|
const handleGoToMain = () => {
|
||||||
window.location.replace(routesPath.MAIN_PATH)
|
window.location.replace(routesPath.MAIN_PATH)
|
||||||
|
|||||||
@ -2,9 +2,8 @@ import { Flex, Group, Text } from '@mantine/core';
|
|||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
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, useRef, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
import CamerasTransferList from '../shared/components/CamerasTransferList';
|
||||||
@ -17,22 +16,13 @@ import RetryErrorPage from './RetryErrorPage';
|
|||||||
|
|
||||||
const AccessSettings = () => {
|
const AccessSettings = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
const { data, isPending, isError, refetch } = useQuery({
|
const { data, isPending, isError, refetch } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getRoles],
|
queryKey: [frigateQueryKeys.getRoles],
|
||||||
queryFn: frigateApi.getRoles
|
queryFn: frigateApi.getRoles
|
||||||
})
|
})
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
const isMobile = useMediaQuery(dimensions.mobileSize)
|
||||||
const [roleId, setRoleId] = useState<string>()
|
const [roleId, setRoleId] = useState<string>()
|
||||||
|
|||||||
@ -3,10 +3,9 @@ import { notifications } from '@mantine/notifications';
|
|||||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext, useEffect, useRef, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import MaskSelect, { MaskItem, MaskType } from '../shared/components/filters/MaskSelect';
|
import MaskSelect, { MaskItem, MaskType } from '../shared/components/filters/MaskSelect';
|
||||||
@ -19,7 +18,6 @@ import RetryErrorPage from './RetryErrorPage';
|
|||||||
|
|
||||||
const EditCameraPage = () => {
|
const EditCameraPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
let { id: cameraId } = useParams<'id'>()
|
let { id: cameraId } = useParams<'id'>()
|
||||||
if (!cameraId) throw Error(t('editCameraPage.cameraIdNotExist'))
|
if (!cameraId) throw Error(t('editCameraPage.cameraIdNotExist'))
|
||||||
const [selectedMask, setSelectedMask] = useState<MaskItem>()
|
const [selectedMask, setSelectedMask] = useState<MaskItem>()
|
||||||
@ -105,16 +103,6 @@ const EditCameraPage = () => {
|
|||||||
|
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
if (isPending || adminLoading) return <CenterLoader />
|
if (isPending || adminLoading) return <CenterLoader />
|
||||||
if (!isAdmin) return <Forbidden />
|
if (!isAdmin) return <Forbidden />
|
||||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
|||||||
@ -3,9 +3,8 @@ import { notifications } from '@mantine/notifications';
|
|||||||
import { IconAlertCircle } from '@tabler/icons-react';
|
import { IconAlertCircle } from '@tabler/icons-react';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext, useEffect, useRef, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
|
||||||
@ -19,22 +18,12 @@ import RetryErrorPage from './RetryErrorPage';
|
|||||||
const FrigateHostsPage = () => {
|
const FrigateHostsPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const executed = useRef(false)
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { isPending: hostsPending, error: hostsError, data } = useQuery({
|
const { isPending: hostsPending, error: hostsError, data } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getFrigateHosts],
|
queryKey: [frigateQueryKeys.getFrigateHosts],
|
||||||
queryFn: frigateApi.getHosts,
|
queryFn: frigateApi.getHosts,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
const [pageData, setPageData] = useState(data)
|
const [pageData, setPageData] = useState(data)
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { Button, Flex, Text, useMantineTheme } from '@mantine/core';
|
import { Button, Flex, useMantineTheme } from '@mantine/core';
|
||||||
import { useClipboard } from '@mantine/hooks';
|
import { useClipboard } from '@mantine/hooks';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
import Editor, { Monaco } from '@monaco-editor/react';
|
import Editor, { Monaco } from '@monaco-editor/react';
|
||||||
|
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||||
@ -16,9 +18,6 @@ import { isProduction } from '../shared/env.const';
|
|||||||
import { SaveOption } from '../types/saveConfig';
|
import { SaveOption } from '../types/saveConfig';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import { notifications } from '@mantine/notifications';
|
|
||||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
|
|
||||||
window.MonacoEnvironment = {
|
window.MonacoEnvironment = {
|
||||||
@ -42,9 +41,7 @@ const HostConfigPage = () => {
|
|||||||
const queryParams = useMemo(() => {
|
const queryParams = useMemo(() => {
|
||||||
return new URLSearchParams(location.search);
|
return new URLSearchParams(location.search);
|
||||||
}, [location.search])
|
}, [location.search])
|
||||||
const executed = useRef(false)
|
|
||||||
const host = useRef<GetFrigateHost | undefined>()
|
const host = useRef<GetFrigateHost | undefined>()
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
|
|
||||||
let { id } = useParams<'id'>()
|
let { id } = useParams<'id'>()
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
@ -96,15 +93,6 @@ const HostConfigPage = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const clipboard = useClipboard({ timeout: 500 })
|
const clipboard = useClipboard({ timeout: 500 })
|
||||||
|
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||||
|
|||||||
@ -2,10 +2,9 @@ import { Flex, Grid, SegmentedControl, Text } from '@mantine/core';
|
|||||||
import { openContextModal } from '@mantine/modals';
|
import { openContextModal } from '@mantine/modals';
|
||||||
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 { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||||
@ -16,9 +15,9 @@ import StorageRingStat from '../shared/components/stats/StorageRingStat';
|
|||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
import { formatUptime } from '../shared/utils/dateUtil';
|
import { formatUptime } from '../shared/utils/dateUtil';
|
||||||
import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable';
|
import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable';
|
||||||
|
import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable';
|
|
||||||
|
|
||||||
export const hostSystemPageQuery = {
|
export const hostSystemPageQuery = {
|
||||||
hostId: 'hostId',
|
hostId: 'hostId',
|
||||||
@ -31,21 +30,10 @@ enum SelectorItems {
|
|||||||
|
|
||||||
const HostSystemPage = () => {
|
const HostSystemPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
const { isAdmin } = useAdminRole()
|
const { isAdmin } = useAdminRole()
|
||||||
const host = useRef<GetFrigateHost | undefined>()
|
const host = useRef<GetFrigateHost | undefined>()
|
||||||
const [selector, setSelector] = useState(SelectorItems.Cameras)
|
const [selector, setSelector] = useState(SelectorItems.Cameras)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
let { id: paramHostId } = useParams<'id'>()
|
let { id: paramHostId } = useParams<'id'>()
|
||||||
|
|
||||||
const { data, isError, isPending, refetch } = useQuery({
|
const { data, isError, isPending, refetch } = useQuery({
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { Flex } from '@mantine/core';
|
import { Flex } from '@mantine/core';
|
||||||
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, useRef } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import Player from '../widgets/Player';
|
import Player from '../widgets/Player';
|
||||||
@ -13,7 +11,6 @@ import RetryErrorPage from './RetryErrorPage';
|
|||||||
|
|
||||||
const LiveCameraPage = () => {
|
const LiveCameraPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
let { id: cameraId } = useParams<'id'>()
|
let { id: cameraId } = useParams<'id'>()
|
||||||
if (!cameraId) throw Error('Camera id does not exist')
|
if (!cameraId) throw Error('Camera id does not exist')
|
||||||
|
|
||||||
@ -22,17 +19,6 @@ const LiveCameraPage = () => {
|
|||||||
queryFn: () => frigateApi.getCameraWHost(cameraId!)
|
queryFn: () => frigateApi.getCameraWHost(cameraId!)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
|
|
||||||
if (isPending) return <CenterLoader />
|
if (isPending) return <CenterLoader />
|
||||||
|
|
||||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
import { Flex, Grid, TextInput } from '@mantine/core';
|
import { Flex, Grid } from '@mantine/core';
|
||||||
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, useRef, useState } from 'react';
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
|
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';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import CameraCard from '../widgets/CameraCard';
|
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
|
||||||
import ClearableTextInput from '../shared/components/inputs/ClearableTextInput';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
import { useKeycloak } from '@react-keycloak/web';
|
import CameraCard from '../widgets/CameraCard';
|
||||||
import { useRealmUser } from '../hooks/useRealmUser';
|
import MainFiltersRightSide from '../widgets/sidebars/MainFiltersRightSide';
|
||||||
|
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
||||||
|
import RetryErrorPage from './RetryErrorPage';
|
||||||
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
|
import ClearableTextInput from '../shared/components/inputs/ClearableTextInput';
|
||||||
|
|
||||||
const MainPage = () => {
|
const MainPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const { mainStore } = useContext(Context)
|
||||||
const { sideBarsStore, mainStore } = useContext(Context)
|
const { setChildrenComponent } = useContext(SideBarContext)
|
||||||
const { selectedHostId } = mainStore
|
const { selectedHostId } = mainStore
|
||||||
const [searchQuery, setSearchQuery] = useState<string>()
|
const [searchQuery, setSearchQuery] = useState<string>()
|
||||||
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
||||||
@ -32,6 +32,11 @@ const MainPage = () => {
|
|||||||
queryFn: frigateApi.getCamerasWHost
|
queryFn: frigateApi.getCamerasWHost
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChildrenComponent(<MainFiltersRightSide />);
|
||||||
|
return () => setChildrenComponent(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cameras) {
|
if (!cameras) {
|
||||||
setFilteredCameras(undefined)
|
setFilteredCameras(undefined)
|
||||||
@ -48,26 +53,6 @@ const MainPage = () => {
|
|||||||
}, [searchQuery, cameras, selectedHostId])
|
}, [searchQuery, cameras, selectedHostId])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.leftVisible = false
|
|
||||||
executed.current = true
|
|
||||||
if (!isProduction) console.log('MainPage rendered first time')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
sideBarsStore.setRightChildren(<MainFiltersRightSide />)
|
|
||||||
sideBarsStore.rightVisible = true
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
//test change
|
|
||||||
|
|
||||||
const cards = useMemo(() => {
|
const cards = useMemo(() => {
|
||||||
if (filteredCameras)
|
if (filteredCameras)
|
||||||
return filteredCameras.filter(camera => {
|
return filteredCameras.filter(camera => {
|
||||||
@ -115,7 +100,8 @@ const MainPage = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||||
{cards}
|
{/* TODO DELETE SLICE TO WORK */}
|
||||||
|
{cards.slice(0, 5)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,26 +1,14 @@
|
|||||||
import { Flex } from '@mantine/core';
|
import { Flex } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import VideoPlayer from '../shared/components/players/VideoPlayer';
|
import VideoPlayer from '../shared/components/players/VideoPlayer';
|
||||||
import NotFound from './404';
|
import NotFound from './404';
|
||||||
import { Context } from '..';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
|
|
||||||
export const playRecordPageQuery = {
|
export const playRecordPageQuery = {
|
||||||
link: 'link',
|
link: 'link',
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayRecordPage = () => {
|
const PlayRecordPage = () => {
|
||||||
const executed = useRef(false)
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const queryParams = new URLSearchParams(location.search)
|
const queryParams = new URLSearchParams(location.search)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import SelectedCameraList from '../widgets/SelectedCameraList';
|
|||||||
import SelectedDayList from '../widgets/SelectedDayList';
|
import SelectedDayList from '../widgets/SelectedDayList';
|
||||||
import SelectedHostList from '../widgets/SelectedHostList';
|
import SelectedHostList from '../widgets/SelectedHostList';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SideBarContext } from '../widgets/sidebars/SideBarContext';
|
||||||
|
|
||||||
|
|
||||||
export const recordingsPageQuery = {
|
export const recordingsPageQuery = {
|
||||||
@ -24,7 +25,7 @@ export const recordingsPageQuery = {
|
|||||||
const RecordingsPage = () => {
|
const RecordingsPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore, recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -40,12 +41,17 @@ const RecordingsPage = () => {
|
|||||||
const [cameraId, setCameraId] = useState<string>('')
|
const [cameraId, setCameraId] = useState<string>('')
|
||||||
const [period, setPeriod] = useState<[Date | null, Date | null]>([null, null])
|
const [period, setPeriod] = useState<[Date | null, Date | null]>([null, null])
|
||||||
|
|
||||||
|
const { setChildrenComponent } = useContext(SideBarContext)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChildrenComponent(<RecordingsFiltersRightSide />);
|
||||||
|
|
||||||
|
return () => setChildrenComponent(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!executed.current) {
|
if (!executed.current) {
|
||||||
sideBarsStore.rightVisible = true
|
|
||||||
sideBarsStore.setRightChildren(
|
|
||||||
<RecordingsFiltersRightSide />
|
|
||||||
)
|
|
||||||
if (paramHostId) recStore.hostIdParam = paramHostId
|
if (paramHostId) recStore.hostIdParam = paramHostId
|
||||||
if (paramCameraId) recStore.cameraIdParam = paramCameraId
|
if (paramCameraId) recStore.cameraIdParam = paramCameraId
|
||||||
if (paramStartDay && paramEndDay) {
|
if (paramStartDay && paramEndDay) {
|
||||||
@ -55,10 +61,6 @@ const RecordingsPage = () => {
|
|||||||
}
|
}
|
||||||
executed.current = true
|
executed.current = true
|
||||||
if (!isProduction) console.log('RecordingsPage rendered first time')
|
if (!isProduction) console.log('RecordingsPage rendered first time')
|
||||||
return () => {
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { Flex, Button, Text } from '@mantine/core';
|
import { Button, Flex, Text } from '@mantine/core';
|
||||||
import React, { useContext, useEffect, useRef } from 'react';
|
|
||||||
import { routesPath } from '../router/routes.path';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
|
||||||
import { Context } from '..';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { routesPath } from '../router/routes.path';
|
||||||
|
import { ExclamationCogWheel } from '../shared/components/svg/ExclamationCogWheel';
|
||||||
|
|
||||||
interface RetryErrorPageProps {
|
interface RetryErrorPageProps {
|
||||||
repeatVisible?: boolean
|
repeatVisible?: boolean
|
||||||
@ -21,20 +20,9 @@ const RetryErrorPage = ({
|
|||||||
onRetry
|
onRetry
|
||||||
}: RetryErrorPageProps) => {
|
}: RetryErrorPageProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const handleGoToMain = () => {
|
const handleGoToMain = () => {
|
||||||
navigate(routesPath.MAIN_PATH)
|
navigate(routesPath.MAIN_PATH)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,8 @@ import { Flex, Space } from '@mantine/core';
|
|||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useContext, useEffect, useRef, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '..';
|
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
|
||||||
import { GetRole } from '../services/frigate.proxy/frigate.schema';
|
import { GetRole } from '../services/frigate.proxy/frigate.schema';
|
||||||
@ -16,21 +15,10 @@ import Forbidden from './403';
|
|||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const executed = useRef(false)
|
|
||||||
|
|
||||||
const [showRoles, setShowRoles] = useState<boolean>(false)
|
const [showRoles, setShowRoles] = useState<boolean>(false)
|
||||||
const [allRoles, setAllRoles] = useState<GetRole[]>()
|
const [allRoles, setAllRoles] = useState<GetRole[]>()
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
|
|
||||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
const isMobile = useMediaQuery(dimensions.mobileSize)
|
||||||
|
|||||||
@ -3,29 +3,13 @@ import { Editor, Monaco } from '@monaco-editor/react';
|
|||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||||
import { useContext, useEffect, useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { Context } from '..';
|
|
||||||
import HeadSearch from '../shared/components/inputs/HeadSearch';
|
import HeadSearch from '../shared/components/inputs/HeadSearch';
|
||||||
|
|
||||||
const Test = () => {
|
const Test = () => {
|
||||||
const executed = useRef(false)
|
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
|
||||||
sideBarsStore.rightVisible = true
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!executed.current) {
|
|
||||||
sideBarsStore.rightVisible = false
|
|
||||||
sideBarsStore.setLeftChildren(null)
|
|
||||||
sideBarsStore.setRightChildren(null)
|
|
||||||
executed.current = true
|
|
||||||
}
|
|
||||||
}, [sideBarsStore])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const value = `
|
const value = `
|
||||||
# Property descriptions are displayed when hovering over properties using your cursor
|
# Property descriptions are displayed when hovering over properties using your cursor
|
||||||
|
|||||||
@ -21,10 +21,6 @@ interface IRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
{ //todo delete
|
|
||||||
path: routesPath.TEST_PATH,
|
|
||||||
component: <Test />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: routesPath.SETTINGS_PATH,
|
path: routesPath.SETTINGS_PATH,
|
||||||
component: <SettingsPage />,
|
component: <SettingsPage />,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { FrigateStats, GetFfprobe, GetHostStorage, GetVaInfo } from "../../types
|
|||||||
import { PostSaveConfig, SaveOption } from "../../types/saveConfig";
|
import { PostSaveConfig, SaveOption } from "../../types/saveConfig";
|
||||||
import keycloak from "../keycloak-config";
|
import keycloak from "../keycloak-config";
|
||||||
import { PutMask } from "../../types/mask";
|
import { PutMask } from "../../types/mask";
|
||||||
|
import { GetUserTag, PutUserTag } from "../../types/tags";
|
||||||
|
|
||||||
const instanceApi = axios.create({
|
const instanceApi = axios.create({
|
||||||
baseURL: proxyURL.toString(),
|
baseURL: proxyURL.toString(),
|
||||||
@ -61,6 +62,9 @@ export const frigateApi = {
|
|||||||
cameraIDs: cameraIDs
|
cameraIDs: cameraIDs
|
||||||
}).then(res => res.data),
|
}).then(res => res.data),
|
||||||
getAdminRole: () => instanceApi.get<GetConfig>('apiv1/config/admin').then(res => res.data),
|
getAdminRole: () => instanceApi.get<GetConfig>('apiv1/config/admin').then(res => res.data),
|
||||||
|
getUserTags: () => instanceApi.get<GetUserTag[]>('apiv1/tags').then(res => res.data),
|
||||||
|
putUserTag: (tag: PutUserTag) => instanceApi.put<GetUserTag>('apiv1/tags', tag).then(res => res.data),
|
||||||
|
delUserTag: (tagId: string) => instanceApi.delete<GetUserTag>(`apiv1/tags/${tagId}`).then(res => res.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
|
export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
|
||||||
@ -242,4 +246,6 @@ export const frigateQueryKeys = {
|
|||||||
getRoleWCameras: 'roles-cameras',
|
getRoleWCameras: 'roles-cameras',
|
||||||
getUsersByRole: 'users-role',
|
getUsersByRole: 'users-role',
|
||||||
getAdminRole: 'admin-role',
|
getAdminRole: 'admin-role',
|
||||||
|
getUserTags: 'users-tags',
|
||||||
|
putUserTag: 'put-user-tag',
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/shared/components/RightSideBar.tsx
Normal file
62
src/shared/components/RightSideBar.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Aside, Button, createStyles } from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { SideBarContext } from '../../widgets/sidebars/SideBarContext';
|
||||||
|
import { dimensions } from '../dimensions/dimensions';
|
||||||
|
import { useMantineSize } from '../utils/mantine.size.convertor';
|
||||||
|
import { SideButton } from './SideButton';
|
||||||
|
|
||||||
|
|
||||||
|
interface RightSideBarProps {
|
||||||
|
onChangeHidden?: (isHidden: boolean) => void,
|
||||||
|
children?: React.ReactNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme,
|
||||||
|
{ visible }: { visible: boolean }) => ({
|
||||||
|
navbar: {
|
||||||
|
transition: 'transform 0.3s ease-in-out',
|
||||||
|
transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
||||||
|
},
|
||||||
|
|
||||||
|
aside: {
|
||||||
|
transition: 'transform 0.3s ease-in-out',
|
||||||
|
transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
const RightSideBar = ({ onChangeHidden, children }: RightSideBarProps) => {
|
||||||
|
|
||||||
|
const side = 'right'
|
||||||
|
|
||||||
|
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||||
|
|
||||||
|
const [visible, { open, close }] = useDisclosure(true);
|
||||||
|
|
||||||
|
const { classes } = useStyles({ visible })
|
||||||
|
|
||||||
|
const { childrenComponent } = useContext(SideBarContext);
|
||||||
|
|
||||||
|
const handleClickVisible = (state: boolean) => {
|
||||||
|
if (state) open()
|
||||||
|
else close()
|
||||||
|
if (onChangeHidden) onChangeHidden(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<div>
|
||||||
|
<Aside
|
||||||
|
className={classes.aside}
|
||||||
|
p={dimensions.hideSidebarsSize}
|
||||||
|
width={{ sm: 200, lg: 300 }}>
|
||||||
|
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||||
|
{childrenComponent}
|
||||||
|
</Aside>
|
||||||
|
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightSideBar;
|
||||||
@ -8,111 +8,113 @@ import { dimensions } from '../dimensions/dimensions';
|
|||||||
import { useMantineSize } from '../utils/mantine.size.convertor';
|
import { useMantineSize } from '../utils/mantine.size.convertor';
|
||||||
import { SideButton } from './SideButton';
|
import { SideButton } from './SideButton';
|
||||||
|
|
||||||
export interface SideBarProps {
|
// export interface SideBarProps {
|
||||||
isHidden: (isHidden: boolean) => void,
|
// onChangeHidden: (isHidden: boolean) => void,
|
||||||
side: 'left' | 'right',
|
// side: 'left' | 'right',
|
||||||
children?: React.ReactNode,
|
// children?: React.ReactNode,
|
||||||
}
|
// }
|
||||||
|
|
||||||
const useStyles = createStyles((theme,
|
// const useStyles = createStyles((theme,
|
||||||
{ visible }: { visible: boolean }) => ({
|
// { visible }: { visible: boolean }) => ({
|
||||||
navbar: {
|
// navbar: {
|
||||||
transition: 'transform 0.3s ease-in-out',
|
// transition: 'transform 0.3s ease-in-out',
|
||||||
transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
// transform: visible ? 'translateX(0)' : 'translateX(-100%)',
|
||||||
},
|
// },
|
||||||
|
|
||||||
aside: {
|
// aside: {
|
||||||
transition: 'transform 0.3s ease-in-out',
|
// transition: 'transform 0.3s ease-in-out',
|
||||||
transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
// transform: visible ? 'translateX(0)' : 'translateX(+100%)',
|
||||||
},
|
// },
|
||||||
}))
|
// }))
|
||||||
|
|
||||||
|
|
||||||
const SideBar = ({ isHidden, side, children }: SideBarProps) => {
|
// const SideBar = ({ onChangeHidden: isHidden, side, children }: SideBarProps) => {
|
||||||
const { t } = useTranslation()
|
// const { t } = useTranslation()
|
||||||
const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
// const hideSizePx = useMantineSize(dimensions.hideSidebarsSize)
|
||||||
const initialVisible = () => {
|
// const initialVisible = () => {
|
||||||
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
// const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||||
if (savedVisibility === null) {
|
// if (savedVisibility === null) {
|
||||||
return window.innerWidth < hideSizePx;
|
// return window.innerWidth < hideSizePx;
|
||||||
}
|
// }
|
||||||
return savedVisibility === 'true';
|
// return savedVisibility === 'true';
|
||||||
}
|
// }
|
||||||
const [visible, { open, close }] = useDisclosure(initialVisible());
|
// const [visible, { open, close }] = useDisclosure(initialVisible());
|
||||||
|
|
||||||
const { classes } = useStyles({ visible })
|
// const { classes } = useStyles({ visible })
|
||||||
|
|
||||||
const handleClickVisible = (state: boolean) => {
|
// const handleClickVisible = (state: boolean) => {
|
||||||
localStorage.setItem(`sidebarVisible_${side}`, String(state))
|
// localStorage.setItem(`sidebarVisible_${side}`, String(state))
|
||||||
if (state) open()
|
// if (state) open()
|
||||||
else close()
|
// else close()
|
||||||
}
|
// }
|
||||||
|
|
||||||
const { sideBarsStore } = useContext(Context)
|
// const { sideBarsStore } = useContext(Context)
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (sideBarsStore.rightVisible && side === 'right' && !visible) {
|
// if (sideBarsStore.rightVisible && side === 'right' && !visible) {
|
||||||
open()
|
// open()
|
||||||
} else if (!sideBarsStore.rightVisible && side === 'right' && visible) {
|
// } else if (!sideBarsStore.rightVisible && side === 'right' && visible) {
|
||||||
close()
|
// close()
|
||||||
}
|
// }
|
||||||
}, [sideBarsStore.rightVisible])
|
// }, [sideBarsStore.rightVisible])
|
||||||
|
|
||||||
const [leftChildren, setLeftChildren] = useState<React.ReactNode>(() => {
|
// const [leftChildren, setLeftChildren] = useState<React.ReactNode>(() => {
|
||||||
if (children && side === 'left') return children
|
// if (children && side === 'left') return children
|
||||||
else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren
|
// else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren
|
||||||
return null
|
// return null
|
||||||
})
|
// })
|
||||||
const [rightChildren, setRightChildren] = useState<React.ReactNode>(() => {
|
// const [rightChildren, setRightChildren] = useState<React.ReactNode>(() => {
|
||||||
if (children && side === 'right') return children
|
// if (children && side === 'right') return children
|
||||||
else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren
|
// else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren
|
||||||
return null
|
// return null
|
||||||
})
|
// })
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
setLeftChildren(sideBarsStore.leftChildren)
|
// setLeftChildren(sideBarsStore.leftChildren)
|
||||||
}, [sideBarsStore.leftChildren])
|
// }, [sideBarsStore.leftChildren])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
setRightChildren(sideBarsStore.rightChildren)
|
// setRightChildren(sideBarsStore.rightChildren)
|
||||||
}, [sideBarsStore.rightChildren])
|
// }, [sideBarsStore.rightChildren])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
isHidden(!visible)
|
// isHidden(!visible)
|
||||||
}, [visible])
|
// }, [visible])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
// const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`);
|
||||||
if (savedVisibility === null && window.innerWidth < hideSizePx) {
|
// if (savedVisibility === null && window.innerWidth < hideSizePx) {
|
||||||
open()
|
// open()
|
||||||
} else if (savedVisibility) {
|
// } else if (savedVisibility) {
|
||||||
savedVisibility === 'true' ? open() : close()
|
// savedVisibility === 'true' ? open() : close()
|
||||||
}
|
// }
|
||||||
}, [])
|
// }, [])
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div>
|
// <div>
|
||||||
{
|
// {
|
||||||
side === 'left' ?
|
// side === 'left' ?
|
||||||
<Navbar
|
// <Navbar
|
||||||
className={classes.navbar}
|
// className={classes.navbar}
|
||||||
p={dimensions.hideSidebarsSize}
|
// p={dimensions.hideSidebarsSize}
|
||||||
width={{ sm: 200, lg: 300, }}
|
// width={{ sm: 200, lg: 300, }}
|
||||||
>
|
// >
|
||||||
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
// <Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||||
{leftChildren}
|
// {leftChildren}
|
||||||
</Navbar>
|
// </Navbar>
|
||||||
:
|
// :
|
||||||
<Aside
|
// <Aside
|
||||||
className={classes.aside}
|
// className={classes.aside}
|
||||||
p={dimensions.hideSidebarsSize}
|
// p={dimensions.hideSidebarsSize}
|
||||||
width={{ sm: 200, lg: 300 }}>
|
// width={{ sm: 200, lg: 300 }}>
|
||||||
<Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
// <Button onClick={() => handleClickVisible(false)}>{t('hide')}</Button>
|
||||||
{rightChildren}
|
// {rightChildren}
|
||||||
</Aside>
|
// </Aside>
|
||||||
}
|
// }
|
||||||
<SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
// <SideButton side={side} hide={visible} onClick={() => handleClickVisible(true)} />
|
||||||
</div>
|
// </div>
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default observer(SideBar)
|
export const toDoDelete = () => ( <></> )
|
||||||
|
|
||||||
|
// export default observer(SideBar)
|
||||||
@ -1,53 +1,78 @@
|
|||||||
import { Box, Flex, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core';
|
import { Box, Flex, Group, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core';
|
||||||
import { t } from 'i18next';
|
import React, { CSSProperties, forwardRef } from 'react';
|
||||||
import React, { CSSProperties, FC } from 'react';
|
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
|
import { IconTrash } from '@tabler/icons-react';
|
||||||
|
|
||||||
interface CreatableMultiSelectProps {
|
interface CreatableMultiSelectProps {
|
||||||
id?: string
|
id?: string
|
||||||
|
value?: string[]
|
||||||
data: SelectItem[]
|
data: SelectItem[]
|
||||||
spaceBetween?: SystemProp<SpacingValue>
|
spaceBetween?: SystemProp<SpacingValue>
|
||||||
label?: string
|
label?: string
|
||||||
defaultValue?: string[]
|
defaultValue?: string[]
|
||||||
textClassName?: string
|
textClassName?: string
|
||||||
selectProps?: MultiSelectProps,
|
|
||||||
display?: SystemProp<CSSProperties['display']>
|
display?: SystemProp<CSSProperties['display']>
|
||||||
showClose?: boolean,
|
showClose?: boolean,
|
||||||
changedState?(value: string[], id?: string): void
|
onChange?(value: string[]): void
|
||||||
onClose?(): void
|
onClose?(): void
|
||||||
onCreate?(value: string): void
|
onCreate?(query: string | SelectItem | null | undefined): SelectItem | string | null | undefined
|
||||||
|
error?: string
|
||||||
|
onTrashClick?(value: string): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
onTrashClick?: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeletableItem = forwardRef<HTMLDivElement, ItemProps>(
|
||||||
|
({ label, value, onTrashClick, ...others }, ref) => (
|
||||||
|
<div {...others} ref={ref}>
|
||||||
|
<Flex justify='space-between'>
|
||||||
|
<Text>{label}</Text>
|
||||||
|
<IconTrash
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onTrashClick) onTrashClick(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
||||||
id,
|
id,
|
||||||
|
value,
|
||||||
data,
|
data,
|
||||||
spaceBetween,
|
spaceBetween,
|
||||||
label,
|
label,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
textClassName,
|
textClassName,
|
||||||
selectProps,
|
|
||||||
display,
|
display,
|
||||||
showClose,
|
showClose,
|
||||||
changedState,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
onCreate
|
onCreate,
|
||||||
|
onTrashClick,
|
||||||
|
error,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleOnChange = (value: string[]) => {
|
const handleOnChange = (value: string[]) => {
|
||||||
if (changedState) {
|
if (onChange) onChange(value)
|
||||||
changedState(value, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnCreate = (query: string | SelectItem | null | undefined) => {
|
const handleOnCreate = (query: string | SelectItem | null | undefined) => {
|
||||||
const item = { value: query, label: query } as SelectItem
|
if (onCreate) return onCreate(query)
|
||||||
if (onCreate && typeof query === 'string') {
|
}
|
||||||
onCreate(query)
|
|
||||||
}
|
const handleTrashClick = (value: string) => {
|
||||||
return item
|
if (onTrashClick) onTrashClick(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -59,17 +84,25 @@ const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
|||||||
: null}
|
: null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
{...selectProps}
|
value={value}
|
||||||
mt={spaceBetween}
|
mt={spaceBetween}
|
||||||
data={data}
|
data={data}
|
||||||
|
disableSelectedItemFiltering
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
itemComponent={forwardRef((itemProps, ref) => (
|
||||||
|
<DeletableItem
|
||||||
|
{...itemProps}
|
||||||
|
ref={ref} // передаем ref в DeletableItem
|
||||||
|
onTrashClick={handleTrashClick} // передаем коллбэк сюда напрямую
|
||||||
|
/>
|
||||||
|
))}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
creatable
|
creatable
|
||||||
getCreateLabel={(query) => `+ ${t('create') + ' ' + query}`}
|
getCreateLabel={(query) => `+ ${t('create') + ' ' + query}`}
|
||||||
onCreate={handleOnCreate}
|
onCreate={handleOnCreate}
|
||||||
{...selectProps}
|
error={error}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { CSSProperties } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
|
|
||||||
interface MultiSelectFilterProps {
|
interface MultiSelectFilterProps extends MultiSelectProps {
|
||||||
id?: string
|
id?: string
|
||||||
data: SelectItem[]
|
data: SelectItem[]
|
||||||
spaceBetween?: SystemProp<SpacingValue>
|
spaceBetween?: SystemProp<SpacingValue>
|
||||||
|
|||||||
157
src/shared/components/filters/UserTagsFilter.tsx
Normal file
157
src/shared/components/filters/UserTagsFilter.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { SelectItem } from '@mantine/core';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import CreatableMultiSelect from './CreatableMultiSelect';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { frigateApi, frigateQueryKeys } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import RetryError from '../RetryError';
|
||||||
|
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||||
|
import { mapUserTagsToSelectItems, PutUserTag } from '../../../types/tags';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import { IconAlertCircle } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const UserTagsFilter = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const [selectedList, setSelectedList] = useState<string[]>([])
|
||||||
|
|
||||||
|
const { data, isPending, isError, refetch } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getUserTags],
|
||||||
|
queryFn: frigateApi.getUserTags
|
||||||
|
})
|
||||||
|
|
||||||
|
const SelectItemSchema = z.object({
|
||||||
|
value: z.string(),
|
||||||
|
label: z.string().optional(),
|
||||||
|
selected: z.boolean().optional(),
|
||||||
|
disabled: z.boolean().optional(),
|
||||||
|
group: z.string().optional(),
|
||||||
|
}).passthrough();
|
||||||
|
|
||||||
|
const [tagsError, setTagsError] = useState<string>('')
|
||||||
|
|
||||||
|
const validateNewTag = (query: string): boolean => {
|
||||||
|
if (query.length > 10) {
|
||||||
|
setTagsError(t('mainPage.tagsError'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mutate } = useMutation({
|
||||||
|
mutationFn: (newTag: PutUserTag) => frigateApi.putUserTag(newTag)
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
return Promise.reject(error.response.data)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
setSelectedList([...selectedList, data.id])
|
||||||
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (e && e.message) {
|
||||||
|
notifications.show({
|
||||||
|
id: e.message,
|
||||||
|
withCloseButton: true,
|
||||||
|
autoClose: 5000,
|
||||||
|
title: "Error",
|
||||||
|
message: e.message,
|
||||||
|
color: 'red',
|
||||||
|
icon: <IconAlertCircle />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutate: deleteTag } = useMutation({
|
||||||
|
mutationFn: (tagId: string) => frigateApi.delUserTag(tagId)
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
return Promise.reject(error.response.data)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getUserTags] })
|
||||||
|
const updatedList = selectedList.filter(item => data.id === item)
|
||||||
|
setSelectedList(updatedList)
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
if (e && e.message) {
|
||||||
|
notifications.show({
|
||||||
|
id: e.message,
|
||||||
|
withCloseButton: true,
|
||||||
|
autoClose: 5000,
|
||||||
|
title: "Error",
|
||||||
|
message: e.message,
|
||||||
|
color: 'red',
|
||||||
|
icon: <IconAlertCircle />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveNewTag = (value: string) => {
|
||||||
|
const newTag: PutUserTag = {
|
||||||
|
value,
|
||||||
|
cameraIds: []
|
||||||
|
}
|
||||||
|
mutate(newTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCreate = (query: string | SelectItem | null | undefined) => {
|
||||||
|
setTagsError('')
|
||||||
|
const parseQuery = SelectItemSchema.safeParse(query)
|
||||||
|
if (typeof query === 'string') {
|
||||||
|
if (!validateNewTag(query)) return undefined
|
||||||
|
saveNewTag(query)
|
||||||
|
// return query
|
||||||
|
}
|
||||||
|
else if (parseQuery.success) {
|
||||||
|
const parsedQuery = parseQuery.data as SelectItem
|
||||||
|
if (!validateNewTag(parsedQuery.value)) return undefined
|
||||||
|
saveNewTag(parsedQuery.value)
|
||||||
|
// return parsedQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPending) return <CogwheelLoader />
|
||||||
|
if (isError) return <RetryError onRetry={refetch} />
|
||||||
|
|
||||||
|
const handleOnChange = (value: string[]) => {
|
||||||
|
console.log('cahnged:', value)
|
||||||
|
const updatedList = selectedList.filter(item => value.includes(item))
|
||||||
|
const newItems = value.filter(item => !selectedList.includes(item))
|
||||||
|
setSelectedList([...updatedList, ...newItems])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTrashClick = (value: string) => {
|
||||||
|
if (value) {
|
||||||
|
deleteTag(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CreatableMultiSelect
|
||||||
|
value={selectedList}
|
||||||
|
label={t('mainPage.createSelectTags')}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
spaceBetween='1rem'
|
||||||
|
data={mapUserTagsToSelectItems(data)}
|
||||||
|
onCreate={onCreate}
|
||||||
|
error={tagsError}
|
||||||
|
onTrashClick={handleTrashClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserTagsFilter;
|
||||||
@ -1,20 +1,17 @@
|
|||||||
import { MainStore } from "./main.store";
|
import { MainStore } from "./main.store";
|
||||||
import { ModalStore } from "./modal.store";
|
import { ModalStore } from "./modal.store";
|
||||||
import { RecordingsStore } from "./recordings.store";
|
import { RecordingsStore } from "./recordings.store";
|
||||||
import { SideBarsStore } from "./sidebars.store";
|
|
||||||
import { UserStore } from "./user.store";
|
import { UserStore } from "./user.store";
|
||||||
|
|
||||||
class RootStore {
|
class RootStore {
|
||||||
mainStore: MainStore
|
mainStore: MainStore
|
||||||
userStore: UserStore
|
userStore: UserStore
|
||||||
modalStore: ModalStore
|
modalStore: ModalStore
|
||||||
sideBarsStore: SideBarsStore
|
|
||||||
recordingsStore: RecordingsStore
|
recordingsStore: RecordingsStore
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mainStore = new MainStore()
|
this.mainStore = new MainStore()
|
||||||
this.userStore = new UserStore()
|
this.userStore = new UserStore()
|
||||||
this.modalStore = new ModalStore(this)
|
this.modalStore = new ModalStore(this)
|
||||||
this.sideBarsStore = new SideBarsStore()
|
|
||||||
this.recordingsStore = new RecordingsStore()
|
this.recordingsStore = new RecordingsStore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +1,47 @@
|
|||||||
import { action, makeAutoObservable } from "mobx"
|
import { action, makeAutoObservable } from "mobx"
|
||||||
|
|
||||||
|
|
||||||
|
// TODO Delete
|
||||||
export class SideBarsStore {
|
export class SideBarsStore {
|
||||||
|
|
||||||
private _rightVisible: boolean = true
|
// private _rightVisible: boolean = true
|
||||||
public get rightVisible(): boolean {
|
// public get rightVisible(): boolean {
|
||||||
return this._rightVisible
|
// return this._rightVisible
|
||||||
}
|
// }
|
||||||
public set rightVisible(visible: boolean) {
|
// public set rightVisible(visible: boolean) {
|
||||||
this._rightVisible = visible
|
// this._rightVisible = visible
|
||||||
}
|
// }
|
||||||
private _leftVisible: boolean = true
|
// private _leftVisible: boolean = true
|
||||||
public get leftVisible(): boolean {
|
// public get leftVisible(): boolean {
|
||||||
return this._leftVisible
|
// return this._leftVisible
|
||||||
}
|
// }
|
||||||
public set leftVisible(visible: boolean) {
|
// public set leftVisible(visible: boolean) {
|
||||||
this._leftVisible = visible
|
// this._leftVisible = visible
|
||||||
}
|
// }
|
||||||
|
|
||||||
private _leftChildren: React.ReactNode = null
|
// private _leftChildren: React.ReactNode = null
|
||||||
public get leftChildren(): React.ReactNode {
|
// public get leftChildren(): React.ReactNode {
|
||||||
return this._leftChildren
|
// return this._leftChildren
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
private _rightChildren: React.ReactNode = null
|
// private _rightChildren: React.ReactNode = null
|
||||||
public get rightChildren(): React.ReactNode {
|
// public get rightChildren(): React.ReactNode {
|
||||||
return this._rightChildren
|
// return this._rightChildren
|
||||||
}
|
// }
|
||||||
|
|
||||||
constructor () {
|
// constructor () {
|
||||||
makeAutoObservable(this, {
|
// makeAutoObservable(this, {
|
||||||
setRightChildren: action,
|
// setRightChildren: action,
|
||||||
setLeftChildren: action,
|
// setLeftChildren: action,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
setRightChildren = (value: React.ReactNode) => {
|
// setRightChildren = (value: React.ReactNode) => {
|
||||||
this._rightChildren = value
|
// this._rightChildren = value
|
||||||
}
|
// }
|
||||||
|
|
||||||
setLeftChildren = (value: React.ReactNode) => {
|
// setLeftChildren = (value: React.ReactNode) => {
|
||||||
this._leftChildren = value
|
// this._leftChildren = value
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
26
src/types/tags.ts
Normal file
26
src/types/tags.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { SelectItem } from "@mantine/core";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const putUserTag = z.object({
|
||||||
|
value: z.string(),
|
||||||
|
cameraIds: z.string().array()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getUserTag = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
createdAt: z.string().datetime(),
|
||||||
|
updatedAt: z.string().datetime(),
|
||||||
|
value: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
cameraIds: z.string().array(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type GetUserTag = z.infer<typeof getUserTag>
|
||||||
|
export type PutUserTag = z.infer<typeof putUserTag>
|
||||||
|
|
||||||
|
export const mapUserTagsToSelectItems = (tags: GetUserTag[]): SelectItem[] => {
|
||||||
|
return tags.map(tag => ({
|
||||||
|
value: tag.id,
|
||||||
|
label: tag.value
|
||||||
|
}))
|
||||||
|
}
|
||||||
@ -3,10 +3,13 @@ import { useContext } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Context } from '../..';
|
import { Context } from '../..';
|
||||||
import HostSelect from '../../shared/components/filters/HostSelect';
|
import HostSelect from '../../shared/components/filters/HostSelect';
|
||||||
|
import UserTagsFilter from '../../shared/components/filters/UserTagsFilter';
|
||||||
import { isProduction } from '../../shared/env.const';
|
import { isProduction } from '../../shared/env.const';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const MainFiltersRightSide = () => {
|
const MainFiltersRightSide = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -26,16 +29,11 @@ const MainFiltersRightSide = () => {
|
|||||||
defaultId={selectedHostId}
|
defaultId={selectedHostId}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
/>
|
/>
|
||||||
{/* TODO Add tags select */}
|
|
||||||
{/* <CreatableMultiSelect
|
<UserTagsFilter />
|
||||||
label={t('mainPage.createSelectTags')}
|
|
||||||
spaceBetween='1rem'
|
|
||||||
data={[]}
|
|
||||||
/> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default observer(MainFiltersRightSide);
|
export default observer(MainFiltersRightSide);
|
||||||
21
src/widgets/sidebars/SideBarContext.tsx
Normal file
21
src/widgets/sidebars/SideBarContext.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React, { createContext, ReactNode, useState } from 'react';
|
||||||
|
|
||||||
|
interface SideBarContextProps {
|
||||||
|
childrenComponent: ReactNode;
|
||||||
|
setChildrenComponent: (component: ReactNode) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SideBarContext = createContext<SideBarContextProps>({
|
||||||
|
childrenComponent: null,
|
||||||
|
setChildrenComponent: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SideBarProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
|
const [childrenComponent, setChildrenComponent] = useState<ReactNode>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideBarContext.Provider value={{ childrenComponent, setChildrenComponent }}>
|
||||||
|
{children}
|
||||||
|
</SideBarContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user