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(() => (
+
+
+
+
+ } iconPosition="end">
+ {t('exportButton')}
+
+
+
+ ), [archived, handleArchivedChange, t, dropdownMenuItems]);
+
+ // Memoize the card title to prevent recreation on every render
+ const cardTitle = useMemo(() => , []);
return (
-
-
-
- } iconPosition="end">
- {t('exportButton')}
-
-
-
- }
+ title={pageTitle}
+ children={headerChildren}
/>
- }>
+
);
};
-export default ProjectsReports;
+export default memo(ProjectsReports);