diff --git a/worklenz-frontend/public/locales/alb/home.json b/worklenz-frontend/public/locales/alb/home.json
index 9dd14e4e..81821062 100644
--- a/worklenz-frontend/public/locales/alb/home.json
+++ b/worklenz-frontend/public/locales/alb/home.json
@@ -44,7 +44,9 @@
"refresh": "Rifresko",
"recentActivity": "Aktiviteti i Fundit",
"recentTasks": "Detyrat e Fundit",
- "timeLoggedTasks": "Koha e Regjistruar",
+ "recentTasksSegment": "Detyrat e Fundit",
+ "timeLogged": "Koha e Regjistruar",
+ "timeLoggedSegment": "Koha e Regjistruar",
"noRecentTasks": "Asnjë detyrë e fundit",
"noTimeLoggedTasks": "Asnjë detyrë me kohë të regjistruar",
"activityTag": "Aktiviteti",
@@ -53,6 +55,8 @@
"activitySingular": "aktivitet",
"activityPlural": "aktivitete",
"recentTaskAriaLabel": "Detyrë e fundit:",
- "timeLoggedTaskAriaLabel": "Detyrë me kohë të regjistruar:"
+ "timeLoggedTaskAriaLabel": "Detyrë me kohë të regjistruar:",
+ "errorLoadingRecentTasks": "Gabim në ngarkimin e detyrave të fundit",
+ "errorLoadingTimeLoggedTasks": "Gabim në ngarkimin e detyrave me kohë të regjistruar"
}
}
diff --git a/worklenz-frontend/public/locales/de/home.json b/worklenz-frontend/public/locales/de/home.json
index 03381614..bc9ff9ac 100644
--- a/worklenz-frontend/public/locales/de/home.json
+++ b/worklenz-frontend/public/locales/de/home.json
@@ -44,7 +44,9 @@
"refresh": "Aktualisieren",
"recentActivity": "Aktuelle Aktivitäten",
"recentTasks": "Aktuelle Aufgaben",
- "timeLoggedTasks": "Erfasste Zeit",
+ "recentTasksSegment": "Aktuelle Aufgaben",
+ "timeLogged": "Erfasste Zeit",
+ "timeLoggedSegment": "Erfasste Zeit",
"noRecentTasks": "Keine aktuellen Aufgaben",
"noTimeLoggedTasks": "Keine Aufgaben mit erfasster Zeit",
"activityTag": "Aktivität",
@@ -53,6 +55,8 @@
"activitySingular": "Aktivität",
"activityPlural": "Aktivitäten",
"recentTaskAriaLabel": "Aktuelle Aufgabe:",
- "timeLoggedTaskAriaLabel": "Aufgabe mit erfasster Zeit:"
+ "timeLoggedTaskAriaLabel": "Aufgabe mit erfasster Zeit:",
+ "errorLoadingRecentTasks": "Fehler beim Laden aktueller Aufgaben",
+ "errorLoadingTimeLoggedTasks": "Fehler beim Laden der Zeiterfassung"
}
}
diff --git a/worklenz-frontend/public/locales/en/home.json b/worklenz-frontend/public/locales/en/home.json
index 290316f0..cfd51310 100644
--- a/worklenz-frontend/public/locales/en/home.json
+++ b/worklenz-frontend/public/locales/en/home.json
@@ -44,7 +44,9 @@
"refresh": "Refresh",
"recentActivity": "Recent Activity",
"recentTasks": "Recent Tasks",
- "timeLoggedTasks": "Time Logged",
+ "recentTasksSegment": "Recent Tasks",
+ "timeLogged": "Time Logged",
+ "timeLoggedSegment": "Time Logged",
"noRecentTasks": "No recent tasks",
"noTimeLoggedTasks": "No time logged tasks",
"activityTag": "Activity",
@@ -54,12 +56,7 @@
"activityPlural": "activities",
"recentTaskAriaLabel": "Recent task:",
"timeLoggedTaskAriaLabel": "Time logged task:",
- "Recent Activity": "Recent Activity",
- "Recent Tasks": "Recent Tasks",
- "Time Logged Tasks": "Time Logged Tasks",
- "Error Loading Recent Tasks": "Error loading recent tasks",
- "Error Loading Time Logged Tasks": "Error loading time logged tasks",
- "No Recent Tasks": "No recent tasks",
- "No Time Logged Tasks": "No time logged tasks"
+ "errorLoadingRecentTasks": "Error loading recent tasks",
+ "errorLoadingTimeLoggedTasks": "Error loading time logged tasks"
}
}
diff --git a/worklenz-frontend/public/locales/es/home.json b/worklenz-frontend/public/locales/es/home.json
index 63403089..c2206148 100644
--- a/worklenz-frontend/public/locales/es/home.json
+++ b/worklenz-frontend/public/locales/es/home.json
@@ -43,7 +43,9 @@
"refresh": "Actualizar",
"recentActivity": "Actividad Reciente",
"recentTasks": "Tareas Recientes",
- "timeLoggedTasks": "Tiempo Registrado",
+ "recentTasksSegment": "Tareas Recientes",
+ "timeLogged": "Tiempo Registrado",
+ "timeLoggedSegment": "Tiempo Registrado",
"noRecentTasks": "No hay tareas recientes",
"noTimeLoggedTasks": "No hay tareas con tiempo registrado",
"activityTag": "Actividad",
@@ -52,6 +54,8 @@
"activitySingular": "actividad",
"activityPlural": "actividades",
"recentTaskAriaLabel": "Tarea reciente:",
- "timeLoggedTaskAriaLabel": "Tarea con tiempo registrado:"
+ "timeLoggedTaskAriaLabel": "Tarea con tiempo registrado:",
+ "errorLoadingRecentTasks": "Error al cargar tareas recientes",
+ "errorLoadingTimeLoggedTasks": "Error al cargar tareas con tiempo registrado"
}
}
diff --git a/worklenz-frontend/public/locales/pt/home.json b/worklenz-frontend/public/locales/pt/home.json
index 9a84464c..398f16c7 100644
--- a/worklenz-frontend/public/locales/pt/home.json
+++ b/worklenz-frontend/public/locales/pt/home.json
@@ -43,7 +43,9 @@
"refresh": "Atualizar",
"recentActivity": "Atividade Recente",
"recentTasks": "Tarefas Recentes",
- "timeLoggedTasks": "Tempo Registrado",
+ "recentTasksSegment": "Tarefas Recentes",
+ "timeLogged": "Tempo Registrado",
+ "timeLoggedSegment": "Tempo Registrado",
"noRecentTasks": "Nenhuma tarefa recente",
"noTimeLoggedTasks": "Nenhuma tarefa com tempo registrado",
"activityTag": "Atividade",
@@ -52,6 +54,8 @@
"activitySingular": "atividade",
"activityPlural": "atividades",
"recentTaskAriaLabel": "Tarefa recente:",
- "timeLoggedTaskAriaLabel": "Tarefa com tempo registrado:"
+ "timeLoggedTaskAriaLabel": "Tarefa com tempo registrado:",
+ "errorLoadingRecentTasks": "Erro ao carregar tarefas recentes",
+ "errorLoadingTimeLoggedTasks": "Erro ao carregar tarefas com tempo registrado"
}
}
diff --git a/worklenz-frontend/public/locales/zh/home.json b/worklenz-frontend/public/locales/zh/home.json
index c50e37ca..638e14f8 100644
--- a/worklenz-frontend/public/locales/zh/home.json
+++ b/worklenz-frontend/public/locales/zh/home.json
@@ -44,7 +44,9 @@
"refresh": "刷新",
"recentActivity": "最近活动",
"recentTasks": "最近任务",
- "timeLoggedTasks": "时间记录",
+ "recentTasksSegment": "最近任务",
+ "timeLogged": "时间记录",
+ "timeLoggedSegment": "时间记录",
"noRecentTasks": "没有最近任务",
"noTimeLoggedTasks": "没有时间记录任务",
"activityTag": "活动",
@@ -53,6 +55,8 @@
"activitySingular": "活动",
"activityPlural": "活动",
"recentTaskAriaLabel": "最近任务:",
- "timeLoggedTaskAriaLabel": "时间记录任务:"
+ "timeLoggedTaskAriaLabel": "时间记录任务:",
+ "errorLoadingRecentTasks": "加载最近任务时出错",
+ "errorLoadingTimeLoggedTasks": "加载时间记录任务时出错"
}
}
\ No newline at end of file
diff --git a/worklenz-frontend/src/features/home-page/user-activity.slice.ts b/worklenz-frontend/src/features/home-page/user-activity.slice.ts
index bfe207f2..21bf5876 100644
--- a/worklenz-frontend/src/features/home-page/user-activity.slice.ts
+++ b/worklenz-frontend/src/features/home-page/user-activity.slice.ts
@@ -16,7 +16,7 @@ interface UserActivityState {
}
const initialState: UserActivityState = {
- activeTab: ActivityFeedType.RECENT_TASKS,
+ activeTab: ActivityFeedType.TIME_LOGGED_TASKS,
activities: [],
loading: false,
error: null,
diff --git a/worklenz-frontend/src/pages/home/home-page.tsx b/worklenz-frontend/src/pages/home/home-page.tsx
index 0e47a4b1..aba0890a 100644
--- a/worklenz-frontend/src/pages/home/home-page.tsx
+++ b/worklenz-frontend/src/pages/home/home-page.tsx
@@ -29,7 +29,9 @@ const SIDEBAR_MAX_WIDTH = 400;
// Lazy load heavy components
const TaskDrawer = React.lazy(() => import('@/components/task-drawer/task-drawer'));
-const SurveyPromptModal = React.lazy(() => import('@/components/survey/SurveyPromptModal').then(m => ({ default: m.SurveyPromptModal })));
+const SurveyPromptModal = React.lazy(() =>
+ import('@/components/survey/SurveyPromptModal').then(m => ({ default: m.SurveyPromptModal }))
+);
const HomePage = memo(() => {
const dispatch = useAppDispatch();
@@ -109,18 +111,18 @@ const HomePage = memo(() => {
-
+
+
+
+
+
-
-
-
-
-
+
diff --git a/worklenz-frontend/src/pages/home/todo-list/todo-list.tsx b/worklenz-frontend/src/pages/home/todo-list/todo-list.tsx
index 28054667..d1ab980b 100644
--- a/worklenz-frontend/src/pages/home/todo-list/todo-list.tsx
+++ b/worklenz-frontend/src/pages/home/todo-list/todo-list.tsx
@@ -144,7 +144,7 @@ const TodoList = () => {
-
+
{data?.body.length === 0 ? (
= React.memo(({ tasks })
const dispatch = useAppDispatch();
const { token } = theme.useToken();
- // Enhanced dark mode detection
- const isDarkMode = useMemo(() => {
- return token.colorBgContainer === '#1f1f1f' ||
- token.colorBgBase === '#141414' ||
- token.colorBgElevated === '#1f1f1f' ||
- document.documentElement.getAttribute('data-theme') === 'dark' ||
- document.body.classList.contains('dark');
- }, [token]);
-
const handleTaskClick = useCallback(
(taskId: string, projectId: string) => {
dispatch(setSelectedTaskId(taskId));
@@ -40,143 +31,63 @@ const TaskActivityList: React.FC = React.memo(({ tasks })
[dispatch]
);
- // Enhanced styling with theme support
- const listItemStyles = useMemo(() => ({
- padding: '16px 20px',
- borderBottom: isDarkMode ? '1px solid #404040' : '1px solid #f0f2f5',
- cursor: 'pointer',
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
- borderRadius: '8px',
- margin: '0 0 2px 0',
- background: isDarkMode ? 'transparent' : 'transparent',
- position: 'relative' as const,
- overflow: 'hidden',
- }), [isDarkMode]);
-
- const listItemHoverStyles = useMemo(() => ({
- background: isDarkMode
- ? 'linear-gradient(135deg, #2a2a2a 0%, #353535 100%)'
- : 'linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%)',
- borderColor: isDarkMode ? '#505050' : '#d1d9e6',
- transform: 'translateY(-1px)',
- boxShadow: isDarkMode
- ? '0 4px 16px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2)'
- : '0 4px 16px rgba(24, 144, 255, 0.15), 0 1px 4px rgba(0, 0, 0, 0.1)',
- }), [isDarkMode]);
-
- const iconStyles = useMemo(() => ({
- color: isDarkMode ? '#40a9ff' : '#1890ff',
- fontSize: '16px',
- padding: '8px',
- borderRadius: '6px',
- background: isDarkMode
- ? 'linear-gradient(135deg, #1a2332 0%, #2a3441 100%)'
- : 'linear-gradient(135deg, #e6f7ff 0%, #f0f8ff 100%)',
- border: isDarkMode ? '1px solid #40a9ff20' : '1px solid #1890ff20',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- minWidth: '32px',
- minHeight: '32px',
- }), [isDarkMode]);
-
- const taskNameStyles = useMemo(() => ({
- color: isDarkMode ? '#ffffff' : '#1f2937',
- fontSize: '15px',
- fontWeight: 600,
- lineHeight: '1.4',
- }), [isDarkMode]);
-
- const tagStyles = useMemo(() => ({
- background: isDarkMode
- ? 'linear-gradient(135deg, #1e40af 0%, #3b82f6 100%)'
- : 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)',
- color: isDarkMode ? '#ffffff' : '#1e40af',
- border: isDarkMode ? '1px solid #3b82f6' : '1px solid #93c5fd',
- borderRadius: '6px',
- fontSize: '11px',
- fontWeight: 600,
- padding: '2px 8px',
- textTransform: 'uppercase' as const,
- letterSpacing: '0.5px',
- }), [isDarkMode]);
-
- const metaTextStyles = useMemo(() => ({
- color: isDarkMode ? '#9ca3af' : '#6b7280',
- fontSize: '13px',
- fontWeight: 500,
- }), [isDarkMode]);
-
- const timeTextStyles = useMemo(() => ({
- color: isDarkMode ? '#8c8c8c' : '#9ca3af',
- fontSize: '12px',
- fontWeight: 400,
- }), [isDarkMode]);
-
- const activityCountStyles = useMemo(() => ({
- color: isDarkMode ? '#10b981' : '#059669',
- fontSize: '12px',
- fontWeight: 600,
- background: isDarkMode
- ? 'linear-gradient(135deg, #064e3b20 0%, #065f4620 100%)'
- : 'linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%)',
- padding: '2px 6px',
- borderRadius: '4px',
- border: isDarkMode ? '1px solid #065f4640' : '1px solid #a7f3d040',
- }), [isDarkMode]);
-
- return (
- (
- handleTaskClick(item.task_id, item.project_id)}
- style={listItemStyles}
- onMouseEnter={(e) => {
- Object.assign(e.currentTarget.style, listItemHoverStyles);
+ const columns = [
+ {
+ key: 'task',
+ render: (record: IUserRecentTask) => (
+ {
- Object.assign(e.currentTarget.style, listItemStyles);
- }}
- aria-label={`${t('recentTaskAriaLabel')} ${item.task_name}`}
+ onClick={() => handleTaskClick(record.task_id, record.project_id)}
+ aria-label={`${t('tasks.recentTaskAriaLabel')} ${record.task_name}`}
>
-
-
-
+
+
+
+
+
+
+ {record.task_name}
+
-
-
-
- {item.task_name}
+
+
+ {record.project_name}
+
+
+
+ {fromNow(record.last_activity_at)}
-
- {t('activityTag')}
-
-
-
-
- {item.project_name}
-
-
-
- {item.activity_count} {item.activity_count === 1 ? t('activitySingular') : t('activityPlural')}
-
-
-
- {moment(item.last_activity_at).fromNow()}
-
-
-
-
+
-
- )}
+
+ ),
+ },
+ ];
+
+ return (
+
);
});
diff --git a/worklenz-frontend/src/pages/home/user-activity-feed/time-logged-task-list.tsx b/worklenz-frontend/src/pages/home/user-activity-feed/time-logged-task-list.tsx
index 0185c2c8..b436b72b 100644
--- a/worklenz-frontend/src/pages/home/user-activity-feed/time-logged-task-list.tsx
+++ b/worklenz-frontend/src/pages/home/user-activity-feed/time-logged-task-list.tsx
@@ -1,9 +1,9 @@
-import React, { useCallback, useMemo } from 'react';
-import { List, Typography, Tag, Tooltip, Space, theme } from 'antd';
-import { ClockCircleOutlined } from '@ant-design/icons';
-import moment from 'moment';
+import React, { useCallback } from 'react';
+import { Table, Typography, Tag, Tooltip, Space, theme } from '@/shared/antd-imports';
+import { ClockCircleOutlined } from '@/shared/antd-imports';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
+import { fromNow, formatDate } from '@/utils/dateUtils';
import {
setSelectedTaskId,
setShowTaskDrawer,
@@ -22,15 +22,6 @@ const TimeLoggedTaskList: React.FC
= React.memo(({ task
const dispatch = useAppDispatch();
const { token } = theme.useToken();
- // Enhanced dark mode detection
- const isDarkMode = useMemo(() => {
- return token.colorBgContainer === '#1f1f1f' ||
- token.colorBgBase === '#141414' ||
- token.colorBgElevated === '#1f1f1f' ||
- document.documentElement.getAttribute('data-theme') === 'dark' ||
- document.body.classList.contains('dark');
- }, [token]);
-
const handleTaskClick = useCallback(
(taskId: string, projectId: string) => {
dispatch(setSelectedTaskId(taskId));
@@ -40,160 +31,134 @@ const TimeLoggedTaskList: React.FC = React.memo(({ task
[dispatch]
);
- // Enhanced styling with theme support
- const listItemStyles = useMemo(() => ({
- padding: '16px 20px',
- borderBottom: isDarkMode ? '1px solid #404040' : '1px solid #f0f2f5',
- cursor: 'pointer',
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
- borderRadius: '8px',
- margin: '0 0 2px 0',
- background: isDarkMode ? 'transparent' : 'transparent',
- position: 'relative' as const,
- overflow: 'hidden',
- }), [isDarkMode]);
-
- const listItemHoverStyles = useMemo(() => ({
- background: isDarkMode
- ? 'linear-gradient(135deg, #2a2a2a 0%, #353535 100%)'
- : 'linear-gradient(135deg, #f6ffed 0%, #f0fff4 100%)',
- borderColor: isDarkMode ? '#505050' : '#b7eb8f',
- transform: 'translateY(-1px)',
- boxShadow: isDarkMode
- ? '0 4px 16px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2)'
- : '0 4px 16px rgba(82, 196, 26, 0.15), 0 1px 4px rgba(0, 0, 0, 0.1)',
- }), [isDarkMode]);
-
- const iconStyles = useMemo(() => ({
- color: isDarkMode ? '#73d13d' : '#52c41a',
- fontSize: '16px',
- padding: '8px',
- borderRadius: '6px',
- background: isDarkMode
- ? 'linear-gradient(135deg, #1b2918 0%, #273622 100%)'
- : 'linear-gradient(135deg, #f6ffed 0%, #f0fff4 100%)',
- border: isDarkMode ? '1px solid #52c41a20' : '1px solid #52c41a20',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- minWidth: '32px',
- minHeight: '32px',
- }), [isDarkMode]);
-
- const taskNameStyles = useMemo(() => ({
- color: isDarkMode ? '#ffffff' : '#1f2937',
- fontSize: '15px',
- fontWeight: 600,
- lineHeight: '1.4',
- }), [isDarkMode]);
-
- const timeLogTagStyles = useMemo(() => ({
- background: isDarkMode
- ? 'linear-gradient(135deg, #365314 0%, #4d7c0f 100%)'
- : 'linear-gradient(135deg, #f0fff4 0%, #d9f7be 100%)',
- color: isDarkMode ? '#ffffff' : '#365314',
- border: isDarkMode ? '1px solid #4d7c0f' : '1px solid #95de64',
- borderRadius: '6px',
- fontSize: '11px',
- fontWeight: 600,
- padding: '2px 8px',
- textTransform: 'uppercase' as const,
- letterSpacing: '0.5px',
- }), [isDarkMode]);
-
- const timerTagStyles = useMemo(() => ({
- background: isDarkMode
- ? 'linear-gradient(135deg, #0f766e 0%, #14b8a6 100%)'
- : 'linear-gradient(135deg, #f0fdfa 0%, #ccfbf1 100%)',
- color: isDarkMode ? '#ffffff' : '#0f766e',
- border: isDarkMode ? '1px solid #14b8a6' : '1px solid #5eead4',
- borderRadius: '6px',
- fontSize: '10px',
- fontWeight: 600,
- padding: '1px 6px',
- }), [isDarkMode]);
-
- const metaTextStyles = useMemo(() => ({
- color: isDarkMode ? '#9ca3af' : '#6b7280',
- fontSize: '13px',
- fontWeight: 500,
- }), [isDarkMode]);
-
- const timeTextStyles = useMemo(() => ({
- color: isDarkMode ? '#8c8c8c' : '#9ca3af',
- fontSize: '12px',
- fontWeight: 400,
- }), [isDarkMode]);
-
- const timeLoggedStyles = useMemo(() => ({
- color: isDarkMode ? '#73d13d' : '#52c41a',
- fontSize: '13px',
- fontWeight: 700,
- background: isDarkMode
- ? 'linear-gradient(135deg, #1b291820 0%, #27362220 100%)'
- : 'linear-gradient(135deg, #f6ffed 0%, #f0fff4 100%)',
- padding: '4px 8px',
- borderRadius: '6px',
- border: isDarkMode ? '1px solid #52c41a40' : '1px solid #b7eb8f40',
- }), [isDarkMode]);
+ const columns = [
+ {
+ key: 'task',
+ render: (record: IUserTimeLoggedTask) => (
+ handleTaskClick(record.task_id, record.project_id)}
+ aria-label={`${t('tasks.timeLoggedTaskAriaLabel')} ${record.task_name}`}
+ >
+ {/* Clock Icon */}
+
+
+
+
+ {/* Main Content */}
+
+ {/* Task Name */}
+
+
+ {record.task_name}
+
+
+
+ {/* Project Name */}
+
+ {record.project_name}
+
+
+
+ {/* Right Side - Time and Status */}
+
+ {/* Time Logged */}
+
+
+ {record.total_time_logged_string}
+
+ {record.logged_by_timer && (
+
+ {t('tasks.timerTag')}
+
+ )}
+
+
+ {/* Time Ago */}
+
+
+ {fromNow(record.last_logged_at)}
+
+
+
+
+ ),
+ },
+ ];
return (
- (
- handleTaskClick(item.task_id, item.project_id)}
- style={listItemStyles}
- onMouseEnter={(e) => {
- Object.assign(e.currentTarget.style, listItemHoverStyles);
- }}
- onMouseLeave={(e) => {
- Object.assign(e.currentTarget.style, listItemStyles);
- }}
- aria-label={`${t('timeLoggedTaskAriaLabel')} ${item.task_name}`}
- >
-
-
-
-
-
-
-
- {item.task_name}
-
-
- {t('timeLogTag')}
-
-
-
-
- {item.project_name}
-
-
-
- {item.total_time_logged_string}
-
- {item.logged_by_timer && (
-
- {t('timerTag')}
-
- )}
-
-
- {moment(item.last_logged_at).fromNow()}
-
-
-
-
-
-
-
- )}
+ columns={columns}
+ rowKey="task_id"
+ showHeader={false}
+ pagination={false}
+ size="small"
/>
);
});
diff --git a/worklenz-frontend/src/pages/home/user-activity-feed/user-activity-feed.tsx b/worklenz-frontend/src/pages/home/user-activity-feed/user-activity-feed.tsx
index fa014202..b0b57ded 100644
--- a/worklenz-frontend/src/pages/home/user-activity-feed/user-activity-feed.tsx
+++ b/worklenz-frontend/src/pages/home/user-activity-feed/user-activity-feed.tsx
@@ -1,6 +1,6 @@
import React, { useMemo, useCallback, useEffect } from 'react';
-import { Card, Segmented, Skeleton, Empty, Typography, Alert } from 'antd';
-import { ClockCircleOutlined, UnorderedListOutlined } from '@ant-design/icons';
+import { Card, Segmented, Skeleton, Empty, Typography, Alert, Button, Tooltip } from '@/shared/antd-imports';
+import { ClockCircleOutlined, UnorderedListOutlined, SyncOutlined } from '@/shared/antd-imports';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
@@ -54,11 +54,12 @@ const UserActivityFeed: React.FC = () => {
}
// If it's an object with a data property (common API pattern)
if (recentTasksData && typeof recentTasksData === 'object' && 'data' in recentTasksData) {
- return Array.isArray(recentTasksData.data) ? recentTasksData.data : [];
+ const data = (recentTasksData as any).data;
+ return Array.isArray(data) ? data : [];
}
// If it's a different object structure, try to extract tasks
if (recentTasksData && typeof recentTasksData === 'object') {
- const possibleArrays = Object.values(recentTasksData).filter(Array.isArray);
+ const possibleArrays = Object.values(recentTasksData as any).filter(Array.isArray);
return possibleArrays.length > 0 ? possibleArrays[0] : [];
}
return [];
@@ -72,11 +73,12 @@ const UserActivityFeed: React.FC = () => {
}
// If it's an object with a data property (common API pattern)
if (timeLoggedTasksData && typeof timeLoggedTasksData === 'object' && 'data' in timeLoggedTasksData) {
- return Array.isArray(timeLoggedTasksData.data) ? timeLoggedTasksData.data : [];
+ const data = (timeLoggedTasksData as any).data;
+ return Array.isArray(data) ? data : [];
}
// If it's a different object structure, try to extract tasks
if (timeLoggedTasksData && typeof timeLoggedTasksData === 'object') {
- const possibleArrays = Object.values(timeLoggedTasksData).filter(Array.isArray);
+ const possibleArrays = Object.values(timeLoggedTasksData as any).filter(Array.isArray);
return possibleArrays.length > 0 ? possibleArrays[0] : [];
}
return [];
@@ -85,21 +87,21 @@ const UserActivityFeed: React.FC = () => {
const segmentOptions = useMemo(
() => [
{
- value: ActivityFeedType.RECENT_TASKS,
+ value: ActivityFeedType.TIME_LOGGED_TASKS,
label: (
-
-
- {t('Recent Tasks')}
-
+
+
+ {t('tasks.timeLoggedSegment')}
+
),
},
{
- value: ActivityFeedType.TIME_LOGGED_TASKS,
+ value: ActivityFeedType.RECENT_TASKS,
label: (
-
-
- {t('Time Logged Tasks')}
-
+
+
+ {t('tasks.recentTasksSegment')}
+
),
},
],
@@ -122,41 +124,77 @@ const UserActivityFeed: React.FC = () => {
}
}, [activeTab, refetchRecentTasks, refetchTimeLoggedTasks]);
- const renderContent = () => {
- if (activeTab === ActivityFeedType.RECENT_TASKS) {
- if (loadingRecentTasks) {
- return ;
- }
- if (recentTasksError) {
- return ;
- }
- if (recentTasks.length === 0) {
- return ;
- }
- return ;
+ const handleRefresh = useCallback(() => {
+ if (activeTab === ActivityFeedType.TIME_LOGGED_TASKS) {
+ refetchTimeLoggedTasks();
} else {
+ refetchRecentTasks();
+ }
+ }, [activeTab, refetchRecentTasks, refetchTimeLoggedTasks]);
+
+ const isLoading = activeTab === ActivityFeedType.TIME_LOGGED_TASKS ? loadingTimeLoggedTasks : loadingRecentTasks;
+ const currentCount = activeTab === ActivityFeedType.TIME_LOGGED_TASKS ? timeLoggedTasks.length : recentTasks.length;
+
+ const renderContent = () => {
+ if (activeTab === ActivityFeedType.TIME_LOGGED_TASKS) {
if (loadingTimeLoggedTasks) {
return ;
}
if (timeLoggedTasksError) {
- return ;
+ return ;
}
if (timeLoggedTasks.length === 0) {
- return ;
+ return ;
}
- return ;
+ return (
+
+
+
+ );
+ } else if (activeTab === ActivityFeedType.RECENT_TASKS) {
+ if (loadingRecentTasks) {
+ return ;
+ }
+ if (recentTasksError) {
+ return ;
+ }
+ if (recentTasks.length === 0) {
+ return ;
+ }
+ return (
+
+
+
+ );
}
+ return null;
};
return (
-
-
-
-
+
+ {t('tasks.recentActivity')} ({currentCount})
+
+ }
+ extra={
+
+ }
+ onClick={handleRefresh}
+ />
+
+ }
+ style={{ width: '100%' }}
+ >
+
{renderContent()}
);
diff --git a/worklenz-frontend/src/utils/dateUtils.ts b/worklenz-frontend/src/utils/dateUtils.ts
index 4acddc4d..ad22c911 100644
--- a/worklenz-frontend/src/utils/dateUtils.ts
+++ b/worklenz-frontend/src/utils/dateUtils.ts
@@ -1,32 +1,59 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
+import localizedFormat from 'dayjs/plugin/localizedFormat';
+import 'dayjs/locale/de';
+import 'dayjs/locale/es';
+import 'dayjs/locale/pt';
+import 'dayjs/locale/zh-cn';
+import { getLanguageFromLocalStorage } from './language-utils';
-// Initialize the relativeTime plugin
+// Initialize plugins
dayjs.extend(relativeTime);
+dayjs.extend(localizedFormat);
-/**
- * Formats a date to a relative time string (e.g., "2 hours ago", "a day ago")
- * This mimics the Angular fromNow pipe functionality
- *
- * @param date - The date to format (string, Date, or dayjs object)
- * @returns A string representing the relative time
- */
-export const fromNow = (date: string | Date | dayjs.Dayjs): string => {
- if (!date) return '';
- return dayjs(date).fromNow();
+// Map application languages to dayjs locales
+const getLocaleFromLanguage = (language: string): string => {
+ const localeMap: Record = {
+ 'en': 'en',
+ 'de': 'de',
+ 'es': 'es',
+ 'pt': 'pt',
+ 'alb': 'en', // Albanian not supported by dayjs, fallback to English
+ 'zh': 'zh-cn'
+ };
+ return localeMap[language] || 'en';
};
/**
- * Formats a date to a specific format
+ * Formats a date to a relative time string (e.g., "2 hours ago", "a day ago")
+ * This mimics the Angular fromNow pipe functionality with locale support
+ *
+ * @param date - The date to format (string, Date, or dayjs object)
+ * @param language - Optional language override (defaults to stored language)
+ * @returns A string representing the relative time
+ */
+export const fromNow = (date: string | Date | dayjs.Dayjs, language?: string): string => {
+ if (!date) return '';
+ const currentLanguage = language || getLanguageFromLocalStorage();
+ const locale = getLocaleFromLanguage(currentLanguage);
+ return dayjs(date).locale(locale).fromNow();
+};
+
+/**
+ * Formats a date to a specific format with locale support
*
* @param date - The date to format (string, Date, or dayjs object)
* @param format - The format string (default: 'YYYY-MM-DD')
+ * @param language - Optional language override (defaults to stored language)
* @returns A formatted date string
*/
export const formatDate = (
date: string | Date | dayjs.Dayjs,
- format: string = 'YYYY-MM-DD'
+ format: string = 'YYYY-MM-DD',
+ language?: string
): string => {
if (!date) return '';
- return dayjs(date).format(format);
+ const currentLanguage = language || getLanguageFromLocalStorage();
+ const locale = getLocaleFromLanguage(currentLanguage);
+ return dayjs(date).locale(locale).format(format);
};