Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { EquipmentType, ModificationType } from '@gridsuite/commons-ui';
import { UUID } from 'node:crypto';

export interface LccShuntCompensatorConnectionInfos {
id: string;
connectedToHvdc: boolean;
}

export interface EquipmentDeletionSpecificInfos {
specificType: string;
// below is specific to HVDC-LCC deletion (then specificType = HVDC_LINE_LCC_DELETION_SPECIFIC_TYPE)
mcsOnSide1: LccShuntCompensatorConnectionInfos[];
mcsOnSide2: LccShuntCompensatorConnectionInfos[];
}

export type EquipmentDeletionInfos = {
type: ModificationType;
uuid?: UUID;
equipmentId: UUID;
equipmentType: EquipmentType;
equipmentInfos?: EquipmentDeletionSpecificInfos;
};

// Maps HvdcLccDeletionInfos from modification-server
export interface HvdcLccDeletionInfos extends EquipmentDeletionSpecificInfos {
id?: UUID;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,62 @@
import { yupResolver } from '@hookform/resolvers/yup';
import yup from 'components/utils/yup-config';
import { DELETION_SPECIFIC_DATA, EQUIPMENT_ID, TYPE } from '../../../utils/field-constants';
import { CustomFormProvider, snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui';
import {
CustomFormProvider,
DeepNullable,
EquipmentType,
snackWithFallback,
useSnackMessage,
} from '@gridsuite/commons-ui';
import { useForm } from 'react-hook-form';
import { useCallback, useEffect } from 'react';
import { ModificationDialog } from '../../commons/modificationDialog';
import { EQUIPMENT_TYPES } from 'components/utils/equipment-types';
import DeleteEquipmentForm from './equipment-deletion-form';
import PropTypes from 'prop-types';
import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form';
import { FORM_LOADING_DELAY } from 'components/network/constants';
import { deleteEquipment } from '../../../../services/study/network-modifications';
import { FetchStatus } from '../../../../services/utils';
import { UUID } from 'node:crypto';
import { FetchStatus } from 'services/utils.type';
import { EquipmentDeletionInfos } from './equipement-deletion-dialog.type';
import { NetworkModificationDialogProps } from '../../../graph/menus/network-modifications/network-modification-menu.type';
import { getHvdcLccDeletionSchema } from './hvdc-lcc-deletion/hvdc-lcc-deletion-utils';

const formSchema = yup
.object()
.shape({
[TYPE]: yup.string().nullable().required(),
[EQUIPMENT_ID]: yup.string().nullable().required(),
[TYPE]: yup.mixed<EquipmentType>().oneOf(Object.values(EquipmentType)).nullable().required(),
[DELETION_SPECIFIC_DATA]: getHvdcLccDeletionSchema(),
})
.required();

const emptyFormData = {
[TYPE]: null,
[EQUIPMENT_ID]: null,
export type EquipmentDeletionFormInfos = yup.InferType<typeof formSchema>;

const emptyFormData: EquipmentDeletionFormInfos = {
[EQUIPMENT_ID]: '',
[TYPE]: EquipmentType.SUBSTATION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is causing double fetch of equipment ids wen we open the dialog and equipment type is different than Substation

[DELETION_SPECIFIC_DATA]: null,
};

type EquipmentDeletionDialogProps = NetworkModificationDialogProps & {
editData?: EquipmentDeletionInfos;
defaultIdValue?: UUID;
equipmentType: EquipmentType;
editDataFetchStatus?: FetchStatus;
};

/**
* Dialog to delete an equipment from its type and ID.
* Dialog to delete equipment from its type and ID.
* @param studyUuid the study we are currently working on
* @param currentNode the node we are currently working on
* @param currentRootNetworkUuid
* @param editData the data to edit
* @param isUpdate check if edition form
* @param defaultIdValue the default equipment id
* @param editDataFetchStatus indicates the status of fetching EditData
* @param equipmentType
* @param onClose a callback when dialog has closed
* @param editDataFetchStatus indicates the status of fetching EditData
* @param onClose a callback when dialog has been closed
* @param onValidated a callback when dialog has been validated
* @param dialogProps props that are forwarded to the generic ModificationDialog component
*/
const EquipmentDeletionDialog = ({
Expand All @@ -54,36 +73,38 @@ const EquipmentDeletionDialog = ({
editData,
isUpdate,
defaultIdValue, // Used to pre-select an equipmentId when calling this dialog from the SLD/map
editDataFetchStatus,
equipmentType,
editDataFetchStatus,
onClose,
onValidated,
...dialogProps
}) => {
}: EquipmentDeletionDialogProps) => {
const currentNodeUuid = currentNode?.id;

const { snackError } = useSnackMessage();

const formMethods = useForm({
const formMethods = useForm<DeepNullable<EquipmentDeletionFormInfos>>({
defaultValues: emptyFormData,
resolver: yupResolver(formSchema),
resolver: yupResolver<DeepNullable<EquipmentDeletionFormInfos>>(formSchema),
});

const { reset } = formMethods;

const fromEditDataToFormValues = useCallback(
(editData) => {
(editData: EquipmentDeletionInfos) => {
reset({
[TYPE]: EQUIPMENT_TYPES[editData.equipmentType],
[TYPE]: editData.equipmentType,
[EQUIPMENT_ID]: editData.equipmentId,
[DELETION_SPECIFIC_DATA]: null,
});
},
[reset]
);

const fromMenuDataValues = useCallback(
(menuSelectId) => {
(menuSelectId: UUID) => {
reset({
[TYPE]: EQUIPMENT_TYPES.HVDC_LINE,
[TYPE]: EquipmentType.HVDC_LINE,
[EQUIPMENT_ID]: menuSelectId,
[DELETION_SPECIFIC_DATA]: null,
});
Expand All @@ -92,7 +113,7 @@ const EquipmentDeletionDialog = ({
);

const resetFormWithEquipmentType = useCallback(
(equipmentType) => {
(equipmentType: EquipmentType) => {
reset({
[TYPE]: equipmentType,
});
Expand All @@ -118,15 +139,15 @@ const EquipmentDeletionDialog = ({
]);

const onSubmit = useCallback(
(formData) => {
deleteEquipment(
(formData: EquipmentDeletionFormInfos) => {
deleteEquipment({
studyUuid,
currentNodeUuid,
formData[TYPE],
formData[EQUIPMENT_ID],
editData?.uuid,
formData[DELETION_SPECIFIC_DATA]
).catch((error) => {
nodeUuid: currentNodeUuid,
uuid: editData?.uuid,
equipmentId: formData[EQUIPMENT_ID] as UUID,
equipmentType: formData[TYPE],
equipmentSpecificInfos: formData[DELETION_SPECIFIC_DATA] ?? undefined,
}).catch((error) => {
snackWithFallback(snackError, error, { headerId: 'UnableToDeleteEquipment' });
});
},
Expand All @@ -149,8 +170,9 @@ const EquipmentDeletionDialog = ({
fullWidth
maxWidth="md"
onClear={clear}
onSave={onSubmit}
onClose={onClose}
onValidated={onValidated}
onSave={onSubmit}
titleId="DeleteEquipment"
open={open}
isDataFetching={isUpdate && editDataFetchStatus === FetchStatus.RUNNING}
Expand All @@ -167,16 +189,4 @@ const EquipmentDeletionDialog = ({
);
};

EquipmentDeletionDialog.propTypes = {
studyUuid: PropTypes.string,
currentNode: PropTypes.object,
currentRootNetworkUuid: PropTypes.string,
editData: PropTypes.object,
isUpdate: PropTypes.bool,
defaultIdValue: PropTypes.string,
editDataFetchStatus: PropTypes.string,
equipmentType: PropTypes.string,
onClose: PropTypes.func,
};

export default EquipmentDeletionDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
import { Grid } from '@mui/material';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useSnackMessage, AutocompleteInput, snackWithFallback, filledTextField } from '@gridsuite/commons-ui';
import {
useSnackMessage,
AutocompleteInput,
EquipmentType,
filledTextField,
snackWithFallback,
} from '@gridsuite/commons-ui';
import {
DELETION_SPECIFIC_DATA,
EQUIPMENT_ID,
Expand All @@ -18,16 +24,33 @@ import {
import { areIdsEqual, getObjectId, richTypeEquals } from 'components/utils/utils';
import { EQUIPMENT_TYPES } from 'components/utils/equipment-types';
import HvdcLccDeletionSpecificForm from './hvdc-lcc-deletion/hvdc-lcc-deletion-specific-form';
import useHvdcLccDeletion from './hvdc-lcc-deletion/hvdc-lcc-deletion-utils';

import { fetchEquipmentsIds } from '../../../../services/study/network-map';
import useGetLabelEquipmentTypes from '../../../../hooks/use-get-label-equipment-types';
import GridItem from '../../commons/grid-item';
import type { UUID } from 'node:crypto';
import { CurrentTreeNode } from '../../../graph/tree-node.type';
import { EquipmentDeletionInfos } from './equipement-deletion-dialog.type';
import useHvdcLccDeletion from './hvdc-lcc-deletion/use-hvdc-lcc-deletion';

export interface DeleteEquipmentFormProps {
studyUuid: UUID;
currentNode: CurrentTreeNode;
currentRootNetworkUuid: UUID;
editData?: EquipmentDeletionInfos;
}

const DeleteEquipmentForm = ({ studyUuid, currentNode, currentRootNetworkUuid, editData }) => {
const NULL_UUID: UUID = '00000000-0000-0000-0000-000000000000';

export default function DeleteEquipmentForm({
studyUuid,
currentNode,
currentRootNetworkUuid,
editData,
}: Readonly<DeleteEquipmentFormProps>) {
const { snackError } = useSnackMessage();
const editedIdRef = useRef(null);
const currentTypeRef = useRef(null);
const editedIdRef = useRef<UUID | null>(null);
const currentTypeRef = useRef<EquipmentType>(null);

const watchType = useWatch({
name: TYPE,
Expand Down Expand Up @@ -84,31 +107,32 @@ const DeleteEquipmentForm = ({ studyUuid, currentNode, currentRootNetworkUuid, e
}, [studyUuid, currentNode?.id, currentRootNetworkUuid, watchType, snackError]);

useEffect(() => {
if (studyUuid && currentNode?.id && currentRootNetworkUuid) {
if (editData?.equipmentId) {
if (editedIdRef.current === null) {
// The first time we render an edition, we want to merge the
// dynamic data with the edition data coming from the database
editedIdRef.current = editData.equipmentId;
} else if (watchEquipmentId !== editedIdRef.current && editedIdRef.current !== '') {
// we have changed eqptId, leave the "first edit" mode (then if we circle back
// to editData?.equipmentId, we wont make the merge anymore).
editedIdRef.current = '';
}
if (!studyUuid || !currentNode?.id || !currentRootNetworkUuid) {
return;
}
if (editData?.equipmentId) {
if (editedIdRef.current === null) {
// The first time we render an edition, we want to merge the
// dynamic data with the edition data coming from the database
editedIdRef.current = editData.equipmentId;
} else if (watchEquipmentId !== editedIdRef.current && editedIdRef.current !== NULL_UUID) {
// we have changed eqptId, leave the "first edit" mode (then if we circle back
// to editData?.equipmentId, we won't make the merge anymore).
editedIdRef.current = NULL_UUID;
}
}

if (watchEquipmentId && currentTypeRef.current === EQUIPMENT_TYPES.HVDC_LINE) {
// need specific update related to HVDC LCC deletion (for MCS lists)
hvdcLccSpecificUpdate(
studyUuid,
currentNode?.id,
currentRootNetworkUuid,
watchEquipmentId,
watchEquipmentId === editedIdRef.current ? editData : null
);
} else {
setValue(DELETION_SPECIFIC_DATA, null);
}
if (watchEquipmentId && currentTypeRef.current === EquipmentType.HVDC_LINE) {
// need specific update related to HVDC LCC deletion (for MCS lists)
hvdcLccSpecificUpdate(
studyUuid,
currentNode?.id,
currentRootNetworkUuid,
watchEquipmentId,
watchEquipmentId === editedIdRef.current ? editData : undefined
);
} else {
setValue(DELETION_SPECIFIC_DATA, null);
}
}, [
studyUuid,
Expand Down Expand Up @@ -149,9 +173,9 @@ const DeleteEquipmentForm = ({ studyUuid, currentNode, currentRootNetworkUuid, e
options={equipmentsOptions}
getOptionLabel={getObjectId}
//hack to work with freesolo autocomplete
//setting null programatically when freesolo is enable wont empty the field
//setting null programmatically when freesolo is enable won't empty the field
inputTransform={(value) => value ?? ''}
outputTransform={(value) => (value === '' ? null : getObjectId(value))}
outputTransform={(value: any) => (value === '' ? null : getObjectId(value))}
size={'small'}
formProps={filledTextField}
/>
Expand All @@ -168,6 +192,4 @@ const DeleteEquipmentForm = ({ studyUuid, currentNode, currentRootNetworkUuid, e
)}
</>
);
};

export default DeleteEquipmentForm;
}
Loading
Loading