Skip to content

Commit 77a6a80

Browse files
Fix contingency CustomAgGridTable types
1 parent 615af38 commit 77a6a80

File tree

3 files changed

+112
-101
lines changed

3 files changed

+112
-101
lines changed

src/components/inputs/reactHookForm/agGridTable/BottomRightButtons.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import IconButton from '@mui/material/IconButton';
99
import { ArrowCircleDown, ArrowCircleUp, Upload } from '@mui/icons-material';
1010
import AddIcon from '@mui/icons-material/ControlPoint';
1111
import DeleteIcon from '@mui/icons-material/Delete';
12-
import { useState } from 'react';
12+
import { useCallback, useState } from 'react';
1313
import { useIntl } from 'react-intl';
1414
import { styled } from '@mui/material/styles';
1515
import { FieldValues, UseFieldArrayReturn } from 'react-hook-form';
1616
import ErrorInput from '../errorManagement/ErrorInput';
1717
import FieldErrorAlert from '../errorManagement/FieldErrorAlert';
18-
import CsvUploader from './csvUploader/CsvUploader';
18+
import CsvUploader, { CsvUploaderProps } from './csvUploader/CsvUploader';
1919

2020
const InnerColoredButton = styled(IconButton)(({ theme }) => {
2121
return {
@@ -33,7 +33,7 @@ export interface BottomRightButtonsProps {
3333
handleMoveRowUp: () => void;
3434
handleMoveRowDown: () => void;
3535
useFieldArrayOutput: UseFieldArrayReturn<FieldValues, string, 'id'>;
36-
csvProps: any;
36+
csvProps: Omit<CsvUploaderProps, 'open' | 'onClose' | 'name' | 'useFieldArrayOutput'>;
3737
}
3838

3939
function BottomRightButtons({
@@ -47,7 +47,7 @@ function BottomRightButtons({
4747
handleMoveRowDown,
4848
useFieldArrayOutput,
4949
csvProps,
50-
}: BottomRightButtonsProps) {
50+
}: Readonly<BottomRightButtonsProps>) {
5151
const [uploaderOpen, setUploaderOpen] = useState(false);
5252
const intl = useIntl();
5353

@@ -88,7 +88,7 @@ function BottomRightButtons({
8888
</Grid>
8989
<CsvUploader
9090
open={uploaderOpen}
91-
onClose={() => setUploaderOpen(false)}
91+
onClose={useCallback(() => setUploaderOpen(false), [])}
9292
name={name}
9393
useFieldArrayOutput={useFieldArrayOutput}
9494
{...csvProps}

src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx

Lines changed: 84 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@ import 'ag-grid-community/styles/ag-grid.css';
1212
import 'ag-grid-community/styles/ag-theme-alpine.css';
1313
import { Grid, useTheme } from '@mui/material';
1414
import { useIntl } from 'react-intl';
15-
import { CellEditingStoppedEvent, ColumnState, SortChangedEvent } from 'ag-grid-community';
16-
import BottomRightButtons from './BottomRightButtons';
15+
import { ColDef, GridApi, GridOptions } from 'ag-grid-community';
16+
import BottomRightButtons, { BottomRightButtonsProps } from './BottomRightButtons';
1717
import FieldConstants from '../../../../utils/constants/fieldConstants';
1818

19+
type AgGridFn<TFn extends keyof GridOptions, TData> = NonNullable<GridOptions<TData>[TFn]>;
20+
1921
export const ROW_DRAGGING_SELECTION_COLUMN_DEF = [
2022
{
2123
rowDrag: true,
2224
headerCheckboxSelection: true,
2325
checkboxSelection: true,
2426
maxWidth: 50,
2527
},
26-
];
28+
] as const satisfies Readonly<ColDef[]>;
2729

28-
const style = (customProps: any) => ({
30+
const style = (customProps: object = {}) => ({
2931
grid: (theme: any) => ({
3032
width: 'auto',
3133
height: '100%',
@@ -83,21 +85,23 @@ const style = (customProps: any) => ({
8385
}),
8486
});
8587

86-
export interface CustomAgGridTableProps {
88+
export interface CustomAgGridTableProps<TData, TValue> {
8789
name: string;
88-
columnDefs: any;
89-
makeDefaultRowData: any;
90-
csvProps: unknown;
91-
cssProps: unknown;
92-
defaultColDef: unknown;
90+
columnDefs: ColDef<TData, TValue>[];
91+
makeDefaultRowData: () => unknown;
92+
csvProps: BottomRightButtonsProps['csvProps'];
93+
cssProps?: object;
94+
defaultColDef: GridOptions<TData>['defaultColDef'];
9395
pagination: boolean;
9496
paginationPageSize: number;
9597
suppressRowClickSelection: boolean;
9698
alwaysShowVerticalScroll: boolean;
9799
stopEditingWhenCellsLoseFocus: boolean;
98100
}
99101

100-
function CustomAgGridTable({
102+
// TODO: rename ContingencyAgGridTable
103+
// TODO: used only once in gridexplore, move to gridexplore?
104+
function CustomAgGridTable<TData = unknown, TValue = unknown>({
101105
name,
102106
columnDefs,
103107
makeDefaultRowData,
@@ -109,11 +113,10 @@ function CustomAgGridTable({
109113
suppressRowClickSelection,
110114
alwaysShowVerticalScroll,
111115
stopEditingWhenCellsLoseFocus,
112-
...props
113-
}: CustomAgGridTableProps) {
116+
}: Readonly<CustomAgGridTableProps<TData, TValue>>) {
114117
const theme: any = useTheme();
115-
const [gridApi, setGridApi] = useState<any>(null);
116-
const [selectedRows, setSelectedRows] = useState([]);
118+
const [gridApi, setGridApi] = useState<GridApi<TData>>();
119+
const [selectedRows, setSelectedRows] = useState<TData[]>([]);
117120
const [newRowAdded, setNewRowAdded] = useState(false);
118121
const [isSortApplied, setIsSortApplied] = useState(false);
119122

@@ -124,15 +127,15 @@ function CustomAgGridTable({
124127
});
125128
const { append, remove, update, swap, move } = useFieldArrayOutput;
126129

127-
const rowData = watch(name);
130+
const rowData = watch(name); // TODO: use correct types for useFormContext<...>()
128131

129132
const isFirstSelected = Boolean(
130-
rowData?.length && gridApi?.api.getRowNode(rowData[0][FieldConstants.AG_GRID_ROW_UUID])?.isSelected()
133+
rowData?.length && gridApi?.getRowNode(rowData[0][FieldConstants.AG_GRID_ROW_UUID])?.isSelected()
131134
);
132135

133136
const isLastSelected = Boolean(
134137
rowData?.length &&
135-
gridApi?.api.getRowNode(rowData[rowData.length - 1][FieldConstants.AG_GRID_ROW_UUID])?.isSelected()
138+
gridApi?.getRowNode(rowData[rowData.length - 1][FieldConstants.AG_GRID_ROW_UUID])?.isSelected()
136139
);
137140

138141
const noRowSelected = selectedRows.length === 0;
@@ -146,26 +149,26 @@ function CustomAgGridTable({
146149
[getValues, name]
147150
);
148151

149-
const handleMoveRowUp = () => {
152+
const handleMoveRowUp = useCallback(() => {
150153
selectedRows
151-
.map((row) => getIndex(row))
154+
.map(getIndex)
152155
.sort()
153156
.forEach((idx) => {
154157
swap(idx, idx - 1);
155158
});
156-
};
159+
}, [getIndex, selectedRows, swap]);
157160

158-
const handleMoveRowDown = () => {
161+
const handleMoveRowDown = useCallback(() => {
159162
selectedRows
160-
.map((row) => getIndex(row))
163+
.map(getIndex)
161164
.sort()
162165
.reverse()
163166
.forEach((idx) => {
164167
swap(idx, idx + 1);
165168
});
166-
};
169+
}, [getIndex, selectedRows, swap]);
167170

168-
const handleDeleteRows = () => {
171+
const handleDeleteRows = useCallback(() => {
169172
if (selectedRows.length === rowData.length) {
170173
remove();
171174
} else {
@@ -174,52 +177,59 @@ function CustomAgGridTable({
174177
remove(idx);
175178
});
176179
}
177-
};
178-
179-
useEffect(() => {
180-
if (gridApi) {
181-
gridApi.api.refreshCells({
182-
force: true,
183-
});
184-
}
185-
}, [gridApi, rowData]);
180+
}, [getIndex, remove, rowData.length, selectedRows]);
186181

187-
const handleAddRow = () => {
182+
const handleAddRow = useCallback(() => {
188183
append(makeDefaultRowData());
189184
setNewRowAdded(true);
190-
};
185+
}, [append, makeDefaultRowData]);
191186

192187
useEffect(() => {
193-
if (gridApi) {
194-
gridApi.api.sizeColumnsToFit();
195-
}
188+
gridApi?.refreshCells({
189+
force: true,
190+
});
191+
}, [gridApi, rowData]);
192+
193+
useEffect(() => {
194+
gridApi?.sizeColumnsToFit();
196195
}, [columnDefs, gridApi]);
197196

198197
const intl = useIntl();
199-
const getLocaleText = useCallback(
200-
(params: any) => {
201-
const key = `agGrid.${params.key}`;
202-
return intl.messages[key] || params.defaultValue;
203-
},
198+
const getLocaleText = useCallback<AgGridFn<'getLocaleText', TData>>(
199+
(params) => intl.formatMessage({ id: `agGrid.${params.key}`, defaultMessage: params.defaultValue }),
204200
[intl]
205201
);
206202

207-
const onGridReady = (params: any) => {
208-
setGridApi(params);
209-
};
203+
const onGridReady = useCallback<AgGridFn<'onGridReady', TData>>((event) => {
204+
setGridApi(event.api);
205+
}, []);
206+
207+
const onRowDragEnd = useCallback<AgGridFn<'onRowDragEnd', TData>>(
208+
(e) => move(getIndex(e.node.data), e.overIndex),
209+
[getIndex, move]
210+
);
211+
212+
const onSelectionChanged = useCallback<AgGridFn<'onSelectionChanged', TData>>(
213+
// @ts-expect-error TODO manage null api case (not possible at runtime?)
214+
() => setSelectedRows(gridApi.getSelectedRows()),
215+
[gridApi]
216+
);
210217

211-
const onRowDataUpdated = () => {
212-
setNewRowAdded(false);
213-
if (gridApi?.api) {
214-
// update due to new appended row, let's scroll
215-
const lastIndex = rowData.length - 1;
216-
gridApi.api.paginationGoToLastPage();
217-
gridApi.api.ensureIndexVisible(lastIndex, 'bottom');
218-
}
219-
};
218+
const onRowDataUpdated = useCallback<AgGridFn<'onRowDataUpdated', TData>>(
219+
(/* event */) => {
220+
setNewRowAdded(false);
221+
if (gridApi) {
222+
// update due to new appended row, let's scroll
223+
const lastIndex = rowData.length - 1;
224+
gridApi.paginationGoToLastPage();
225+
gridApi.ensureIndexVisible(lastIndex, 'bottom');
226+
}
227+
},
228+
[gridApi, rowData.length]
229+
);
220230

221-
const onCellEditingStopped = useCallback(
222-
(event: CellEditingStoppedEvent) => {
231+
const onCellEditingStopped = useCallback<AgGridFn<'onCellEditingStopped', TData>>(
232+
(event) => {
223233
const rowIndex = getIndex(event.data);
224234
if (rowIndex === -1) {
225235
return;
@@ -229,15 +239,22 @@ function CustomAgGridTable({
229239
[getIndex, update]
230240
);
231241

232-
const onSortChanged = useCallback((event: SortChangedEvent) => {
233-
const isAnycolumnhasSort = event.api.getColumnState().some((col: ColumnState) => col.sort);
234-
setIsSortApplied(isAnycolumnhasSort);
235-
}, []);
242+
const onSortChanged = useCallback<AgGridFn<'onSortChanged', TData>>(
243+
(event) => setIsSortApplied(event.api.getColumnState().some((col) => col.sort)),
244+
[]
245+
);
246+
247+
const getRowId = useCallback<AgGridFn<'getRowId', TData>>(
248+
// @ts-expect-error: we don't know at compile time if TData has a "FieldConstants.AG_GRID_ROW_UUID" field
249+
// TODO maybe force TData type to have this field?
250+
(row) => row.data[FieldConstants.AG_GRID_ROW_UUID],
251+
[]
252+
);
236253

237254
return (
238255
<Grid container spacing={2}>
239256
<Grid item xs={12} className={theme.aggrid.theme} sx={style(cssProps).grid}>
240-
<AgGridReact
257+
<AgGridReact<TData>
241258
rowData={rowData}
242259
onGridReady={onGridReady}
243260
getLocaleText={getLocaleText}
@@ -246,23 +263,21 @@ function CustomAgGridTable({
246263
domLayout="autoHeight"
247264
rowDragEntireRow
248265
rowDragManaged
249-
onRowDragEnd={(e) => move(getIndex(e.node.data), e.overIndex)}
266+
onRowDragEnd={onRowDragEnd}
250267
suppressBrowserResizeObserver
268+
defaultColDef={defaultColDef}
251269
columnDefs={columnDefs}
252270
detailRowAutoHeight
253-
onSelectionChanged={() => {
254-
setSelectedRows(gridApi.api.getSelectedRows());
255-
}}
271+
onSelectionChanged={onSelectionChanged}
256272
onRowDataUpdated={newRowAdded ? onRowDataUpdated : undefined}
257273
onCellEditingStopped={onCellEditingStopped}
258274
onSortChanged={onSortChanged}
259-
getRowId={(row) => row.data[FieldConstants.AG_GRID_ROW_UUID]}
275+
getRowId={getRowId}
260276
pagination={pagination}
261277
paginationPageSize={paginationPageSize}
262278
suppressRowClickSelection={suppressRowClickSelection}
263279
alwaysShowVerticalScroll={alwaysShowVerticalScroll}
264280
stopEditingWhenCellsLoseFocus={stopEditingWhenCellsLoseFocus}
265-
{...props}
266281
/>
267282
</Grid>
268283
<BottomRightButtons

0 commit comments

Comments
 (0)