feat(task-time-logs): enhance time log retrieval and formatting with user timezone
- Integrated user timezone handling in the task time logs API service to ensure accurate time representation. - Added a new utility function to format date/time strings according to the user's profile timezone. - Updated the TimeLogItem component to utilize the new formatting function for displaying timestamps.
This commit is contained in:
@@ -2,6 +2,7 @@ import { API_BASE_URL } from '@/shared/constants';
|
|||||||
import apiClient from '../api-client';
|
import apiClient from '../api-client';
|
||||||
import { IServerResponse } from '@/types/common.types';
|
import { IServerResponse } from '@/types/common.types';
|
||||||
import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types';
|
import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types';
|
||||||
|
import { getUserSession } from '@/utils/session-helper';
|
||||||
|
|
||||||
const rootUrl = `${API_BASE_URL}/task-time-log`;
|
const rootUrl = `${API_BASE_URL}/task-time-log`;
|
||||||
|
|
||||||
@@ -17,7 +18,11 @@ export interface IRunningTimer {
|
|||||||
|
|
||||||
export const taskTimeLogsApiService = {
|
export const taskTimeLogsApiService = {
|
||||||
getByTask: async (id: string): Promise<IServerResponse<ITaskLogViewModel[]>> => {
|
getByTask: async (id: string): Promise<IServerResponse<ITaskLogViewModel[]>> => {
|
||||||
const response = await apiClient.get(`${rootUrl}/task/${id}`);
|
const session = getUserSession();
|
||||||
|
const timezone = session?.timezone_name || 'UTC';
|
||||||
|
const response = await apiClient.get(`${rootUrl}/task/${id}`, {
|
||||||
|
params: { time_zone_name: timezone }
|
||||||
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Button, Divider, Flex, Popconfirm, Typography, Space } from '@/shared/a
|
|||||||
import { colors } from '@/styles/colors';
|
import { colors } from '@/styles/colors';
|
||||||
import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types';
|
import { ITaskLogViewModel } from '@/types/tasks/task-log-view.types';
|
||||||
import SingleAvatar from '@/components/common/single-avatar/single-avatar';
|
import SingleAvatar from '@/components/common/single-avatar/single-avatar';
|
||||||
import { formatDateTimeWithLocale } from '@/utils/format-date-time-with-locale';
|
import { formatDateTimeWithUserTimezone } from '@/utils/format-date-time-with-user-timezone';
|
||||||
import { calculateTimeGap } from '@/utils/calculate-time-gap';
|
import { calculateTimeGap } from '@/utils/calculate-time-gap';
|
||||||
import './time-log-item.css';
|
import './time-log-item.css';
|
||||||
import { taskTimeLogsApiService } from '@/api/tasks/task-time-logs.api.service';
|
import { taskTimeLogsApiService } from '@/api/tasks/task-time-logs.api.service';
|
||||||
@@ -101,7 +101,7 @@ const TimeLogItem = ({ log, onDelete }: TimeLogItemProps) => {
|
|||||||
{renderLoggedByTimer()} {calculateTimeGap(created_at || '')}
|
{renderLoggedByTimer()} {calculateTimeGap(created_at || '')}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
{formatDateTimeWithLocale(created_at || '')}
|
{formatDateTimeWithUserTimezone(created_at || '', currentSession?.timezone_name)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
{renderActionButtons()}
|
{renderActionButtons()}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
|
import { enUS, es, pt } from 'date-fns/locale';
|
||||||
|
import { getLanguageFromLocalStorage } from './language-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a date/time string using the user's profile timezone
|
||||||
|
* This ensures consistency between time logs display and reporting filters
|
||||||
|
*
|
||||||
|
* @param dateString - The date string to format (typically in UTC)
|
||||||
|
* @param userTimezone - The user's timezone from their profile (e.g., 'America/New_York')
|
||||||
|
* @returns Formatted date string in user's timezone
|
||||||
|
*/
|
||||||
|
export const formatDateTimeWithUserTimezone = (
|
||||||
|
dateString: string,
|
||||||
|
userTimezone?: string | null
|
||||||
|
): string => {
|
||||||
|
if (!dateString) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
|
||||||
|
// If timezone is provided, use it for formatting
|
||||||
|
if (userTimezone && userTimezone !== 'UTC') {
|
||||||
|
// Use the browser's toLocaleString with timezone option
|
||||||
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
|
timeZone: userTimezone,
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the appropriate locale
|
||||||
|
const localeString = getLanguageFromLocalStorage();
|
||||||
|
const localeMap = {
|
||||||
|
'en': 'en-US',
|
||||||
|
'es': 'es-ES',
|
||||||
|
'pt': 'pt-PT'
|
||||||
|
};
|
||||||
|
const locale = localeMap[localeString as keyof typeof localeMap] || 'en-US';
|
||||||
|
|
||||||
|
return date.toLocaleString(locale, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to date-fns formatting for UTC or when no timezone
|
||||||
|
const localeString = getLanguageFromLocalStorage();
|
||||||
|
const locale = localeString === 'en' ? enUS : localeString === 'es' ? es : pt;
|
||||||
|
return format(date, 'MMM d, yyyy, h:mm:ss a', { locale });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error formatting date with user timezone:', error);
|
||||||
|
// Fallback to original date string if formatting fails
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a date is "yesterday" in the user's timezone
|
||||||
|
* This is used to ensure consistency with reporting filters
|
||||||
|
*
|
||||||
|
* @param dateString - The date string to check
|
||||||
|
* @param userTimezone - The user's timezone from their profile
|
||||||
|
* @returns true if the date is yesterday in user's timezone
|
||||||
|
*/
|
||||||
|
export const isYesterdayInUserTimezone = (dateString: string, userTimezone?: string | null): boolean => {
|
||||||
|
if (!dateString || !userTimezone) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
|
||||||
|
// Get current date in user's timezone
|
||||||
|
const nowInTimezone = new Date().toLocaleString('en-US', { timeZone: userTimezone });
|
||||||
|
const now = new Date(nowInTimezone);
|
||||||
|
|
||||||
|
// Get yesterday in user's timezone
|
||||||
|
const yesterday = new Date(now);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
// Convert the input date to user's timezone
|
||||||
|
const dateInTimezone = new Date(date.toLocaleString('en-US', { timeZone: userTimezone }));
|
||||||
|
|
||||||
|
// Compare dates (ignoring time)
|
||||||
|
return (
|
||||||
|
dateInTimezone.getFullYear() === yesterday.getFullYear() &&
|
||||||
|
dateInTimezone.getMonth() === yesterday.getMonth() &&
|
||||||
|
dateInTimezone.getDate() === yesterday.getDate()
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking if date is yesterday:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user