From 90114ac7a698aa847f59eb66537a1fe937a12dde Mon Sep 17 00:00:00 2001 From: NlightN22 Date: Sun, 29 Sep 2024 22:21:59 +0700 Subject: [PATCH] Add user tags filter at main page --- src/App.tsx | 7 +- src/AppBody.tsx | 28 +-- src/locales/en.ts | 3 +- src/locales/ru.ts | 3 +- src/pages/403.tsx | 10 - src/pages/404.tsx | 17 +- src/pages/AccessSettingsPage.tsx | 14 +- src/pages/EditCameraPage.tsx | 14 +- src/pages/FrigateHostsPage.tsx | 13 +- src/pages/HostConfigPage.tsx | 22 +- src/pages/HostSystemPage.tsx | 16 +- src/pages/LiveCameraPage.tsx | 14 -- src/pages/MainPage.tsx | 52 ++--- src/pages/PlayRecordPage.tsx | 14 +- src/pages/RecordingsPage.tsx | 22 +- src/pages/RetryErrorPage.tsx | 22 +- src/pages/SettingsPage.tsx | 14 +- src/pages/TestPage.tsx | 18 +- src/router/routes.tsx | 4 - src/services/frigate.proxy/frigate.api.ts | 6 + src/shared/components/RightSideBar.tsx | 62 ++++++ src/shared/components/SideBar.tsx | 190 +++++++++--------- .../filters/CreatableMultiSelect.tsx | 73 +++++-- .../components/filters/MultiSelectFilter.tsx | 2 +- .../components/filters/UserTagsFilter.tsx | 157 +++++++++++++++ src/shared/stores/root.store.ts | 3 - src/shared/stores/sidebars.store.ts | 70 +++---- src/types/tags.ts | 26 +++ src/widgets/sidebars/MainFiltersRightSide.tsx | 14 +- src/widgets/sidebars/SideBarContext.tsx | 21 ++ 30 files changed, 541 insertions(+), 390 deletions(-) create mode 100644 src/shared/components/RightSideBar.tsx create mode 100644 src/shared/components/filters/UserTagsFilter.tsx create mode 100644 src/types/tags.ts create mode 100644 src/widgets/sidebars/SideBarContext.tsx diff --git a/src/App.tsx b/src/App.tsx index d2d74e9..8dd32b5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { useEffect, useState } from 'react'; import AppBody from './AppBody'; import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal'; import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal'; +import { SideBarProvider } from './widgets/sidebars/SideBarContext'; const queryClient = new QueryClient({ defaultOptions: { @@ -67,8 +68,10 @@ function App() { }} > - - + + + + diff --git a/src/AppBody.tsx b/src/AppBody.tsx index d558902..0230ad1 100644 --- a/src/AppBody.tsx +++ b/src/AppBody.tsx @@ -5,9 +5,10 @@ import { useTranslation } from 'react-i18next'; import { Context } from '.'; import AppRouter from './router/AppRouter'; import { routesPath } from './router/routes.path'; -import SideBar from './shared/components/SideBar'; import { isProduction } from './shared/env.const'; import { HeaderAction } from './widgets/header/HeaderAction'; +import RightSideBar from "./shared/components/RightSideBar"; +import { useLocation } from "react-router-dom"; const AppBody = () => { const { t } = useTranslation() @@ -20,20 +21,22 @@ const AppBody = () => { { link: routesPath.ACCESS_PATH, label: t('header.acessSettings'), admin: true }, ] - const { sideBarsStore } = useContext(Context) - const [leftSideBar, setLeftSidebar] = useState(false) - const [rightSideBar, setRightSidebar] = useState(false) + const location = useLocation() - const leftSideBarIsHidden = (isHidden: boolean) => { - setLeftSidebar(!isHidden) - } - const rightSideBarIsHidden = (isHidden: boolean) => { - setRightSidebar(!isHidden) - } + const pathsWithLeftSidebar: string[] = [] + const pathsWithRightSidebar: string[] = [routesPath.MAIN_PATH, routesPath.RECORDINGS_PATH] + + const [leftSideBar, setLeftSidebar] = useState(pathsWithLeftSidebar.includes(location.pathname)) + const [rightSideBar, setRightSidebar] = useState(pathsWithRightSidebar.includes(location.pathname)) + + const handleRightSidebarChange = (isVisible: boolean) => { + setRightSidebar(isVisible); + }; const theme = useMantineTheme(); + if (!isProduction) console.log("render Main") return ( { header={ } + aside={ - !sideBarsStore.rightVisible ? <> : - + !pathsWithRightSidebar.includes(location.pathname) ? <> : + } > diff --git a/src/locales/en.ts b/src/locales/en.ts index ceeea7c..9ca9779 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -2,7 +2,8 @@ import { error } from "console" const en = { mainPage: { - createSelectTags: 'Create\\Select tags' + createSelectTags: 'Create\\Select tags', + tagsError: 'Cannot be more than 10 symbols', }, editCameraPage: { notFrigateCamera: 'Not frigate camera', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index b18562c..b9ee6e7 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1,6 +1,7 @@ const ru = { mainPage: { - createSelectTags: 'Создать\\Выбрать тэги' + createSelectTags: 'Создай\\Выбери тэги', + tagsError: 'Не может быть более 10 символов', }, editCameraPage: { notFrigateCamera: 'Не камера Фригата', diff --git a/src/pages/403.tsx b/src/pages/403.tsx index 2fe0016..7940448 100644 --- a/src/pages/403.tsx +++ b/src/pages/403.tsx @@ -10,17 +10,7 @@ import { useNavigate } from 'react-router-dom'; const Forbidden = () => { const { t } = useTranslation() 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 = () => { navigate(routesPath.MAIN_PATH) diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 6a35057..1e3d452 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,24 +1,11 @@ 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 { useTranslation } from 'react-i18next'; +import { routesPath } from '../router/routes.path'; +import CogWheelWithText from '../shared/components/loaders/CogWheelWithText'; const NotFound = () => { 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 = () => { window.location.replace(routesPath.MAIN_PATH) diff --git a/src/pages/AccessSettingsPage.tsx b/src/pages/AccessSettingsPage.tsx index f13566f..0c640f1 100644 --- a/src/pages/AccessSettingsPage.tsx +++ b/src/pages/AccessSettingsPage.tsx @@ -2,9 +2,8 @@ import { Flex, Group, Text } from '@mantine/core'; import { useMediaQuery } from '@mantine/hooks'; import { useQuery } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import CamerasTransferList from '../shared/components/CamerasTransferList'; @@ -17,22 +16,13 @@ import RetryErrorPage from './RetryErrorPage'; const AccessSettings = () => { const { t } = useTranslation() - const executed = useRef(false) const { data, isPending, isError, refetch } = useQuery({ queryKey: [frigateQueryKeys.getRoles], queryFn: frigateApi.getRoles }) - const { sideBarsStore } = useContext(Context) 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 [roleId, setRoleId] = useState() diff --git a/src/pages/EditCameraPage.tsx b/src/pages/EditCameraPage.tsx index 2c08155..81e23dd 100644 --- a/src/pages/EditCameraPage.tsx +++ b/src/pages/EditCameraPage.tsx @@ -3,10 +3,9 @@ import { notifications } from '@mantine/notifications'; import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react'; import { useMutation, useQuery } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; import MaskSelect, { MaskItem, MaskType } from '../shared/components/filters/MaskSelect'; @@ -19,7 +18,6 @@ import RetryErrorPage from './RetryErrorPage'; const EditCameraPage = () => { const { t } = useTranslation() - const executed = useRef(false) let { id: cameraId } = useParams<'id'>() if (!cameraId) throw Error(t('editCameraPage.cameraIdNotExist')) const [selectedMask, setSelectedMask] = useState() @@ -105,16 +103,6 @@ const EditCameraPage = () => { 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 if (!isAdmin) return if (isError) return diff --git a/src/pages/FrigateHostsPage.tsx b/src/pages/FrigateHostsPage.tsx index 2108353..a25b7f7 100644 --- a/src/pages/FrigateHostsPage.tsx +++ b/src/pages/FrigateHostsPage.tsx @@ -3,9 +3,8 @@ import { notifications } from '@mantine/notifications'; import { IconAlertCircle } from '@tabler/icons-react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema'; @@ -19,22 +18,12 @@ import RetryErrorPage from './RetryErrorPage'; const FrigateHostsPage = () => { const { t } = useTranslation() - const executed = useRef(false) const queryClient = useQueryClient() const { isPending: hostsPending, error: hostsError, data } = useQuery({ queryKey: [frigateQueryKeys.getFrigateHosts], 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 [pageData, setPageData] = useState(data) diff --git a/src/pages/HostConfigPage.tsx b/src/pages/HostConfigPage.tsx index 3902430..09d6153 100644 --- a/src/pages/HostConfigPage.tsx +++ b/src/pages/HostConfigPage.tsx @@ -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 { notifications } from '@mantine/notifications'; import Editor, { Monaco } from '@monaco-editor/react'; +import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react'; import { useMutation, useQuery } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; import * as monaco from "monaco-editor"; 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 { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema'; @@ -16,9 +18,6 @@ import { isProduction } from '../shared/env.const'; import { SaveOption } from '../types/saveConfig'; import Forbidden from './403'; import RetryErrorPage from './RetryErrorPage'; -import { notifications } from '@mantine/notifications'; -import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react'; -import { useTranslation } from 'react-i18next'; window.MonacoEnvironment = { @@ -42,9 +41,7 @@ const HostConfigPage = () => { const queryParams = useMemo(() => { return new URLSearchParams(location.search); }, [location.search]) - const executed = useRef(false) const host = useRef() - const { sideBarsStore } = useContext(Context) let { id } = useParams<'id'>() 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 editorRef = useRef(null) diff --git a/src/pages/HostSystemPage.tsx b/src/pages/HostSystemPage.tsx index 85afb90..1f8d3a0 100644 --- a/src/pages/HostSystemPage.tsx +++ b/src/pages/HostSystemPage.tsx @@ -2,10 +2,9 @@ import { Flex, Grid, SegmentedControl, Text } from '@mantine/core'; import { openContextModal } from '@mantine/modals'; import { useQuery } from '@tanstack/react-query'; 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 { useParams } from 'react-router-dom'; -import { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; 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 { formatUptime } from '../shared/utils/dateUtil'; import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable'; +import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable'; import Forbidden from './403'; import RetryErrorPage from './RetryErrorPage'; -import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable'; export const hostSystemPageQuery = { hostId: 'hostId', @@ -31,21 +30,10 @@ enum SelectorItems { const HostSystemPage = () => { const { t } = useTranslation() - const executed = useRef(false) - const { sideBarsStore } = useContext(Context) const { isAdmin } = useAdminRole() const host = useRef() 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'>() const { data, isError, isPending, refetch } = useQuery({ diff --git a/src/pages/LiveCameraPage.tsx b/src/pages/LiveCameraPage.tsx index 61a1d13..be2fd17 100644 --- a/src/pages/LiveCameraPage.tsx +++ b/src/pages/LiveCameraPage.tsx @@ -1,10 +1,8 @@ import { Flex } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import { Context } from '..'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import CenterLoader from '../shared/components/loaders/CenterLoader'; import Player from '../widgets/Player'; @@ -13,7 +11,6 @@ import RetryErrorPage from './RetryErrorPage'; const LiveCameraPage = () => { const { t } = useTranslation() - const executed = useRef(false) let { id: cameraId } = useParams<'id'>() if (!cameraId) throw Error('Camera id does not exist') @@ -22,17 +19,6 @@ const LiveCameraPage = () => { 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 if (isError) return diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 832a5e0..56614f7 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -1,25 +1,25 @@ -import { Flex, Grid, TextInput } from '@mantine/core'; -import { IconSearch } from '@tabler/icons-react'; +import { Flex, Grid } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; 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 { useRealmUser } from '../hooks/useRealmUser'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema'; 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 { useKeycloak } from '@react-keycloak/web'; -import { useRealmUser } from '../hooks/useRealmUser'; +import CameraCard from '../widgets/CameraCard'; +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 { t } = useTranslation() - const executed = useRef(false) - const { sideBarsStore, mainStore } = useContext(Context) + const { mainStore } = useContext(Context) + const { setChildrenComponent } = useContext(SideBarContext) const { selectedHostId } = mainStore const [searchQuery, setSearchQuery] = useState() const [filteredCameras, setFilteredCameras] = useState() @@ -32,6 +32,11 @@ const MainPage = () => { queryFn: frigateApi.getCamerasWHost }) + useEffect(() => { + setChildrenComponent(); + return () => setChildrenComponent(null); + }, []); + useEffect(() => { if (!cameras) { setFilteredCameras(undefined) @@ -48,26 +53,6 @@ const MainPage = () => { }, [searchQuery, cameras, selectedHostId]) - - useEffect(() => { - sideBarsStore.setLeftChildren(null) - sideBarsStore.leftVisible = false - executed.current = true - if (!isProduction) console.log('MainPage rendered first time') - }, []) - - useEffect(() => { - sideBarsStore.setRightChildren() - sideBarsStore.rightVisible = true - - return () => { - sideBarsStore.setRightChildren(null) - sideBarsStore.rightVisible = false - } - }, [sideBarsStore]) - - //test change - const cards = useMemo(() => { if (filteredCameras) return filteredCameras.filter(camera => { @@ -115,7 +100,8 @@ const MainPage = () => { - {cards} + {/* TODO DELETE SLICE TO WORK */} + {cards.slice(0, 5)} diff --git a/src/pages/PlayRecordPage.tsx b/src/pages/PlayRecordPage.tsx index b3a47d5..3d52b2c 100644 --- a/src/pages/PlayRecordPage.tsx +++ b/src/pages/PlayRecordPage.tsx @@ -1,26 +1,14 @@ 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 VideoPlayer from '../shared/components/players/VideoPlayer'; import NotFound from './404'; -import { Context } from '..'; -import { observer } from 'mobx-react-lite'; export const playRecordPageQuery = { link: 'link', } 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 queryParams = new URLSearchParams(location.search) diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx index 706d5ca..91aa214 100644 --- a/src/pages/RecordingsPage.tsx +++ b/src/pages/RecordingsPage.tsx @@ -11,6 +11,7 @@ import SelectedCameraList from '../widgets/SelectedCameraList'; import SelectedDayList from '../widgets/SelectedDayList'; import SelectedHostList from '../widgets/SelectedHostList'; import { useTranslation } from 'react-i18next'; +import { SideBarContext } from '../widgets/sidebars/SideBarContext'; export const recordingsPageQuery = { @@ -24,7 +25,7 @@ export const recordingsPageQuery = { const RecordingsPage = () => { const { t } = useTranslation() const executed = useRef(false) - const { sideBarsStore, recordingsStore: recStore } = useContext(Context) + const { recordingsStore: recStore } = useContext(Context) const location = useLocation() const navigate = useNavigate() @@ -40,12 +41,17 @@ const RecordingsPage = () => { const [cameraId, setCameraId] = useState('') const [period, setPeriod] = useState<[Date | null, Date | null]>([null, null]) + const { setChildrenComponent } = useContext(SideBarContext) + + useEffect(() => { + setChildrenComponent(); + + return () => setChildrenComponent(null); + }, []); + + useEffect(() => { if (!executed.current) { - sideBarsStore.rightVisible = true - sideBarsStore.setRightChildren( - - ) if (paramHostId) recStore.hostIdParam = paramHostId if (paramCameraId) recStore.cameraIdParam = paramCameraId if (paramStartDay && paramEndDay) { @@ -55,10 +61,6 @@ const RecordingsPage = () => { } executed.current = true if (!isProduction) console.log('RecordingsPage rendered first time') - return () => { - sideBarsStore.setRightChildren(null) - sideBarsStore.rightVisible = false - } } }, []) @@ -114,7 +116,7 @@ const RecordingsPage = () => { if (hostId && paramHostId && !cameraId) { return } - + if (!isProduction) console.log('RecordingsPage rendered') return ( diff --git a/src/pages/RetryErrorPage.tsx b/src/pages/RetryErrorPage.tsx index 484449d..2b99e5a 100644 --- a/src/pages/RetryErrorPage.tsx +++ b/src/pages/RetryErrorPage.tsx @@ -1,11 +1,10 @@ -import { Flex, Button, 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 { Button, Flex, Text } from '@mantine/core'; import { observer } from 'mobx-react-lite'; +import React from 'react'; 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 { repeatVisible?: boolean @@ -21,20 +20,9 @@ const RetryErrorPage = ({ onRetry }: RetryErrorPageProps) => { const { t } = useTranslation() - const executed = useRef(false) 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 = () => { navigate(routesPath.MAIN_PATH) } diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 4030478..6f3f0a8 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -2,9 +2,8 @@ import { Flex, Space } from '@mantine/core'; import { useMediaQuery } from '@mantine/hooks'; import { useMutation } from '@tanstack/react-query'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Context } from '..'; import { useAdminRole } from '../hooks/useAdminRole'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetRole } from '../services/frigate.proxy/frigate.schema'; @@ -16,21 +15,10 @@ import Forbidden from './403'; const SettingsPage = () => { const { t } = useTranslation() - const executed = useRef(false) const [showRoles, setShowRoles] = useState(false) const [allRoles, setAllRoles] = useState() - 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 isMobile = useMediaQuery(dimensions.mobileSize) diff --git a/src/pages/TestPage.tsx b/src/pages/TestPage.tsx index 7e2006b..20e48c1 100644 --- a/src/pages/TestPage.tsx +++ b/src/pages/TestPage.tsx @@ -3,29 +3,13 @@ import { Editor, Monaco } from '@monaco-editor/react'; import { observer } from 'mobx-react-lite'; import * as monaco from 'monaco-editor'; import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml'; -import { useContext, useEffect, useRef } from 'react'; -import { Context } from '..'; +import { useRef } from 'react'; import HeadSearch from '../shared/components/inputs/HeadSearch'; const Test = () => { - const executed = useRef(false) const editorRef = useRef(null) 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 = ` # Property descriptions are displayed when hovering over properties using your cursor diff --git a/src/router/routes.tsx b/src/router/routes.tsx index 5c043e4..7467220 100644 --- a/src/router/routes.tsx +++ b/src/router/routes.tsx @@ -21,10 +21,6 @@ interface IRoute { } export const routes: IRoute[] = [ - { //todo delete - path: routesPath.TEST_PATH, - component: , - }, { path: routesPath.SETTINGS_PATH, component: , diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts index c89d4c0..dfd918a 100644 --- a/src/services/frigate.proxy/frigate.api.ts +++ b/src/services/frigate.proxy/frigate.api.ts @@ -15,6 +15,7 @@ import { FrigateStats, GetFfprobe, GetHostStorage, GetVaInfo } from "../../types import { PostSaveConfig, SaveOption } from "../../types/saveConfig"; import keycloak from "../keycloak-config"; import { PutMask } from "../../types/mask"; +import { GetUserTag, PutUserTag } from "../../types/tags"; const instanceApi = axios.create({ baseURL: proxyURL.toString(), @@ -61,6 +62,9 @@ export const frigateApi = { cameraIDs: cameraIDs }).then(res => res.data), getAdminRole: () => instanceApi.get('apiv1/config/admin').then(res => res.data), + getUserTags: () => instanceApi.get('apiv1/tags').then(res => res.data), + putUserTag: (tag: PutUserTag) => instanceApi.put('apiv1/tags', tag).then(res => res.data), + delUserTag: (tagId: string) => instanceApi.delete(`apiv1/tags/${tagId}`).then(res => res.data) } export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/` @@ -242,4 +246,6 @@ export const frigateQueryKeys = { getRoleWCameras: 'roles-cameras', getUsersByRole: 'users-role', getAdminRole: 'admin-role', + getUserTags: 'users-tags', + putUserTag: 'put-user-tag', } diff --git a/src/shared/components/RightSideBar.tsx b/src/shared/components/RightSideBar.tsx new file mode 100644 index 0000000..a856938 --- /dev/null +++ b/src/shared/components/RightSideBar.tsx @@ -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 (
+ + handleClickVisible(true)} /> +
+ ); +}; + +export default RightSideBar; \ No newline at end of file diff --git a/src/shared/components/SideBar.tsx b/src/shared/components/SideBar.tsx index fab5c86..3fffcbd 100644 --- a/src/shared/components/SideBar.tsx +++ b/src/shared/components/SideBar.tsx @@ -8,111 +8,113 @@ import { dimensions } from '../dimensions/dimensions'; import { useMantineSize } from '../utils/mantine.size.convertor'; import { SideButton } from './SideButton'; -export interface SideBarProps { - isHidden: (isHidden: boolean) => void, - side: 'left' | 'right', - children?: React.ReactNode, -} +// export interface SideBarProps { +// onChangeHidden: (isHidden: boolean) => void, +// side: 'left' | 'right', +// children?: React.ReactNode, +// } -const useStyles = createStyles((theme, - { visible }: { visible: boolean }) => ({ - navbar: { - transition: 'transform 0.3s ease-in-out', - transform: visible ? 'translateX(0)' : 'translateX(-100%)', - }, +// 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%)', - }, - })) +// aside: { +// transition: 'transform 0.3s ease-in-out', +// transform: visible ? 'translateX(0)' : 'translateX(+100%)', +// }, +// })) -const SideBar = ({ isHidden, side, children }: SideBarProps) => { - const { t } = useTranslation() - const hideSizePx = useMantineSize(dimensions.hideSidebarsSize) - const initialVisible = () => { - const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`); - if (savedVisibility === null) { - return window.innerWidth < hideSizePx; - } - return savedVisibility === 'true'; - } - const [visible, { open, close }] = useDisclosure(initialVisible()); +// const SideBar = ({ onChangeHidden: isHidden, side, children }: SideBarProps) => { +// const { t } = useTranslation() +// const hideSizePx = useMantineSize(dimensions.hideSidebarsSize) +// const initialVisible = () => { +// 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) => { - localStorage.setItem(`sidebarVisible_${side}`, String(state)) - if (state) open() - else close() - } +// const handleClickVisible = (state: boolean) => { +// localStorage.setItem(`sidebarVisible_${side}`, String(state)) +// if (state) open() +// else close() +// } - const { sideBarsStore } = useContext(Context) - useEffect(() => { - if (sideBarsStore.rightVisible && side === 'right' && !visible) { - open() - } else if (!sideBarsStore.rightVisible && side === 'right' && visible) { - close() - } - }, [sideBarsStore.rightVisible]) +// const { sideBarsStore } = useContext(Context) +// useEffect(() => { +// if (sideBarsStore.rightVisible && side === 'right' && !visible) { +// open() +// } else if (!sideBarsStore.rightVisible && side === 'right' && visible) { +// close() +// } +// }, [sideBarsStore.rightVisible]) - const [leftChildren, setLeftChildren] = useState(() => { - if (children && side === 'left') return children - else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren - return null - }) - const [rightChildren, setRightChildren] = useState(() => { - if (children && side === 'right') return children - else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren - return null - }) +// const [leftChildren, setLeftChildren] = useState(() => { +// if (children && side === 'left') return children +// else if (sideBarsStore.leftChildren) return sideBarsStore.leftChildren +// return null +// }) +// const [rightChildren, setRightChildren] = useState(() => { +// if (children && side === 'right') return children +// else if (sideBarsStore.rightChildren) return sideBarsStore.rightChildren +// return null +// }) - useEffect(() => { - setLeftChildren(sideBarsStore.leftChildren) - }, [sideBarsStore.leftChildren]) +// useEffect(() => { +// setLeftChildren(sideBarsStore.leftChildren) +// }, [sideBarsStore.leftChildren]) - useEffect(() => { - setRightChildren(sideBarsStore.rightChildren) - }, [sideBarsStore.rightChildren]) +// useEffect(() => { +// setRightChildren(sideBarsStore.rightChildren) +// }, [sideBarsStore.rightChildren]) - useEffect(() => { - isHidden(!visible) - }, [visible]) +// useEffect(() => { +// isHidden(!visible) +// }, [visible]) - useEffect(() => { - const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`); - if (savedVisibility === null && window.innerWidth < hideSizePx) { - open() - } else if (savedVisibility) { - savedVisibility === 'true' ? open() : close() - } - }, []) +// useEffect(() => { +// const savedVisibility = localStorage.getItem(`sidebarVisible_${side}`); +// if (savedVisibility === null && window.innerWidth < hideSizePx) { +// open() +// } else if (savedVisibility) { +// savedVisibility === 'true' ? open() : close() +// } +// }, []) - return ( -
- { - side === 'left' ? - - - {leftChildren} - - : - - } - handleClickVisible(true)} /> -
- ) -} +// return ( +//
+// { +// side === 'left' ? +// +// +// {leftChildren} +// +// : +// +// } +// handleClickVisible(true)} /> +//
+// ) +// } -export default observer(SideBar) \ No newline at end of file +export const toDoDelete = () => ( <> ) + +// export default observer(SideBar) \ No newline at end of file diff --git a/src/shared/components/filters/CreatableMultiSelect.tsx b/src/shared/components/filters/CreatableMultiSelect.tsx index 8d2c47b..4889a59 100644 --- a/src/shared/components/filters/CreatableMultiSelect.tsx +++ b/src/shared/components/filters/CreatableMultiSelect.tsx @@ -1,53 +1,78 @@ -import { Box, Flex, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core'; -import { t } from 'i18next'; -import React, { CSSProperties, FC } from 'react'; -import CloseWithTooltip from '../buttons/CloseWithTooltip'; +import { Box, Flex, Group, MultiSelect, MultiSelectProps, SelectItem, SpacingValue, SystemProp, Text } from '@mantine/core'; +import React, { CSSProperties, forwardRef } from 'react'; import { useTranslation } from 'react-i18next'; +import CloseWithTooltip from '../buttons/CloseWithTooltip'; +import { IconTrash } from '@tabler/icons-react'; interface CreatableMultiSelectProps { id?: string + value?: string[] data: SelectItem[] spaceBetween?: SystemProp label?: string defaultValue?: string[] textClassName?: string - selectProps?: MultiSelectProps, display?: SystemProp showClose?: boolean, - changedState?(value: string[], id?: string): void + onChange?(value: string[]): 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( + ({ label, value, onTrashClick, ...others }, ref) => ( +
+ + {label} + { + e.preventDefault(); + e.stopPropagation(); + if (onTrashClick) onTrashClick(value); + }} + /> + +
+ ) +); + const CreatableMultiSelect: React.FC = ({ id, + value, data, spaceBetween, label, defaultValue, textClassName, - selectProps, display, showClose, - changedState, + onChange, onClose, - onCreate + onCreate, + onTrashClick, + error, }) => { const { t } = useTranslation() const handleOnChange = (value: string[]) => { - if (changedState) { - changedState(value, id) - } + if (onChange) onChange(value) } const handleOnCreate = (query: string | SelectItem | null | undefined) => { - const item = { value: query, label: query } as SelectItem - if (onCreate && typeof query === 'string') { - onCreate(query) - } - return item + if (onCreate) return onCreate(query) + } + + const handleTrashClick = (value: string) => { + if (onTrashClick) onTrashClick(value) } return ( @@ -59,17 +84,25 @@ const CreatableMultiSelect: React.FC = ({ : null} ( + + ))} onChange={handleOnChange} searchable clearable creatable getCreateLabel={(query) => `+ ${t('create') + ' ' + query}`} onCreate={handleOnCreate} - {...selectProps} + error={error} /> ); diff --git a/src/shared/components/filters/MultiSelectFilter.tsx b/src/shared/components/filters/MultiSelectFilter.tsx index 0c9b67e..e6c07f9 100644 --- a/src/shared/components/filters/MultiSelectFilter.tsx +++ b/src/shared/components/filters/MultiSelectFilter.tsx @@ -3,7 +3,7 @@ import { CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import CloseWithTooltip from '../buttons/CloseWithTooltip'; -interface MultiSelectFilterProps { +interface MultiSelectFilterProps extends MultiSelectProps { id?: string data: SelectItem[] spaceBetween?: SystemProp diff --git a/src/shared/components/filters/UserTagsFilter.tsx b/src/shared/components/filters/UserTagsFilter.tsx new file mode 100644 index 0000000..c0b5e84 --- /dev/null +++ b/src/shared/components/filters/UserTagsFilter.tsx @@ -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([]) + + 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('') + + 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: , + }) + } + 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: , + }) + } + 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 + if (isError) return + + 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 ( + + ); +}; + +export default UserTagsFilter; \ No newline at end of file diff --git a/src/shared/stores/root.store.ts b/src/shared/stores/root.store.ts index 43d23a3..89ddb1d 100644 --- a/src/shared/stores/root.store.ts +++ b/src/shared/stores/root.store.ts @@ -1,20 +1,17 @@ import { MainStore } from "./main.store"; import { ModalStore } from "./modal.store"; import { RecordingsStore } from "./recordings.store"; -import { SideBarsStore } from "./sidebars.store"; import { UserStore } from "./user.store"; class RootStore { mainStore: MainStore userStore: UserStore modalStore: ModalStore - sideBarsStore: SideBarsStore recordingsStore: RecordingsStore constructor() { this.mainStore = new MainStore() this.userStore = new UserStore() this.modalStore = new ModalStore(this) - this.sideBarsStore = new SideBarsStore() this.recordingsStore = new RecordingsStore() } } diff --git a/src/shared/stores/sidebars.store.ts b/src/shared/stores/sidebars.store.ts index 33b1a75..175adfe 100644 --- a/src/shared/stores/sidebars.store.ts +++ b/src/shared/stores/sidebars.store.ts @@ -1,45 +1,47 @@ import { action, makeAutoObservable } from "mobx" + +// TODO Delete export class SideBarsStore { - private _rightVisible: boolean = true - public get rightVisible(): boolean { - return this._rightVisible - } - public set rightVisible(visible: boolean) { - this._rightVisible = visible - } - private _leftVisible: boolean = true - public get leftVisible(): boolean { - return this._leftVisible - } - public set leftVisible(visible: boolean) { - this._leftVisible = visible - } + // private _rightVisible: boolean = true + // public get rightVisible(): boolean { + // return this._rightVisible + // } + // public set rightVisible(visible: boolean) { + // this._rightVisible = visible + // } + // private _leftVisible: boolean = true + // public get leftVisible(): boolean { + // return this._leftVisible + // } + // public set leftVisible(visible: boolean) { + // this._leftVisible = visible + // } - private _leftChildren: React.ReactNode = null - public get leftChildren(): React.ReactNode { - return this._leftChildren - } + // private _leftChildren: React.ReactNode = null + // public get leftChildren(): React.ReactNode { + // return this._leftChildren + // } - private _rightChildren: React.ReactNode = null - public get rightChildren(): React.ReactNode { - return this._rightChildren - } + // private _rightChildren: React.ReactNode = null + // public get rightChildren(): React.ReactNode { + // return this._rightChildren + // } - constructor () { - makeAutoObservable(this, { - setRightChildren: action, - setLeftChildren: action, - }) - } + // constructor () { + // makeAutoObservable(this, { + // setRightChildren: action, + // setLeftChildren: action, + // }) + // } - setRightChildren = (value: React.ReactNode) => { - this._rightChildren = value - } + // setRightChildren = (value: React.ReactNode) => { + // this._rightChildren = value + // } - setLeftChildren = (value: React.ReactNode) => { - this._leftChildren = value - } + // setLeftChildren = (value: React.ReactNode) => { + // this._leftChildren = value + // } } \ No newline at end of file diff --git a/src/types/tags.ts b/src/types/tags.ts new file mode 100644 index 0000000..72db97b --- /dev/null +++ b/src/types/tags.ts @@ -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 +export type PutUserTag = z.infer + +export const mapUserTagsToSelectItems = (tags: GetUserTag[]): SelectItem[] => { + return tags.map(tag => ({ + value: tag.id, + label: tag.value + })) +} \ No newline at end of file diff --git a/src/widgets/sidebars/MainFiltersRightSide.tsx b/src/widgets/sidebars/MainFiltersRightSide.tsx index ce3b561..3d6c29b 100644 --- a/src/widgets/sidebars/MainFiltersRightSide.tsx +++ b/src/widgets/sidebars/MainFiltersRightSide.tsx @@ -3,10 +3,13 @@ import { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { Context } from '../..'; import HostSelect from '../../shared/components/filters/HostSelect'; +import UserTagsFilter from '../../shared/components/filters/UserTagsFilter'; import { isProduction } from '../../shared/env.const'; + + const MainFiltersRightSide = () => { const { t } = useTranslation() @@ -26,16 +29,11 @@ const MainFiltersRightSide = () => { defaultId={selectedHostId} onChange={handleSelect} /> - {/* TODO Add tags select */} - {/* */} + + + ); }; - - export default observer(MainFiltersRightSide); \ No newline at end of file diff --git a/src/widgets/sidebars/SideBarContext.tsx b/src/widgets/sidebars/SideBarContext.tsx new file mode 100644 index 0000000..b0c0261 --- /dev/null +++ b/src/widgets/sidebars/SideBarContext.tsx @@ -0,0 +1,21 @@ +import React, { createContext, ReactNode, useState } from 'react'; + +interface SideBarContextProps { + childrenComponent: ReactNode; + setChildrenComponent: (component: ReactNode) => void; +} + +export const SideBarContext = createContext({ + childrenComponent: null, + setChildrenComponent: () => {}, +}); + +export const SideBarProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [childrenComponent, setChildrenComponent] = useState(null); + + return ( + + {children} + + ); +}; \ No newline at end of file