Skip to content

Commit 663a284

Browse files
glen-aotzgong-gov
andauthored
ORV2-3259 - FE: Bridge Calc Tool (#1792)
Co-authored-by: GlenAOT <[email protected]> Co-authored-by: zgong-gov <[email protected]>
1 parent fecad69 commit 663a284

24 files changed

+1153
-62
lines changed

database/mssql/scripts/sampledata/dbo.ORBC_FEATURE_FLAG.Table.sql

+22
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,28 @@ VALUES
301301
N'dbo',
302302
GETUTCDATE()
303303
);
304+
INSERT INTO
305+
[dbo].[ORBC_FEATURE_FLAG] (
306+
[FEATURE_ID],
307+
[FEATURE_KEY],
308+
[FEATURE_VALUE],
309+
[CONCURRENCY_CONTROL_NUMBER],
310+
[DB_CREATE_USERID],
311+
[DB_CREATE_TIMESTAMP],
312+
[DB_LAST_UPDATE_USERID],
313+
[DB_LAST_UPDATE_TIMESTAMP]
314+
)
315+
VALUES
316+
(
317+
'14',
318+
'BRIDGE-FORMULA-CALCULATION-TOOL',
319+
'ENABLED',
320+
NULL,
321+
N'dbo',
322+
GETUTCDATE(),
323+
N'dbo',
324+
GETUTCDATE()
325+
);
304326

305327
SET
306328
IDENTITY_INSERT [dbo].[ORBC_FEATURE_FLAG] OFF

frontend/package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"material-react-table": "^2.13.3",
2222
"mui-nested-menu": "^3.4.0",
2323
"oidc-client-ts": "^3.1.0",
24-
"onroute-policy-engine": "^1.4.1",
24+
"onroute-policy-engine": "^1.5.0",
2525
"react": "^18.3.1",
2626
"react-dom": "^18.3.1",
2727
"react-error-boundary": "^4.1.2",
Loading

frontend/src/App.tsx

+1-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import { Header } from "./common/components/header/Header";
1818
import { Footer } from "./common/components/footer/Footer";
1919
import { bcGovTheme } from "./themes/bcGovTheme";
2020
import { NavIconSideBar } from "./common/components/naviconsidebar/NavIconSideBar";
21-
import { NavIconHomeButton } from "./common/components/naviconsidebar/NavIconHomeButton";
22-
import { NavIconReportButton } from "./common/components/naviconsidebar/NavIconReportButton";
2321
import { Nullable, Optional } from "./common/types/common";
2422
import { VerifiedClient, UserClaimsType } from "./common/authentication/types";
2523
import { SuspendSnackBar } from "./common/components/snackbar/SuspendSnackBar";
@@ -168,10 +166,7 @@ const App = () => {
168166
<Router>
169167
<Header />
170168
<SuspendSnackBar />
171-
<NavIconSideBar>
172-
<NavIconHomeButton />
173-
<NavIconReportButton />
174-
</NavIconSideBar>
169+
<NavIconSideBar />
175170
<AppRoutes />
176171
</Router>
177172
</CartContextProvider>

frontend/src/common/authentication/PermissionMatrix.ts

+7
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ const MISCELLANEOUS = {
433433
STAFF_ACT_AS_COMPANY: {
434434
allowedIDIRRoles: [PC, SA, FIN, CTPO, HQA],
435435
},
436+
437+
/**
438+
* Bridge Formula Calculation Tool available from Sidebar
439+
*/
440+
BRIDGE_FORMULA_CALCULATION_TOOL: {
441+
allowedIDIRRoles: [HQA, SA, PC, CTPO, EO],
442+
},
436443
} as const;
437444

438445
/**

frontend/src/common/components/banners/BcGovAlertBanner.scss

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
align-items: flex-start;
1010
width: fit-content;
1111
width: -moz-fit-content; /* For Firefox, Firefox Android */
12+
white-space: pre-wrap;
1213

1314
&#{&}--error {
1415
background-color: orbcStyles.$bc-messages-red-text;

frontend/src/common/components/form/subFormComponents/NumberInput.scss

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@use "../../../../themes/orbcStyles";
1+
@use "../../../../themes/orbcStyles.scss";
22

33
.number-input {
44
& &__label {
@@ -10,7 +10,7 @@
1010
&#{&}--error &__label {
1111
color: orbcStyles.$bc-black;
1212
}
13-
13+
1414
&#{&}--error &__input {
1515
&:hover {
1616
fieldset {
@@ -33,5 +33,9 @@
3333
border: 2px solid orbcStyles.$focus-blue;
3434
}
3535
}
36+
37+
input:disabled + fieldset {
38+
background-color: orbcStyles.$bc-background-light-grey;
39+
}
3640
}
3741
}

frontend/src/common/components/form/subFormComponents/NumberInput.tsx

+40-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import {
33
OutlinedInput,
44
OutlinedInputProps,
@@ -8,12 +8,14 @@ import {
88
} from "@mui/material";
99

1010
import "./NumberInput.scss";
11-
import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../helpers/util";
11+
import {
12+
applyWhenNotNullable,
13+
getDefaultRequiredVal,
14+
} from "../../../helpers/util";
1215
import { convertToNumberIfValid } from "../../../helpers/numeric/convertToNumberIfValid";
1316
import { isNull, RequiredOrNull } from "../../../types/common";
1417

15-
type NumberInputClassKey =
16-
"root" | "label";
18+
type NumberInputClassKey = "root" | "label";
1719

1820
export interface NumberInputProps {
1921
classes?: Partial<Record<NumberInputClassKey, string>>;
@@ -35,34 +37,56 @@ export const NumberInput = (props: NumberInputProps) => {
3537
const helperMessages = getDefaultRequiredVal([], props.helperText?.messages);
3638
const errorMessages = getDefaultRequiredVal([], props.helperText?.errors);
3739
const helperTexts = [
38-
...helperMessages.map(message => ({
40+
...helperMessages.map((message) => ({
3941
type: "message",
4042
message,
4143
})),
42-
...errorMessages.map(message => ({
44+
...errorMessages.map((message) => ({
4345
type: "error",
4446
message,
4547
})),
4648
];
4749

48-
const { maskFn, onChange, onBlur, ...inputProps} = props.inputProps;
50+
const { maskFn, onChange, onBlur, ...inputProps } = props.inputProps;
4951
const inputSlotProps = inputProps.slotProps?.input;
52+
const inputValue = inputProps.value;
5053
const initialValueDisplay = applyWhenNotNullable(
51-
(num) => maskFn ? maskFn(num) : `${num}`,
52-
inputProps.value,
54+
(num) => (maskFn ? maskFn(num) : `${num}`),
55+
inputValue,
5356
"",
5457
);
5558

5659
const [valueDisplay, setValueDisplay] = useState<string>(initialValueDisplay);
5760

61+
useEffect(() => {
62+
setValueDisplay(
63+
applyWhenNotNullable(
64+
(num) => (maskFn ? maskFn(num) : `${num}`),
65+
inputValue,
66+
"",
67+
),
68+
);
69+
}, [inputValue]);
70+
5871
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
5972
const updatedVal = e.target.value;
73+
74+
// Allow clearing the input
75+
if (updatedVal === "") {
76+
setValueDisplay(updatedVal);
77+
onChange?.(e);
78+
return;
79+
}
80+
6081
const numericVal = convertToNumberIfValid(updatedVal, null);
6182

6283
// If an invalid numeric string was inputted, do nothing
63-
if (isNull(numericVal)) return;
84+
if (isNull(numericVal)) {
85+
return;
86+
}
6487

6588
// Otherwise display it without formatting it immediately (as that affects user's ability to input)
89+
6690
setValueDisplay(updatedVal);
6791
onChange?.(e);
6892
};
@@ -81,9 +105,7 @@ export const NumberInput = (props: NumberInputProps) => {
81105
<FormControl
82106
margin="normal"
83107
className={`
84-
number-input ${
85-
props.classes?.root ? props.classes.root : ""
86-
} ${
108+
number-input ${props.classes?.root ? props.classes.root : ""} ${
87109
errorMessages.length > 0 ? "number-input--error" : ""
88110
}
89111
`}
@@ -102,20 +124,18 @@ export const NumberInput = (props: NumberInputProps) => {
102124

103125
<OutlinedInput
104126
{...inputProps}
105-
className={
106-
`number-input__input ${
107-
inputProps.className ? inputProps.className : ""
108-
}`
109-
}
110-
type="number"
127+
className={`number-input__input ${
128+
inputProps.className ? inputProps.className : ""
129+
}`}
130+
type="text"
111131
value={valueDisplay}
112132
onChange={handleChange}
113133
onBlur={handleBlur}
114134
slotProps={{
115135
...inputProps.slotProps,
116136
input: {
117137
...inputSlotProps,
118-
type: "number",
138+
type: "text",
119139
},
120140
}}
121141
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useLocation, useNavigate } from "react-router-dom";
2+
3+
import { useContext } from "react";
4+
import { IDIR_ROUTES } from "../../../routes/constants";
5+
import OnRouteBCContext from "../../authentication/OnRouteBCContext";
6+
import { NavButton } from "./NavButton";
7+
import { NAV_BUTTON_TYPES } from "./types/NavButtonType";
8+
9+
/**
10+
* Displays the navigation icon for the Bridge Formula Calculation Tool on the NavIconSideBar
11+
*/
12+
export const NavIconBFCTButton = () => {
13+
const navigate = useNavigate();
14+
const { pathname } = useLocation();
15+
const isActive = pathname === IDIR_ROUTES.WELCOME;
16+
const { clearCompanyContext } = useContext(OnRouteBCContext);
17+
18+
return (
19+
<NavButton
20+
type={NAV_BUTTON_TYPES.BFCT}
21+
onClick={() => {
22+
clearCompanyContext?.();
23+
navigate(IDIR_ROUTES.BRIDGE_FORMULA_CALCULATION_TOOL);
24+
}}
25+
isActive={isActive}
26+
/>
27+
);
28+
};

frontend/src/common/components/naviconsidebar/NavIconSideBar.tsx

+15-7
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import { IDPS } from "../../types/idp";
55
import "./NavIconSideBar.scss";
66
import OnRouteBCContext from "../../../common/authentication/OnRouteBCContext";
77
import { USER_ROLE } from "../../../common/authentication/types";
8-
9-
interface NavIconSideBarProps {
10-
children?: React.ReactNode;
11-
}
8+
import { NavIconHomeButton } from "./NavIconHomeButton";
9+
import { NavIconReportButton } from "./NavIconReportButton";
10+
import { NavIconBFCTButton } from "./NavIconBFCTButton";
11+
import { useFeatureFlagsQuery } from "../../hooks/hooks";
1212

1313
/**
1414
* Displays a sidebar with NavIcon buttons as children
1515
*/
16-
export const NavIconSideBar = (props: NavIconSideBarProps) => {
17-
const { children } = props;
16+
export const NavIconSideBar = () => {
1817
const { isAuthenticated, user } = useAuth();
1918
const { idirUserDetails } = useContext(OnRouteBCContext);
2019
const isIdir = user?.profile?.identity_provider === IDPS.IDIR;
@@ -24,7 +23,16 @@ export const NavIconSideBar = (props: NavIconSideBarProps) => {
2423
const shouldShowSideBar =
2524
isAuthenticated && isIdir && idirUserDetails?.userName && !isEofficer;
2625

26+
// Determine when to hide the BridgeFormulaCalculationTool button based on its corresponding feature flag
27+
const { data: featureFlags } = useFeatureFlagsQuery();
28+
const enableBFCT =
29+
featureFlags?.["BRIDGE-FORMULA-CALCULATION-TOOL"] === "ENABLED";
30+
2731
return shouldShowSideBar ? (
28-
<div className="nav-icon-side-bar">{children}</div>
32+
<div className="nav-icon-side-bar">
33+
<NavIconHomeButton />
34+
<NavIconReportButton />
35+
{enableBFCT && <NavIconBFCTButton />}
36+
</div>
2937
) : null;
3038
};
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
2-
import { faFileLines, faHome } from "@fortawesome/free-solid-svg-icons";
2+
import {
3+
faFileLines,
4+
faHome,
5+
faTruckMoving,
6+
} from "@fortawesome/free-solid-svg-icons";
37

48
export const NAV_BUTTON_TYPES = {
59
HOME: "home",
610
REPORT: "report",
11+
BFCT: "bfct",
712
} as const;
813

914
export type NavButtonType =
@@ -14,13 +19,21 @@ export const getNavButtonTitle = (type: NavButtonType): string => {
1419
return "Home";
1520
}
1621

17-
return "Report";
22+
if (type === NAV_BUTTON_TYPES.REPORT) {
23+
return "Report";
24+
}
25+
26+
return "Bridge Formula Calculation Tool";
1827
};
1928

2029
export const getIcon = (type: NavButtonType): IconDefinition => {
2130
if (type === NAV_BUTTON_TYPES.HOME) {
2231
return faHome;
2332
}
2433

25-
return faFileLines;
34+
if (type === NAV_BUTTON_TYPES.REPORT) {
35+
return faFileLines;
36+
}
37+
38+
return faTruckMoving;
2639
};

0 commit comments

Comments
 (0)