diff --git a/package.json b/package.json index 611085a..7d67329 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "zod": "^3.21.4" }, "scripts": { - "start": "react-scripts start", + "dev": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/src/pages/FrigateHostsPage.tsx b/src/pages/FrigateHostsPage.tsx index 1c05db6..7ef0b79 100644 --- a/src/pages/FrigateHostsPage.tsx +++ b/src/pages/FrigateHostsPage.tsx @@ -1,16 +1,20 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; -import FrigateHostsTable from '../widgets/FrigateHostsTable'; +import { Button, Flex, Text } from '@mantine/core'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; -import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema'; -import CenterLoader from '../shared/components/loaders/CenterLoader'; -import RetryErrorPage from './RetryErrorPage'; +import { observer } from 'mobx-react-lite'; +import { useContext, useEffect, useRef, useState } from 'react'; import { Context } from '..'; -import { strings } from '../shared/strings/strings'; -import { Button, Flex } from '@mantine/core'; -import { observer } from 'mobx-react-lite' import { useAdminRole } from '../hooks/useAdminRole'; +import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api'; +import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema'; +import CenterLoader from '../shared/components/loaders/CenterLoader'; +import { isProduction } from '../shared/env.const'; +import { strings } from '../shared/strings/strings'; +import FrigateHostsTable from '../widgets/FrigateHostsTable'; import Forbidden from './403'; +import RetryErrorPage from './RetryErrorPage'; +import { notifications } from '@mantine/notifications'; +import { IconAlertCircle } from '@tabler/icons-react'; + const FrigateHostsPage = () => { const executed = useRef(false) @@ -36,6 +40,10 @@ const FrigateHostsPage = () => { if (data) setPageData(data) }, [data]) + useEffect(() => { + if (!isProduction) console.log('pageData', pageData) + }, [pageData]) + const { mutate } = useMutation({ mutationFn: (tableData: GetFrigateHost[]) => { let fetchPromises = [] @@ -48,16 +56,29 @@ const FrigateHostsPage = () => { const parsedChanged = putFrigateHostSchema.array().parse(tableData) fetchPromises.push(frigateApi.putHosts(parsedChanged)) } - return Promise.all(fetchPromises) + return Promise.all(fetchPromises).catch(error => { + if (error.response && error.response.data) { + return Promise.reject(error.response.data); + } + return Promise.reject(error); + }) }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] }) }, - onError: () => { + 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] }) - }, - onSettled: () => { - if (data) setPageData([...data]) } }) @@ -79,18 +100,16 @@ const FrigateHostsPage = () => { if (hostsPending || adminLoading) return if (!isAdmin) return if (hostsError) return + if (!pageData) return Empty server response return ( -
- { - !pageData ? <> : - - } + + -
+ ); } diff --git a/src/widgets/FrigateHostsTable.tsx b/src/widgets/FrigateHostsTable.tsx index 64f2616..a7f1ca6 100644 --- a/src/widgets/FrigateHostsTable.tsx +++ b/src/widgets/FrigateHostsTable.tsx @@ -1,17 +1,16 @@ import { Button, Flex, Table } from '@mantine/core'; import { IconPlus, IconTrash } from '@tabler/icons-react'; import ObjectId from 'bson-objectid'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema'; import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu'; import SortedTh from '../shared/components/table.aps/SortedTh'; +import { isProduction } from '../shared/env.const'; import { strings } from '../shared/strings/strings'; -import { debounce } from '../shared/utils/debounce'; import StateCell from './hosts.table/StateCell'; import SwitchCell from './hosts.table/SwitchCell'; import TextInputCell from './hosts.table/TextInputCell'; -import { isProduction } from '../shared/env.const'; interface TableProps { data: T[], @@ -21,22 +20,22 @@ interface TableProps { } const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps) => { - if (!isProduction) console.log('FrigateHostsTable rendered') const [tableData, setTableData] = useState(data) const [reversed, setReversed] = useState(false) const [sortedName, setSortedName] = useState(null) - useEffect(() => { - setTableData(data) - }, [data]) - - const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => { - if (changedCallback) changedCallback(tableData) - }, 200), [tableData]) + console.log('data', data) + console.log('tableData', tableData) useEffect(() => { - debouncedChanged(tableData) - }, [tableData, debouncedChanged]) + setTableData(data); + }, [data]); + + useEffect(() => { + if (!isProduction) console.log('TableData', tableData) + if (changedCallback) + changedCallback(tableData) + }, [tableData]) function sortByKey(array: T[], key: K): T[] { return array.sort((a, b) => { @@ -80,7 +79,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC ) }) - const handleTextChange = (id: string | number, propertyName: string, value: string,) => { + const handleTextChange = (id: string | number, propertyName: string, value: string | number | boolean | undefined,) => { setTableData(tableData.map(item => { if (item.id === id) { return { @@ -116,7 +115,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC name: '', enabled: true } - setTableData(prevTableData => [...prevTableData, newHost]) + setTableData([...tableData, newHost]) } const rows = tableData.map(item => { @@ -135,7 +134,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC ) }) - + if (!isProduction) console.log('FrigateHostsTable rendered') return (
diff --git a/src/widgets/hosts.table/SwitchCell.tsx b/src/widgets/hosts.table/SwitchCell.tsx index 98d73e4..c0f676a 100644 --- a/src/widgets/hosts.table/SwitchCell.tsx +++ b/src/widgets/hosts.table/SwitchCell.tsx @@ -1,6 +1,7 @@ import { Flex, Switch, useMantineTheme } from '@mantine/core'; import { IconBulbFilled, IconBulbOff } from '@tabler/icons-react'; import React from 'react'; +import { boolean } from 'zod'; interface SwithCellProps { value?: boolean, @@ -14,7 +15,7 @@ interface SwithCellProps { export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => { const theme = useMantineTheme(); - if (!value && !defaultValue) value = defaultValue + if (typeof value !== 'boolean') value = defaultValue const handleChange = (event: React.ChangeEvent) => { if (id && toggle && propertyName) toggle(id, propertyName, event.target.value) } diff --git a/src/widgets/hosts.table/TextInputCell.tsx b/src/widgets/hosts.table/TextInputCell.tsx index 1fd6707..9358e72 100644 --- a/src/widgets/hosts.table/TextInputCell.tsx +++ b/src/widgets/hosts.table/TextInputCell.tsx @@ -1,5 +1,6 @@ import { TextInput } from '@mantine/core'; -import React from 'react'; +import { useDebouncedValue } from '@mantine/hooks'; +import React, { useEffect, useState } from 'react'; interface TextImputCellProps { text?: string | number | boolean, @@ -9,18 +10,25 @@ interface TextImputCellProps { onChange?: ( id: string | number, propertyName: string, - value: string, + value?: string | number | boolean, ) => void, } const TextInputCell = ({ text, width, id, propertyName, onChange }: TextImputCellProps) => { + const [value, setValue] = useState(text); + const [debouncedValue] = useDebouncedValue(value, 300) + useEffect(() => { + if (debouncedValue !== text) { + if (id && propertyName && onChange) + onChange(id, propertyName, debouncedValue) + } + }, [debouncedValue, id, propertyName, onChange, text]) const handleChange = (event: React.ChangeEvent) => { - if (id && propertyName && onChange) - onChange(id, propertyName, event.currentTarget.value) + setValue(event.currentTarget.value) } return ( ) }
- +