From 78d387bc6c0752de52153694dfd93672b16b30b9 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 5 Nov 2024 17:19:35 -0600 Subject: [PATCH 01/35] refactor: simplify CAN Detail prefer parent to do the hard work --- .pre-commit-config.yaml | 6 ++-- frontend/src/pages/cans/detail/Can.jsx | 20 +++++++++---- frontend/src/pages/cans/detail/CanDetail.jsx | 30 +++++++++++-------- .../src/pages/cans/detail/CanSpending.jsx | 19 ++++++++++-- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 930bc2da23..d4491a68de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -54,13 +54,13 @@ repos: # For running trufflehog locally, use the following: # entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail' # For running trufflehog in docker, use the following entry instead: - entry: bash -c 'docker run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail' + entry: bash -c 'podman run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail' language: system stages: ["pre-commit", "pre-push"] - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook rev: v9.18.0 hooks: - id: commitlint - stages: [ commit-msg ] - additional_dependencies: [ "@commitlint/config-conventional" ] + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] language_version: 22.8.0 diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index af5d31525c..b29b996bfa 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -20,20 +20,21 @@ const Can = () => { const { data: can, isLoading } = useGetCanByIdQuery(canId); const selectedFiscalYear = useSelector((state) => state.canDetail.selectedFiscalYear); const fiscalYear = Number(selectedFiscalYear.value); - if (isLoading) { return
Loading Can...
; } if (!can) { return
Can not found
; } - + const { number, description, nick_name: nickname, portfolio } = can; + const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; + const noData = "TBD"; const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; return ( @@ -47,11 +48,20 @@ const Can = () => { } + element={ + + } /> } + element={} /> { - const canDivisionId = can.portfolio.division_id; - const { data: division, isSuccess } = useGetDivisionQuery(canDivisionId); +const CanDetail = ({ description, number, nickname, portfolioName, teamLeaders, divisionId }) => { + const { data: division, isSuccess } = useGetDivisionQuery(divisionId); const divisionDirectorFullName = useGetUserFullNameFromId(isSuccess ? division.division_director_id : null); return ( @@ -35,7 +39,7 @@ const CanDetail = ({ can }) => {
@@ -51,22 +55,22 @@ const CanDetail = ({ can }) => {
-
Team Leaders
- {can.portfolio?.team_leaders && - can.portfolio?.team_leaders.length > 0 && - can.portfolio.team_leaders.map((teamLeader) => ( +
Team Leader
+ {teamLeaders && + teamLeaders.length > 0 && + teamLeaders.map((teamLeader) => (
{ +import DebugCode from "../../../components/DebugCode"; +/** + @typedef {import("../../../components/CANs/CANTypes").CAN} CAN +*/ + +/** + * @typedef {Object} CanSpendingProps + * @property {CAN} can + */ + +/** + * @component - The CAN detail page. + * @param {CanSpendingProps} props + * @returns {JSX.Element} - The component JSX. + */ +const CanSpending = ({ can }) => { return (

Can Spending

-

coming soon...

+
); }; From cc8a2b2881294ca6500c944722b56e5e5b9ec63b Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 5 Nov 2024 17:26:43 -0600 Subject: [PATCH 02/35] style: update breadcrumb for CANs --- frontend/src/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index c1b93859fd..d82fbd06e9 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -247,7 +247,7 @@ const router = createBrowserRouter( to="/cans" className="text-primary" > - Cans + CANs ) }} From 9b4d97aa8c5eef076f7b4b1f2e1b4cd724742afc Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 5 Nov 2024 17:39:57 -0600 Subject: [PATCH 03/35] refactor: add BLIs for table --- frontend/src/pages/cans/detail/Can.jsx | 4 ++-- frontend/src/pages/cans/detail/CanSpending.jsx | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index b29b996bfa..31a2b54c0e 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -26,7 +26,7 @@ const Can = () => { if (!can) { return
Can not found
; } - const { number, description, nick_name: nickname, portfolio } = can; + const { number, description, nick_name: nickname, portfolio, budget_line_items: budgetLines } = can; const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; const noData = "TBD"; const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; @@ -61,7 +61,7 @@ const Can = () => { /> } + element={} /> { +const CanSpending = ({ budgetLines }) => { return ( -
+

Can Spending

- -
+ + ); }; From 74aa335d6e5c3e59f57edb8a410d090ea8a9e3e7 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 6 Nov 2024 13:20:38 -0600 Subject: [PATCH 04/35] feat: adds CanBudgetLines Table and Row --- .../CABBudgetLineTable.constants.js | 1 + .../CANBudgetLineTable/CANBudgetLineTable.jsx | 34 +++++++++++++++++++ .../CANBudgetLineTableRow.jsx | 25 ++++++++++++++ .../CANs/CANBudgetLineTable/index.js | 1 + .../src/pages/cans/detail/CanSpending.jsx | 10 ++++-- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/index.js diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js b/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js new file mode 100644 index 0000000000..29dfdd25f8 --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js @@ -0,0 +1 @@ +export const TABLE_HEADERS = ["BL ID #", "Agreement", "Obligate By", "FY", "% of CAN", "Status"]; diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx new file mode 100644 index 0000000000..0e9e24e2ef --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -0,0 +1,34 @@ +import Table from "../../UI/Table"; +import { TABLE_HEADERS } from "./CABBudgetLineTable.constants"; +import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; +/** + * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine + */ + +/** + * @typedef {Object} CANBudgetLineTableProps + * @property {BudgetLine[]} budgetLines + */ + +/** + * @component - The CAN Budget Line Table. + * @param {CANBudgetLineTableProps} props + * @returns {JSX.Element} - The component JSX. + */ +const CANBudgetLineTable = ({ budgetLines }) => { + if (budgetLines.length === 0) { + return

No budget lines have been added to this CAN.

; + } + return ( + + {budgetLines.map((budgetLine) => ( + + ))} +
+ ); +}; + +export default CANBudgetLineTable; diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx new file mode 100644 index 0000000000..e13934f842 --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -0,0 +1,25 @@ +/** + * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine + */ + +import DebugCode from "../../DebugCode"; + +/** + * @typedef {Object} CANBudgetLineTableRowProps + * @property {BudgetLine} budgetLine + */ + +/** + * @component - The CAN Budget Line Table. + * @param {CANBudgetLineTableRowProps} props + * @returns {JSX.Element} - The component JSX. + */ +const CANBudgetLineTableRow = ({ budgetLine }) => { + return ( + + + + ); +}; + +export default CANBudgetLineTableRow; diff --git a/frontend/src/components/CANs/CANBudgetLineTable/index.js b/frontend/src/components/CANs/CANBudgetLineTable/index.js new file mode 100644 index 0000000000..41a7b91331 --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/index.js @@ -0,0 +1 @@ +export { default } from "./CANBudgetLineTable"; diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index ad4a269b48..e41724ce33 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -1,4 +1,4 @@ -import DebugCode from "../../../components/DebugCode"; +import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; /** @typedef {import("../../../components/CANs/CANTypes").CAN} CAN @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine @@ -17,8 +17,12 @@ import DebugCode from "../../../components/DebugCode"; const CanSpending = ({ budgetLines }) => { return (
-

Can Spending

- +

CAN Spending Summary

+

The summary below shows the CANs total budget and spending across all budget lines

+ {/* Note: Cards go here */} +

CAN Budget Lines

+

This is a list of all budget lines allocating funding from this CAN for the selected fiscal year.

+
); }; From 47353e15151238291aff3ea53a76bb902e24eeb0 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 6 Nov 2024 14:51:23 -0600 Subject: [PATCH 05/35] chore: wip --- .../CABBudgetLineTable.constants.js | 2 +- .../CANBudgetLineTable/CANBudgetLineTable.jsx | 4 +- .../CANBudgetLineTableRow.jsx | 40 ++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js b/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js index 29dfdd25f8..c141ef13b7 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js +++ b/frontend/src/components/CANs/CANBudgetLineTable/CABBudgetLineTable.constants.js @@ -1 +1 @@ -export const TABLE_HEADERS = ["BL ID #", "Agreement", "Obligate By", "FY", "% of CAN", "Status"]; +export const TABLE_HEADERS = ["BL ID #", "Agreement", "Obligate By", "FY", "Total", "% of CAN", "Status"]; diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index 0e9e24e2ef..384d3b0f97 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -24,7 +24,9 @@ const CANBudgetLineTable = ({ budgetLines }) => { {budgetLines.map((budgetLine) => ( ))} diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx index e13934f842..2cdbc237d6 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -2,11 +2,18 @@ * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine */ -import DebugCode from "../../DebugCode"; +// import DebugCode from "../../DebugCode"; +import TableRowExpandable from "../../UI/TableRowExpandable"; /** * @typedef {Object} CANBudgetLineTableRowProps - * @property {BudgetLine} budgetLine + * @property {number} blId + * @property {string} agreementName + * @property {string} obligateDate + * @property {string} fiscalYear + * @property {number} total + * @property {number} percentOfCAN + * @property {string} status */ /** @@ -14,11 +21,32 @@ import DebugCode from "../../DebugCode"; * @param {CANBudgetLineTableRowProps} props * @returns {JSX.Element} - The component JSX. */ -const CANBudgetLineTableRow = ({ budgetLine }) => { +const CANBudgetLineTableRow = ({ blId, agreementName, obligateDate, fiscalYear, total, percentOfCAN, status }) => { + const TableRowData = ( + <> + {blId} + {agreementName} + {obligateDate} + {fiscalYear} + {total} + {percentOfCAN} + {status} + + ); + + const ExpandedData =

Expanded Data

; + return ( - - - + <> + {}} + setIsRowActive={() => {}} + /> + {/* */} + ); }; From 6b2248931c97510c312cea4f6c58c19174fc55f5 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 6 Nov 2024 16:31:42 -0600 Subject: [PATCH 06/35] feat: filter CAN BLIs by fiscal year --- .../CANBudgetLineTable/CANBudgetLineTable.jsx | 9 +++- .../CANBudgetLineTableRow.jsx | 54 +++++++++++++------ frontend/src/pages/cans/detail/Can.jsx | 26 ++++++--- 3 files changed, 64 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index 384d3b0f97..b3d76ac25b 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -1,3 +1,4 @@ +import { formatDateNeeded } from "../../../helpers/utils"; import Table from "../../UI/Table"; import { TABLE_HEADERS } from "./CABBudgetLineTable.constants"; import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; @@ -19,6 +20,7 @@ const CANBudgetLineTable = ({ budgetLines }) => { if (budgetLines.length === 0) { return

No budget lines have been added to this CAN.

; } + return ( {budgetLines.map((budgetLine) => ( @@ -26,7 +28,12 @@ const CANBudgetLineTable = ({ budgetLines }) => { key={budgetLine.id} blId={budgetLine.id} agreementName="TBD" - fiscalYear={budgetLine.fiscal_year} + obligateDate={formatDateNeeded(budgetLine.date_needed || "")} + fiscalYear={budgetLine.fiscal_year || "TBD"} + amount={budgetLine.amount || 0} + fee={budgetLine.proc_shop_fee_percentage} + percentOfCAN={3} + status={budgetLine.status} /> ))}
diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx index 2cdbc237d6..1022b229c3 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -1,17 +1,19 @@ +import CurrencyFormat from "react-currency-format"; +import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; +import TableRowExpandable from "../../UI/TableRowExpandable"; +import { totalBudgetLineAmountPlusFees, totalBudgetLineFeeAmount } from "../../../helpers/utils"; /** * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine */ -// import DebugCode from "../../DebugCode"; -import TableRowExpandable from "../../UI/TableRowExpandable"; - /** * @typedef {Object} CANBudgetLineTableRowProps * @property {number} blId * @property {string} agreementName * @property {string} obligateDate - * @property {string} fiscalYear - * @property {number} total + * @property {number | string } fiscalYear + * @property {number} amount + * @property {number} fee * @property {number} percentOfCAN * @property {string} status */ @@ -21,14 +23,35 @@ import TableRowExpandable from "../../UI/TableRowExpandable"; * @param {CANBudgetLineTableRowProps} props * @returns {JSX.Element} - The component JSX. */ -const CANBudgetLineTableRow = ({ blId, agreementName, obligateDate, fiscalYear, total, percentOfCAN, status }) => { +const CANBudgetLineTableRow = ({ + blId, + agreementName, + obligateDate, + fiscalYear, + amount, + fee, + percentOfCAN, + status +}) => { + const feeTotal = totalBudgetLineFeeAmount(amount, fee); + const budgetLineTotalPlusFees = totalBudgetLineAmountPlusFees(amount, feeTotal); + const TableRowData = ( <> {blId} {agreementName} {obligateDate} {fiscalYear} - {total} + + + {percentOfCAN} {status} @@ -37,16 +60,13 @@ const CANBudgetLineTableRow = ({ blId, agreementName, obligateDate, fiscalYear, const ExpandedData =

Expanded Data

; return ( - <> - {}} - setIsRowActive={() => {}} - /> - {/* */} - + {}} + setIsRowActive={() => {}} + /> ); }; diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index 31a2b54c0e..fc717e5a16 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -9,9 +9,11 @@ import CANFiscalYearSelect from "../list/CANFiscalYearSelect"; import CanDetail from "./CanDetail"; import CanFunding from "./CanFunding"; import CanSpending from "./CanSpending"; +import React from "react"; /** - @typedef {import("../../../components/CANs/CANTypes").CAN} CAN -*/ + * @typedef {import("../../../components/CANs/CANTypes").CAN} CAN + * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine + */ const Can = () => { const urlPathParams = useParams(); @@ -20,13 +22,23 @@ const Can = () => { const { data: can, isLoading } = useGetCanByIdQuery(canId); const selectedFiscalYear = useSelector((state) => state.canDetail.selectedFiscalYear); const fiscalYear = Number(selectedFiscalYear.value); + + const filteredCANByFiscalYear = React.useMemo(() => { + if (!fiscalYear || !can) return {}; + return can.funding_details?.fiscal_year === fiscalYear ? can : {}; + }, [can, fiscalYear]); + if (isLoading) { return
Loading Can...
; } if (!can) { return
Can not found
; } - const { number, description, nick_name: nickname, portfolio, budget_line_items: budgetLines } = can; + + const { number, description, nick_name: nickname, portfolio } = can; + + /** @type {{budget_line_items?: BudgetLine[]}} */ + const { budget_line_items: budgetLines } = filteredCANByFiscalYear; const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; const noData = "TBD"; const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; @@ -51,17 +63,17 @@ const Can = () => { element={ } /> } + element={} /> Date: Wed, 6 Nov 2024 19:34:27 -0600 Subject: [PATCH 07/35] feat: adds Expandable Row --- .../CANBudgetLineTable/CANBudgetLineTable.jsx | 5 + .../CANBudgetLineTableRow.jsx | 179 ++++++++++++++++-- 2 files changed, 167 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index b3d76ac25b..9f751323f7 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -34,6 +34,11 @@ const CANBudgetLineTable = ({ budgetLines }) => { fee={budgetLine.proc_shop_fee_percentage} percentOfCAN={3} status={budgetLine.status} + inReview={budgetLine.in_review} + creatorId={budgetLine.created_by} + creationDate={budgetLine.created_on} + procShopCode="TBD" + procShopFeePercentage={budgetLine.proc_shop_fee_percentage} /> ))} diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx index 1022b229c3..fd1b56f9f5 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -1,7 +1,21 @@ +import { faClock } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import CurrencyFormat from "react-currency-format"; -import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; +import { + formatDateToMonthDayYear, + totalBudgetLineAmountPlusFees, + totalBudgetLineFeeAmount +} from "../../../helpers/utils"; +import useGetUserFullNameFromId from "../../../hooks/user.hooks"; import TableRowExpandable from "../../UI/TableRowExpandable"; -import { totalBudgetLineAmountPlusFees, totalBudgetLineFeeAmount } from "../../../helpers/utils"; +import { + changeBgColorIfExpanded, + expandedRowBGColor, + removeBorderBottomIfExpanded +} from "../../UI/TableRowExpandable/TableRowExpandable.helpers"; +import { useTableRow } from "../../UI/TableRowExpandable/TableRowExpandable.hooks"; +import TableTag from "../../UI/TableTag"; + /** * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine */ @@ -9,13 +23,18 @@ import { totalBudgetLineAmountPlusFees, totalBudgetLineFeeAmount } from "../../. /** * @typedef {Object} CANBudgetLineTableRowProps * @property {number} blId - * @property {string} agreementName + * @property {string} agreementName - TODO * @property {string} obligateDate * @property {number | string } fiscalYear * @property {number} amount * @property {number} fee - * @property {number} percentOfCAN + * @property {number} percentOfCAN - TODO * @property {string} status + * @property {boolean} inReview + * @property {number} creatorId + * @property {string} creationDate + * @property {string} procShopCode - TODO + * @property {number} procShopFeePercentage */ /** @@ -31,41 +50,167 @@ const CANBudgetLineTableRow = ({ amount, fee, percentOfCAN, - status + status, + inReview, + creatorId, + creationDate, + procShopCode, + procShopFeePercentage }) => { + const { isExpanded, setIsRowActive, setIsExpanded } = useTableRow(); + const borderExpandedStyles = removeBorderBottomIfExpanded(isExpanded); + const bgExpandedStyles = changeBgColorIfExpanded(isExpanded); + const budgetLineCreatorName = useGetUserFullNameFromId(creatorId); const feeTotal = totalBudgetLineFeeAmount(amount, fee); const budgetLineTotalPlusFees = totalBudgetLineAmountPlusFees(amount, feeTotal); + const displayCreatedDate = formatDateToMonthDayYear(creationDate); const TableRowData = ( <> - {blId} - {agreementName} - {obligateDate} - {fiscalYear} - + + {blId} + + + {agreementName} + + + {obligateDate} + + + {fiscalYear} + + - {percentOfCAN} - {status} + + {percentOfCAN}% + + + + ); - const ExpandedData =

Expanded Data

; + const ExpandedData = ( + +
+
+
Created By
+
+ {budgetLineCreatorName} +
+
+ + {displayCreatedDate} +
+
+
+
Notes
+
+ No Notes added +
+
+
+
+
Procurement Shop
+
+ {`${procShopCode}-Fee Rate: ${procShopFeePercentage * 100}%`} +
+
+
+
+
SubTotal
+
+ +
+
+
+
Fees
+
+ +
+
+
+
+
+ + ); return ( {}} - setIsRowActive={() => {}} + isExpanded={isExpanded} + setIsExpanded={setIsExpanded} + setIsRowActive={setIsRowActive} /> ); }; From b8d22cf46a1e66c1cbbaefb950e570bc68227379 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 08:57:33 -0600 Subject: [PATCH 08/35] style: use correct icon --- .../CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx index fd1b56f9f5..8f47ce33fa 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -1,4 +1,4 @@ -import { faClock } from "@fortawesome/free-solid-svg-icons"; +import { faClock } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import CurrencyFormat from "react-currency-format"; import { From f7c337eabbffae7b19627b180b53137ecc0d9200 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 10:01:57 -0600 Subject: [PATCH 09/35] feat: adds messages to in_review BLI --- .../CANs/CANBudgetLineTable/CANBudgetLineTable.jsx | 1 + .../CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index 9f751323f7..8529284b9e 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -26,6 +26,7 @@ const CANBudgetLineTable = ({ budgetLines }) => { {budgetLines.map((budgetLine) => ( { + const lockedMessage = useChangeRequestsForTooltip(budgetLine); const { isExpanded, setIsRowActive, setIsExpanded } = useTableRow(); const borderExpandedStyles = removeBorderBottomIfExpanded(isExpanded); const bgExpandedStyles = changeBgColorIfExpanded(isExpanded); @@ -118,6 +122,7 @@ const CANBudgetLineTableRow = ({ From 26a0abb03205011109888db38684fde1aea68419 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 10:48:35 -0600 Subject: [PATCH 10/35] test: adds unit/e2e tests --- frontend/cypress/e2e/canDetail.cy.js | 17 +++- .../CANBudgetLineTable.test.jsx | 33 ++++++++ .../CANBudgetLineTableRow.test.jsx | 83 +++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.test.jsx create mode 100644 frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx diff --git a/frontend/cypress/e2e/canDetail.cy.js b/frontend/cypress/e2e/canDetail.cy.js index 5542caa246..796805add5 100644 --- a/frontend/cypress/e2e/canDetail.cy.js +++ b/frontend/cypress/e2e/canDetail.cy.js @@ -11,7 +11,7 @@ afterEach(() => { }); describe("CAN detail page", () => { - it("loads", () => { + it("shows relevant CAN data", () => { cy.visit("/cans/502/"); cy.get("h1").should("contain", "G99PHS9"); // heading cy.get("p").should("contain", "SSRD - 5 Years"); // sub-heading @@ -19,4 +19,19 @@ describe("CAN detail page", () => { cy.get("span").should("contain", "Director Derrek"); // division director cy.get("span").should("contain", "Program Support"); // portfolio }); + it("shows the CAN Spending page", () => { + cy.visit("/cans/504/spending"); + cy.get("#fiscal-year-select").select("2021"); + cy.get("h1").should("contain", "G99PHS9"); // heading + cy.get("p").should("contain", "HS - 5 Years"); // sub-heading + // should contain the budget line table + cy.get("table").should("exist"); + // table should have more than 1 row + cy.get("tbody").children().should("have.length.greaterThan", 1); + // switch to a different fiscal year + cy.get("#fiscal-year-select").select("2021"); + // table should not exist + cy.get("tbody").should("not.exist"); + cy.get("p").should("contain", "No budget lines have been added to this CAN."); + }); }); diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.test.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.test.jsx new file mode 100644 index 0000000000..8e4402f829 --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.test.jsx @@ -0,0 +1,33 @@ +import { render, screen } from "@testing-library/react"; +import { Provider } from "react-redux"; +import CANBudgetLineTable from "./CANBudgetLineTable"; +import store from "../../../store"; +import { budgetLine } from "../../../tests/data"; + +describe("CANBudgetLineTable", () => { + const mockBudgetLines = [ + { ...budgetLine, status: "Approved", amount: 1000 }, + { ...budgetLine, status: "Pending", amount: 2000 } + ]; + + it("renders 'No budget lines have been added to this CAN.' when there are no budget lines", () => { + render( + + + + ); + expect(screen.getByText("No budget lines have been added to this CAN.")).toBeInTheDocument(); + }); + + it("renders table with budget lines", () => { + render( + + + + ); + expect(screen.getByText("Approved")).toBeInTheDocument(); + expect(screen.getByText("Pending")).toBeInTheDocument(); + expect(screen.getByText("$1,000.00")).toBeInTheDocument(); + expect(screen.getByText("$2,000.00")).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx new file mode 100644 index 0000000000..15ca246f69 --- /dev/null +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx @@ -0,0 +1,83 @@ +import { render, screen } from "@testing-library/react"; +import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; +import { formatDateNeeded } from "../../../helpers/utils"; +import { Provider } from "react-redux"; +import store from "../../../store"; +import { budgetLine } from "../../../tests/data"; +import userEvent from "@testing-library/user-event"; + +const mockBudgetLine = { + ...budgetLine, + id: 1, + date_needed: "2023-10-01", + fiscal_year: 2023, + amount: 1000, + proc_shop_fee_percentage: 0.05, + status: "Pending", + in_review: true, + created_by: 1, + created_on: "2023-09-01" +}; + +describe("CANBudgetLineTableRow", () => { + test("renders table row data correctly", () => { + render( + + + + ); + + expect(screen.getByText("TBD")).toBeInTheDocument(); + expect(screen.getByText(formatDateNeeded(mockBudgetLine.date_needed))).toBeInTheDocument(); + expect(screen.getByText(mockBudgetLine.fiscal_year)).toBeInTheDocument(); + expect(screen.getByText("$1,050.00")).toBeInTheDocument(); // amount + fee + expect(screen.getByText("3%")).toBeInTheDocument(); + }); + + test("renders expanded data correctly", async () => { + render( + + + + ); + + // Simulate expanding the row + await userEvent.click(screen.getByTestId("expand-row")); + + expect(screen.getByText("Created By")).toBeInTheDocument(); + expect(screen.getByText("No Notes added")).toBeInTheDocument(); + expect(screen.getByText("Procurement Shop")).toBeInTheDocument(); + expect(screen.getByText("$1,000.00")).toBeInTheDocument(); // amount + expect(screen.getByText("$50.00")).toBeInTheDocument(); // fee + }); +}); From 724b15017c56bed7f80cdc3ef41ca87baddf9be0 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 10:55:13 -0600 Subject: [PATCH 11/35] test: wrong can data --- frontend/cypress/e2e/canDetail.cy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/cypress/e2e/canDetail.cy.js b/frontend/cypress/e2e/canDetail.cy.js index 796805add5..31081bb55a 100644 --- a/frontend/cypress/e2e/canDetail.cy.js +++ b/frontend/cypress/e2e/canDetail.cy.js @@ -22,14 +22,14 @@ describe("CAN detail page", () => { it("shows the CAN Spending page", () => { cy.visit("/cans/504/spending"); cy.get("#fiscal-year-select").select("2021"); - cy.get("h1").should("contain", "G99PHS9"); // heading + cy.get("h1").should("contain", "G994426"); // heading cy.get("p").should("contain", "HS - 5 Years"); // sub-heading // should contain the budget line table cy.get("table").should("exist"); // table should have more than 1 row cy.get("tbody").children().should("have.length.greaterThan", 1); // switch to a different fiscal year - cy.get("#fiscal-year-select").select("2021"); + cy.get("#fiscal-year-select").select("2022"); // table should not exist cy.get("tbody").should("not.exist"); cy.get("p").should("contain", "No budget lines have been added to this CAN."); From 4dcce2dff6fdfceec9c8882267cc152d92ad78cd Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 11:32:21 -0600 Subject: [PATCH 12/35] chore: test --- .pre-commit-config.yaml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4491a68de..05ff838eba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,16 +12,10 @@ repos: - id: check-yaml - id: check-added-large-files - id: check-merge-conflict - - repo: https://github.com/hadolint/hadolint rev: v2.10.0 hooks: - id: hadolint - # We're running black, but doing it via nox session instead - see below - # - repo: https://github.com/psf/black - # rev: 22.6.0 - # hooks: - # - id: black - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: @@ -46,15 +40,10 @@ repos: - css - html pass_filenames: false - - repo: local - hooks: - id: trufflehog name: TruffleHog description: Detect secrets in your data. - # For running trufflehog locally, use the following: - # entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail' - # For running trufflehog in docker, use the following entry instead: - entry: bash -c 'podman run --rm -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail' + entry: bash -c 'if command -v podman >/dev/null 2>&1; then podman run --rm -v "$(pwd):/workdir" -i trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail; elif command -v docker >/dev/null 2>&1; then docker run --rm -v "$(pwd):/workdir" -i trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --only-verified --fail; else echo "Neither docker nor podman found. Please install one of them." && exit 1; fi' language: system stages: ["pre-commit", "pre-push"] - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook From 346be174150c2f36c6e94000ba2ddfdeb1784bed Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 11:52:48 -0600 Subject: [PATCH 13/35] feat: adds notes --- .../CANs/CANBudgetLineTable/CANBudgetLineTable.jsx | 1 + .../CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index 8529284b9e..a52d7cf4eb 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -40,6 +40,7 @@ const CANBudgetLineTable = ({ budgetLines }) => { creationDate={budgetLine.created_on} procShopCode="TBD" procShopFeePercentage={budgetLine.proc_shop_fee_percentage} + notes={budgetLine.comments || "No Notes added"} /> ))} diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx index b6f7db305c..68cc051516 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.jsx @@ -37,6 +37,7 @@ import { useChangeRequestsForTooltip } from "../../../hooks/useChangeRequests.ho * @property {string} creationDate * @property {string} procShopCode - TODO * @property {number} procShopFeePercentage + * @property {string} notes */ /** @@ -58,7 +59,8 @@ const CANBudgetLineTableRow = ({ creatorId, creationDate, procShopCode, - procShopFeePercentage + procShopFeePercentage, + notes }) => { const lockedMessage = useChangeRequestsForTooltip(budgetLine); const { isExpanded, setIsRowActive, setIsExpanded } = useTableRow(); @@ -160,7 +162,7 @@ const CANBudgetLineTableRow = ({ className="margin-0" style={{ maxWidth: "25rem" }} > - No Notes added + {notes}
Date: Thu, 7 Nov 2024 12:04:10 -0600 Subject: [PATCH 14/35] test: adds comments --- .../CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx index 15ca246f69..bdc2d9e8af 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTableRow.test.jsx @@ -67,6 +67,7 @@ describe("CANBudgetLineTableRow", () => { creationDate={mockBudgetLine.created_on} procShopCode="TBD" procShopFeePercentage={mockBudgetLine.proc_shop_fee_percentage} + notes={mockBudgetLine.comments} /> ); @@ -75,7 +76,7 @@ describe("CANBudgetLineTableRow", () => { await userEvent.click(screen.getByTestId("expand-row")); expect(screen.getByText("Created By")).toBeInTheDocument(); - expect(screen.getByText("No Notes added")).toBeInTheDocument(); + expect(screen.getByText("comment one")).toBeInTheDocument(); expect(screen.getByText("Procurement Shop")).toBeInTheDocument(); expect(screen.getByText("$1,000.00")).toBeInTheDocument(); // amount expect(screen.getByText("$50.00")).toBeInTheDocument(); // fee From cfd2ce454141c04070134dddd03ba662002995a2 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 7 Nov 2024 18:18:02 -0600 Subject: [PATCH 15/35] chore: wip --- frontend/src/api/opsAPI.js | 6 +++++- frontend/src/pages/cans/detail/Can.jsx | 8 +++++++- frontend/src/pages/cans/detail/CanSpending.jsx | 14 +++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index 690d659a61..036bb7a0f3 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -204,9 +204,13 @@ export const opsApi = createApi({ providesTags: ["Cans"] }), getCanFundingSummary: builder.query({ - query: (id) => `/can-funding-summary/${id}`, + query: ({ id }) => `/can-funding-summary/${id}`, providesTags: ["CanFunding"] }), + // getCanFundingSummary: builder.query({ + // query: ({ id, fiscalYear }) => `/can-funding-summary?can_ids=${id}&fiscal_year=${fiscalYear}`, + // providesTags: ["CanFunding"] + // }), getNotificationsByUserId: builder.query({ query: ({ id, auth_header }) => { if (!id) { diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index fc717e5a16..0febe0e9fe 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -73,7 +73,13 @@ const Can = () => { /> } + element={ + + } /> { +const CanSpending = ({ budgetLines, fiscalYear, canId }) => { + // const { data: CANFunding } = useGetCanFundingSummaryQuery({ + // id: canId, + // fiscalYear: fiscalYear + // }); + const { data: CANFunding } = useGetCanFundingSummaryQuery({ id: canId, fiscalYear: fiscalYear }); + return (

CAN Spending Summary

@@ -23,6 +34,7 @@ const CanSpending = ({ budgetLines }) => {

CAN Budget Lines

This is a list of all budget lines allocating funding from this CAN for the selected fiscal year.

+
); }; From f24cf6d29df0518f3ec547ec5c77082c54dc7a01 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 8 Nov 2024 13:53:00 -0600 Subject: [PATCH 16/35] refactor: update api calls --- frontend/src/api/opsAPI.js | 2 +- frontend/src/components/CANs/CANTable/CANTableRow.jsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index 036bb7a0f3..e8f5cdc936 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -204,7 +204,7 @@ export const opsApi = createApi({ providesTags: ["Cans"] }), getCanFundingSummary: builder.query({ - query: ({ id }) => `/can-funding-summary/${id}`, + query: ({ id, fiscalYear }) => `/can-funding-summary/${id}?fiscal_year=${fiscalYear}`, providesTags: ["CanFunding"] }), // getCanFundingSummary: builder.query({ diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index 87ab2d7d09..3c942f1c67 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -33,7 +33,11 @@ const CANTableRow = ({ portfolio, transfer }) => { - const { data: fundingSummary, isError, isLoading } = useGetCanFundingSummaryQuery(canId); + const { + data: fundingSummary, + isError, + isLoading + } = useGetCanFundingSummaryQuery({ id: canId, fiscalYear: fiscalYear }); const availableFunds = fundingSummary?.available_funding ?? 0; if (isLoading) From 8cc815acab86e0183cdcf0f0790014cb29912b38 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 8 Nov 2024 16:56:19 -0600 Subject: [PATCH 17/35] feat: adds BigBudgetCard --- .../UI/Cards/BudgetCard/BigBudgetCard.jsx | 113 ++++++++++++++++++ .../src/pages/cans/detail/CanSpending.jsx | 21 ++-- 2 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx diff --git a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx new file mode 100644 index 0000000000..5f08fe4ddc --- /dev/null +++ b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx @@ -0,0 +1,113 @@ +import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import CurrencyFormat from "react-currency-format"; +import LineGraph from "../../DataViz/LineGraph"; +import CurrencyWithSmallCents from "../../CurrencyWithSmallCents/CurrencyWithSmallCents"; +import RoundedBox from "../../RoundedBox"; +import Tag from "../../Tag"; + +/** + * @typedef {Object} BigBudgetCardProps + * @property {string} title - The title of the card. + * @property {number} totalSpending - The total spending. + * @property {number} totalFunding - The total funding. + */ + +/** + * @component BigBudgetCard + * @param {BigBudgetCardProps} props - Properties passed to component + * @returns {JSX.Element} - The BudgetSummaryCard component. + */ +const BigBudgetCard = ({ title, totalSpending, totalFunding }) => { + const overBudget = totalSpending > totalFunding; + const remainingBudget = totalFunding - totalSpending; + const graphData = [ + { + id: 1, + value: totalSpending, + color: overBudget ? "var(--feedback-error)" : "var(--data-viz-budget-graph-2)" + }, + { + id: 2, + value: remainingBudget, + color: overBudget ? "var(--feedback-error)" : "var(--data-viz-budget-graph-1)" + } + ]; + + return ( + +

+ {title} +

+ +
+
+ + + {overBudget ? ( + + {" "} + Over Budget + + ) : ( + + Available + + )} +
+ +
+
+ Spending {""} + {totalSpending}} + />{" "} + of{" "} + {totalFunding}} + /> +
+
+
+
+ +
+
+ ); +}; +export default BigBudgetCard; diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 78a115ed35..4ea4e52e9e 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -1,6 +1,7 @@ -import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; +import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; import DebugCode from "../../../components/DebugCode"; +import BigBudgetCard from "../../../components/UI/Cards/BudgetCard/BigBudgetCard"; /** @typedef {import("../../../components/CANs/CANTypes").CAN} CAN @@ -20,17 +21,23 @@ import DebugCode from "../../../components/DebugCode"; * @returns {JSX.Element} - The component JSX. */ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { - // const { data: CANFunding } = useGetCanFundingSummaryQuery({ - // id: canId, - // fiscalYear: fiscalYear - // }); - const { data: CANFunding } = useGetCanFundingSummaryQuery({ id: canId, fiscalYear: fiscalYear }); + const { data: CANFunding, isLoading } = useGetCanFundingSummaryQuery({ id: canId, fiscalYear: fiscalYear }); + + if (isLoading) return
Loading...
; + if (!CANFunding) return
No data
; + + const { total_funding: totalFunding, planned_funding, obligated_funding, in_execution_funding } = CANFunding; + const totalSpending = planned_funding + obligated_funding + in_execution_funding; return (

CAN Spending Summary

The summary below shows the CANs total budget and spending across all budget lines

- {/* Note: Cards go here */} +

CAN Budget Lines

This is a list of all budget lines allocating funding from this CAN for the selected fiscal year.

From 97d334c5ce2a3fa5028ee87820a00ffb4e8decae Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 8 Nov 2024 17:32:30 -0600 Subject: [PATCH 18/35] chore: cleanup --- frontend/src/api/opsAPI.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index e8f5cdc936..50312e18fd 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -207,10 +207,6 @@ export const opsApi = createApi({ query: ({ id, fiscalYear }) => `/can-funding-summary/${id}?fiscal_year=${fiscalYear}`, providesTags: ["CanFunding"] }), - // getCanFundingSummary: builder.query({ - // query: ({ id, fiscalYear }) => `/can-funding-summary?can_ids=${id}&fiscal_year=${fiscalYear}`, - // providesTags: ["CanFunding"] - // }), getNotificationsByUserId: builder.query({ query: ({ id, auth_header }) => { if (!id) { From ea4986ecb311fae8aacc6e77e09d6f3014b9f7b9 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 13:30:57 -0600 Subject: [PATCH 19/35] refactor: :construction: filters budgetline by fiscal year updates api endpoint to optionally pass fiscal year --- frontend/src/api/opsAPI.js | 3 ++- frontend/src/constants.js | 8 +++++--- .../pages/agreements/approve/ApproveAgreement.hooks.js | 4 ++-- frontend/src/pages/cans/detail/Can.jsx | 10 ++++------ frontend/src/pages/cans/detail/CanSpending.jsx | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js index 50312e18fd..d71da4fe17 100644 --- a/frontend/src/api/opsAPI.js +++ b/frontend/src/api/opsAPI.js @@ -204,7 +204,8 @@ export const opsApi = createApi({ providesTags: ["Cans"] }), getCanFundingSummary: builder.query({ - query: ({ id, fiscalYear }) => `/can-funding-summary/${id}?fiscal_year=${fiscalYear}`, + query: ({ id, fiscalYear }) => + `/can-funding-summary/${id}${fiscalYear ? `?fiscal_year=${fiscalYear}` : ""}`, providesTags: ["CanFunding"] }), getNotificationsByUserId: builder.query({ diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 72802bfc37..a5fbf1bcc0 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -5,11 +5,13 @@ const constants = { const currentMonth = currentDate.getMonth(); const currentYear = currentDate.getFullYear(); const currentFiscalYear = currentMonth >= 9 ? currentYear + 1 : currentYear; - const years = []; + const years = [2044, 2043]; for (let i = currentFiscalYear + 5; i >= currentFiscalYear - 5; i--) { - years.push(i); + if (!years.includes(i)) { + years.push(i); + } } - return years; + return years.sort((a, b) => b - a); })(), barChartColors: [ { color: "var(--feedback-success-dark)" }, diff --git a/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js b/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js index 3fd3c15e4d..ab8704dc00 100644 --- a/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js +++ b/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js @@ -24,7 +24,7 @@ import { useSelector } from "react-redux"; * @typedef {import('../../../components/ChangeRequests/ChangeRequestsTypes').ChangeRequest} ChangeRequest * @typedef {import('../../../components/BudgetLineItems/BudgetLineTypes').BudgetLine} BudgetLine * @typedef {import('../../../components/CANs/CANTypes').CAN} CAN - * @typedef {import('../../../components/CANs/CANTypes').SimpleCAN} SimpleCAN + * @typedef {import('../../../components/CANs/CANTypes').BasicCAN} BasicCAN * @typedef {import('../../../components/Agreements/AgreementTypes').Agreement} Agreement */ @@ -239,7 +239,7 @@ const useApproveAgreement = () => { /** * @description This function is used to apply the pending changes to the budget lines * @param {BudgetLine[]} originalBudgetLines - The original budget lines - * @param {SimpleCAN[]} cans - The CAN data retrieved from the RTL Query + * @param {BasicCAN[]} cans - The CAN data retrieved from the RTL Query * @returns {BudgetLine[]} The updated budget lines */ function applyPendingChangesToBudgetLines(originalBudgetLines, cans) { diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index 0febe0e9fe..2a97dc1111 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -23,9 +23,10 @@ const Can = () => { const selectedFiscalYear = useSelector((state) => state.canDetail.selectedFiscalYear); const fiscalYear = Number(selectedFiscalYear.value); - const filteredCANByFiscalYear = React.useMemo(() => { + const budgetLineItemsByFiscalYear = React.useMemo(() => { if (!fiscalYear || !can) return {}; - return can.funding_details?.fiscal_year === fiscalYear ? can : {}; + + return can.budget_line_items?.filter((bli) => bli.fiscal_year === fiscalYear) ?? []; }, [can, fiscalYear]); if (isLoading) { @@ -36,9 +37,6 @@ const Can = () => { } const { number, description, nick_name: nickname, portfolio } = can; - - /** @type {{budget_line_items?: BudgetLine[]}} */ - const { budget_line_items: budgetLines } = filteredCANByFiscalYear; const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; const noData = "TBD"; const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; @@ -75,7 +73,7 @@ const Can = () => { path="spending" element={ diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 4ea4e52e9e..123c78a434 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -27,7 +27,7 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { if (!CANFunding) return
No data
; const { total_funding: totalFunding, planned_funding, obligated_funding, in_execution_funding } = CANFunding; - const totalSpending = planned_funding + obligated_funding + in_execution_funding; + const totalSpending = Number(planned_funding) + Number(obligated_funding) + Number(in_execution_funding); return (
From 5ed1028b582ef7430b3bfdb4050abf7094a2e2a2 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 14:05:21 -0600 Subject: [PATCH 20/35] refactor: adds a variable for TBD --- frontend/src/constants.js | 2 ++ frontend/src/pages/cans/detail/Can.jsx | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/constants.js b/frontend/src/constants.js index a5fbf1bcc0..6d227ef2d4 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -27,4 +27,6 @@ const constants = { ] }; +export const NO_DATA = "TBD"; + export default constants; diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index 2a97dc1111..159ce8d323 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -1,15 +1,16 @@ +import React from "react"; import { useSelector } from "react-redux"; import { Route, Routes, useParams } from "react-router-dom"; import { useGetCanByIdQuery } from "../../../api/opsAPI"; import App from "../../../App"; import CanDetailTabs from "../../../components/CANs/CanDetailTabs/CanDetailTabs"; import PageHeader from "../../../components/UI/PageHeader"; +import { NO_DATA } from "../../../constants"; import { setSelectedFiscalYear } from "../../../pages/cans/detail/canDetailSlice"; import CANFiscalYearSelect from "../list/CANFiscalYearSelect"; import CanDetail from "./CanDetail"; import CanFunding from "./CanFunding"; import CanSpending from "./CanSpending"; -import React from "react"; /** * @typedef {import("../../../components/CANs/CANTypes").CAN} CAN * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine @@ -24,7 +25,7 @@ const Can = () => { const fiscalYear = Number(selectedFiscalYear.value); const budgetLineItemsByFiscalYear = React.useMemo(() => { - if (!fiscalYear || !can) return {}; + if (!fiscalYear || !can) return []; return can.budget_line_items?.filter((bli) => bli.fiscal_year === fiscalYear) ?? []; }, [can, fiscalYear]); @@ -38,13 +39,13 @@ const Can = () => { const { number, description, nick_name: nickname, portfolio } = can; const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; - const noData = "TBD"; + const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; return ( @@ -61,10 +62,10 @@ const Can = () => { element={ } @@ -73,7 +74,7 @@ const Can = () => { path="spending" element={ From aa67ae59ca51034b7b6a0750a6c8b570d3a9413a Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 14:10:29 -0600 Subject: [PATCH 21/35] style: label changes --- frontend/src/pages/cans/detail/Can.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index 159ce8d323..32f18f5a81 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -31,10 +31,10 @@ const Can = () => { }, [can, fiscalYear]); if (isLoading) { - return
Loading Can...
; + return
Loading CAN...
; } if (!can) { - return
Can not found
; + return
CAN not found
; } const { number, description, nick_name: nickname, portfolio } = can; From e4c5805a5e4d3f3f93ac4dd27964f7bdefe3a044 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 14:27:48 -0600 Subject: [PATCH 22/35] test: fixes e2e test --- frontend/cypress/e2e/canDetail.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/cypress/e2e/canDetail.cy.js b/frontend/cypress/e2e/canDetail.cy.js index 31081bb55a..b1e7cabcfd 100644 --- a/frontend/cypress/e2e/canDetail.cy.js +++ b/frontend/cypress/e2e/canDetail.cy.js @@ -21,7 +21,7 @@ describe("CAN detail page", () => { }); it("shows the CAN Spending page", () => { cy.visit("/cans/504/spending"); - cy.get("#fiscal-year-select").select("2021"); + cy.get("#fiscal-year-select").select("2043"); cy.get("h1").should("contain", "G994426"); // heading cy.get("p").should("contain", "HS - 5 Years"); // sub-heading // should contain the budget line table From 0f85ce9138f588bb625d8e50b3fc594c0d454eab Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 16:36:50 -0600 Subject: [PATCH 23/35] feat: adds DonutGraphWithLegend Card --- .../UI/Cards/BudgetCard/BigBudgetCard.jsx | 139 +++++++++--------- .../DonutGraphWithLegendCard.jsx | 59 ++++++++ .../Cards/DonutGraphWithLegendCard/index.js | 1 + .../styles.module.css | 13 ++ .../src/pages/cans/detail/CanSpending.jsx | 44 ++++++ 5 files changed, 189 insertions(+), 67 deletions(-) create mode 100644 frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx create mode 100644 frontend/src/components/UI/Cards/DonutGraphWithLegendCard/index.js create mode 100644 frontend/src/components/UI/Cards/DonutGraphWithLegendCard/styles.module.css diff --git a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx index 5f08fe4ddc..badbd56250 100644 --- a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx +++ b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx @@ -35,79 +35,84 @@ const BigBudgetCard = ({ title, totalSpending, totalFunding }) => { ]; return ( - -

+ - {title} -

+

+ {title} +

-
-
- +
+
+ - {overBudget ? ( - - {" "} - Over Budget - - ) : ( - - Available - - )} -
+ {overBudget ? ( + + {" "} + Over Budget + + ) : ( + + Available + + )} +
-
-
- Spending {""} - {totalSpending}} - />{" "} - of{" "} - {totalFunding}} - /> +
+
+ Spending {""} + {totalSpending}} + />{" "} + of{" "} + {totalFunding}} + /> +
-
-
- -
- +
+ +
+ +

+ *Spending equals the sum of Budget Lines in Planned, Executing and Obligated Status +

+ ); }; export default BigBudgetCard; diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx new file mode 100644 index 0000000000..565e4cbfa5 --- /dev/null +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -0,0 +1,59 @@ +import React from "react"; +import ResponsiveDonutWithInnerPercent from "../../DataViz/ResponsiveDonutWithInnerPercent"; +import CustomLayerComponent from "../../DataViz/ResponsiveDonutWithInnerPercent/CustomLayerComponent"; +import RoundedBox from "../../RoundedBox"; +import LegendItem from "../LineGraphWithLegendCard/LegendItem"; +import styles from "./styles.module.css"; + +const DonutGraphWithLegendCard = ({ data, fiscalYear, totalFunding }) => { + const [percent, setPercent] = React.useState(""); + const [hoverId, setHoverId] = React.useState(""); + + return ( + +

+ FY {fiscalYear.value} Budget Status +

+ +
+
0 ? `${styles.widthLegend} maxw-card-lg font-12px` : "width-card-lg font-12px" + } + > + {data.map((item) => ( + + ))} +
+ +
+
+ ); +}; + +export default DonutGraphWithLegendCard; diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/index.js b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/index.js new file mode 100644 index 0000000000..5dea8ebe50 --- /dev/null +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/index.js @@ -0,0 +1 @@ +export { default } from "./DonutGraphWithLegendCard"; diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/styles.module.css b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/styles.module.css new file mode 100644 index 0000000000..16ebd25655 --- /dev/null +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/styles.module.css @@ -0,0 +1,13 @@ +.cardBody { + flex: 3; +} + +.chartArea { + align-items: center; + justify-content: center; + flex: 2; +} + +.widthLegend { + width: 12.5rem; +} diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 123c78a434..a16551790b 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -1,7 +1,10 @@ import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; import DebugCode from "../../../components/DebugCode"; +import ProjectsAndAgreements from "../../../components/Portfolios/ResearchProjects/ProjectsAndAgreements"; import BigBudgetCard from "../../../components/UI/Cards/BudgetCard/BigBudgetCard"; +import DonutGraphWithLegendCard from "../../../components/UI/Cards/DonutGraphWithLegendCard"; +import { calculatePercent } from "../../../helpers/utils"; /** @typedef {import("../../../components/CANs/CANTypes").CAN} CAN @@ -28,6 +31,38 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { const { total_funding: totalFunding, planned_funding, obligated_funding, in_execution_funding } = CANFunding; const totalSpending = Number(planned_funding) + Number(obligated_funding) + Number(in_execution_funding); + const DRAFT_FUNDING = 0; // replace with actual data + + const data = [ + { + id: 1, + label: "DRAFT", + value: Math.round(DRAFT_FUNDING) || 0, + color: "var(--data-viz-primary-5)", + percent: `${calculatePercent(DRAFT_FUNDING, totalFunding)}%` + }, + { + id: 2, + label: "Planned", + value: Math.round(planned_funding) || 0, + color: "var(--data-viz-bl-by-status-2)", + percent: `${calculatePercent(planned_funding, totalFunding)}%` + }, + { + id: 3, + label: "Executing", + value: Math.round(in_execution_funding) || 0, + color: "var(--data-viz-bl-by-status-3)", + percent: `${calculatePercent(in_execution_funding, totalFunding)}%` + }, + { + id: 4, + label: "Obligated", + value: Math.round(obligated_funding) || 0, + color: "var(--data-viz-bl-by-status-4)", + percent: `${calculatePercent(obligated_funding, totalFunding)}%` + } + ]; return (
@@ -38,6 +73,15 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { totalSpending={totalSpending} totalFunding={totalFunding} /> +
+ {/* TODO: Create component for ProjectsAgreementsAndBLIs */} + + +

CAN Budget Lines

This is a list of all budget lines allocating funding from this CAN for the selected fiscal year.

From 7a4768dda8fa6d2ac24446cc2d8dcfbd4e967fad Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 17:12:24 -0600 Subject: [PATCH 24/35] docs: adds JSDocs types --- .../DonutGraphWithLegendCard.jsx | 39 ++++++++++++++----- .../src/pages/cans/detail/CanSpending.jsx | 12 +++--- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx index 565e4cbfa5..bb0fdd1a5e 100644 --- a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -5,15 +5,36 @@ import RoundedBox from "../../RoundedBox"; import LegendItem from "../LineGraphWithLegendCard/LegendItem"; import styles from "./styles.module.css"; -const DonutGraphWithLegendCard = ({ data, fiscalYear, totalFunding }) => { +/** + * @typedef {Object} Data + * @property {number} id - The id. + * @property {string} label - The label. + * @property {number} value - The value. + * @property {string} color - The color. + * @property {string} percent - The percent. + * @property {string} [tagStyleActive] - The tag style active. + */ + +/** + * @typedef {Object} DonutGraphWithLegendCardProps + * @property {Data[]} data - The array of data items. + * @property {string} title - The title for the card + * @property {number} totalFunding - The total funding. + */ + +/** + * @component DonutGraphWithLegendCard + * @param {DonutGraphWithLegendCardProps} props + * @returns {JSX.Element} + */ +const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { const [percent, setPercent] = React.useState(""); - const [hoverId, setHoverId] = React.useState(""); + const [hoverId, setHoverId] = React.useState(-1); + const reactId = React.useId(); return ( -

- FY {fiscalYear.value} Budget Status -

+

{title}

{ > {data.map((item) => ( { ))}
diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index a16551790b..517e47353c 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -31,14 +31,14 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { const { total_funding: totalFunding, planned_funding, obligated_funding, in_execution_funding } = CANFunding; const totalSpending = Number(planned_funding) + Number(obligated_funding) + Number(in_execution_funding); - const DRAFT_FUNDING = 0; // replace with actual data + const DRAFT_FUNDING = 1_000_000; // replace with actual data - const data = [ + const graphData = [ { id: 1, - label: "DRAFT", + label: "Draft", value: Math.round(DRAFT_FUNDING) || 0, - color: "var(--data-viz-primary-5)", + color: "var(--neutral-lighter)", percent: `${calculatePercent(DRAFT_FUNDING, totalFunding)}%` }, { @@ -77,8 +77,8 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { {/* TODO: Create component for ProjectsAgreementsAndBLIs */}
From f6b41b3c5a8bc8d710650e66e864c969a3af93b0 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 12 Nov 2024 17:29:03 -0600 Subject: [PATCH 25/35] refactor: use RoundedBox --- .../ProjectsAndAgreements.jsx | 21 ++++++++----------- .../DonutGraphWithLegendCard.jsx | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx index 68f61d9741..bf23018997 100644 --- a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx +++ b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx @@ -1,23 +1,20 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; -import CurrencyCard from "../../UI/Cards/CurrencyCard"; +import RoundedBox from "../../UI/RoundedBox"; import Tag from "../../UI/Tag/Tag"; -const ProjectsAndAgreements = ({ - numberOfProjects = 0, - numOfResearchProjects = 0, - numOfAdminAndSupportProjects = 0 -}) => { +const ProjectsAndAgreements = ({ numOfResearchProjects = 3, numOfAdminAndSupportProjects = 2 }) => { const fiscalYear = useSelector((state) => state.portfolio.selectedFiscalYear); const projectHeading = `FY ${fiscalYear.value} Projects`; const agreementHeading = `FY ${fiscalYear.value} Agreements`; - const plannedAgreements = "3"; - const executingAgreements = "2"; - const obligatedAgreements = "2"; - const numberOfAgreements = "7"; + const plannedAgreements = 3; + const executingAgreements = 2; + const obligatedAgreements = 2; + const numberOfProjects = numOfResearchProjects + numOfAdminAndSupportProjects; + const numberOfAgreements = plannedAgreements + executingAgreements + obligatedAgreements; return ( - +
{/* NOTE: left side */}
@@ -60,7 +57,7 @@ const ProjectsAndAgreements = ({
- + ); }; diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx index bb0fdd1a5e..e23ac7553a 100644 --- a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -33,7 +33,7 @@ const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { const reactId = React.useId(); return ( - +

{title}

From 278a160436334a37b06705969e6042ccc08a3707 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 13 Nov 2024 10:09:02 -0600 Subject: [PATCH 26/35] feat: adds percentage of CAN --- .../CANs/CANBudgetLineTable/CANBudgetLineTable.jsx | 8 +++++--- frontend/src/pages/cans/detail/CanSpending.jsx | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx index a52d7cf4eb..4d2f47647e 100644 --- a/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx +++ b/frontend/src/components/CANs/CANBudgetLineTable/CANBudgetLineTable.jsx @@ -2,6 +2,7 @@ import { formatDateNeeded } from "../../../helpers/utils"; import Table from "../../UI/Table"; import { TABLE_HEADERS } from "./CABBudgetLineTable.constants"; import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; +import { calculatePercent } from "../../../helpers/utils"; /** * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine */ @@ -9,6 +10,7 @@ import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; /** * @typedef {Object} CANBudgetLineTableProps * @property {BudgetLine[]} budgetLines + * @property {number} totalFunding */ /** @@ -16,7 +18,7 @@ import CANBudgetLineTableRow from "./CANBudgetLineTableRow"; * @param {CANBudgetLineTableProps} props * @returns {JSX.Element} - The component JSX. */ -const CANBudgetLineTable = ({ budgetLines }) => { +const CANBudgetLineTable = ({ budgetLines, totalFunding }) => { if (budgetLines.length === 0) { return

No budget lines have been added to this CAN.

; } @@ -31,9 +33,9 @@ const CANBudgetLineTable = ({ budgetLines }) => { agreementName="TBD" obligateDate={formatDateNeeded(budgetLine.date_needed || "")} fiscalYear={budgetLine.fiscal_year || "TBD"} - amount={budgetLine.amount || 0} + amount={budgetLine.amount ?? 0} fee={budgetLine.proc_shop_fee_percentage} - percentOfCAN={3} + percentOfCAN={calculatePercent(budgetLine.amount ?? 0, totalFunding)} status={budgetLine.status} inReview={budgetLine.in_review} creatorId={budgetLine.created_by} diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 517e47353c..11cb4286b8 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -84,7 +84,10 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => {

CAN Budget Lines

This is a list of all budget lines allocating funding from this CAN for the selected fiscal year.

- + ); From ad6c27df5b031cf345fc665ec4db1a6cc24009fa Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 13 Nov 2024 11:04:48 -0600 Subject: [PATCH 27/35] feat: adds ProjectAgreementBLI card --- .../ProjectAgreementBLICard.jsx | 73 +++++++++++++++++++ .../UI/Cards/ProjectAgreementBLICard/index.js | 1 + .../src/pages/cans/detail/CanSpending.jsx | 4 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx create mode 100644 frontend/src/components/UI/Cards/ProjectAgreementBLICard/index.js diff --git a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx new file mode 100644 index 0000000000..52fdcfc69a --- /dev/null +++ b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx @@ -0,0 +1,73 @@ +import RoundedBox from "../../RoundedBox"; +import Tag from "../../Tag"; + +/** + * @typedef {Object} ProjectAgreementBLICardProps + * @property {number} fiscalYear + */ + +/** + * @component - The Project Agreement BLI Card. + * @param {ProjectAgreementBLICardProps} props + * @returns {JSX.Element} - The Project Agreement BLI Card. + */ +const ProjectAgreementBLICard = ({ fiscalYear }) => { + const numOfResearchProjects = 3; + const numOfAdminAndSupportProjects = 2; + const projectHeading = `FY ${fiscalYear} Projects`; + const agreementHeading = `FY ${fiscalYear} Agreements`; + const plannedAgreements = 3; + const executingAgreements = 2; + const obligatedAgreements = 2; + const numberOfProjects = numOfResearchProjects + numOfAdminAndSupportProjects; + const numberOfAgreements = plannedAgreements + executingAgreements + obligatedAgreements; + + return ( + +
+ {/* NOTE: left side */} +
+

{projectHeading}

+
+ {numberOfProjects} +
+ + +
+
+
+ {/* NOTE: right side */} +
+

+ {agreementHeading} +

+
+ {numberOfAgreements} +
+ + + +
+
+
+
+
+ ); +}; + +export default ProjectAgreementBLICard; diff --git a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/index.js b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/index.js new file mode 100644 index 0000000000..d5944d2be5 --- /dev/null +++ b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/index.js @@ -0,0 +1 @@ +export { default } from "./ProjectAgreementBLICard"; diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 11cb4286b8..4dad49c245 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -1,9 +1,9 @@ import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; import DebugCode from "../../../components/DebugCode"; -import ProjectsAndAgreements from "../../../components/Portfolios/ResearchProjects/ProjectsAndAgreements"; import BigBudgetCard from "../../../components/UI/Cards/BudgetCard/BigBudgetCard"; import DonutGraphWithLegendCard from "../../../components/UI/Cards/DonutGraphWithLegendCard"; +import ProjectAgreementBLICard from "../../../components/UI/Cards/ProjectAgreementBLICard"; import { calculatePercent } from "../../../helpers/utils"; /** @@ -75,7 +75,7 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { />
{/* TODO: Create component for ProjectsAgreementsAndBLIs */} - + Date: Wed, 13 Nov 2024 16:12:06 -0600 Subject: [PATCH 28/35] docs: updates types --- .../components/Agreements/AgreementTypes.d.ts | 4 +-- .../src/components/DebugCode/DebugCode.jsx | 2 +- .../src/components/Projects/ProjectTypes.d.ts | 26 ++++++++++++++----- .../UI/Cards/BudgetCard/BigBudgetCard.jsx | 6 ++--- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Agreements/AgreementTypes.d.ts b/frontend/src/components/Agreements/AgreementTypes.d.ts index 16d620155d..3f6eb852fa 100644 --- a/frontend/src/components/Agreements/AgreementTypes.d.ts +++ b/frontend/src/components/Agreements/AgreementTypes.d.ts @@ -1,5 +1,5 @@ import { SafeUser } from "../Users/UserTypes"; -import { Project } from "../Projects/ProjectTypes"; +import { ResearchProject } from "../Projects/ProjectTypes"; import { BudgetLine } from "../BudgetLineItems/BudgetLineTypes"; export type Agreement = { @@ -16,7 +16,7 @@ export type Agreement = { procurement_tracker_id?: number; product_service_code?: ProductServiceCode; product_service_code_id?: number; - project?: Project; + project?: ResearchProject; project_id?: number; project_officer_id?: number; team_members?: SafeUser[]; diff --git a/frontend/src/components/DebugCode/DebugCode.jsx b/frontend/src/components/DebugCode/DebugCode.jsx index b87fa2af76..7388c5c016 100644 --- a/frontend/src/components/DebugCode/DebugCode.jsx +++ b/frontend/src/components/DebugCode/DebugCode.jsx @@ -7,7 +7,7 @@ import PropTypes from "prop-types"; * @component * @param {Object} props - The properties passed to the component. * @param {string} [props.title="DEBUG CODE"] - The title of the debug section. - * @param {Object | []} props.data - The data to be displayed in the debug section. + * @param {Object | any[]} props.data - The data to be displayed in the debug section. * * @returns {JSX.Element | null | boolean } The rendered JSX element, or null if not in development mode. * diff --git a/frontend/src/components/Projects/ProjectTypes.d.ts b/frontend/src/components/Projects/ProjectTypes.d.ts index 4b03f87faa..0e92ec9474 100644 --- a/frontend/src/components/Projects/ProjectTypes.d.ts +++ b/frontend/src/components/Projects/ProjectTypes.d.ts @@ -1,16 +1,28 @@ import { SafeUser } from "../Users/UserTypes"; -export type Project = { - created_by: number | null; - created_on: Date; - description: string; +export type ResearchProject = { id: number; + description: string; methodologies: string[]; - origination_date: Date; populations: string[]; - short_title: string; team_leaders: SafeUser[]; + origination_date: Date; + short_title: string; title: string; - updated_on: Date; url: string; + created_on: any; + updated_on: any; + created_by: any; + updated_by: any; + created_by_user: any; + updated_by_user: any; +}; + +export type Project = { + id: number; + project_type: string; + title: string; + short_title: string; + description: string; + url?: string; }; diff --git a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx index badbd56250..cb2655135c 100644 --- a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx +++ b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx @@ -1,8 +1,8 @@ import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import CurrencyFormat from "react-currency-format"; -import LineGraph from "../../DataViz/LineGraph"; import CurrencyWithSmallCents from "../../CurrencyWithSmallCents/CurrencyWithSmallCents"; +import LineGraph from "../../DataViz/LineGraph"; import RoundedBox from "../../RoundedBox"; import Tag from "../../Tag"; @@ -81,7 +81,7 @@ const BigBudgetCard = ({ title, totalSpending, totalFunding }) => {
Spending {""} { />{" "} of{" "} Date: Wed, 13 Nov 2024 16:24:18 -0600 Subject: [PATCH 29/35] refactor: adds dynamic data to cards --- .../ProjectAgreementBLICard.jsx | 119 +++++++++++++----- frontend/src/helpers/utils.js | 7 +- frontend/src/pages/cans/detail/Can.jsx | 30 ++++- .../src/pages/cans/detail/CanSpending.jsx | 21 +++- 4 files changed, 136 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx index 52fdcfc69a..48ffbac272 100644 --- a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx +++ b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx @@ -1,9 +1,20 @@ +import { BLI_STATUS } from "../../../../helpers/budgetLines.helpers"; +import { convertCodeForDisplay } from "../../../../helpers/utils"; import RoundedBox from "../../RoundedBox"; import Tag from "../../Tag"; +/** + * @typedef ItemCount + * @property {string} type + * @property {number} count + */ + /** * @typedef {Object} ProjectAgreementBLICardProps * @property {number} fiscalYear + * @property {ItemCount[]} [projects] + * @property {ItemCount[]} [agreements] + * @property {ItemCount[]} [budgetLines] */ /** @@ -11,57 +22,97 @@ import Tag from "../../Tag"; * @param {ProjectAgreementBLICardProps} props * @returns {JSX.Element} - The Project Agreement BLI Card. */ -const ProjectAgreementBLICard = ({ fiscalYear }) => { - const numOfResearchProjects = 3; - const numOfAdminAndSupportProjects = 2; +const ProjectAgreementBLICard = ({ fiscalYear, projects, agreements, budgetLines }) => { const projectHeading = `FY ${fiscalYear} Projects`; - const agreementHeading = `FY ${fiscalYear} Agreements`; - const plannedAgreements = 3; - const executingAgreements = 2; - const obligatedAgreements = 2; - const numberOfProjects = numOfResearchProjects + numOfAdminAndSupportProjects; - const numberOfAgreements = plannedAgreements + executingAgreements + obligatedAgreements; + const budgetLinesHeading = `FY ${fiscalYear} Budget Lines`; + const agreementsHeading = `FY ${fiscalYear} Agreements`; + + /** @param {ItemCount[]} items */ + const calculateTotalCount = (items) => { + return items && items.length > 0 ? items.reduce((acc, item) => acc + item.count, 0) : 0; + }; + + const totalProjectCount = calculateTotalCount(projects); + const totalBudgetLinesCount = calculateTotalCount(budgetLines); + const totalAgreementsCount = calculateTotalCount(agreements); + + /** @param {"DRAFT" | "PLANNED" | "IN_EXECUTING" | "OBLIGATED"} type */ + const tagStylesByType = (type) => { + switch (type) { + case BLI_STATUS.DRAFT: + return "bg-brand-data-viz-bl-by-status-1"; + case BLI_STATUS.PLANNED: + return "bg-brand-data-viz-bl-by-status-2 text-white margin-top-1"; + case BLI_STATUS.EXECUTING: + return "bg-brand-data-viz-bl-by-status-3 margin-top-1"; + case BLI_STATUS.OBLIGATED: + return "bg-brand-data-viz-bl-by-status-4 text-white margin-top-1"; + default: + return ""; + } + }; return (
- {/* NOTE: left side */}

{projectHeading}

- {numberOfProjects} + {totalProjectCount} +
+ {projects && + projects.length > 0 && + projects.map(({ type, count }, index) => ( + 0 ? "margin-top-1" : "" + }`} + text={`${count} ${convertCodeForDisplay("project", type)}`} + /> + ))} +
+
+
+ +
+

+ {agreementsHeading} +

+
+ {totalAgreementsCount}
- - + {agreements && + agreements.length > 0 && + agreements.map(({ type, count }, index) => ( + 0 ? "margin-top-1" : "" + }`} + text={`${count} ${convertCodeForDisplay("agreementType", type)}`} + /> + ))}
- {/* NOTE: right side */} +

- {agreementHeading} + {budgetLinesHeading}

- {numberOfAgreements} + {totalBudgetLinesCount}
- - - + {budgetLines && + budgetLines.length > 0 && + budgetLines.map(({ type, count }, index) => ( + 0 ? "margin-top-1" : ""}`} + text={`${count} ${convertCodeForDisplay("budgetLineStatus", type)}`} + /> + ))}
diff --git a/frontend/src/helpers/utils.js b/frontend/src/helpers/utils.js index 5394518595..417a60785c 100644 --- a/frontend/src/helpers/utils.js +++ b/frontend/src/helpers/utils.js @@ -84,6 +84,7 @@ export const draftBudgetLineStatuses = ["DRAFT"]; * @property {Object.} serviceRequirementType - Display text for service requirement types. * @property {Object.} changeToTypes - Display text for change to types. * @property {Object.} methodOfTransfer - Display text for change to statuses. + * @property {Object.} project - Display text for project types. * */ @@ -187,12 +188,16 @@ export const codesToDisplayText = { COST_SHARE: "MOU", IAA: "IAA", IDDA: "IDDA" + }, + project: { + ADMINISTRATIVE_AND_SUPPORT: "Admin & Support", + RESEARCH: "Research" } }; /** * Converts a code value into a display text value based on a predefined mapping. - * @param {("agreementType" | "agreementReason" | "budgetLineStatus" | "validation" | "classNameLabels" | "baseClassNameLabels"| "agreementPropertyLabels" | "budgetLineItemPropertyLabels" | "changeToTypes" | "methodOfTransfer")} listName - The name of the list to retrieve the mapping from the codesToDisplayText object. This parameter is required. + * @param {("agreementType" | "agreementReason" | "budgetLineStatus" | "validation" | "classNameLabels" | "baseClassNameLabels"| "agreementPropertyLabels" | "budgetLineItemPropertyLabels" | "changeToTypes" | "methodOfTransfer" | 'project')} listName - The name of the list to retrieve the mapping from the codesToDisplayText object. This parameter is required. * @param {string} code - The code value to convert. This parameter is required. * @returns {string} The display text value for the code, or the original code value if no mapping is found. * @throws {Error} If either the listName or code parameter is not provided. diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index 32f18f5a81..b260ac4fd9 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -16,6 +16,21 @@ import CanSpending from "./CanSpending"; * @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine */ +const getTypesCounts = (items, keyToCount) => { + if (!items || items.length === 0) return []; + + return Object.entries( + items.reduce((acc, item) => { + const type = item[keyToCount]; + if (!acc[type]) { + acc[type] = 0; + } + acc[type]++; + return acc; + }, {}) + ).map(([type, count]) => ({ type, count })); +}; + const Can = () => { const urlPathParams = useParams(); const canId = parseInt(urlPathParams.id || "-1"); @@ -37,11 +52,21 @@ const Can = () => { return
CAN not found
; } - const { number, description, nick_name: nickname, portfolio } = can; + const { number, description, nick_name: nickname, portfolio, projects } = can; const { division_id: divisionId, team_leaders: teamLeaders, name: portfolioName } = portfolio; const subTitle = `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}`; + const projectTypesCount = getTypesCounts(projects, "project_type"); + const budgetLineTypesCount = getTypesCounts(budgetLineItemsByFiscalYear, "status"); + const testAgreements = [ + { type: "CONTRACT", count: 8 }, + { type: "GRANT", count: 2 } + // { type: "DIRECT_ALLOCATION", count: 1 }, + // { type: "IAA", count: 1 } + // { type: "MISCELLANEOUS", count: 1 } + ]; + return ( { budgetLines={budgetLineItemsByFiscalYear} fiscalYear={fiscalYear} canId={canId} + projects={projectTypesCount} + budgetLineTypesCount={budgetLineTypesCount} + agreements={testAgreements} /> } /> diff --git a/frontend/src/pages/cans/detail/CanSpending.jsx b/frontend/src/pages/cans/detail/CanSpending.jsx index 4dad49c245..950a4debca 100644 --- a/frontend/src/pages/cans/detail/CanSpending.jsx +++ b/frontend/src/pages/cans/detail/CanSpending.jsx @@ -1,11 +1,16 @@ import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; import CANBudgetLineTable from "../../../components/CANs/CANBudgetLineTable"; -import DebugCode from "../../../components/DebugCode"; import BigBudgetCard from "../../../components/UI/Cards/BudgetCard/BigBudgetCard"; import DonutGraphWithLegendCard from "../../../components/UI/Cards/DonutGraphWithLegendCard"; import ProjectAgreementBLICard from "../../../components/UI/Cards/ProjectAgreementBLICard"; import { calculatePercent } from "../../../helpers/utils"; +/** + * @typedef ItemCount + * @property {string} type + * @property {number} count + */ + /** @typedef {import("../../../components/CANs/CANTypes").CAN} CAN @typedef {import("../../../components/BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine @@ -16,6 +21,9 @@ import { calculatePercent } from "../../../helpers/utils"; * @property {BudgetLine[]} budgetLines * @property {number} fiscalYear * @property {number} canId + * @property {ItemCount[]} [projects] + * @property {ItemCount[]} [budgetLineTypesCount] + * @property {ItemCount[]} [agreements] */ /** @@ -23,7 +31,7 @@ import { calculatePercent } from "../../../helpers/utils"; * @param {CanSpendingProps} props * @returns {JSX.Element} - The component JSX. */ -const CanSpending = ({ budgetLines, fiscalYear, canId }) => { +const CanSpending = ({ budgetLines, fiscalYear, canId, projects, budgetLineTypesCount, agreements }) => { const { data: CANFunding, isLoading } = useGetCanFundingSummaryQuery({ id: canId, fiscalYear: fiscalYear }); if (isLoading) return
Loading...
; @@ -74,8 +82,12 @@ const CanSpending = ({ budgetLines, fiscalYear, canId }) => { totalFunding={totalFunding} />
- {/* TODO: Create component for ProjectsAgreementsAndBLIs */} - + { budgetLines={budgetLines} totalFunding={totalFunding} /> - ); }; From 3ec245241a6f3e4e59b3c4d72039e16bc5f53a74 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 13 Nov 2024 16:38:19 -0600 Subject: [PATCH 30/35] fix: a11y issue --- .../DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx index e23ac7553a..e00512ad67 100644 --- a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -30,7 +30,7 @@ import styles from "./styles.module.css"; const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { const [percent, setPercent] = React.useState(""); const [hoverId, setHoverId] = React.useState(-1); - const reactId = React.useId(); + const id = crypto.randomUUID(); return ( @@ -56,7 +56,7 @@ const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { ))}
From 1d37996691022aa7d9c80ba11223eb659210d14e Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 13 Nov 2024 16:58:07 -0600 Subject: [PATCH 31/35] fix: api call --- .../AgreementCANReviewAccordion.jsx | 2 +- .../CANs/CANFundingCard/CANFundingCard.jsx | 17 +++++++++-------- .../ResearchProjects/ProjectsAndAgreements.jsx | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.jsx b/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.jsx index f9157fac37..8174092259 100644 --- a/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.jsx +++ b/frontend/src/components/Agreements/AgreementCANReviewAccordion/AgreementCANReviewAccordion.jsx @@ -28,7 +28,7 @@ const AgreementCANReviewAccordion = ({ action, isApprovePage = false }) => { - const { data: portfolios, error, isLoading } = useGetPortfoliosQuery(); + const { data: portfolios, error, isLoading } = useGetPortfoliosQuery({}); if (isLoading) { return
Loading...
; } diff --git a/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx b/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx index d2dade6706..bccc0a9978 100644 --- a/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx +++ b/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx @@ -7,20 +7,21 @@ import CurrencyWithSmallCents from "../../UI/CurrencyWithSmallCents/CurrencyWith import RoundedBox from "../../UI/RoundedBox"; import Tag from "../../UI/Tag"; import LineGraph from "../../UI/DataViz/LineGraph"; - /** - * A component that displays funding information for a CAN - * @component - * @param {Object} props - The props object. - * @param {Object} props.can - The CAN object. - * @param {number} props.pendingAmount - The pending amount. - * @param {boolean} props.afterApproval - A flag indicating whether the funding is after approval. + * @typedef {Object} CANFundingCardProps + * @property {import("../../../components/CANs/CANTypes").CAN} can - The CAN object. + * @property {number} pendingAmount - The pending amount. + * @property {boolean} afterApproval - A flag indicating whether the funding is after approval. + */ +/** + * @component - displays funding information for a CAN in a card format + * @param {CANFundingCardProps} props - The component props. * @returns {JSX.Element} - The CANFundingCard component. */ const CANFundingCard = ({ can, pendingAmount, afterApproval }) => { const adjustAmount = afterApproval ? pendingAmount : 0; const canId = can?.id; - const { data, error, isLoading } = useGetCanFundingSummaryQuery(canId); + const { data, error, isLoading } = useGetCanFundingSummaryQuery({ id: canId }); if (isLoading) { return
Loading...
; diff --git a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx index bf23018997..12679f7525 100644 --- a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx +++ b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx @@ -14,7 +14,7 @@ const ProjectsAndAgreements = ({ numOfResearchProjects = 3, numOfAdminAndSupport const numberOfAgreements = plannedAgreements + executingAgreements + obligatedAgreements; return ( - +
{/* NOTE: left side */}
From 6ad828b35a5a397bd54ac2fa44f5cd330cd4adf4 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 13 Nov 2024 17:22:31 -0600 Subject: [PATCH 32/35] test: adds e2e test --- frontend/cypress/e2e/canDetail.cy.js | 7 +++++++ .../src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx | 1 + .../DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx | 5 ++++- .../ProjectAgreementBLICard/ProjectAgreementBLICard.jsx | 5 ++++- frontend/src/components/UI/RoundedBox/RoundedBox.jsx | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/frontend/cypress/e2e/canDetail.cy.js b/frontend/cypress/e2e/canDetail.cy.js index b1e7cabcfd..e16d1a2298 100644 --- a/frontend/cypress/e2e/canDetail.cy.js +++ b/frontend/cypress/e2e/canDetail.cy.js @@ -28,6 +28,13 @@ describe("CAN detail page", () => { cy.get("table").should("exist"); // table should have more than 1 row cy.get("tbody").children().should("have.length.greaterThan", 1); + cy.get("#big-budget-summary-card").should("exist"); + cy.get("#big-budget-summary-card").should("contain", "-$ 3,000,000.00"); + cy.get("#project-agreement-bli-card").should("exist"); + cy.get("span").should("contain", "3 Draft"); + cy.get("span").should("contain", "1 Executing"); + cy.get("span").should("contain", "1 Planned"); + cy.get("#donut-graph-with-legend-card").should("exist"); // switch to a different fiscal year cy.get("#fiscal-year-select").select("2022"); // table should not exist diff --git a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx index cb2655135c..00d36c4fd9 100644 --- a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx +++ b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx @@ -38,6 +38,7 @@ const BigBudgetCard = ({ title, totalSpending, totalFunding }) => { <> diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx index e00512ad67..31b56b4a23 100644 --- a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -33,7 +33,10 @@ const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { const id = crypto.randomUUID(); return ( - +

{title}

diff --git a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx index 48ffbac272..17bb777f17 100644 --- a/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx +++ b/frontend/src/components/UI/Cards/ProjectAgreementBLICard/ProjectAgreementBLICard.jsx @@ -53,7 +53,10 @@ const ProjectAgreementBLICard = ({ fiscalYear, projects, agreements, budgetLines }; return ( - +

{projectHeading}

diff --git a/frontend/src/components/UI/RoundedBox/RoundedBox.jsx b/frontend/src/components/UI/RoundedBox/RoundedBox.jsx index 8433a36751..57983b5cde 100644 --- a/frontend/src/components/UI/RoundedBox/RoundedBox.jsx +++ b/frontend/src/components/UI/RoundedBox/RoundedBox.jsx @@ -5,6 +5,7 @@ import cssClasses from "./styles.module.css"; * @param {Object} props - Component props. * @param {React.ReactNode} props.children - Child elements. * @param {string} [props.className] - Additional CSS classes. + * @param {string} [props.id] - Element ID. * @param {string} [props.dataCy] - Data attribute for Cypress tests. * @param {Object} [props.rest] - Additional props to be passed * @returns {JSX.Element} Rendered component. From 62a9ab66a3d1f3c5b1fe0069d97060cc0f8a4623 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 14 Nov 2024 08:57:32 -0600 Subject: [PATCH 33/35] refactor: better fallback values --- frontend/src/pages/cans/detail/Can.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx index b260ac4fd9..04de218159 100644 --- a/frontend/src/pages/cans/detail/Can.jsx +++ b/frontend/src/pages/cans/detail/Can.jsx @@ -33,7 +33,7 @@ const getTypesCounts = (items, keyToCount) => { const Can = () => { const urlPathParams = useParams(); - const canId = parseInt(urlPathParams.id || "-1"); + const canId = parseInt(urlPathParams.id ?? "-1"); /** @type {{data?: CAN | undefined, isLoading: boolean}} */ const { data: can, isLoading } = useGetCanByIdQuery(canId); const selectedFiscalYear = useSelector((state) => state.canDetail.selectedFiscalYear); @@ -70,7 +70,7 @@ const Can = () => { return ( @@ -88,7 +88,7 @@ const Can = () => { Date: Thu, 14 Nov 2024 11:26:07 -0600 Subject: [PATCH 34/35] style: ux review --- .../BLIStatusSummaryCard/BLIStatusSummaryCard.jsx | 2 +- .../CANs/CANFundingCard/CANFundingCard.jsx | 2 +- .../PortfolioFundingByBudgetStatus.jsx | 2 +- .../ResearchProjects/ProjectsAndAgreements.jsx | 2 +- .../UI/Cards/BudgetCard/BigBudgetCard.jsx | 2 +- .../components/UI/Cards/BudgetCard/BudgetCard.jsx | 2 +- frontend/src/components/UI/Cards/Card/Card.jsx | 3 +-- .../DonutGraphWithLegendCard.jsx | 11 +++-------- .../DonutGraphWithLegendCard/styles.module.css | 2 +- .../Cards/LineGraphWithLegendCard/LegendItem.jsx | 8 ++++---- .../ProjectAgreementBLICard.jsx | 14 +++++++------- .../src/components/UI/RoundedBox/RoundedBox.jsx | 2 ++ frontend/src/pages/Home.jsx | 2 +- frontend/src/pages/cans/detail/Can.jsx | 8 ++++---- 14 files changed, 29 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/BudgetLineItems/BLIStatusSummaryCard/BLIStatusSummaryCard.jsx b/frontend/src/components/BudgetLineItems/BLIStatusSummaryCard/BLIStatusSummaryCard.jsx index 912348e2d2..96bf1ce777 100644 --- a/frontend/src/components/BudgetLineItems/BLIStatusSummaryCard/BLIStatusSummaryCard.jsx +++ b/frontend/src/components/BudgetLineItems/BLIStatusSummaryCard/BLIStatusSummaryCard.jsx @@ -124,7 +124,7 @@ const BLIStatusSummaryCard = ({ budgetLines }) => { return (

Budget Lines By Status

diff --git a/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx b/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx index bccc0a9978..c380322e04 100644 --- a/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx +++ b/frontend/src/components/CANs/CANFundingCard/CANFundingCard.jsx @@ -61,7 +61,7 @@ const CANFundingCard = ({ can, pendingAmount, afterApproval }) => { return ( diff --git a/frontend/src/components/Portfolios/PortfolioFundingByBudgetStatus/PortfolioFundingByBudgetStatus.jsx b/frontend/src/components/Portfolios/PortfolioFundingByBudgetStatus/PortfolioFundingByBudgetStatus.jsx index 9e3339ff97..0adab43058 100644 --- a/frontend/src/components/Portfolios/PortfolioFundingByBudgetStatus/PortfolioFundingByBudgetStatus.jsx +++ b/frontend/src/components/Portfolios/PortfolioFundingByBudgetStatus/PortfolioFundingByBudgetStatus.jsx @@ -82,7 +82,7 @@ const PortfolioFundingByBudgetStatus = () => { }; return ( - +

FY {fiscalYear.value} Budget Status

diff --git a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx index 12679f7525..fd05deb593 100644 --- a/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx +++ b/frontend/src/components/Portfolios/ResearchProjects/ProjectsAndAgreements.jsx @@ -14,7 +14,7 @@ const ProjectsAndAgreements = ({ numOfResearchProjects = 3, numOfAdminAndSupport const numberOfAgreements = plannedAgreements + executingAgreements + obligatedAgreements; return ( - +
{/* NOTE: left side */}
diff --git a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx index 00d36c4fd9..1c3b70cfe4 100644 --- a/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx +++ b/frontend/src/components/UI/Cards/BudgetCard/BigBudgetCard.jsx @@ -37,7 +37,7 @@ const BigBudgetCard = ({ title, totalSpending, totalFunding }) => { return ( <> { return ( diff --git a/frontend/src/components/UI/Cards/Card/Card.jsx b/frontend/src/components/UI/Cards/Card/Card.jsx index c6d9442add..4b972425e3 100644 --- a/frontend/src/components/UI/Cards/Card/Card.jsx +++ b/frontend/src/components/UI/Cards/Card/Card.jsx @@ -17,10 +17,9 @@ import RoundedBox from "../../RoundedBox"; const Card = ({ title, children, dataCy = "", ...rest }) => { return ( {title &&

{title}

} diff --git a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx index 31b56b4a23..85bc961cd5 100644 --- a/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx +++ b/frontend/src/components/UI/Cards/DonutGraphWithLegendCard/DonutGraphWithLegendCard.jsx @@ -34,17 +34,12 @@ const DonutGraphWithLegendCard = ({ data, title, totalFunding }) => { return (

{title}

-
-
0 ? `${styles.widthLegend} maxw-card-lg font-12px` : "width-card-lg font-12px" - } - > +
0 ? `${styles.widthLegend} font-12px` : "width-card-lg font-12px"}> {data.map((item) => ( {