Загрузка данных
import { OTHER_VALUES } from '@/domains/controller/components/CreateController/model/formNames';
import { useControllerRepository } from '@/domains/equipment/repositories/controller.repository';
import { useNetInterfaceRepository } from '@/domains/equipment/repositories/netInterface.repository';
import { Helpers } from '@/shared/lib';
import { yupResolver } from '@hookform/resolvers/yup';
import { QueryStatus } from '@reduxjs/toolkit/query';
import {
DeviceModel,
useGetApiV1SecondaryInterfaceValueOwnerByOwnerIdQuery,
useGetControllerByIdQuery,
} from '@shared/api/controller/client';
import {
Button,
Drawer,
DrawerActions,
DrawerProps,
Loader,
} 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 { useControllerActions } from '@shared/store/сontrollers/controllers.actions';
import {
useSelectActiveControllerId,
useSelectControllerLocationId,
} 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 { ConfirmIpChangeModal } from './ConfirmIpChangeModal';
import { UpdateControllerForm } from './UpdateControllerForm';
import { OTHER_NAMES } from './model/formNames';
import { UpdateControllerFormData } from './model/types';
import { updateControllerFormSchema } from './validation/validator';
export const UpdateControllerDrawer = memo(() => {
const { t } = useTranslation();
const { sendEvent } = useSendClickStreamEvent(
EventActionTypes.createController,
);
const { cleanControllerLocationId } = useControllerActions();
const closeDrawer = useCloseDrawer();
const drawerType = useSelectDrawerType();
const open = drawerType[DrawerTypes.updateController];
const controllerId = useSelectActiveControllerId();
const locationId = useSelectControllerLocationId();
const {
updateController,
isUpdateControllerLoading,
controllerUpdateStatus,
resetUpdateController,
} = useControllerRepository();
const { data: controller } = useGetControllerByIdQuery(
{ controllerId: controllerId ?? '' },
{
skip: !controllerId,
refetchOnMountOrArgChange: true,
},
);
const id = controller?.id;
const {
data: secondaryInterfaceData,
isSuccess: isSecondaryInterfaceSuccess,
} = useGetApiV1SecondaryInterfaceValueOwnerByOwnerIdQuery(
{ ownerId: id! },
{ skip: !id },
);
const [model, setModel] = useState<DeviceModel | null>(null);
const [pendingValues, setPendingValues] =
useState<UpdateControllerFormData | null>(null);
const defaultValues: UpdateControllerFormData = {
name: controller?.name,
description: controller?.description,
locationId: controller?.locationId,
firmware: controller?.firmware,
controlPage: controller?.controlPage,
subnetMask: controller?.subnetMask,
gateway: controller?.gateway,
telnetPort: controller?.telnetPort,
port: controller?.interface?.port,
timeZone: controller?.timeZone,
sendNotifications: controller?.sendNotifications,
ip: controller?.ip,
mac: controller?.mac,
typeName: controller?.model?.type?.name,
manufacturerName: controller?.model?.manufacturer?.name,
modelId: controller?.model?.id,
segment: controller?.segment,
serialNumber: controller?.serialNumber,
inventoryNumber: controller?.inventoryNumber,
protocolIp: controller?.interface?.protocolIp,
localAddress: controller?.interface?.localAddress,
ieee8021x: controller?.interface?.ieee8021x,
isMain: controller?.isMain,
netInterface: !!secondaryInterfaceData?.length,
netInterfaceInfo: secondaryInterfaceData?.map((secondaryInterface) => {
return {
gateway: secondaryInterface.gateway,
mac: secondaryInterface.mac,
ip: secondaryInterface.ip,
subnetMask: secondaryInterface.subnetMask,
nameId: secondaryInterface.name?.id,
id: secondaryInterface.id,
};
}),
disableMonitoring: controller?.disableMonitoring ?? false,
qrCodeType: controller?.qrCodeType,
};
const methods = useForm<UpdateControllerFormData>({
mode: 'onBlur',
reValidateMode: 'onChange',
defaultValues,
resolver: yupResolver(
updateControllerFormSchema,
) as Resolver<UpdateControllerFormData>,
});
const handleClearModel = () => {
methods.setValue('modelId', '');
methods.setValue('manufacturerName', '');
methods.setValue('typeName', '');
};
useEffect(() => {
if (controller?.model) {
setModel(controller.model);
}
}, [controller]);
useEffect(() => {
if (model) {
methods.setValue('manufacturerName', model?.manufacturer?.name);
methods.setValue('typeName', model?.type?.name);
}
}, [model]);
useEffect(() => {
if (isSecondaryInterfaceSuccess && secondaryInterfaceData) {
methods.setValue(
OTHER_VALUES.netInterface,
!!secondaryInterfaceData?.length,
);
methods.setValue(
OTHER_VALUES.netInterfaceInfo,
secondaryInterfaceData?.map((secondaryInterface) => {
return {
gateway: secondaryInterface.gateway,
mac: secondaryInterface.mac,
ip: secondaryInterface.ip,
subnetMask: secondaryInterface.subnetMask,
nameId: secondaryInterface.name?.id,
id: secondaryInterface.id,
};
}),
);
}
}, [isSecondaryInterfaceSuccess, secondaryInterfaceData]);
const netInterfaceInfo = methods.watch(OTHER_NAMES.netInterfaceInfo);
const netInterface = methods.watch(OTHER_NAMES.netInterface);
const { deleteNetInterfaces } = useNetInterfaceRepository();
useEffect(() => {
(async () => {
if (
!netInterface &&
isSecondaryInterfaceSuccess &&
netInterfaceInfo &&
!!netInterfaceInfo.length
) {
await deleteNetInterfaces(
netInterfaceInfo?.map((netInterface) => netInterface.id),
);
methods.setValue(OTHER_VALUES.netInterfaceInfo, []);
}
})();
}, [netInterface, netInterfaceInfo, isSecondaryInterfaceSuccess]);
useSyncControlPage(controller?.ip, methods);
useEffect(() => {
if (!Helpers.isEmpty(locationId)) {
methods.setValue(OTHER_NAMES.locationId, locationId);
methods.reset(methods.getValues());
}
}, [locationId]);
useEffect(() => {
if (controllerUpdateStatus === QueryStatus.fulfilled && controller?.id) {
closeDrawer(DrawerTypes.updateController);
resetUpdateController();
sendEvent();
}
}, [controller?.id, controllerUpdateStatus]);
const updateNetworkDevice = async (values: UpdateControllerFormData) => {
if (!id) {
return;
}
await updateController(values, id);
cleanControllerLocationId();
};
const submitHandler: SubmitHandler<UpdateControllerFormData> = async (
values,
) => {
const oldIp = controller?.ip?.trim() ?? '';
const newIp = values.ip?.trim() ?? '';
if (oldIp !== newIp) {
setPendingValues(values);
return;
}
await updateNetworkDevice(values);
};
const handleConfirmIpChange = async () => {
if (!pendingValues) {
return;
}
const values = pendingValues;
setPendingValues(null);
await updateNetworkDevice(values);
};
const handleCancelIpChange = () => {
setPendingValues(null);
};
useEffect(() => {
methods.reset(defaultValues);
}, [controller, open]);
const { isValid, dirtyFields } = methods.formState;
const isDirtyLocationId =
methods.getValues().locationId !== defaultValues.locationId;
const isDirtyFields = !Helpers.isEmpty(dirtyFields) || isDirtyLocationId;
const isSaveButtonDisabled = !isDirtyFields || !isValid;
const actions: DrawerActions = {
actionPrimary: (
<Button
onClick={methods.handleSubmit(submitHandler)}
disabled={isSaveButtonDisabled}
loading={isUpdateControllerLoading}
>
{controllerUpdateStatus === QueryStatus.pending ? (
<Loader />
) : (
t('common:buttons.save')
)}
</Button>
),
};
const handleClose: DrawerProps['onClose'] = () => {
cleanControllerLocationId();
methods.reset(defaultValues);
setModel(null);
setPendingValues(null);
};
if (!controller) {
return null;
}
return (
<>
<FormProvider {...methods}>
<Drawer
type={DrawerTypes.updateController}
actions={actions}
title={t('common:drawerTitle.updateController')}
onClose={handleClose}
>
<UpdateControllerForm
initialModel={controller?.model}
setModel={setModel}
handleClearModel={handleClearModel}
/>
</Drawer>
</FormProvider>
<ConfirmIpChangeModal
open={!!pendingValues}
oldIp={controller?.ip ?? ''}
newIp={pendingValues?.ip ?? ''}
onConfirm={handleConfirmIpChange}
onClose={handleCancelIpChange}
/>
</>
);
});
import { Button, Drawer } from '@/shared/components';
import { DrawerActions } from '@/shared/components/Drawer/Drawer';
import { DrawerTypes } from '@/shared/model/drawer/drawer.types';
import { FormProvider } from 'react-hook-form';
import { ConfirmIpChangeModal } from './ConfirmIpChangeModal';
import { PadletUpdateForm } from './PadletUpdateForm';
import { usePadletUpdateState } from './hooks/usePadletUpdateState';
export const PadletUpdateDrawer = () => {
const {
updateDrawerLabel,
updateLabel,
methods,
onDrawerClose,
submitHandler,
model,
setModel,
handleClearModel,
isSaveButtonDisabled,
isConfirmIpChangeModalOpen,
oldIp,
newIp,
handleConfirmIpChange,
handleCancelIpChange,
} = usePadletUpdateState();
const actions: DrawerActions = {
actionPrimary: (
<Button
onClick={methods.handleSubmit(submitHandler)}
disabled={isSaveButtonDisabled}
>
{updateLabel}
</Button>
),
};
return (
<>
<FormProvider {...methods}>
<Drawer
type={DrawerTypes.padletUpdate}
actions={actions}
title={updateDrawerLabel}
onClose={onDrawerClose}
>
<PadletUpdateForm
initialModel={model}
setModel={setModel}
handleClearModel={handleClearModel}
/>
</Drawer>
</FormProvider>
<ConfirmIpChangeModal
open={isConfirmIpChangeModalOpen}
oldIp={oldIp}
newIp={newIp}
onConfirm={handleConfirmIpChange}
onClose={handleCancelIpChange}
/>
</>
);
};
import { normalizeSipUriForPayload } from '@/domains/equipment/repositories/helpers/normalizeSipUriForPayload';
import { usePreservedSipUri } from '@/domains/equipment/repositories/helpers/usePreservedSipUri';
import { PadletModelItem } from '@/shared/api/controller/client';
import { useCloseDrawer } from '@/shared/hooks/drawer/useCloseDrawer';
import { Helpers } from '@/shared/lib';
import { DrawerTypes } from '@/shared/model/drawer/drawer.types';
import { useSelectControllerLocationId } from '@/shared/store/сontrollers/сontrollers.selectors';
import {
Ieee8021X,
ProtocolIp,
Segment,
} from '@/widgets/Monitoring/model/padlet.model';
import { usePadletRepository } from '@/widgets/Monitoring/repositories/padlet.repository';
import { yupResolver } from '@hookform/resolvers/yup';
import { QueryStatus } from '@reduxjs/toolkit/dist/query';
import { useEffect, useState } from 'react';
import { SubmitHandler, UseFormReturn, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { PadletCreateFormData } from '../../PadletCreateDrawer/model/padletCreateCreateForm.model';
import { useGetPadletById } from '../../PadletInfoDrawer/hooks/useGetPadletById';
import {
PADLET_UPDATE_FORM_KEY,
PadletUpdateFormData,
} from '../model/padletUpdateForm.model';
import { padletUpdateSchema } from '../validation/padletUpdate.schema';
type PadletUpdateState = {
updateLabel: string;
updateDrawerLabel: string;
methods: UseFormReturn<PadletUpdateFormData, unknown, undefined>;
onDrawerClose: () => void;
submitHandler: SubmitHandler<PadletUpdateFormData>;
model: PadletModelItem | undefined | null;
isSaveButtonDisabled: boolean;
handleClearModel: () => void;
setModel: (value: PadletModelItem | null) => void;
isConfirmIpChangeModalOpen: boolean;
oldIp: string;
newIp: string;
handleConfirmIpChange: () => Promise<void>;
handleCancelIpChange: () => void;
};
export const usePadletUpdateState = (): PadletUpdateState => {
const { t } = useTranslation();
const updateLabel = t('common:buttons.save');
const updateDrawerLabel = t('common:drawerTitle.PadletUpdate');
const closeDrawer = useCloseDrawer();
const [model, setModel] = useState<PadletModelItem | null>(null);
const [pendingValues, setPendingValues] =
useState<PadletCreateFormData | null>(null);
const { updatePadlet, updatePadletQueryStatus } = usePadletRepository();
const padlet = useGetPadletById();
const id = padlet?.id;
const locationId = useSelectControllerLocationId();
const defaultValues: PadletUpdateFormData = {
locationId: padlet?.locationId ?? '',
name: padlet?.name ?? '',
ip: padlet?.interface?.ip ?? '',
mac: padlet?.interface?.mac ?? '',
subnetMask: padlet?.interface?.subnetMask ?? '',
gateway: padlet?.interface?.gateway ?? '',
port: padlet?.interface?.port ?? 8080,
timeZone: padlet?.timeZone ?? '',
modelId: padlet?.model?.id ?? '',
typeName: padlet?.model?.type?.name ?? '',
sipUri: padlet?.sipUri,
hasSipUri: padlet?.model?.type?.hasSipUri ?? null,
manufacturerName: padlet?.model?.manufacturer?.name ?? '',
//@ts-ignore
segment: padlet?.segment ?? Segment.SIGMA,
//@ts-ignore
protocolIp: padlet?.interface?.protocolIp ?? ProtocolIp.DHCP,
ieee8021x: padlet?.interface?.ieee8021x ?? Ieee8021X.MAB,
description: padlet?.description ?? '',
telnetPort: padlet?.telnetPort ?? '',
firmware: padlet?.firmware ?? '',
serialNumber: padlet?.serialNumber ?? '',
inventoryNumber: padlet?.inventoryNumber ?? '',
sendNotifications: padlet?.sendNotifications ?? undefined,
controlPage: padlet?.controlPage ?? '',
disableMonitoring: padlet?.disableMonitoring ?? false,
};
const methods = useForm<PadletUpdateFormData, unknown, undefined>({
mode: 'onBlur',
reValidateMode: 'onChange',
resolver: yupResolver(padletUpdateSchema),
defaultValues,
});
const { preserveSipUri, getPreservedSipUri, resetPreservedSipUri } =
usePreservedSipUri();
const onDrawerClose = (): void => {
methods.reset(defaultValues);
setModel(null);
setPendingValues(null);
resetPreservedSipUri();
};
useEffect(() => {
if (padlet?.model) {
setModel(padlet.model);
}
}, [padlet]);
useEffect(() => {
preserveSipUri(padlet?.sipUri ?? '');
}, [padlet?.id, padlet?.sipUri, preserveSipUri]);
const handleClearModel = () => {
if (methods.getValues('hasSipUri')) {
preserveSipUri(methods.getValues('sipUri'));
}
methods.setValue('modelId', '');
methods.setValue('manufacturerName', '');
methods.setValue('typeName', '');
methods.setValue('hasSipUri', null);
};
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());
}
}
}, [model]);
useEffect(() => {
methods.reset(defaultValues);
}, [padlet]);
const updateNetworkDevice = async (values: PadletCreateFormData) => {
if (!id) {
return;
}
await updatePadlet(
normalizeSipUriForPayload(values) as PadletCreateFormData,
id,
);
};
const submitHandler: SubmitHandler<PadletUpdateFormData> = async (values) => {
const oldIp = padlet?.interface?.ip?.trim() ?? '';
const newIp = values.ip?.trim() ?? '';
if (oldIp !== newIp) {
setPendingValues(values as PadletCreateFormData);
return;
}
await updateNetworkDevice(values as PadletCreateFormData);
};
const handleConfirmIpChange = async () => {
if (!pendingValues) {
return;
}
const values = pendingValues;
setPendingValues(null);
await updateNetworkDevice(values);
};
const handleCancelIpChange = () => {
setPendingValues(null);
};
useEffect(() => {
if (updatePadletQueryStatus === QueryStatus.fulfilled) {
closeDrawer(DrawerTypes.padletUpdate);
methods.reset(defaultValues);
setPendingValues(null);
resetPreservedSipUri();
}
}, [updatePadletQueryStatus]);
const { isValid, dirtyFields } = methods.formState;
const isDirtyLocationId =
locationId && locationId !== defaultValues.locationId;
useEffect(() => {
if (locationId) {
methods.setValue(PADLET_UPDATE_FORM_KEY.locationId, locationId);
methods.reset(methods.getValues());
}
}, [locationId]);
const isDirtyFields = !Helpers.isEmpty(dirtyFields) || isDirtyLocationId;
const isSaveButtonDisabled = !isDirtyFields || !isValid;
return {
updateDrawerLabel,
updateLabel,
methods,
onDrawerClose,
submitHandler,
model,
isSaveButtonDisabled,
setModel,
handleClearModel,
isConfirmIpChangeModalOpen: !!pendingValues,
oldIp: padlet?.interface?.ip ?? '',
newIp: pendingValues?.ip ?? '',
handleConfirmIpChange,
handleCancelIpChange,
};
};
import { Box, Button, Modal, Typography } from '@/shared/components';
import { useTranslation } from 'react-i18next';
interface ConfirmIpChangeModalProps {
open: boolean;
oldIp: string;
newIp: string;
onConfirm: () => void | Promise<void>;
onClose: () => void;
}
export const ConfirmIpChangeModal = ({
open,
oldIp,
newIp,
onConfirm,
onClose,
}: ConfirmIpChangeModalProps) => {
const { t } = useTranslation();
return (
<Modal
open={open}
title={t('common:modalTitle.confirmIpChange')}
onClose={onClose}
actionPrimary={
<Button onClick={onConfirm}>
{t('common:buttons.yes')}
</Button>
}
actionSecondary={
<Button onClick={onClose}>
{t('common:buttons.no')}
</Button>
}
>
<Box p='0px 16px' textAlign='center'>
<Typography variant='caption2'>
{t('common:messages.confirmIpChange', {
oldIp,
newIp,
})}
</Typography>
</Box>
</Modal>
);
};