diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f9da50897..78150510c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,7 @@ "material-react-table": "^2.13.3", "mui-nested-menu": "^3.4.0", "oidc-client-ts": "^3.1.0", - "onroute-policy-engine": "^1.4.1", + "onroute-policy-engine": "^1.5.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.1.2", @@ -7661,9 +7661,9 @@ } }, "node_modules/onroute-policy-engine": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/onroute-policy-engine/-/onroute-policy-engine-1.4.1.tgz", - "integrity": "sha512-NSlwb9j2IK6hSk2LOiP02pno51h19t/sCYNUsEpzRww3CPOVOeDw8N4zer7D3tbwDCA9EBkvaLKQh7c8v8nqFQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/onroute-policy-engine/-/onroute-policy-engine-1.5.0.tgz", + "integrity": "sha512-MAbbwtyJUGssrSOepZT0XpyfIKDaCDcNH3QxeybfvnGdd4Ly0sc+WawZoxraZJyo0dVDqt/5tFs8JAdGQQCdYw==", "dependencies": { "dayjs": "^1.11.13", "json-rules-engine": "^7.2.1", @@ -14923,9 +14923,9 @@ } }, "onroute-policy-engine": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/onroute-policy-engine/-/onroute-policy-engine-1.4.1.tgz", - "integrity": "sha512-NSlwb9j2IK6hSk2LOiP02pno51h19t/sCYNUsEpzRww3CPOVOeDw8N4zer7D3tbwDCA9EBkvaLKQh7c8v8nqFQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/onroute-policy-engine/-/onroute-policy-engine-1.5.0.tgz", + "integrity": "sha512-MAbbwtyJUGssrSOepZT0XpyfIKDaCDcNH3QxeybfvnGdd4Ly0sc+WawZoxraZJyo0dVDqt/5tFs8JAdGQQCdYw==", "requires": { "dayjs": "^1.11.13", "json-rules-engine": "^7.2.1", diff --git a/frontend/package.json b/frontend/package.json index da1d2aaaa..25caf20e9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,7 @@ "material-react-table": "^2.13.3", "mui-nested-menu": "^3.4.0", "oidc-client-ts": "^3.1.0", - "onroute-policy-engine": "^1.4.1", + "onroute-policy-engine": "^1.5.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.1.2", diff --git a/frontend/public/Bridge_Formula_Calculation_Tool_Diagram.png b/frontend/public/Bridge_Formula_Calculation_Tool_Diagram.png new file mode 100644 index 000000000..51bb57ba7 Binary files /dev/null and b/frontend/public/Bridge_Formula_Calculation_Tool_Diagram.png differ diff --git a/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.scss b/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.scss index 36d34023f..caa3dc5f4 100644 --- a/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.scss +++ b/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.scss @@ -13,5 +13,10 @@ &__info-banner { font-weight: normal; + width: 100%; + } + + &__image { + width: 100%; } } \ No newline at end of file diff --git a/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.tsx b/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.tsx index 1f259ffca..03a93bf92 100644 --- a/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.tsx +++ b/frontend/src/features/bridgeFormulaCalculationTool/BFCTDashboard.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Banner } from "../../common/components/dashboard/components/banner/Banner"; import { getNavButtonTitle, @@ -9,7 +8,6 @@ import { InfoBcGovBanner } from "../../common/components/banners/InfoBcGovBanner import { BANNER_MESSAGES } from "../../common/constants/bannerMessages"; import "./BFCTDashboard.scss"; import { BridgeFormulaCalculationTool } from "./components/BridgeFormulaCalculationTool"; -import { BFCT } from "./components/BFCT"; export const BFCTDashboard = () => { return ( @@ -28,13 +26,13 @@ export const BFCTDashboard = () => { className="bfct-dashboard__info-banner" msg={BANNER_MESSAGES.BRIDGE_FORMULA_CALCULATION_TOOL} /> - - {/* */} ); diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/BFCT.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/BFCT.tsx deleted file mode 100644 index 1c439a0a4..000000000 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/BFCT.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { - Controller, - FormProvider, - useFieldArray, - useForm, -} from "react-hook-form"; -import "./BridgeFormulaCalculationTool.scss"; -import { NumberInput } from "../../../common/components/form/subFormComponents/NumberInput"; -import { RequiredOrNull } from "../../../common/types/common"; -import { getDefaultRequiredVal } from "../../../common/helpers/util"; -import { convertToNumberIfValid } from "../../../common/helpers/numeric/convertToNumberIfValid"; -import { requiredMessage } from "../../../common/helpers/validationMessages"; -import { useEffect } from "react"; -import { AxleUnit } from "../types/AxleUnit"; -import { BFCTTable } from "./BFCTTable"; - -export const BFCT = () => { - const defaultAxleUnits = [ - { - numberOfAxles: null, - axleSpread: null, - axleUnitWeight: null, - }, - { - interaxleSpacing: null, - }, - { - numberOfAxles: null, - axleSpread: null, - axleUnitWeight: null, - }, - ]; - - const formMethods = useForm<{ - axleUnits: AxleUnit[]; - }>({ - defaultValues: { - axleUnits: defaultAxleUnits, - }, - }); - - const fieldArrayMethods = useFieldArray({ - control: formMethods.control, - name: "axleUnits", - }); - - const addAxleUnit = () => { - fieldArrayMethods.append({ - interaxleSpacing: null, - }); - fieldArrayMethods.append({ - numberOfAxles: null, - axleSpread: null, - axleUnitWeight: null, - }); - }; - - const combineInteraxleSpacing = (axleUnits: AxleUnit[]) => { - for (let i = 1; i < axleUnits.length - 1; i++) { - axleUnits[i + 1].interaxleSpacing = axleUnits[i].interaxleSpacing; - axleUnits.splice(i, 1); - } - return axleUnits; - }; - - const onSubmit = (data: { axleUnits: AxleUnit[] }) => { - console.log(combineInteraxleSpacing(data.axleUnits)); - }; - - return ( -
- - - -
- ); -}; diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/BFCTTable.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/BFCTTable.tsx deleted file mode 100644 index 497d67a14..000000000 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/BFCTTable.tsx +++ /dev/null @@ -1,135 +0,0 @@ -// TODO delete this file -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { useEffect, useMemo, useState } from "react"; - -import { - MRT_Cell, - MRT_ColumnDef, - MRT_Row, - MRT_RowSelectionState, - MaterialReactTable, - useMaterialReactTable, -} from "material-react-table"; - -import { - applyWhenNotNullable, - getDefaultRequiredVal, -} from "../../../common/helpers/util"; - -import { - defaultTableInitialStateOptions, - defaultTableOptions, - defaultTableStateOptions, -} from "../../../common/helpers/tableHelper"; -import { CustomFormComponent } from "../../../common/components/form/CustomFormComponents"; -import { - FieldArrayWithId, - useFieldArray, - useFormContext, -} from "react-hook-form"; -import { Checkbox, FormControlLabel } from "@mui/material"; -import { requiredMessage } from "../../../common/helpers/validationMessages"; -import { AxleUnit } from "../types/AxleUnit"; - -export const BFCTTable = () => { - const formMethods = useFormContext(); - const { control, register, watch, setValue, trigger, getValues } = - formMethods; - const { fields } = useFieldArray({ control, name: "axleUnits" }); - - const axleUnits = getValues("axleUnits"); - - const columns = useMemo[]>( - () => [ - { - accessorKey: "property", - header: "Property", - }, - ...getValues("axleUnits").map( - ( - field: FieldArrayWithId< - { axleUnits: AxleUnit[] }, - "axleUnits", - "id" - >[], - index: number, - ) => ({ - accessorKey: `Axle ${index + 1}`, - header: `Axle ${index + 1}`, - }), - ), - ], - [], - ); - - const rowData = [ - { - property: "numberOfAxles", - ...Object.fromEntries( - axleUnits.map((unit, index) => [ - `Axle ${index + 1}`, - unit.numberOfAxles, - ]), - ), - }, - { - property: "axleSpread", - ...Object.fromEntries( - axleUnits.map((unit, index) => [`Axle ${index + 1}`, unit.axleSpread]), - ), - }, - { - property: "axleUnitWeight", - ...Object.fromEntries( - axleUnits.map((unit, index) => [ - `Axle ${index + 1}`, - unit.axleUnitWeight, - ]), - ), - }, - ]; - - const table = useMaterialReactTable({ - ...defaultTableOptions, - columns: columns, - data: validTransactionHistory, - onRowSelectionChange: setRowSelection, - state: { ...defaultTableStateOptions, rowSelection }, - initialState: { - ...defaultTableInitialStateOptions, - showGlobalFilter: false, - columnVisibility: { chequeRefund: totalRefundDue !== 0 }, - }, - getRowId: (row: RefundFormData) => row.permitNumber, - displayColumnDefOptions: { - "mrt-row-select": { - size: 10, - header: "", - }, - }, - enableRowActions: false, - enableGlobalFilter: false, - enableTopToolbar: false, - enableBottomToolbar: false, - enableRowSelection: (row: MRT_Row) => isRowSelectable(row), - enableSelectAll: false, - muiSelectCheckboxProps: ({ row }: { row: MRT_Row }) => ({ - className: `transaction-history-table__checkbox ${!isRowSelectable(row) && "transaction-history-table__checkbox--disabled"}`, - }), - muiTablePaperProps: { - className: "transaction-history-table", - }, - muiTableContainerProps: { - className: "transaction-history-table__container", - }, - muiTableBodyRowProps: ({ row }) => ({ - className: `transaction-history-table__row ${row.getIsSelected() && "transaction-history-table__row--selected"}`, - }), - }); - - return ( - <> - - - ); -}; diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.scss b/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.scss index ec0829de3..753423998 100644 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.scss +++ b/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.scss @@ -7,7 +7,7 @@ display: flex; align-items: start; gap: 1rem; - overflow: scroll; + overflow-x: auto; } .table { @@ -28,6 +28,7 @@ min-width: 5rem; max-width: 5rem; } + } .column, .row { @@ -40,6 +41,17 @@ font-size: 1rem; color: $bc-black; white-space: nowrap; + + &:first-child { + position: sticky; + left: 0; + z-index: 1; + } + + &--fail { + background-color: $bc-messages-red-background; + color: $bc-red; + } } } @@ -55,7 +67,8 @@ .button-container { display: flex; gap: 1.5rem; - padding: 1.5rem 0; + padding-top: 1.5rem; + padding-bottom: 2.5rem; } .button-icon { @@ -69,16 +82,19 @@ box-shadow: none; min-width: fit-content; - &:hover { + &:hover, &:focus { box-shadow: none; } &--add { - margin-top: 1rem; + display: flex; + align-items: center; + gap: .5rem; + margin-top: 1.5rem; color: $bc-black; border: 2px solid $bc-text-box-border-grey; - &:hover { + &:hover, &:focus { background-color: $bc-background-light-grey; border: 2px solid $bc-text-box-border-grey; } @@ -88,7 +104,7 @@ border: 2px solid $bc-black; border-radius: 4px; - &:hover { + &:hover, &:focus { background: $bc-border-grey; } } @@ -98,7 +114,7 @@ background-color: $bc-background-light-grey; border: 2px solid $bc-background-light-grey; - &:hover { + &:hover, &:focus { background-color: $bc-background-light-grey; border-color: $bc-text-box-border-grey; } @@ -110,5 +126,22 @@ } } + .results { + border-top: 1px solid $bc-border-grey; + padding: 2.5rem 0; + &__heading { + color: $bc-black; + margin: 0; + } + + &__text { + &--fail { + color: $bc-red; + } + &--success { + color: $bc-green; + } + } + } } \ No newline at end of file diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.tsx index 6846f6aa2..b3819af12 100644 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.tsx +++ b/frontend/src/features/bridgeFormulaCalculationTool/components/BridgeFormulaCalculationTool.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { Button, IconButton, Tooltip } from "@mui/material"; -import { faMinus } from "@fortawesome/free-solid-svg-icons"; +import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Controller, useFieldArray, useForm } from "react-hook-form"; @@ -10,6 +10,8 @@ import { RequiredOrNull } from "../../../common/types/common"; import { getDefaultRequiredVal } from "../../../common/helpers/util"; import { convertToNumberIfValid } from "../../../common/helpers/numeric/convertToNumberIfValid"; import { RemoveAxleUnitModal } from "./RemoveAxleUnitModal"; +import { ResetModal } from "./ResetModal"; +import { usePolicyEngine } from "../../policy/hooks/usePolicyEngine"; interface AxleUnit { numberOfAxles?: RequiredOrNull; @@ -20,8 +22,28 @@ interface AxleUnit { tireSize?: RequiredOrNull; } +// the type expected by the caculateBridge function in the policy engine +interface AxleConfiguration { + numberOfAxles: number; + axleSpread?: number; + interaxleSpacing?: number; + axleUnitWeight: number; + numberOfTires?: number; + tireSize?: number; +} + +interface BridgeCalculationResult { + startAxleUnit: number; + endAxleUnit: number; + maxBridge: number; + actualWeight: number; + success: boolean; +} + export const BridgeFormulaCalculationTool = () => { - const { control, handleSubmit, watch, setValue, reset } = useForm<{ + const policy = usePolicyEngine(); + + const { control, handleSubmit, watch, setValue, reset, formState } = useForm<{ axleUnits: AxleUnit[]; }>({ defaultValues: { @@ -61,24 +83,55 @@ export const BridgeFormulaCalculationTool = () => { }); }; + const [isRemoveAxleUnitModalOpen, setIsRemoveAxleUnitModalOpen] = + useState(false); + + const [axleUnitsToRemove, setAxleUnitsToRemove] = useState(); + + const handleRemoveAxleUnits = () => { + remove(axleUnitsToRemove); + setIsRemoveAxleUnitModalOpen(false); + }; + const handleCloseRemoveAxleUnitModal = () => { setAxleUnitsToRemove([]); setIsRemoveAxleUnitModalOpen(false); }; - const [axleUnitsToRemove, setAxleUnitsToRemove] = useState(); - const handleRemoveAxleUnitButton = (index: number) => { setAxleUnitsToRemove([index, index - 1]); setIsRemoveAxleUnitModalOpen(true); }; - const handleRemoveAxleUnits = () => { - remove(axleUnitsToRemove); - setIsRemoveAxleUnitModalOpen(false); + const [isResetModalOpen, setIsResetModalOpen] = useState(false); + + const handleReset = () => { + reset(); + setBridgeCalculationResults([]); + setIsResetModalOpen(false); }; - const combineInteraxleSpacing = (axleUnits: AxleUnit[]) => { + const [bridgeCalculationResults, setBridgeCalculationResults] = useState< + BridgeCalculationResult[] + >([]); + + const failedBridgeCalculationResults = bridgeCalculationResults.filter( + (result) => !result.success, + ); + + const bridgeCalculationSuccess = bridgeCalculationResults.length + ? bridgeCalculationResults.every((result) => result.success) + : false; + + const shouldShowResultsSection = + formState.errors.axleUnits?.length || + failedBridgeCalculationResults.length || + bridgeCalculationSuccess; + + const getFailedResultText = (failedResult: BridgeCalculationResult) => + `⮾ Bridge calculation failed between Axle Unit ${failedResult.startAxleUnit} and ${failedResult.endAxleUnit}, Axle Group Weight is ${failedResult.actualWeight}, Bridge Formula Weight max is ${failedResult.maxBridge}.`; + + const mergeInteraxleSpacingColumn = (axleUnits: AxleUnit[]) => { for (let i = 1; i < axleUnits.length - 1; i++) { axleUnits[i + 1].interaxleSpacing = axleUnits[i].interaxleSpacing; axleUnits.splice(i, 1); @@ -86,16 +139,50 @@ export const BridgeFormulaCalculationTool = () => { return axleUnits; }; - const handleReset = () => { - reset(); + const convertMetreValuesToCentimetres = (axleUnit: AxleUnit) => { + return { + ...axleUnit, + axleSpread: axleUnit.axleSpread + ? Math.round(axleUnit.axleSpread * 100) + : axleUnit.axleSpread, + interaxleSpacing: axleUnit.interaxleSpacing + ? Math.round(axleUnit.interaxleSpacing * 100) + : axleUnit.interaxleSpacing, + }; }; - const onSubmit = (data: { axleUnits: AxleUnit[] }) => { - console.log(combineInteraxleSpacing(data.axleUnits)); + const getDefaultAxleConfiguration = ( + axleUnit: AxleUnit, + ): AxleConfiguration => { + return { + numberOfAxles: getDefaultRequiredVal(0, axleUnit.numberOfAxles), + axleSpread: getDefaultRequiredVal(undefined, axleUnit.axleSpread), + interaxleSpacing: getDefaultRequiredVal( + undefined, + axleUnit.interaxleSpacing, + ), + axleUnitWeight: getDefaultRequiredVal(0, axleUnit.axleUnitWeight), + }; }; - const [isRemoveAxleUnitModalOpen, setIsRemoveAxleUnitModalOpen] = - useState(false); + const onSubmit = (data: { axleUnits: AxleUnit[] }) => { + const mergedAxleUnitData = mergeInteraxleSpacingColumn(data.axleUnits); + + const convertedAxleUnitData = mergedAxleUnitData.map((axleUnit) => + convertMetreValuesToCentimetres(axleUnit), + ); + + const serializedAxleUnitData = convertedAxleUnitData.map((axleUnit) => + getDefaultAxleConfiguration(axleUnit), + ); + + const bridgeCalculationResults = policy?.calculateBridge( + serializedAxleUnitData, + ); + + bridgeCalculationResults && + setBridgeCalculationResults(bridgeCalculationResults); + }; return (
@@ -105,11 +192,34 @@ export const BridgeFormulaCalculationTool = () => { Axle Unit {fields.map((field, index) => { - // Ensure the column number is shown for every axle unit (even and odd index) + const axleUnitNumber = Math.floor(index / 2) + 1; + + // Check if this axle unit fails based on failedBridgeCalculationResults + const axleUnitFailure = failedBridgeCalculationResults.some( + (result) => + axleUnitNumber >= result.startAxleUnit && + axleUnitNumber <= result.endAxleUnit, + ); + + // Check if the spacing column (odd index) is between two failing axle units + const spacingColumnFailure = + index % 2 === 1 && + failedBridgeCalculationResults.some( + (result) => + axleUnitNumber >= result.startAxleUnit && + axleUnitNumber < result.endAxleUnit, + ); + if (index % 2 === 0) { - const axleUnitNumber = Math.floor(index / 2) + 1; return ( - +
{axleUnitNumber} {axleUnitNumber >= 3 && ( @@ -131,8 +241,15 @@ export const BridgeFormulaCalculationTool = () => { ); } else { return ( - - {/* Blank column for odd indices */} + + {/* Blank column for interaxleSpacing */} ); } @@ -147,6 +264,7 @@ export const BridgeFormulaCalculationTool = () => { const axleSpreadFieldName = `axleUnits.${index}.axleSpread` as const; const numberOfAxles = axleUnits[index].numberOfAxles; + return ( {index % 2 === 0 && ( @@ -194,14 +312,10 @@ export const BridgeFormulaCalculationTool = () => { const axleSpread = axleUnits[index].axleSpread; const numberOfAxles = axleUnits[index].numberOfAxles; const disabled = numberOfAxles === 1; + return ( {index % 2 === 0 && ( - // { }} render={({ fieldState: { invalid } }) => ( @@ -240,6 +355,7 @@ export const BridgeFormulaCalculationTool = () => { const fieldName = `axleUnits.${index}.interaxleSpacing` as const; const interaxleSpacing = axleUnits[index].interaxleSpacing; + return ( {index % 2 !== 0 && ( @@ -252,6 +368,7 @@ export const BridgeFormulaCalculationTool = () => { }} render={({ fieldState: { invalid } }) => ( { {fields.map((field, index) => { const fieldName = `axleUnits.${index}.axleUnitWeight` as const; const axleUnitWeight = axleUnits[index].axleUnitWeight; + return ( {index % 2 === 0 && ( @@ -293,6 +411,7 @@ export const BridgeFormulaCalculationTool = () => { }} render={({ fieldState: { invalid } }) => ( { onClick={handleAddAxleUnit} className="button button--add" > - + Add Axle Unit + + Add Axle Unit
+ {shouldShowResultsSection && ( +
+

+ Bridge Formula Calculation Results +

+ + {formState.errors.axleUnits?.length ? ( +

Insufficient and/or invalid data.

+ ) : failedBridgeCalculationResults.length ? ( + failedBridgeCalculationResults.map((failedResult, index) => ( +

+ {getFailedResultText(failedResult)} +

+ )) + ) : ( +

+ ✓ Bridge Calculation is ok. +

+ )} +
+ )} + + + setIsResetModalOpen(false)} + onConfirm={handleReset} + />
); }; diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/RefundPage.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/RefundPage.tsx deleted file mode 100644 index b95dd9c18..000000000 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/RefundPage.tsx +++ /dev/null @@ -1,207 +0,0 @@ -// TODO delete this file -// @ts-nocheck -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { FormProvider, useForm } from "react-hook-form"; -import { Button, Typography } from "@mui/material"; -import "./RefundPage.scss"; -import { RefundFormData } from "./types/RefundFormData"; -import { PermitHistory } from "../../types/PermitHistory"; -import { TransactionHistoryTable } from "./components/TransactionHistoryTable"; -import { calculateNetAmount } from "../../helpers/feeSummary"; -import { isValidTransaction } from "../../helpers/payment"; -import { Nullable } from "../../../../common/types/common"; -import { RefundDetails } from "./components/RefundDetails"; -import { useState } from "react"; -import { RefundErrorModal } from "./components/RefundErrorModal"; -import { MRT_RowSelectionState } from "material-react-table"; - -export const PERMIT_REFUND_ACTIONS = { - VOID: "void", - REVOKE: "revoke", - AMEND: "amend", -} as const; - -export type PermitAction = - (typeof PERMIT_REFUND_ACTIONS)[keyof typeof PERMIT_REFUND_ACTIONS]; - -const permitActionText = (permitAction: PermitAction) => { - switch (permitAction) { - case PERMIT_REFUND_ACTIONS.VOID: - return "Voiding"; - case PERMIT_REFUND_ACTIONS.REVOKE: - return "Revoking"; - case PERMIT_REFUND_ACTIONS.AMEND: - default: - return "Amending"; - } -}; - -export const RefundPage = ({ - permitHistory, - email, - additionalEmail, - fax, - reason, - permitNumber, - permitAction, - amountToRefund, - handleFinish, - disableSubmitButton, -}: { - permitHistory: PermitHistory[]; - email?: Nullable; - additionalEmail?: Nullable; - fax?: Nullable; - reason?: Nullable; - permitNumber?: Nullable; - permitAction: PermitAction; - amountToRefund: number; - handleFinish: (refundData: RefundFormData[]) => void; - disableSubmitButton: boolean; -}) => { - const currentPermitValue = calculateNetAmount(permitHistory); - const newPermitValue = currentPermitValue - Math.abs(amountToRefund); - - const validTransactionHistory = permitHistory.filter((history) => - isValidTransaction(history.paymentMethodTypeCode, history.pgApproved), - ); - - const [showRefundErrorModal, setShowRefundErrorModal] = - useState(false); - - const handleCloseRefundErrorModal = () => { - setShowRefundErrorModal(false); - }; - - const formMethods = useForm<{ - refundData: RefundFormData[]; - }>({ - defaultValues: { - refundData: validTransactionHistory.map((transaction) => ({ - permitNumber: transaction.permitNumber, - pgPaymentMethod: transaction.pgPaymentMethod, - pgTransactionId: transaction.pgTransactionId, - transactionOrderNumber: transaction.transactionOrderNumber, - transactionTypeId: transaction.transactionTypeId, - paymentCardTypeCode: transaction.paymentCardTypeCode, - paymentMethodTypeCode: transaction.paymentMethodTypeCode, - transactionAmount: transaction.transactionAmount, - refundAmount: "", - refundTransactionId: "", - chequeRefund: false, - })), - }, - reValidateMode: "onChange", - }); - - const { handleSubmit } = formMethods; - - const [rowSelection, setRowSelection] = useState({}); - - const onSubmit = (data: { refundData: RefundFormData[] }) => { - if (amountToRefund <= 0) { - handleFinish(data.refundData); - } else { - // Get the selected row IDs based on permitNumber from rowSelection - const selectedRowIds = Object.keys(rowSelection).filter( - (id) => rowSelection[id], - ); - - // Filter table data to include only selected rows based on permitNumber - const selectedTransactions = data.refundData.filter( - (transaction: RefundFormData) => - selectedRowIds.includes(transaction.permitNumber), - ); - - // Call the onSubmit with the selected transactions - handleFinish(selectedTransactions); - } - }; - - return ( -
- - {permitActionText(permitAction)} Permit #: {permitNumber} - - - - Transaction History - - - - {(permitAction === PERMIT_REFUND_ACTIONS.VOID || - permitAction === PERMIT_REFUND_ACTIONS.REVOKE) && ( -
-
-
- Send Permit and Receipt to -
- {email && ( -
- Company Email: - - {email} - -
- )} - {additionalEmail && ( -
- Additional Email: - - {additionalEmail} - -
- )} - {fax && ( -
- Fax: - - {fax} - -
- )} -
- {reason && ( -
-
- Reason for {permitActionText(permitAction)} -
-
{reason}
-
- )} -
- )} - - -
- - {showRefundErrorModal && ( - - )} -
- ); -}; diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/RemoveAxleUnitModal.scss b/frontend/src/features/bridgeFormulaCalculationTool/components/RemoveAxleUnitModal.scss index 57082a741..c425e3685 100644 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/RemoveAxleUnitModal.scss +++ b/frontend/src/features/bridgeFormulaCalculationTool/components/RemoveAxleUnitModal.scss @@ -41,7 +41,7 @@ & &__button { box-shadow: none; - &:hover { + &:hover, &:focus { box-shadow: none; } @@ -51,7 +51,7 @@ border-radius: 4px; color: $bc-black; - &:hover { + &:hover, &:focus { background-color: $bc-background-light-grey; border-color: $bc-text-box-border-grey; } diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.scss b/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.scss new file mode 100644 index 000000000..8be37be38 --- /dev/null +++ b/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.scss @@ -0,0 +1,63 @@ +@import "../../../themes/orbcStyles"; + +.reset-modal { + & &__container { + width: 100%; + display: flex; + flex-direction: column; + } + + &__header { + padding: 2rem 1.5rem; + display: flex; + flex-direction: row; + align-items: center; + background-color: $bc-background-light-grey; + border-bottom: 1px solid $bc-border-grey; + } + + &__title { + font-weight: bold; + font-size: 1.5rem; + margin: 0 0 0 0.5em; + } + + &__body { + padding: 1.5rem; + } + + &__text { + font-size: 1rem; + } + + &__footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding: 0 1.5rem 1.5rem 1.5rem; + gap: 1.5rem; + } + + & &__button { + box-shadow: none; + + &:hover, &:focus { + box-shadow: none; + } + + &--cancel { + background-color: $bc-background-light-grey; + border: 2px solid $bc-background-light-grey; + border-radius: 4px; + color: $bc-black; + + &:hover, &:focus { + background-color: $bc-background-light-grey; + border-color: $bc-text-box-border-grey; + } + } + &--reset { + font-weight: bold; + } + } +} diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.tsx new file mode 100644 index 000000000..d02cfa7ab --- /dev/null +++ b/frontend/src/features/bridgeFormulaCalculationTool/components/ResetModal.tsx @@ -0,0 +1,58 @@ +import { Button, Dialog } from "@mui/material"; +import "./ResetModal.scss"; + +export const ResetModal = ({ + isOpen, + onCancel, + onConfirm, +}: { + /** + * Boolean to control the open and close state of Dialog box. + */ + isOpen: boolean; + /** + * A callback function on clicking cancel button. + * @returns void + */ + onCancel: () => void; + onConfirm: () => void; +}) => { + return ( + +
+

Reset all?

+
+ +
+

+ Reset will remove all data. This action cannot be undone. +

+
+ +
+ + +
+
+ ); +}; diff --git a/frontend/src/features/bridgeFormulaCalculationTool/components/TransactionHistoryTable.tsx b/frontend/src/features/bridgeFormulaCalculationTool/components/TransactionHistoryTable.tsx deleted file mode 100644 index 3505cae0d..000000000 --- a/frontend/src/features/bridgeFormulaCalculationTool/components/TransactionHistoryTable.tsx +++ /dev/null @@ -1,368 +0,0 @@ -// TODO delete this file -// @ts-nocheck -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { useEffect, useMemo, useState } from "react"; - -import { - MRT_Cell, - MRT_ColumnDef, - MRT_Row, - MRT_RowSelectionState, - MaterialReactTable, - useMaterialReactTable, -} from "material-react-table"; - -import "./TransactionHistoryTable.scss"; -import { PermitHistory } from "../../../types/PermitHistory"; -import { getPaymentMethod } from "../../../../../common/types/paymentMethods"; -import { isValidTransaction } from "../../../helpers/payment"; -import { - applyWhenNotNullable, - getDefaultRequiredVal, -} from "../../../../../common/helpers/util"; - -import { - feeSummaryDisplayText, - isTransactionTypeRefund, - isZeroAmount, -} from "../../../helpers/feeSummary"; - -import { - defaultTableInitialStateOptions, - defaultTableOptions, - defaultTableStateOptions, -} from "../../../../../common/helpers/tableHelper"; -import { CustomFormComponent } from "../../../../../common/components/form/CustomFormComponents"; -import { useFormContext } from "react-hook-form"; -import { RefundFormData } from "../types/RefundFormData"; -import { Checkbox, FormControlLabel } from "@mui/material"; -import { requiredMessage } from "../../../../../common/helpers/validationMessages"; - -export const TransactionHistoryTable = ({ - permitHistory, - totalRefundDue, - rowSelection, - setRowSelection, -}: { - permitHistory: PermitHistory[]; - totalRefundDue: number; - rowSelection: MRT_RowSelectionState; - setRowSelection: (rowSelection: MRT_RowSelectionState) => void; -}) => { - const validTransactionHistory = permitHistory.filter((history) => - isValidTransaction(history.paymentMethodTypeCode, history.pgApproved), - ); - - const formMethods = useFormContext(); - const { register, watch, setValue, trigger } = formMethods; - - const isRowSelectable = (row: MRT_Row): boolean => { - return ( - !isTransactionTypeRefund(row.original.transactionTypeId) && - !isZeroAmount(row.original.transactionAmount) && - totalRefundDue !== 0 - ); - }; - - const columns = useMemo[]>( - () => [ - { - accessorKey: "permitNumber", - header: "Permit #", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--permit-number", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--permit-number", - }, - size: 150, - enableSorting: false, - enableColumnActions: false, - }, - { - accessorFn: (originalRow) => { - return getPaymentMethod( - originalRow.paymentMethodTypeCode, - originalRow.paymentCardTypeCode, - ); - }, - id: "paymentMethod", - header: "Payment Method", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--payment-method", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--payment-method", - }, - size: 130, - enableSorting: false, - enableColumnActions: false, - }, - { - accessorFn: (originalRow) => - getDefaultRequiredVal( - originalRow.transactionOrderNumber, - originalRow.pgTransactionId, - ), - id: "providerTransactionId", - header: "Provider Tran ID", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--provider-transaction-id", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--provider-transaction-id", - }, - size: 100, - enableSorting: false, - enableColumnActions: false, - }, - { - accessorFn: (originalRow) => { - const amount = isTransactionTypeRefund(originalRow.transactionTypeId) - ? -1 * originalRow.transactionAmount - : originalRow.transactionAmount; - - return feeSummaryDisplayText( - applyWhenNotNullable((val) => `${val}`, amount), - ); - }, - header: "Amount (CAD)", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--amount", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--amount", - }, - id: "transactionAmount", - size: 50, - enableSorting: false, - enableColumnActions: false, - }, - { - id: "refundAmount", - header: "Refund Amount (CAD)", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--refund-amount", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--refund-amount", - }, - size: 20, - enableSorting: false, - enableColumnActions: false, - Cell: ({ cell }: { cell: MRT_Cell }) => { - const rowIsSelected = cell.row.getIsSelected(); - - // clear refundAmount when row is unselected - useEffect(() => { - !rowIsSelected && - setValue(`refundData.${cell.row.index}.refundAmount`, ""); - }, [rowIsSelected, setValue, cell.row.index]); - - return ( - isRowSelectable(cell.row) && ( - - ) - ); - }, - }, - { - id: "refundTransactionId", - header: "Refund Tran ID", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--refund-transaction-id", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--refund-transaction-id", - }, - size: 20, - enableSorting: false, - enableColumnActions: false, - Cell: ({ cell }: { cell: MRT_Cell }) => { - const rowIsSelected = cell.row.getIsSelected(); - const refundAmount = watch( - `refundData.${cell.row.index}.refundAmount`, - ); - const chequeRefund = watch( - `refundData.${cell.row.index}.chequeRefund`, - ); - - // clear refundTransactionId when refundAmount is empty or zero - useEffect(() => { - if (!refundAmount || Number(refundAmount) <= 0) { - setValue(`refundData.${cell.row.index}.refundTransactionId`, ""); - } - }, [refundAmount, cell.row.index, setValue]); - - // re-validate refundTransactionId when chequeRefund is checked/unchecked - useEffect(() => { - trigger(`refundData.${cell.row.index}.refundTransactionId`); - }, [chequeRefund, cell.row.index, trigger]); - - // clear refundTransactionId when row is unselected - useEffect(() => { - !rowIsSelected && - setValue(`refundData.${cell.row.index}.refundTransactionId`, ""); - }, [rowIsSelected, setValue, cell.row.index]); - - return ( - isRowSelectable(cell.row) && ( - - ) - ); - }, - }, - { - id: "chequeRefund", - header: "Cheque Refund", - muiTableHeadCellProps: { - className: - "transaction-history-table__header transaction-history-table__header--refund-cheque-refund", - }, - muiTableBodyCellProps: { - className: - "transaction-history-table__data transaction-history-table__data--refund-cheque-refund", - }, - size: 20, - enableSorting: false, - enableColumnActions: false, - Cell: ({ cell }: { cell: MRT_Cell }) => { - const refundAmount = watch( - `refundData.${cell.row.index}.refundAmount`, - ); - const refundTransactionId = watch( - `refundData.${cell.row.index}.refundTransactionId`, - ); - - const rowIsSelected = cell.row.getIsSelected(); - - // local state necessary for 'chequeRefund' checkbox column to allow setting it to false when row is unselected - const [chequeRefund, setChequeRefund] = useState( - watch(`refundData.${cell.row.index}.chequeRefund`), - ); - - // sync react-hook-form state when local state changes - useEffect(() => { - setValue(`refundData.${cell.row.index}.chequeRefund`, chequeRefund); - }, [chequeRefund, setValue, cell.row.index]); - - // clear chequeRefund when row is unselected - useEffect(() => { - !rowIsSelected && setChequeRefund(false); - }, [rowIsSelected]); - - return ( - isRowSelectable(cell.row) && ( - setChequeRefund(e.target.checked)} - disabled={ - !cell.row.getIsSelected() || - Number(refundAmount) <= 0 || - refundTransactionId !== "" - } - /> - } - label="Cheque Refund" - classes={{ - root: "cheque-refund-label", - disabled: "cheque-refund-label--disabled", - }} - /> - ) - ); - }, - }, - ], - [], - ); - - const table = useMaterialReactTable({ - ...defaultTableOptions, - columns: columns, - data: validTransactionHistory, - onRowSelectionChange: setRowSelection, - state: { ...defaultTableStateOptions, rowSelection }, - initialState: { - ...defaultTableInitialStateOptions, - showGlobalFilter: false, - columnVisibility: { chequeRefund: totalRefundDue !== 0 }, - }, - getRowId: (row: RefundFormData) => row.permitNumber, - displayColumnDefOptions: { - "mrt-row-select": { - size: 10, - header: "", - }, - }, - enableRowActions: false, - enableGlobalFilter: false, - enableTopToolbar: false, - enableBottomToolbar: false, - enableRowSelection: (row: MRT_Row) => isRowSelectable(row), - enableSelectAll: false, - muiSelectCheckboxProps: ({ row }: { row: MRT_Row }) => ({ - className: `transaction-history-table__checkbox ${!isRowSelectable(row) && "transaction-history-table__checkbox--disabled"}`, - }), - muiTablePaperProps: { - className: "transaction-history-table", - }, - muiTableContainerProps: { - className: "transaction-history-table__container", - }, - muiTableBodyRowProps: ({ row }) => ({ - className: `transaction-history-table__row ${row.getIsSelected() && "transaction-history-table__row--selected"}`, - }), - }); - - return ( - <> - - - ); -};