-
Notifications
You must be signed in to change notification settings - Fork 6
Moving VoltageLevelSectionCreationForm from gridstudy to commons-ui #1295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c8c9e14
8c2fe11
131aac7
1f64ed9
1ae8665
2fd6d5f
ce9b44b
3244698
1ccbea1
211a353
749c8d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,376 @@ | ||
| /** | ||
| * 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 { useCallback, useEffect, useMemo, useState } from 'react'; | ||
| import { Box, Button, Grid, Slider, TextField, Tooltip, Typography } from '@mui/material'; | ||
| import { FormattedMessage, useIntl } from 'react-intl'; | ||
| import { InfoOutlined } from '@mui/icons-material'; | ||
| import { useFormContext, useWatch } from 'react-hook-form'; | ||
| import { AutocompleteInput, SelectInput, SwitchInput, useCustomFormContext } from '../../../../components/ui'; | ||
| import { areIdsEqual, FieldConstants, getObjectId, Option } from '../../../../utils'; | ||
| import { GridSection } from '../../../../components/composite/grid/grid-section'; | ||
| import { filledTextField } from '../../common'; | ||
| import { PositionDiagramPaneType } from '../../common/connectivity/connectivity.type'; | ||
| import { SWITCH_TYPE } from '../creation'; | ||
| import { BusBarSections } from './voltageLevelSectionCreation.types'; | ||
| import { POSITION_NEW_SECTION_SIDE } from './voltageLevelSectionCreation.utils'; | ||
|
|
||
| const getArrayPosition = (data: BusBarSections, selectedOptionId: string) => { | ||
| if (!selectedOptionId || !data) { | ||
| return { position: -1, length: 0 }; | ||
| } | ||
|
|
||
| const matchingArray = Object.values(data).find( | ||
| (array) => Array.isArray(array) && array.indexOf(selectedOptionId) !== -1 | ||
|
Check warning on line 27 in src/features/network-modifications/voltageLevel/section/VoltageLevelSectionCreationForm.tsx
|
||
| ); | ||
| if (matchingArray) { | ||
| return { position: matchingArray.indexOf(selectedOptionId), length: matchingArray.length }; | ||
| } | ||
| return { position: -1, length: 0 }; | ||
| }; | ||
|
|
||
| type OptionWithDisabled = Option & { disabled?: boolean }; | ||
|
|
||
| export interface VoltageLevelSectionCreationFormProps { | ||
| busBarSectionInfos?: BusBarSections; | ||
| allBusbarSectionsList?: string[]; | ||
| isSymmetricalNbBusBarSections?: boolean; | ||
| isNotFoundOrNotSupported?: boolean; | ||
| PositionDiagramPane?: PositionDiagramPaneType; | ||
| } | ||
|
|
||
| export function VoltageLevelSectionCreationForm({ | ||
| busBarSectionInfos, | ||
| allBusbarSectionsList, | ||
| isSymmetricalNbBusBarSections = false, | ||
| isNotFoundOrNotSupported = false, | ||
| PositionDiagramPane, | ||
| }: Readonly<VoltageLevelSectionCreationFormProps>) { | ||
| const intl = useIntl(); | ||
| const { isNodeBuilt, isUpdate } = useCustomFormContext(); | ||
| const [isDiagramPaneOpen, setIsDiagramPaneOpen] = useState(false); | ||
| const [busBarSectionsIdOptions, setBusBarSectionsIdOptions] = useState<Option[]>([]); | ||
| const [isNotRequiredSwitchBefore, setIsNotRequiredSwitchBefore] = useState(false); | ||
| const [isNotRequiredSwitchAfter, setIsNotRequiredSwitchAfter] = useState(false); | ||
| const { setValue } = useFormContext(); | ||
| const voltageLevelId = useWatch({ name: FieldConstants.EQUIPMENT_ID }); | ||
| const busbarIndex = useWatch({ name: FieldConstants.BUS_BAR_INDEX }); | ||
| const selectedOption = useWatch({ name: FieldConstants.BUSBAR_SECTION_ID }); | ||
| const selectedPositionOption = useWatch({ name: FieldConstants.IS_AFTER_BUSBAR_SECTION_ID }); | ||
| // free input mode (no suggestions) is used when no network busbar section data is available, | ||
| // e.g. when this form is used outside of a study (GridExplore) | ||
| const isFreeInputMode = !busBarSectionInfos; | ||
|
|
||
| const voltageLevelIdField = ( | ||
| <TextField | ||
| size="small" | ||
| fullWidth | ||
| label={intl.formatMessage({ id: 'VoltageLevelId' })} | ||
| value={voltageLevelId ?? ''} | ||
| slotProps={{ | ||
| input: { | ||
| readOnly: true, | ||
| }, | ||
| }} | ||
| disabled | ||
| {...filledTextField} | ||
| /> | ||
| ); | ||
| const switchValue = useWatch({ name: FieldConstants.NEW_SWITCH_STATES }); | ||
|
|
||
| useEffect(() => { | ||
| if (busBarSectionInfos && busbarIndex) { | ||
| const selectedKey = busbarIndex?.id; | ||
| setValue(FieldConstants.ALL_BUS_BAR_SECTIONS, false); | ||
| if (selectedKey === 'all') { | ||
| setValue(FieldConstants.ALL_BUS_BAR_SECTIONS, true); | ||
| if (allBusbarSectionsList && Array.isArray(allBusbarSectionsList)) { | ||
| const options = allBusbarSectionsList | ||
| .filter((id): id is string => Boolean(id)) | ||
| .map((id) => ({ | ||
| id, | ||
| label: id, | ||
| })); | ||
| setBusBarSectionsIdOptions(options); | ||
| } else { | ||
| setBusBarSectionsIdOptions([]); | ||
| } | ||
| return; | ||
| } | ||
| const sections = busBarSectionInfos[selectedKey]; | ||
| if (!sections || !Array.isArray(sections)) { | ||
| setBusBarSectionsIdOptions([]); | ||
| return; | ||
| } | ||
| const options = sections | ||
| .filter((id): id is string => Boolean(id)) | ||
| .map((id) => ({ | ||
| id, | ||
| label: id, | ||
| })); | ||
| setBusBarSectionsIdOptions(options); | ||
| } else { | ||
| setBusBarSectionsIdOptions([]); | ||
| } | ||
| }, [allBusbarSectionsList, busBarSectionInfos, intl, busbarIndex, setValue]); | ||
|
|
||
| const arrayPosition = useMemo( | ||
| () => busBarSectionInfos && getArrayPosition(busBarSectionInfos, selectedOption?.id), | ||
| [busBarSectionInfos, selectedOption?.id] | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (selectedOption && selectedPositionOption && busBarSectionInfos && arrayPosition) { | ||
| const selectedSectionIndex = arrayPosition.position; | ||
| const busBarSections = arrayPosition.length - 1; | ||
| if (selectedSectionIndex === 0 && selectedPositionOption === POSITION_NEW_SECTION_SIDE.BEFORE.id) { | ||
| setValue(FieldConstants.SWITCH_BEFORE_NOT_REQUIRED, true); | ||
| setIsNotRequiredSwitchBefore(true); | ||
| } else { | ||
| setValue(FieldConstants.SWITCH_BEFORE_NOT_REQUIRED, false); | ||
| setIsNotRequiredSwitchBefore(false); | ||
| } | ||
| if ( | ||
| busBarSections === selectedSectionIndex && | ||
| selectedPositionOption === POSITION_NEW_SECTION_SIDE.AFTER.id | ||
| ) { | ||
| setValue(FieldConstants.SWITCH_AFTER_NOT_REQUIRED, true); | ||
| setIsNotRequiredSwitchAfter(true); | ||
| } else { | ||
| setValue(FieldConstants.SWITCH_AFTER_NOT_REQUIRED, false); | ||
| setIsNotRequiredSwitchAfter(false); | ||
|
benrejebmoh marked this conversation as resolved.
|
||
| } | ||
| } | ||
| if (isUpdate && isNodeBuilt) { | ||
| setValue(FieldConstants.SWITCH_AFTER_NOT_REQUIRED, true); | ||
| setValue(FieldConstants.SWITCH_BEFORE_NOT_REQUIRED, true); | ||
| } | ||
| }, [selectedOption, setValue, busBarSectionInfos, selectedPositionOption, arrayPosition, isUpdate, isNodeBuilt]); | ||
|
|
||
| const busBarIndexOptions = useMemo((): OptionWithDisabled[] => { | ||
| if (busBarSectionInfos) { | ||
| const sortedOptions = Object.keys(busBarSectionInfos || {}) | ||
|
Check warning on line 155 in src/features/network-modifications/voltageLevel/section/VoltageLevelSectionCreationForm.tsx
|
||
| .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) | ||
|
Check warning on line 156 in src/features/network-modifications/voltageLevel/section/VoltageLevelSectionCreationForm.tsx
|
||
| .map((key) => ({ | ||
| id: key, | ||
| label: key, | ||
| })); | ||
| const allOption = { | ||
| id: 'all', | ||
| label: intl.formatMessage({ id: 'allBusbarSections' }), | ||
| disabled: !isSymmetricalNbBusBarSections, | ||
| } as Option & { disabled?: boolean }; | ||
|
|
||
| return [...sortedOptions, allOption]; | ||
| } | ||
| return []; | ||
| }, [busBarSectionInfos, intl, isSymmetricalNbBusBarSections]); | ||
|
|
||
| const getOptionLabel = (object: string | { id: string | number; label: string | number }) => { | ||
| if (typeof object === 'string') { | ||
| return object; | ||
| } | ||
| if (object?.id === 'all') { | ||
| return intl.formatMessage({ id: 'allBusbarSections' }) ?? ''; | ||
| } | ||
| return String(object?.id ?? ''); | ||
| }; | ||
|
|
||
| const isOptionEqualToValue = (val1: Option, val2: Option) => { | ||
| const getId = (option: Option) => { | ||
| return typeof option === 'string' ? option : String(option?.id ?? ''); | ||
| }; | ||
|
|
||
| return getId(val1) === getId(val2); | ||
| }; | ||
|
|
||
| const handleChangeBusbarIndex = useCallback(() => { | ||
| if (!isFreeInputMode) { | ||
| setValue(FieldConstants.BUSBAR_SECTION_ID, null); | ||
| } | ||
| }, [isFreeInputMode, setValue]); | ||
|
|
||
| const freeInputOutputTransform = useCallback( | ||
| (value: Option | null) => (typeof value === 'string' ? { id: value, label: value } : value), | ||
| [] | ||
| ); | ||
|
|
||
| const busbarCountField = ( | ||
| <AutocompleteInput | ||
| name={FieldConstants.BUS_BAR_INDEX} | ||
| label="Busbar" | ||
| onChangeCallback={handleChangeBusbarIndex} | ||
| options={busBarIndexOptions as Option[]} | ||
| getOptionLabel={getOptionLabel} | ||
| isOptionEqualToValue={isOptionEqualToValue} | ||
| allowNewValue={isFreeInputMode} | ||
| outputTransform={freeInputOutputTransform} | ||
| renderOption={(props, option) => { | ||
| const allOptionsDisabled = (option as any).id === 'all' && (option as any)?.disabled; | ||
| const { key, ...otherProps } = props; | ||
| return ( | ||
| <li key={key} {...otherProps}> | ||
| <div> | ||
| <div>{getOptionLabel(option)}</div> | ||
| {allOptionsDisabled && ( | ||
| <div | ||
| style={{ | ||
| fontSize: '0.85rem', | ||
| color: 'red', | ||
| marginTop: '2px', | ||
| }} | ||
| > | ||
| {intl.formatMessage({ id: 'allOptionHelperText' })} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </li> | ||
| ); | ||
| }} | ||
| getOptionDisabled={(option) => (option as any)?.disabled} | ||
| size="small" | ||
| disabled={isNotFoundOrNotSupported} | ||
| /> | ||
| ); | ||
|
|
||
| const busbarSectionsField = ( | ||
| <AutocompleteInput | ||
| name={FieldConstants.BUSBAR_SECTION_ID} | ||
| label="BusBarSectionsReference" | ||
| options={busBarSectionsIdOptions} | ||
| getOptionLabel={getObjectId} | ||
| isOptionEqualToValue={areIdsEqual} | ||
| allowNewValue={isFreeInputMode} | ||
| outputTransform={freeInputOutputTransform} | ||
| size="small" | ||
| disabled={!busbarIndex || isNotFoundOrNotSupported} | ||
| /> | ||
| ); | ||
|
|
||
| const positionSideNewSectionField = ( | ||
| <SelectInput | ||
| name={FieldConstants.IS_AFTER_BUSBAR_SECTION_ID} | ||
| label="isAfterBusBarSectionId" | ||
| options={Object.values(POSITION_NEW_SECTION_SIDE)} | ||
| size="small" | ||
| disabled={!busbarIndex || isNotFoundOrNotSupported} | ||
| /> | ||
| ); | ||
|
|
||
| const switchBeforeField = ( | ||
| <SelectInput | ||
| name={FieldConstants.SWITCHES_BEFORE_SECTIONS} | ||
| label="switchesBeforeSections" | ||
| options={Object.values(SWITCH_TYPE)} | ||
| size="small" | ||
| disabled={!busbarIndex || isNotRequiredSwitchBefore || isNotFoundOrNotSupported} | ||
| /> | ||
| ); | ||
| const switchAfterField = ( | ||
| <SelectInput | ||
| name={FieldConstants.SWITCHES_AFTER_SECTIONS} | ||
| label="switchesAfterSections" | ||
| options={Object.values(SWITCH_TYPE)} | ||
| size="small" | ||
| disabled={!busbarIndex || isNotRequiredSwitchAfter || isNotFoundOrNotSupported} | ||
| /> | ||
| ); | ||
| const newSwitchState = ( | ||
| <SwitchInput | ||
| name={FieldConstants.NEW_SWITCH_STATES} | ||
| label={switchValue ? 'areSwitchesClosed' : 'areSwitchesOpen'} | ||
| /> | ||
| ); | ||
| const getLabelDescription = useCallback(() => { | ||
| return intl.formatMessage({ id: 'newSection' }); | ||
| }, [intl]); | ||
| const newSectionField = ( | ||
| <Slider | ||
| min={0} | ||
| max={3} | ||
| step={0.1} | ||
| value={1.5} | ||
| track={false} | ||
| valueLabelFormat={getLabelDescription} | ||
| valueLabelDisplay="on" | ||
| size="medium" | ||
| disabled | ||
| sx={{ | ||
| '& .MuiSlider-thumb': { | ||
| backgroundColor: '#1976d2', | ||
| color: '#1976d2', | ||
| '&:hover': { | ||
| backgroundColor: '#1976d2', | ||
| }, | ||
| '&.Mui-disabled': { | ||
| backgroundColor: '#1976d2', | ||
| color: '#1976d2', | ||
| }, | ||
| }, | ||
| }} | ||
| /> | ||
| ); | ||
| const handleCloseDiagramPane = useCallback(() => { | ||
| setIsDiagramPaneOpen(false); | ||
| }, []); | ||
| const handleClickOpenDiagramPane = useCallback(() => { | ||
| setIsDiagramPaneOpen(true); | ||
| }, []); | ||
| const diagramToolTip = ( | ||
| <Tooltip sx={{ paddingLeft: 1 }} title={intl.formatMessage({ id: 'builtNodeTooltipForDiagram' })}> | ||
| <InfoOutlined color="info" fontSize="medium" /> | ||
| </Tooltip> | ||
| ); | ||
| return ( | ||
| <Box sx={{ p: 2 }}> | ||
| <Grid container spacing={2}> | ||
| <Grid size={12}> | ||
| <Grid container spacing={2} alignItems="center"> | ||
| <Grid size={{ xs: 12, md: 6 }}>{voltageLevelIdField}</Grid> | ||
| {PositionDiagramPane && isNodeBuilt && ( | ||
| <Grid size={{ xs: 12, md: 3 }}> | ||
| <Button onClick={handleClickOpenDiagramPane} variant="outlined" size="small"> | ||
| <FormattedMessage id="CreateCouplingDeviceDiagramButton" /> | ||
| </Button> | ||
| {diagramToolTip} | ||
| </Grid> | ||
| )} | ||
| </Grid> | ||
| </Grid> | ||
| {isNotFoundOrNotSupported && ( | ||
| <Grid size={12}> | ||
| <Typography variant="body1" color="red"> | ||
| <FormattedMessage id="notValidVoltageLevel" /> | ||
| </Typography> | ||
| </Grid> | ||
| )} | ||
| <Grid size={12}> | ||
| <GridSection title="SectionPosition" /> | ||
| </Grid> | ||
|
|
||
| <Grid size={4}>{busbarCountField}</Grid> | ||
| <Grid size={4}>{busbarSectionsField}</Grid> | ||
| <Grid size={4}>{positionSideNewSectionField}</Grid> | ||
|
|
||
| <Grid size={12}> | ||
| <GridSection title="Switch" /> | ||
| </Grid> | ||
|
|
||
| <Grid size={{ xs: 12, sm: 4 }}>{switchBeforeField}</Grid> | ||
| <Grid size={{ xs: 12, sm: 4 }}>{newSectionField}</Grid> | ||
| <Grid size={{ xs: 12, sm: 4 }}>{switchAfterField}</Grid> | ||
| <Grid size={12}>{newSwitchState}</Grid> | ||
| </Grid> | ||
| {PositionDiagramPane && ( | ||
| <PositionDiagramPane | ||
| open={isDiagramPaneOpen} | ||
| onClose={handleCloseDiagramPane} | ||
| voltageLevelId={voltageLevelId} | ||
| /> | ||
| )} | ||
| </Box> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| /** | ||
| * 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/. | ||
| */ | ||
|
|
||
| export * from './VoltageLevelSectionCreationForm'; | ||
| export * from './voltageLevelSectionCreation.types'; | ||
| export * from './voltageLevelSectionCreation.utils'; |
Uh oh!
There was an error while loading. Please reload this page.