-
Notifications
You must be signed in to change notification settings - Fork 3
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
Changes from 7 commits
406b7f7
75c0498
b8e22af
2e0cad5
dd16e04
c15d8b9
f770e02
d40fca3
0035c25
a81da8c
0a83c19
d856fc2
f2d1993
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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]]); | ||
|
||
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} /> | ||
</> | ||
); | ||
} |
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); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Also directly this ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||||||||||
); | ||||||||||
} |
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>; |
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} | ||
/> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do this directly, no ?
There was a problem hiding this comment.
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