fix sidebar rendering

add sidebar to main page
add creatable multiselect component
This commit is contained in:
NlightN22 2024-09-01 17:29:55 +07:00
parent 2ca9195fbf
commit 76481ba12e
12 changed files with 216 additions and 44 deletions

View File

@ -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: {

View File

@ -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
View 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
}

View File

@ -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',

View File

@ -1,4 +1,7 @@
const ru = {
mainPage: {
createSelectTags: 'Создать\\Выбрать тэги'
},
editCameraPage: {
notFrigateCamera: 'Не камера Фригата',
errorAtPut: 'Ошибка при отправке маски',
@ -88,6 +91,7 @@ const ru = {
rating: 'Рейтинг',
},
config: 'Конфиг.',
create: 'Создать',
clear: 'Очистить',
edit: 'Изменить',
version: 'Версия',

View File

@ -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.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)
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 <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'
>
<ClearableTextInput
clerable
maw={400}
style={{ flexGrow: 1 }}
placeholder={t('search')}
icon={<IconSearch size="0.9rem" stroke={1.5} />}
value={searchQuery}
onChange={(event) => setSearchQuery(event.currentTarget.value)}
/>
<HostSelect
valueId={selectedHostId}
onChange={handleSelectHost}
ml='1rem'
spaceBetween='0px'
placeholder={t('selectHost')}
/>
</Flex>
<Flex w='100%'
justify='center'
>
<ClearableTextInput
clerable
maw={400}
style={{ flexGrow: 1 }}
placeholder={t('search')}
icon={<IconSearch size="0.9rem" stroke={1.5} />}
value={searchQuery}
onChange={(event) => setSearchQuery(event.currentTarget.value)}
/>
</Flex>
<Flex justify='center' h='100%' direction='column' w='100%' >
<Grid mt='sm' justify="center" mb='sm' align='stretch'>

View File

@ -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';

View 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;

View File

@ -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>
)

View File

@ -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) => {

View 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);

View File

@ -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)