Skip to content
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

[WIP] New IHM : root network #2457

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
930b4db
new IHM : root network
souissimai Dec 17, 2024
600b9f2
Merge branch 'main' into root-network-panel
souissimai Dec 17, 2024
914900d
feat: add root network uuid to redux + fix fetch computation status e…
Dec 17, 2024
3e02681
add
souissimai Dec 17, 2024
2a4a010
conflicts
souissimai Dec 17, 2024
c99f16c
fix: migrating several other endpoints
Dec 17, 2024
7653e64
create root network
souissimai Dec 18, 2024
9abc7b4
fix: node can now be built
Dec 18, 2024
11d0b82
changing endpoints
souissimai Dec 19, 2024
2b530ab
resolve conflicts
souissimai Dec 19, 2024
9af2d7b
switch between rootnetworks
souissimai Dec 19, 2024
8b546d4
fix: reload tree + reset equipment on root network change
Dec 20, 2024
7c74af0
refresh spreadsheet when switching root networ + endpoint with root n…
souissimai Dec 23, 2024
f62c33c
fix: reload tree when changing root network
Dec 23, 2024
d126d2d
fix: fetch single node info
Dec 23, 2024
bdf757a
migrating endpoints
souissimai Dec 24, 2024
cb1f093
notifications and migrating endpoints
souissimai Dec 26, 2024
bba1991
resolve conflicts
souissimai Dec 26, 2024
671dea5
resolve conflicts + migrating endpoints
souissimai Dec 26, 2024
506e7c2
fixes
souissimai Dec 27, 2024
7a28d0d
clean
souissimai Dec 29, 2024
114c8ff
fixes clean code
souissimai Dec 29, 2024
06f1dd9
fix search endpoint migrating
souissimai Jan 2, 2025
298ddbd
changes: clean + migrating endpoints
souissimai Jan 2, 2025
02e144d
add labes when updating root network
souissimai Jan 3, 2025
d63e9a6
fix: when switching of root network, reset nominal voltages and cente…
Jan 3, 2025
9101699
Merge branch 'root-network-panel' of https://github.com/gridsuite/gri…
Jan 3, 2025
e377afc
manage notifications
souissimai Jan 3, 2025
b82126c
Merge branch 'root-network-panel' of https://github.com/gridsuite/gri…
souissimai Jan 3, 2025
ebf0d0b
fix fetchnetworkelement endpoint ith current root network uuid
souissimai Jan 3, 2025
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
231 changes: 231 additions & 0 deletions src/components/dialogs/case-list-selector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/**
* Copyright (c) 2020, 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, useState } from 'react';
import PropTypes from 'prop-types';

import { FormattedMessage, useIntl } from 'react-intl';
import { PARAM_FAVORITE_CONTINGENCY_LISTS } from '../../utils/config-params';
import { useSelector } from 'react-redux';
import { ElementType } from '@gridsuite/commons-ui';
import { useSnackMessage, CheckBoxList } from '@gridsuite/commons-ui';
import { updateConfigParameter } from '../../services/config';
import { fetchContingencyAndFiltersLists } from '../../services/directory';
import { fetchContingencyCount } from '../../services/study';
import { DirectoryItemSelector } from '@gridsuite/commons-ui';
import { isNodeBuilt } from 'components/graph/util/model-functions';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
import { toggleElementFromList } from 'components/utils/utils';
import { Grid, DialogActions, Button, DialogTitle, Typography, Dialog, DialogContent, Alert } from '@mui/material';

function makeButton(onClick, message, disabled) {
return (
<Grid item>
<Button onClick={onClick} variant="contained" disabled={disabled}>
<FormattedMessage id={message} />
</Button>
</Grid>
);
}

const CaseListSelector = (props) => {
const favoriteContingencyListUuids = useSelector((state) => state[PARAM_FAVORITE_CONTINGENCY_LISTS]);

const currentNode = useSelector((state) => state.currentTreeNode);

const [contingencyList, setContingencyList] = useState([]);

const [simulatedContingencyCount, setSimulatedContingencyCount] = useState(0);

const [checkedContingencyList, setCheckedContingencyList] = useState([]);

const [favoriteSelectorOpen, setFavoriteSelectorOpen] = useState(false);

const { snackError } = useSnackMessage();

const intl = useIntl();

const handleClose = () => {
props.onClose();
};

const handleStart = () => {
props.onStart(checkedContingencyList.map((c) => c.id));
};

const saveFavorites = useCallback(
(newList) => {
updateConfigParameter(PARAM_FAVORITE_CONTINGENCY_LISTS, newList)
.then()
.catch((error) => {
snackError({
messageTxt: error.message,
headerId: 'paramsChangingError',
});
});
},
[snackError]
);

useEffect(() => {
setSimulatedContingencyCount(null);
var discardResult = false;
if (isNodeBuilt(currentNode) && props.open) {
fetchContingencyCount(
props.studyUuid,
currentNode.id,
checkedContingencyList.map((c) => c.id)
).then((contingencyCount) => {
if (!discardResult) {
setSimulatedContingencyCount(contingencyCount);
}
});
}
return () => {
discardResult = true;
};
}, [props.open, props.studyUuid, currentNode, checkedContingencyList]);

useEffect(() => {
if (favoriteContingencyListUuids && favoriteContingencyListUuids.length > 0 && props.open) {
fetchContingencyAndFiltersLists(favoriteContingencyListUuids)
.then((res) => {
const mapCont = res.reduce((map, obj) => {
map[obj.elementUuid] = {
id: obj.elementUuid,
type: obj.type,
name: obj.elementName,
};
return map;
}, {});
setContingencyList(
favoriteContingencyListUuids
.map((id) => mapCont[id])
.filter((item) => item !== undefined)
.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
);
})
.catch(() => {
snackError({
headerId: 'getContingencyListError',
});
});
} else {
setContingencyList([]);
}
}, [favoriteContingencyListUuids, snackError, props.open]);

function getSimulatedContingencyCountLabel() {
return simulatedContingencyCount != null ? simulatedContingencyCount : '...';
}

const handleAddFavorite = () => {
setFavoriteSelectorOpen(true);
};

const removeFromFavorites = useCallback(
(toRemove) => {
const toRemoveIdsSet = new Set(toRemove.map((e) => e.id));
saveFavorites(contingencyList.map((e) => e.id).filter((id) => !toRemoveIdsSet.has(id)));

setCheckedContingencyList((oldChecked) => oldChecked.filter((item) => !toRemoveIdsSet.has(item.id)));
},
[contingencyList, saveFavorites]
);

const addFavorites = (favorites) => {
if (favorites && favorites.length > 0) {
// avoid duplicates here
const newFavoriteIdsSet = new Set([...favoriteContingencyListUuids, ...favorites.map((item) => item.id)]);
saveFavorites(Array.from([...newFavoriteIdsSet]));
}
setFavoriteSelectorOpen(false);
};

const handleSecondaryAction = useCallback(
(item, isItemHovered) =>
isItemHovered && (
<IconButton
style={{
alignItems: 'end',
}}
edge="end"
onClick={(e) => {
e.stopPropagation();
removeFromFavorites([item]);
}}
size={'small'}
>
<DeleteIcon />
</IconButton>
),
[removeFromFavorites]
);

return (
<>
<Dialog open={props.open} onClose={handleClose} maxWidth={'sm'} fullWidth={true}>
<DialogTitle>
<Typography component="span" variant="h5">
<FormattedMessage id="ContingencyListsSelection" />
</Typography>
</DialogTitle>
<DialogContent>
<CheckBoxList
items={contingencyList || []}
getItemId={(v) => v.id}
getItemLabel={(v) => v.name}
selectedItems={checkedContingencyList}
onSelectionChange={setCheckedContingencyList}
secondaryAction={handleSecondaryAction}
onItemClick={(contingencyList) =>
setCheckedContingencyList((oldCheckedElements) => [
...toggleElementFromList(contingencyList, oldCheckedElements, (element) => element.id),
])
}
/>
<Alert variant="standard" severity="info">
<FormattedMessage
id="xContingenciesWillBeSimulated"
values={{
x: getSimulatedContingencyCountLabel(),
}}
/>
</Alert>
</DialogContent>
<DialogActions sx={{ justifyContent: 'center' }}>
{makeButton(handleClose, 'close', false)}
{makeButton(handleAddFavorite, 'AddContingencyList', false)}
{makeButton(
() => removeFromFavorites(checkedContingencyList),
'DeleteContingencyList',
checkedContingencyList.length === 0
)}
{makeButton(handleStart, 'Execute', simulatedContingencyCount === 0)}
</DialogActions>
</Dialog>
<DirectoryItemSelector
open={favoriteSelectorOpen}
onClose={addFavorites}
types={ElementType.CASE}
multiSelect={false}
title={intl.formatMessage({ id: 'ContingencyListsSelection' })}
/>
</>
);
};

CaseListSelector.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onStart: PropTypes.func.isRequired,
studyUuid: PropTypes.string,
currentNodeUuid: PropTypes.string,
};

export default CaseListSelector;
97 changes: 97 additions & 0 deletions src/components/dialogs/create-case-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2023, 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 { useSelector } from 'react-redux';
import { Grid } from '@mui/material';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup/dist/yup';
import {
CustomMuiDialog,
DescriptionField,
FieldConstants,
isObjectEmpty,
useConfidentialityWarning,
useSnackMessage,
} from '@gridsuite/commons-ui';

import yup from '../utils/yup-config';
import { AppState } from 'redux/reducer';

interface IFormData {
[FieldConstants.CASE_NAME]: string;
[FieldConstants.CASE_FILE]: File | null;
}

export interface CreateCaseDialogProps {
onClose: (e?: unknown, nextSelectedDirectoryId?: string | null) => void;
open: boolean;
}

const getCreateCaseDialogFormValidationDefaultValues = () => ({
[FieldConstants.CASE_NAME]: '',
[FieldConstants.CASE_FILE]: null,
});

const createCaseDialogFormValidationSchema = yup.object().shape({
[FieldConstants.CASE_NAME]: yup.string().trim().required('nameEmpty'),
[FieldConstants.CASE_FILE]: yup.mixed<File>().nullable().required(),
});
export default function CreateCaseDialog({ onClose, open }: Readonly<CreateCaseDialogProps>) {
const { snackError } = useSnackMessage();
const confidentialityWarningKey = useConfidentialityWarning();

const createCaseFormMethods = useForm<IFormData>({
defaultValues: getCreateCaseDialogFormValidationDefaultValues(),
resolver: yupResolver<IFormData>(createCaseDialogFormValidationSchema),
});

const {
formState: { errors, isValid },
} = createCaseFormMethods;

const isFormValid = isObjectEmpty(errors) && isValid;

const userId = useSelector((state: AppState) => state.user?.profile.sub);

const handleCreateNewCase = ({ caseName, caseFile }: IFormData): void => {
// @ts-expect-error TODO: manage null cases here
createCase(caseName, description ?? '', caseFile, activeDirectory)
.then(onClose)
.catch((err: any) => {
console.log('$$$$$');
});
};

return (
<CustomMuiDialog
titleId="CreateRootNetwork"
formSchema={createCaseDialogFormValidationSchema}
formMethods={createCaseFormMethods}
removeOptional
open={open}
onClose={onClose}
onSave={handleCreateNewCase}
disabledSave={!isFormValid}
confirmationMessageKey={confidentialityWarningKey}
>
<Grid container spacing={2} marginTop="auto" direction="column">
<Grid item>
{/* <PrefilledNameInput
name={FieldConstants.CASE_NAME}
label="nameProperty"
elementType={ElementType.CASE}
/> */}
</Grid>
<Grid item>
<DescriptionField />
</Grid>
</Grid>
{/* <ErrorInput name={FieldConstants.CASE_FILE} InputField={FieldErrorAlert} /> */}
{/* <UploadNewCase /> */}
</CustomMuiDialog>
);
}
Loading
Loading