diff --git a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-reports-filters.tsx b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-reports-filters.tsx index 21f7710d..c37328b3 100644 --- a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-reports-filters.tsx +++ b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-filters/project-reports-filters.tsx @@ -1,4 +1,5 @@ import { Flex } from 'antd'; +import { useMemo, useCallback, memo } from 'react'; import { useTranslation } from 'react-i18next'; import ProjectStatusFilterDropdown from './project-status-filter-dropdown'; import ProjectHealthFilterDropdown from './project-health-filter-dropdown'; @@ -15,26 +16,39 @@ const ProjectsReportsFilters = () => { const { t } = useTranslation('reporting-projects-filters'); const { searchQuery } = useAppSelector(state => state.projectReportsReducer); + // Memoize the search query handler to prevent recreation on every render + const handleSearchQueryChange = useCallback((text: string) => { + dispatch(setSearchQuery(text)); + }, [dispatch]); + + // Memoize the filter dropdowns container to prevent recreation on every render + const filterDropdowns = useMemo(() => ( + + + + + + + ), []); + + // Memoize the right side controls to prevent recreation on every render + const rightControls = useMemo(() => ( + + + + + ), [t, searchQuery, handleSearchQueryChange]); + return ( - - - - - - - - - - - dispatch(setSearchQuery(text))} - /> - + {filterDropdowns} + {rightControls} ); }; -export default ProjectsReportsFilters; +export default memo(ProjectsReportsFilters); diff --git a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx index c77d04fd..7eba1a0e 100644 --- a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx +++ b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports-table/projects-reports-table.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo, useCallback, memo } from 'react'; import { Button, ConfigProvider, Flex, PaginationProps, Table, TableColumnsType } from 'antd'; import { useTranslation } from 'react-i18next'; import { createPortal } from 'react-dom'; @@ -63,10 +63,11 @@ const ProjectsReportsTable = () => { const columnsVisibility = useAppSelector(state => state.projectReportsTableColumnsReducer); - const handleDrawerOpen = (record: IRPTProject) => { + // Memoize the drawer open handler to prevent recreation on every render + const handleDrawerOpen = useCallback((record: IRPTProject) => { setSelectedProject(record); dispatch(toggleProjectReportsDrawer()); - }; + }, [dispatch]); const columns: TableColumnsType = useMemo( () => [ @@ -231,7 +232,7 @@ const ProjectsReportsTable = () => { width: 200, }, ], - [t, order] + [t, order, handleDrawerOpen] ); // filter columns based on the `hidden` state from Redux @@ -240,12 +241,13 @@ const ProjectsReportsTable = () => { [columns, columnsVisibility] ); - const handleTableChange = (pagination: PaginationProps, filters: any, sorter: any) => { + // Memoize the table change handler to prevent recreation on every render + const handleTableChange = useCallback((pagination: PaginationProps, filters: any, sorter: any) => { if (sorter.order) dispatch(setOrder(sorter.order)); if (sorter.field) dispatch(setField(sorter.field)); dispatch(setIndex(pagination.current)); dispatch(setPageSize(pagination.pageSize)); - }; + }, [dispatch]); useEffect(() => { if (!isLoading) dispatch(fetchProjectData()); @@ -268,7 +270,7 @@ const ProjectsReportsTable = () => { return () => { dispatch(resetProjectReports()); }; - }, []); + }, [dispatch]); const tableRowProps = useMemo( () => ({ @@ -292,27 +294,39 @@ const ProjectsReportsTable = () => { [] ); + // Memoize pagination configuration to prevent recreation on every render + const paginationConfig = useMemo(() => ({ + showSizeChanger: true, + defaultPageSize: 10, + total: total, + current: index, + pageSizeOptions: PAGE_SIZE_OPTIONS, + }), [total, index]); + + // Memoize scroll configuration to prevent recreation on every render + const scrollConfig = useMemo(() => ({ x: 'max-content' }), []); + + // Memoize row key function to prevent recreation on every render + const getRowKey = useCallback((record: IRPTProject) => record.id, []); + + // Memoize onRow function to prevent recreation on every render + const getRowProps = useCallback(() => tableRowProps, [tableRowProps]); + return ( record.id} - onRow={() => tableRowProps} + rowKey={getRowKey} + onRow={getRowProps} /> {createPortal(, document.body)} ); }; -export default ProjectsReportsTable; +export default memo(ProjectsReportsTable); diff --git a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports.tsx b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports.tsx index 1129575b..325efe02 100644 --- a/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports.tsx +++ b/worklenz-frontend/src/pages/reporting/projects-reports/projects-reports.tsx @@ -1,4 +1,5 @@ import { Button, Card, Checkbox, Dropdown, Flex, Space, Typography } from 'antd'; +import { useMemo, useCallback, memo } from 'react'; import CustomPageHeader from '@/pages/reporting/page-header/custom-page-header'; import { DownOutlined } from '@ant-design/icons'; import ProjectReportsTable from './projects-reports-table/projects-reports-table'; @@ -20,40 +21,60 @@ const ProjectsReports = () => { const { total, archived } = useAppSelector(state => state.projectReportsReducer); - const handleExcelExport = () => { + // Memoize the title to prevent recalculation on every render + const pageTitle = useMemo(() => { + return `${total === 1 ? `${total} ${t('projectCount')}` : `${total} ${t('projectCountPlural')}`} `; + }, [total, t]); + + // Memoize the Excel export handler to prevent recreation on every render + const handleExcelExport = useCallback(() => { if (currentSession?.team_name) { reportingExportApiService.exportProjects(currentSession.team_name); } - }; + }, [currentSession?.team_name]); + + // Memoize the archived checkbox handler to prevent recreation on every render + const handleArchivedChange = useCallback(() => { + dispatch(setArchived(!archived)); + }, [dispatch, archived]); + + // Memoize the dropdown menu items to prevent recreation on every render + const dropdownMenuItems = useMemo(() => [ + { key: '1', label: t('excelButton'), onClick: handleExcelExport } + ], [t, handleExcelExport]); + + // Memoize the header children to prevent recreation on every render + const headerChildren = useMemo(() => ( + + + + + + + + ), [archived, handleArchivedChange, t, dropdownMenuItems]); + + // Memoize the card title to prevent recreation on every render + const cardTitle = useMemo(() => , []); return ( - - - - - - - } + title={pageTitle} + children={headerChildren} /> - }> + ); }; -export default ProjectsReports; +export default memo(ProjectsReports);