diff --git a/frontend/cypress/e2e/canDetail.cy.js b/frontend/cypress/e2e/canDetail.cy.js
index c4cca8f402..148642e622 100644
--- a/frontend/cypress/e2e/canDetail.cy.js
+++ b/frontend/cypress/e2e/canDetail.cy.js
@@ -1,8 +1,9 @@
///
import { terminalLog, testLogin } from "./utils";
+import { getCurrentFiscalYear } from "../../src/helpers/utils.js";
beforeEach(() => {
- testLogin("system-owner");
+ testLogin("budget-team");
});
afterEach(() => {
@@ -10,20 +11,75 @@ afterEach(() => {
cy.checkA11y(null, null, terminalLog);
});
+const can502Nickname = "SSRD";
+const can502Description = "Social Science Research and Development";
+
+const currentFiscalYear = getCurrentFiscalYear();
+
describe("CAN detail page", () => {
- it("shows relevant CAN data", () => {
+ it("shows the CAN details page", () => {
cy.visit("/cans/502/");
cy.get("h1").should("contain", "G99PHS9"); // heading
- cy.get("p").should("contain", "SSRD - 5 Years"); // sub-heading
+ cy.get("p").should("contain", can502Nickname); // sub-heading
cy.get("span").should("contain", "Nicole Deterding"); // team member
cy.get("span").should("contain", "Director Derrek"); // division director
cy.get("span").should("contain", "Program Support"); // portfolio
+ cy.get("span").should("contain", "Division of Data and Improvement"); // division
+ });
+ it("CAN Edit form", () => {
+ cy.visit("/cans/502/");
+ cy.get("#fiscal-year-select").select("2024");
+ cy.get("#edit").should("not.exist");
+ cy.get("#fiscal-year-select").select(currentFiscalYear);
+ cy.get("#edit").should("exist");
+ cy.get("#edit").click();
+ cy.get("h2").should("contain", "Edit CAN Details");
+ cy.get("#can-nickName").invoke("val").should("equal", can502Nickname);
+ cy.get("#can-nickName").clear();
+ cy.get(".usa-error-message").should("exist").contains("This is required information");
+ cy.get("#save-changes").should("be.disabled");
+ cy.get("#can-nickName").type("Test Can Nickname");
+ cy.get("#save-changes").should("not.be.disabled");
+ cy.get(".usa-error-message").should("not.exist");
+ cy.get("#description").invoke("val").should("equal", can502Description);
+ cy.get("#description").clear();
+ cy.get("#description").type("Test description.");
+ cy.get("#save-changes").click();
+ cy.get(".usa-alert__body").should("contain", "The CAN G99PHS9 has been successfully updated.");
+ cy.get("p").should("contain", "Test Can Nickname");
+ cy.get("dd").should("contain", "Test description.");
+ // revert back to original values
+ cy.get("#edit").click();
+ cy.get("#can-nickName").clear();
+ cy.get("#can-nickName").type(can502Nickname);
+ cy.get("#description").clear();
+ cy.get("#description").type(can502Description);
+ cy.get("#save-changes").click();
+ });
+ it("handles cancelling from CAN edit form", () => {
+ cy.visit("/cans/502/");
+ cy.get("#fiscal-year-select").select(currentFiscalYear);
+ // Attempt cancel without making any changes
+ cy.get("#edit").click();
+ cy.get("[data-cy='cancel-button']").click();
+ cy.get(".usa-modal__heading").should(
+ "contain",
+ "Are you sure you want to cancel editing? Your changes will not be saved."
+ );
+ cy.get("[data-cy='cancel-action']").click();
+ // Exit out of the cancel modal
+ cy.get("[data-cy='cancel-button']").click();
+ // Actual cancel event
+ cy.get("[data-cy='confirm-action']").click();
+ cy.get("h2").should("contain", "CAN Details");
+ cy.get("p").should("contain", can502Nickname);
+ cy.get("dd").should("contain", can502Description);
});
it("shows the CAN Spending page", () => {
cy.visit("/cans/504/spending");
cy.get("#fiscal-year-select").select("2043");
cy.get("h1").should("contain", "G994426"); // heading
- cy.get("p").should("contain", "HS - 5 Years"); // sub-heading
+ cy.get("p").should("contain", "HS"); // sub-heading
// should contain the budget line table
cy.get("table").should("exist");
// table should have more than 1 row
@@ -75,14 +131,17 @@ describe("CAN detail page", () => {
cy.visit("/cans/504/funding");
cy.get("#fiscal-year-select").select("2024");
cy.get("h1").should("contain", "G994426"); // heading
- cy.get("p").should("contain", "HS - 5 Years"); // sub-heading
+ cy.get("p").should("contain", "HS"); // sub-heading
cy.get("[data-cy=can-funding-info-card]")
.should("exist")
.and("contain", "EEXXXX20215DAD")
.and("contain", "5 Years")
.and("contain", "IDDA")
.and("contain", "09/30/25")
- .and("contain", "2021");
+ .and("contain", "2021")
+ .and("contain", "Quarterly")
+ .and("contain", "Direct")
+ .and("contain", "Discretionary");
cy.get("[data-cy=budget-received-card]")
.should("exist")
.and("contain", "FY 2024 Funding Received YTD")
diff --git a/frontend/src/api/opsAPI.js b/frontend/src/api/opsAPI.js
index 6598f8bb96..7007f513e6 100644
--- a/frontend/src/api/opsAPI.js
+++ b/frontend/src/api/opsAPI.js
@@ -203,12 +203,21 @@ export const opsApi = createApi({
query: (id) => `/cans/${id}`,
providesTags: ["Cans"]
}),
+ updateCan: builder.mutation({
+ query: ({ id, data }) => ({
+ url: `/cans/${id}`,
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: data
+ }),
+ invalidatesTags: ["Cans"]
+ }),
getCanFundingSummary: builder.query({
query: ({ ids, fiscalYear, activePeriod, transfer, portfolio, fyBudgets }) => {
const queryParams = [];
if (ids && ids.length > 0) {
- ids.forEach(id => queryParams.push(`can_ids=${id}`));
+ ids.forEach((id) => queryParams.push(`can_ids=${id}`));
}
if (fiscalYear) {
@@ -216,15 +225,15 @@ export const opsApi = createApi({
}
if (activePeriod && activePeriod.length > 0) {
- activePeriod.forEach(period => queryParams.push(`active_period=${period}`));
+ activePeriod.forEach((period) => queryParams.push(`active_period=${period}`));
}
if (transfer && transfer.length > 0) {
- transfer.forEach(t => queryParams.push(`transfer=${t}`));
+ transfer.forEach((t) => queryParams.push(`transfer=${t}`));
}
if (portfolio && portfolio.length > 0) {
- portfolio.forEach(p => queryParams.push(`portfolio=${p}`));
+ portfolio.forEach((p) => queryParams.push(`portfolio=${p}`));
}
if (fyBudgets && fyBudgets.length === 2) {
@@ -403,6 +412,7 @@ export const {
useUpdateUserMutation,
useGetCansQuery,
useGetCanByIdQuery,
+ useUpdateCanMutation,
useGetCanFundingSummaryQuery,
useGetNotificationsByUserIdQuery,
useGetNotificationsByUserIdAndAgreementIdQuery,
diff --git a/frontend/src/components/CANs/CANDetailForm/CANDetailForm.hooks.js b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.hooks.js
new file mode 100644
index 0000000000..9ddeb84dbe
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.hooks.js
@@ -0,0 +1,97 @@
+import React from "react";
+import classnames from "vest/classnames";
+import { useUpdateCanMutation } from "../../../api/opsAPI";
+import useAlert from "../../../hooks/use-alert.hooks";
+import suite from "./suite.js";
+
+export default function useCanDetailForm(canId, canNumber, canNickname, canDescription, portfolioId, toggleEditMode) {
+ const [nickName, setNickName] = React.useState(canNickname);
+ const [description, setDescription] = React.useState(canDescription);
+ const [showModal, setShowModal] = React.useState(false);
+ const [modalProps, setModalProps] = React.useState({
+ heading: "",
+ actionButtonText: "",
+ secondaryButtonText: "",
+ handleConfirm: () => {}
+ });
+ const [updateCan] = useUpdateCanMutation();
+ const { setAlert } = useAlert();
+
+ let res = suite.get();
+
+ const cn = classnames(suite.get(), {
+ invalid: "usa-form-group--error",
+ valid: "success",
+ warning: "warning"
+ });
+
+ const handleCancel = (e) => {
+ e.preventDefault();
+ setShowModal(true);
+ setModalProps({
+ heading: "Are you sure you want to cancel editing? Your changes will not be saved.",
+ actionButtonText: "Cancel Edits",
+ secondaryButtonText: "Continue Editing",
+ handleConfirm: () => cleanUp()
+ });
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ const payload = {
+ number: canNumber,
+ portfolio_id: portfolioId,
+ nick_name: nickName,
+ description: description
+ };
+
+ setAlert({
+ type: "success",
+ heading: "CAN Updated",
+ message: `The CAN ${canNumber} has been successfully updated.`
+ });
+
+ updateCan({
+ id: canId,
+ data: payload
+ });
+
+ cleanUp();
+ };
+
+ const cleanUp = () => {
+ setNickName("");
+ setDescription("");
+ setShowModal(false);
+ setModalProps({
+ heading: "",
+ actionButtonText: "",
+ secondaryButtonText: "",
+ handleConfirm: () => {}
+ });
+ toggleEditMode();
+ };
+
+ const runValidate = (name, value) => {
+ suite(
+ {
+ ...{ [name]: value }
+ },
+ name
+ );
+ };
+ return {
+ nickName,
+ setNickName,
+ description,
+ setDescription,
+ handleCancel,
+ handleSubmit,
+ runValidate,
+ res,
+ cn,
+ setShowModal,
+ showModal,
+ modalProps
+ };
+}
diff --git a/frontend/src/components/CANs/CANDetailForm/CANDetailForm.jsx b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.jsx
new file mode 100644
index 0000000000..fcefce09ae
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.jsx
@@ -0,0 +1,94 @@
+import Input from "../../UI/Form/Input";
+import TextArea from "../../UI/Form/TextArea";
+import ConfirmationModal from "../../UI/Modals/ConfirmationModal";
+import useCanDetailForm from "./CANDetailForm.hooks";
+
+/**
+ * @typedef {Object} CANDetailFormProps
+ * @property {number} canId - CAN ID
+ * @property {string} canNumber - CAN number
+ * @property {string} canNickname - CAN nick name
+ * @property {string} canDescription - CAN description
+ * @property {number} portfolioId - Portfolio ID
+ * @property {Function} toggleEditMode - Function to toggle edit mode
+ */
+
+/**
+ * @component - The CAN Details form
+ * @param {CANDetailFormProps} props
+ * @returns {JSX.Element}
+ */
+const CANDetailForm = ({ canId, canNumber, canNickname, canDescription, portfolioId, toggleEditMode }) => {
+ const {
+ nickName,
+ setNickName,
+ description,
+ setDescription,
+ handleCancel,
+ handleSubmit,
+ runValidate,
+ res,
+ cn,
+ showModal,
+ setShowModal,
+ modalProps
+ } = useCanDetailForm(canId, canNumber, canNickname, canDescription, portfolioId, toggleEditMode);
+
+ return (
+
+ );
+};
+
+export default CANDetailForm;
diff --git a/frontend/src/components/CANs/CANDetailForm/CANDetailForm.test.jsx b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.test.jsx
new file mode 100644
index 0000000000..711be9aed5
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailForm/CANDetailForm.test.jsx
@@ -0,0 +1,87 @@
+import { configureStore } from "@reduxjs/toolkit";
+import { fireEvent, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Provider } from "react-redux";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+import { opsApi } from "../../../api/opsAPI";
+import CANDetailForm from "./CANDetailForm";
+
+// Mock data
+const mockCan = {
+ id: 123,
+ number: "CAN-001",
+ nick_name: "Test CAN",
+ description: "Test Description",
+ portfolio_id: 456
+};
+
+// Mock store setup
+const store = configureStore({
+ reducer: {
+ [opsApi.reducerPath]: opsApi.reducer
+ },
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(opsApi.middleware)
+});
+
+vi.mock("react-redux", async () => {
+ const actual = await vi.importActual("react-redux");
+ return {
+ ...actual,
+ useSelector: vi.fn((selector) => {
+ // Mock the auth state
+ const mockState = {
+ alert: {
+ isActive: false
+ }
+ };
+ return selector(mockState);
+ })
+ };
+});
+
+const renderComponent = () => {
+ return render(
+
+
+
+ );
+};
+
+describe("CANDetailForm", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test("renders form with initial values", () => {
+ renderComponent();
+
+ expect(screen.getByLabelText(/nickname/i)).toHaveValue(mockCan.nick_name);
+ expect(screen.getByLabelText(/description/i)).toHaveValue(mockCan.description);
+ });
+
+ test("validates required nickname field", async () => {
+ renderComponent();
+ const nicknameInput = screen.getByLabelText(/nickname/i);
+
+ await userEvent.clear(nicknameInput);
+ fireEvent.blur(nicknameInput);
+
+ expect(await screen.findByText("This is required information")).toBeInTheDocument();
+ });
+
+ test("shows confirmation modal when canceling", async () => {
+ renderComponent();
+
+ const cancelButton = screen.getByText(/cancel/i);
+ await userEvent.click(cancelButton);
+
+ expect(screen.getByText(/are you sure you want to cancel editing/i)).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/CANs/CANDetailForm/index.js b/frontend/src/components/CANs/CANDetailForm/index.js
new file mode 100644
index 0000000000..82e5cfda6f
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailForm/index.js
@@ -0,0 +1 @@
+export {default} from "./CANDetailForm"
diff --git a/frontend/src/components/CANs/CANDetailForm/suite.js b/frontend/src/components/CANs/CANDetailForm/suite.js
new file mode 100644
index 0000000000..ddb4a54438
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailForm/suite.js
@@ -0,0 +1,11 @@
+import { create, test, enforce, only } from "vest";
+
+const suite = create((data = {}, fieldName) => {
+ only(fieldName);
+
+ test("can-nickName", "This is required information", () => {
+ enforce(data["can-nickName"]).isNotBlank();
+ });
+});
+
+export default suite;
diff --git a/frontend/src/components/CANs/CANDetailView/CANDetailView.jsx b/frontend/src/components/CANs/CANDetailView/CANDetailView.jsx
new file mode 100644
index 0000000000..171f7ddb79
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailView/CANDetailView.jsx
@@ -0,0 +1,87 @@
+import TermTag from "../../UI/Term/TermTag";
+import Term from "../../UI/Term";
+import Tag from "../../UI/Tag";
+/**
+ * @typedef {Object} CANDetailViewProps
+ * @property {string} description
+ * @property {string} number
+ * @property {string} nickname
+ * @property {string} portfolioName
+ * @property {import("../../Users/UserTypes").SafeUser[]} teamLeaders
+ * @property {string} divisionDirectorFullName
+ * @property {string} divisionName
+ */
+/**
+ * This component needs to wrapped in a element.
+ * @component - Renders a term with a tag.
+ * @param {CANDetailViewProps} props - The properties passed to the component.
+ * @returns {JSX.Element} - The rendered component.
+ */
+const CANDetailView = ({ description, number, nickname, portfolioName, teamLeaders, divisionDirectorFullName, divisionName}) => {
+ return (
+
+ {/* // NOTE: Left Column */}
+
+
+
+
+
+ History
+ Not yet implemented
+
+
+ {/* // NOTE: Right Column */}
+
+
+
+
+
+
+
+
+ - Team Leader
+ {teamLeaders &&
+ teamLeaders.length > 0 &&
+ teamLeaders.map((teamLeader) => (
+ -
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default CANDetailView;
diff --git a/frontend/src/components/CANs/CANDetailView/CANDetailView.test.jsx b/frontend/src/components/CANs/CANDetailView/CANDetailView.test.jsx
new file mode 100644
index 0000000000..02012620c0
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailView/CANDetailView.test.jsx
@@ -0,0 +1,66 @@
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import CANDetailView from "./CANDetailView";
+
+const mockProps = {
+ description: "Test CAN Description",
+ number: "CAN-123",
+ nickname: "Test Nickname",
+ portfolioName: "Test Portfolio",
+ teamLeaders: [
+ { id: 1, full_name: "John Doe" },
+ { id: 2, full_name: "Jane Smith" }
+ ],
+ divisionDirectorFullName: "Director Name",
+ divisionName: "Test Division"
+};
+
+describe("CANDetailView", () => {
+ it("renders all CAN details correctly", () => {
+ render(
+
+
+
+ );
+
+ // Check for basic text content
+ expect(screen.getByText("Test CAN Description")).toBeInTheDocument();
+ expect(screen.getByText("CAN-123")).toBeInTheDocument();
+ expect(screen.getByText("Test Nickname")).toBeInTheDocument();
+ expect(screen.getByText("Test Portfolio")).toBeInTheDocument();
+ expect(screen.getByText("Test Division")).toBeInTheDocument();
+
+ // Check for team leaders
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ expect(screen.getByText("Jane Smith")).toBeInTheDocument();
+
+ // Check for division director
+ expect(screen.getByText("Director Name")).toBeInTheDocument();
+ });
+
+ it("renders history section", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText("History")).toBeInTheDocument();
+ expect(screen.getByText("Not yet implemented")).toBeInTheDocument();
+ });
+
+ it("renders without team leaders", () => {
+ render(
+
+
+
+ );
+
+ // Verify other content still renders
+ expect(screen.getByText("Test CAN Description")).toBeInTheDocument();
+ expect(screen.getByText("Team Leader")).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/CANs/CANDetailView/index.js b/frontend/src/components/CANs/CANDetailView/index.js
new file mode 100644
index 0000000000..ca236edc61
--- /dev/null
+++ b/frontend/src/components/CANs/CANDetailView/index.js
@@ -0,0 +1 @@
+export { default } from "./CANDetailView";
diff --git a/frontend/src/pages/cans/detail/Can.hooks.js b/frontend/src/pages/cans/detail/Can.hooks.js
index e6f3ec0620..072fef1c46 100644
--- a/frontend/src/pages/cans/detail/Can.hooks.js
+++ b/frontend/src/pages/cans/detail/Can.hooks.js
@@ -2,6 +2,7 @@ import React from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import { useGetCanByIdQuery, useGetCanFundingSummaryQuery } from "../../../api/opsAPI";
+import { USER_ROLES } from "../../../components/Users/User.constants";
import { getTypesCounts } from "./Can.helpers";
import { NO_DATA } from "../../../constants";
@@ -10,6 +11,9 @@ export default function useCan() {
* @typedef {import("../../../components/CANs/CANTypes").CAN} CAN
*/
+ const activeUser = useSelector((state) => state.auth.activeUser);
+ const userRoles = activeUser?.roles ?? [];
+ const isBudgetTeam = userRoles.includes(USER_ROLES.BUDGET_TEAM);
const selectedFiscalYear = useSelector((state) => state.canDetail.selectedFiscalYear);
const fiscalYear = Number(selectedFiscalYear.value);
const urlPathParams = useParams();
@@ -43,18 +47,24 @@ export default function useCan() {
[budgetLineItemsByFiscalYear]
);
+ const budgetLinesAgreements = budgetLineItemsByFiscalYear?.map((item) => item.agreement) ?? [];
- const budgetLineAgreements = can?.budget_line_items?.map(
- (item) => item.agreement
- ) ?? [];
-
+ /**
+ * @type {import("../../../components/Agreements/AgreementTypes").SimpleAgreement[]} - Array of unique budget line agreements
+ */
+ const uniqueBudgetLineAgreements =
+ budgetLinesAgreements?.reduce((acc, item) => {
+ if (!acc.some((existingItem) => existingItem.name === item.name)) {
+ acc.push(item);
+ }
+ return acc;
+ }, []) ?? [];
const agreementTypesCount = React.useMemo(
- () => getTypesCounts(budgetLineAgreements, "agreement_type"),
+ () => getTypesCounts(uniqueBudgetLineAgreements, "agreement_type"),
[fiscalYear, can]
);
-
return {
can: can ?? null,
isLoading,
@@ -62,7 +72,7 @@ export default function useCan() {
fiscalYear,
CANFundingLoading,
budgetLineItemsByFiscalYear,
- number: can?.number ?? NO_DATA,
+ canNumber: can?.number ?? NO_DATA,
description: can?.description,
nickname: can?.nick_name,
fundingDetails: can?.funding_details ?? {},
@@ -71,6 +81,7 @@ export default function useCan() {
divisionId: can?.portfolio?.division_id ?? -1,
teamLeaders: can?.portfolio?.team_leaders ?? [],
portfolioName: can?.portfolio?.name,
+ portfolioId: can?.portfolio_id ?? -1,
totalFunding: CANFunding?.total_funding,
plannedFunding: CANFunding?.planned_funding,
obligatedFunding: CANFunding?.obligated_funding,
@@ -78,9 +89,10 @@ export default function useCan() {
inDraftFunding: CANFunding?.in_draft_funding,
expectedFunding: CANFunding?.expected_funding,
receivedFunding: CANFunding?.received_funding,
- subTitle: can ? `${can.nick_name} - ${can.active_period} ${can.active_period > 1 ? "Years" : "Year"}` : "",
+ subTitle: can?.nick_name ?? "",
projectTypesCount,
budgetLineTypesCount,
- agreementTypesCount
+ agreementTypesCount,
+ isBudgetTeam
};
}
diff --git a/frontend/src/pages/cans/detail/Can.jsx b/frontend/src/pages/cans/detail/Can.jsx
index 09ad660eaa..d3dad9b144 100644
--- a/frontend/src/pages/cans/detail/Can.jsx
+++ b/frontend/src/pages/cans/detail/Can.jsx
@@ -21,7 +21,7 @@ const Can = () => {
fiscalYear,
CANFundingLoading,
budgetLineItemsByFiscalYear,
- number,
+ canNumber,
description,
nickname,
fundingDetails,
@@ -30,6 +30,7 @@ const Can = () => {
divisionId,
teamLeaders,
portfolioName,
+ portfolioId,
totalFunding,
plannedFunding,
obligatedFunding,
@@ -39,7 +40,8 @@ const Can = () => {
projectTypesCount,
budgetLineTypesCount,
agreementTypesCount,
- receivedFunding
+ receivedFunding,
+ isBudgetTeam
} = useCan();
if (isLoading || CANFundingLoading) {
@@ -69,12 +71,16 @@ const Can = () => {
path=""
element={
}
/>
diff --git a/frontend/src/pages/cans/detail/CanDetail.jsx b/frontend/src/pages/cans/detail/CanDetail.jsx
index ca27b7bbbe..a163af88c1 100644
--- a/frontend/src/pages/cans/detail/CanDetail.jsx
+++ b/frontend/src/pages/cans/detail/CanDetail.jsx
@@ -1,7 +1,11 @@
+import { faPen } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import React from "react";
import { useGetDivisionQuery } from "../../../api/opsAPI";
-import Tag from "../../../components/UI/Tag";
-import Term from "../../../components/UI/Term";
-import TermTag from "../../../components/UI/Term/TermTag";
+import CANDetailForm from "../../../components/CANs/CANDetailForm";
+import CANDetailView from "../../../components/CANs/CANDetailView";
+import { NO_DATA } from "../../../constants.js";
+import { getCurrentFiscalYear } from "../../../helpers/utils";
import useGetUserFullNameFromId from "../../../hooks/user.hooks";
/**
@@ -10,12 +14,16 @@ import useGetUserFullNameFromId from "../../../hooks/user.hooks";
/**
* @typedef {Object} CanDetailProps
+ * @property {number} canId
* @property {string} description
- * @property {string} number
+ * @property {string} canNumber
* @property {string} nickname
* @property {string} portfolioName
+ * @property {number} portfolioId
* @property {SafeUser[]} teamLeaders
* @property {number} divisionId
+ * @property {number} fiscalYear
+ * @property {boolean} isBudgetTeamMember
*/
/**
@@ -23,71 +31,71 @@ import useGetUserFullNameFromId from "../../../hooks/user.hooks";
* @param {CanDetailProps} props
* @returns {JSX.Element} - The component JSX.
*/
-const CanDetail = ({ description, number, nickname, portfolioName, teamLeaders, divisionId }) => {
+const CanDetail = ({
+ canId,
+ description,
+ canNumber,
+ nickname,
+ portfolioName,
+ portfolioId,
+ teamLeaders,
+ divisionId,
+ fiscalYear,
+ isBudgetTeamMember
+}) => {
const { data: division, isSuccess } = useGetDivisionQuery(divisionId);
const divisionDirectorFullName = useGetUserFullNameFromId(isSuccess ? division.division_director_id : null);
+ const [isEditMode, setIsEditMode] = React.useState(false);
+
+ const toggleEditMode = () => {
+ setIsEditMode(!isEditMode);
+ };
+
+ const currentFiscalYear = getCurrentFiscalYear();
+ const showButton = isBudgetTeamMember && fiscalYear === Number(currentFiscalYear);
+ const divisionName = division?.display_name ?? NO_DATA;
return (
- CAN Details
-
- {/* // NOTE: Left Column */}
-
-
-
-
-
- History
- Not yet implemented
-
-
- {/* // NOTE: Right Column */}
-
-
-
-
-
-
-
- - Team Leader
- {teamLeaders &&
- teamLeaders.length > 0 &&
- teamLeaders.map((teamLeader) => (
- -
-
-
- ))}
-
+ {!isEditMode ? "CAN Details" : "Edit CAN Details"}
+ {showButton && (
+
-
+
Edit
+
+ )}
+ {isEditMode ? (
+
+ ) : (
+
+ )}
);
};