diff --git a/src/App.tsx b/src/App.tsx index 153fc96..d2d74e9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,6 @@ 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 { isProduction } from './shared/env.const'; const queryClient = new QueryClient({ defaultOptions: { diff --git a/src/AppBody.tsx b/src/AppBody.tsx index 0d37f40..d558902 100644 --- a/src/AppBody.tsx +++ b/src/AppBody.tsx @@ -8,8 +8,6 @@ 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 { useAdminRole } from "./hooks/useAdminRole"; -import { useRealmAccessRoles } from "./hooks/useRealmAccessRoles"; const AppBody = () => { const { t } = useTranslation() diff --git a/src/hooks/useRealmUser.ts b/src/hooks/useRealmUser.ts new file mode 100644 index 0000000..9dc2f0e --- /dev/null +++ b/src/hooks/useRealmUser.ts @@ -0,0 +1,37 @@ +import { useKeycloak } from "@react-keycloak/web" +import { z } from "zod" +import { isProduction } from "../shared/env.const" + +const keycloakUser = z.object({ + sub: z.string(), + name: z.string(), + preferred_username: z.string(), + given_name: z.string(), + family_name: z.string() +}) + +interface RealmUser { + id: string // sub + username: string // preferred_username + givenName?: string // given_name + familyName?: string // family_name +} + +export const useRealmUser = () => { + const { keycloak } = useKeycloak() + if (!isProduction) console.log('Authenticated:', keycloak.authenticated) + if (!isProduction) console.log('userInfo:', keycloak.userInfo) + if (keycloak.authenticated && keycloak.tokenParsed) { + const parsedUser = keycloakUser.safeParse(keycloak.tokenParsed) + if (parsedUser.success) { + const realmUser: RealmUser = { + id: parsedUser.data.sub, + username: parsedUser.data.preferred_username, + givenName: parsedUser.data.given_name, + familyName: parsedUser.data.family_name + } + return realmUser + } + } + return null +} \ No newline at end of file diff --git a/src/locales/en.ts b/src/locales/en.ts index 84db484..ceeea7c 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1,6 +1,9 @@ import { error } from "console" const en = { + mainPage: { + createSelectTags: 'Create\\Select tags' + }, editCameraPage: { notFrigateCamera: 'Not frigate camera', errorAtPut: 'Error at sending mask', @@ -90,6 +93,7 @@ const en = { rating: 'Rating', }, config: 'Config', + create: 'Create', clear: 'Clear', edit: 'Edit', version: 'Version', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index d4e7178..b18562c 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1,4 +1,7 @@ const ru = { + mainPage: { + createSelectTags: 'Создать\\Выбрать тэги' + }, editCameraPage: { notFrigateCamera: 'Не камера Фригата', errorAtPut: 'Ошибка при отправке маски', @@ -88,6 +91,7 @@ const ru = { rating: 'Рейтинг', }, config: 'Конфиг.', + create: 'Создать', clear: 'Очистить', edit: 'Изменить', version: 'Версия', diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 075907a..832a5e0 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -6,12 +6,15 @@ import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Context } from '..'; import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema'; -import HostSelect from '../shared/components/filters/HostSelect'; 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'; const MainPage = () => { const { t } = useTranslation() @@ -21,6 +24,9 @@ const MainPage = () => { const [searchQuery, setSearchQuery] = useState() const [filteredCameras, setFilteredCameras] = useState() + const realmUser = useRealmUser() + if (!isProduction) console.log('Realmuser:', realmUser) + const { data: cameras, isPending, isError, refetch } = useQuery({ queryKey: [frigateQueryKeys.getCamerasWHost], queryFn: frigateApi.getCamerasWHost @@ -41,15 +47,27 @@ const MainPage = () => { setFilteredCameras(cameras.filter(filterCameras)) }, [searchQuery, cameras, selectedHostId]) + + useEffect(() => { - if (!executed.current) { - sideBarsStore.rightVisible = false - sideBarsStore.setLeftChildren(null) + 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) - executed.current = true + sideBarsStore.rightVisible = false } }, [sideBarsStore]) + //test change + const cards = useMemo(() => { if (filteredCameras) return filteredCameras.filter(camera => { @@ -78,33 +96,22 @@ const MainPage = () => { if (isError) return - const handleSelectHost = (hostId: string) => { - mainStore.selectedHostId = hostId - } + if (!isProduction) console.log('MainPage rendered') return ( - - - } - value={searchQuery} - onChange={(event) => setSearchQuery(event.currentTarget.value)} - /> - - + + } + value={searchQuery} + onChange={(event) => setSearchQuery(event.currentTarget.value)} + /> diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx index e78c71a..706d5ca 100644 --- a/src/pages/RecordingsPage.tsx +++ b/src/pages/RecordingsPage.tsx @@ -6,7 +6,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { Context } from '..'; import { isProduction } from '../shared/env.const'; import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil'; -import RecordingsFiltersRightSide from '../widgets/RecordingsFiltersRightSide'; +import RecordingsFiltersRightSide from '../widgets/sidebars/RecordingsFiltersRightSide'; import SelectedCameraList from '../widgets/SelectedCameraList'; import SelectedDayList from '../widgets/SelectedDayList'; import SelectedHostList from '../widgets/SelectedHostList'; diff --git a/src/shared/components/filters/CreatableMultiSelect.tsx b/src/shared/components/filters/CreatableMultiSelect.tsx new file mode 100644 index 0000000..8d2c47b --- /dev/null +++ b/src/shared/components/filters/CreatableMultiSelect.tsx @@ -0,0 +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 { useTranslation } from 'react-i18next'; + +interface CreatableMultiSelectProps { + id?: string + data: SelectItem[] + spaceBetween?: SystemProp + label?: string + defaultValue?: string[] + textClassName?: string + selectProps?: MultiSelectProps, + display?: SystemProp + showClose?: boolean, + changedState?(value: string[], id?: string): void + onClose?(): void + onCreate?(value: string): void +} + +const CreatableMultiSelect: React.FC = ({ + id, + data, + spaceBetween, + label, + defaultValue, + textClassName, + selectProps, + display, + showClose, + changedState, + onClose, + onCreate +}) => { + + const { t } = useTranslation() + + const handleOnChange = (value: string[]) => { + if (changedState) { + changedState(value, id) + } + } + + const handleOnCreate = (query: string | SelectItem | null | undefined) => { + const item = { value: query, label: query } as SelectItem + if (onCreate && typeof query === 'string') { + onCreate(query) + } + return item + } + + return ( + + + {label} + {showClose ? + + : null} + + `+ ${t('create') + ' ' + query}`} + onCreate={handleOnCreate} + {...selectProps} + /> + + ); +}; + +export default CreatableMultiSelect; \ No newline at end of file diff --git a/src/shared/components/filters/MultiSelectFilter.tsx b/src/shared/components/filters/MultiSelectFilter.tsx index 91b4c47..0c9b67e 100644 --- a/src/shared/components/filters/MultiSelectFilter.tsx +++ b/src/shared/components/filters/MultiSelectFilter.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import CloseWithTooltip from '../buttons/CloseWithTooltip'; interface MultiSelectFilterProps { - id: string + id?: string data: SelectItem[] spaceBetween?: SystemProp label?: string @@ -13,7 +13,7 @@ interface MultiSelectFilterProps { selectProps?: MultiSelectProps, display?: SystemProp showClose?: boolean, - changedState?(id: string, value: string[]): void + changedState?(value: string[], id?: string): void onClose?(): void } @@ -26,7 +26,7 @@ const MultiSelectFilter = ({ const handleOnChange = (value: string[]) => { if (changedState) { - changedState(id, value) + changedState(value, id) } } @@ -39,13 +39,13 @@ const MultiSelectFilter = ({ : null} ) diff --git a/src/shared/stores/sidebars.store.ts b/src/shared/stores/sidebars.store.ts index aad6662..33b1a75 100644 --- a/src/shared/stores/sidebars.store.ts +++ b/src/shared/stores/sidebars.store.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable } from "mobx" +import { action, makeAutoObservable } from "mobx" export class SideBarsStore { @@ -29,7 +29,10 @@ export class SideBarsStore { } constructor () { - makeAutoObservable(this) + makeAutoObservable(this, { + setRightChildren: action, + setLeftChildren: action, + }) } setRightChildren = (value: React.ReactNode) => { diff --git a/src/widgets/sidebars/MainFiltersRightSide.tsx b/src/widgets/sidebars/MainFiltersRightSide.tsx new file mode 100644 index 0000000..8121fd8 --- /dev/null +++ b/src/widgets/sidebars/MainFiltersRightSide.tsx @@ -0,0 +1,42 @@ +import React, { useContext } from 'react'; +import HostSelect from '../../shared/components/filters/HostSelect'; +import { observer } from 'mobx-react-lite'; +import { Context } from '../..'; +import { isProduction } from '../../shared/env.const'; +import CreatableMultiSelect from '../../shared/components/filters/CreatableMultiSelect'; +import { useTranslation } from 'react-i18next'; + + + +const MainFiltersRightSide = () => { + const { t } = useTranslation() + + const { mainStore } = useContext(Context) + const { selectedHostId } = mainStore + + const handleSelect = (value: string) => { + if (!isProduction) console.log('handleSelect value', value) + mainStore.selectedHostId = value + } + + return ( + <> + + {/* TODO Add tags select */} + {/* */} + + ); +}; + + + +export default observer(MainFiltersRightSide); \ No newline at end of file diff --git a/src/widgets/RecordingsFiltersRightSide.tsx b/src/widgets/sidebars/RecordingsFiltersRightSide.tsx similarity index 61% rename from src/widgets/RecordingsFiltersRightSide.tsx rename to src/widgets/sidebars/RecordingsFiltersRightSide.tsx index 6e07137..16ce071 100644 --- a/src/widgets/RecordingsFiltersRightSide.tsx +++ b/src/widgets/sidebars/RecordingsFiltersRightSide.tsx @@ -1,10 +1,10 @@ import React, { useContext } from 'react'; import { observer } from 'mobx-react-lite'; -import { Context } from '..'; -import CameraSelectFilter from '../shared/components/filters/CameraSelectFilter'; -import DateRangeSelectFilter from '../shared/components/filters/DateRangeSelectFilter'; -import RecordingsHostFilter from '../shared/components/filters/RecordingsHostFilter'; -import { isProduction } from '../shared/env.const'; +import { Context } from '../..'; +import CameraSelectFilter from '../../shared/components/filters/CameraSelectFilter'; +import DateRangeSelectFilter from '../../shared/components/filters/DateRangeSelectFilter'; +import RecordingsHostFilter from '../../shared/components/filters/RecordingsHostFilter'; +import { isProduction } from '../../shared/env.const'; const RecordingsFiltersRightSide = () => { const { recordingsStore: recStore } = useContext(Context)