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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"react-intl": "^7.1.11",
"react-papaparse": "^4.4.0",
"react-redux": "^9.2.0",
"react-resizable-panels": "^3.0.6",
"react-router": "^7.8.2",
"reconnecting-websocket": "^4.4.0",
"redux": "^5.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default function FilterBasedContingencyListDialog({
const activeDirectory = useSelector((state: AppState) => state.activeDirectory);
const { snackError } = useSnackMessage();
const [isFetching, setIsFetching] = useState(!!id);
const [isSubOrVlFilterIncluded, setIsSubOrVlFilterIncluded] = useState<boolean>(false);

const methods = useForm<ContingencyListFilterBasedFormData>({
defaultValues: emptyFormData(),
Expand Down Expand Up @@ -195,12 +196,15 @@ export default function FilterBasedContingencyListDialog({
isDataFetching={isFetching}
sx={{
'.MuiDialog-paper': {
minWidth: '60vw',
minWidth: isSubOrVlFilterIncluded ? '80vw' : '50vw',
height: '95vh',
},
}}
>
<ContingencyListFilterBasedForm />
<ContingencyListFilterBasedForm
isSubOrVlFilterIncluded={isSubOrVlFilterIncluded}
setIsSubOrVlFilterIncluded={setIsSubOrVlFilterIncluded}
/>
</CustomMuiDialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,27 @@
*/
import {
ArrayAction,
CONTINGENCY_LIST_EQUIPMENTS,
ContingencyListEquipment,
DescriptionField,
OverflowableChipWithHelperText,
DirectoryItemsInput,
ElementType,
EquipmentType,
FieldConstants,
MuiStyles,
OverflowableChipWithHelperText,
ResizeHandle,
UniqueNameInput,
} from '@gridsuite/commons-ui';
import {
Checkbox,
Divider,
Grid,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from '@mui/material';
import { Grid } from '@mui/material';
import { useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { ImperativePanelGroupHandle, Panel, PanelGroup } from 'react-resizable-panels';
import { AppState } from '../../../../redux/types';
import { ContingencyFieldConstants, FilterElement, FilterSubEquipments } from '../../../../utils/contingency-list.type';
import { FilterBasedContingencyListVisualizationPanel } from './filter-based-contingency-list-visualization-panel';
import { isSubstationOrVoltageLevelFilter } from '../contingency-list-utils';
import { EquipmentTypesByFilters } from './equipment-types-by-filters';
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
import { EquipmentTypesByFilters } from './equipment-types-by-filters';
import EquipmentTypesByFilters from './equipment-types-by-filters';

use import default

Copy link
Contributor Author

@flomillot flomillot Nov 26, 2025

Choose a reason for hiding this comment

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

no I cant there is an eslint error

Ok works with the code above


const equipmentTypes: string[] = [
EquipmentType.SUBSTATION,
Expand All @@ -52,61 +43,73 @@ const equipmentTypes: string[] = [
EquipmentType.DANGLING_LINE,
];

export default function ContingencyListFilterBasedForm() {
const styles = {
handle: (theme) => ({
backgroundColor: 'inherit',
width: 15,
marginLeft: 8,
marginRight: 8,
}),
} as const satisfies MuiStyles;

// sizes in percent
const LEFT_PANEL_MIN_SIZE = 25;
const LEFT_PANEL_DEFAULT_SIZE = 33;
const RIGHT_PANEL_MIN_SIZE = 25;

interface ContingencyListFilterBasedFormProps {
isSubOrVlFilterIncluded: boolean;
setIsSubOrVlFilterIncluded: (value: boolean) => void;
}

export default function ContingencyListFilterBasedForm({
isSubOrVlFilterIncluded,
setIsSubOrVlFilterIncluded,
}: Readonly<ContingencyListFilterBasedFormProps>) {
const { setValue, getValues } = useFormContext();
const activeDirectory = useSelector((state: AppState) => state.activeDirectory);
const [selectedFilterId, setSelectedFilterId] = useState<string | null>(null);
const [isDataOutdated, setIsDataOutdated] = useState<boolean>(false);

const filters: FilterElement[] = useWatch({ name: FieldConstants.FILTERS }) as unknown as FilterElement[];
const filters: FilterElement[] = useWatch({
name: FieldConstants.FILTERS,
}) as unknown as FilterElement[];
const substationAndVLFilters = filters.filter(isSubstationOrVoltageLevelFilter);
const filtersSubEquipments: FilterSubEquipments[] = useWatch({
name: ContingencyFieldConstants.SUB_EQUIPMENT_TYPES_BY_FILTER,
}) as unknown as FilterSubEquipments[];

const handleFilterRowClick = useCallback((clickedFilterId: string, currentFilterId: string | null) => {
if (clickedFilterId !== currentFilterId) {
setSelectedFilterId(clickedFilterId);
}
}, []);

const filterEquipmentTypes =
selectedFilterId &&
filtersSubEquipments?.find((value) => value[ContingencyFieldConstants.FILTER_ID] === selectedFilterId)?.[
ContingencyFieldConstants.SUB_EQUIPMENT_TYPES
];
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);

const handleEquipmentRowClick = useCallback(
(equipmentType: string, isEquipmentSelected: boolean, selectedFilterIdP: string | null) => {
const currentSubEquipmentsByFilters: FilterSubEquipments[] = getValues(
ContingencyFieldConstants.SUB_EQUIPMENT_TYPES_BY_FILTER
);
setValue(
ContingencyFieldConstants.SUB_EQUIPMENT_TYPES_BY_FILTER,
currentSubEquipmentsByFilters.map((value) => {
if (value[ContingencyFieldConstants.FILTER_ID] === selectedFilterIdP) {
// we either add or remove the clicked equipment from the list of subEquipments
// depending on if the equipment is already selected or not
const updatedSubEquipments = isEquipmentSelected
? value[ContingencyFieldConstants.SUB_EQUIPMENT_TYPES].filter(
(subEquipment: string) => subEquipment !== equipmentType
)
: [...value[ContingencyFieldConstants.SUB_EQUIPMENT_TYPES], equipmentType];
useEffect(() => {
const panelGroup = panelGroupRef.current;
if (substationAndVLFilters.length > 0 && !isSubOrVlFilterIncluded) {
setIsSubOrVlFilterIncluded(true);
if (panelGroup) {
panelGroup.setLayout([60, 40]);
}
} else if (substationAndVLFilters.length === 0 && isSubOrVlFilterIncluded) {
setIsSubOrVlFilterIncluded(false);
if (panelGroup) {
panelGroup.setLayout([33, 67]);
}
}
}, [substationAndVLFilters, setIsSubOrVlFilterIncluded, isSubOrVlFilterIncluded]);

return {
...value,
[ContingencyFieldConstants.SUB_EQUIPMENT_TYPES]: updatedSubEquipments,
};
}
// for the other filters, we just return the value as is
return value;
}),
{ shouldDirty: true }
);
setIsDataOutdated(true);
},
[setValue, getValues]
);
// TODO : uncomment when useEffectEvent will be available
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// TODO : uncomment when useEffectEvent will be available
// TODO : uncomment when useEffectEvent will be available and remove above useEffect

// const theme = useTheme();
// const isMdDown = useMediaQuery(theme.breakpoints.down('md'));
// const resizePanelsOnBreakpoint = useEffectEvent(isMdDown => {
// const panelGroup = panelGroupRef.current;
// if (panelGroup) {
// if (isMdDown) {
// panelGroup.setLayout([50, 50]);
// } else if (isSubOrVlFilterIncluded) {
// panelGroup.setLayout([33, 67]);
// } else {
// panelGroup.setLayout([60, 40]);
// }
// }
// });
// useEffect(() => {
// onMdDown(isMdDown);
// }, [isMdDown]);

const handleFilterOnChange = useCallback(
(_currentFilters: any, action?: ArrayAction, filter?: FilterElement) => {
Expand Down Expand Up @@ -142,120 +145,62 @@ export default function ContingencyListFilterBasedForm() {
);

return (
<Grid container spacing={2} sx={{ height: '100%' }}>
<Grid item container direction="column" xs={8} spacing={1}>
<Grid item>
<UniqueNameInput
name={FieldConstants.NAME}
label="nameProperty"
elementType={ElementType.CONTINGENCY_LIST}
activeDirectory={activeDirectory}
/>
</Grid>
<Grid item>
<DescriptionField />
</Grid>
<Grid item>
<FormattedMessage id="Filters" />
</Grid>
<Grid item>
<DirectoryItemsInput
titleId="FiltersListsSelection"
label=""
name={FieldConstants.FILTERS}
elementType={ElementType.FILTER}
equipmentTypes={equipmentTypes}
onChange={handleFilterOnChange}
ChipComponent={OverflowableChipWithHelperText}
chipProps={{ variant: 'outlined' }}
/>
</Grid>
{substationAndVLFilters.length > 0 && (
<>
<PanelGroup direction="horizontal" ref={panelGroupRef}>
<Panel defaultSize={LEFT_PANEL_DEFAULT_SIZE} minSize={LEFT_PANEL_MIN_SIZE}>
<Grid
container
columnSpacing={1.5}
sx={(theme) => ({
height: '100%',
[theme.breakpoints.up('md')]: {
flexWrap: 'nowrap',
},
})}
>
<Grid item container direction="column" rowSpacing={0.5} xs>
<Grid item>
<UniqueNameInput
name={FieldConstants.NAME}
label="nameProperty"
elementType={ElementType.CONTINGENCY_LIST}
activeDirectory={activeDirectory}
/>
</Grid>
<Grid item>
<FormattedMessage id="equipmentTypesByFilters" />
<DescriptionField />
</Grid>
<Grid item component="h4">
<FormattedMessage id="Filters" />
</Grid>
<Grid item container xs>
<Grid item xs={6} marginRight={-0.08}>
<TableContainer component={Paper} sx={{ height: '100%', border: 0.5 }}>
<Table>
<TableHead>
<TableRow>
<TableCell>
<FormattedMessage id="contingencyList.filterBased.filtersTableColumn" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{substationAndVLFilters.map((filterRow) => (
<TableRow
key={filterRow.id}
hover
onClick={() => handleFilterRowClick(filterRow.id, selectedFilterId)}
selected={filterRow.id === selectedFilterId}
>
<TableCell component="th" scope="row">
{filterRow.name}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item xs={6} marginLeft={-0.08}>
<TableContainer component={Paper} sx={{ height: '100%', border: 0.5 }}>
<Table>
<TableHead>
<TableRow>
<TableCell>
<FormattedMessage id="contingencyList.filterBased.subEquipmentsTableColumn" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filterEquipmentTypes &&
Object.values(CONTINGENCY_LIST_EQUIPMENTS).map(
(equipmentRow: ContingencyListEquipment) => {
const isEquipmentSelected = filterEquipmentTypes.includes(
equipmentRow.id
);
return (
<TableRow
key={equipmentRow.id}
hover
onClick={() =>
handleEquipmentRowClick(
equipmentRow.id,
isEquipmentSelected,
selectedFilterId
)
}
selected={isEquipmentSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={isEquipmentSelected} />
<FormattedMessage id={equipmentRow.label} />
</TableCell>
</TableRow>
);
}
)}
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item xs>
<DirectoryItemsInput
titleId="FiltersListsSelection"
label=""
name={FieldConstants.FILTERS}
elementType={ElementType.FILTER}
equipmentTypes={equipmentTypes}
onChange={handleFilterOnChange}
ChipComponent={OverflowableChipWithHelperText}
chipProps={{ variant: 'outlined' }}
fullHeight
/>
</Grid>
</>
)}
</Grid>
<Grid item>
<Divider orientation="vertical" />
</Grid>
<FilterBasedContingencyListVisualizationPanel
isDataOutdated={isDataOutdated}
setIsDataOutdated={setIsDataOutdated}
/>
</Grid>
</Grid>
{isSubOrVlFilterIncluded && (
<EquipmentTypesByFilters
substationAndVLFilters={substationAndVLFilters}
setIsDataOutdated={setIsDataOutdated}
/>
)}
</Grid>
</Panel>
<ResizeHandle style={styles.handle} />
<Panel minSize={RIGHT_PANEL_MIN_SIZE}>
<FilterBasedContingencyListVisualizationPanel
isDataOutdated={isDataOutdated}
setIsDataOutdated={setIsDataOutdated}
/>
</Panel>
</PanelGroup>
);
}
Loading
Loading