Загрузка данных
import { InterfaceType } from '@/shared/api/controller/client';
import { Schema } from '@shared/lib/Schema';
import { SchemaMessage } from '@shared/lib/SchemaMessage';
import { Validator } from '@shared/lib/Validator';
import { array, boolean, object, string } from 'yup';
import { OTHER_VALUES } from '../model/formNames';
const validator = new Validator();
const schemaMessage = new SchemaMessage();
export const createDeviceFormSchema = object().shape(
{
name: string().required(schemaMessage.requiredMessage),
description: string().notRequired(),
bidirectional: boolean().notRequired(),
modelId: string().required(schemaMessage.requiredMessage),
sipUri: Schema.sipUriSchema,
ip: Schema.deviceIpV4.when('type', ([type]: InterfaceType[], schema) => {
return schema;
}),
mac: Schema.deviceMac.when('type', ([type]: InterfaceType[], schema) => {
if (type === 'av-lan') {
return schema.notRequired().nullable();
}
return schema;
}),
controlPage: string().when('controlPage', (controlPage, schema) => {
if (!controlPage) {
return schema.notRequired().nullable();
}
return schema
.matches(validator.controlPageRegExp, schemaMessage.wrongFormatMessage)
.notRequired();
}),
hardwareId: Schema.hardwareId,
type: Schema.interfaceType,
ieee8021x: Schema.ieee8021x.when(
'type',
([type]: InterfaceType[], schema) => {
if (type === 'ethernet') {
return schema.required(schemaMessage.requiredMessage);
}
return schema.notRequired().nullable();
},
),
protocolIp: Schema.protocolIp.when('type', {
is: 'ethernet',
then: (schema) => schema.required(schemaMessage.requiredMessage),
otherwise: (schema) => schema.notRequired(),
}),
firmware: string().nullable(),
serialNumber: string().nullable(),
inventoryNumber: string().nullable(),
ipAvLan: string().when('type', ([type]: InterfaceType[], schema) => {
if (type === 'av-lan') {
return schema
.matches(validator.ipV4RegExp, schemaMessage.wrongFormatMessage)
.required();
}
return schema.notRequired().nullable();
}),
localAddress: Schema.localAddressCreateSchema,
port: Schema.portNotRequired,
netInterface: boolean().optional(),
disableMonitoring: boolean().optional(),
netInterfaceInfo: array().when(
[OTHER_VALUES.netInterface, OTHER_VALUES.type],
([netInterface, type], schema) => {
if (netInterface && type === 'ethernet') {
return schema.of(
object({
nameId: string().required(schemaMessage.requiredMessage),
ip: Schema.ipV4NotRequired,
mac: Schema.macNotRequired,
subnetMask: Schema.ipV4NotRequired,
gateway: Schema.ipV4NotRequired,
}),
);
} else {
return schema.optional().default([]);
}
},
),
},
[
['mac', 'mac'],
['port', 'port'],
['controlPage', 'controlPage'],
['localAddress', 'localAddress'],
],
);
import { useDeviceRepository } from '@/domains/equipment/repositories/device.repository';
import { normalizeSipUriForPayload } from '@/domains/equipment/repositories/helpers/normalizeSipUriForPayload';
import { usePreservedSipUri } from '@/domains/equipment/repositories/helpers/usePreservedSipUri';
import { DeviceModel } from '@/shared/api/controller/client';
import { yupResolver } from '@hookform/resolvers/yup';
import { QueryStatus } from '@reduxjs/toolkit/dist/query';
import { Button, Drawer, DrawerActions } from '@shared/components';
import { useCloseDrawer } from '@shared/hooks/drawer/useCloseDrawer';
import { useSelectDrawerType } from '@shared/hooks/drawer/useSelectDrawerType';
import {
EventActionTypes,
useSendClickStreamEvent,
} from '@shared/hooks/useSendClickStreamEvent';
import { DrawerTypes } from '@shared/model/drawer/drawer.types';
import { useSelectActiveController } from '@shared/store/сontrollers/сontrollers.selectors';
import { useSyncControlPage } from '@widgets/Monitoring/hooks';
import { memo, useEffect, useState } from 'react';
import {
FormProvider,
Resolver,
SubmitHandler,
useForm,
} from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { CreateDeviceForm } from '../CreateDeviceForm';
import { CreateDeviceFormData } from '../model/types';
import { createDeviceFormSchema } from '../validation/validator';
export const CreateDeviceDrawer = memo(() => {
const { t } = useTranslation('common');
const { sendEvent } = useSendClickStreamEvent(EventActionTypes.createDevice);
const drawerType = useSelectDrawerType();
const isVisible = drawerType[DrawerTypes.createDevice];
const closeDrawer = useCloseDrawer();
const activeController = useSelectActiveController();
const [model, setModel] = useState<DeviceModel | null>(null);
const { preserveSipUri, getPreservedSipUri, resetPreservedSipUri } =
usePreservedSipUri();
const {
saveDevice,
createDeviceStatus,
isCreateDeviceLoading,
resetCreateDevice,
} = useDeviceRepository();
const defaultValues: CreateDeviceFormData = {
name: '',
description: '',
ip: '',
mac: '',
controlPage: '',
typeName: '',
manufacturerName: '',
sipUri: '',
hasSipUri: null,
modelId: '',
hardwareId: '',
type: 'ethernet',
bidirectional: false,
ieee8021x: 'MAB',
protocolIp: 'dhcp',
firmware: null,
serialNumber: null,
inventoryNumber: null,
ipAvLan: '',
localAddress: undefined,
port: undefined,
netInterface: false,
netInterfaceInfo: [],
disableMonitoring: false,
};
const methods = useForm<CreateDeviceFormData>({
mode: 'onBlur',
reValidateMode: 'onChange',
defaultValues,
resolver: yupResolver(
createDeviceFormSchema,
) as Resolver<CreateDeviceFormData>,
});
useSyncControlPage(defaultValues.ip, methods);
useEffect(() => {
const currentHasSipUri = methods.getValues('hasSipUri');
if (currentHasSipUri) {
preserveSipUri(methods.getValues('sipUri'));
}
if (model) {
const modelHasSipUri = model.type?.hasSipUri ?? false;
methods.setValue('manufacturerName', model.manufacturer?.name);
methods.setValue('typeName', model.type?.name);
methods.setValue('hasSipUri', modelHasSipUri);
if (modelHasSipUri) {
methods.setValue('sipUri', getPreservedSipUri());
}
return;
}
methods.setValue('modelId', '');
methods.resetField('manufacturerName');
methods.resetField('typeName');
methods.setValue('hasSipUri', null);
}, [model]);
useEffect(() => {
if (isVisible) {
methods.reset(defaultValues);
resetPreservedSipUri();
}
}, [isVisible]);
useEffect(() => {
if (createDeviceStatus === QueryStatus.fulfilled) {
closeDrawer(DrawerTypes.createDevice);
methods.reset(defaultValues);
resetPreservedSipUri();
resetCreateDevice();
sendEvent();
}
}, [createDeviceStatus]);
const submitHandler: SubmitHandler<CreateDeviceFormData> = (values) => {
if (activeController?.id) {
saveDevice(normalizeSipUriForPayload(values), activeController.id);
}
};
const actions: DrawerActions = {
actionPrimary: (
<Button
data-fui-tid={`${DrawerTypes.createDevice}-actionPrimary`}
onClick={methods.handleSubmit(submitHandler)}
loading={isCreateDeviceLoading}
disabled={!methods.formState.isValid}
>
{t('buttons.save')}
</Button>
),
};
return (
<FormProvider {...methods}>
<Drawer
type={DrawerTypes.createDevice}
actions={actions}
title={t('drawerTitle.createDevice')}
>
<CreateDeviceForm setModel={setModel} />
</Drawer>
</FormProvider>
);
});
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@babel/preset-env": "7.26.9",
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@hookform/resolvers": "3.1.1",
"@mui/icons-material": "5.14.9",
"@mui/material": "5.14.9",
"@reduxjs/toolkit": "1.9.5",
"@sber-friend/flamingo-charts": "4.10.0",
"@sber-friend/flamingo-components": "4.10.0",
"@sber-friend/flamingo-core": "4.10.0",
"@sber-friend/flamingo-icons": "4.10.0",
"@sber-friend/flamingo-pickers": "4.10.0",
"@sbol/clickstream-agent": "0.9.9",
"@tanstack/react-router": "1.29.2",
"ajv": "8.18.0",
"axios": "1.13.0",
"d3-color": "3.1.0",
"i18next": "23.2.8",
"i18next-browser-languagedetector": "7.2.1",
"i18next-http-backend": "2.5.2",
"ip-regex": "4.3.0",
"jest-fixed-jsdom": "0.0.9",
"jwt-decode": "3.1.2",
"klona": "2.0.6",
"notistack": "3.0.1",
"object-hash": "3.0.0",
"plural-ru": "2.0.2",
"postcss": "8.4.49",
"react": "18.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.45.4",
"react-i18next": "13.0.1",
"react-redux": "^8.1.2",
"react-router-dom": "7.13.2",
"react-use-websocket": "4.13.0",
"react-virtuoso": "4.12.3",
"rxjs": "8.0.0-alpha.14",
"styled-components": "6.1.12",
"typescript": "5.4.5",
"use-debounce": "10.0.0",
"yup": "1.3.3"
},
"devDependencies": {
"@babel/core": "7.24.5",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"@babel/preset-env": "7.26.9",
"@babel/preset-react": "7.24.1",
"@babel/preset-typescript": "7.24.1",
"@babel/register": "8.0.0-alpha.6",
"@eslint/compat": "1.2.2",
"@eslint/eslintrc": "3.1.0",
"@eslint/js": "9.14.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@rtk-query/codegen-openapi": "1.0.0",
"@svgr/webpack": "8.1.0",
"@tanstack/router-devtools": "1.29.2",
"@testing-library/dom": "10.3.0",
"@testing-library/jest-dom": "6.4.5",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.10",
"@types/node": "20.10.3",
"@types/object-hash": "3.0.6",
"@types/react": "18.2.41",
"@types/react-dom": "18.2.17",
"@types/react-input-mask": "3.0.5",
"@types/webpack": "5.28.0",
"@types/webpack-bundle-analyzer": "4.4.1",
"@types/webpack-dev-server": "4.7.2",
"@typescript-eslint/parser": "8.13.1-alpha.1",
"@typescript-eslint/typescript-estree": "8.13.1-alpha.1",
"@typescript-eslint/utils": "8.13.1-alpha.1",
"autoprefixer": "9.4.5",
"babel-jest": "27.5.1",
"babel-loader": "8.2.3",
"babel-preset-react-app": "10.0.1",
"clean-webpack-plugin": "4.0.0",
"copy-webpack-plugin": "10.2.4",
"cross-env": "7.0.3",
"css-loader": "6.8.1",
"cssnano": "6.0.2",
"dayjs": "1.11.11",
"eslint": "9.14.0",
"eslint-config-prettier": "9.0.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react-hooks": "4.3.0",
"fork-ts-checker-webpack-plugin": "7.2.1",
"globals": "15.12.0",
"html-webpack-plugin": "5.5.4",
"husky": "^8.0.0",
"ignore-styles": "5.0.1",
"jest": "29.7.0",
"jest-environment-jsdom": "30.0.0",
"jest-transform-stub": "2.0.0",
"jsdom": "18.1.0",
"lint-staged": "9.5.0",
"merge": "2.1.1",
"mini-css-extract-plugin": "2.7.6",
"msw": "2.6.3",
"postcss-loader": "8.1.1",
"prettier": "3.2.5",
"prettier-plugin-organize-imports": "4.1.0",
"react-refresh": "0.9.0",
"react-refresh-typescript": "2.0.9",
"style-loader": "3.3.3",
"ts-loader": "9.5.1",
"ts-node": "10.9.1",
"typescript-eslint": "8.13.0",
"url-loader": "4.1.1",
"webpack": "5.89.0",
"webpack-bundle-analyzer": "4.10.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1",
"whatwg-fetch": "3.6.2"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"npm run lint",
"npm run prettier",
"git add"
],
"**/*.test.{js,ts,jsx,tsx}": [
"npm run test",
"git add"
]
},
"name": "smart-room-front",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve --env mode=development",
"build:dev": "cross-env NODE_ENV=development webpack --env mode=development",
"build": "cross-env NODE_ENV=production webpack --env mode=production",
"generate-auth-client": "npx @rtk-query/codegen-openapi src/shared/api/auth/openapi-config.ts",
"generate-auth-equipment-client": "npx @rtk-query/codegen-openapi src/shared/api/auth/equipment/openapi-config.ts",
"generate-auth-refresh-tokens-client": "npx @rtk-query/codegen-openapi src/shared/api/auth/refreshTokens/openapi-config.ts",
"generate-auth-click-stream-client": "npx @rtk-query/codegen-openapi src/shared/api/auth/clickStream/openapi-config.ts",
"generate-user-client": "npx @rtk-query/codegen-openapi src/shared/api/user/openapi-config.ts",
"generate-controller-client": "npx @rtk-query/codegen-openapi src/shared/api/controller/openapi-config.ts",
"generate-location-client": "npx @rtk-query/codegen-openapi src/shared/api/location/openapi-config.ts",
"generate-task-client": "npx @rtk-query/codegen-openapi src/shared/api/task/openapi-config.ts",
"generate-audit-client": "npx @rtk-query/codegen-openapi src/shared/api/audit/openapi-config.ts",
"generate-stat-client": "npx @rtk-query/codegen-openapi src/shared/api/stat/openapi-config.ts",
"generate-notifications-client": "npx @rtk-query/codegen-openapi src/shared/api/notifications/openapi-config.ts",
"generate-ws-client": "npx @rtk-query/codegen-openapi src/shared/api/ws/openapi-config.ts",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"prepare": "husky install",
"lint": "eslint src/ -c eslint.config.mjs --fix",
"prettier": "prettier -w --check"
},
"version": "1.1.15",
"overrides": {
"eslint": "9.14.0",
"@typescript-eslint/utils": "8.13.1-alpha.1"
},
"msw": {
"workerDirectory": [
"public"
]
}
}
Шаги воспроизведения при создании:
Создать ДУ типа Ethernet,
Заполнить обязательные поля,
Изменить тип ДУ например на avlan
Вернуть Ethernet
Фактический результат: возможность создать ДУ блокируется
Ожидаемый результат: Создать ДУ возможно