Skip to content
Merged
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
Expand Up @@ -5,22 +5,23 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { FC, MouseEvent, useCallback, useState } from 'react';
import { Button, Menu, MenuItem, Theme, Tooltip } from '@mui/material';
import { useCallback, useState } from 'react';
import { Button, type ButtonProps, Menu, MenuItem, Theme, Tooltip } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import { useStateBoolean, UseStateBooleanReturn } from '@gridsuite/commons-ui';
import { useStateBoolean } from '@gridsuite/commons-ui';
import { FormattedMessage } from 'react-intl';
import { SpreadsheetTabDefinition } from '../types/spreadsheet.type';
import { ResetNodeAliasCallback } from '../hooks/use-node-aliases';
import AddEmptySpreadsheetDialog from './dialogs/add-empty-spreadsheet-dialog';
import AddSpreadsheetFromModelDialog from './dialogs/add-spreadsheet-from-model-dialog';
import AddSpreadsheetsFromCollectionDialog from './dialogs/add-spreadsheets-from-collection-dialog';
import type { DialogComponent } from './types';

interface AddSpreadsheetButtonProps {
export type AddSpreadsheetButtonProps = {
disabled: boolean;
resetTabIndex: (newTablesDefinitions: SpreadsheetTabDefinition[]) => void;
resetNodeAliases: ResetNodeAliasCallback;
}
};

const styles = {
addButton: (theme: Theme) => ({
Expand All @@ -29,12 +30,6 @@ const styles = {
}),
};

type DialogComponent = FC<{
open: UseStateBooleanReturn;
resetTabIndex: (newTablesDefinitions: SpreadsheetTabDefinition[]) => void;
resetNodeAliases: ResetNodeAliasCallback;
}>;

export interface SpreadsheetOption {
id: string;
label: string;
Expand All @@ -44,7 +39,7 @@ export interface SpreadsheetOption {
/**
* Constants for spreadsheet creation options with associated dialog components
*/
const NEW_SPREADSHEET_CREATION_OPTIONS: Record<string, SpreadsheetOption> = {
const NEW_SPREADSHEET_CREATION_OPTIONS = {
EMPTY: {
id: 'EMPTY',
label: 'spreadsheet/create_new_spreadsheet/empty_spreadsheet_option',
Expand All @@ -60,20 +55,23 @@ const NEW_SPREADSHEET_CREATION_OPTIONS: Record<string, SpreadsheetOption> = {
label: 'spreadsheet/create_new_spreadsheet/apply_collection_option',
dialog: AddSpreadsheetsFromCollectionDialog,
},
};
} as const satisfies Record<string, SpreadsheetOption>;

const AddSpreadsheetButton: React.FC<AddSpreadsheetButtonProps> = ({ disabled, resetTabIndex, resetNodeAliases }) => {
export default function AddSpreadsheetButton({
disabled,
resetTabIndex,
resetNodeAliases,
}: Readonly<AddSpreadsheetButtonProps>) {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const dialogOpen = useStateBoolean(false);
const [selectedOption, setSelectedOption] = useState<SpreadsheetOption | undefined>();

const handleClick = useCallback((event: MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
}, []);
const handleClick = useCallback<NonNullable<ButtonProps['onClick']>>(
(event) => setAnchorEl(event.currentTarget),
[]
);

const handleClose = useCallback(() => {
setAnchorEl(null);
}, []);
const handleClose = useCallback(() => setAnchorEl(null), []);

const handleMenuItemClick = useCallback(
(option: SpreadsheetOption) => {
Expand Down Expand Up @@ -102,12 +100,9 @@ const AddSpreadsheetButton: React.FC<AddSpreadsheetButtonProps> = ({ disabled, r
</MenuItem>
))}
</Menu>

{SelectedDialog && (
<SelectedDialog open={dialogOpen} resetTabIndex={resetTabIndex} resetNodeAliases={resetNodeAliases} />
)}
</>
);
};

export default AddSpreadsheetButton;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,46 @@

import { useCallback, useEffect, useMemo } from 'react';
import { Grid } from '@mui/material';
import {
CustomFormProvider,
EquipmentType,
SelectInput,
TextInput,
UseStateBooleanReturn,
useSnackMessage,
} from '@gridsuite/commons-ui';
import { CustomFormProvider, SelectInput, TextInput, useSnackMessage } from '@gridsuite/commons-ui';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { useDispatch, useSelector } from 'react-redux';
import { EQUIPMENT_TYPE_FIELD } from 'components/utils/field-constants';
import { AppState } from 'redux/reducer';
import { UUID } from 'crypto';
import { dialogStyles } from '../styles/styles';
import { ModificationDialog } from 'components/dialogs/commons/modificationDialog';
import { getEmptySpreadsheetFormSchema, initialEmptySpreadsheetForm, SPREADSHEET_NAME } from './add-spreadsheet-form';
import { ModificationDialog, type ModificationDialogProps } from 'components/dialogs/commons/modificationDialog';
import {
type EmptySpreadsheetForm,
getEmptySpreadsheetFormSchema,
initialEmptySpreadsheetForm,
SPREADSHEET_NAME,
} from './add-spreadsheet-form';
import { addNewSpreadsheet } from './add-spreadsheet-utils';
import { COLUMN_TYPES } from 'components/custom-aggrid/custom-aggrid-header.type';
import { ColumnDefinitionDto } from '../../types/spreadsheet.type';
import { ColumnDefinitionDto, SpreadsheetEquipmentType } from '../../types/spreadsheet.type';
import { v4 as uuid4 } from 'uuid';
import type { DialogComponentProps } from '../types';

interface AddEmptySpreadsheetDialogProps {
open: UseStateBooleanReturn;
}
export type AddEmptySpreadsheetDialogProps = Pick<DialogComponentProps, 'open'>;

const TABLES_TYPES = [
EquipmentType.SUBSTATION,
EquipmentType.VOLTAGE_LEVEL,
EquipmentType.LINE,
EquipmentType.TWO_WINDINGS_TRANSFORMER,
EquipmentType.THREE_WINDINGS_TRANSFORMER,
EquipmentType.GENERATOR,
EquipmentType.LOAD,
EquipmentType.SHUNT_COMPENSATOR,
EquipmentType.STATIC_VAR_COMPENSATOR,
EquipmentType.BATTERY,
EquipmentType.HVDC_LINE,
EquipmentType.LCC_CONVERTER_STATION,
EquipmentType.VSC_CONVERTER_STATION,
EquipmentType.TIE_LINE,
EquipmentType.DANGLING_LINE,
EquipmentType.BUS,
EquipmentType.BUSBAR_SECTION,
];
const TABLES_OPTIONS = Object.values(SpreadsheetEquipmentType).map(
(elementType) => ({ id: elementType, label: elementType }) as const
);

const DEFAULT_ID_COLUMN: ColumnDefinitionDto = {
const DEFAULT_ID_COLUMN = {
uuid: uuid4() as UUID,
name: 'ID',
id: 'id',
type: COLUMN_TYPES.TEXT,
formula: 'id',
visible: true,
};
} as const satisfies ColumnDefinitionDto;

/**
* Dialog for creating an empty spreadsheet
*/
export default function AddEmptySpreadsheetDialog({ open, ...dialogProps }: Readonly<AddEmptySpreadsheetDialogProps>) {
export default function AddEmptySpreadsheetDialog({ open }: Readonly<AddEmptySpreadsheetDialogProps>) {
const dispatch = useDispatch();
const { snackError } = useSnackMessage();
const studyUuid = useSelector((state: AppState) => state.studyUuid);
Expand All @@ -88,21 +69,17 @@ export default function AddEmptySpreadsheetDialog({ open, ...dialogProps }: Read
reset(initialEmptySpreadsheetForm);
}, [open.value, reset]);

const onSubmit = useCallback(
(formData: any) => {
const onSubmit = useCallback<ModificationDialogProps<EmptySpreadsheetForm>['onSave']>(
(formData) => {
if (!studyUuid) {
return;
}
const tabIndex = tablesDefinitions.length;
const tabName = formData[SPREADSHEET_NAME];
const equipmentType = formData.equipmentType;

addNewSpreadsheet({
studyUuid,
columns: [DEFAULT_ID_COLUMN],
sheetType: equipmentType,
tabIndex,
tabName,
sheetType: formData.equipmentType,
tabIndex: tablesDefinitions.length,
tabName: formData[SPREADSHEET_NAME],
spreadsheetsCollectionUuid: spreadsheetsCollectionUuid as UUID,
dispatch,
snackError,
Expand All @@ -119,9 +96,8 @@ export default function AddEmptySpreadsheetDialog({ open, ...dialogProps }: Read
open={open.value}
onClose={open.setFalse}
onSave={onSubmit}
onClear={() => null}
onClear={() => {}}
PaperProps={{ sx: dialogStyles.dialogContent }}
{...dialogProps}
>
<Grid container spacing={2} direction="column" marginTop="auto">
<Grid item xs>
Expand All @@ -133,10 +109,7 @@ export default function AddEmptySpreadsheetDialog({ open, ...dialogProps }: Read
</Grid>
<Grid item xs>
<SelectInput
options={Object.values(TABLES_TYPES).map((elementType) => ({
id: elementType,
label: elementType,
}))}
options={TABLES_OPTIONS}
name={EQUIPMENT_TYPE_FIELD}
label="spreadsheet/create_new_spreadsheet/element_type"
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { EQUIPMENT_TYPE_FIELD, ID, NAME } from 'components/utils/field-constants';
import yup from '../../../utils/yup-config';
import { SpreadsheetEquipmentType } from '../../types/spreadsheet.type';

export const SPREADSHEET_NAME = 'spreadsheetName';
export const SPREADSHEET_MODEL = 'spreadsheetModel';
Expand All @@ -15,13 +16,14 @@ export const SPREADSHEET_COLLECTION_IMPORT_MODE = 'spreadsheetCollectionMode';

export const initialEmptySpreadsheetForm: EmptySpreadsheetForm = {
[SPREADSHEET_NAME]: '',
//@ts-expect-error TS2418: Type of computed property's value is '', which is not assignable to type NonNullable<SpreadsheetEquipmentType | undefined>
[EQUIPMENT_TYPE_FIELD]: '',
};
} as const;

export const initialSpreadsheetFromModelForm: SpreadsheetFromModelForm = {
[SPREADSHEET_NAME]: '',
[SPREADSHEET_MODEL]: [],
};
} as const;

export enum SpreadsheetCollectionImportMode {
REPLACE = 'REPLACE',
Expand All @@ -33,28 +35,28 @@ export const initialSpreadsheetCollectionForm: SpreadsheetCollectionForm = {
[SPREADSHEET_COLLECTION_IMPORT_MODE]: SpreadsheetCollectionImportMode.REPLACE,
};

export const getEmptySpreadsheetFormSchema = (tablesNames: string[]) => {
function schemaSpreadsheetName(tablesNames: string[]) {
return yup
.string()
.required()
.max(60, 'spreadsheet/spreadsheet_name_le_60')
.test(
'unique',
'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists',
(value) => !tablesNames.includes(value || '')
);
}

export function getEmptySpreadsheetFormSchema(tablesNames: string[]) {
return yup.object().shape({
[SPREADSHEET_NAME]: yup
.string()
.required()
.max(60, 'spreadsheet/spreadsheet_name_le_60')
.test('unique', 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists', (value) => {
return !tablesNames.includes(value || '');
}),
[EQUIPMENT_TYPE_FIELD]: yup.string().required(),
[SPREADSHEET_NAME]: schemaSpreadsheetName(tablesNames),
[EQUIPMENT_TYPE_FIELD]: yup.string().oneOf(Object.values(SpreadsheetEquipmentType)).required(),
});
};
}

export const getSpreadsheetFromModelFormSchema = (tablesNames: string[]) => {
export function getSpreadsheetFromModelFormSchema(tablesNames: string[]) {
return yup.object().shape({
[SPREADSHEET_NAME]: yup
.string()
.required()
.max(60, 'spreadsheet/spreadsheet_name_le_60')
.test('unique', 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists', (value) => {
return !tablesNames.includes(value || '');
}),
[SPREADSHEET_NAME]: schemaSpreadsheetName(tablesNames),
[SPREADSHEET_MODEL]: yup
.array()
.of(
Expand All @@ -67,7 +69,7 @@ export const getSpreadsheetFromModelFormSchema = (tablesNames: string[]) => {
.min(1, 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_model')
.max(1, 'spreadsheet/create_new_spreadsheet/must_select_only_one_spreadsheet_model'),
});
};
}

export const getSpreadsheetCollectionFormSchema = () => {
return yup.object().shape({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,33 @@ import {
DirectoryItemsInput,
ElementType,
TextInput,
UseStateBooleanReturn,
useSnackMessage,
} from '@gridsuite/commons-ui';
import { useForm, useWatch } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'redux/reducer';
import {
SPREADSHEET_MODEL,
SPREADSHEET_NAME,
getSpreadsheetFromModelFormSchema,
initialSpreadsheetFromModelForm,
SPREADSHEET_MODEL,
SPREADSHEET_NAME,
} from './add-spreadsheet-form';
import { addNewSpreadsheet } from './add-spreadsheet-utils';
import { getSpreadsheetModel } from 'services/study-config';
import { UUID } from 'crypto';
import { ModificationDialog } from 'components/dialogs/commons/modificationDialog';
import { dialogStyles } from '../styles/styles';
import { ResetNodeAliasCallback } from '../../hooks/use-node-aliases';
import type { DialogComponentProps } from '../types';

interface AddSpreadsheetFromModelDialogProps {
open: UseStateBooleanReturn;
resetNodeAliases: ResetNodeAliasCallback;
}
export type AddSpreadsheetFromModelDialogProps = Pick<DialogComponentProps, 'open' | 'resetNodeAliases'>;

/**
* Dialog for creating a spreadsheet from an existing model
*/
export default function AddSpreadsheetFromModelDialog({
open,
resetNodeAliases,
...dialogProps
}: Readonly<AddSpreadsheetFromModelDialogProps>) {
const dispatch = useDispatch();
const { snackError } = useSnackMessage();
Expand Down Expand Up @@ -126,7 +121,6 @@ export default function AddSpreadsheetFromModelDialog({
onSave={onSubmit}
onClear={() => null}
PaperProps={{ sx: dialogStyles.dialogContent }}
{...dialogProps}
>
<Grid container spacing={2} direction="column" marginTop="auto">
<Grid item xs>
Expand Down
Loading
Loading