diff --git a/frontend/src/__test__/components/SilvicultureSearch/Openings/OpeningSearchTab.test.tsx b/frontend/src/__test__/components/SilvicultureSearch/Openings/OpeningSearchTab.test.tsx index 9f40c749..23a91b08 100644 --- a/frontend/src/__test__/components/SilvicultureSearch/Openings/OpeningSearchTab.test.tsx +++ b/frontend/src/__test__/components/SilvicultureSearch/Openings/OpeningSearchTab.test.tsx @@ -189,9 +189,9 @@ describe('OpeningSearchTab', () => { expect(screen.getByTestId('Opening Id')).toBeInTheDocument(); const editColumnsBtn = screen.getByTestId('edit-columns'); await act(async () => fireEvent.click(editColumnsBtn)); - const checkbox = container.querySelector('input[type="checkbox"]#checkbox-label-openingId'); + const checkbox = container.querySelector('#checkbox-label-openingNumber'); await act(async () => fireEvent.click(checkbox)); - expect(screen.queryByTestId('Opening Id')).not.toBeInTheDocument(); + expect(screen.queryByTestId('Opening Number')).not.toBeInTheDocument(); }); @@ -220,7 +220,7 @@ describe('OpeningSearchTab', () => { expect(screen.getByTestId('openings-map')).toBeInTheDocument(); }); - it('should display more or less columns when checkboxes are clicked', async () => { + it('should display openingNumber once users clicks the chekbox', async () => { (useOpeningsQuery as vi.Mock).mockReturnValue({ data, isFetching: false }); let container; @@ -245,9 +245,9 @@ describe('OpeningSearchTab', () => { expect(screen.getByTestId('Opening Id')).toBeInTheDocument(); const editColumnsBtn = screen.getByTestId('edit-columns'); await act(async () => fireEvent.click(editColumnsBtn)); - const checkbox = container.querySelector('input[type="checkbox"]#checkbox-label-openingId'); + const checkbox = container.querySelector('input[type="checkbox"]#checkbox-label-openingNumber'); await act(async () => fireEvent.click(checkbox)); - expect(screen.queryByTestId('Opening Id')).not.toBeInTheDocument(); + expect(screen.queryByTestId('Opening number')).toBeInTheDocument(); }); }); \ No newline at end of file diff --git a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx index 46760a00..77b500cd 100644 --- a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx +++ b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx @@ -453,4 +453,41 @@ describe('Search Screen Data table test', () => { }); + it('should call handleCheckboxChange appropriately when the select-all/default-column div is clicked', async () => { + const handleCheckboxChange = vi.fn(); + let container; + + await act(async () => + ({ container } = + render( + + + + + + + + + + + + ))); + expect(container).toBeInTheDocument(); + const selectAllColumn = screen.getByTestId('select-all-column'); + fireEvent.click(selectAllColumn); + expect(handleCheckboxChange).toHaveBeenCalledWith("select-all"); + const selectDefaultColumn = screen.getByTestId('select-default-column'); + fireEvent.click(selectDefaultColumn); + expect(handleCheckboxChange).toHaveBeenCalledWith("select-default"); + }); + }); \ No newline at end of file diff --git a/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/index.tsx b/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/index.tsx index 89d9e9bb..72d7029a 100644 --- a/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/index.tsx +++ b/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/index.tsx @@ -24,7 +24,7 @@ import { MenuItemDivider, Tooltip, MenuItem, - FlexGrid + FlexGrid, } from "@carbon/react"; import * as Icons from "@carbon/icons-react"; import StatusTag from "../../../StatusTag"; @@ -37,16 +37,20 @@ import { convertToCSV, downloadCSV, downloadPDF, - downloadXLSX + downloadXLSX, } from "../../../../utils/fileConversions"; import { useNavigate } from "react-router-dom"; -import { setOpeningFavorite, deleteOpeningFavorite } from '../../../../services/OpeningFavouriteService'; +import { + setOpeningFavorite, + deleteOpeningFavorite, +} from "../../../../services/OpeningFavouriteService"; import { usePostViewedOpening } from "../../../../services/queries/dashboard/dashboardQueries"; import { useNotification } from "../../../../contexts/NotificationProvider"; import TruncatedText from "../../../TruncatedText"; import FriendlyDate from "../../../FriendlyDate"; import ComingSoonModal from "../../../ComingSoonModal"; - +import { Icon } from "@carbon/icons-react"; +import { set } from "date-fns"; interface ISearchScreenDataTable { rows: OpeningsSearch[]; @@ -71,7 +75,7 @@ const SearchScreenDataTable: React.FC = ({ toggleSpatial, showSpatial, totalItems, - setOpeningIds + setOpeningIds, }) => { const { handlePageChange, @@ -84,8 +88,14 @@ const SearchScreenDataTable: React.FC = ({ const [openEdit, setOpenEdit] = useState(false); const [openDownload, setOpenDownload] = useState(false); const [selectedRows, setSelectedRows] = useState([]); // State to store selected rows - const [openingDetails, setOpeningDetails] = useState(''); - const { mutate: markAsViewedOpening, isError, error } = usePostViewedOpening(); + const [openingDetails, setOpeningDetails] = useState(""); + const [columnsSelected, setColumnsSelected] = + useState("select-default"); + const { + mutate: markAsViewedOpening, + isError, + error, + } = usePostViewedOpening(); const navigate = useNavigate(); // This ref is used to calculate the width of the container for each cell @@ -95,14 +105,18 @@ const SearchScreenDataTable: React.FC = ({ const { displayNotification } = useNotification(); useEffect(() => { - const widths = cellRefs.current.map((cell: ICellRefs) => cell.offsetWidth || 0); + const widths = cellRefs.current.map( + (cell: ICellRefs) => cell.offsetWidth || 0 + ); setCellWidths(widths); const handleResize = () => { - const newWidths = cellRefs.current.map((cell: ICellRefs) => cell.offsetWidth || 0); + const newWidths = cellRefs.current.map( + (cell: ICellRefs) => cell.offsetWidth || 0 + ); setCellWidths(newWidths); }; - + window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); @@ -110,17 +124,19 @@ const SearchScreenDataTable: React.FC = ({ useEffect(() => { setInitialItemsPerPage(itemsPerPage); }, [rows, totalItems]); - + // Function to handle row selection changes const handleRowSelectionChanged = (openingId: string) => { setSelectedRows((prevSelectedRows) => { if (prevSelectedRows.includes(openingId)) { // If the row is already selected, remove it from the selected rows - const selectedValues = prevSelectedRows.filter((id) => id !== openingId); + const selectedValues = prevSelectedRows.filter( + (id) => id !== openingId + ); setOpeningIds(selectedValues.map(parseFloat)); return selectedValues; } else { - // If the row is not selected, add it to the selected rows + // If the row is not selected, add it to the selected rows const selectedValues = [...prevSelectedRows, openingId]; setOpeningIds(selectedValues.map(parseFloat)); return selectedValues; @@ -129,55 +145,57 @@ const SearchScreenDataTable: React.FC = ({ }; const handleRowClick = (openingId: string) => { - // Call the mutation to mark as viewed - markAsViewedOpening(openingId, { - onSuccess: () => { - setOpeningDetails(openingId); - }, - onError: (err: any) => { - displayNotification({ - title: 'Unable to process your request', - subTitle: 'Please try again in a few minutes', - type: "error", - onClose: () => {} - }) - } - }); - }; + // Call the mutation to mark as viewed + markAsViewedOpening(openingId, { + onSuccess: () => { + setOpeningDetails(openingId); + }, + onError: (err: any) => { + displayNotification({ + title: "Unable to process your request", + subTitle: "Please try again in a few minutes", + type: "error", + onClose: () => {}, + }); + }, + }); + }; //Function to handle the favourite feature of the opening for a user - const handleFavouriteOpening = async (openingId: string, favorite: boolean) => { - try{ - if(favorite){ + const handleFavouriteOpening = async ( + openingId: string, + favorite: boolean + ) => { + try { + if (favorite) { await deleteOpeningFavorite(parseInt(openingId)); displayNotification({ - title: `Opening Id ${openingId} unfavourited`, - type: 'success', + title: `Opening Id ${openingId} unfavourited`, + type: "success", dismissIn: 8000, - onClose: () => {} + onClose: () => {}, }); - }else{ + } else { await setOpeningFavorite(parseInt(openingId)); displayNotification({ title: `Opening Id ${openingId} favourited`, - subTitle: 'You can follow this opening ID on your dashboard', + subTitle: "You can follow this opening ID on your dashboard", type: "success", buttonLabel: "Go to track openings", onClose: () => { - navigate('/opening?tab=metrics&scrollTo=trackOpenings') - } + navigate("/opening?tab=metrics&scrollTo=trackOpenings"); + }, }); } - } catch (favoritesError) { displayNotification({ - title: 'Unable to process your request', - subTitle: 'Please try again in a few minutes', + title: "Unable to process your request", + subTitle: "Please try again in a few minutes", type: "error", - onClose: () => {} - }) + onClose: () => {}, + }); } - } + }; return ( <> @@ -244,50 +262,84 @@ const SearchScreenDataTable: React.FC = ({

Select Columns you want to see:

- - {headers.map((header, index) => - index > 0 && index % 2 === 1 ? ( // Start from index 1 and handle even-indexed pairs to skip the actions - - - handleCheckboxChange(header.key)} - /> - - {headers[index + 1] && ( - - - handleCheckboxChange(headers[index + 1].key) - } - /> + + {headers.map( + (header, index) => + header && + header.key !== "actions" && ( + + + {header.key === "openingId" ? ( +
+ +

+ {header.header} +

+
+ ) : ( + { + handleCheckboxChange(header.key); + setColumnsSelected("select-custom"); + }} + /> + )}
- )} -
- ) : null +
+ ) )}
- handleCheckboxChange("select-all")} - /> - handleCheckboxChange("select-default")} - /> +
{ + handleCheckboxChange("select-all"); + setColumnsSelected("select-all"); + }} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleCheckboxChange("select-all"); + setColumnsSelected("select-all"); + } + }} + > +

Select all columns

+ {columnsSelected === "select-all" && ( + + )} +
+
{ + handleCheckboxChange("select-default"); + setColumnsSelected("select-default"); + }} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleCheckboxChange("select-default"); + setColumnsSelected("select-default"); + } + }} + > +

Reset columns to default

+ {columnsSelected === "select-default" && ( + + )} +
@@ -343,7 +395,9 @@ const SearchScreenDataTable: React.FC = ({ {headers.map((header) => header.selected ? ( - {header.header} + + {header.header} + ) : null )} @@ -362,12 +416,10 @@ const SearchScreenDataTable: React.FC = ({ ref={(el: never) => (cellRefs.current[i] = el)} key={header.key} className={ - header.key === "actions" && showSpatial - ? "p-0" - : null + header.key === "actions" && showSpatial ? "p-0" : null } - onClick={() =>{ - if(header.key !== "actions"){ + onClick={() => { + if (header.key !== "actions") { handleRowClick(row.openingId); } }} @@ -376,15 +428,15 @@ const SearchScreenDataTable: React.FC = ({ ) : header.key === "actions" ? ( <> - {showSpatial && ( - - {/* Checkbox for selecting rows */} - + {showSpatial && ( + + {/* Checkbox for selecting rows */} + = ({ /> - + )} - {!showSpatial &&( - - { - handleFavouriteOpening(row.openingId,row.favourite) - row.favourite = !row.favourite; + {!showSpatial && ( + + { + handleFavouriteOpening( + row.openingId, + row.favourite + ); + row.favourite = !row.favourite; + }} + /> + + downloadPDF(defaultColumns, [row]) } - } - /> - - downloadPDF(defaultColumns, [row]) - } - /> - { - const csvData = convertToCSV(defaultColumns, [ - row, - ]); - downloadCSV(csvData, "openings-data.csv"); - }} - /> - - + /> + { + const csvData = convertToCSV( + defaultColumns, + [row] + ); + downloadCSV(csvData, "openings-data.csv"); + }} + /> + + )} - - ) : header.header === "Category" ? ( - ) : header.key === 'disturbanceStartDate' ? ( + text={ + row["categoryCode"] + + " - " + + row["categoryDescription"] + } + parentWidth={cellWidths[i]} + /> + ) : header.key === "disturbanceStartDate" ? ( ) : ( row[header.key] @@ -488,7 +554,10 @@ const SearchScreenDataTable: React.FC = ({ /> )} - + ); }; diff --git a/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/styles.scss b/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/styles.scss index badbfad9..453daab6 100644 --- a/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/styles.scss +++ b/frontend/src/components/SilvicultureSearch/Openings/SearchScreenDataTable/styles.scss @@ -52,6 +52,19 @@ } .menu-item{ font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: 0.32px; + font-family: 'BC Sans'; + color: var(--#{vars.$bcgov-prefix}-text-primary); + } + .menu-item-container{ + background-color: var(--#{vars.$bcgov-prefix}-layer-01); + padding: 16px; + } + .menu-item-container:hover{ + background-color: var(--#{vars.$bcgov-prefix}-layer-accent-02); + cursor: pointer; } .checkbox-item .bx--checkbox-label-text{ font-size: 14px; @@ -59,6 +72,9 @@ letter-spacing: 0.16px; font-weight: 400; } + .bx--checkbox-label { + min-width: 300px; + } } .download-column-content{ @@ -153,6 +169,24 @@ } +.dropdown-container { + display: flex; + flex-direction: column; + max-height: 265px; + overflow-y: auto; +} +::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; +} +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + box-shadow: 0 0 1px rgba(255, 255, 255, .5); +} + + + @media only screen and (max-width: 672px) { .#{vars.$bcgov-prefix}--data-table-content { width: 100%; diff --git a/frontend/src/constants/tableConstants.ts b/frontend/src/constants/tableConstants.ts index 66414fe8..bbac9393 100644 --- a/frontend/src/constants/tableConstants.ts +++ b/frontend/src/constants/tableConstants.ts @@ -1,60 +1,41 @@ import { ITableHeader } from "../types/TableHeader"; -export const searchScreenColumns: ITableHeader[] = [ - { - key: 'actions', - header: 'Actions', - selected: true - }, - { - key: 'openingId', - header: 'Opening Id', - selected: true - }, - { - key: 'forestFileId', - header: 'File Id', - selected: true - }, - { - key: 'categoryDescription', - header: 'Category', - selected: true, - elipsis: true - }, - { - key: 'orgUnitName', - header: 'Org unit', - selected: true - }, - { - key: 'statusDescription', - header: 'Status', - selected: true - }, - { - key: 'cuttingPermitId', - header: 'Cutting permit', - selected: true - }, - { - key: 'cutBlockId', - header: 'Cut block', - selected: true - }, - { - key: 'openingGrossAreaHa', - header: 'Gross Area', - selected: true - }, - { - key: 'disturbanceStartDate', - header: 'Disturbance Date', - selected: true - } +const searchScreenColumnDefinitions = [ + { key: 'actions', header: 'Actions' }, + + { key: 'openingId', header: 'Opening Id' }, + { key: 'openingNumber', header: 'Opening number' }, + { key: 'forestFileId', header: 'File Id' }, + { key: 'categoryDescription', header: 'Category', elipsis: true }, + { key: 'orgUnitName', header: 'Org unit' }, + { key: 'statusDescription', header: 'Status' }, + { key: 'clientNumber', header: 'Client number' }, + { key: 'timberMark', header: 'Timber mark' }, + { key: 'cuttingPermitId', header: 'Cutting permit' }, + { key: 'cutBlockId', header: 'Cut block' }, + { key: 'openingGrossAreaHa', header: 'Gross Area' }, + { key: 'disturbanceStartDate', header: 'Disturbance date' }, + { key: 'regenDelayDate', header: 'Regen delay due date' }, + { key: 'earlyFreeGrowingDate', header: 'Free growing due date' }, + { key: 'updateTimestamp', header: 'Update date' }, ]; -// List of column definitions with key and header +export const searchScreenColumns: ITableHeader[] = searchScreenColumnDefinitions.map((col) => ({ + ...col, + selected: [ + 'actions', + 'openingId', + 'forestFileId', + 'categoryDescription', + 'orgUnitName', + 'statusDescription', + 'cuttingPermitId', + 'cutBlockId', + 'openingGrossAreaHa', + 'disturbanceStartDate', + ].includes(col.key), +})); + const recentOpeningsColumnDefinitions = [ { key: 'openingId', header: 'Opening Id' }, { key: 'forestFileId', header: 'File Id' }, @@ -68,8 +49,7 @@ const recentOpeningsColumnDefinitions = [ { key: 'actions', header: 'Actions' }, ]; -// Assign the selected flag to each column (true/false based on your requirements) export const recentOpeningsColumns: ITableHeader[] = recentOpeningsColumnDefinitions.map((col) => ({ ...col, - selected: col.key !== 'disturbanceStartDate', // Assuming 'Disturbance Date' is not selected + selected: col.key !== 'disturbanceStartDate', })); \ No newline at end of file