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

Morecast: Validation on editable fields #3919

Merged
merged 23 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"cffdrs",
"colour",
"cutline",
"CWFIS",
"determinates",
"excinfo",
"fastapi",
Expand Down
4 changes: 3 additions & 1 deletion web/src/app/rootReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import fireZoneElevationInfoSlice from 'features/fba/slices/fireZoneElevationInf
import stationGroupsSlice from 'commonSlices/stationGroupsSlice'
import selectedStationGroupsMembersSlice from 'commonSlices/selectedStationGroupMembers'
import dataSlice from 'features/moreCast2/slices/dataSlice'
import morecastInputValidSlice from 'features/moreCast2/slices/validInputSlice'
import selectedStationsSlice from 'features/moreCast2/slices/selectedStationsSlice'
import provincialSummarySlice from 'features/fba/slices/provincialSummarySlice'
import fireCentreTPIStatsSlice from 'features/fba/slices/fireCentreTPIStatsSlice'
Expand Down Expand Up @@ -44,7 +45,8 @@ const rootReducer = combineReducers({
stationGroupsMembers: selectedStationGroupsMembersSlice,
weatherIndeterminates: dataSlice,
selectedStations: selectedStationsSlice,
provincialSummary: provincialSummarySlice
provincialSummary: provincialSummarySlice,
morecastInputValid: morecastInputValidSlice
})

// Infer whatever gets returned from rootReducer and use it as the type of the root state
Expand Down
70 changes: 57 additions & 13 deletions web/src/features/moreCast2/components/ColumnDefBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
GridCellParams,
GridColDef,
GridColumnHeaderParams,
GridEditCellProps,
GridPreProcessEditCellProps,
GridRenderCellParams,
GridRenderEditCellParams,
GridValueFormatterParams,
GridValueGetterParams,
GridValueSetterParams
Expand All @@ -12,6 +15,7 @@
import { modelColorClass, modelHeaderColorClass } from 'app/theme'
import { GridComponentRenderer } from 'features/moreCast2/components/GridComponentRenderer'
import { ColumnClickHandlerProps } from 'features/moreCast2/components/TabbedDataGrid'
import { EditInputCell } from '@/features/moreCast2/components/EditInputCell'

export const DEFAULT_COLUMN_WIDTH = 80
export const DEFAULT_FORECAST_COLUMN_WIDTH = 145
Expand Down Expand Up @@ -44,17 +48,29 @@

export interface ForecastColDefGenerator {
getField: () => string
generateForecastColDef: (columnClickHandlerProps: ColumnClickHandlerProps, headerName?: string) => GridColDef
generateForecastSummaryColDef: (columnClickHandlerProps: ColumnClickHandlerProps) => GridColDef
generateForecastColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => GridColDef
generateForecastSummaryColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => GridColDef
}

export interface ColDefGenerator {
getField: () => string
generateColDef: (columnClickHandlerProps: ColumnClickHandlerProps, headerName?: string) => GridColDef
generateColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => GridColDef
generateColDefs: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
includeBiasFields?: boolean
includeBiasFields?: boolean,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => GridColDef[]
}

Expand All @@ -73,34 +89,48 @@
return this.generateColDefWith(this.field, this.headerName, this.precision, DEFAULT_COLUMN_WIDTH)
}

public generateForecastColDef = (columnClickHandlerProps: ColumnClickHandlerProps, headerName?: string) => {
private renderEditCell(params: GridRenderEditCellParams) {
return <EditInputCell {...params} />

Check warning on line 93 in web/src/features/moreCast2/components/ColumnDefBuilder.tsx

View check run for this annotation

Codecov / codecov/patch

web/src/features/moreCast2/components/ColumnDefBuilder.tsx#L93

Added line #L93 was not covered by tests
}

public generateForecastColDef = (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
return this.generateForecastColDefWith(
`${this.field}${WeatherDeterminate.FORECAST}`,
headerName ?? this.headerName,
this.precision,
columnClickHandlerProps,
DEFAULT_FORECAST_COLUMN_WIDTH
DEFAULT_FORECAST_COLUMN_WIDTH,
preProcessEditCellProps
)
}

public generateForecastSummaryColDef = (columnClickHandlerProps: ColumnClickHandlerProps) => {
public generateForecastSummaryColDef = (
columnClickHandlerProps: ColumnClickHandlerProps,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
return this.generateForecastSummaryColDefWith(
`${this.field}${WeatherDeterminate.FORECAST}`,
this.headerName,
this.precision,
columnClickHandlerProps,
DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH
DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH,
preProcessEditCellProps
)
}

public generateColDefs = (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
includeBiasFields = true
includeBiasFields = true,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
const gridColDefs: GridColDef[] = []
// Forecast columns have unique requirement (eg. column header menu, editable, etc.)
const forecastColDef = this.generateForecastColDef(columnClickHandlerProps, headerName)
const forecastColDef = this.generateForecastColDef(columnClickHandlerProps, headerName, preProcessEditCellProps)
gridColDefs.push(forecastColDef)

for (const colDef of this.generateNonForecastColDefs(includeBiasFields)) {
Expand All @@ -119,7 +149,13 @@
)
}

public generateColDefWith = (field: string, headerName: string, precision: number, width?: number) => {
public generateColDefWith = (
field: string,
headerName: string,
precision: number,
width?: number,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
return {
field,
disableColumnMenu: true,
Expand All @@ -129,6 +165,8 @@
sortable: false,
type: 'number',
width: width ?? DEFAULT_COLUMN_WIDTH,
renderEditCell: this.renderEditCell,
preProcessEditCellProps,
cellClassName: (params: Pick<GridCellParams, 'field'>) => {
return modelColorClass(params)
},
Expand All @@ -154,7 +192,8 @@
headerName: string,
precision: number,
columnClickHandlerProps: ColumnClickHandlerProps,
width?: number
width?: number,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
const isGrassField = field.includes('grass')
const isCalcField = field.includes('Calc')
Expand All @@ -171,6 +210,8 @@
sortable: false,
type: 'number',
width: width ?? DEFAULT_FORECAST_COLUMN_WIDTH,
renderEditCell: this.renderEditCell,
preProcessEditCellProps,
renderHeader: (params: GridColumnHeaderParams) => {
return isCalcField || isGrassField
? this.gridComponentRenderer.renderHeaderWith(params)
Expand All @@ -196,7 +237,8 @@
headerName: string,
precision: number,
columnClickHandlerProps: ColumnClickHandlerProps,
width?: number
width?: number,
preProcessEditCellProps?: (params: GridPreProcessEditCellProps) => GridEditCellProps | Promise<GridEditCellProps>
) => {
const isGrassField = field.includes('grass')
const isCalcField = field.includes('Calc')
Expand All @@ -213,6 +255,8 @@
sortable: false,
type: 'number',
width: width ?? DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH,
preProcessEditCellProps,
renderEditCell: this.renderEditCell,
renderHeader: (params: GridColumnHeaderParams) => {
return isCalcField || isGrassField
? this.gridComponentRenderer.renderHeaderWith(params)
Expand Down
89 changes: 89 additions & 0 deletions web/src/features/moreCast2/components/EditInputCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid-pro'
import Tooltip from '@mui/material/Tooltip'
import React, { useRef, useEffect } from 'react'
import { TextField } from '@mui/material'
import { theme } from '@/app/theme'
import { isEmpty } from 'lodash'
import { AppDispatch } from '@/app/store'
import { useDispatch } from 'react-redux'
import { setInputValid } from '@/features/moreCast2/slices/validInputSlice'

export const EditInputCell = (props: GridRenderEditCellParams) => {
const { id, value, field, hasFocus, error } = props
const apiRef = useGridApiContext()
const inputRef = useRef<HTMLInputElement | null>(null)
const dispatch: AppDispatch = useDispatch()

dispatch(setInputValid(isEmpty(error)))

useEffect(() => {
if (hasFocus && inputRef.current) {
inputRef.current.focus()
}
}, [hasFocus])

const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value
apiRef.current.setEditCellValue({ id, field, value: newValue })
}

const handleBlur = () => {
apiRef.current.stopCellEditMode({ id, field })
}

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
event.stopPropagation()
if (isEmpty(error)) {
apiRef.current.stopCellEditMode({ id, field })
} else {
event.stopPropagation()
}
}
}

return (
<Tooltip
data-testid="validation-tooltip"
title={error || ''}
open={!isEmpty(error)}
arrow
sx={{
'& .MuiTooltip-tooltip': {
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText
}
}}
>
<TextField
data-testid="forecast-edit-cell"
type="number"
inputMode="numeric"
inputRef={inputRef}
size="small"
InputLabelProps={{
shrink: true
}}
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
},
'&:hover fieldset': {
borderColor: error ? theme.palette.error.main : '#737373'
},
'&.Mui-focused fieldset': {
borderColor: error ? theme.palette.error.main : '#737373',
borderWidth: '2px'
}
}
}}
value={value}
onChange={handleValueChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
</Tooltip>
)
}
Loading
Loading