From 40cd82a2e69e29e783a2e803bbff548e720d1e55 Mon Sep 17 00:00:00 2001 From: Chris Veleris Date: Sun, 27 Jul 2025 16:53:21 +0300 Subject: [PATCH] Update detailed view for tasks --- frontend/components/Task/TaskDetails.tsx | 119 +++++++++++----------- frontend/components/Task/TaskTimeline.tsx | 50 ++++++++- frontend/utils/taskEventService.ts | 4 +- public/locales/en/translation.json | 8 ++ 4 files changed, 117 insertions(+), 64 deletions(-) diff --git a/frontend/components/Task/TaskDetails.tsx b/frontend/components/Task/TaskDetails.tsx index 7ced37e..06c810c 100644 --- a/frontend/components/Task/TaskDetails.tsx +++ b/frontend/components/Task/TaskDetails.tsx @@ -9,7 +9,7 @@ import { CalendarIcon, ExclamationTriangleIcon, ArrowPathIcon, - ClockIcon, + ListBulletIcon, } from '@heroicons/react/24/outline'; import ConfirmDialog from '../Shared/ConfirmDialog'; import TaskModal from './TaskModal'; @@ -28,7 +28,7 @@ import { useToast } from '../Shared/ToastContext'; import TaskPriorityIcon from './TaskPriorityIcon'; import LoadingScreen from '../Shared/LoadingScreen'; import MarkdownRenderer from '../Shared/MarkdownRenderer'; -import TimelinePanel from './TimelinePanel'; +import TaskTimeline from './TaskTimeline'; const TaskDetails: React.FC = () => { const { uuid } = useParams<{ uuid: string }>(); @@ -46,7 +46,6 @@ const TaskDetails: React.FC = () => { const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [taskToDelete, setTaskToDelete] = useState(null); - const [isTimelineExpanded, setIsTimelineExpanded] = useState(false); // Date and recurrence formatting functions (from TaskHeader) const formatDueDate = (dueDate: string) => { @@ -254,7 +253,7 @@ const TaskDetails: React.FC = () => { onToggleCompletion={handleToggleCompletion} />
-

+

{task.name}

{/* Project, tags, due date, and recurrence under title */} @@ -337,29 +336,12 @@ const TaskDetails: React.FC = () => { )}
-
- +
- {/* Content - Two column layout for notes and subtasks */} - {(task.note || subtasks.length > 0) && ( -
-
- {/* Notes Column */} - {task.note && ( -
-

- {t('task.notes', 'Notes')} -

+ {/* Content - Two column layout */} +
+
+ {/* Left Column - Notes and Subtasks */} +
+ {/* Notes Section - Always Visible */} +
+

+ {t('task.notes', 'Notes')} +

+ {task.note ? (
-
- )} + ) : ( +
+
+ + + {t('task.noNotes', 'No notes added yet')} + +
+
+ )} +
- {/* Subtasks Column */} - {subtasks.length > 0 && ( -
-

- {t('task.subtasks', 'Subtasks')} -

+ {/* Subtasks Section - Always Visible */} +
+

+ {t('task.subtasks', 'Subtasks')} +

+ {subtasks.length > 0 ? (
{subtasks.map((subtask) => (
{
))}
-
- )} + ) : ( +
+
+ + + {t('task.noSubtasks', 'No subtasks yet')} + +
+
+ )} +
+
+ + {/* Right Column - Recent Activity */} +
+

+ {t('task.recentActivity', 'Recent Activity')} +

+
+ +
- )} +
+ {/* End of main content sections */} + - {/* Activity Timeline */} - {isTimelineExpanded && task?.id && ( -
- - setIsTimelineExpanded(!isTimelineExpanded) - } - /> -
- )} {/* Task Modal for Editing */} = ({ taskId }) => { } }; + const getTranslatedStatusLabel = (status: number | string): string => { + // Handle both numeric and string status values + const statusMap: Record = { + // Numeric values + 0: t('status.notStarted'), + 1: t('status.inProgress'), + 2: t('status.completed'), + 3: t('status.archived'), + 4: t('status.waiting'), + // String values + 'not_started': t('status.notStarted'), + 'in_progress': t('status.inProgress'), + 'done': t('status.completed'), + 'completed': t('status.completed'), + 'archived': t('status.archived'), + 'waiting': t('status.waiting'), + }; + + return statusMap[status] || t('status.unknown', { status }); + }; + const getEventDescription = (event: TaskEvent) => { const { event_type, old_value, new_value } = event; @@ -136,7 +156,7 @@ const TaskTimeline: React.FC = ({ taskId }) => { const oldStatus = old_value?.status; const newStatus = new_value?.status; if (oldStatus !== undefined && newStatus !== undefined) { - return `${t('timeline.events.status')}: ${getStatusLabel(oldStatus)} → ${getStatusLabel(newStatus)}`; + return `${t('timeline.events.status')}: ${getTranslatedStatusLabel(oldStatus)} → ${getTranslatedStatusLabel(newStatus)}`; } return t('timeline.events.statusChanged'); } @@ -152,7 +172,7 @@ const TaskTimeline: React.FC = ({ taskId }) => { const oldDate = old_value?.due_date; const newDate = new_value?.due_date; if (oldDate || newDate) { - return `${t('timeline.events.dueDate')}: ${oldDate || t('timeline.events.none')} → ${newDate || t('timeline.events.none')}`; + return `${t('timeline.events.dueDate')}: ${formatDate(oldDate)} → ${formatDate(newDate)}`; } return t('timeline.events.dueDateChanged'); } @@ -175,6 +195,30 @@ const TaskTimeline: React.FC = ({ taskId }) => { } }; + const formatDate = (dateString: string | null) => { + if (!dateString) return t('timeline.events.none'); + + // Handle ISO date strings (e.g., "2025-07-15T00:00:00.000Z") + const date = new Date(dateString); + + // Check if it's today, tomorrow, or yesterday + const today = new Date().toISOString().split('T')[0]; + const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + const dateOnly = date.toISOString().split('T')[0]; + + if (dateOnly === today) return t('dateIndicators.today'); + if (dateOnly === tomorrow) return t('dateIndicators.tomorrow'); + if (dateOnly === yesterday) return t('dateIndicators.yesterday'); + + // Return formatted date (e.g., "Jul 15, 2025") + return date.toLocaleDateString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + }; + const formatTimeAgo = (dateString: string) => { const date = new Date(dateString); const now = new Date(); diff --git a/frontend/utils/taskEventService.ts b/frontend/utils/taskEventService.ts index c20bb6f..85627d8 100644 --- a/frontend/utils/taskEventService.ts +++ b/frontend/utils/taskEventService.ts @@ -186,8 +186,8 @@ export const getEventTypeLabel = (eventType: string): string => { export const getStatusLabel = (status: number): string => { const statusLabels: Record = { 0: 'Not Started', - 1: 'In Progress', - 2: 'Done', + 1: 'In Progress', + 2: 'Completed', 3: 'Archived', 4: 'Waiting', }; diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 7ae0e8f..5e29972 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -543,6 +543,14 @@ "tomorrow": "TOMORROW", "yesterday": "YESTERDAY" }, + "status": { + "notStarted": "Not Started", + "inProgress": "In Progress", + "completed": "Completed", + "archived": "Archived", + "waiting": "Waiting", + "unknown": "Status {{status}}" + }, "taskViews": { "project": { "withName": "You are currently viewing all tasks associated with the \"{{projectName}}\" project. You can organize tasks within this project, set their priority, and track their completion. Use this space to focus on the tasks that belong specifically to this project.",