From 00d372b16e1bedd0148e0241b4e9bff4d89e7b0b Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Mon, 16 Sep 2024 16:43:26 -0500 Subject: [PATCH 01/41] refactor: starts CANs list --- frontend/src/index.jsx | 41 ++++++++++-------- frontend/src/pages/cans/list/CanList.jsx | 53 ++++++++++-------------- frontend/src/pages/cans/list/CanTabs.jsx | 49 ++++++++++++++++++++++ 3 files changed, 94 insertions(+), 49 deletions(-) create mode 100644 frontend/src/pages/cans/list/CanTabs.jsx diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index d4268be8a1..95c8bce709 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -231,24 +231,29 @@ const router = createBrowserRouter( ) }} /> - } - /> - } - handle={{ - crumb: () => ( - - Cans - - ) - }} - /> + {/*TODO: remove flag once CANS are ready */} + {import.meta.env.DEV && ( + <> + } + /> + } + handle={{ + crumb: () => ( + + Cans + + ) + }} + /> + + )} } diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index 4b28c5ace1..6f50daf7e7 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -1,48 +1,39 @@ import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { Link, Outlet } from "react-router-dom"; +import { Link } from "react-router-dom"; import App from "../../../App"; import { getCanList } from "./getCanList"; import TextClip from "../../../components/UI/Text/TextClip"; +import DebugCode from "../../../components/DebugCode"; +import TablePageLayout from "../../../components/Layouts/TablePageLayout"; +import BLITags from "../../budgetLines/list/BLITabs"; +import CANTags from "./CansTabs"; const CanList = () => { const dispatch = useDispatch(); const canList = useSelector((state) => state.canList.cans); - - const tableClasses = "usa-table usa-table--borderless margin-x-auto"; - + const myCANsUrl = false; useEffect(() => { dispatch(getCanList()); }, [dispatch]); + // TODO: remove flag once CANS are ready + /**/ return ( - -

CANs

- - -
+ import.meta.env.DEV && ( + + } + /> + + ) ); }; diff --git a/frontend/src/pages/cans/list/CanTabs.jsx b/frontend/src/pages/cans/list/CanTabs.jsx new file mode 100644 index 0000000000..3a7d27db49 --- /dev/null +++ b/frontend/src/pages/cans/list/CanTabs.jsx @@ -0,0 +1,49 @@ +import { Link, useLocation } from "react-router-dom"; +import styles from "../../../components/Portfolios/PortfolioTabsSection/PortfolioTabsSection.module.scss"; +import TabsSection from "../../../components/UI/TabsSection"; + +/** + * A header section of the Budget lines list page that contains the filters. + * @component + * @returns {JSX.Element} - The procurement shop select element. + */ +const CANTags = () => { + const location = useLocation(); + const selected = `font-sans-2xs text-bold ${styles.listItemSelected}`; + const notSelected = `font-sans-2xs text-bold ${styles.listItemNotSelected}`; + + const paths = [ + { + name: "", + label: "All CANs" + }, + { + name: "?filter=my-cans", + label: "My CANs" + } + ]; + + const links = paths.map((path) => { + const queryString = `${path.name}`; + + return ( + + {path.label} + + ); + }); + + return ( + + ); +}; + +export default CANTags; From 1f8adcf74b2ea1efd74423fdd1fa497ce2c45e41 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Mon, 16 Sep 2024 17:36:51 -0500 Subject: [PATCH 02/41] feat: adds CANTable and friends --- .../CANs/CANTable/CANTable.constants.js | 12 ++++++++ .../src/components/CANs/CANTable/CANTable.jsx | 24 +++++++++++++++ .../components/CANs/CANTable/CANTableRow.jsx | 29 +++++++++++++++++++ .../src/components/CANs/CANTable/index.js | 1 + frontend/src/pages/cans/list/CanList.jsx | 11 ++++--- 5 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/CANs/CANTable/CANTable.constants.js create mode 100644 frontend/src/components/CANs/CANTable/CANTable.jsx create mode 100644 frontend/src/components/CANs/CANTable/CANTableRow.jsx create mode 100644 frontend/src/components/CANs/CANTable/index.js diff --git a/frontend/src/components/CANs/CANTable/CANTable.constants.js b/frontend/src/components/CANs/CANTable/CANTable.constants.js new file mode 100644 index 0000000000..6e066038b9 --- /dev/null +++ b/frontend/src/components/CANs/CANTable/CANTable.constants.js @@ -0,0 +1,12 @@ +export const CAN_TABLE_HEADINGS = [ + "CAN", + "Portfolio", + "FY", + "Active Period", + "Obligate By", + "Transfer", + "FY Budget", + "$ Available" +]; + +export const CANS_PER_PAGE = 25; diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx new file mode 100644 index 0000000000..75fc765726 --- /dev/null +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -0,0 +1,24 @@ +import Table from "../../UI/Table"; +import { CAN_TABLE_HEADINGS } from "./CANTable.constants"; +import CANTableRow from "./CANTableRow"; + +const CANTable = ({ cans }) => { + return ( + + {cans.map((can) => ( + + ))} +
+ ); +}; + +export default CANTable; diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx new file mode 100644 index 0000000000..28bd3fd5e5 --- /dev/null +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -0,0 +1,29 @@ +import CurrencyFormat from "react-currency-format"; +import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; + +const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, availableFunds }) => { + return ( + + {can} + {portfolio} + {FY} + {activePeriod > 1 ? `${activePeriod} years` : `${activePeriod} year`} + {obligateBy} + {transfer} + + value} + /> + + {availableFunds} + + ); +}; + +export default CANTableRow; diff --git a/frontend/src/components/CANs/CANTable/index.js b/frontend/src/components/CANs/CANTable/index.js new file mode 100644 index 0000000000..ed3769afde --- /dev/null +++ b/frontend/src/components/CANs/CANTable/index.js @@ -0,0 +1 @@ +export { default } from "./CANTable"; diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index 6f50daf7e7..2613bce476 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -1,13 +1,11 @@ import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { Link } from "react-router-dom"; import App from "../../../App"; -import { getCanList } from "./getCanList"; -import TextClip from "../../../components/UI/Text/TextClip"; +import CANTable from "../../../components/CANs/CANTable"; import DebugCode from "../../../components/DebugCode"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; -import BLITags from "../../budgetLines/list/BLITabs"; -import CANTags from "./CansTabs"; +import CANTags from "./CanTabs"; +import { getCanList } from "./getCanList"; const CanList = () => { const dispatch = useDispatch(); @@ -18,7 +16,6 @@ const CanList = () => { }, [dispatch]); // TODO: remove flag once CANS are ready - /**/ return ( import.meta.env.DEV && ( @@ -31,7 +28,9 @@ const CanList = () => { : "This is a list of all CANs across OPRE that are or were active within the selected Fiscal Year." } TabsSection={} + TableSection={} /> + ) ); From 2bcbc40de0f8a999ac7855188552cbbd8767d7e2 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 12:38:43 -0500 Subject: [PATCH 03/41] refactor: conditionally render button --- .../TablePageLayout/TablePageLayout.jsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/Layouts/TablePageLayout/TablePageLayout.jsx b/frontend/src/components/Layouts/TablePageLayout/TablePageLayout.jsx index b38cfcf271..0b6bed8bb5 100644 --- a/frontend/src/components/Layouts/TablePageLayout/TablePageLayout.jsx +++ b/frontend/src/components/Layouts/TablePageLayout/TablePageLayout.jsx @@ -16,8 +16,8 @@ import icons from "../../../uswds/img/sprite.svg"; * @param {React.ReactNode} [props.FilterButton] - The filter button to display. * @param {React.ReactNode} [props.TableSection] - The table to display. * @param {React.ReactNode} [props.SummaryCardsSection] - The summary cards to display. - * @param {string} props.buttonText - The text to display on the button. - * @param {string} props.buttonLink - The link to navigate to when the button is clicked. + * @param {string} [props.buttonText] - The text to display on the button. + * @param {string} [props.buttonLink] - The link to navigate to when the button is clicked. * @returns {JSX.Element} - The rendered component. */ export const TablePageLayout = ({ @@ -37,18 +37,20 @@ export const TablePageLayout = ({ <>

{title}

- - - - - {buttonText} - + + + + {buttonText} + + )}
{TabsSection}
@@ -78,6 +80,6 @@ TablePageLayout.propTypes = { FilterButton: PropTypes.node, TableSection: PropTypes.node, SummaryCardsSection: PropTypes.node, - buttonText: PropTypes.string.isRequired, - buttonLink: PropTypes.string.isRequired + buttonText: PropTypes.string, + buttonLink: PropTypes.string }; From d2163a31c58b9d1e94f5f4c1e0ad4227e7dbfe52 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 13:25:19 -0500 Subject: [PATCH 04/41] feat: adds mycans bool --- frontend/src/pages/cans/list/CanList.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index 2613bce476..90826c692c 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -6,11 +6,13 @@ import DebugCode from "../../../components/DebugCode"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; import CANTags from "./CanTabs"; import { getCanList } from "./getCanList"; +import { useSearchParams } from "react-router-dom"; const CanList = () => { const dispatch = useDispatch(); const canList = useSelector((state) => state.canList.cans); - const myCANsUrl = false; + const [searchParams] = useSearchParams(); + const myCANsUrl = searchParams.get("filter") === "my-cans"; useEffect(() => { dispatch(getCanList()); }, [dispatch]); From f7a1d5e3464e00865bcc7542f17099f6ad9d022b Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 13:25:32 -0500 Subject: [PATCH 05/41] feat: adds method_of_transfer to cans data --- backend/data_tools/data/can_data.json5 | 91 +++++++++++++++----------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/backend/data_tools/data/can_data.json5 b/backend/data_tools/data/can_data.json5 index b6a4de71d5..80e91688ce 100644 --- a/backend/data_tools/data/can_data.json5 +++ b/backend/data_tools/data/can_data.json5 @@ -5,87 +5,104 @@ fiscal_year: 2023, fund_code: "AAXXXX20231DAD", funding_source: "OPRE", + method_of_transfer: "DIRECT", }, { // 2 fiscal_year: 2021, fund_code: "BBXXXX20215DAD", + method_of_transfer: "COST_SHARE", }, { // 3 fiscal_year: 2022, - fund_code: "CCXXXX20225DAD" + fund_code: "CCXXXX20225DAD", + method_of_transfer: "IDDA", }, { // 4 fiscal_year: 2021, fund_code: "DDXXXX20215DAD", + method_of_transfer: "IAA", }, { // 5 fiscal_year: 2021, fund_code: "EEXXXX20215DAD", + method_of_transfer: "IDDA", }, { // 6 fiscal_year: 2021, - fund_code: "FFXXXX20215DAD" + fund_code: "FFXXXX20215DAD", + method_of_transfer: "DIRECT", }, { // 7 fiscal_year: 2023, - fund_code: "GGXXXX20231DAD" + fund_code: "GGXXXX20231DAD", + method_of_transfer: "DIRECT", }, { // 8 fiscal_year: 2023, - fund_code: "HHXXXX20231DAD" + fund_code: "HHXXXX20231DAD", + method_of_transfer: "IDDA", }, { // 9 fiscal_year: 2023, - fund_code: "IIXXXX20231DAD" + fund_code: "IIXXXX20231DAD", + method_of_transfer: "IAA", }, { // 10 fiscal_year: 2023, - fund_code: "JJXXXX20231DAD" + fund_code: "JJXXXX20231DAD", + method_of_transfer: "DIRECT", }, { // 11 fiscal_year: 2023, - fund_code: "KKXXXX20235DAD" + fund_code: "KKXXXX20235DAD", + method_of_transfer: "IDDA", }, { // 12 fiscal_year: 2022, - fund_code: "LLXXXX20225DAD" + fund_code: "LLXXXX20225DAD", + method_of_transfer: "IAA", }, { // 13 fiscal_year: 2023, - fund_code: "MMXXXX20235DAD" + fund_code: "MMXXXX20235DAD", + method_of_transfer: "DIRECT", }, { // 14 fiscal_year: 2023, - fund_code: "NNXXXX20231DAD" + fund_code: "NNXXXX20231DAD", + method_of_transfer: "IDDA", }, { // 15 fiscal_year: 2023, - fund_code: "OOXXXX20235DAD" + fund_code: "OOXXXX20235DAD", + method_of_transfer: "DIRECT", }, { // 16 fiscal_year: 2023, - fund_code: "PPXXXX20235DAD" + fund_code: "PPXXXX20235DAD", + method_of_transfer: "DIRECT", }, { // 17 fiscal_year: 2023, - fund_code: "QQXXXX20235DAD" - } + fund_code: "QQXXXX20235DAD", + method_of_transfer: "IDDA", + }, ], can: [ { @@ -94,7 +111,7 @@ description: "Healthy Marriages Responsible Fatherhood - OPRE", nick_name: "HMRF-OPRE", portfolio_id: 6, - funding_details_id: 1 + funding_details_id: 1, }, { // 501 @@ -102,7 +119,7 @@ description: "Incoming Interagency Agreements", nick_name: "IAA-Incoming", portfolio_id: 1, - funding_details_id: 2 + funding_details_id: 2, }, { // 502 @@ -110,7 +127,7 @@ description: "Social Science Research and Development", nick_name: "SSRD", portfolio_id: 8, - funding_details_id: 3 + funding_details_id: 3, }, { // 503 @@ -118,7 +135,7 @@ description: "Child Development Research Fellowship Grant Program", nick_name: "ASPE SRCD-IDDA", portfolio_id: 1, - funding_details_id: 4 + funding_details_id: 4, }, { // 504 @@ -126,7 +143,7 @@ description: "Head Start Research", nick_name: "HS", portfolio_id: 2, - funding_details_id: 5 + funding_details_id: 5, }, { // 505 @@ -134,7 +151,7 @@ description: "Kinship Navigation", nick_name: "Kin-Nav", portfolio_id: 6, - funding_details_id: 6 + funding_details_id: 6, }, { // 506 @@ -142,7 +159,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 7 + funding_details_id: 7, }, { // 507 @@ -150,7 +167,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 8 + funding_details_id: 8, }, { // 508 @@ -158,7 +175,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 9 + funding_details_id: 9, }, { // 509 @@ -166,7 +183,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 10 + funding_details_id: 10, }, { // 510 @@ -174,7 +191,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 11 + funding_details_id: 11, }, { // 511 @@ -182,7 +199,7 @@ description: "Healthy Marriages Responsible Fatherhood - OFA", nick_name: "HMRF-OFA", portfolio_id: 6, - funding_details_id: 12 + funding_details_id: 12, }, { // 512 @@ -193,10 +210,10 @@ funding_details_id: 13, projects: [ { - "tablename": "project", - "id": 1000 - } - ] + tablename: "project", + id: 1000, + }, + ], }, { // 513 @@ -204,7 +221,7 @@ description: "MIHOPE Check-in 2023", nick_name: "MIHOPE 23", portfolio_id: 3, - funding_details_id: 14 + funding_details_id: 14, }, { // 514 @@ -212,7 +229,7 @@ description: "MIHOPE Check-in 2024", nick_name: "MIHOPE 24", portfolio_id: 3, - funding_details_id: 15 + funding_details_id: 15, }, { // 515 @@ -220,7 +237,7 @@ description: "MOHOPE Long-Term", nick_name: "MIHOPE LT", portfolio_id: 3, - funding_details_id: 16 + funding_details_id: 16, }, { // 516 @@ -228,8 +245,8 @@ description: "Shared CAN", nick_name: "SHARED", portfolio_id: 3, - funding_details_id: 17 - } + funding_details_id: 17, + }, ], can_funding_budget: [ { @@ -386,7 +403,7 @@ fiscal_year: 2023, can_id: 516, budget: 500000.0, - } + }, ], can_funding_received: [ { @@ -518,6 +535,6 @@ fiscal_year: 2023, can_id: 515, funding: 1000000.0, - } + }, ], } From 099e037aa1d39743e497fd41e3344b8561538c11 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:10:36 -0500 Subject: [PATCH 06/41] refactor: use ENUM over string --- backend/ops_api/ops/schemas/cans.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/ops_api/ops/schemas/cans.py b/backend/ops_api/ops/schemas/cans.py index 02101e3b35..b0f218eaa9 100644 --- a/backend/ops_api/ops/schemas/cans.py +++ b/backend/ops_api/ops/schemas/cans.py @@ -1,6 +1,6 @@ from marshmallow import Schema, fields -from models import PortfolioStatus +from models import PortfolioStatus, CANMethodOfTransfer from ops_api.ops.schemas.budget_line_items import BudgetLineItemResponseSchema from ops_api.ops.schemas.projects import ProjectSchema from ops_api.ops.schemas.users import SafeUserSchema @@ -90,7 +90,7 @@ class FundingDetailsSchema(Schema): funding_partner = fields.String(allow_none=True) funding_source = fields.String(allow_none=True) id = fields.Integer(required=True) - method_of_transfer = fields.String(allow_none=True) + method_of_transfer = fields.Enum(CANMethodOfTransfer, allow_none=True) sub_allowance = fields.String(allow_none=True) created_on = fields.DateTime(format="%Y-%m-%dT%H:%M:%S.%fZ", allow_none=True) updated_on = fields.DateTime(format="%Y-%m-%dT%H:%M:%S.%fZ", allow_none=True) From 20cdd3161669940c0befbf4f936509a4b68aebd1 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:12:14 -0500 Subject: [PATCH 07/41] test: starts tests for CANs list page not working but close --- .../src/pages/cans/list/CansList.test.jsx | 28 + frontend/src/tests/data.js | 545 ++++++++++++++++++ frontend/src/tests/mocks.js | 6 +- 3 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/cans/list/CansList.test.jsx diff --git a/frontend/src/pages/cans/list/CansList.test.jsx b/frontend/src/pages/cans/list/CansList.test.jsx new file mode 100644 index 0000000000..285dcb9e21 --- /dev/null +++ b/frontend/src/pages/cans/list/CansList.test.jsx @@ -0,0 +1,28 @@ +import { server } from "../../../tests/mocks"; +import { waitFor, screen } from "@testing-library/react"; +// import { useGetAgreementsQuery } from "./opsAPI"; +import { renderWithProviders } from "../../../test-utils"; +import CanList from "./CanList"; +import { Router } from "react-router-dom"; +import { Provider } from "react-redux"; +import store from "../../../store"; + +server.listen(); +// TODO: Fix this test +describe.skip("opsApi", () => { + test("should GET /cans using mocks", async () => { + const { container } = renderWithProviders(); + screen.debug(); + await waitFor(() => { + expect(container).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getByText("G99HRF2")).toBeInTheDocument(); + }); + }); +}); + +function TestComponent() { + return ; +} diff --git a/frontend/src/tests/data.js b/frontend/src/tests/data.js index 741f3a4ceb..545cb1f3cd 100644 --- a/frontend/src/tests/data.js +++ b/frontend/src/tests/data.js @@ -892,3 +892,548 @@ export const budgetLineWithStatusChangeRequestToExecuting = { ], updated_on: "2024-07-26T14:07:14.544417" }; + +export const cans = [ + { + active_period: 1, + budget_line_items: [ + { + agreement_id: 2, + amount: 2000000, + can: { + active_period: 1, + description: "Healthy Marriages Responsible Fatherhood - OPRE", + display_name: "G99HRF2", + id: 500, + nick_name: "HMRF-OPRE", + number: "G99HRF2", + portfolio_id: 6 + }, + can_id: 500, + change_requests_in_review: null, + comments: "", + created_by: 503, + created_on: "2024-09-17T18:12:32.976627", + date_needed: "2043-06-13", + fiscal_year: 2043, + id: 15008, + in_review: false, + line_description: "Line Item 2", + portfolio_id: 6, + proc_shop_fee_percentage: 0.005, + services_component_id: null, + status: "IN_EXECUTION", + team_members: [ + { + email: "chris.fortunato@example.com", + full_name: "Chris Fortunato", + id: 500 + }, + { + email: "Amelia.Popham@example.com", + full_name: "Amelia Popham", + id: 503 + }, + { + email: "admin.demo@email.com", + full_name: "Admin Demo", + id: 520 + } + ], + updated_on: "2024-09-17T18:12:32.976627" + } + ], + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.558006Z", + description: "Healthy Marriages Responsible Fatherhood - OPRE", + display_name: "G99HRF2", + funding_budgets: [ + { + budget: 1140000, + can: { + active_period: 1, + description: "Healthy Marriages Responsible Fatherhood - OPRE", + display_name: "G99HRF2", + id: 500, + nick_name: "HMRF-OPRE", + number: "G99HRF2", + portfolio_id: 6, + projects: [] + }, + can_id: 500, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.781382Z", + display_name: "CANFundingBudget#1", + fiscal_year: 2023, + id: 1, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.781382Z", + versions: [ + { + budget: 1140000, + can: { + description: "Healthy Marriages Responsible Fatherhood - OPRE", + id: 500, + nick_name: "HMRF-OPRE", + number: "G99HRF2", + portfolio_id: 6, + projects: [] + }, + can_id: 500, + created_by: null, + created_on: "2024-09-17T18:12:25.781382Z", + end_transaction_id: null, + fiscal_year: 2023, + id: 1, + notes: null, + operation_type: 0, + transaction_id: 186, + updated_by: null, + updated_on: "2024-09-17T18:12:25.781382Z" + } + ] + } + ], + funding_details: { + allotment: null, + allowance: null, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.370020Z", + display_name: "CANFundingDetails#1", + fiscal_year: 2023, + fund_code: "AAXXXX20231DAD", + funding_partner: null, + funding_source: "CANFundingSource.OPRE", + id: 1, + method_of_transfer: "DIRECT", + sub_allowance: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.370020Z" + }, + funding_details_id: 1, + funding_received: [ + { + can: { + active_period: 1, + description: "Healthy Marriages Responsible Fatherhood - OPRE", + display_name: "G99HRF2", + id: 500, + nick_name: "HMRF-OPRE", + number: "G99HRF2", + portfolio_id: 6, + projects: [] + }, + can_id: 500, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:26.088324Z", + display_name: "CANFundingReceived#500", + fiscal_year: 2023, + funding: 880000, + id: 500, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:26.088324Z" + } + ], + id: 500, + nick_name: "HMRF-OPRE", + number: "G99HRF2", + portfolio: { + abbreviation: "HMRF", + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:16.659182Z", + division_id: 5, + id: 6, + name: "Healthy Marriage & Responsible Fatherhood", + status: "IN_PROCESS", + team_leaders: [ + { + full_name: "Katie Pahigiannis", + id: 505 + } + ], + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:16.659182Z", + urls: [ + { + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:16.802256Z", + id: 6, + portfolio_id: 6, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:16.802256Z", + url: "https://www.acf.hhs.gov/opre/topic/strengthening-families-healthy-marriage-responsible-fatherhood" + } + ] + }, + portfolio_id: 6, + projects: [], + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.558006Z" + }, + { + active_period: 5, + budget_line_items: [ + { + agreement_id: 2, + amount: 2000000, + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1 + }, + can_id: 501, + change_requests_in_review: null, + comments: "", + created_by: 503, + created_on: "2024-09-17T18:12:33.000154", + date_needed: "2043-06-13", + fiscal_year: 2043, + id: 15010, + in_review: false, + line_description: "Line Item 2", + portfolio_id: 1, + proc_shop_fee_percentage: 0.005, + services_component_id: null, + status: "IN_EXECUTION", + team_members: [ + { + email: "chris.fortunato@example.com", + full_name: "Chris Fortunato", + id: 500 + }, + { + email: "Amelia.Popham@example.com", + full_name: "Amelia Popham", + id: 503 + }, + { + email: "admin.demo@email.com", + full_name: "Admin Demo", + id: 520 + } + ], + updated_on: "2024-09-17T18:12:33.000154" + }, + { + agreement_id: 2, + amount: 1000000, + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1 + }, + can_id: 501, + change_requests_in_review: null, + comments: "", + created_by: 503, + created_on: "2024-09-17T18:12:33.060480", + date_needed: "2043-06-13", + fiscal_year: 2043, + id: 15015, + in_review: false, + line_description: "Line Item 2", + portfolio_id: 1, + proc_shop_fee_percentage: 0.005, + services_component_id: null, + status: "PLANNED", + team_members: [ + { + email: "chris.fortunato@example.com", + full_name: "Chris Fortunato", + id: 500 + }, + { + email: "Amelia.Popham@example.com", + full_name: "Amelia Popham", + id: 503 + }, + { + email: "admin.demo@email.com", + full_name: "Admin Demo", + id: 520 + } + ], + updated_on: "2024-09-17T18:12:33.060480" + }, + { + agreement_id: 2, + amount: 3000000, + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1 + }, + can_id: 501, + change_requests_in_review: null, + comments: "", + created_by: 503, + created_on: "2024-09-17T18:12:33.073854", + date_needed: "2043-06-13", + fiscal_year: 2043, + id: 15016, + in_review: false, + line_description: "Line Item 2", + portfolio_id: 1, + proc_shop_fee_percentage: 0.005, + services_component_id: null, + status: "OBLIGATED", + team_members: [ + { + email: "chris.fortunato@example.com", + full_name: "Chris Fortunato", + id: 500 + }, + { + email: "Amelia.Popham@example.com", + full_name: "Amelia Popham", + id: 503 + }, + { + email: "admin.demo@email.com", + full_name: "Admin Demo", + id: 520 + } + ], + updated_on: "2024-09-17T18:12:33.073854" + } + ], + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.579527Z", + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + funding_budgets: [ + { + budget: 200000, + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.799918Z", + display_name: "CANFundingBudget#2", + fiscal_year: 2021, + id: 2, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.799918Z", + versions: [ + { + budget: 200000, + can: { + description: "Incoming Interagency Agreements", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_on: "2024-09-17T18:12:25.799918Z", + end_transaction_id: null, + fiscal_year: 2021, + id: 2, + notes: null, + operation_type: 0, + transaction_id: 187, + updated_by: null, + updated_on: "2024-09-17T18:12:25.799918Z" + } + ] + }, + { + budget: 10000000, + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.903789Z", + display_name: "CANFundingBudget#13", + fiscal_year: 2023, + id: 13, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.903789Z", + versions: [ + { + budget: 10000000, + can: { + description: "Incoming Interagency Agreements", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_on: "2024-09-17T18:12:25.903789Z", + end_transaction_id: null, + fiscal_year: 2023, + id: 13, + notes: null, + operation_type: 0, + transaction_id: 198, + updated_by: null, + updated_on: "2024-09-17T18:12:25.903789Z" + } + ] + } + ], + funding_details: { + allotment: null, + allowance: null, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:25.394257Z", + display_name: "CANFundingDetails#2", + fiscal_year: 2021, + fund_code: "BBXXXX20215DAD", + funding_partner: null, + funding_source: null, + id: 2, + method_of_transfer: "COST_SHARE", + sub_allowance: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.394257Z" + }, + funding_details_id: 2, + funding_received: [ + { + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:26.106152Z", + display_name: "CANFundingReceived#501", + fiscal_year: 2021, + funding: 200000, + id: 501, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:26.106152Z" + }, + { + can: { + active_period: 5, + description: "Incoming Interagency Agreements", + display_name: "G99IA14", + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio_id: 1, + projects: [] + }, + can_id: 501, + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:26.216160Z", + display_name: "CANFundingReceived#512", + fiscal_year: 2023, + funding: 6000000, + id: 512, + notes: null, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:26.216160Z" + } + ], + id: 501, + nick_name: "IAA-Incoming", + number: "G99IA14", + portfolio: { + abbreviation: "CWR", + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:16.472498Z", + division_id: 4, + id: 1, + name: "Child Welfare Research", + status: "IN_PROCESS", + team_leaders: [ + { + full_name: "Chris Fortunato", + id: 500 + } + ], + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:16.472498Z", + urls: [ + { + created_by: null, + created_by_user: null, + created_on: "2024-09-17T18:12:16.760426Z", + id: 1, + portfolio_id: 1, + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:16.760426Z", + url: "https://www.acf.hhs.gov/opre/topic/overview/abuse-neglect-adoption-foster-care" + } + ] + }, + portfolio_id: 1, + projects: [], + updated_by: null, + updated_by_user: null, + updated_on: "2024-09-17T18:12:25.579527Z" + } +]; diff --git a/frontend/src/tests/mocks.js b/frontend/src/tests/mocks.js index 1481f93719..4f9a740970 100644 --- a/frontend/src/tests/mocks.js +++ b/frontend/src/tests/mocks.js @@ -1,6 +1,6 @@ import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; -import { changeRequests, divisions, roles } from "./data"; +import { changeRequests, divisions, roles, cans } from "./data"; export const handlers = [ http.get(`https://localhost:8000/api/v1/agreements/`, () => { @@ -49,6 +49,10 @@ export const handlers = [ const { body } = req; return res(ctx.status(200), ctx.json({ id, ...body }), ctx.delay(150)); + }), + + http.get("https://localhost:8000/api/v1/cans/", () => { + return HttpResponse.json(cans); }) ]; From 3f816a7afbd5aa568f3c02fdbfde52bff1308b65 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:12:46 -0500 Subject: [PATCH 08/41] feat: adds method_of_transfer helper --- frontend/src/helpers/utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/helpers/utils.js b/frontend/src/helpers/utils.js index 2794807498..f90bf97351 100644 --- a/frontend/src/helpers/utils.js +++ b/frontend/src/helpers/utils.js @@ -176,12 +176,18 @@ export const codesToDisplayText = { can_id: "CAN", date_needed: "Obligate By Date", status: "Status" + }, + methodOfTransfer: { + COST_SHARE: "Cost Share", + DIRECT: "Direct", + IAA: "IAA", + IDDA: "IDDA" } }; /** * Converts a code value into a display text value based on a predefined mapping. - * @param {("agreementType" | "agreementReason" | "budgetLineStatus" | "validation" | "classNameLabels" | "baseClassNameLabels"| "agreementPropertyLabels" | "budgetLineItemPropertyLabels" | "changeToTypes")} 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")} 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. From 8544d811b795df73179a66e2f9d6cff35ec9d1b4 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:13:21 -0500 Subject: [PATCH 09/41] refactor: replaces with RTK Query --- frontend/src/pages/cans/list/CanList.jsx | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index 90826c692c..f62c89d30c 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -1,22 +1,34 @@ -import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; +// import { useEffect } from "react"; +// import { useDispatch, useSelector } from "react-redux"; import App from "../../../App"; import CANTable from "../../../components/CANs/CANTable"; import DebugCode from "../../../components/DebugCode"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; import CANTags from "./CanTabs"; -import { getCanList } from "./getCanList"; +// import { getCanList } from "./getCanList"; import { useSearchParams } from "react-router-dom"; +import { useGetCansQuery } from "../../../api/opsAPI"; +import ErrorPage from "../../ErrorPage"; const CanList = () => { - const dispatch = useDispatch(); - const canList = useSelector((state) => state.canList.cans); + // const dispatch = useDispatch(); + // const canList = useSelector((state) => state.canList.cans); const [searchParams] = useSearchParams(); const myCANsUrl = searchParams.get("filter") === "my-cans"; - useEffect(() => { - dispatch(getCanList()); - }, [dispatch]); - + // useEffect(() => { + // dispatch(getCanList()); + // }, [dispatch]); + const { data: canList, isError, isLoading } = useGetCansQuery(); + if (isLoading) { + return ( + +

Loading...

+
+ ); + } + if (isError) { + return ; + } // TODO: remove flag once CANS are ready return ( import.meta.env.DEV && ( From 0cd517686ba860831260148f81503b34c2c63d93 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:14:11 -0500 Subject: [PATCH 10/41] feat: adds available funds to table row --- .../src/components/CANs/CANTable/CANTable.jsx | 1 + .../components/CANs/CANTable/CANTableRow.jsx | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index 75fc765726..86c0675e06 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -8,6 +8,7 @@ const CANTable = ({ cans }) => { {cans.map((can) => ( { +const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, canId }) => { + const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; return ( - {can} + + {can} + {portfolio} {FY} {activePeriod > 1 ? `${activePeriod} years` : `${activePeriod} year`} {obligateBy} - {transfer} + {convertCodeForDisplay("methodOfTransfer", transfer)} value} /> - {availableFunds} + + value} + /> + ); }; From 9ab99871e651145588368f10a26879e923de8da6 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:27:17 -0500 Subject: [PATCH 11/41] chore: remove unneeded modules --- frontend/src/pages/cans/list/CanList.jsx | 14 ++------ .../src/pages/cans/list/CansList.test.jsx | 8 ++--- frontend/src/pages/cans/list/canListSlice.js | 19 ----------- frontend/src/pages/cans/list/getCanList.js | 12 ------- .../src/pages/cans/list/getCanList.test.js | 34 ------------------- frontend/src/store.js | 2 -- 6 files changed, 5 insertions(+), 84 deletions(-) delete mode 100644 frontend/src/pages/cans/list/canListSlice.js delete mode 100644 frontend/src/pages/cans/list/getCanList.js delete mode 100644 frontend/src/pages/cans/list/getCanList.test.js diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index f62c89d30c..b647ab798c 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -1,23 +1,15 @@ -// import { useEffect } from "react"; -// import { useDispatch, useSelector } from "react-redux"; +import { useSearchParams } from "react-router-dom"; +import { useGetCansQuery } from "../../../api/opsAPI"; import App from "../../../App"; import CANTable from "../../../components/CANs/CANTable"; import DebugCode from "../../../components/DebugCode"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; -import CANTags from "./CanTabs"; -// import { getCanList } from "./getCanList"; -import { useSearchParams } from "react-router-dom"; -import { useGetCansQuery } from "../../../api/opsAPI"; import ErrorPage from "../../ErrorPage"; +import CANTags from "./CanTabs"; const CanList = () => { - // const dispatch = useDispatch(); - // const canList = useSelector((state) => state.canList.cans); const [searchParams] = useSearchParams(); const myCANsUrl = searchParams.get("filter") === "my-cans"; - // useEffect(() => { - // dispatch(getCanList()); - // }, [dispatch]); const { data: canList, isError, isLoading } = useGetCansQuery(); if (isLoading) { return ( diff --git a/frontend/src/pages/cans/list/CansList.test.jsx b/frontend/src/pages/cans/list/CansList.test.jsx index 285dcb9e21..18dc2a295a 100644 --- a/frontend/src/pages/cans/list/CansList.test.jsx +++ b/frontend/src/pages/cans/list/CansList.test.jsx @@ -1,11 +1,7 @@ -import { server } from "../../../tests/mocks"; -import { waitFor, screen } from "@testing-library/react"; -// import { useGetAgreementsQuery } from "./opsAPI"; +import { screen, waitFor } from "@testing-library/react"; import { renderWithProviders } from "../../../test-utils"; +import { server } from "../../../tests/mocks"; import CanList from "./CanList"; -import { Router } from "react-router-dom"; -import { Provider } from "react-redux"; -import store from "../../../store"; server.listen(); // TODO: Fix this test diff --git a/frontend/src/pages/cans/list/canListSlice.js b/frontend/src/pages/cans/list/canListSlice.js deleted file mode 100644 index 2692848b76..0000000000 --- a/frontend/src/pages/cans/list/canListSlice.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; - -const initialState = { - cans: [] -}; - -const canListSlice = createSlice({ - name: "canList", - initialState, - reducers: { - setCanList: (state, action) => { - state.cans = action.payload; - } - } -}); - -export const { setCanList } = canListSlice.actions; - -export default canListSlice.reducer; diff --git a/frontend/src/pages/cans/list/getCanList.js b/frontend/src/pages/cans/list/getCanList.js deleted file mode 100644 index 79b9842ed8..0000000000 --- a/frontend/src/pages/cans/list/getCanList.js +++ /dev/null @@ -1,12 +0,0 @@ -import { setCanList } from "./canListSlice"; -import ApplicationContext from "../../../applicationContext/ApplicationContext"; - -// TODO: Replace with RTK Query - -export const getCanList = () => { - return async (dispatch) => { - const api_version = ApplicationContext.get().helpers().backEndConfig.apiVersion; - const responseData = await ApplicationContext.get().helpers().callBackend(`/api/${api_version}/cans/`, "get"); - dispatch(setCanList(responseData)); - }; -}; diff --git a/frontend/src/pages/cans/list/getCanList.test.js b/frontend/src/pages/cans/list/getCanList.test.js deleted file mode 100644 index 9dac35da3a..0000000000 --- a/frontend/src/pages/cans/list/getCanList.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { getCanList } from "./getCanList"; -import store from "../../../store"; -import TestApplicationContext from "../../../applicationContext/TestApplicationContext"; -import { dispatchUsecase } from "../../../helpers/test"; - -test("successfully gets the CAN list from the backend and directly puts it into state", async () => { - const mockBackendResponse = [ - { - id: 1, - number: "G99HRF2", - otherStuff: "Moof" - }, - { - id: 2, - number: "G99IA14", - otherStuff: "DogCow" - }, - { - id: 3, - number: "G99PHS9", - otherStuff: "Clarus" - } - ]; - TestApplicationContext.helpers().callBackend.mockImplementation(async () => { - return mockBackendResponse; - }); - - const actualGetCanList = getCanList(); - - await dispatchUsecase(actualGetCanList); - - const canList = store.getState().canList.cans; - expect(canList).toEqual(mockBackendResponse); -}); diff --git a/frontend/src/store.js b/frontend/src/store.js index 046cba4fad..b9ec9b73ed 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -1,5 +1,4 @@ import { combineReducers, configureStore } from "@reduxjs/toolkit"; -import canListSlice from "./pages/cans/list/canListSlice"; import canDetailSlice from "./pages/cans/detail/canDetailSlice"; import portfolioListSlice from "./pages/portfolios/list/portfolioListSlice"; import portfolioBudgetSummarySlice from "./components/Portfolios/PortfolioBudgetSummary/portfolioBudgetSummarySlice"; @@ -17,7 +16,6 @@ import { opsAuthApi } from "./api/opsAuthAPI.js"; const rootReducer = combineReducers({ [opsApi.reducerPath]: opsApi.reducer, [opsAuthApi.reducerPath]: opsAuthApi.reducer, - canList: canListSlice, canDetail: canDetailSlice, portfolioList: portfolioListSlice, portfolioBudgetSummary: portfolioBudgetSummarySlice, From 281689472582f18be588e5ed28be8e23c1522392 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:33:14 -0500 Subject: [PATCH 12/41] style: fix links styles --- frontend/src/components/CANs/CANTable/CANTableRow.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index 04fab1f3b0..a03ce6ee89 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -1,15 +1,20 @@ import CurrencyFormat from "react-currency-format"; -import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; +import { Link } from "react-router-dom"; import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; +import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; import { convertCodeForDisplay } from "../../../helpers/utils"; -import { Link } from "react-router-dom"; const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, canId }) => { const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; return ( - {can} + + {can} + {portfolio} {FY} From ebfe4888ed3f35818323a776347b5d43643dd93f Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Tue, 17 Sep 2024 17:33:28 -0500 Subject: [PATCH 13/41] chore: comment debug code --- frontend/src/pages/cans/list/CanList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index b647ab798c..d815a5e082 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -36,7 +36,7 @@ const CanList = () => { TabsSection={} TableSection={} /> - + {/* */} ) ); From da032eed5afa0a9821c26069a46522a0a875c18d Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 18 Sep 2024 16:06:29 -0500 Subject: [PATCH 14/41] docs: adds some type files --- .../BudgetLineItems/BudgetLineTypes.ts | 25 +++++ .../src/components/CANs/CANTable/CANTable.jsx | 16 +++ .../components/CANs/CANTable/CANTableRow.jsx | 27 +++++ frontend/src/components/CANs/CANTypes.ts | 101 ++++++++++++++++++ ...quest.hooks.js => ChangeRequests.hooks.js} | 0 .../ChangeRequestsList/ChangeRequestsList.jsx | 2 +- ...geRequests.d.ts => ChangeRequestsTypes.ts} | 0 .../ReviewChangeRequestAccordion.jsx | 2 +- .../components/Portfolios/PortfolioTypes.ts | 18 ++++ frontend/src/components/Users/UserTypes.ts | 5 + frontend/src/hooks/useChangeRequests.hooks.js | 4 +- .../approve/ApproveAgreement.hooks.js | 2 +- frontend/src/pages/cans/list/CanList.jsx | 2 +- 13 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/BudgetLineItems/BudgetLineTypes.ts create mode 100644 frontend/src/components/CANs/CANTypes.ts rename frontend/src/components/ChangeRequests/{ChangeRequest.hooks.js => ChangeRequests.hooks.js} (100%) rename frontend/src/components/ChangeRequests/{ChangeRequestsList/ChangeRequests.d.ts => ChangeRequestsTypes.ts} (100%) create mode 100644 frontend/src/components/Portfolios/PortfolioTypes.ts create mode 100644 frontend/src/components/Users/UserTypes.ts diff --git a/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts b/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts new file mode 100644 index 0000000000..e6d96c0fa5 --- /dev/null +++ b/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts @@ -0,0 +1,25 @@ +import { SimpleCAN } from "../CANs/CANTypes"; +import { ChangeRequest } from "../ChangeRequests/ChangeRequests"; +import { SafeUser } from "../Users/UserTypes"; + +export type BudgetLine = { + agreement_id: number; + amount: number; + can: SimpleCAN; + can_id: number; + change_requests_in_review: ChangeRequest[]; + comments: string; + created_by: number; + created_on: Date; + date_needed: Date; + fiscal_year: number; + id: number; + in_review: boolean; + line_description: string; + portfolio_id: number; + proc_shop_fee_percentage: number; + services_component_id: number | null; + status: string; + team_members: SafeUser[]; + updated_on: Date; +}; diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index 86c0675e06..7c7bb4096a 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -1,8 +1,20 @@ +import PropTypes from "prop-types"; import Table from "../../UI/Table"; import { CAN_TABLE_HEADINGS } from "./CANTable.constants"; import CANTableRow from "./CANTableRow"; +/** + * CANTable component of CanList + * @component + * @typedef {import("../../CANs/CANTypes").CAN} CAN + * @param {Object} props + * @param {CAN[]} props.cans - Array of CANs + * @returns {JSX.Element} + */ const CANTable = ({ cans }) => { + if (cans.length === 0) { + return

No CANs found

; + } return ( {cans.map((can) => ( @@ -22,4 +34,8 @@ const CANTable = ({ cans }) => { ); }; +CANTable.propTypes = { + cans: PropTypes.array.isRequired +}; + export default CANTable; diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index a03ce6ee89..abc5a8023c 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -1,11 +1,27 @@ +import PropTypes from "prop-types"; import CurrencyFormat from "react-currency-format"; import { Link } from "react-router-dom"; import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; import { getDecimalScale } from "../../../helpers/currencyFormat.helpers"; import { convertCodeForDisplay } from "../../../helpers/utils"; +/** + * CanTableRow component of CANTable + * @component + * @param {Object} props + * @param {string} props.can - CAN name + * @param {string} props.portfolio - Portfolio abbreviation + * @param {number} props.FY - Fiscal Year + * @param {number} props.activePeriod - Active Period + * @param {string} props.obligateBy - Obligate By + * @param {string} props.transfer - Method of Transfer + * @param {number} props.fyBudget - Fiscal Year Budget + * @param {number} props.canId - CAN ID + * @returns {JSX.Element} + */ const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, canId }) => { const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; + return ( diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index b647ab798c..f14f2484d0 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -10,7 +10,7 @@ import CANTags from "./CanTabs"; const CanList = () => { const [searchParams] = useSearchParams(); const myCANsUrl = searchParams.get("filter") === "my-cans"; - const { data: canList, isError, isLoading } = useGetCansQuery(); + const { data: canList, isError, isLoading } = useGetCansQuery({}); if (isLoading) { return ( From eabf3cecd40baa9338eecf1425aa6ee5fa7f3ce1 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 14:13:31 -0500 Subject: [PATCH 22/41] feat: adds Tooltip --- .../src/components/CANs/CANTable/CANTable.jsx | 3 ++- .../components/CANs/CANTable/CANTableRow.jsx | 24 ++++++++++++------- frontend/src/pages/cans/list/CanList.jsx | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index 7c7bb4096a..706677f37b 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -21,7 +21,8 @@ const CANTable = ({ cans }) => { { +const CANTableRow = ({ name, nickname, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, canId }) => { const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; return ( @@ -62,7 +69,8 @@ const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, f }; CANTableRow.propTypes = { - can: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + nickname: PropTypes.string.isRequired, portfolio: PropTypes.string.isRequired, FY: PropTypes.string.isRequired, activePeriod: PropTypes.number.isRequired, diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index f14f2484d0..aa63a36542 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -30,7 +30,7 @@ const CanList = () => { subtitle={myCANsUrl ? "My CANs" : "All CANs"} details={ myCANsUrl - ? "This is a list of the CANs you are listed as a Team Member on within the selected Fiscal Year. Please select filter options to see CANs by Portfolio, Status, or Fiscal Year." + ? "This is a list of CANs from agreements you are listed as a team member on. Please select filter options to see CANs by Portfolio, Fiscal Year, or other criteria." : "This is a list of all CANs across OPRE that are or were active within the selected Fiscal Year." } TabsSection={} From 98f964f246f5ef184fe8224f72f7c745c5981ef0 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 15:38:15 -0500 Subject: [PATCH 23/41] chore: add nickName to test data --- backend/data_tools/data/can_data.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/data_tools/data/can_data.json5 b/backend/data_tools/data/can_data.json5 index 80e91688ce..151852a73a 100644 --- a/backend/data_tools/data/can_data.json5 +++ b/backend/data_tools/data/can_data.json5 @@ -205,7 +205,7 @@ // 512 number: "G99XXX8", description: "Example CAN", - nick_name: "", + nick_name: "Next Generation Leadership Program", portfolio_id: 3, funding_details_id: 13, projects: [ From bc41c3463790e6a774ca1c3cb5769a3dfe624c25 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 15:38:31 -0500 Subject: [PATCH 24/41] chore: remove unneeded constant --- .../components/CANs/CANTable/CANTable.constants.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTable.constants.js b/frontend/src/components/CANs/CANTable/CANTable.constants.js index 6e066038b9..1a296e6165 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.constants.js +++ b/frontend/src/components/CANs/CANTable/CANTable.constants.js @@ -1,12 +1 @@ -export const CAN_TABLE_HEADINGS = [ - "CAN", - "Portfolio", - "FY", - "Active Period", - "Obligate By", - "Transfer", - "FY Budget", - "$ Available" -]; - export const CANS_PER_PAGE = 25; From e88603cd7d147c0285b20063b4bb148528dff45b Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 15:38:58 -0500 Subject: [PATCH 25/41] refactor: better prop naming --- .../components/CANs/CANTable/CANTableRow.jsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index 7405b241be..d7d0ad13ca 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -11,17 +11,27 @@ import Tooltip from "../../UI/USWDS/Tooltip"; * @component * @param {Object} props * @param {string} props.name - CAN name - * @param {string} props.nickname - Portfolio nickname + * @param {string} props.nickname - CAN nickname * @param {string} props.portfolio - Portfolio abbreviation - * @param {number} props.FY - Fiscal Year - * @param {number} props.activePeriod - Active Period - * @param {string} props.obligateBy - Obligate By + * @param {number} props.fiscalYear - Fiscal Year + * @param {number} props.activePeriod - Active Period in years + * @param {string} props.obligateBy - Obligate By Date * @param {string} props.transfer - Method of Transfer * @param {number} props.fyBudget - Fiscal Year Budget * @param {number} props.canId - CAN ID * @returns {JSX.Element} */ -const CANTableRow = ({ name, nickname, portfolio, FY, activePeriod, obligateBy, transfer, fyBudget, canId }) => { +const CANTableRow = ({ + name, + nickname, + portfolio, + fiscalYear, + activePeriod, + obligateBy, + transfer, + fyBudget, + canId +}) => { const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; return ( @@ -40,7 +50,7 @@ const CANTableRow = ({ name, nickname, portfolio, FY, activePeriod, obligateBy, - + @@ -72,7 +82,7 @@ CANTableRow.propTypes = { name: PropTypes.string.isRequired, nickname: PropTypes.string.isRequired, portfolio: PropTypes.string.isRequired, - FY: PropTypes.string.isRequired, + fiscalYear: PropTypes.number.isRequired, activePeriod: PropTypes.number.isRequired, obligateBy: PropTypes.string.isRequired, transfer: PropTypes.string.isRequired, From d722d28cfe0768269b8844c2a273ee7942a5726c Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 15:39:21 -0500 Subject: [PATCH 26/41] chore: remove Debug code --- frontend/src/pages/cans/list/CanList.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index aa63a36542..c94bc232d8 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -2,7 +2,6 @@ import { useSearchParams } from "react-router-dom"; import { useGetCansQuery } from "../../../api/opsAPI"; import App from "../../../App"; import CANTable from "../../../components/CANs/CANTable"; -import DebugCode from "../../../components/DebugCode"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; import ErrorPage from "../../ErrorPage"; import CANTags from "./CanTabs"; @@ -36,7 +35,6 @@ const CanList = () => { TabsSection={} TableSection={} /> - ) ); From 7e26cc4a928e2e96364d8a9145ea8b3942ec7bb7 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 15:39:59 -0500 Subject: [PATCH 27/41] feat: adds Tooltip to Table Heading --- .../src/components/CANs/CANTable/CANTable.jsx | 102 ++++++++++++++---- .../components/CANs/CANTable/style.module.css | 4 + 2 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/CANs/CANTable/style.module.css diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index 706677f37b..b5b865ce93 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -1,8 +1,7 @@ import PropTypes from "prop-types"; -import Table from "../../UI/Table"; -import { CAN_TABLE_HEADINGS } from "./CANTable.constants"; +import Tooltip from "../../UI/USWDS/Tooltip"; import CANTableRow from "./CANTableRow"; - +import styles from "./style.module.css"; /** * CANTable component of CanList * @component @@ -15,23 +14,88 @@ const CANTable = ({ cans }) => { if (cans.length === 0) { return

No CANs found

; } + + return ( +
@@ -47,4 +63,15 @@ const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, f ); }; +CANTableRow.propTypes = { + can: PropTypes.string.isRequired, + portfolio: PropTypes.string.isRequired, + FY: PropTypes.string.isRequired, + activePeriod: PropTypes.number.isRequired, + obligateBy: PropTypes.string.isRequired, + transfer: PropTypes.string.isRequired, + fyBudget: PropTypes.number.isRequired, + canId: PropTypes.number.isRequired +}; + export default CANTableRow; diff --git a/frontend/src/components/CANs/CANTypes.ts b/frontend/src/components/CANs/CANTypes.ts new file mode 100644 index 0000000000..bb230e393b --- /dev/null +++ b/frontend/src/components/CANs/CANTypes.ts @@ -0,0 +1,101 @@ +import { SafeUser } from "../Users/UserTypes"; +import { BudgetLine } from "../BudgetLineItems/BudgetLineTypes"; +import { Portfolio } from "../Portfolios/PortfolioTypes"; + +export type CAN = { + active_period: number; + budget_line_items: BudgetLine[]; + created_by: number | null; + created_by_user: number | null; + created_on: Date; + description: string; + display_name: string; + funding_budgets: CANFundingBudget[]; + funding_details: CANFundingDetails; + funding_details_id: number; + funding_received: CANFundingReceived[]; + id: number; + nick_name: string; + number: string; + portfolio: Portfolio; + portfolio_id: number; + // TODO: Add projects + projects: []; + updated_by: number | null; + updated_by_user: number | null; + updated_on: Date; +}; + +export type SimpleCAN = { + active_period: number; + description: string; + display_name: string; + id: number; + nick_name: string; + number: string; + portfolio_id: number; + projects: []; +}; + +export type URL = { + created_by: number | null; + created_by_user: number | null; + created_on: Date; + id: number; + portfolio_id: number; + updated_by: number | null; + updated_by_user: number | null; + updated_on: Date; + url: string; +}; + +export type CANFundingBudget = { + budget: number; + can: SimpleCAN; + can_id: number; + created_by: number | null; + created_by_user: number | null; + created_on: Date; + display_name: string; + fiscal_year: number; + id: number; + notes: string | null; + updated_by: number | null; + updated_by_user: number | null; + updated_on: Date; +}; + +export type CANFundingDetails = { + allotment: null; + allowance: null; + created_by: null; + created_by_user: null; + created_on: Date; + display_name: string; + fiscal_year: number; + fund_code: string; + funding_partner: null; + funding_source: string; + id: number; + method_of_transfer: "DIRECT" | "COST_SHARE" | "IDDA" | "IAA"; + sub_allowance: null; + updated_by: null; + updated_by_user: null; + updated_on: Date; +}; + +export type CANFundingReceived = { + can: SimpleCAN; + can_id: number; + created_by: number | null; + created_by_user: number | null; + created_on: Date; + display_name: string; + fiscal_year: number; + funding: number; + id: number; + notes: string | null; + updated_by: number | null; + updated_by_user: number | null; + updated_on: Date; +}; diff --git a/frontend/src/components/ChangeRequests/ChangeRequest.hooks.js b/frontend/src/components/ChangeRequests/ChangeRequests.hooks.js similarity index 100% rename from frontend/src/components/ChangeRequests/ChangeRequest.hooks.js rename to frontend/src/components/ChangeRequests/ChangeRequests.hooks.js diff --git a/frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequestsList.jsx b/frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequestsList.jsx index 81da62c890..457e24449b 100644 --- a/frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequestsList.jsx +++ b/frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequestsList.jsx @@ -25,7 +25,7 @@ function ChangeRequestsList({ handleReviewChangeRequest }) { } /** - * @typedef {import('./ChangeRequests').ChangeRequest} ChangeRequest + * @typedef {import('../ChangeRequestsTypes').ChangeRequest} ChangeRequest * @type {ChangeRequest[]} */ return changeRequests.length > 0 ? ( diff --git a/frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequests.d.ts b/frontend/src/components/ChangeRequests/ChangeRequestsTypes.ts similarity index 100% rename from frontend/src/components/ChangeRequests/ChangeRequestsList/ChangeRequests.d.ts rename to frontend/src/components/ChangeRequests/ChangeRequestsTypes.ts diff --git a/frontend/src/components/ChangeRequests/ReviewChangeRequestAccordion/ReviewChangeRequestAccordion.jsx b/frontend/src/components/ChangeRequests/ReviewChangeRequestAccordion/ReviewChangeRequestAccordion.jsx index 2ec6748125..258576c8ec 100644 --- a/frontend/src/components/ChangeRequests/ReviewChangeRequestAccordion/ReviewChangeRequestAccordion.jsx +++ b/frontend/src/components/ChangeRequests/ReviewChangeRequestAccordion/ReviewChangeRequestAccordion.jsx @@ -7,7 +7,7 @@ import { CHANGE_REQUEST_TYPES } from "../ChangeRequests.constants"; import StatusChangeReviewCard from "../StatusChangeReviewCard"; /** - * @typedef {import('../ChangeRequestsList/ChangeRequests').ChangeRequest} ChangeRequest + * @typedef {import('../ChangeRequestsTypes').ChangeRequest} ChangeRequest * @type {ChangeRequest[]} */ /** diff --git a/frontend/src/components/Portfolios/PortfolioTypes.ts b/frontend/src/components/Portfolios/PortfolioTypes.ts new file mode 100644 index 0000000000..9f78c79cc3 --- /dev/null +++ b/frontend/src/components/Portfolios/PortfolioTypes.ts @@ -0,0 +1,18 @@ +import { URL } from "../CANs/CANTypes"; +import { SafeUser } from "../Users/UserTypes"; + +export type Portfolio = { + abbreviation: string; + created_by: number | null; + created_by_user: number | null; + created_on: Date; + division_id: number; + id: number; + name: string; + status: string; + team_leaders: SafeUser[]; + updated_by: null; + updated_by_user: null; + updated_on: Date; + urls: URL[]; +}; diff --git a/frontend/src/components/Users/UserTypes.ts b/frontend/src/components/Users/UserTypes.ts new file mode 100644 index 0000000000..b0fef34740 --- /dev/null +++ b/frontend/src/components/Users/UserTypes.ts @@ -0,0 +1,5 @@ +export type SafeUser = { + email: string; + full_name: string; + id: number; +}; diff --git a/frontend/src/hooks/useChangeRequests.hooks.js b/frontend/src/hooks/useChangeRequests.hooks.js index d0899b2c5d..8f3bbcd818 100644 --- a/frontend/src/hooks/useChangeRequests.hooks.js +++ b/frontend/src/hooks/useChangeRequests.hooks.js @@ -1,7 +1,7 @@ import { useGetAgreementByIdQuery, useGetCansQuery, useGetChangeRequestsListQuery } from "../api/opsAPI"; import { renderField } from "../helpers/utils"; /** - * @typedef {import ('../components/ChangeRequests/ChangeRequestsList/ChangeRequests.d.ts').ChangeRequest} ChangeRequest + * @typedef {import ('../components/ChangeRequests/ChangeRequests').ChangeRequest} ChangeRequest */ /** @@ -61,7 +61,7 @@ export const useChangeRequestsForBudgetLines = (budgetLines, targetStatus, isBud * Custom hook that returns the change requests for a budget line. * @param {Object} budgetLine - The budget line. * @returns {string} The change requests messages. - + */ export const useChangeRequestsForTooltip = (budgetLine) => { const { data: cans, isSuccess: cansSuccess } = useGetCansQuery(); diff --git a/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js b/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js index 341a2c0b79..e89694c076 100644 --- a/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js +++ b/frontend/src/pages/agreements/approve/ApproveAgreement.hooks.js @@ -20,7 +20,7 @@ import useToggle from "../../../hooks/useToggle"; import { getTotalByCans } from "../review/ReviewAgreement.helpers"; /** - * @typedef {import('../../../components/ChangeRequests/ChangeRequestsList/ChangeRequests').ChangeRequest} ChangeRequest + * @typedef {import('../../../components/ChangeRequests/ChangeRequests').ChangeRequest} ChangeRequest * @type {ChangeRequest[]} */ /** diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index d815a5e082..b647ab798c 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -36,7 +36,7 @@ const CanList = () => { TabsSection={} TableSection={} /> - {/* */} + ) ); From 9b74c2de2092abdb7a6a99191bcf45410a3a97fe Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 18 Sep 2024 16:48:27 -0500 Subject: [PATCH 15/41] docs: adds type file for Projects --- .../BudgetLineItems/BudgetLineTypes.ts | 2 +- frontend/src/components/CANs/CANTypes.ts | 4 ++-- frontend/src/components/Projects/ProjectTypes.ts | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Projects/ProjectTypes.ts diff --git a/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts b/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts index e6d96c0fa5..79e0f5102f 100644 --- a/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts +++ b/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts @@ -1,5 +1,5 @@ import { SimpleCAN } from "../CANs/CANTypes"; -import { ChangeRequest } from "../ChangeRequests/ChangeRequests"; +import { ChangeRequest } from "../ChangeRequests/ChangeRequestsTypes"; import { SafeUser } from "../Users/UserTypes"; export type BudgetLine = { diff --git a/frontend/src/components/CANs/CANTypes.ts b/frontend/src/components/CANs/CANTypes.ts index bb230e393b..a65576b834 100644 --- a/frontend/src/components/CANs/CANTypes.ts +++ b/frontend/src/components/CANs/CANTypes.ts @@ -1,6 +1,7 @@ import { SafeUser } from "../Users/UserTypes"; import { BudgetLine } from "../BudgetLineItems/BudgetLineTypes"; import { Portfolio } from "../Portfolios/PortfolioTypes"; +import { Project } from "../Projects/ProjectTypes";} export type CAN = { active_period: number; @@ -19,8 +20,7 @@ export type CAN = { number: string; portfolio: Portfolio; portfolio_id: number; - // TODO: Add projects - projects: []; + projects: Project[]; updated_by: number | null; updated_by_user: number | null; updated_on: Date; diff --git a/frontend/src/components/Projects/ProjectTypes.ts b/frontend/src/components/Projects/ProjectTypes.ts new file mode 100644 index 0000000000..4b03f87faa --- /dev/null +++ b/frontend/src/components/Projects/ProjectTypes.ts @@ -0,0 +1,16 @@ +import { SafeUser } from "../Users/UserTypes"; + +export type Project = { + created_by: number | null; + created_on: Date; + description: string; + id: number; + methodologies: string[]; + origination_date: Date; + populations: string[]; + short_title: string; + team_leaders: SafeUser[]; + title: string; + updated_on: Date; + url: string; +}; From 6e456cb386915e19e832d3840160d69eed7ac915 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 18 Sep 2024 16:53:17 -0500 Subject: [PATCH 16/41] chore: fix import --- frontend/src/components/CANs/CANTypes.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/CANs/CANTypes.ts b/frontend/src/components/CANs/CANTypes.ts index a65576b834..df39505b0b 100644 --- a/frontend/src/components/CANs/CANTypes.ts +++ b/frontend/src/components/CANs/CANTypes.ts @@ -1,7 +1,6 @@ -import { SafeUser } from "../Users/UserTypes"; import { BudgetLine } from "../BudgetLineItems/BudgetLineTypes"; import { Portfolio } from "../Portfolios/PortfolioTypes"; -import { Project } from "../Projects/ProjectTypes";} +import { Project } from "../Projects/ProjectTypes"; export type CAN = { active_period: number; @@ -34,7 +33,7 @@ export type SimpleCAN = { nick_name: string; number: string; portfolio_id: number; - projects: []; + projects: Project[]; }; export type URL = { From dce566f0e20d902a13e40c9006e8330280e7ab9e Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Wed, 18 Sep 2024 17:16:29 -0500 Subject: [PATCH 17/41] docs: rename to `d.ts` cleanup linting errors --- .../{BudgetLineTypes.ts => BudgetLineTypes.d.ts} | 0 frontend/src/components/CANs/{CANTypes.ts => CANTypes.d.ts} | 0 .../{ChangeRequestsTypes.ts => ChangeRequestsTypes.d.ts} | 0 .../Portfolios/{PortfolioTypes.ts => PortfolioTypes.d.ts} | 0 .../components/Projects/{ProjectTypes.ts => ProjectTypes.d.ts} | 0 frontend/src/components/Users/{UserTypes.ts => UserTypes.d.ts} | 0 frontend/src/pages/cans/list/CansList.test.jsx | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/components/BudgetLineItems/{BudgetLineTypes.ts => BudgetLineTypes.d.ts} (100%) rename frontend/src/components/CANs/{CANTypes.ts => CANTypes.d.ts} (100%) rename frontend/src/components/ChangeRequests/{ChangeRequestsTypes.ts => ChangeRequestsTypes.d.ts} (100%) rename frontend/src/components/Portfolios/{PortfolioTypes.ts => PortfolioTypes.d.ts} (100%) rename frontend/src/components/Projects/{ProjectTypes.ts => ProjectTypes.d.ts} (100%) rename frontend/src/components/Users/{UserTypes.ts => UserTypes.d.ts} (100%) diff --git a/frontend/src/components/BudgetLineItems/BudgetLineTypes.ts b/frontend/src/components/BudgetLineItems/BudgetLineTypes.d.ts similarity index 100% rename from frontend/src/components/BudgetLineItems/BudgetLineTypes.ts rename to frontend/src/components/BudgetLineItems/BudgetLineTypes.d.ts diff --git a/frontend/src/components/CANs/CANTypes.ts b/frontend/src/components/CANs/CANTypes.d.ts similarity index 100% rename from frontend/src/components/CANs/CANTypes.ts rename to frontend/src/components/CANs/CANTypes.d.ts diff --git a/frontend/src/components/ChangeRequests/ChangeRequestsTypes.ts b/frontend/src/components/ChangeRequests/ChangeRequestsTypes.d.ts similarity index 100% rename from frontend/src/components/ChangeRequests/ChangeRequestsTypes.ts rename to frontend/src/components/ChangeRequests/ChangeRequestsTypes.d.ts diff --git a/frontend/src/components/Portfolios/PortfolioTypes.ts b/frontend/src/components/Portfolios/PortfolioTypes.d.ts similarity index 100% rename from frontend/src/components/Portfolios/PortfolioTypes.ts rename to frontend/src/components/Portfolios/PortfolioTypes.d.ts diff --git a/frontend/src/components/Projects/ProjectTypes.ts b/frontend/src/components/Projects/ProjectTypes.d.ts similarity index 100% rename from frontend/src/components/Projects/ProjectTypes.ts rename to frontend/src/components/Projects/ProjectTypes.d.ts diff --git a/frontend/src/components/Users/UserTypes.ts b/frontend/src/components/Users/UserTypes.d.ts similarity index 100% rename from frontend/src/components/Users/UserTypes.ts rename to frontend/src/components/Users/UserTypes.d.ts diff --git a/frontend/src/pages/cans/list/CansList.test.jsx b/frontend/src/pages/cans/list/CansList.test.jsx index 18dc2a295a..b43267a3e7 100644 --- a/frontend/src/pages/cans/list/CansList.test.jsx +++ b/frontend/src/pages/cans/list/CansList.test.jsx @@ -8,7 +8,7 @@ server.listen(); describe.skip("opsApi", () => { test("should GET /cans using mocks", async () => { const { container } = renderWithProviders(); - screen.debug(); + await waitFor(() => { expect(container).toBeInTheDocument(); }); From 9dc6053fd47d8fc35238cccb6fd9db2ef73387be Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 09:24:27 -0500 Subject: [PATCH 18/41] test: fix method_of_transfer --- .../tests/ops/funding_summary/test_can_funding_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py index f3b02cff32..40b1daa935 100644 --- a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py +++ b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py @@ -141,7 +141,7 @@ def test_get_can_funding_summary_with_fiscal_year(loaded_db, test_can) -> None: "funding_partner": None, "funding_source": "OPRE", "id": 1, - "method_of_transfer": None, + "method_of_transfer": "DIRECT", "sub_allowance": None, "updated_by": None, "updated_by_user": None, From dae5463fd09f149662e6068cd9cac1e7dddec876 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 09:38:26 -0500 Subject: [PATCH 19/41] test: fixes method_of_transfer --- .../tests/ops/funding_summary/test_can_funding_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py index 40b1daa935..2ab354bc56 100644 --- a/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py +++ b/backend/ops_api/tests/ops/funding_summary/test_can_funding_summary.py @@ -53,7 +53,7 @@ def test_get_can_funding_summary_no_fiscal_year(loaded_db, test_can) -> None: "funding_partner": None, "funding_source": "OPRE", "id": 1, - "method_of_transfer": None, + "method_of_transfer": "DIRECT", "sub_allowance": None, "updated_by": None, "updated_by_user": None, From 567b1e9cc8ff681679eee10f4b466d150552ea72 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 09:56:11 -0500 Subject: [PATCH 20/41] chore: cleanup import --- frontend/src/components/ChangeRequests/ChangeRequests.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ChangeRequests/ChangeRequests.jsx b/frontend/src/components/ChangeRequests/ChangeRequests.jsx index 1fa2c06f87..3dbe3b4868 100644 --- a/frontend/src/components/ChangeRequests/ChangeRequests.jsx +++ b/frontend/src/components/ChangeRequests/ChangeRequests.jsx @@ -1,5 +1,5 @@ import ConfirmationModal from "../UI/Modals/ConfirmationModal"; -import useChangeRequest from "./ChangeRequest.hooks"; +import useChangeRequest from "./ChangeRequests.hooks"; import ChangeRequestsList from "./ChangeRequestsList"; function ChangeRequests() { From 2fe0009ab96bbf69302982edfc80362f09ab2053 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 09:56:38 -0500 Subject: [PATCH 21/41] chore: cleanup linting issues --- frontend/jsconfig.json | 2 +- frontend/src/components/CANs/CANTable/CANTableRow.jsx | 2 -- frontend/src/pages/cans/list/CanList.jsx | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json index ed3d5d2eaa..c33186023e 100644 --- a/frontend/jsconfig.json +++ b/frontend/jsconfig.json @@ -17,7 +17,7 @@ "skipLibCheck": true, "strict": true, "target": "ESNext", - "types": ["vite/client", "vite-plugin-svgr/client", "vite-plugin-babel-macros/client", "vitest/globals"] + "types": ["vite/client", "vite-plugin-svgr/client", "vite-plugin-babel-macros", "vitest/globals"] }, "display": "Recommended", "include": ["src", "src/setupTests.js"] diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index abc5a8023c..97346b1d4e 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -45,7 +45,6 @@ const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, f prefix={"$"} decimalScale={getDecimalScale(fyBudget)} fixedDecimalScale={true} - renderText={(value) => value} /> @@ -56,7 +55,6 @@ const CANTableRow = ({ can, portfolio, FY, activePeriod, obligateBy, transfer, f prefix={"$"} decimalScale={getDecimalScale(availableFunds)} fixedDecimalScale={true} - renderText={(value) => value} />
- - {can} - + + {name} + + {portfolio} {FY}{portfolio}{FY}{fiscalYear} {activePeriod > 1 ? `${activePeriod} years` : `${activePeriod} year`} {obligateBy} {convertCodeForDisplay("methodOfTransfer", transfer)}
+ + + {cans.map((can) => ( + + ))} + +
+ ); +}; + +const TableHead = () => { + const availbleTooltip = + "$ Available is the remaining amount of the total budget that is available to plan from (Total FY Budget minus budget lines in Planned, Executing or Obligated Status)"; return ( - - {cans.map((can) => ( - - ))} -
+ + + + CAN + + + Portfolio + + + FY + + + Active Period + + + Obligate By + + + Transfer + + + FY Budget + + + + $ Available + + + + ); }; diff --git a/frontend/src/components/CANs/CANTable/style.module.css b/frontend/src/components/CANs/CANTable/style.module.css new file mode 100644 index 0000000000..cfb7dcf40c --- /dev/null +++ b/frontend/src/components/CANs/CANTable/style.module.css @@ -0,0 +1,4 @@ +.tableHover tbody tr:hover td, +.tableHover tbody tr:hover th { + background-color: var(--base-light-variant); +} From 230ddefd40de3530cf1f88f4fa43d281cad820ae Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 16:17:14 -0500 Subject: [PATCH 28/41] refactor: better RTK Query --- .../src/components/CANs/CANTable/CANTableRow.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.jsx index d7d0ad13ca..fc9a2080cc 100644 --- a/frontend/src/components/CANs/CANTable/CANTableRow.jsx +++ b/frontend/src/components/CANs/CANTable/CANTableRow.jsx @@ -32,7 +32,21 @@ const CANTableRow = ({ fyBudget, canId }) => { - const availableFunds = useGetCanFundingSummaryQuery(canId).data?.available_funding ?? 0; + const { data: fundingSummary, isError, isLoading } = useGetCanFundingSummaryQuery(canId); + const availableFunds = fundingSummary?.available_funding ?? 0; + + if (isLoading) + return ( + + Loading... + + ); + if (isError) + return ( + + Error: {isError.valueOf()} + + ); return ( From 790137fe4c473de177edd50bdc67917b5286cbd0 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 16:55:40 -0500 Subject: [PATCH 29/41] docs: use new BudgetLine Types --- .../BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx | 3 ++- .../AllBudgetLinesTable/AllBudgetLinesTable.jsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx index d764c1a4d4..7cfd1e9733 100644 --- a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx +++ b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx @@ -24,8 +24,9 @@ import ChangeIcons from "../ChangeIcons"; /** * BLIRow component that represents a single row in the Budget Lines table. * @component + * @typedef {import("../../BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine * @param {Object} props - The props for the BLIRow component. - * @param {Object} props.budgetLine - The budget line object. + * @param {BudgetLine} props.budgetLine - The budget line object. * @param {boolean} [props.canUserEditBudgetLines] - Whether the user can edit budget lines. * @param {Function} [props.handleSetBudgetLineForEditing] - The function to set the budget line for editing. * @param {Function} [props.handleDeleteBudgetLine] - The function to delete the budget line. diff --git a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBudgetLinesTable.jsx b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBudgetLinesTable.jsx index d78502be8f..2d534a62ed 100644 --- a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBudgetLinesTable.jsx +++ b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBudgetLinesTable.jsx @@ -12,8 +12,9 @@ import ConfirmationModal from "../../UI/Modals/ConfirmationModal"; /** * TableRow component that represents a single row in the budget lines table. * @component + * @typedef {import("../../BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine * @param {Object} props - The props for the TableRow component. - * @param {Object[]} props.budgetLines - The budget line data for the row. + * @param {BudgetLine[]} props.budgetLines - The budget line data for the row. * @returns {JSX.Element} The TableRow component. */ const AllBudgetLinesTable = ({ budgetLines }) => { From 21fb9ae498af92b72cbe39d268d974faea96010b Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 16:56:07 -0500 Subject: [PATCH 30/41] feat: adds Pagination went with 10 per page like the BLI list --- .../src/components/CANs/CANTable/CANTable.jsx | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index b5b865ce93..ab50d40bca 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -1,4 +1,7 @@ +import _ from "lodash"; import PropTypes from "prop-types"; +import React from "react"; +import PaginationNav from "../../UI/PaginationNav"; import Tooltip from "../../UI/USWDS/Tooltip"; import CANTableRow from "./CANTableRow"; import styles from "./style.module.css"; @@ -11,30 +14,45 @@ import styles from "./style.module.css"; * @returns {JSX.Element} */ const CANTable = ({ cans }) => { + const CANS_PER_PAGE = 10; + const [currentPage, setCurrentPage] = React.useState(1); + let cansPerPage = _.cloneDeep(cans); + cansPerPage = cansPerPage.slice((currentPage - 1) * CANS_PER_PAGE, currentPage * CANS_PER_PAGE); + if (cans.length === 0) { return

No CANs found

; } return ( - - - - {cans.map((can) => ( - - ))} - -
+ <> + + + + {cansPerPage.map((can) => ( + + ))} + +
+ {cans.length > 0 && ( + + )} + ); }; From 5de24a73f43b3064c535c72ed5670b29a5dbeeb5 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 16:56:16 -0500 Subject: [PATCH 31/41] docs: better types --- frontend/src/helpers/utils.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/helpers/utils.js b/frontend/src/helpers/utils.js index f90bf97351..08e2757cf1 100644 --- a/frontend/src/helpers/utils.js +++ b/frontend/src/helpers/utils.js @@ -29,7 +29,10 @@ export const calculatePercent = (numerator, denominator) => { return Math.round((numerator / denominator) * 100); }; - +/** + * This function formats a date into a string in the format MM/DD/YYYY. + * @param {Date} date - The date to format. This parameter is required. + */ export const formatDate = (date) => { const options = { timeZone: "UTC" }; @@ -38,8 +41,8 @@ export const formatDate = (date) => { /** * Formats a date string into a date string in the format MM/DD/YYYY. - * @param {string} dateNeeded - The date string to format. This parameter is required. - * @returns {string} The formatted date string. + * @param {Date | string} dateNeeded - The date string to format. This parameter is required. + * @returns {string | undefined} The formatted date string or undefined if input is invalid. */ export const formatDateNeeded = (dateNeeded) => { let formatted_date_needed; From d1c185e540321697c5c70988a8873edd8d349dbe Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 17:11:09 -0500 Subject: [PATCH 32/41] docs: updates types --- .../BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx | 3 --- .../BudgetLineItems/BLIDiffTable/BLIDiffTable.jsx | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx index 7cfd1e9733..e9434cc4a3 100644 --- a/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx +++ b/frontend/src/components/BudgetLineItems/AllBudgetLinesTable/AllBLIRow.jsx @@ -114,7 +114,6 @@ const AllBLIRow = ({ prefix={"$"} decimalScale={getDecimalScale(budgetLineTotalPlusFees)} fixedDecimalScale={true} - renderText={(value) => value} /> value} /> @@ -207,7 +205,6 @@ const AllBLIRow = ({ prefix={"$"} decimalScale={getDecimalScale(feeTotal)} fixedDecimalScale={true} - renderText={(value) => value} /> diff --git a/frontend/src/components/BudgetLineItems/BLIDiffTable/BLIDiffTable.jsx b/frontend/src/components/BudgetLineItems/BLIDiffTable/BLIDiffTable.jsx index 71595b8b7d..87ecd9fc78 100644 --- a/frontend/src/components/BudgetLineItems/BLIDiffTable/BLIDiffTable.jsx +++ b/frontend/src/components/BudgetLineItems/BLIDiffTable/BLIDiffTable.jsx @@ -6,11 +6,12 @@ import "./BLIDiffTable.scss"; /** * A table component that displays budget lines. + * @typedef {import("../../BudgetLineItems/BudgetLineTypes").BudgetLine} BudgetLine * @param {Object} props - The component props. - * @param {Array} [props.budgetLines] - An array of budget lines to display. - optional + * @param {BudgetLine[]} [props.budgetLines=[]] - The budget lines to display. * @param {string} props.changeType - The type of change request. - * @param {string} [props.statusChangeTo=""] - The status change to. - optional - * @returns {JSX.Element} - The rendered table component. + * @param {string} [props.statusChangeTo=""] - The status change to. + * @returns {JSX.Element} The rendered table component. */ const BLIDiffTable = ({ budgetLines = [], changeType, statusChangeTo = "" }) => { const sortedBudgetLines = budgetLines From 3b42de9f30d13c910060fa6429b038fe8645b058 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 17:17:54 -0500 Subject: [PATCH 33/41] refactor: moves CanTabs to Cans dir --- .../{pages/cans/list => components/CANs/CanTabs}/CanTabs.jsx | 0 frontend/src/components/CANs/CanTabs/index.js | 1 + frontend/src/pages/cans/list/CanList.jsx | 2 +- .../src/pages/cans/list/{CansList.test.jsx => CanList.test.jsx} | 0 4 files changed, 2 insertions(+), 1 deletion(-) rename frontend/src/{pages/cans/list => components/CANs/CanTabs}/CanTabs.jsx (100%) create mode 100644 frontend/src/components/CANs/CanTabs/index.js rename frontend/src/pages/cans/list/{CansList.test.jsx => CanList.test.jsx} (100%) diff --git a/frontend/src/pages/cans/list/CanTabs.jsx b/frontend/src/components/CANs/CanTabs/CanTabs.jsx similarity index 100% rename from frontend/src/pages/cans/list/CanTabs.jsx rename to frontend/src/components/CANs/CanTabs/CanTabs.jsx diff --git a/frontend/src/components/CANs/CanTabs/index.js b/frontend/src/components/CANs/CanTabs/index.js new file mode 100644 index 0000000000..ccab092012 --- /dev/null +++ b/frontend/src/components/CANs/CanTabs/index.js @@ -0,0 +1 @@ +export { default } from "./CanTabs"; diff --git a/frontend/src/pages/cans/list/CanList.jsx b/frontend/src/pages/cans/list/CanList.jsx index c94bc232d8..3868d0b34a 100644 --- a/frontend/src/pages/cans/list/CanList.jsx +++ b/frontend/src/pages/cans/list/CanList.jsx @@ -2,9 +2,9 @@ import { useSearchParams } from "react-router-dom"; import { useGetCansQuery } from "../../../api/opsAPI"; import App from "../../../App"; import CANTable from "../../../components/CANs/CANTable"; +import CANTags from "../../../components/CANs/CanTabs"; import TablePageLayout from "../../../components/Layouts/TablePageLayout"; import ErrorPage from "../../ErrorPage"; -import CANTags from "./CanTabs"; const CanList = () => { const [searchParams] = useSearchParams(); diff --git a/frontend/src/pages/cans/list/CansList.test.jsx b/frontend/src/pages/cans/list/CanList.test.jsx similarity index 100% rename from frontend/src/pages/cans/list/CansList.test.jsx rename to frontend/src/pages/cans/list/CanList.test.jsx From e66c29c653d79e1c21dbe520bd413da801980d5d Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 17:40:26 -0500 Subject: [PATCH 34/41] chore: remove unneeded module --- frontend/src/components/CANs/CANTable/CANTable.constants.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/src/components/CANs/CANTable/CANTable.constants.js diff --git a/frontend/src/components/CANs/CANTable/CANTable.constants.js b/frontend/src/components/CANs/CANTable/CANTable.constants.js deleted file mode 100644 index 1a296e6165..0000000000 --- a/frontend/src/components/CANs/CANTable/CANTable.constants.js +++ /dev/null @@ -1 +0,0 @@ -export const CANS_PER_PAGE = 25; From d4e06add2526a27b3876442f32737bef46d1277e Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 17:41:01 -0500 Subject: [PATCH 35/41] test: adds unit-test for CANTable --- .../CANs/CANTable/CANTable.test.jsx | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 frontend/src/components/CANs/CANTable/CANTable.test.jsx diff --git a/frontend/src/components/CANs/CANTable/CANTable.test.jsx b/frontend/src/components/CANs/CANTable/CANTable.test.jsx new file mode 100644 index 0000000000..ce39e3b8fb --- /dev/null +++ b/frontend/src/components/CANs/CANTable/CANTable.test.jsx @@ -0,0 +1,88 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import CANTable from "./CANTable"; +import { cans } from "../../../tests/data"; +import { useGetCansQuery, useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; +import { MemoryRouter } from "react-router-dom"; + +// Mock the PaginationNav component +vi.mock("../../UI/PaginationNav", () => ({ + default: () =>
+})); + +// Mock the Tooltip component +vi.mock("../../UI/USWDS/Tooltip", () => ({ + default: ({ children }) =>
{children}
+})); +vi.mock("../../../api/opsAPI"); + +describe("CANTable", () => { + useGetCansQuery.mockReturnValue({ + data: cans + }); + useGetCanFundingSummaryQuery.mockReturnValue({ + data: { + fundingSummary: { + available_funding: 1000 + } + } + }); + it("renders the table with correct headers", () => { + render( + + + + ); + + expect(screen.getByText("CAN")).toBeInTheDocument(); + expect(screen.getByText("Portfolio")).toBeInTheDocument(); + expect(screen.getByText("FY")).toBeInTheDocument(); + expect(screen.getByText("Active Period")).toBeInTheDocument(); + expect(screen.getByText("Obligate By")).toBeInTheDocument(); + expect(screen.getByText("Transfer")).toBeInTheDocument(); + expect(screen.getByText("FY Budget")).toBeInTheDocument(); + expect(screen.getByText("$ Available")).toBeInTheDocument(); + }); + + it("renders the correct number of rows", () => { + render( + + + + ); + + const rows = screen.getAllByRole("row"); + // +1 for the header row + expect(rows.length).toBe(cans.length + 1); + }); + + it('displays "No CANs found" when cans array is empty', () => { + render( + + + + ); + + expect(screen.getByText("No CANs found")).toBeInTheDocument(); + }); + + it("renders PaginationNav when there are CANs", () => { + render( + + + + ); + + expect(screen.getByTestId("pagination-nav")).toBeInTheDocument(); + }); + + it("does not render PaginationNav when there are no CANs", () => { + render( + + + + ); + + expect(screen.queryByTestId("pagination-nav")).not.toBeInTheDocument(); + }); +}); From 0130308d4f118699079dc3d8923736a47fee7029 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Thu, 19 Sep 2024 17:51:38 -0500 Subject: [PATCH 36/41] test: adds test for CANTableRow --- .../CANs/CANTable/CANTableRow.test.jsx | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 frontend/src/components/CANs/CANTable/CANTableRow.test.jsx diff --git a/frontend/src/components/CANs/CANTable/CANTableRow.test.jsx b/frontend/src/components/CANs/CANTable/CANTableRow.test.jsx new file mode 100644 index 0000000000..7bc3b3a66f --- /dev/null +++ b/frontend/src/components/CANs/CANTable/CANTableRow.test.jsx @@ -0,0 +1,124 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import CANTableRow from "./CANTableRow"; +import { MemoryRouter } from "react-router-dom"; +import { useGetCanFundingSummaryQuery } from "../../../api/opsAPI"; + +// Mock the Tooltip component +vi.mock("../../UI/USWDS/Tooltip", () => ({ + default: ({ children }) =>
{children}
+})); + +// Mock the API hook +vi.mock("../../../api/opsAPI"); + +describe("CANTableRow", () => { + const mockProps = { + name: "Test CAN", + nickname: "Test Nickname", + portfolio: "Test Portfolio", + fiscalYear: 2023, + activePeriod: 2, + obligateBy: "2023-09-30", + transfer: "RWA", + fyBudget: 1_000_000, + canId: 1 + }; + + beforeEach(() => { + useGetCanFundingSummaryQuery.mockReturnValue({ + data: { available_funding: 500000 }, + isLoading: false, + isError: false + }); + }); + + it("renders the row with correct data", () => { + render( + + + + + +
+
+ ); + + expect(screen.getByText("Test CAN")).toBeInTheDocument(); + expect(screen.getByText("Test Portfolio")).toBeInTheDocument(); + expect(screen.getByText("2023")).toBeInTheDocument(); + expect(screen.getByText("2 years")).toBeInTheDocument(); + expect(screen.getByText("2023-09-30")).toBeInTheDocument(); + expect(screen.getByText("$1,000,000.00")).toBeInTheDocument(); + expect(screen.getByText("$500,000.00")).toBeInTheDocument(); + }); + + it("renders 'Loading...' when data is loading", () => { + useGetCanFundingSummaryQuery.mockReturnValue({ + isLoading: true + }); + + render( + + + + + +
+
+ ); + + expect(screen.getByText("Loading...")).toBeInTheDocument(); + }); + + it("renders error message when there's an error", () => { + useGetCanFundingSummaryQuery.mockReturnValue({ + isError: true, + error: { message: "Test error" } + }); + + render( + + + + + +
+
+ ); + + expect(screen.getByText("Error:")).toBeInTheDocument(); + }); + + it("renders correct active period text for single year", () => { + render( + + + + + +
+
+ ); + + expect(screen.getByText("1 year")).toBeInTheDocument(); + }); + + it("renders tooltip with nickname", () => { + render( + + + + + +
+
+ ); + + expect(screen.getByTestId("tooltip")).toBeInTheDocument(); + expect(screen.getByText("Test CAN")).toBeInTheDocument(); + }); +}); From b5c24f8df888d809efb995750f31c39d1f119e76 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 20 Sep 2024 10:20:25 -0500 Subject: [PATCH 37/41] =?UTF-8?q?test:=20=F0=9F=A4=B7=20fix=20da=20test=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/cypress/e2e/loginPage.cy.js | 1 - frontend/cypress/fixtures/initial-state.json | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/frontend/cypress/e2e/loginPage.cy.js b/frontend/cypress/e2e/loginPage.cy.js index a358f2e4a8..7224293618 100644 --- a/frontend/cypress/e2e/loginPage.cy.js +++ b/frontend/cypress/e2e/loginPage.cy.js @@ -6,7 +6,6 @@ it("has expected state on initial load", () => { cy.visit("/login"); cy.fixture("initial-state").then((initState) => { const currentFY = getCurrentFiscalYear(); - initState.canDetail.selectedFiscalYear.value = currentFY; initState.portfolio.selectedFiscalYear.value = currentFY; initState.researchProjectFunding.selectedFiscalYear.value = currentFY; diff --git a/frontend/cypress/fixtures/initial-state.json b/frontend/cypress/fixtures/initial-state.json index ceb1e8a769..e1373fd96e 100644 --- a/frontend/cypress/fixtures/initial-state.json +++ b/frontend/cypress/fixtures/initial-state.json @@ -1,15 +1,4 @@ { - "canList": { - "cans": [] - }, - "canDetail": { - "can": {}, - "canFiscalYearObj": {}, - "pendingFunds": "--", - "selectedFiscalYear": { - "value": null - } - }, "portfolioList": { "portfolios": [] }, From 12a3b677462acb66c9ce587578aab1dc710a0819 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 20 Sep 2024 11:45:28 -0500 Subject: [PATCH 38/41] test: adds pagination test --- frontend/cypress/e2e/canList.cy.js | 54 +++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/frontend/cypress/e2e/canList.cy.js b/frontend/cypress/e2e/canList.cy.js index 2d65d72e1c..0f16e1df20 100644 --- a/frontend/cypress/e2e/canList.cy.js +++ b/frontend/cypress/e2e/canList.cy.js @@ -3,7 +3,7 @@ import { terminalLog, testLogin } from "./utils"; beforeEach(() => { testLogin("admin"); - cy.visit("/cans"); + cy.visit("/cans").wait(1000); }); afterEach(() => { @@ -11,18 +11,48 @@ afterEach(() => { cy.checkA11y(null, null, terminalLog); }); -it("loads", () => { - // beforeEach has ran... - cy.get("h1").should("have.text", "CANs"); - cy.get('a[href="/cans/502"]').should("exist"); -}); +describe("CAN List", () => { + it("loads", () => { + // beforeEach has ran... + cy.get("h1").should("have.text", "CANs"); + cy.get('a[href="/cans/502"]').should("exist"); + }); + + it("clicking on a CAN takes you to the detail page", () => { + // beforeEach has ran... + const canNumber = "G99PHS9"; + + cy.contains(canNumber).click(); + + cy.url().should("include", "/cans/502"); + cy.get("h1").should("contain", canNumber); + }); -it("clicking on a CAN takes you to the detail page", () => { - // beforeEach has ran... - const canNumber = "G99PHS9"; + it("pagination on the bli table works as expected", () => { + cy.get("ul").should("have.class", "usa-pagination__list"); + cy.get("li").should("have.class", "usa-pagination__item").contains("1"); + cy.get("button").should("have.class", "usa-current").contains("1"); + cy.get("li").should("have.class", "usa-pagination__item").contains("2"); + cy.get("li").should("have.class", "usa-pagination__item").contains("Next"); + cy.get("tbody").find("tr").should("have.length", 10); + cy.get("li") + .should("have.class", "usa-pagination__item") + .contains("Previous") + .find("svg") + .should("have.attr", "aria-hidden", "true"); - cy.contains(canNumber).click(); + // go to the second page + cy.get("li").should("have.class", "usa-pagination__item").contains("2").click(); + cy.get("button").should("have.class", "usa-current").contains("2"); + cy.get("li").should("have.class", "usa-pagination__item").contains("Previous"); + cy.get("li") + .should("have.class", "usa-pagination__item") + .contains("Next") + .find("svg") + .should("have.attr", "aria-hidden", "true"); - cy.url().should("include", "/cans/502"); - cy.get("h1").should("contain", canNumber); + // go back to the first page + cy.get("li").should("have.class", "usa-pagination__item").contains("1").click(); + cy.get("button").should("have.class", "usa-current").contains("1"); + }); }); From 490eb4fb7955d871b1cd383eaead33377a5f6fab Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 20 Sep 2024 14:22:31 -0500 Subject: [PATCH 39/41] docs: add docs for TS and JSDocs examples and usage --- docs/developers/frontend/types.md | 117 ++++++++++++++++++ .../src/components/CANs/CANTable/CANTable.jsx | 2 +- .../components/Users/UserInfo/UserInfo.jsx | 7 +- 3 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 docs/developers/frontend/types.md diff --git a/docs/developers/frontend/types.md b/docs/developers/frontend/types.md new file mode 100644 index 0000000000..afe6a4261f --- /dev/null +++ b/docs/developers/frontend/types.md @@ -0,0 +1,117 @@ +# Process for Creating and Using Types + +This document describes the process for creating and using types in OPS based on TypeScript best practices and conventions in a JS Doc format. + +## Creating a Type Definition + +1. **Create a new file co-located with the component directory**. The file should be named after the type it defines that type names should be in PascalCase. For example, a type definition for a user object should be named `UserTypes.d.ts`. + +```markdown +src/components/Users +├── UsersEmailComboBox +├── UserInfo +├── IserInfoForm +└── UserTypes.d.ts +``` + +The `.d.ts` extension is used to indicate that the file contains type definitions and does not contain any executable code. + +2. **Define the type**. The type definition should be exported and named. The type should be defined using the `type` keyword. + +```typescript +export type SafeUser = { + email: string; + full_name: string; + id: number; +}; +``` + + +## Using a Type Definition + +1. **Import the type**. Import the type in the component file where it is used. Since we are using JS Doc, the type should be imported using the `@typedef` tag and imported as a module. + +```jsx +// src/components/Users/UserInfo.jsx + +/** + * Renders the User information. + * @component + * @typedef {import("../UserTypes").SafeUser} User + * @param {Object} props - The component props. + * @param {User} props.user - The user object. + * @param {Boolean} props.isEditable - Whether the user information is editable. + * @returns {JSX.Element} - The rendered component. + */ +const UserInfo = ({ user, isEditable }) => { +... +``` + +2. Another example of using a type definition with a list or array: + +Let's say you have a CANs component ytree like this: + +```markdown +src/components/CANs +├── CANBudgetSummary +├── CANTable +├── CanTypes.d.ts +``` + +The `CanTypes.d.ts` file would look like this: + +```typescript +import { BudgetLine } from "../BudgetLineItems/BudgetLineTypes"; +import { Portfolio } from "../Portfolios/PortfolioTypes"; +import { Project } from "../Projects/ProjectTypes"; + +export type CAN = { + active_period: number; + budget_line_items: BudgetLine[]; + created_by: number | null; + created_by_user: number | null; + created_on: Date; + description: string; + display_name: string; + funding_budgets: CANFundingBudget[]; + funding_details: CANFundingDetails; + funding_details_id: number; + funding_received: CANFundingReceived[]; + id: number; + nick_name: string; + number: string; + portfolio: Portfolio; + portfolio_id: number; + projects: Project[]; + updated_by: number | null; + updated_by_user: number | null; + updated_on: Date; +}; + +... +``` + +And then you would import the `CAN` type in the `CANTable` component like this: + +```jsx +// src/components/CANs/CANTable.jsx + +/** + * CANTable component of CanList + * @component + * @typedef {import("../CANTypes").CAN} CAN + * @param {Object} props + * @param {CAN[]} props.cans - Array of CANs + * @returns {JSX.Element} + */ +const CANTable = ({ cans }) => { + const CANS_PER_PAGE = 10; +... +``` + +## Resources + +- [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) +- [JSDocs TypeDefinition](https://jsdoc.app/tags-typedef) +- [JSDocs Types](https://jsdoc.app/tags-type) +``` diff --git a/frontend/src/components/CANs/CANTable/CANTable.jsx b/frontend/src/components/CANs/CANTable/CANTable.jsx index ab50d40bca..5ac792fffa 100644 --- a/frontend/src/components/CANs/CANTable/CANTable.jsx +++ b/frontend/src/components/CANs/CANTable/CANTable.jsx @@ -8,7 +8,7 @@ import styles from "./style.module.css"; /** * CANTable component of CanList * @component - * @typedef {import("../../CANs/CANTypes").CAN} CAN + * @typedef {import("../CANTypes").CAN} CAN * @param {Object} props * @param {CAN[]} props.cans - Array of CANs * @returns {JSX.Element} diff --git a/frontend/src/components/Users/UserInfo/UserInfo.jsx b/frontend/src/components/Users/UserInfo/UserInfo.jsx index 1f0efad9d7..d79802e8fb 100644 --- a/frontend/src/components/Users/UserInfo/UserInfo.jsx +++ b/frontend/src/components/Users/UserInfo/UserInfo.jsx @@ -10,8 +10,11 @@ import { setIsActive } from "../../UI/Alert/alertSlice.js"; /** * Renders the user information. - * @param {Object} user - The user object. - * @param {Boolean} isEditable - Whether the user information is editable. + * @component + * @typedef {import("../UserTypes").SafeUser} User + * @param {Object} props - The component props. + * @param {User} props.user - The user object. + * @param {Boolean} props.isEditable - Whether the user information is editable. * @returns {JSX.Element} - The rendered component. */ const UserInfo = ({ user, isEditable }) => { From f49a9c54269bf18685be139a993e1fb5d9314731 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Fri, 20 Sep 2024 14:28:36 -0500 Subject: [PATCH 40/41] docs: adds Prime's inspiration for all this --- docs/developers/frontend/types.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/developers/frontend/types.md b/docs/developers/frontend/types.md index afe6a4261f..34925ba505 100644 --- a/docs/developers/frontend/types.md +++ b/docs/developers/frontend/types.md @@ -114,4 +114,5 @@ const CANTable = ({ cans }) => { - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) - [JSDocs TypeDefinition](https://jsdoc.app/tags-typedef) - [JSDocs Types](https://jsdoc.app/tags-type) +- [ThePrimeTimeagen inspiration TY Short](https://www.youtube.com/shorts/tj5VW2xJsqU) ``` From 938706866b6da8214d2111ffc2be82394e3ef112 Mon Sep 17 00:00:00 2001 From: Frank Pigeon Jr Date: Mon, 23 Sep 2024 13:41:47 -0500 Subject: [PATCH 41/41] chore: add optional types --- frontend/src/components/CANs/CANTypes.d.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/CANs/CANTypes.d.ts b/frontend/src/components/CANs/CANTypes.d.ts index df39505b0b..abdb289e9b 100644 --- a/frontend/src/components/CANs/CANTypes.d.ts +++ b/frontend/src/components/CANs/CANTypes.d.ts @@ -3,20 +3,21 @@ import { Portfolio } from "../Portfolios/PortfolioTypes"; import { Project } from "../Projects/ProjectTypes"; export type CAN = { - active_period: number; + active_period?: number; budget_line_items: BudgetLine[]; created_by: number | null; created_by_user: number | null; created_on: Date; - description: string; - display_name: string; + description?: string; + display_name?: string; funding_budgets: CANFundingBudget[]; funding_details: CANFundingDetails; funding_details_id: number; funding_received: CANFundingReceived[]; id: number; - nick_name: string; + nick_name?: string; number: string; + obligate_by?: Date; portfolio: Portfolio; portfolio_id: number; projects: Project[];