mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-01-27 17:19:08 +03:00
new linting, make sure code is type safe
This commit is contained in:
@@ -1,34 +1,36 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Box, Button, Checkbox, Divider, Grid, InputAdornment, MenuItem, TextField, Typography } from '@mui/material';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { BOARD_PROFILES } from './types';
|
||||
import { createSettingsValidator } from './validators';
|
||||
import type { Settings } from './types';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import type { FC } from 'react';
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
SectionContent,
|
||||
FormLoader,
|
||||
BlockFormControlLabel,
|
||||
ValidatedTextField,
|
||||
ButtonRow,
|
||||
MessageBox,
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
FormLoader,
|
||||
MessageBox,
|
||||
SectionContent,
|
||||
ValidatedTextField,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
|
||||
import RestartMonitor from 'framework/system/RestartMonitor';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { BOARD_PROFILES } from './types';
|
||||
import type { Settings } from './types';
|
||||
import { createSettingsValidator } from './validators';
|
||||
|
||||
export function boardProfileSelectItems() {
|
||||
return Object.keys(BOARD_PROFILES).map((code) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
@@ -67,7 +69,7 @@ const ApplicationSettings: FC = () => {
|
||||
loading: processingBoard,
|
||||
send: readBoardProfile,
|
||||
onSuccess: onSuccessBoardProfile
|
||||
} = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), {
|
||||
} = useRequest((boardProfile: string) => EMSESP.getBoardProfile(boardProfile), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -93,7 +95,7 @@ const ApplicationSettings: FC = () => {
|
||||
});
|
||||
|
||||
const updateBoardProfile = async (board_profile: string) => {
|
||||
await readBoardProfile(board_profile).catch((error) => {
|
||||
await readBoardProfile(board_profile).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
@@ -109,8 +111,8 @@ const ApplicationSettings: FC = () => {
|
||||
try {
|
||||
setFieldErrors(undefined);
|
||||
await validate(createSettingsValidator(data), data);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
} finally {
|
||||
await saveData();
|
||||
}
|
||||
@@ -131,7 +133,7 @@ const ApplicationSettings: FC = () => {
|
||||
|
||||
const restart = async () => {
|
||||
await validateAndSubmit();
|
||||
await restartCommand().catch((error) => {
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Button, Typography, Box } from '@mui/material';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
||||
import * as EMSESP from './api';
|
||||
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||
import { entityItemValidation } from './validators';
|
||||
import type { EntityItem } from './types';
|
||||
import type { FC } from 'react';
|
||||
import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
|
||||
|
||||
import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
|
||||
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||
import type { EntityItem } from './types';
|
||||
import { entityItemValidation } from './validators';
|
||||
|
||||
const CustomEntities: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
@@ -42,7 +41,10 @@ const CustomEntities: FC = () => {
|
||||
force: true
|
||||
});
|
||||
|
||||
const { send: writeEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
|
||||
const { send: writeEntities } = useRequest(
|
||||
(data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomEntities(data),
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
function hasEntityChanged(ei: EntityItem) {
|
||||
return (
|
||||
@@ -139,8 +141,8 @@ const CustomEntities: FC = () => {
|
||||
.then(() => {
|
||||
toast.success(LL.ENTITIES_UPDATED());
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(async () => {
|
||||
await fetchEntities();
|
||||
@@ -167,7 +169,7 @@ const CustomEntities: FC = () => {
|
||||
const onDialogSave = (updatedItem: EntityItem) => {
|
||||
setDialogOpen(false);
|
||||
|
||||
updateState('entities', (data) => {
|
||||
updateState('entities', (data: EntityItem[]) => {
|
||||
const new_data = creating
|
||||
? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]
|
||||
: data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei));
|
||||
@@ -195,12 +197,12 @@ const CustomEntities: FC = () => {
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
function formatValue(value: any, uom: number) {
|
||||
return value === undefined || uom === undefined
|
||||
function formatValue(value: unknown, uom: number) {
|
||||
return value === undefined
|
||||
? ''
|
||||
: typeof value === 'number'
|
||||
? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
||||
: value;
|
||||
: (value as string);
|
||||
}
|
||||
|
||||
function showHex(value: number, digit: number) {
|
||||
@@ -214,7 +216,7 @@ const CustomEntities: FC = () => {
|
||||
|
||||
return (
|
||||
<Table data={{ nodes: entities.filter((ei) => !ei.deleted) }} theme={entity_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: EntityItem[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
@@ -15,29 +17,26 @@ import {
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { DeviceValueUOM_s, DeviceValueType } from './types';
|
||||
import type { EntityItem } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
type CustomEntitiesDialogProps = {
|
||||
import { DeviceValueType, DeviceValueUOM_s } from './types';
|
||||
import type { EntityItem } from './types';
|
||||
|
||||
interface CustomEntitiesDialogProps {
|
||||
open: boolean;
|
||||
creating: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (ei: EntityItem) => void;
|
||||
selectedItem: EntityItem;
|
||||
validator: Schema;
|
||||
};
|
||||
}
|
||||
|
||||
const CustomEntitiesDialog = ({
|
||||
open,
|
||||
@@ -80,8 +79,8 @@ const CustomEntitiesDialog = ({
|
||||
editItem.type_id = parseInt(editItem.type_id, 16);
|
||||
}
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker, useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
MenuItem,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
Link,
|
||||
MenuItem,
|
||||
TextField,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Grid,
|
||||
TextField,
|
||||
Link,
|
||||
InputAdornment
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useBlocker, useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova';
|
||||
import { BlockNavigation, ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import RestartMonitor from 'framework/system/RestartMonitor';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import SettingsCustomizationDialog from './CustomizationDialog';
|
||||
import EntityMaskToggle from './EntityMaskToggle';
|
||||
import OptionIcon from './OptionIcon';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import { DeviceEntityMask } from './types';
|
||||
import type { DeviceShort, DeviceEntity } from './types';
|
||||
import type { FC } from 'react';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import * as SystemApi from 'api/system';
|
||||
import { ButtonRow, SectionContent, MessageBox, BlockNavigation, useLayoutTitle } from 'components';
|
||||
|
||||
import RestartMonitor from 'framework/system/RestartMonitor';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { DeviceEntity, DeviceShort } from './types';
|
||||
|
||||
export const APIURL = window.location.origin + '/api/';
|
||||
|
||||
@@ -63,22 +63,27 @@ const Customization: FC = () => {
|
||||
// fetch devices first
|
||||
const { data: devices } = useRequest(EMSESP.readDevices);
|
||||
|
||||
// const { state } = useLocation();
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(useLocation().state || -1);
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>(Number(useLocation().state) || -1);
|
||||
const [selectedDeviceName, setSelectedDeviceName] = useState<string>('');
|
||||
|
||||
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { send: writeCustomizationEntities } = useRequest((data) => EMSESP.writeCustomizationEntities(data), {
|
||||
immediate: false
|
||||
});
|
||||
const { send: writeCustomizationEntities } = useRequest(
|
||||
(data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomizationEntities(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), {
|
||||
initialData: [],
|
||||
immediate: false
|
||||
});
|
||||
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest(
|
||||
(data: number) => EMSESP.readDeviceEntities(data),
|
||||
{
|
||||
initialData: [],
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const setOriginalSettings = (data: DeviceEntity[]) => {
|
||||
setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
|
||||
@@ -195,17 +200,16 @@ const Customization: FC = () => {
|
||||
setRestartNeeded(false);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [devices, selectedDevice]);
|
||||
|
||||
const restart = async () => {
|
||||
await restartCommand().catch((error) => {
|
||||
await restartCommand().catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
setRestarting(true);
|
||||
};
|
||||
|
||||
function formatValue(value: any) {
|
||||
function formatValue(value: unknown) {
|
||||
if (typeof value === 'number') {
|
||||
return new Intl.NumberFormat().format(value);
|
||||
} else if (value === undefined) {
|
||||
@@ -213,7 +217,7 @@ const Customization: FC = () => {
|
||||
} else if (typeof value === 'boolean') {
|
||||
return value ? 'true' : 'false';
|
||||
}
|
||||
return value;
|
||||
return value as string;
|
||||
}
|
||||
|
||||
const formatName = (de: DeviceEntity, withShortname: boolean) =>
|
||||
@@ -273,7 +277,7 @@ const Customization: FC = () => {
|
||||
await resetCustomizations();
|
||||
toast.info(LL.CUSTOMIZATIONS_RESTART());
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
toast.error((error as Error).message);
|
||||
} finally {
|
||||
setConfirmReset(false);
|
||||
}
|
||||
@@ -326,7 +330,7 @@ const Customization: FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error) => {
|
||||
await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error: Error) => {
|
||||
if (error.message === 'Reboot required') {
|
||||
setRestartNeeded(true);
|
||||
} else {
|
||||
@@ -402,7 +406,7 @@ const Customization: FC = () => {
|
||||
size="small"
|
||||
color="secondary"
|
||||
value={getMaskString(selectedFilters)}
|
||||
onChange={(event, mask) => {
|
||||
onChange={(event, mask: string[]) => {
|
||||
setSelectedFilters(getMaskNumber(mask));
|
||||
}}
|
||||
>
|
||||
@@ -456,7 +460,7 @@ const Customization: FC = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Table data={{ nodes: shown_data }} theme={entities_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: DeviceEntity[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -13,23 +14,21 @@ import {
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { updateValue } from 'utils';
|
||||
|
||||
import EntityMaskToggle from './EntityMaskToggle';
|
||||
import { DeviceEntityMask } from './types';
|
||||
import type { DeviceEntity } from './types';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { updateValue } from 'utils';
|
||||
|
||||
type SettingsCustomizationDialogProps = {
|
||||
interface SettingsCustomizationDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (di: DeviceEntity) => void;
|
||||
selectedItem: DeviceEntity;
|
||||
};
|
||||
}
|
||||
|
||||
const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||
import { AiOutlineControl, AiOutlineGateway, AiOutlineAlert } from 'react-icons/ai';
|
||||
import type { FC } from 'react';
|
||||
import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ai';
|
||||
import { CgSmartHomeBoiler } from 'react-icons/cg';
|
||||
import { FaSolarPanel } from 'react-icons/fa';
|
||||
import { GiHeatHaze, GiTap } from 'react-icons/gi';
|
||||
import { MdThermostatAuto, MdOutlineSensors, MdOutlineDevices, MdOutlinePool } from 'react-icons/md';
|
||||
import { MdOutlineDevices, MdOutlinePool, MdOutlineSensors, MdThermostatAuto } from 'react-icons/md';
|
||||
import { TiFlowSwitch } from 'react-icons/ti';
|
||||
import { VscVmConnect } from 'react-icons/vsc';
|
||||
import { DeviceType } from './types';
|
||||
|
||||
import type { FC } from 'react';
|
||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||
|
||||
import { DeviceType } from './types';
|
||||
|
||||
interface DeviceIconProps {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
const DeviceIcon: FC<DeviceIconProps> = ({ type_id }) => {
|
||||
switch (type_id) {
|
||||
switch (type_id as DeviceType) {
|
||||
case DeviceType.TEMPERATURESENSOR:
|
||||
case DeviceType.ANALOGSENSOR:
|
||||
return <MdOutlineSensors />;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { useCallback, useContext, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { IconContext } from 'react-icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||
@@ -12,48 +18,40 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Box,
|
||||
Grid,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { useRowSelect } from '@table-library/react-table-library/select';
|
||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useEffect, useCallback, useLayoutEffect, useContext } from 'react';
|
||||
|
||||
import { IconContext } from 'react-icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
import DashboardDevicesDialog from './DevicesDialog';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import { formatValue } from './deviceValue';
|
||||
|
||||
import { DeviceValueUOM_s, DeviceEntityMask, DeviceType } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
import type { Device, DeviceValue } from './types';
|
||||
import type { FC } from 'react';
|
||||
import type { Action, State } from '@table-library/react-table-library/types/common';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { ButtonRow, SectionContent, MessageBox, useLayoutTitle } from 'components';
|
||||
|
||||
import { useRequest } from 'alova';
|
||||
import { ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import DeviceIcon from './DeviceIcon';
|
||||
import DashboardDevicesDialog from './DevicesDialog';
|
||||
import { formatValue } from './deviceValue';
|
||||
import { DeviceEntityMask, DeviceType, DeviceValueUOM_s } from './types';
|
||||
import type { Device, DeviceValue } from './types';
|
||||
import { deviceValueItemValidation } from './validators';
|
||||
|
||||
const Devices: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
@@ -76,16 +74,19 @@ const Devices: FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), {
|
||||
const { data: deviceData, send: readDeviceData } = useRequest((id: number) => EMSESP.readDeviceData(id), {
|
||||
initialData: {
|
||||
data: []
|
||||
},
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { loading: submitting, send: writeDeviceValue } = useRequest((data) => EMSESP.writeDeviceValue(data), {
|
||||
immediate: false
|
||||
});
|
||||
const { loading: submitting, send: writeDeviceValue } = useRequest(
|
||||
(data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
function updateSize() {
|
||||
@@ -213,7 +214,7 @@ const Devices: FC = () => {
|
||||
}
|
||||
]);
|
||||
|
||||
const getSortIcon = (state: any, sortKey: any) => {
|
||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
return <KeyboardArrowDownOutlinedIcon />;
|
||||
}
|
||||
@@ -235,13 +236,14 @@ const Devices: FC = () => {
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString()))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function onSelectChange(action: any, state: any) {
|
||||
setSelectedDevice(state.id);
|
||||
async function onSelectChange(action: Action, state: State) {
|
||||
setSelectedDevice(state.id as number);
|
||||
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
|
||||
await readDeviceData(state.id);
|
||||
}
|
||||
@@ -259,8 +261,8 @@ const Devices: FC = () => {
|
||||
};
|
||||
|
||||
const escFunction = useCallback(
|
||||
(event: any) => {
|
||||
if (event.keyCode === 27) {
|
||||
(event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (device_select) {
|
||||
device_select.fns.onRemoveAll();
|
||||
}
|
||||
@@ -290,7 +292,7 @@ const Devices: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const escapeCsvCell = (cell: any) => {
|
||||
const escapeCsvCell = (cell: string) => {
|
||||
if (cell == null) {
|
||||
return '';
|
||||
}
|
||||
@@ -336,9 +338,13 @@ const Devices: FC = () => {
|
||||
: deviceData.data;
|
||||
|
||||
const csvData = data.reduce(
|
||||
(csvString: any, rowItem: any) =>
|
||||
csvString + columns.map(({ accessor }: any) => escapeCsvCell(accessor(rowItem))).join(';') + '\r\n',
|
||||
columns.map(({ name }: any) => escapeCsvCell(name)).join(';') + '\r\n'
|
||||
(csvString: string, rowItem: DeviceValue) =>
|
||||
csvString +
|
||||
columns
|
||||
.map(({ accessor }: { accessor: (dv: DeviceValue) => unknown }) => escapeCsvCell(accessor(rowItem) as string))
|
||||
.join(';') +
|
||||
'\r\n',
|
||||
columns.map(({ name }: { name: string }) => escapeCsvCell(name)).join(';') + '\r\n'
|
||||
);
|
||||
|
||||
const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' });
|
||||
@@ -363,7 +369,7 @@ const Devices: FC = () => {
|
||||
.then(() => {
|
||||
toast.success(LL.WRITE_CMD_SENT());
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(async () => {
|
||||
@@ -428,7 +434,7 @@ const Devices: FC = () => {
|
||||
|
||||
{coreData.connected && (
|
||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: Device[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
@@ -556,7 +562,7 @@ const Devices: FC = () => {
|
||||
sort={dv_sort}
|
||||
layout={{ custom: true, fixedHeader: true }}
|
||||
>
|
||||
{(tableList: any) => (
|
||||
{(tableList: DeviceValue[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
Box,
|
||||
Typography,
|
||||
CircularProgress
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||
import type { DeviceValue } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { updateValue, numberValue } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
|
||||
type DashboardDevicesDialogProps = {
|
||||
interface DashboardDevicesDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (as: DeviceValue) => void;
|
||||
@@ -38,7 +37,7 @@ type DashboardDevicesDialogProps = {
|
||||
writeable: boolean;
|
||||
validator: Schema;
|
||||
progress: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const DevicesDialog = ({
|
||||
open,
|
||||
@@ -71,12 +70,12 @@ const DevicesDialog = ({
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
}
|
||||
};
|
||||
|
||||
const setUom = (uom: number) => {
|
||||
const setUom = (uom: DeviceValueUOM) => {
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return LL.HOURS();
|
||||
@@ -133,7 +132,7 @@ const DevicesDialog = ({
|
||||
fieldErrors={fieldErrors}
|
||||
name="v"
|
||||
label={LL.VALUE(1)}
|
||||
value={numberValue(Math.round(editItem.v * 10) / 10)}
|
||||
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
|
||||
autoFocus
|
||||
disabled={!writeable}
|
||||
type="number"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
|
||||
import OptionIcon from './OptionIcon';
|
||||
import { DeviceEntityMask } from './types';
|
||||
import type { DeviceEntity } from './types';
|
||||
|
||||
type EntityMaskToggleProps = {
|
||||
interface EntityMaskToggleProps {
|
||||
onUpdate: (de: DeviceEntity) => void;
|
||||
de: DeviceEntity;
|
||||
};
|
||||
}
|
||||
|
||||
const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
|
||||
const getMaskNumber = (newMask: string[]) => {
|
||||
@@ -42,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
|
||||
size="small"
|
||||
color="secondary"
|
||||
value={getMaskString(de.m)}
|
||||
onChange={(event, mask) => {
|
||||
onChange={(event, mask: string[]) => {
|
||||
de.m = getMaskNumber(mask);
|
||||
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
|
||||
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Link,
|
||||
Typography,
|
||||
Button,
|
||||
ListItemButton,
|
||||
Avatar
|
||||
ListItemText,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import * as EMSESP from 'project/api';
|
||||
import { useRequest } from 'alova';
|
||||
import { toast } from 'react-toastify';
|
||||
import type { FC } from 'react';
|
||||
import { SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import * as EMSESP from 'project/api';
|
||||
|
||||
import type { APIcall } from './types';
|
||||
|
||||
const Help: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.HELP_OF(''));
|
||||
|
||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
|
||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data: APIcall) => EMSESP.API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -36,6 +40,7 @@ const Help: FC = () => {
|
||||
type: 'text/plain'
|
||||
})
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(anchor.href);
|
||||
@@ -43,7 +48,7 @@ const Help: FC = () => {
|
||||
});
|
||||
|
||||
const callAPI = async (device: string, entity: string) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error) => {
|
||||
await getAPI({ device, entity, id: 0 }).catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
|
||||
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import StarOutlineIcon from '@mui/icons-material/StarOutline';
|
||||
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
|
||||
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
import type { FC } from 'react';
|
||||
|
||||
type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite';
|
||||
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import CircleIcon from '@mui/icons-material/Circle';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Box, Button, Divider, Stack, Typography } from '@mui/material';
|
||||
|
||||
import { Box, Typography, Divider, Stack, Button } from '@mui/material';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
// eslint-disable-next-line import/named
|
||||
import { updateState, useRequest } from 'alova';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useBlocker } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||
import * as EMSESP from './api';
|
||||
import { ScheduleFlag } from './types';
|
||||
import { schedulerItemValidation } from './validators';
|
||||
import type { ScheduleItem } from './types';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { ButtonRow, FormLoader, SectionContent, BlockNavigation, useLayoutTitle } from 'components';
|
||||
|
||||
import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import SettingsSchedulerDialog from './SchedulerDialog';
|
||||
import { ScheduleFlag } from './types';
|
||||
import type { Schedule, ScheduleItem } from './types';
|
||||
import { schedulerItemValidation } from './validators';
|
||||
|
||||
const Scheduler: FC = () => {
|
||||
const { LL, locale } = useI18nContext();
|
||||
const [numChanges, setNumChanges] = useState<number>(0);
|
||||
@@ -40,7 +39,9 @@ const Scheduler: FC = () => {
|
||||
force: true
|
||||
});
|
||||
|
||||
const { send: writeSchedule } = useRequest((data) => EMSESP.writeSchedule(data), { immediate: false });
|
||||
const { send: writeSchedule } = useRequest((data: Schedule) => EMSESP.writeSchedule(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
function hasScheduleChanged(si: ScheduleItem) {
|
||||
return (
|
||||
@@ -126,8 +127,8 @@ const Scheduler: FC = () => {
|
||||
.then(() => {
|
||||
toast.success(LL.SCHEDULE_UPDATED());
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
.catch((error: Error) => {
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(async () => {
|
||||
await fetchSchedule();
|
||||
@@ -154,11 +155,13 @@ const Scheduler: FC = () => {
|
||||
const onDialogSave = (updatedItem: ScheduleItem) => {
|
||||
setDialogOpen(false);
|
||||
|
||||
updateState('schedule', (data) => {
|
||||
updateState('schedule', (data: ScheduleItem[]) => {
|
||||
const new_data = creating
|
||||
? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]
|
||||
: data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si));
|
||||
|
||||
setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length);
|
||||
|
||||
return new_data;
|
||||
});
|
||||
};
|
||||
@@ -202,7 +205,7 @@ const Scheduler: FC = () => {
|
||||
theme={schedule_theme}
|
||||
layout={{ custom: true }}
|
||||
>
|
||||
{(tableList: any) => (
|
||||
{(tableList: ScheduleItem[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -17,22 +18,19 @@ import {
|
||||
ToggleButtonGroup,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { ScheduleFlag } from './types';
|
||||
import type { ScheduleItem } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import { updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
type SchedulerDialogProps = {
|
||||
import { ScheduleFlag } from './types';
|
||||
import type { ScheduleItem } from './types';
|
||||
|
||||
interface SchedulerDialogProps {
|
||||
open: boolean;
|
||||
creating: boolean;
|
||||
onClose: () => void;
|
||||
@@ -40,7 +38,7 @@ type SchedulerDialogProps = {
|
||||
selectedItem: ScheduleItem;
|
||||
validator: Schema;
|
||||
dow: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
@@ -65,8 +63,8 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,7 +130,7 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida
|
||||
size="small"
|
||||
color="secondary"
|
||||
value={getFlagString(editItem.flags)}
|
||||
onChange={(event, flag) => {
|
||||
onChange={(_event, flag: string[]) => {
|
||||
setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 });
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import { Button, Typography, Box } from '@mui/material';
|
||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import type { State } from '@table-library/react-table-library/types/common';
|
||||
import { useRequest } from 'alova';
|
||||
import { useState, useEffect, useContext } from 'react';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
|
||||
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
|
||||
import * as EMSESP from './api';
|
||||
|
||||
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames, AnalogType } from './types';
|
||||
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
|
||||
import type { TemperatureSensor, AnalogSensor } from './types';
|
||||
import type { FC } from 'react';
|
||||
import { ButtonRow, SectionContent, useLayoutTitle } from 'components';
|
||||
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import DashboardSensorsAnalogDialog from './SensorsAnalogDialog';
|
||||
import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog';
|
||||
import { AnalogType, AnalogTypeNames, DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||
import type { AnalogSensor, TemperatureSensor, WriteAnalogSensor, WriteTemperatureSensor } from './types';
|
||||
import { analogSensorItemValidation, temperatureSensorItemValidation } from './validators';
|
||||
|
||||
const Sensors: FC = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
@@ -44,11 +44,14 @@ const Sensors: FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const { send: writeTemperatureSensor } = useRequest((data) => EMSESP.writeTemperatureSensor(data), {
|
||||
immediate: false
|
||||
});
|
||||
const { send: writeTemperatureSensor } = useRequest(
|
||||
(data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { send: writeAnalogSensor } = useRequest((data) => EMSESP.writeAnalogSensor(data), {
|
||||
const { send: writeAnalogSensor } = useRequest((data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
@@ -116,7 +119,7 @@ const Sensors: FC = () => {
|
||||
}
|
||||
]);
|
||||
|
||||
const getSortIcon = (state: any, sortKey: any) => {
|
||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
return <KeyboardArrowDownOutlinedIcon />;
|
||||
}
|
||||
@@ -138,6 +141,7 @@ const Sensors: FC = () => {
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
GPIO: (array) => array.sort((a, b) => a.g - b.g),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
|
||||
TYPE: (array) => array.sort((a, b) => a.t - b.t),
|
||||
VALUE: (array) => array.sort((a, b) => a.v - b.v)
|
||||
@@ -156,6 +160,7 @@ const Sensors: FC = () => {
|
||||
},
|
||||
sortToggleType: SortToggleType.AlternateWithReset,
|
||||
sortFns: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
NAME: (array) => array.sort((a, b) => a.n.localeCompare(b.n)),
|
||||
VALUE: (array) => array.sort((a, b) => a.t - b.t)
|
||||
}
|
||||
@@ -189,10 +194,13 @@ const Sensors: FC = () => {
|
||||
return formatted;
|
||||
};
|
||||
|
||||
function formatValue(value: any, uom: number) {
|
||||
function formatValue(value: unknown, uom: DeviceValueUOM) {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (typeof value !== 'number') {
|
||||
return value as string;
|
||||
}
|
||||
switch (uom) {
|
||||
case DeviceValueUOM.HOURS:
|
||||
return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 });
|
||||
@@ -201,10 +209,7 @@ const Sensors: FC = () => {
|
||||
case DeviceValueUOM.SECONDS:
|
||||
return LL.NUM_SECONDS({ num: value });
|
||||
case DeviceValueUOM.NONE:
|
||||
if (typeof value === 'number') {
|
||||
return new Intl.NumberFormat().format(value);
|
||||
}
|
||||
return value;
|
||||
return new Intl.NumberFormat().format(value);
|
||||
case DeviceValueUOM.DEGREES:
|
||||
case DeviceValueUOM.DEGREES_R:
|
||||
case DeviceValueUOM.FAHRENHEIT:
|
||||
@@ -300,7 +305,7 @@ const Sensors: FC = () => {
|
||||
|
||||
const RenderTemperatureSensors = () => (
|
||||
<Table data={{ nodes: sensorData.ts }} theme={temperature_theme} sort={temperature_sort} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: TemperatureSensor[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
@@ -341,7 +346,7 @@ const Sensors: FC = () => {
|
||||
|
||||
const RenderAnalogSensors = () => (
|
||||
<Table data={{ nodes: sensorData.as }} theme={analog_theme} sort={analog_sort} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: AnalogSensor[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,42 +1,41 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
InputAdornment,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { ValidatedTextField } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
|
||||
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
||||
import type { AnalogSensor } from './types';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
|
||||
type DashboardSensorsAnalogDialogProps = {
|
||||
interface DashboardSensorsAnalogDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (as: AnalogSensor) => void;
|
||||
creating: boolean;
|
||||
selectedItem: AnalogSensor;
|
||||
validator: Schema;
|
||||
};
|
||||
}
|
||||
|
||||
const SensorsAnalogDialog = ({
|
||||
open,
|
||||
@@ -67,8 +66,8 @@ const SensorsAnalogDialog = ({
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
InputAdornment,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
TextField
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import type { TemperatureSensor } from './types';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { ValidatedTextField } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { numberValue, updateValue } from 'utils';
|
||||
|
||||
import { validate } from 'validators';
|
||||
|
||||
type SensorsTemperatureDialogProps = {
|
||||
import type { TemperatureSensor } from './types';
|
||||
|
||||
interface SensorsTemperatureDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (ts: TemperatureSensor) => void;
|
||||
selectedItem: TemperatureSensor;
|
||||
validator: Schema;
|
||||
};
|
||||
}
|
||||
|
||||
const SensorsTemperatureDialog = ({
|
||||
open,
|
||||
@@ -62,8 +61,8 @@ const SensorsTemperatureDialog = ({
|
||||
setFieldErrors(undefined);
|
||||
await validate(validator, editItem);
|
||||
onSave(editItem);
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { Button } from '@mui/material';
|
||||
|
||||
import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||
import { useRequest } from 'alova';
|
||||
import { useEffect } from 'react';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { Translation } from 'i18n/i18n-types';
|
||||
|
||||
import * as EMSESP from './api';
|
||||
import type { Stat } from './types';
|
||||
|
||||
import type { Translation } from 'i18n/i18n-types';
|
||||
import type { FC } from 'react';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const SystemActivity: FC = () => {
|
||||
const { data: data, send: loadData, error } = useRequest(EMSESP.readActivity);
|
||||
|
||||
@@ -65,7 +66,8 @@ const SystemActivity: FC = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const showName = (id: any) => {
|
||||
const showName = (id: number) => {
|
||||
// TODO fix this
|
||||
const name: keyof Translation['STATUS_NAMES'] = id;
|
||||
return LL.STATUS_NAMES[name]();
|
||||
};
|
||||
@@ -92,7 +94,7 @@ const SystemActivity: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Table data={{ nodes: data.stats }} theme={stats_theme} layout={{ custom: true }}>
|
||||
{(tableList: any) => (
|
||||
{(tableList: Stat[]) => (
|
||||
<>
|
||||
<Header>
|
||||
<HeaderRow>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { alovaInstance } from 'api/endpoints';
|
||||
|
||||
import type {
|
||||
APIcall,
|
||||
Settings,
|
||||
Activity,
|
||||
CoreData,
|
||||
Devices,
|
||||
DeviceEntity,
|
||||
WriteTemperatureSensor,
|
||||
WriteAnalogSensor,
|
||||
SensorData,
|
||||
Entities,
|
||||
DeviceData,
|
||||
DeviceEntity,
|
||||
Devices,
|
||||
Entities,
|
||||
EntityItem,
|
||||
Schedule,
|
||||
ScheduleItem,
|
||||
EntityItem
|
||||
SensorData,
|
||||
Settings,
|
||||
WriteAnalogSensor,
|
||||
WriteTemperatureSensor
|
||||
} from './types';
|
||||
import { alovaInstance } from 'api/endpoints';
|
||||
|
||||
// DashboardDevices
|
||||
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
|
||||
@@ -23,11 +25,12 @@ export const readDeviceData = (id: number) =>
|
||||
params: { id }, // TODO replace later with id
|
||||
responseType: 'arraybuffer' // uses msgpack
|
||||
});
|
||||
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
|
||||
export const writeDeviceValue = (data: { id: number; c: string; v: unknown }) =>
|
||||
alovaInstance.Post('/rest/writeDeviceValue', data);
|
||||
|
||||
// Application Settings
|
||||
export const readSettings = () => alovaInstance.Get<Settings>('/rest/settings');
|
||||
export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data);
|
||||
export const writeSettings = (data: Settings) => alovaInstance.Post('/rest/settings', data);
|
||||
export const getBoardProfile = (boardProfile: string) =>
|
||||
alovaInstance.Get('/rest/boardProfile', {
|
||||
params: { boardProfile }
|
||||
@@ -59,20 +62,27 @@ export const readDeviceEntities = (id: number) =>
|
||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||
params: { id }, // TODO replace later with id
|
||||
responseType: 'arraybuffer',
|
||||
transformData(data: any) {
|
||||
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
|
||||
transformData(data) {
|
||||
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
|
||||
...de,
|
||||
o_m: de.m,
|
||||
o_cn: de.cn,
|
||||
o_mi: de.mi,
|
||||
o_ma: de.ma
|
||||
}));
|
||||
}
|
||||
});
|
||||
export const readDevices = () => alovaInstance.Get<Devices>('/rest/devices');
|
||||
export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations');
|
||||
export const writeCustomizationEntities = (data: any) => alovaInstance.Post('/rest/customizationEntities', data);
|
||||
export const writeCustomizationEntities = (data: { id: number; entity_ids: string[] }) =>
|
||||
alovaInstance.Post('/rest/customizationEntities', data);
|
||||
|
||||
// SettingsScheduler
|
||||
export const readSchedule = () =>
|
||||
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||
name: 'schedule',
|
||||
transformData(data: any) {
|
||||
return data.schedule.map((si: ScheduleItem) => ({
|
||||
transformData(data) {
|
||||
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
|
||||
...si,
|
||||
o_id: si.id,
|
||||
o_active: si.active,
|
||||
@@ -85,14 +95,14 @@ export const readSchedule = () =>
|
||||
}));
|
||||
}
|
||||
});
|
||||
export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data);
|
||||
export const writeSchedule = (data: Schedule) => alovaInstance.Post('/rest/schedule', data);
|
||||
|
||||
// SettingsEntities
|
||||
export const readCustomEntities = () =>
|
||||
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
|
||||
name: 'entities',
|
||||
transformData(data: any) {
|
||||
return data.entities.map((ei: EntityItem) => ({
|
||||
transformData(data) {
|
||||
return (data as Entities).entities.map((ei: EntityItem) => ({
|
||||
...ei,
|
||||
o_id: ei.id,
|
||||
o_device_id: ei.device_id,
|
||||
@@ -107,4 +117,5 @@ export const readCustomEntities = () =>
|
||||
}));
|
||||
}
|
||||
});
|
||||
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);
|
||||
export const writeCustomEntities = (data: { id: number; entity_ids: string[] }) =>
|
||||
alovaInstance.Post('/rest/customEntities', data);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||
import type { TranslationFunctions } from 'i18n/i18n-types';
|
||||
|
||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||
|
||||
const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => {
|
||||
const days = Math.trunc((duration_min * 60000) / 86400000);
|
||||
const hours = Math.trunc((duration_min * 60000) / 3600000) % 24;
|
||||
@@ -24,8 +25,8 @@ const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => {
|
||||
return formatted;
|
||||
};
|
||||
|
||||
export function formatValue(LL: TranslationFunctions, value: any, uom: number) {
|
||||
if (value === undefined) {
|
||||
export function formatValue(LL: TranslationFunctions, value: unknown, uom: DeviceValueUOM) {
|
||||
if (typeof value !== 'number') {
|
||||
return '';
|
||||
}
|
||||
switch (uom) {
|
||||
|
||||
@@ -119,7 +119,7 @@ export interface Devices {
|
||||
|
||||
export interface DeviceValue {
|
||||
id: string; // index, contains mask+name
|
||||
v: any; // value, Number or String
|
||||
v: unknown; // value, Number or String
|
||||
u: number; // uom
|
||||
c?: string; // command, optional
|
||||
l?: string[]; // list, optional
|
||||
@@ -134,10 +134,10 @@ export interface DeviceData {
|
||||
|
||||
export interface DeviceEntity {
|
||||
id: string; // shortname
|
||||
v?: any; // value, in any format, optional
|
||||
v?: unknown; // value, in any format, optional
|
||||
n?: string; // fullname, optional
|
||||
cn?: string; // custom fullname, optional
|
||||
m: number; // mask
|
||||
m: DeviceEntityMask; // mask
|
||||
w: boolean; // writeable
|
||||
mi?: number; // min value
|
||||
ma?: number; // max value
|
||||
@@ -230,9 +230,7 @@ export const AnalogTypeNames = [
|
||||
'PWM 2'
|
||||
];
|
||||
|
||||
type BoardProfiles = {
|
||||
[name: string]: string;
|
||||
};
|
||||
type BoardProfiles = Record<string, string>;
|
||||
|
||||
export const BOARD_PROFILES: BoardProfiles = {
|
||||
S32: 'BBQKees Gateway S32',
|
||||
@@ -265,7 +263,7 @@ export interface BoardProfile {
|
||||
export interface APIcall {
|
||||
device: string;
|
||||
entity: string;
|
||||
id: any;
|
||||
id: unknown;
|
||||
}
|
||||
export interface WriteAnalogSensor {
|
||||
id: number;
|
||||
@@ -306,6 +304,10 @@ export interface ScheduleItem {
|
||||
o_name?: string;
|
||||
}
|
||||
|
||||
export interface Schedule {
|
||||
schedule: ScheduleItem[];
|
||||
}
|
||||
|
||||
export enum ScheduleFlag {
|
||||
SCHEDULE_SUN = 1,
|
||||
SCHEDULE_MON = 2,
|
||||
@@ -327,7 +329,7 @@ export interface EntityItem {
|
||||
factor: number;
|
||||
uom: number;
|
||||
value_type: number;
|
||||
value?: any;
|
||||
value?: unknown;
|
||||
writeable: boolean;
|
||||
deleted?: boolean;
|
||||
o_id?: number;
|
||||
@@ -341,7 +343,7 @@ export interface EntityItem {
|
||||
o_value_type?: number;
|
||||
o_deleted?: boolean;
|
||||
o_writeable?: boolean;
|
||||
o_value?: any;
|
||||
o_value?: unknown;
|
||||
}
|
||||
|
||||
export interface Entities {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Schema from 'async-validator';
|
||||
import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
|
||||
import type { InternalRuleItem } from 'async-validator';
|
||||
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
||||
|
||||
import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
|
||||
|
||||
export const GPIO_VALIDATOR = {
|
||||
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
|
||||
if (
|
||||
@@ -239,7 +240,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) =>
|
||||
v: [
|
||||
{ required: true, message: 'Value is required' },
|
||||
{
|
||||
validator(rule: InternalRuleItem, value: any, callback: (error?: string) => void) {
|
||||
validator(rule: InternalRuleItem, value: unknown, callback: (error?: string) => void) {
|
||||
if (typeof value === 'number' && dv.m && dv.x && (value < dv.m || value > dv.x)) {
|
||||
callback('Value out of range');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user