e.stopPropagation()}>
+
- {visibleMembers.map((member, index) => renderAvatar(member, index))}
+ {avatarElements}
);
-};
+});
+
+Avatars.displayName = 'Avatars';
export default Avatars;
diff --git a/worklenz-frontend/src/pages/reporting/overview-reports/overview-reports.tsx b/worklenz-frontend/src/pages/reporting/overview-reports/overview-reports.tsx
index 3ca14c17..ea91b756 100644
--- a/worklenz-frontend/src/pages/reporting/overview-reports/overview-reports.tsx
+++ b/worklenz-frontend/src/pages/reporting/overview-reports/overview-reports.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from 'react';
+import { useEffect, useCallback, useMemo } from 'react';
import { Button, Card, Checkbox, Flex, Typography } from 'antd';
import { useTranslation } from 'react-i18next';
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
@@ -25,29 +25,37 @@ const OverviewReports = () => {
trackMixpanelEvent(evt_reporting_overview);
}, [trackMixpanelEvent]);
- const handleArchiveToggle = () => {
+ const handleArchiveToggle = useCallback(() => {
dispatch(toggleIncludeArchived());
- };
+ }, [dispatch]);
+
+ // Memoize the header children to prevent unnecessary re-renders
+ const headerChildren = useMemo(() => (
+
+ ), [handleArchiveToggle, includeArchivedProjects, t]);
+
+ // Memoize the teams text to prevent unnecessary re-renders
+ const teamsText = useMemo(() => (
+
+ {t('teamsText')}
+
+ ), [t]);
return (
-
- {t('includeArchivedButton')}
-
- }
+ children={headerChildren}
/>
-
- {t('teamsText')}
-
+ {teamsText}
diff --git a/worklenz-frontend/src/pages/reporting/overview-reports/overview-stat-card.tsx b/worklenz-frontend/src/pages/reporting/overview-reports/overview-stat-card.tsx
index d437f5cb..418f676a 100644
--- a/worklenz-frontend/src/pages/reporting/overview-reports/overview-stat-card.tsx
+++ b/worklenz-frontend/src/pages/reporting/overview-reports/overview-stat-card.tsx
@@ -1,32 +1,151 @@
-import { ReactNode } from 'react';
-import { Card, Flex, Typography } from 'antd';
+import { Card, Flex, Typography, theme } from 'antd';
+import React, { useMemo } from 'react';
-type InsightCardProps = {
- icon: ReactNode;
+interface InsightCardProps {
+ icon: React.ReactNode;
title: string;
- children: ReactNode;
+ children: React.ReactNode;
loading?: boolean;
-};
+}
+
+const OverviewStatCard = React.memo(({ icon, title, children, loading = false }: InsightCardProps) => {
+ const { token } = theme.useToken();
+ // Better dark mode detection using multiple token properties
+ const isDarkMode = token.colorBgContainer === '#1f1f1f' ||
+ token.colorBgBase === '#141414' ||
+ token.colorBgElevated === '#1f1f1f' ||
+ document.documentElement.getAttribute('data-theme') === 'dark' ||
+ document.body.classList.contains('dark');
+
+ // Memoize enhanced card styles with dark mode support
+ const cardStyles = useMemo(() => ({
+ body: {
+ padding: '24px',
+ background: isDarkMode
+ ? '#1f1f1f !important'
+ : '#ffffff !important',
+ }
+ }), [isDarkMode]);
+
+ // Memoize card container styles with dark mode support
+ const cardContainerStyle = useMemo(() => ({
+ width: '100%',
+ borderRadius: '0px',
+ border: isDarkMode
+ ? '1px solid #303030'
+ : '1px solid #f0f0f0',
+ boxShadow: isDarkMode
+ ? '0 2px 8px rgba(0, 0, 0, 0.3)'
+ : '0 2px 8px rgba(0, 0, 0, 0.06)',
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ overflow: 'hidden',
+ position: 'relative' as const,
+ cursor: 'default',
+ backgroundColor: isDarkMode ? '#1f1f1f !important' : '#ffffff !important',
+ }), [isDarkMode]);
+
+ // Memoize icon container styles with dark mode support
+ const iconContainerStyle = useMemo(() => ({
+ padding: '12px',
+ borderRadius: '0px',
+ background: isDarkMode
+ ? '#2a2a2a'
+ : '#f8f9ff',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ minWidth: '64px',
+ minHeight: '64px',
+ boxShadow: isDarkMode
+ ? '0 2px 4px rgba(24, 144, 255, 0.2)'
+ : '0 2px 4px rgba(24, 144, 255, 0.1)',
+ border: isDarkMode
+ ? '1px solid #404040'
+ : '1px solid rgba(24, 144, 255, 0.1)',
+ }), [isDarkMode]);
+
+ // Memoize title styles with dark mode support
+ const titleStyle = useMemo(() => ({
+ fontSize: '18px',
+ fontWeight: 600,
+ color: isDarkMode ? '#ffffff !important' : '#262626 !important',
+ marginBottom: '8px',
+ lineHeight: '1.4',
+ }), [isDarkMode]);
+
+ // Memoize decorative element styles with dark mode support
+ const decorativeStyle = useMemo(() => ({
+ position: 'absolute' as const,
+ top: 0,
+ right: 0,
+ width: '60px',
+ height: '60px',
+ background: isDarkMode
+ ? 'linear-gradient(135deg, rgba(24, 144, 255, 0.15) 0%, rgba(24, 144, 255, 0.08) 100%)'
+ : 'linear-gradient(135deg, rgba(24, 144, 255, 0.05) 0%, rgba(24, 144, 255, 0.02) 100%)',
+ opacity: isDarkMode ? 0.8 : 0.6,
+ clipPath: 'polygon(100% 0%, 0% 100%, 100% 100%)',
+ }), [isDarkMode]);
-const OverviewStatCard = ({ icon, title, children, loading = false }: InsightCardProps) => {
return (
-
-
- {icon}
+
+
+
+ {icon}
+
-
- {title}
+
+
+ {title}
+
- <>{children}>
+
+ {children}
+
+
-
-
+
+ {/* Decorative element */}
+
+
+
);
-};
+});
+
+OverviewStatCard.displayName = 'OverviewStatCard';
export default OverviewStatCard;
diff --git a/worklenz-frontend/src/pages/reporting/overview-reports/overview-stats.tsx b/worklenz-frontend/src/pages/reporting/overview-reports/overview-stats.tsx
index fd2a4f5c..9c889d3d 100644
--- a/worklenz-frontend/src/pages/reporting/overview-reports/overview-stats.tsx
+++ b/worklenz-frontend/src/pages/reporting/overview-reports/overview-stats.tsx
@@ -1,5 +1,5 @@
-import { Flex, Typography } from 'antd';
-import React, { useEffect, useState } from 'react';
+import { Flex, Typography, theme } from 'antd';
+import React, { useEffect, useState, useCallback, useMemo } from 'react';
import OverviewStatCard from './overview-stat-card';
import { BankOutlined, FileOutlined, UsergroupAddOutlined } from '@ant-design/icons';
import { colors } from '@/styles/colors';
@@ -12,11 +12,12 @@ const OverviewStats = () => {
const [stats, setStats] = useState