diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d323171d..48b16aa8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,10 +9,10 @@ "version": "0.1.0", "dependencies": { "@bcgov-nr/nr-theme": "^1.7.0", - "@carbon/charts-react": "^1.13.32", - "@carbon/icons-react": "^11.50.1", - "@carbon/pictograms-react": "^11.49.0", - "@carbon/react": "^1.27.0", + "@carbon/charts-react": "^1.22.5", + "@carbon/icons-react": "^11.53.0", + "@carbon/pictograms-react": "^11.69.0", + "@carbon/react": "^1.71.1", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", @@ -1690,9 +1690,9 @@ "license": "MIT" }, "node_modules/@carbon/charts": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/@carbon/charts/-/charts-1.22.4.tgz", - "integrity": "sha512-htpbi6y2pIlHiH7FysqSu9qQVT9NpnbY0Z6c4a7QXi/0gf54TZWn4Lgmn3m6wA4gmsNp1Emqc0SE8tRIsh7LRA==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@carbon/charts/-/charts-1.22.5.tgz", + "integrity": "sha512-/EZBnFbud3IOPcCroBgR7mNXOB3U/z0BETyjv38FlkC/f5GtGvnXu+8RtrsYH+YW5JEfZlycjY5F8C5eP6E0Yg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1714,13 +1714,13 @@ } }, "node_modules/@carbon/charts-react": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/@carbon/charts-react/-/charts-react-1.22.4.tgz", - "integrity": "sha512-e0yQQyuOEtrJk/pazSlF3E5M1jTqcHxLevxe4jfaHB/tFw5qhHPyTNR3qUdJ2Mf5evXQJghqGqxwE9xXK4djIA==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@carbon/charts-react/-/charts-react-1.22.5.tgz", + "integrity": "sha512-a/aZiCdN6BSw/b3dbRW4Segfy5bLISDjODdUi+oHoXPHQh8A+Sz9GMN5SREbuZ03jtZUvtocr51dEuoq5y8MKQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@carbon/charts": "1.22.4", + "@carbon/charts": "1.22.5", "@carbon/icons-react": "^11.53.0", "@ibm/telemetry-js": "^1.8.0" }, @@ -1821,9 +1821,9 @@ } }, "node_modules/@carbon/react": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.71.0.tgz", - "integrity": "sha512-OavbieRyUk11s6g9vClrzVTnWkoRbEcXjtieI7CD0lvHVAEc5hNf1+lKtS27LINBPKchJdi6Ox/p7N9Vc0jh1g==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.71.1.tgz", + "integrity": "sha512-wLlHFwJKoCFt2ltpvc9mRfdMtRg3YTNr/GHGcNFNfPjRtNFTuTx/ImmXc9WInctsEts3i2VOizT/pdRJ9fAP+A==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1840,7 +1840,6 @@ "flatpickr": "4.6.13", "invariant": "^2.2.3", "lodash.debounce": "^4.0.8", - "lodash.findlast": "^4.5.0", "lodash.omit": "^4.5.0", "lodash.throttle": "^4.1.1", "prop-types": "^15.7.2", @@ -10451,12 +10450,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, - "node_modules/lodash.findlast": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.findlast/-/lodash.findlast-4.6.0.tgz", - "integrity": "sha512-+OGwb1FVKjhc2aIEQ9vKqNDW1a0/HaCLr0iCIK10jfVif3dBE0nhQD0jOZNZLh7zOlmFUTrk+vt85eXoH4vKuA==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4af11e42..00082c76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,10 +5,10 @@ "type": "module", "dependencies": { "@bcgov-nr/nr-theme": "^1.7.0", - "@carbon/charts-react": "^1.13.32", - "@carbon/icons-react": "^11.50.1", - "@carbon/pictograms-react": "^11.49.0", - "@carbon/react": "^1.27.0", + "@carbon/charts-react": "^1.22.5", + "@carbon/icons-react": "^11.53.0", + "@carbon/pictograms-react": "^11.69.0", + "@carbon/react": "^1.71.1", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", diff --git a/frontend/src/__test__/components/ActionButtons.test.tsx b/frontend/src/__test__/components/ActionButtons.test.tsx index 6f554d9b..0c6137b5 100644 --- a/frontend/src/__test__/components/ActionButtons.test.tsx +++ b/frontend/src/__test__/components/ActionButtons.test.tsx @@ -1,39 +1,41 @@ // ActionButtons.test.tsx import React from "react"; -import { vi } from "vitest"; +import { MemoryRouter } from 'react-router-dom'; import { render, screen, fireEvent } from "@testing-library/react"; import ActionButtons from "../../components/ActionButtons"; - -// Mock console.log -const consoleLogMock = vi.spyOn(console, "log").mockImplementationOnce(() =>vi.fn()) ; - -afterEach(() => { - consoleLogMock.mockClear(); -}); - -afterAll(() => { - consoleLogMock.mockRestore(); -}); +import { NotificationProvider } from "../../contexts/NotificationProvider" describe("ActionButtons", () => { - const rowId = "test-row-id"; - - it("renders the 'View' and 'Document Download' buttons", () => { - render(); + const rowId = "123456"; + const favorited = false; + + it("renders the 'Favorite Opening' and 'Document Download' buttons", () => { + render( + + + + + + ); // Check that both buttons are in the document - expect(screen.getByRole("button", { name: /View/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Favorite Opening/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /Document Download/i })).toBeInTheDocument(); }); - it("calls console.log with rowId when the 'View' button is clicked", () => { - render(); + it("set the 'Favorite Opening' as favorited when button is clicked", () => { + render( + + + + + + ); // Find the "View" button and click it - const viewButton = screen.getByRole("button", { name: /View/i }); + const viewButton = screen.getByRole("button", { name: /Favorite Opening/i }); + expect(viewButton.classList).toContain('favorite-button'); fireEvent.click(viewButton); - - // Check if console.log was called with the correct rowId - expect(consoleLogMock).toHaveBeenCalledWith(rowId); + expect(screen.getByRole("button", { name: /Favorite Opening/i }).classList).toContain('favorite'); }); }); diff --git a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx index 77b500cd..6970522a 100644 --- a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx +++ b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent, act } from '@testing-library/react'; +import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import SearchScreenDataTable from '../../../../components/SilvicultureSearch/Openings/SearchScreenDataTable'; import { searchScreenColumns as columns } from '../../../../constants/tableConstants'; @@ -358,7 +358,7 @@ describe('Search Screen Data table test', () => { expect(screen.getByTestId('toggle-spatial')).toContainHTML('Hide map'); - const checkbox = document.querySelector('.cds--checkbox-group'); + const checkbox = await screen.findByTestId('checkbox-114207'); expect(checkbox).toBeInTheDocument(); }); @@ -386,13 +386,13 @@ describe('Search Screen Data table test', () => { ); - const checkboxGroup = document.querySelector('.cds--checkbox-group'); - expect(checkboxGroup).toBeInTheDocument(); + const checkbox = screen.getByTestId(`checkbox-${rows[0].openingId}`); + expect(checkbox).toBeInTheDocument(); expect(screen.getByTestId(`checkbox-${rows[0].openingId}`)).toBeInTheDocument(); - const checkbox = screen.getByTestId(`checkbox-${rows[0].openingId}`); - fireEvent.click(checkbox); - expect(checkbox).toBeChecked(); + const checkbox2 = screen.getByTestId(`checkbox-${rows[0].openingId}`); + fireEvent.click(checkbox2); + expect(checkbox2).toBeChecked(); expect(setOpeningIds).toHaveBeenCalledWith([parseFloat(rows[0].openingId)]); }); @@ -435,21 +435,13 @@ describe('Search Screen Data table test', () => { await act(async () => expect(screen.getByTestId('row-114207')).toBeInTheDocument() ); await act(async () => expect(screen.getByTestId('cell-actions-114206')).toBeInTheDocument() ); - - const overflowMenu = screen.getByTestId('action-ofl-114207'); - await act(async () => expect(overflowMenu).toBeInTheDocument() ); - await act(async () => fireEvent.click(overflowMenu)); const actionOverflow = screen.getByTestId(`action-fav-114207`); await act(async () => expect(actionOverflow).toBeInTheDocument() ); - expect(actionOverflow).toContainHTML('Favourite opening'); + expect(actionOverflow).toHaveAttribute('aria-pressed', 'false'); await act(async () => fireEvent.click(actionOverflow)); - const overflowMenuAgain = screen.getByTestId('action-ofl-114207'); - await act(async () => expect(overflowMenuAgain).toBeInTheDocument() ); - await act(async () => fireEvent.click(overflowMenuAgain)); - - expect(screen.getByTestId(`action-fav-114207`)).toContainHTML('Unfavourite opening'); + expect(screen.getByTestId(`action-fav-114207`)).toHaveAttribute('aria-pressed', 'true'); }); diff --git a/frontend/src/__test__/components/TableCellContent.test.tsx b/frontend/src/__test__/components/TableCellContent.test.tsx index 3cebab09..9c55bc85 100644 --- a/frontend/src/__test__/components/TableCellContent.test.tsx +++ b/frontend/src/__test__/components/TableCellContent.test.tsx @@ -1,9 +1,11 @@ // TableCellContent.test.tsx import React from "react"; +import { MemoryRouter } from 'react-router-dom'; import { vi } from "vitest"; import { render, screen } from "@testing-library/react"; import TableCellContent from "../../components/TableCellContent"; import { OpeningsSearch } from "../../types/OpeningsSearch"; +import { NotificationProvider } from "../../contexts/NotificationProvider" // Mock components vi.mock("../StatusTag", () => ({ @@ -28,6 +30,7 @@ describe("TableCellContent", () => { it("renders StatusTag when headerKey is 'statusDescription'", () => { render( + { selectedRows={selectedRows} handleRowSelectionChanged={handleRowSelectionChanged} /> + ); expect(screen.getByText(/Active/i)).toBeInTheDocument(); @@ -42,40 +46,52 @@ describe("TableCellContent", () => { it("renders ActionButtons and optionally SpatialCheckbox when headerKey is 'actions'", () => { const { rerender } = render( - + + + + + ); console.log(screen.debug()); - expect(screen.queryByText(/View/i)).toBeInTheDocument(); + expect(screen.queryByText(/Favorite Opening/i)).toBeInTheDocument(); }); it("renders category code and description when headerKey is 'Category'", () => { render( - + + + + + ); expect(screen.getAllByText("A - Category A")[0]).toBeInTheDocument(); }); it("renders default content for other headerKey values", () => { render( - + + + + + ); expect(screen.getByText(/Unknown Value/i)).toBeInTheDocument(); @@ -83,13 +99,17 @@ describe("TableCellContent", () => { it("renders SpatialCheckbox when headerKey is 'actions' and showSpatial is true", () => { render( - + + + + + ); //check if the Checkbox text is present expect(screen.getByText(/Click to view this opening's map activity./i)).toBeInTheDocument(); diff --git a/frontend/src/__test__/components/TableRowComponent.test.tsx b/frontend/src/__test__/components/TableRowComponent.test.tsx index 49fc2286..22f52f71 100644 --- a/frontend/src/__test__/components/TableRowComponent.test.tsx +++ b/frontend/src/__test__/components/TableRowComponent.test.tsx @@ -61,8 +61,8 @@ describe("TableRowComponent", () => { ); // Simulate click on the row - const tableRow = screen.getByRole("row"); - fireEvent.click(tableRow); + const tableRow = screen.getAllByRole("cell"); + fireEvent.click(tableRow[1]); // Verify that setOpeningDetails was called with true expect(setOpeningDetails).toHaveBeenCalledWith("1"); diff --git a/frontend/src/components/ActionButtons/index.tsx b/frontend/src/components/ActionButtons/index.tsx index 6de44329..4aac3f17 100644 --- a/frontend/src/components/ActionButtons/index.tsx +++ b/frontend/src/components/ActionButtons/index.tsx @@ -3,31 +3,71 @@ import React from "react"; import { Button } from "@carbon/react"; import * as Icons from "@carbon/icons-react"; +import { useNavigate } from "react-router-dom"; +import FavoriteButton from "../FavoriteButton"; +import { useNotification } from "../../contexts/NotificationProvider"; +import { setOpeningFavorite, deleteOpeningFavorite } from "../../services/OpeningFavouriteService"; interface ActionButtonsProps { + favorited: boolean; rowId: string; } -const ActionButtons: React.FC = ({ rowId }) => ( +const ActionButtons: React.FC = ({ favorited, rowId }) => { + const { displayNotification } = useNotification(); + const navigate = useNavigate(); + + const handleFavoriteChange = async (newStatus: boolean, openingId: number) => { + try { + if(!newStatus){ + await deleteOpeningFavorite(openingId); + }else{ + await setOpeningFavorite(openingId); + } + displayNotification({ + title: `Opening Id ${openingId} ${!newStatus ? 'un' : ''}favourited`, + subTitle: newStatus ? "You can follow this opening ID on your dashboard" : undefined, + type: 'success', + dismissIn: 8000, + buttonLabel: newStatus ? "Go to track openings" : undefined, + onClose: () => { + navigate("/opening?tab=metrics&scrollTo=trackOpenings"); + } + }); + } catch (error) { + displayNotification({ + title: 'Error', + subTitle: `Failed to update favorite status for ${openingId}`, + type: 'error', + dismissIn: 8000, + onClose: () => {} + }); + } + }; + + return ( <> - console.log(rowId)} - size="md" + size="sm" + fill="#0073E6" + favorited={favorited} + onFavoriteChange={(newStatus: boolean) => handleFavoriteChange(newStatus, parseFloat(rowId))} /> null} size="md" disabled /> > -); +)}; export default ActionButtons; diff --git a/frontend/src/components/Dashboard/Opening/RecentOpeningsDataTable/index.tsx b/frontend/src/components/Dashboard/Opening/RecentOpeningsDataTable/index.tsx index 91084a3d..dce2c34e 100644 --- a/frontend/src/components/Dashboard/Opening/RecentOpeningsDataTable/index.tsx +++ b/frontend/src/components/Dashboard/Opening/RecentOpeningsDataTable/index.tsx @@ -12,7 +12,6 @@ import EmptySection from "../../../EmptySection"; import PaginationContext from "../../../../contexts/PaginationContext"; import { OpeningsSearch } from "../../../../types/OpeningsSearch"; import { ITableHeader } from "../../../../types/TableHeader"; -import { useNavigate } from "react-router-dom"; import TableRowComponent from "../../../TableRowComponent"; import ComingSoonModal from "../../../ComingSoonModal"; @@ -39,7 +38,6 @@ const RecentOpeningsDataTable: React.FC = ({ } = useContext(PaginationContext); const [selectedRows, setSelectedRows] = useState([]); const [openingDetails, setOpeningDetails] = useState(""); - const navigate = useNavigate(); useEffect(() => { setInitialItemsPerPage(itemsPerPage); diff --git a/frontend/src/components/FavoriteButton/index.tsx b/frontend/src/components/FavoriteButton/index.tsx index d2870d6f..860969b1 100644 --- a/frontend/src/components/FavoriteButton/index.tsx +++ b/frontend/src/components/FavoriteButton/index.tsx @@ -4,6 +4,7 @@ import * as Icons from '@carbon/icons-react'; import './style.scss'; // Import the styles interface FavoriteButtonProps { + id?: string tooltipPosition: string; kind: string; size: string; @@ -24,6 +25,7 @@ interface FavoriteButtonProps { * @returns {JSX.Element} The FavoriteButton element to be rendered. */ function FavoriteButton({ + id, tooltipPosition, kind, size, @@ -53,7 +55,9 @@ function FavoriteButton({ return ( { void; toggleSpatial: () => void; showSpatial: boolean; @@ -68,7 +60,6 @@ interface ICellRefs { const SearchScreenDataTable: React.FC = ({ rows, headers, - defaultColumns, handleCheckboxChange, toggleSpatial, showSpatial, @@ -87,14 +78,8 @@ const SearchScreenDataTable: React.FC = ({ const [openDownload, setOpenDownload] = useState(false); const [selectedRows, setSelectedRows] = useState([]); // State to store selected rows const [openingDetails, setOpeningDetails] = useState(""); - const [columnsSelected, setColumnsSelected] = - useState("select-default"); - const { - mutate: markAsViewedOpening, - isError, - error, - } = usePostViewedOpening(); - const navigate = useNavigate(); + const [columnsSelected, setColumnsSelected] = useState("select-default"); + const { mutate: markAsViewedOpening } = usePostViewedOpening(); // This ref is used to calculate the width of the container for each cell const cellRefs = useRef([]); @@ -159,42 +144,6 @@ const SearchScreenDataTable: React.FC = ({ }); }; - //Function to handle the favourite feature of the opening for a user - const handleFavouriteOpening = async ( - openingId: string, - favorite: boolean - ) => { - try { - if (favorite) { - await deleteOpeningFavorite(parseInt(openingId)); - displayNotification({ - title: `Opening Id ${openingId} unfavourited`, - type: "success", - dismissIn: 8000, - onClose: () => {}, - }); - } else { - await setOpeningFavorite(parseInt(openingId)); - displayNotification({ - title: `Opening Id ${openingId} favourited`, - subTitle: "You can follow this opening ID on your dashboard", - type: "success", - buttonLabel: "Go to track openings", - onClose: () => { - navigate("/opening?tab=metrics&scrollTo=trackOpenings"); - }, - }); - } - } catch (favoritesError) { - displayNotification({ - title: "Unable to process your request", - subTitle: "Please try again in a few minutes", - type: "error", - onClose: () => {}, - }); - } - }; - return ( <> @@ -425,79 +374,20 @@ const SearchScreenDataTable: React.FC = ({ {header.key === "statusDescription" ? ( ) : header.key === "actions" ? ( - <> - {showSpatial && ( - - {/* Checkbox for selecting rows */} - - - - - handleRowSelectionChanged(row.openingId) - } - /> - - - - )} - {!showSpatial && ( - - { - handleFavouriteOpening( - row.openingId, - row.favourite - ); - row.favourite = !row.favourite; - }} - /> - - downloadPDF(defaultColumns, [row]) - } - /> - { - const csvData = convertToCSV( - defaultColumns, - [row] - ); - downloadCSV(csvData, "openings-data.csv"); - }} + + + {showSpatial && ( + - - - )} - > + )} + + ) : header.header === "Category" ? ( = ({ rowId, selectedRows, - handleRowSelectionChanged, + handleRowSelectionChanged }) => ( - - - handleRowSelectionChanged(rowId)} - /> - - + + handleRowSelectionChanged(rowId)} + /> + ); export default SpatialCheckbox; diff --git a/frontend/src/components/TableCellContent/index.tsx b/frontend/src/components/TableCellContent/index.tsx index 3cc7a2cd..6fb4c075 100644 --- a/frontend/src/components/TableCellContent/index.tsx +++ b/frontend/src/components/TableCellContent/index.tsx @@ -1,5 +1,3 @@ -// TableCellContent.tsx - import React from "react"; import StatusTag from "../StatusTag"; import ActionButtons from "../ActionButtons"; @@ -20,23 +18,26 @@ const TableCellContent: React.FC = ({ row, showSpatial, selectedRows, - handleRowSelectionChanged, + handleRowSelectionChanged }) => { switch (headerKey) { case "statusDescription": return ; case "actions": return ( - <> - - {showSpatial && ( + + {showSpatial && ( - )} - > + )} + + ); case "Category": return ( diff --git a/frontend/src/components/TableRowComponent/index.tsx b/frontend/src/components/TableRowComponent/index.tsx index de31b020..f5817e83 100644 --- a/frontend/src/components/TableRowComponent/index.tsx +++ b/frontend/src/components/TableRowComponent/index.tsx @@ -23,10 +23,16 @@ const TableRowComponent: React.FC = ({ handleRowSelectionChanged, setOpeningDetails }) => ( - setOpeningDetails(row.openingId.toString())}> + {headers.map((header) => header.selected ? ( - + { + if (header.key !== "actions") + setOpeningDetails(row.openingId.toString()) + }} + > ({ diff --git a/frontend/src/types/OpeningsSearch.ts b/frontend/src/types/OpeningsSearch.ts index e094581e..946c926a 100644 --- a/frontend/src/types/OpeningsSearch.ts +++ b/frontend/src/types/OpeningsSearch.ts @@ -9,4 +9,5 @@ export interface OpeningsSearch { cutBlockId: string | null; orgUnitName: string; updateTimestamp: string; + favourite: boolean; }