From 3f7b969e4433d75f5163f9d753667102e97cb14d Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Mon, 2 Jun 2025 16:51:29 +0530 Subject: [PATCH] feat(time-report-localization): enhance English, Spanish, and Portuguese translations for time reporting - Added new localization keys for total time logged, expected capacity, team utilization, variance, and related terms in English, Spanish, and Portuguese JSON files. - Updated the Total Time Utilization component to utilize new translations and improve UI elements for better user experience. - Enhanced theme support for card styles and progress indicators based on utilization status. --- .../public/locales/en/time-report.json | 18 +- .../public/locales/es/time-report.json | 20 +- .../public/locales/pt/time-report.json | 20 +- .../total-time-utilization.tsx | 335 +++++++++++++++++- 4 files changed, 373 insertions(+), 20 deletions(-) diff --git a/worklenz-frontend/public/locales/en/time-report.json b/worklenz-frontend/public/locales/en/time-report.json index dc1f1fe0..fd5ee164 100644 --- a/worklenz-frontend/public/locales/en/time-report.json +++ b/worklenz-frontend/public/locales/en/time-report.json @@ -43,6 +43,20 @@ "noData": "No data found", "members": "Members", "searchByMember": "Search by member", - "utilization": "Utilization" - + "utilization": "Utilization", + + "totalTimeLogged": "Total Time Logged", + "expectedCapacity": "Expected Capacity", + "teamUtilization": "Team Utilization", + "variance": "Variance", + "acrossAllTeamMembers": "Across all team members", + "basedOnWorkingSchedule": "Based on working schedule", + "optimal": "Optimal", + "underUtilized": "Under-utilized", + "overUtilized": "Over-utilized", + "overCapacity": "Over capacity", + "underCapacity": "Under capacity", + "considerWorkloadRedistribution": "Consider workload redistribution", + "capacityAvailableForNewProjects": "Capacity available for new projects", + "targetRange": "Target: 90-110%" } diff --git a/worklenz-frontend/public/locales/es/time-report.json b/worklenz-frontend/public/locales/es/time-report.json index a602ec1d..eb523cf3 100644 --- a/worklenz-frontend/public/locales/es/time-report.json +++ b/worklenz-frontend/public/locales/es/time-report.json @@ -40,5 +40,23 @@ "noCategory": "No Categoría", "noProjects": "No se encontraron proyectos", "noTeams": "No se encontraron equipos", - "noData": "No se encontraron datos" + "noData": "No se encontraron datos", + "members": "Miembros", + "searchByMember": "Buscar por miembro", + "utilization": "Utilización", + + "totalTimeLogged": "Tiempo Total Registrado", + "expectedCapacity": "Capacidad Esperada", + "teamUtilization": "Utilización del Equipo", + "variance": "Varianza", + "acrossAllTeamMembers": "En todos los miembros del equipo", + "basedOnWorkingSchedule": "Basado en horario de trabajo", + "optimal": "Óptimo", + "underUtilized": "Sub-utilizado", + "overUtilized": "Sobre-utilizado", + "overCapacity": "Sobre capacidad", + "underCapacity": "Bajo capacidad", + "considerWorkloadRedistribution": "Considerar redistribución de carga de trabajo", + "capacityAvailableForNewProjects": "Capacidad disponible para nuevos proyectos", + "targetRange": "Objetivo: 90-110%" } diff --git a/worklenz-frontend/public/locales/pt/time-report.json b/worklenz-frontend/public/locales/pt/time-report.json index 8d09db4c..f6adb098 100644 --- a/worklenz-frontend/public/locales/pt/time-report.json +++ b/worklenz-frontend/public/locales/pt/time-report.json @@ -40,5 +40,23 @@ "noCategory": "Nenhuma Categoria", "noProjects": "Nenhum projeto encontrado", "noTeams": "Nenhum time encontrado", - "noData": "Nenhum dado encontrado" + "noData": "Nenhum dado encontrado", + "members": "Membros", + "searchByMember": "Pesquisar por membro", + "utilization": "Utilização", + + "totalTimeLogged": "Tempo Total Registrado", + "expectedCapacity": "Capacidade Esperada", + "teamUtilization": "Utilização da Equipe", + "variance": "Variância", + "acrossAllTeamMembers": "Em todos os membros da equipe", + "basedOnWorkingSchedule": "Baseado no horário de trabalho", + "optimal": "Ótimo", + "underUtilized": "Sub-utilizado", + "overUtilized": "Super-utilizado", + "overCapacity": "Acima da capacidade", + "underCapacity": "Abaixo da capacidade", + "considerWorkloadRedistribution": "Considerar redistribuição da carga de trabalho", + "capacityAvailableForNewProjects": "Capacidade disponível para novos projetos", + "targetRange": "Meta: 90-110%" } diff --git a/worklenz-frontend/src/pages/reporting/timeReports/total-time-utilization/total-time-utilization.tsx b/worklenz-frontend/src/pages/reporting/timeReports/total-time-utilization/total-time-utilization.tsx index ce760d4c..a5d1969a 100644 --- a/worklenz-frontend/src/pages/reporting/timeReports/total-time-utilization/total-time-utilization.tsx +++ b/worklenz-frontend/src/pages/reporting/timeReports/total-time-utilization/total-time-utilization.tsx @@ -1,30 +1,333 @@ -import { Card, Flex } from 'antd'; -import React, { useEffect } from 'react'; +import { Card, Flex, Progress, Tooltip } from 'antd'; +import React, { useMemo } from 'react'; import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useTranslation } from 'react-i18next'; import { IRPTTimeTotals } from '@/types/reporting/reporting.types'; +import { ClockCircleOutlined, CalendarOutlined, PercentageOutlined, ArrowUpOutlined, ArrowDownOutlined, CheckCircleOutlined } from '@ant-design/icons'; interface TotalTimeUtilizationProps { totals: IRPTTimeTotals; } + const TotalTimeUtilization: React.FC = ({ totals }) => { + const { t } = useTranslation('time-report'); + const themeMode = useAppSelector(state => state.themeReducer.mode); + const isDark = themeMode === 'dark'; + + const utilizationData = useMemo(() => { + const timeLogged = parseFloat(totals.total_time_logs || '0'); + const estimatedHours = parseFloat(totals.total_estimated_hours || '0'); + const utilizationPercent = parseFloat(totals.total_utilization || '0'); + + // Determine utilization status and color + let status: 'under' | 'optimal' | 'over' = 'optimal'; + let statusColor = '#52c41a'; // Green + let statusIcon = ; + let statusText = t('optimal'); + + if (utilizationPercent < 90) { + status = 'under'; + statusColor = '#faad14'; // Orange + statusIcon = ; + statusText = t('underUtilized'); + } else if (utilizationPercent > 110) { + status = 'over'; + statusColor = '#ff4d4f'; // Red + statusIcon = ; + statusText = t('overUtilized'); + } + + return { + timeLogged, + estimatedHours, + utilizationPercent, + status, + statusColor, + statusIcon, + statusText + }; + }, [totals, t]); + + const getThemeColors = useMemo(() => ({ + cardBackground: isDark ? '#1f1f1f' : '#ffffff', + cardBorder: isDark ? '#303030' : '#f0f0f0', + cardShadow: isDark ? '0 2px 8px rgba(0, 0, 0, 0.3)' : '0 2px 8px rgba(0, 0, 0, 0.06)', + cardHoverShadow: isDark ? '0 4px 16px rgba(0, 0, 0, 0.5)' : '0 4px 16px rgba(0, 0, 0, 0.12)', + primaryText: isDark ? '#ffffff' : '#262626', + secondaryText: isDark ? '#bfbfbf' : '#8c8c8c', + tertiaryText: isDark ? '#8c8c8c' : '#595959', + iconBackgrounds: { + blue: isDark ? '#0f1419' : '#e6f7ff', + green: isDark ? '#0f1b0f' : '#f6ffed', + }, + iconColors: { + blue: isDark ? '#40a9ff' : '#1890ff', + green: isDark ? '#73d13d' : '#52c41a', + }, + progressTrail: isDark ? '#262626' : '#f5f5f5', + varianceBackgrounds: { + positive: isDark ? '#0f1b0f' : '#f6ffed', + negative: isDark ? '#1f0f0f' : '#fff2f0', + }, + varianceColors: { + positive: isDark ? '#73d13d' : '#389e0d', + negative: isDark ? '#ff7875' : '#a8071a', + } + }), [isDark]); + + const cardStyle = { + borderRadius: '8px', + flex: 1, + boxShadow: getThemeColors.cardShadow, + border: `1px solid ${getThemeColors.cardBorder}`, + backgroundColor: getThemeColors.cardBackground, + transition: 'all 0.3s ease', + }; + return ( - -
-
Total Time Logs
-
{totals.total_time_logs}h
-
+ {/* Total Time Logs Card */} + + +
+ +
+
+
+ {t('totalTimeLogged')} +
+
+ {totals.total_time_logs}h +
+
+ {t('acrossAllTeamMembers')} +
+
+
- -
-
Estimated Hours
-
{totals.total_estimated_hours}h
-
+ + {/* Estimated Hours Card */} + + +
+ +
+
+
+ {t('expectedCapacity')} +
+
+ {totals.total_estimated_hours}h +
+
+ {t('basedOnWorkingSchedule')} +
+
+
- -
-
Utilization (%)
-
{totals.total_utilization}%
+ + {/* Utilization Card with Progress */} + + +
+ {utilizationData.statusIcon} +
+
+ +
+ {t('teamUtilization')} +
+ +
+ {utilizationData.statusText} +
+
+
+
+ {totals.total_utilization}% +
+ + + 0% + 90% - 110% + 150%+ + +
+
+
+ + {/* Additional Insights Card */} + +
+
+ {t('variance')} +
+
utilizationData.estimatedHours + ? getThemeColors.varianceColors.negative + : getThemeColors.varianceColors.positive, + lineHeight: 1, + marginBottom: '4px' + }}> + {utilizationData.timeLogged > utilizationData.estimatedHours ? '+' : ''} + {(utilizationData.timeLogged - utilizationData.estimatedHours).toFixed(1)}h +
+
+ {utilizationData.timeLogged > utilizationData.estimatedHours ? t('overCapacity') : t('underCapacity')} +
+
utilizationData.estimatedHours + ? getThemeColors.varianceBackgrounds.negative + : getThemeColors.varianceBackgrounds.positive, + fontSize: 10, + color: utilizationData.timeLogged > utilizationData.estimatedHours + ? getThemeColors.varianceColors.negative + : getThemeColors.varianceColors.positive, + fontWeight: 500 + }}> + {utilizationData.timeLogged > utilizationData.estimatedHours + ? t('considerWorkloadRedistribution') + : t('capacityAvailableForNewProjects') + } +