diff --git a/backend/lcfs/web/api/fuel_export/repo.py b/backend/lcfs/web/api/fuel_export/repo.py
index 36aeb4ce1..d09a546dc 100644
--- a/backend/lcfs/web/api/fuel_export/repo.py
+++ b/backend/lcfs/web/api/fuel_export/repo.py
@@ -260,6 +260,7 @@ async def create_fuel_export(self, fuel_export: FuelExport) -> FuelExport:
"fuel_type",
"provision_of_the_act",
"end_use_type",
+ "fuel_code",
],
)
return fuel_export
diff --git a/backend/lcfs/web/api/other_uses/schema.py b/backend/lcfs/web/api/other_uses/schema.py
index 51327f772..db3e591be 100644
--- a/backend/lcfs/web/api/other_uses/schema.py
+++ b/backend/lcfs/web/api/other_uses/schema.py
@@ -40,12 +40,19 @@ class ExpectedUseTypeSchema(BaseSchema):
description: Optional[str] = None
+class FuelCategorySchema(BaseSchema):
+ fuel_category_id: int
+ category: str
+ description: Optional[str] = None
+
+
class FuelTypeSchema(BaseSchema):
fuel_type_id: int
fuel_type: str
fossil_derived: Optional[bool] = None
provision_1_id: Optional[int] = None
provision_2_id: Optional[int] = None
+ fuel_categories: List[FuelCategorySchema]
default_carbon_intensity: Optional[float] = None
fuel_codes: Optional[List[FuelCodeSchema]] = []
provision_of_the_act: Optional[List[ProvisionOfTheActSchema]] = []
diff --git a/backend/lcfs/web/application.py b/backend/lcfs/web/application.py
index e7117a105..8aef6126c 100644
--- a/backend/lcfs/web/application.py
+++ b/backend/lcfs/web/application.py
@@ -1,4 +1,6 @@
import logging
+import os
+import debugpy
import uuid
import structlog
diff --git a/frontend/src/assets/locales/en/allocationAgreement.json b/frontend/src/assets/locales/en/allocationAgreement.json
index 1da1af078..f120b7ebe 100644
--- a/frontend/src/assets/locales/en/allocationAgreement.json
+++ b/frontend/src/assets/locales/en/allocationAgreement.json
@@ -3,6 +3,7 @@
"noAllocationAgreementsFound": "No allocation agreements found",
"addAllocationAgreementRowsTitle": "Allocation agreements (e.g., allocating responsibility for fuel)",
"allocationAgreementSubtitle": "Enter allocation agreement details below",
+ "fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"allocationAgreementColLabels": {
"transaction": "Responsibility",
"transactionPartner": "Legal name of transaction partner",
diff --git a/frontend/src/assets/locales/en/fuelExport.json b/frontend/src/assets/locales/en/fuelExport.json
index 002fba7c1..d5cf55dc6 100644
--- a/frontend/src/assets/locales/en/fuelExport.json
+++ b/frontend/src/assets/locales/en/fuelExport.json
@@ -32,5 +32,6 @@
},
"validateMsg": {
"isRequired": "{{field}} is required"
- }
+ },
+ "fuelCodeFieldRequiredError": "Error updating row: Fuel code field required"
}
diff --git a/frontend/src/assets/locales/en/fuelSupply.json b/frontend/src/assets/locales/en/fuelSupply.json
index 3e6036080..93c75760b 100644
--- a/frontend/src/assets/locales/en/fuelSupply.json
+++ b/frontend/src/assets/locales/en/fuelSupply.json
@@ -9,6 +9,7 @@
"LoadFailMsg": "Failed to load supply of fuel rows",
"addRow": "Add row",
"rows": "rows",
+ "fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"fuelSupplyColLabels": {
"complianceReportId": "Compliance Report ID",
"fuelSupplyId": "Fuel supply ID",
diff --git a/frontend/src/assets/locales/en/otherUses.json b/frontend/src/assets/locales/en/otherUses.json
index c67e328ab..70b32650d 100644
--- a/frontend/src/assets/locales/en/otherUses.json
+++ b/frontend/src/assets/locales/en/otherUses.json
@@ -21,6 +21,7 @@
"approveConfirmText": "Are you sure you want to approve this other use entry?",
"addRow": "Add row",
"rows": "rows",
+ "fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"otherUsesColLabels": {
"complianceReportId": "Compliance report ID",
"fuelType": "Fuel type",
diff --git a/frontend/src/components/BCDataGrid/columns.js b/frontend/src/components/BCDataGrid/columns.js
index 0e491f34e..4e26c12cd 100644
--- a/frontend/src/components/BCDataGrid/columns.js
+++ b/frontend/src/components/BCDataGrid/columns.js
@@ -24,6 +24,7 @@ export const actions = (props) => ({
cellRendererParams: props,
pinned: 'left',
maxWidth: 110,
+ minWidth: 90,
editable: false,
suppressKeyboardEvent,
filter: false,
diff --git a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx
index f09d3ee0e..e5a2e34c9 100644
--- a/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx
+++ b/frontend/src/views/AllocationAgreements/AddEditAllocationAgreements.jsx
@@ -3,7 +3,6 @@ import BCTypography from '@/components/BCTypography'
import Grid2 from '@mui/material/Unstable_Grid2/Grid2'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
-import { BCAlert2 } from '@/components/BCAlert'
import BCBox from '@/components/BCBox'
import { BCGridEditor } from '@/components/BCDataGrid/BCGridEditor'
import {
@@ -169,6 +168,29 @@ export const AddEditAllocationAgreements = () => {
updatedData.ciOfFuel = DEFAULT_CI_FUEL[updatedData.fuelCategory]
}
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ if (isFuelCodeScenario && !updatedData.fuelCode) {
+ // Fuel code is required but not provided
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ [params.node.data.id]: ['fuelCode']
+ }))
+
+ alertRef.current?.triggerAlert({
+ message: t('allocationAgreement:fuelCodeFieldRequiredError'),
+ severity: 'error'
+ })
+
+ updatedData = {
+ ...updatedData,
+ validationStatus: 'error'
+ }
+
+ params.node.updateData(updatedData)
+ return // Stop execution, do not proceed to save
+ }
+
try {
setErrors({})
await saveRow(updatedData)
diff --git a/frontend/src/views/AllocationAgreements/_schema.jsx b/frontend/src/views/AllocationAgreements/_schema.jsx
index 6e503e3dd..d90a4e212 100644
--- a/frontend/src/views/AllocationAgreements/_schema.jsx
+++ b/frontend/src/views/AllocationAgreements/_schema.jsx
@@ -196,6 +196,7 @@ export const allocationAgreementColDefs = (optionsData, errors) => [
params.data.units = fuelType?.units
params.data.unrecognized = fuelType?.unrecognized
params.data.provisionOfTheAct = null
+ params.data.fuelCode = undefined
}
return true
},
@@ -302,16 +303,85 @@ export const allocationAgreementColDefs = (optionsData, errors) => [
}),
cellStyle: (params) => {
const style = StandardCellErrors(params, errors)
- const conditionalStyle =
+ const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
- ? { backgroundColor: '#fff', borderColor: 'unset' }
- : { backgroundColor: '#f2f2f2' }
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes = fuelType?.fuelCodes || []
+ const fuelCodeRequiredAndMissing =
+ isFuelCodeScenario && !params.data.fuelCode
+
+ let conditionalStyle = {}
+
+ // If required and missing, show red border and white background
+ if (fuelCodeRequiredAndMissing) {
+ style.borderColor = 'red'
+ style.backgroundColor = '#fff'
+ } else {
+ // Apply conditional styling if not missing
+ conditionalStyle =
+ isFuelCodeScenario && fuelCodes.length > 0
+ ? {
+ backgroundColor: '#fff',
+ borderColor: style.borderColor || 'unset'
+ }
+ : { backgroundColor: '#f2f2f2' }
+ }
+
return { ...style, ...conditionalStyle }
},
suppressKeyboardEvent,
minWidth: 150,
editable: (params) =>
- params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE,
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
+ optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )?.fuelCodes?.length > 0,
+ valueGetter: (params) => {
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ if (!fuelTypeObj) return params.data.fuelCode
+
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelCodes =
+ fuelTypeObj.fuelCodes?.map((item) => item.fuelCode) || []
+
+ if (isFuelCodeScenario && !params.data.fuelCode) {
+ // Autopopulate if only one fuel code is available
+ if (fuelCodes.length === 1) {
+ const singleFuelCode = fuelTypeObj.fuelCodes[0]
+ params.data.fuelCode = singleFuelCode.fuelCode
+ params.data.fuelCodeId = singleFuelCode.fuelCodeId
+ }
+ }
+
+ return params.data.fuelCode
+ },
+ valueSetter: (params) => {
+ if (params.newValue) {
+ params.data.fuelCode = params.newValue
+
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ if (params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
+ const matchingFuelCode = fuelType?.fuelCodes?.find(
+ (fuelCode) => params.data.fuelCode === fuelCode.fuelCode
+ )
+ if (matchingFuelCode) {
+ params.data.fuelCodeId = matchingFuelCode.fuelCodeId
+ }
+ }
+ } else {
+ // If user clears the value
+ params.data.fuelCode = undefined
+ params.data.fuelCodeId = undefined
+ }
+ return true
+ },
tooltipValueGetter: (p) => 'Select the approved fuel code'
},
{
diff --git a/frontend/src/views/FuelExports/AddEditFuelExports.jsx b/frontend/src/views/FuelExports/AddEditFuelExports.jsx
index ebaa03498..b9a40d86d 100644
--- a/frontend/src/views/FuelExports/AddEditFuelExports.jsx
+++ b/frontend/src/views/FuelExports/AddEditFuelExports.jsx
@@ -13,7 +13,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { v4 as uuid } from 'uuid'
-import { defaultColDef, fuelExportColDefs } from './_schema'
+import {
+ defaultColDef,
+ fuelExportColDefs,
+ PROVISION_APPROVED_FUEL_CODE
+} from './_schema'
export const AddEditFuelExports = () => {
const [rowData, setRowData] = useState([])
@@ -143,7 +147,33 @@ export const AddEditFuelExports = () => {
acc[key] = value
return acc
}, {})
+
updatedData.compliancePeriod = compliancePeriod
+
+ // Local validation before saving
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+
+ if (isFuelCodeScenario && !updatedData.fuelCode) {
+ // Fuel code is required but not provided
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ [params.node.data.id]: ['fuelCode']
+ }))
+
+ alertRef.current?.triggerAlert({
+ message: t('fuelExport:fuelCodeFieldRequiredError'),
+ severity: 'error'
+ })
+
+ updatedData = {
+ ...updatedData,
+ validationStatus: 'error'
+ }
+ params.node.updateData(updatedData)
+ return // Don't proceed with save
+ }
+
try {
setErrors({})
await saveRow(updatedData)
@@ -189,7 +219,7 @@ export const AddEditFuelExports = () => {
params.node.updateData(updatedData)
},
- [saveRow, t]
+ [saveRow, t, compliancePeriod]
)
const onAction = async (action, params) => {
diff --git a/frontend/src/views/FuelExports/_schema.jsx b/frontend/src/views/FuelExports/_schema.jsx
index f113671d3..0caae593b 100644
--- a/frontend/src/views/FuelExports/_schema.jsx
+++ b/frontend/src/views/FuelExports/_schema.jsx
@@ -17,6 +17,8 @@ import {
fuelTypeOtherConditionalStyle
} from '@/utils/fuelTypeOther'
+export const PROVISION_APPROVED_FUEL_CODE = 'Fuel code - section 19 (b) (i)'
+
const cellErrorStyle = (params, errors) => {
let style = {}
if (
@@ -318,29 +320,94 @@ export const fuelExportColDefs = (optionsData, errors) => [
field: 'fuelCode',
headerName: i18n.t('fuelExport:fuelExportColLabels.fuelCode'),
cellEditor: 'agSelectCellEditor',
- cellEditorParams: (params) => ({
- values: optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes.map((item) => item.fuelCode)
- }),
+ suppressKeyboardEvent,
+ minWidth: 135,
+ cellEditorParams: (params) => {
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ return {
+ values: fuelTypeObj?.fuelCodes?.map((item) => item.fuelCode) || []
+ }
+ },
cellStyle: (params) => {
const style = cellErrorStyle(params, errors)
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes =
+ fuelTypeObj?.fuelCodes.map((item) => item.fuelCode) || []
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+
+ // Check if fuel code is required (scenario) but missing
+ const fuelCodeRequiredAndMissing =
+ isFuelCodeScenario && !params.data.fuelCode
+
+ if (fuelCodeRequiredAndMissing) {
+ // If required and missing, force a red border
+ style.borderColor = 'red'
+ }
+
const conditionalStyle =
- optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes.map((item) => item.fuelCode).length > 0 &&
- /Fuel code/i.test(params.data.provisionOfTheAct)
+ fuelCodes.length > 0 &&
+ isFuelCodeScenario &&
+ !fuelCodeRequiredAndMissing
? { backgroundColor: '#fff', borderColor: 'unset' }
: { backgroundColor: '#f2f2f2' }
return { ...style, ...conditionalStyle }
},
- suppressKeyboardEvent,
- minWidth: 135,
- editable: (params) =>
- optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes.map((item) => item.fuelCode).length > 0 &&
- /Fuel code/i.test(params.data.provisionOfTheAct)
+ editable: (params) => {
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes = fuelTypeObj?.fuelCodes || []
+ return (
+ fuelCodes.length > 0 &&
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ )
+ },
+ valueGetter: (params) => {
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ if (!fuelTypeObj) return params.data.fuelCode
+
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelCodes =
+ fuelTypeObj.fuelCodes?.map((item) => item.fuelCode) || []
+
+ if (isFuelCodeScenario && !params.data.fuelCode) {
+ // Autopopulate if only one fuel code is available
+ if (fuelCodes.length === 1) {
+ const singleFuelCode = fuelTypeObj.fuelCodes[0]
+ params.data.fuelCode = singleFuelCode.fuelCode
+ params.data.fuelCodeId = singleFuelCode.fuelCodeId
+ }
+ }
+
+ return params.data.fuelCode
+ },
+ valueSetter: (params) => {
+ const newCode = params.newValue
+ const fuelTypeObj = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const selectedFuelCodeObj = fuelTypeObj?.fuelCodes.find(
+ (item) => item.fuelCode === newCode
+ )
+
+ if (selectedFuelCodeObj) {
+ params.data.fuelCode = selectedFuelCodeObj.fuelCode
+ params.data.fuelCodeId = selectedFuelCodeObj.fuelCodeId
+ } else {
+ params.data.fuelCode = undefined
+ params.data.fuelCodeId = undefined
+ }
+
+ return true
+ }
},
{
field: 'quantity',
diff --git a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx
index 7d7c4bd29..c78718694 100644
--- a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx
+++ b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx
@@ -14,7 +14,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { v4 as uuid } from 'uuid'
-import { defaultColDef, fuelSupplyColDefs } from './_schema'
+import {
+ defaultColDef,
+ fuelSupplyColDefs,
+ PROVISION_APPROVED_FUEL_CODE
+} from './_schema'
export const AddEditFuelSupplies = () => {
const [rowData, setRowData] = useState([])
@@ -131,15 +135,6 @@ export const AddEditFuelSupplies = () => {
'fuelCategory',
fuelCategoryOptions[0] ?? null
)
-
- const fuelCodeOptions = selectedFuelType.fuelCodes.map(
- (code) => code.fuelCode
- )
- params.node.setDataValue('fuelCode', fuelCodeOptions[0] ?? null)
- params.node.setDataValue(
- 'fuelCodeId',
- selectedFuelType.fuelCodes[0]?.fuelCodeId ?? null
- )
}
}
},
@@ -164,6 +159,28 @@ export const AddEditFuelSupplies = () => {
if (updatedData.fuelType === 'Other') {
updatedData.ciOfFuel = DEFAULT_CI_FUEL[updatedData.fuelCategory]
}
+
+ const isFuelCodeScenario =
+ params.node.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ if (isFuelCodeScenario && !params.node.data.fuelCode) {
+ // Set error on the row
+ setErrors({
+ [params.node.data.id]: ['fuelCode']
+ })
+
+ alertRef.current?.triggerAlert({
+ message: t('fuelSupply:fuelCodeFieldRequiredError'),
+ severity: 'error'
+ })
+
+ // Update node data to reflect error state
+ params.node.updateData({
+ ...params.node.data,
+ validationStatus: 'error'
+ })
+ return // Stop saving further
+ }
+
try {
setErrors({})
await saveRow(updatedData)
diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx
index 912dc994d..0dc792d73 100644
--- a/frontend/src/views/FuelSupplies/_schema.jsx
+++ b/frontend/src/views/FuelSupplies/_schema.jsx
@@ -19,6 +19,8 @@ import {
} from '@/utils/grid/errorRenderers'
import { apiRoutes } from '@/constants/routes'
+export const PROVISION_APPROVED_FUEL_CODE = 'Fuel code - section 19 (b) (i)'
+
export const fuelSupplyColDefs = (optionsData, errors, warnings) => [
validation,
actions({
@@ -292,21 +294,35 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [
field: 'fuelCode',
headerName: i18n.t('fuelSupply:fuelSupplyColLabels.fuelCode'),
cellEditor: 'agSelectCellEditor',
- cellEditorParams: (params) => ({
- values: optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes.map((item) => item.fuelCode)
- }),
+ cellEditorParams: (params) => {
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ return {
+ values: fuelType?.fuelCodes.map((item) => item.fuelCode) || []
+ }
+ },
cellStyle: (params) => {
const style = StandardCellWarningAndErrors(params, errors, warnings)
- const conditionalStyle =
- optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes.map((item) => item.fuelCode).length > 0 &&
- /Fuel code/i.test(params.data.provisionOfTheAct)
- ? { backgroundColor: '#fff' }
- : { backgroundColor: '#f2f2f2', borderColor: 'unset' }
- return { ...style, ...conditionalStyle }
+ const isFuelCodeScenario =
+ params.data?.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes = fuelType?.fuelCodes || []
+ const fuelCodeRequiredAndMissing =
+ isFuelCodeScenario && !params.data.fuelCode
+
+ if (fuelCodeRequiredAndMissing) {
+ // Highlight the cell if fuel code is required but not selected
+ return { ...style, backgroundColor: '#fff', borderColor: 'red' }
+ } else if (isFuelCodeScenario && fuelCodes.length > 0) {
+ // Allow selection when scenario matches and codes are present
+ return { ...style, backgroundColor: '#fff', borderColor: 'unset' }
+ } else {
+ // Otherwise disabled styling
+ return { ...style, backgroundColor: '#f2f2f2', borderColor: 'unset' }
+ }
},
suppressKeyboardEvent,
minWidth: 135,
@@ -314,29 +330,50 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [
const fuelType = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
- if (fuelType) {
- return (
- fuelType.fuelCodes.map((item) => item.fuelCode).length > 0 &&
- /Fuel code/i.test(params.data.provisionOfTheAct)
- )
+ return (
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
+ fuelType?.fuelCodes?.length > 0
+ )
+ },
+ valueGetter: (params) => {
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ if (!fuelType) return params.data.fuelCode
+
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelCodes = fuelType?.fuelCodes?.map((item) => item.fuelCode) || []
+
+ if (isFuelCodeScenario && !params.data.fuelCode) {
+ // If only one code is available, auto-populate
+ if (fuelCodes.length === 1) {
+ const singleFuelCode = fuelType.fuelCodes[0]
+ params.data.fuelCode = singleFuelCode.fuelCode
+ params.data.fuelCodeId = singleFuelCode.fuelCodeId
+ }
}
- return false
+
+ return params.data.fuelCode
},
valueSetter: (params) => {
if (params.newValue) {
params.data.fuelCode = params.newValue
-
const fuelType = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
- if (/Fuel code/i.test(params.data.provisionOfTheAct)) {
- const matchingFuelCode = fuelType.fuelCodes?.find(
+ if (params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
+ const matchingFuelCode = fuelType?.fuelCodes?.find(
(fuelCode) => params.data.fuelCode === fuelCode.fuelCode
)
if (matchingFuelCode) {
params.data.fuelCodeId = matchingFuelCode.fuelCodeId
}
}
+ } else {
+ // If user clears the value
+ params.data.fuelCode = undefined
+ params.data.fuelCodeId = undefined
}
return true
}
diff --git a/frontend/src/views/OtherUses/AddEditOtherUses.jsx b/frontend/src/views/OtherUses/AddEditOtherUses.jsx
index bbd553ca3..885675c30 100644
--- a/frontend/src/views/OtherUses/AddEditOtherUses.jsx
+++ b/frontend/src/views/OtherUses/AddEditOtherUses.jsx
@@ -1,5 +1,4 @@
-import { BCAlert2 } from '@/components/BCAlert'
-import BCButton from '@/components/BCButton'
+
import { BCGridEditor } from '@/components/BCDataGrid/BCGridEditor'
import Loading from '@/components/Loading'
import {
@@ -8,16 +7,17 @@ import {
useSaveOtherUses
} from '@/hooks/useOtherUses'
import { cleanEmptyStringValues } from '@/utils/formatters'
-import { faFloppyDisk } from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { Stack } from '@mui/material'
import BCTypography from '@/components/BCTypography'
import Grid2 from '@mui/material/Unstable_Grid2/Grid2'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { v4 as uuid } from 'uuid'
-import { defaultColDef, otherUsesColDefs, PROVISION_APPROVED_FUEL_CODE} from './_schema'
+import {
+ defaultColDef,
+ otherUsesColDefs,
+ PROVISION_APPROVED_FUEL_CODE
+} from './_schema'
import * as ROUTES from '@/constants/routes/routes.js'
export const AddEditOtherUses = () => {
@@ -55,31 +55,31 @@ export const AddEditOtherUses = () => {
rows.map((row) => ({
...row,
id: row.id || uuid(),
- isValid: true,
- }));
+ isValid: true
+ }))
- setRowData(ensureRowIds(otherUses));
+ setRowData(ensureRowIds(otherUses))
}
- }, [otherUses]);
+ }, [otherUses])
const findCiOfFuel = useCallback((data, optionsData) => {
- let ciOfFuel = 0;
+ let ciOfFuel = 0
if (data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
const fuelType = optionsData?.fuelTypes?.find(
(obj) => data.fuelType === obj.fuelType
- );
+ )
const fuelCode = fuelType?.fuelCodes?.find(
(item) => item.fuelCode === data.fuelCode
- );
- ciOfFuel = fuelCode?.carbonIntensity || 0;
+ )
+ ciOfFuel = fuelCode?.carbonIntensity || 0
} else {
const fuelType = optionsData?.fuelTypes?.find(
(obj) => data.fuelType === obj.fuelType
- );
- ciOfFuel = fuelType?.defaultCarbonIntensity || 0;
+ )
+ ciOfFuel = fuelType?.defaultCarbonIntensity || 0
}
- return ciOfFuel;
- }, []);
+ return ciOfFuel
+ }, [])
const onGridReady = (params) => {
const ensureRowIds = (rows) => {
@@ -151,20 +151,39 @@ export const AddEditOtherUses = () => {
const ciOfFuel = findCiOfFuel(params.data, optionsData)
params.node.setDataValue('ciOfFuel', ciOfFuel)
- // Auto-populate the "Unit" field based on the selected fuel type
- if (params.colDef.field === 'fuelType') {
- const fuelType = optionsData?.fuelTypes?.find(
- (obj) => params.data.fuelType === obj.fuelType
- );
- if (fuelType && fuelType.units) {
- params.node.setDataValue('units', fuelType.units);
- } else {
- params.node.setDataValue('units', '');
+ // Auto-populate fields based on the selected fuel type
+ if (params.colDef.field === 'fuelType') {
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ );
+ if (fuelType) {
+ // Auto-populate the "units" field
+ if (fuelType.units) {
+ params.node.setDataValue('units', fuelType.units);
+ } else {
+ params.node.setDataValue('units', '');
+ }
+
+ // Auto-populate the "fuelCategory" field
+ const fuelCategoryOptions = fuelType.fuelCategories.map(
+ (item) => item.category
+ );
+ params.node.setDataValue('fuelCategory', fuelCategoryOptions[0] ?? null);
+
+ // Auto-populate the "fuelCode" field
+ const fuelCodeOptions = fuelType.fuelCodes.map(
+ (code) => code.fuelCode
+ );
+ params.node.setDataValue('fuelCode', fuelCodeOptions[0] ?? null);
+ params.node.setDataValue(
+ 'fuelCodeId',
+ fuelType.fuelCodes[0]?.fuelCodeId ?? null
+ );
+ }
}
}
- }
},
- [optionsData]
+ [optionsData, findCiOfFuel]
)
const onCellEditingStopped = useCallback(
@@ -181,6 +200,29 @@ export const AddEditOtherUses = () => {
// clean up any null or empty string values
let updatedData = cleanEmptyStringValues(params.data)
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ if (isFuelCodeScenario && !updatedData.fuelCode) {
+ // Fuel code is required but not provided
+ setErrors((prevErrors) => ({
+ ...prevErrors,
+ [params.node.data.id]: ['fuelCode']
+ }))
+
+ alertRef.current?.triggerAlert({
+ message: t('otherUses:fuelCodeFieldRequiredError'),
+ severity: 'error'
+ })
+
+ updatedData = {
+ ...updatedData,
+ validationStatus: 'error'
+ }
+
+ params.node.updateData(updatedData)
+ return // Stop execution, do not proceed to save
+ }
+
try {
setErrors({})
await saveRow(updatedData)
diff --git a/frontend/src/views/OtherUses/_schema.jsx b/frontend/src/views/OtherUses/_schema.jsx
index 9af82de0b..03ce751fb 100644
--- a/frontend/src/views/OtherUses/_schema.jsx
+++ b/frontend/src/views/OtherUses/_schema.jsx
@@ -23,7 +23,7 @@ export const otherUsesColDefs = (optionsData, errors) => [
hide: true
},
{
- field:'otherUsesId',
+ field: 'otherUsesId',
hide: true
},
{
@@ -42,19 +42,32 @@ export const otherUsesColDefs = (optionsData, errors) => [
suppressKeyboardEvent,
cellRenderer: (params) =>
params.value || Select,
- cellStyle: (params) => StandardCellErrors(params, errors)
+ cellStyle: (params) => StandardCellErrors(params, errors),
+ valueSetter: (params) => {
+ if (params.newValue) {
+ // TODO: Evaluate if additional fields need to be reset when fuel type changes
+ params.data.fuelType = params.newValue
+ params.data.fuelCode = undefined
+ }
+ return true
+ }
},
{
field: 'fuelCategory',
headerName: i18n.t('otherUses:otherUsesColLabels.fuelCategory'),
headerComponent: RequiredHeader,
cellEditor: AutocompleteCellEditor,
- cellEditorParams: {
- options: optionsData.fuelCategories.map((obj) => obj.category),
- multiple: false,
- disableCloseOnSelect: false,
- freeSolo: false,
- openOnFocus: true
+ cellEditorParams: (params) => {
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ );
+ return {
+ options: fuelType ? fuelType.fuelCategories.map((item) => item.category) : [],
+ multiple: false,
+ disableCloseOnSelect: false,
+ freeSolo: false,
+ openOnFocus: true
+ };
},
suppressKeyboardEvent,
cellRenderer: (params) =>
@@ -65,9 +78,7 @@ export const otherUsesColDefs = (optionsData, errors) => [
{
field: 'provisionOfTheAct',
headerComponent: RequiredHeader,
- headerName: i18n.t(
- 'otherUses:otherUsesColLabels.provisionOfTheAct'
- ),
+ headerName: i18n.t('otherUses:otherUsesColLabels.provisionOfTheAct'),
cellEditor: 'agSelectCellEditor',
cellEditorParams: (params) => {
const fuelType = optionsData?.fuelTypes?.find(
@@ -89,11 +100,11 @@ export const otherUsesColDefs = (optionsData, errors) => [
suppressKeyboardEvent,
valueSetter: (params) => {
if (params.newValue !== params.oldValue) {
- params.data.provisionOfTheAct = params.newValue;
- params.data.fuelCode = ''; // Reset fuelCode when provisionOfTheAct changes
- return true;
+ params.data.provisionOfTheAct = params.newValue
+ params.data.fuelCode = '' // Reset fuelCode when provisionOfTheAct changes
+ return true
}
- return false;
+ return false
},
minWidth: 300,
editable: true,
@@ -105,61 +116,91 @@ export const otherUsesColDefs = (optionsData, errors) => [
headerName: i18n.t('otherUses:otherUsesColLabels.fuelCode'),
cellEditor: AutocompleteCellEditor,
cellEditorParams: (params) => {
- const fuelType = optionsData?.fuelTypes?.find((obj) => params.data.fuelType === obj.fuelType);
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
- return {
- options: fuelType?.fuelCodes?.map((item) => item.fuelCode) || [], // Safely access fuelCodes
- multiple: false,
- disableCloseOnSelect: false,
- freeSolo: false,
- openOnFocus: true
- };
+ return {
+ options: fuelType?.fuelCodes?.map((item) => item.fuelCode) || [], // Safely access fuelCodes
+ multiple: false,
+ disableCloseOnSelect: false,
+ freeSolo: false,
+ openOnFocus: true
+ }
},
cellRenderer: (params) => {
- const fuelType = optionsData?.fuelTypes?.find((obj) => params.data.fuelType === obj.fuelType);
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
if (
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
fuelType?.fuelCodes?.length > 0
) {
- return params.value || Select;
+ return (
+ params.value || Select
+ )
}
- return null;
+ return null
},
cellStyle: (params) => {
- const style = StandardCellErrors(params, errors);
- const conditionalStyle =
- params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
- optionsData?.fuelTypes
- ?.find((obj) => params.data.fuelType === obj.fuelType)
- ?.fuelCodes?.length > 0
- ? { backgroundColor: '#fff', borderColor: 'unset' }
- : { backgroundColor: '#f2f2f2' };
- return { ...style, ...conditionalStyle };
+ const style = StandardCellErrors(params, errors)
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes = fuelType?.fuelCodes || []
+ const fuelCodeRequiredAndMissing =
+ isFuelCodeScenario && !params.data.fuelCode
+
+ // If required and missing, show red border
+ if (fuelCodeRequiredAndMissing) {
+ style.borderColor = 'red'
+ }
+
+ const conditionalStyle =
+ isFuelCodeScenario &&
+ fuelCodes.length > 0 &&
+ !fuelCodeRequiredAndMissing
+ ? {
+ backgroundColor: '#fff',
+ borderColor: style.borderColor || 'unset'
+ }
+ : { backgroundColor: '#f2f2f2' }
+
+ return { ...style, ...conditionalStyle }
},
suppressKeyboardEvent,
minWidth: 150,
editable: (params) => {
- const fuelType = optionsData?.fuelTypes?.find((obj) => params.data.fuelType === obj.fuelType);
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
return (
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
fuelType?.fuelCodes?.length > 0
- );
+ )
},
- valueSetter: (params) => {
- if (params.newValue) {
- params.data.fuelCode = params.newValue;
+ valueGetter: (params) => {
+ const isFuelCodeScenario =
+ params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
+ const fuelType = optionsData?.fuelTypes?.find(
+ (obj) => params.data.fuelType === obj.fuelType
+ )
+ const fuelCodes = fuelType?.fuelCodes || []
- const fuelType = optionsData?.fuelTypes?.find((obj) => params.data.fuelType === obj.fuelType);
- if (params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
- const matchingFuelCode = fuelType?.fuelCodes?.find(
- (fuelCode) => params.data.fuelCode === fuelCode.fuelCode
- );
- if (matchingFuelCode) {
- params.data.fuelCodeId = matchingFuelCode.fuelCodeId;
- }
- }
- }
- return true;
+ if (
+ isFuelCodeScenario &&
+ !params.data.fuelCode &&
+ fuelCodes.length === 1
+ ) {
+ // Autopopulate if only one fuel code is available
+ const singleFuelCode = fuelCodes[0]
+ params.data.fuelCode = singleFuelCode.fuelCode
+ params.data.fuelCodeId = singleFuelCode.fuelCodeId
+ }
+
+ return params.data.fuelCode
},
tooltipValueGetter: (p) => 'Select the approved fuel code'
},
@@ -207,31 +248,31 @@ export const otherUsesColDefs = (optionsData, errors) => [
valueGetter: (params) => {
const fuelType = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
- );
+ )
if (params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
return (
fuelType?.fuelCodes?.find(
(item) => item.fuelCode === params.data.fuelCode
)?.carbonIntensity || 0
- );
+ )
}
if (fuelType) {
if (params.data.fuelType === 'Other' && params.data.fuelCategory) {
- const categories = fuelType.fuelCategories;
+ const categories = fuelType.fuelCategories
const defaultCI = categories?.find(
(cat) => cat.category === params.data.fuelCategory
- )?.defaultAndPrescribedCi;
+ )?.defaultAndPrescribedCi
- return defaultCI || 0;
+ return defaultCI || 0
}
- return fuelType.defaultCarbonIntensity || 0;
+ return fuelType.defaultCarbonIntensity || 0
}
- return 0;
+ return 0
},
- minWidth: 150,
+ minWidth: 150
},
{
field: 'expectedUse',