Skip to content

Commit

Permalink
Merge branch 'main' into task/cffdrs-py-github
Browse files Browse the repository at this point in the history
  • Loading branch information
brettedw authored Sep 16, 2024
2 parents 4ed0564 + b541b68 commit f826c09
Show file tree
Hide file tree
Showing 16 changed files with 619 additions and 72 deletions.
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
77 changes: 63 additions & 14 deletions web/src/features/moreCast2/components/ColumnDefBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
GridCellParams,
GridColDef,
GridColumnHeaderParams,
GridPreProcessEditCellProps,
GridRenderCellParams,
GridRenderEditCellParams,
GridValueFormatterParams,
GridValueGetterParams,
GridValueSetterParams
Expand All @@ -12,6 +14,7 @@ import { WeatherDeterminate, WeatherDeterminateType } from 'api/moreCast2API'
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 +47,29 @@ export const GC_HEADER = 'GC'

export interface ForecastColDefGenerator {
getField: () => string
generateForecastColDef: (columnClickHandlerProps: ColumnClickHandlerProps, headerName?: string) => GridColDef
generateForecastSummaryColDef: (columnClickHandlerProps: ColumnClickHandlerProps) => GridColDef
generateForecastColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
validator?: (value: string) => string
) => GridColDef
generateForecastSummaryColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
validator?: (value: string) => string
) => GridColDef
}

export interface ColDefGenerator {
getField: () => string
generateColDef: (columnClickHandlerProps: ColumnClickHandlerProps, headerName?: string) => GridColDef
generateColDef: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
validator?: (value: string) => string
) => GridColDef
generateColDefs: (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
includeBiasFields?: boolean
includeBiasFields?: boolean,
validator?: (value: string) => string
) => GridColDef[]
}

Expand All @@ -73,34 +88,48 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
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} />
}

public generateForecastColDef = (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
validator?: (value: string) => string
) => {
return this.generateForecastColDefWith(
`${this.field}${WeatherDeterminate.FORECAST}`,
headerName ?? this.headerName,
this.precision,
columnClickHandlerProps,
DEFAULT_FORECAST_COLUMN_WIDTH
DEFAULT_FORECAST_COLUMN_WIDTH,
validator
)
}

public generateForecastSummaryColDef = (columnClickHandlerProps: ColumnClickHandlerProps) => {
public generateForecastSummaryColDef = (
columnClickHandlerProps: ColumnClickHandlerProps,
validator?: (value: string) => string
) => {
return this.generateForecastSummaryColDefWith(
`${this.field}${WeatherDeterminate.FORECAST}`,
this.headerName,
this.precision,
columnClickHandlerProps,
DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH
DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH,
validator
)
}

public generateColDefs = (
columnClickHandlerProps: ColumnClickHandlerProps,
headerName?: string,
includeBiasFields = true
includeBiasFields = true,
validator?: (value: string) => string
) => {
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, validator)
gridColDefs.push(forecastColDef)

for (const colDef of this.generateNonForecastColDefs(includeBiasFields)) {
Expand All @@ -119,7 +148,13 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
)
}

public generateColDefWith = (field: string, headerName: string, precision: number, width?: number) => {
public generateColDefWith = (
field: string,
headerName: string,
precision: number,
width?: number,
validator?: (value: string) => string
) => {
return {
field,
disableColumnMenu: true,
Expand All @@ -129,6 +164,10 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
sortable: false,
type: 'number',
width: width ?? DEFAULT_COLUMN_WIDTH,
renderEditCell: this.renderEditCell,
preProcessEditCellProps: (params: GridPreProcessEditCellProps) => {
return { ...params.props, error: validator ? validator(params.props.value) : '' }
},
cellClassName: (params: Pick<GridCellParams, 'colDef' | 'field'>) => {
return modelColorClass(params)
},
Expand All @@ -154,7 +193,8 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
headerName: string,
precision: number,
columnClickHandlerProps: ColumnClickHandlerProps,
width?: number
width?: number,
validator?: (value: string) => string
) => {
const isGrassField = field.includes('grass')
const isCalcField = field.includes('Calc')
Expand All @@ -171,6 +211,10 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
sortable: false,
type: 'number',
width: width ?? DEFAULT_FORECAST_COLUMN_WIDTH,
renderEditCell: this.renderEditCell,
preProcessEditCellProps: (params: GridPreProcessEditCellProps) => {
return { ...params.props, error: validator ? validator(params.props.value) : '' }
},
renderHeader: (params: GridColumnHeaderParams) => {
return isCalcField || isGrassField
? this.gridComponentRenderer.renderHeaderWith(params)
Expand All @@ -179,7 +223,7 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
renderCell: (params: Pick<GridRenderCellParams, 'row' | 'formattedValue'>) => {
return isCalcField
? this.gridComponentRenderer.renderCellWith(params)
: this.gridComponentRenderer.renderForecastCellWith(params, field)
: this.gridComponentRenderer.renderForecastCellWith(params, field, validator)
},
valueFormatter: (params: Pick<GridValueFormatterParams, 'value'>) => {
return this.valueFormatterWith(params, precision)
Expand All @@ -196,7 +240,8 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
headerName: string,
precision: number,
columnClickHandlerProps: ColumnClickHandlerProps,
width?: number
width?: number,
validator?: (value: string) => string
) => {
const isGrassField = field.includes('grass')
const isCalcField = field.includes('Calc')
Expand All @@ -213,6 +258,10 @@ export class ColumnDefBuilder implements ColDefGenerator, ForecastColDefGenerato
sortable: false,
type: 'number',
width: width ?? DEFAULT_FORECAST_SUMMARY_COLUMN_WIDTH,
preProcessEditCellProps: (params: GridPreProcessEditCellProps) => {
return { ...params.props, error: validator ? validator(params.props.value) : '' }
},
renderEditCell: this.renderEditCell,
renderHeader: (params: GridColumnHeaderParams) => {
return isCalcField || isGrassField
? this.gridComponentRenderer.renderHeaderWith(params)
Expand Down
78 changes: 78 additions & 0 deletions web/src/features/moreCast2/components/EditInputCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid-pro'
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'
import InvalidCellToolTip from '@/features/moreCast2/components/InvalidCellToolTip'

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 (
<InvalidCellToolTip error={error}>
<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}
/>
</InvalidCellToolTip>
)
}
34 changes: 6 additions & 28 deletions web/src/features/moreCast2/components/ForecastCell.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React from 'react'
import { Grid, TextField, Tooltip } from '@mui/material'
import { Grid, Tooltip } from '@mui/material'
import { GridRenderCellParams } from '@mui/x-data-grid-pro'
import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'
import AddBoxIcon from '@mui/icons-material/AddBox'
import { MEDIUM_GREY, theme } from 'app/theme'
import { MEDIUM_GREY } from 'app/theme'
import ValidatedForecastCell from '@/features/moreCast2/components/ValidatedForecastCell'

interface ForecastCellProps {
disabled: boolean
label: string
showGreaterThan: boolean
showLessThan: boolean
value: Pick<GridRenderCellParams, 'formattedValue'>
validator?: (value: string) => string
}

const ForecastCell = ({ disabled, label, showGreaterThan, showLessThan, value }: ForecastCellProps) => {
const ForecastCell = ({ disabled, label, showGreaterThan, showLessThan, value, validator }: ForecastCellProps) => {
// We should never display both less than and greater than icons at the same time
if (showGreaterThan && showLessThan) {
throw Error('ForecastCell cannot show both greater than and less than icons at the same time.')
Expand All @@ -31,30 +32,7 @@ const ForecastCell = ({ disabled, label, showGreaterThan, showLessThan, value }:
)}
</Grid>
<Grid item xs={8}>
<TextField
data-testid="forecast-cell-text-field"
disabled={disabled}
size="small"
label={label}
InputLabelProps={{
shrink: true
}}
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: `${theme.palette.common.white}`,
'& fieldset': {
borderColor: '#737373',
borderWidth: '2px'
}
},
'& .Mui-disabled': {
'& fieldset': {
borderWidth: '1px'
}
}
}}
value={value}
></TextField>
<ValidatedForecastCell disabled={disabled} label={label} value={value} validator={validator} />
</Grid>
<Grid item xs={2} sx={{ marginLeft: 'auto' }}>
{showGreaterThan && (
Expand Down
22 changes: 10 additions & 12 deletions web/src/features/moreCast2/components/GridComponentRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ForecastHeader from 'features/moreCast2/components/ForecastHeader'
import { ColumnClickHandlerProps } from 'features/moreCast2/components/TabbedDataGrid'
import { cloneDeep, isNumber } from 'lodash'
import ForecastCell from 'features/moreCast2/components/ForecastCell'
import ValidatedForecastCell from '@/features/moreCast2/components/ValidatedForecastCell'

export const NOT_AVAILABLE = 'N/A'
export const NOT_REPORTING = 'N/R'
Expand Down Expand Up @@ -92,7 +93,11 @@ export class GridComponentRenderer {
} else return isNaN(value) ? noDataField : Number(value).toFixed(precision)
}

public renderForecastCellWith = (params: Pick<GridRenderCellParams, 'row' | 'formattedValue'>, field: string) => {
public renderForecastCellWith = (
params: Pick<GridRenderCellParams, 'row' | 'formattedValue'>,
field: string,
validator?: (value: string) => string
) => {
// If a single cell in a row contains an Actual, no Forecast will be entered into the row anymore, so we can disable the whole row.
const isActual = rowContainsActual(params.row)
// We can disable a cell if an Actual exists or the forDate is before today.
Expand All @@ -115,20 +120,12 @@ export class GridComponentRenderer {
// The grass curing 'forecast' field is rendered differently
if (isGrassField) {
return (
<TextField
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: `${theme.palette.common.white}`
}
}}
<ValidatedForecastCell
disabled={isActual || isPreviousDate}
size="small"
label={label}
InputLabelProps={{
shrink: true
}}
value={params.formattedValue}
></TextField>
validator={validator}
/>
)
} else {
// Forecast fields (except wind direction) have plus and minus icons indicating if the forecast was
Expand All @@ -140,6 +137,7 @@ export class GridComponentRenderer {
showGreaterThan={showGreaterThan}
showLessThan={showLessThan}
value={params.formattedValue}
validator={validator}
/>
)
}
Expand Down
Loading

0 comments on commit f826c09

Please sign in to comment.