refactor(task-drawer): update localization keys for created and updated timestamps
- Modified localization JSON files for multiple languages to use double curly braces for variable interpolation in the createdBy and updatedTime fields. - Ensured consistency across English, German, Spanish, Portuguese, Albanian, and Chinese translations for better formatting of dynamic content.
This commit is contained in:
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "Mund të ngarkoni maksimum {count} skedarë",
|
"maxFilesError": "Mund të ngarkoni maksimum {count} skedarë",
|
||||||
"processFilesError": "Dështoi përpunimi i skedarëve",
|
"processFilesError": "Dështoi përpunimi i skedarëve",
|
||||||
"addCommentError": "Ju lutemi shtoni një koment ose bashkëngjitni skedarë",
|
"addCommentError": "Ju lutemi shtoni një koment ose bashkëngjitni skedarë",
|
||||||
"createdBy": "Krijuar {time} nga {user}",
|
"createdBy": "Krijuar {{time}} nga {{user}}",
|
||||||
"updatedTime": "Përditësuar {time}"
|
"updatedTime": "Përditësuar {{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "Kërko sipas emrit",
|
"searchInputPlaceholder": "Kërko sipas emrit",
|
||||||
"pendingInvitation": "Ftesë në Pritje"
|
"pendingInvitation": "Ftesë në Pritje"
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "Sie können maximal {count} Dateien hochladen",
|
"maxFilesError": "Sie können maximal {count} Dateien hochladen",
|
||||||
"processFilesError": "Fehler beim Verarbeiten der Dateien",
|
"processFilesError": "Fehler beim Verarbeiten der Dateien",
|
||||||
"addCommentError": "Bitte fügen Sie einen Kommentar hinzu oder hängen Sie Dateien an",
|
"addCommentError": "Bitte fügen Sie einen Kommentar hinzu oder hängen Sie Dateien an",
|
||||||
"createdBy": "Erstellt {time} von {user}",
|
"createdBy": "Erstellt {{time}} von {{user}}",
|
||||||
"updatedTime": "Aktualisiert {time}"
|
"updatedTime": "Aktualisiert {{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "Nach Name suchen",
|
"searchInputPlaceholder": "Nach Name suchen",
|
||||||
"pendingInvitation": "Ausstehende Einladung"
|
"pendingInvitation": "Ausstehende Einladung"
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "You can only upload a maximum of {count} files",
|
"maxFilesError": "You can only upload a maximum of {count} files",
|
||||||
"processFilesError": "Failed to process files",
|
"processFilesError": "Failed to process files",
|
||||||
"addCommentError": "Please add a comment or attach files",
|
"addCommentError": "Please add a comment or attach files",
|
||||||
"createdBy": "Created {time} by {user}",
|
"createdBy": "Created {{time}} by {{user}}",
|
||||||
"updatedTime": "Updated {time}"
|
"updatedTime": "Updated {{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "Search by name",
|
"searchInputPlaceholder": "Search by name",
|
||||||
"pendingInvitation": "Pending Invitation"
|
"pendingInvitation": "Pending Invitation"
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "Solo puede subir un máximo de {count} archivos",
|
"maxFilesError": "Solo puede subir un máximo de {count} archivos",
|
||||||
"processFilesError": "Error al procesar archivos",
|
"processFilesError": "Error al procesar archivos",
|
||||||
"addCommentError": "Por favor agregue un comentario o adjunte archivos",
|
"addCommentError": "Por favor agregue un comentario o adjunte archivos",
|
||||||
"createdBy": "Creado {time} por {user}",
|
"createdBy": "Creado {{time}} por {{user}}",
|
||||||
"updatedTime": "Actualizado {time}"
|
"updatedTime": "Actualizado {{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "Buscar por nombre",
|
"searchInputPlaceholder": "Buscar por nombre",
|
||||||
"pendingInvitation": "Invitación Pendiente"
|
"pendingInvitation": "Invitación Pendiente"
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "Você pode fazer upload de no máximo {count} arquivos",
|
"maxFilesError": "Você pode fazer upload de no máximo {count} arquivos",
|
||||||
"processFilesError": "Falha ao processar arquivos",
|
"processFilesError": "Falha ao processar arquivos",
|
||||||
"addCommentError": "Por favor adicione um comentário ou anexe arquivos",
|
"addCommentError": "Por favor adicione um comentário ou anexe arquivos",
|
||||||
"createdBy": "Criado {time} por {user}",
|
"createdBy": "Criado {{time}} por {{user}}",
|
||||||
"updatedTime": "Atualizado {time}"
|
"updatedTime": "Atualizado {{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "Pesquisar por nome",
|
"searchInputPlaceholder": "Pesquisar por nome",
|
||||||
"pendingInvitation": "Convite Pendente"
|
"pendingInvitation": "Convite Pendente"
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
"maxFilesError": "您最多只能上传{count}个文件",
|
"maxFilesError": "您最多只能上传{count}个文件",
|
||||||
"processFilesError": "处理文件失败",
|
"processFilesError": "处理文件失败",
|
||||||
"addCommentError": "请添加评论或附加文件",
|
"addCommentError": "请添加评论或附加文件",
|
||||||
"createdBy": "{time}由{user}创建",
|
"createdBy": "{{time}}由{{user}}创建",
|
||||||
"updatedTime": "更新于{time}"
|
"updatedTime": "更新于{{time}}"
|
||||||
},
|
},
|
||||||
"searchInputPlaceholder": "按名称搜索",
|
"searchInputPlaceholder": "按名称搜索",
|
||||||
"pendingInvitation": "待处理邀请"
|
"pendingInvitation": "待处理邀请"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Flex, Form, Mentions, Space, Tooltip, Typography, message } from 'antd';
|
import { Button, Flex, Form, Mentions, Space, Tooltip, Typography, message } from 'antd';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PaperClipOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
import { PaperClipOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
@@ -15,6 +15,7 @@ import logger from '@/utils/errorLogger';
|
|||||||
import taskCommentsApiService from '@/api/tasks/task-comments.api.service';
|
import taskCommentsApiService from '@/api/tasks/task-comments.api.service';
|
||||||
import { teamMembersApiService } from '@/api/team-members/teamMembers.api.service';
|
import { teamMembersApiService } from '@/api/team-members/teamMembers.api.service';
|
||||||
import { ITeamMember } from '@/types/teamMembers/teamMember.types';
|
import { ITeamMember } from '@/types/teamMembers/teamMember.types';
|
||||||
|
import { fromNow } from '@/utils/dateUtils';
|
||||||
|
|
||||||
// Utility function to convert file to base64
|
// Utility function to convert file to base64
|
||||||
const getBase64 = (file: File): Promise<string> => {
|
const getBase64 = (file: File): Promise<string> => {
|
||||||
@@ -66,6 +67,31 @@ const InfoTabFooter = () => {
|
|||||||
// get member list from project members slice
|
// get member list from project members slice
|
||||||
const projectMembersList = useAppSelector(state => state.projectMemberReducer.membersList);
|
const projectMembersList = useAppSelector(state => state.projectMemberReducer.membersList);
|
||||||
|
|
||||||
|
// Calculate relative time values
|
||||||
|
const createdFromNow = useMemo(() => {
|
||||||
|
const createdAt = taskFormViewModel?.task?.created_at;
|
||||||
|
if (!createdAt) return 'N/A';
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fromNow(createdAt);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error formatting created_at:', error, createdAt);
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
}, [taskFormViewModel?.task?.created_at]);
|
||||||
|
|
||||||
|
const updatedFromNow = useMemo(() => {
|
||||||
|
const updatedAt = taskFormViewModel?.task?.updated_at;
|
||||||
|
if (!updatedAt) return 'N/A';
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fromNow(updatedAt);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error formatting updated_at:', error, updatedAt);
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
}, [taskFormViewModel?.task?.updated_at]);
|
||||||
|
|
||||||
// function to handle cancel
|
// function to handle cancel
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
form.resetFields(['comment']);
|
form.resetFields(['comment']);
|
||||||
@@ -454,28 +480,28 @@ const InfoTabFooter = () => {
|
|||||||
<Flex align="center" justify="space-between" style={{ width: '100%', marginTop: 8 }}>
|
<Flex align="center" justify="space-between" style={{ width: '100%', marginTop: 8 }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
taskFormViewModel?.task?.created_from_now
|
createdFromNow !== 'N/A'
|
||||||
? `Created ${taskFormViewModel.task.created_from_now}`
|
? `Created ${createdFromNow}`
|
||||||
: 'N/A'
|
: 'N/A'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
{t('taskInfoTab.comments.createdBy', {
|
{t('taskInfoTab.comments.createdBy', {
|
||||||
time: taskFormViewModel?.task?.created_from_now || 'N/A',
|
time: createdFromNow,
|
||||||
user: taskFormViewModel?.task?.reporter || ''
|
user: taskFormViewModel?.task?.reporter || ''
|
||||||
})}
|
})}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
taskFormViewModel?.task?.updated_from_now
|
updatedFromNow !== 'N/A'
|
||||||
? `Updated ${taskFormViewModel.task.updated_from_now}`
|
? `Updated ${updatedFromNow}`
|
||||||
: 'N/A'
|
: 'N/A'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
{t('taskInfoTab.comments.updatedTime', {
|
{t('taskInfoTab.comments.updatedTime', {
|
||||||
time: taskFormViewModel?.task?.updated_from_now || 'N/A'
|
time: updatedFromNow
|
||||||
})}
|
})}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export interface ITask {
|
|||||||
manual_progress: boolean;
|
manual_progress: boolean;
|
||||||
progress_value: number | null;
|
progress_value: number | null;
|
||||||
weight: number | null;
|
weight: number | null;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectMemberViewModel extends IProjectMember {
|
export interface IProjectMemberViewModel extends IProjectMember {
|
||||||
|
|||||||
Reference in New Issue
Block a user