Загрузка данных


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