diff --git a/docker-compose.yml b/docker-compose.yml index 92d18423e..400dd9f0d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -153,7 +153,7 @@ volumes: node_modules: name: lcfs_node_data s3: - name: lfcs_s3_data + name: lcfs_s3_data clamav: name: clamav clamsocket: @@ -161,4 +161,4 @@ volumes: networks: shared_network: - name: shared_network + name: shared_network \ No newline at end of file diff --git a/frontend/src/components/BCDataGrid/components/Editors/AutocompleteCellEditor.jsx b/frontend/src/components/BCDataGrid/components/Editors/AutocompleteCellEditor.jsx index 66dfa308f..49096dbf0 100644 --- a/frontend/src/components/BCDataGrid/components/Editors/AutocompleteCellEditor.jsx +++ b/frontend/src/components/BCDataGrid/components/Editors/AutocompleteCellEditor.jsx @@ -12,7 +12,8 @@ import { Checkbox, Box, Chip, - Stack + Stack, + Divider } from '@mui/material' import CheckBoxIcon from '@mui/icons-material/CheckBox' import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank' @@ -37,21 +38,30 @@ export const AutocompleteCellEditor = forwardRef((props, ref) => { onPaste } = props - const [selectedValues, setSelectedValues] = useState(() => { - if (!value) { - return [] - } else if (Array.isArray(value)) { - return value - } else { - return value.split(',').map((v) => v.trim) + // Fix initial value parsing + const parseInitialValue = (initialValue) => { + if (!initialValue) return multiple ? [] : null + if (Array.isArray(initialValue)) return initialValue + if (typeof initialValue === 'string') { + const values = initialValue.split(',').map((v) => v.trim()) + return multiple ? values : values[0] } - }) + return multiple ? [initialValue] : initialValue + } + const [selectedValues, setSelectedValues] = useState(() => + parseInitialValue(value) + ) const [isOpen, setIsOpen] = useState(false) const inputRef = useRef() useImperativeHandle(ref, () => ({ - getValue: () => selectedValues, + getValue: () => { + if (multiple) { + return Array.isArray(selectedValues) ? selectedValues : [] + } + return selectedValues || '' + }, isCancelBeforeStart: () => false, isCancelAfterEnd: () => false, afterGuiAttached: () => { @@ -68,8 +78,11 @@ export const AutocompleteCellEditor = forwardRef((props, ref) => { }, []) const handleChange = (event, newValue) => { - setSelectedValues(newValue) - onValueChange(newValue) + const processedValue = multiple ? newValue : newValue || '' + setSelectedValues(processedValue) + if (onValueChange) { + onValueChange(processedValue) + } } const navigateToNextCell = () => { @@ -87,14 +100,12 @@ export const AutocompleteCellEditor = forwardRef((props, ref) => { onKeyDownCapture(event) } - // Handle Enter key to toggle dropdown if (event.key === 'Enter') { event.preventDefault() setIsOpen(!isOpen) return } - // Handle Tab key navigation - directly move to next/previous cell if (event.key === 'Tab') { event.preventDefault() onValueChange(selectedValues) @@ -117,6 +128,28 @@ export const AutocompleteCellEditor = forwardRef((props, ref) => { api.stopEditing() } + const isOptionEqualToValue = (option, value) => { + if (!option || !value) return false + + if (typeof option === 'string' && typeof value === 'string') { + return option === value + } + + if (typeof option === 'object' && typeof value === 'object') { + return option.label === value.label + } + + if (typeof option === 'object' && typeof value === 'string') { + return option.label === value + } + + if (typeof option === 'string' && typeof value === 'object') { + return option === value.label + } + + return false + } + return ( { autoHighlight size="medium" freeSolo={freeSolo} - getOptionLabel={(option) => - typeof option === 'string' ? option : option.label || '' - } - renderOption={({ key, ...propsIn }, option, { selected }) => { - const isOptionSelected = - Array.isArray(selectedValues) && selectedValues.includes(option) + isOptionEqualToValue={isOptionEqualToValue} + getOptionLabel={(option) => { + if (!option) return '' + return typeof option === 'string' ? option : option.label || '' + }} + renderOption={(props, option, { selected }) => { + const isOptionSelected = multiple + ? Array.isArray(selectedValues) && + selectedValues.some((val) => isOptionEqualToValue(val, option)) + : isOptionEqualToValue(selectedValues, option) + return ( - img': { mr: 2, flexShrink: 0 } }} - aria-label={`select ${ - typeof option === 'string' ? option : option.label - }`} - data-testid={`select-${ - typeof option === 'string' ? option : option.label - }`} - {...propsIn} - tabIndex={0} + - {multiple && ( - - )} - {typeof option === 'string' ? option : option.label} - + img': { mr: 2, flexShrink: 0 } }} + aria-label={`select ${ + typeof option === 'string' ? option : option.label + }`} + data-testid={`select-${ + typeof option === 'string' ? option : option.label + }`} + {...props} + tabIndex={0} + > + {multiple && ( + + )} + {typeof option === 'string' ? option : option.label} + + + ) }} renderInput={(params) => ( @@ -205,7 +247,7 @@ export const AutocompleteCellEditor = forwardRef((props, ref) => { onBlur={handleBlur} inputProps={{ ...params.inputProps, - autoComplete: 'off' // disable autocomplete and autofill + autoComplete: 'off' }} /> )} diff --git a/frontend/src/views/AllocationAgreements/_schema.jsx b/frontend/src/views/AllocationAgreements/_schema.jsx index 1d275cd06..8893c676c 100644 --- a/frontend/src/views/AllocationAgreements/_schema.jsx +++ b/frontend/src/views/AllocationAgreements/_schema.jsx @@ -268,7 +268,7 @@ export const allocationAgreementColDefs = (optionsData, errors, currentUser) => headerName: i18n.t( 'allocationAgreement:allocationAgreementColLabels.provisionOfTheAct' ), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellEditorParams: (params) => { const fuelType = optionsData?.fuelTypes?.find( (type) => type.fuelType === params.data.fuelType @@ -279,7 +279,11 @@ export const allocationAgreementColDefs = (optionsData, errors, currentUser) => : [] return { - values: provisionsOfTheAct.sort() + options: provisionsOfTheAct.sort(), + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true } }, cellRenderer: (params) => diff --git a/frontend/src/views/FuelExports/_schema.jsx b/frontend/src/views/FuelExports/_schema.jsx index 7286a7f3b..e6ae64992 100644 --- a/frontend/src/views/FuelExports/_schema.jsx +++ b/frontend/src/views/FuelExports/_schema.jsx @@ -290,15 +290,19 @@ export const fuelExportColDefs = (optionsData, errors, gridReady) => [ field: 'provisionOfTheAct', headerComponent: RequiredHeader, headerName: i18n.t('fuelExport:fuelExportColLabels.provisionOfTheActId'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellRenderer: (params) => params.value || (!params.value && Select), cellEditorParams: (params) => ({ - values: optionsData?.fuelTypes + options: optionsData?.fuelTypes ?.find((obj) => params.data.fuelType === obj.fuelType) ?.provisions.map((item) => item.name) - .sort() + .sort(), + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true }), cellStyle: (params) => cellErrorStyle(params, errors), suppressKeyboardEvent, @@ -322,7 +326,7 @@ export const fuelExportColDefs = (optionsData, errors, gridReady) => [ { field: 'fuelCode', headerName: i18n.t('fuelExport:fuelExportColLabels.fuelCode'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, suppressKeyboardEvent, minWidth: 135, cellEditorParams: (params) => { @@ -330,7 +334,11 @@ export const fuelExportColDefs = (optionsData, errors, gridReady) => [ (obj) => params.data.fuelType === obj.fuelType ) return { - values: fuelTypeObj?.fuelCodes?.map((item) => item.fuelCode) || [] + options: fuelTypeObj?.fuelCodes?.map((item) => item.fuelCode) || [], + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true } }, cellStyle: (params) => { diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx index 2fb070c23..2d3c2d0bd 100644 --- a/frontend/src/views/FuelSupplies/_schema.jsx +++ b/frontend/src/views/FuelSupplies/_schema.jsx @@ -245,15 +245,19 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ field: 'provisionOfTheAct', headerComponent: RequiredHeader, headerName: i18n.t('fuelSupply:fuelSupplyColLabels.provisionOfTheActId'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellRenderer: (params) => params.value || (!params.value && Select), cellEditorParams: (params) => ({ - values: optionsData?.fuelTypes + options: optionsData?.fuelTypes ?.find((obj) => params.data.fuelType === obj.fuelType) ?.provisions.map((item) => item.name) - .sort() + .sort(), + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true }), cellStyle: (params) => StandardCellWarningAndErrors(params, errors, warnings), @@ -279,13 +283,17 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ { field: 'fuelCode', headerName: i18n.t('fuelSupply:fuelSupplyColLabels.fuelCode'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellEditorParams: (params) => { const fuelType = optionsData?.fuelTypes?.find( (obj) => params.data.fuelType === obj.fuelType ) return { - values: fuelType?.fuelCodes.map((item) => item.fuelCode) || [] + options: fuelType?.fuelCodes.map((item) => item.fuelCode) || [], + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true } }, cellStyle: (params) => { diff --git a/frontend/src/views/OtherUses/_schema.jsx b/frontend/src/views/OtherUses/_schema.jsx index af28e5126..e6028431a 100644 --- a/frontend/src/views/OtherUses/_schema.jsx +++ b/frontend/src/views/OtherUses/_schema.jsx @@ -81,7 +81,7 @@ export const otherUsesColDefs = (optionsData, errors) => [ field: 'provisionOfTheAct', headerComponent: RequiredHeader, headerName: i18n.t('otherUses:otherUsesColLabels.provisionOfTheAct'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellEditorParams: (params) => { const fuelType = optionsData?.fuelTypes?.find( (type) => type.fuelType === params.data.fuelType @@ -92,7 +92,11 @@ export const otherUsesColDefs = (optionsData, errors) => [ : [] return { - values: provisionsOfTheAct.sort() + options: provisionsOfTheAct.sort(), + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true } }, cellRenderer: (params) => @@ -219,14 +223,18 @@ export const otherUsesColDefs = (optionsData, errors) => [ { field: 'units', headerName: i18n.t('otherUses:otherUsesColLabels.units'), - cellEditor: 'agSelectCellEditor', + cellEditor: AutocompleteCellEditor, cellEditorParams: (params) => { const fuelType = optionsData?.fuelTypes?.find( (obj) => params.data.fuelType === obj.fuelType ) const values = fuelType ? [fuelType.units] : [] return { - values: values + options: values, + multiple: false, + disableCloseOnSelect: false, + freeSolo: false, + openOnFocus: true } }, cellRenderer: (params) => {