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

Custom and dynamic columns in spreadsheet #2272

Merged
merged 13 commits into from
Oct 2, 2024
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"eventemitter3": "^5.0.1",
"localized-countries": "^2.0.0",
"lucene-escape-query": "^1.0.1",
"mathjs": "^13.1.1",
"mjolnir.js": "^2.7.1",
"mui-nested-menu": "^3.3.0",
"notistack": "^3.0.1",
Expand Down
4 changes: 4 additions & 0 deletions src/components/app-wrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import errors_locale_en from '../translations/dynamic/errors-locale-en';
import errors_locale_fr from '../translations/dynamic/errors-locale-fr';
import events_locale_fr from '../translations/dynamic/events-locale-fr';
import events_locale_en from '../translations/dynamic/events-locale-en';
import spreadsheet_locale_fr from '../translations/spreadsheet-fr';
import spreadsheet_locale_en from '../translations/spreadsheet-en';
import { store } from '../redux/store';
import CssBaseline from '@mui/material/CssBaseline';
import {
Expand Down Expand Up @@ -267,6 +269,7 @@ const messages = {
...table_locale_en,
...errors_locale_en,
...events_locale_en,
...spreadsheet_locale_en,
...messages_plugins.en, // keep it at the end to allow translation overwriting
},
fr: {
Expand Down Expand Up @@ -295,6 +298,7 @@ const messages = {
...table_locale_fr,
...errors_locale_fr,
...events_locale_fr,
...spreadsheet_locale_fr,
...messages_plugins.fr, // keep it at the end to allow translation overwriting
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/custom-aggrid/custom-aggrid-header.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export enum FILTER_NUMBER_COMPARATORS {
GREATER_THAN = 'greaterThan',
}

type FilterParams = {
export type FilterParams = {
filterDataType?: string;
isDuration?: boolean;
filterComparators?: string[];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright © 2024, 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 { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Badge, Box } from '@mui/material';
import { LoadingButton } from '@mui/lab';
import { Calculate as CalculateIcon } from '@mui/icons-material';
import { useSelector } from 'react-redux';
import { TABLES_NAMES } from '../utils/config-tables';
import { AppState } from '../../../redux/reducer';
import CustomColumnDialog from './custom-columns-dialog';
import { useStateBoolean, useStateNumber } from '@gridsuite/commons-ui';

export type CustomColumnsConfigProps = {
indexTab: number;
};

export default function CustomColumnsConfig({ indexTab }: Readonly<CustomColumnsConfigProps>) {
const formulaCalculating = useStateBoolean(false);
const formulaError = useStateBoolean(false);
const numberColumns = useStateNumber(0);
const dialogOpen = useStateBoolean(false);
const allDefinitions = useSelector((state: AppState) => state.allCustomColumnsDefinitions[TABLES_NAMES[indexTab]]);
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
const numberColumns = useStateNumber(0);
const dialogOpen = useStateBoolean(false);
const allDefinitions = useSelector((state: AppState) => state.allCustomColumnsDefinitions[TABLES_NAMES[indexTab]]);
const dialogOpen = useStateBoolean(false);
const customColumnsNumber = useSelector(
(state: AppState) => state.allCustomColumnsDefinitions[TABLES_NAMES[indexTab]].columns.length
);

We can do this directly, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm passing this value in the props and removed the useSelector in the child component


useEffect(() => {
numberColumns.setValue(allDefinitions.columns.length);
}, [allDefinitions.columns.length, numberColumns]);

/* eslint-enable react-hooks/rules-of-hooks */
return (
<>
<LoadingButton
variant="text"
color={formulaError.value ? 'error' : 'inherit'}
endIcon={
<Badge
color="secondary"
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
max={9}
badgeContent={numberColumns.value}
>
<CalculateIcon />
</Badge>
}
loadingPosition="start"
loading={formulaCalculating.value}
onClick={dialogOpen.setTrue}
>
<FormattedMessage id="spreadsheet/custom_column/main_button">
{(txt) => (
<Box component="span" data-note="anti-translate-crash">
{txt}
</Box>
)}
</FormattedMessage>
</LoadingButton>
<CustomColumnDialog indexTab={indexTab} open={dialogOpen} />
</>
);
}
104 changes: 104 additions & 0 deletions src/components/spreadsheet/custom-columns/custom-columns-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright © 2024, 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 } from 'react';
import { useIntl } from 'react-intl';
import { Box, Dialog, DialogActions, DialogContent, DialogTitle, Grid, SxProps, Theme } from '@mui/material';
import { CancelButton, CustomFormProvider, SubmitButton, UseStateBooleanReturn } from '@gridsuite/commons-ui';
import { useForm } from 'react-hook-form';
import {
CustomColumnForm,
customColumnFormSchema,
initialCustomColumnForm,
TAB_CUSTOM_COLUMN,
} from './custom-columns-form';

import { yupResolver } from '@hookform/resolvers/yup';
import CustomColumnTable from './custom-columns-table';
import { setCustomColumDefinitions } from 'redux/actions';
import { TABLES_NAMES } from '../utils/config-tables';
import { useDispatch } from 'react-redux';
import { AppDispatch } from 'redux/store';
import { useSelector } from 'react-redux';
import { AppState } from 'redux/reducer';

export type CustomColumnDialogProps = {
open: UseStateBooleanReturn;
indexTab: number;
};

const styles = {
dialogContent: {
width: '50%',
height: '60%',
maxWidth: 'none',
margin: 'auto',
},
actionButtons: { display: 'flex', gap: 2, justifyContent: 'end' },
} as const satisfies Record<string, SxProps<Theme>>;

export default function CustomColumnDialog({ open, indexTab }: Readonly<CustomColumnDialogProps>) {
const formMethods = useForm({
defaultValues: initialCustomColumnForm,
resolver: yupResolver(customColumnFormSchema),
});

const { handleSubmit, reset } = formMethods;
const dispatch = useDispatch<AppDispatch>();
const columnsDefinitions = useSelector((state: AppState) => state.allCustomColumnsDefinitions);
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
const columnsDefinitions = useSelector((state: AppState) => state.allCustomColumnsDefinitions);
const columnsDefinitions = useSelector(
(state: AppState) => state.allCustomColumnsDefinitions[TABLES_NAMES[indexTab]]?.columns
);

Also directly this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now it's retrieved from the props


const intl = useIntl();

const onSubmit = useCallback(
(newParams: CustomColumnForm) => {
dispatch(setCustomColumDefinitions(TABLES_NAMES[indexTab], newParams[TAB_CUSTOM_COLUMN]));
reset(initialCustomColumnForm);
open.setFalse();
},

[dispatch, indexTab, open, reset]
);

useEffect(() => {
if (open.value && columnsDefinitions[TABLES_NAMES[indexTab]]?.columns.length !== 0) {
reset({
[TAB_CUSTOM_COLUMN]: [...columnsDefinitions[TABLES_NAMES[indexTab]].columns],
});
} else {
reset(initialCustomColumnForm);
}
}, [columnsDefinitions, indexTab, open.value, reset]);

return (
<CustomFormProvider validationSchema={customColumnFormSchema} {...formMethods}>
<Dialog
id="custom-column-dialog-edit"
open={open.value}
onClose={open.setFalse}
aria-labelledby="custom-column-dialog-edit-title"
PaperProps={{ sx: styles.dialogContent }}
>
<DialogTitle id="custom-column-dialog-edit-title">
{intl.formatMessage({ id: 'spreadsheet/custom_column/main_button' })}
</DialogTitle>
<DialogContent dividers>
<CustomColumnTable />
</DialogContent>
<DialogActions>
<Grid container spacing={0.5}>
<Grid item xs>
<Box sx={styles.actionButtons}>
<CancelButton onClick={open.setFalse} />
<SubmitButton onClick={handleSubmit(onSubmit)} variant="outlined" />
</Box>
</Grid>
</Grid>
</DialogActions>
</Dialog>
</CustomFormProvider>
);
}
39 changes: 39 additions & 0 deletions src/components/spreadsheet/custom-columns/custom-columns-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2024, 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 yup from '../../../components/utils/yup-config';

export const TAB_CUSTOM_COLUMN = 'TAB_CUSTOM_COLUMN';
export const COLUMN_NAME = 'name';
export const FORMULA = 'formula';

export const initialCustomColumnForm: CustomColumnForm = {
[TAB_CUSTOM_COLUMN]: [
{
[COLUMN_NAME]: '',
[FORMULA]: '',
},
],
};

export const customColumnFormSchema = yup.object().shape({
[TAB_CUSTOM_COLUMN]: yup
.array()
.of(
yup.object().shape({
[COLUMN_NAME]: yup
.string()
.required()
.matches(/^[^\s$]+$/, 'Column name must not contain spaces or $ symbols'),
[FORMULA]: yup.string().required(),
})
)
.required()
.min(1, 'The array must have at least one item'),
});

export type CustomColumnForm = yup.InferType<typeof customColumnFormSchema>;
81 changes: 81 additions & 0 deletions src/components/spreadsheet/custom-columns/custom-columns-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright © 2024, 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 DndTable from 'components/utils/dnd-table/dnd-table';
import { COLUMN_NAME, FORMULA, TAB_CUSTOM_COLUMN } from './custom-columns-form';
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import { useFieldArray } from 'react-hook-form';
import { IconButton, Tooltip } from '@mui/material';
import InfoIcon from '@mui/icons-material/Info';

export default function CustomColumnTable() {
const DndTableTyped = DndTable as React.ComponentType<any>;
const intl = useIntl();
const CustomColumnTooltip = useMemo(() => {
return (
<Tooltip
title={intl.formatMessage({
id: 'spreadsheet/custom_column/column_content_tooltip',
})}
>
<IconButton>
<InfoIcon />
</IconButton>
</Tooltip>
);
}, [intl]);

const CUSTOM_COLUMNS_DEFINITIONS = useMemo(() => {
return [
{
label: 'spreadsheet/custom_column/column_name',
dataKey: COLUMN_NAME,
initialValue: null,
editable: true,
titleId: 'FiltersListsSelection',
width: '30%',
},
{
label: 'spreadsheet/custom_column/column_content',
dataKey: FORMULA,
initialValue: null,
editable: true,
extra: CustomColumnTooltip,
width: '70%',
},
].map((column) => ({
...column,
label: intl
.formatMessage({ id: column.label })
.toLowerCase()
.replace(/^\w/, (c) => c.toUpperCase()),
}));
}, [CustomColumnTooltip, intl]);

const useTabCustomColumnFieldArrayOutput = useFieldArray({
name: `${TAB_CUSTOM_COLUMN}`,
TheMaskedTurtle marked this conversation as resolved.
Show resolved Hide resolved
});

const newCustomColumnRowData = useMemo(() => {
const newRowData: any = {};
CUSTOM_COLUMNS_DEFINITIONS.forEach((column: any) => (newRowData[column.dataKey] = column.initialValue));
return newRowData;
}, [CUSTOM_COLUMNS_DEFINITIONS]);

const createCustomColumnRows = () => [newCustomColumnRowData];
return (
<DndTableTyped
arrayFormName={`${TAB_CUSTOM_COLUMN}`}
TheMaskedTurtle marked this conversation as resolved.
Show resolved Hide resolved
columnsDefinition={CUSTOM_COLUMNS_DEFINITIONS}
useFieldArrayOutput={useTabCustomColumnFieldArrayOutput}
createRows={createCustomColumnRows}
tableHeight={380}
withAddRowsDialog={false}
withLeftButtons={false}
/>
);
}
Loading
Loading