fix sidebar rendering
add sidebar to main page add creatable multiselect component
This commit is contained in:
parent
2ca9195fbf
commit
76481ba12e
@ -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: {
|
||||
|
||||
@ -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()
|
||||
|
||||
37
src/hooks/useRealmUser.ts
Normal file
37
src/hooks/useRealmUser.ts
Normal file
@ -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
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
const ru = {
|
||||
mainPage: {
|
||||
createSelectTags: 'Создать\\Выбрать тэги'
|
||||
},
|
||||
editCameraPage: {
|
||||
notFrigateCamera: 'Не камера Фригата',
|
||||
errorAtPut: 'Ошибка при отправке маски',
|
||||
@ -88,6 +91,7 @@ const ru = {
|
||||
rating: 'Рейтинг',
|
||||
},
|
||||
config: 'Конфиг.',
|
||||
create: 'Создать',
|
||||
clear: 'Очистить',
|
||||
edit: 'Изменить',
|
||||
version: 'Версия',
|
||||
|
||||
@ -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<string>()
|
||||
const [filteredCameras, setFilteredCameras] = useState<GetCameraWHostWConfig[]>()
|
||||
|
||||
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.setRightChildren(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(() => {
|
||||
if (filteredCameras)
|
||||
return filteredCameras.filter(camera => {
|
||||
@ -78,13 +96,10 @@ const MainPage = () => {
|
||||
|
||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||
|
||||
const handleSelectHost = (hostId: string) => {
|
||||
mainStore.selectedHostId = hostId
|
||||
}
|
||||
if (!isProduction) console.log('MainPage rendered')
|
||||
|
||||
return (
|
||||
<Flex direction='column' h='100%' w='100%' >
|
||||
<Flex justify='space-between' align='center' w='100%'>
|
||||
<Flex w='100%'
|
||||
justify='center'
|
||||
>
|
||||
@ -97,14 +112,6 @@ const MainPage = () => {
|
||||
value={searchQuery}
|
||||
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
||||
/>
|
||||
<HostSelect
|
||||
valueId={selectedHostId}
|
||||
onChange={handleSelectHost}
|
||||
ml='1rem'
|
||||
spaceBetween='0px'
|
||||
placeholder={t('selectHost')}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex justify='center' h='100%' direction='column' w='100%' >
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||
|
||||
@ -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';
|
||||
|
||||
78
src/shared/components/filters/CreatableMultiSelect.tsx
Normal file
78
src/shared/components/filters/CreatableMultiSelect.tsx
Normal file
@ -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<SpacingValue>
|
||||
label?: string
|
||||
defaultValue?: string[]
|
||||
textClassName?: string
|
||||
selectProps?: MultiSelectProps,
|
||||
display?: SystemProp<CSSProperties['display']>
|
||||
showClose?: boolean,
|
||||
changedState?(value: string[], id?: string): void
|
||||
onClose?(): void
|
||||
onCreate?(value: string): void
|
||||
}
|
||||
|
||||
const CreatableMultiSelect: React.FC<CreatableMultiSelectProps> = ({
|
||||
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 (
|
||||
<Box display={display} mt={spaceBetween}>
|
||||
<Flex justify='space-between'>
|
||||
<Text className={textClassName}>{label}</Text>
|
||||
{showClose ?
|
||||
<CloseWithTooltip label={t('hide')} onClose={onClose} />
|
||||
: null}
|
||||
</Flex>
|
||||
<MultiSelect
|
||||
{...selectProps}
|
||||
mt={spaceBetween}
|
||||
data={data}
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleOnChange}
|
||||
searchable
|
||||
clearable
|
||||
creatable
|
||||
getCreateLabel={(query) => `+ ${t('create') + ' ' + query}`}
|
||||
onCreate={handleOnCreate}
|
||||
{...selectProps}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreatableMultiSelect;
|
||||
@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||
|
||||
interface MultiSelectFilterProps {
|
||||
id: string
|
||||
id?: string
|
||||
data: SelectItem[]
|
||||
spaceBetween?: SystemProp<SpacingValue>
|
||||
label?: string
|
||||
@ -13,7 +13,7 @@ interface MultiSelectFilterProps {
|
||||
selectProps?: MultiSelectProps,
|
||||
display?: SystemProp<CSSProperties['display']>
|
||||
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}
|
||||
</Flex>
|
||||
<MultiSelect
|
||||
{...selectProps}
|
||||
mt={spaceBetween}
|
||||
data={data}
|
||||
defaultValue={defaultValue}
|
||||
onChange={handleOnChange}
|
||||
searchable
|
||||
clearable
|
||||
{...selectProps}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@ -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) => {
|
||||
|
||||
42
src/widgets/sidebars/MainFiltersRightSide.tsx
Normal file
42
src/widgets/sidebars/MainFiltersRightSide.tsx
Normal file
@ -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 (
|
||||
<>
|
||||
<HostSelect
|
||||
label={t('selectHost')}
|
||||
valueId={selectedHostId}
|
||||
defaultId={selectedHostId}
|
||||
onChange={handleSelect}
|
||||
/>
|
||||
{/* TODO Add tags select */}
|
||||
{/* <CreatableMultiSelect
|
||||
label={t('mainPage.createSelectTags')}
|
||||
spaceBetween='1rem'
|
||||
data={[]}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default observer(MainFiltersRightSide);
|
||||
@ -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)
|
||||
Loading…
Reference in New Issue
Block a user