From c348c4222af25e56bd9fedd95feea9effe7f41bc Mon Sep 17 00:00:00 2001 From: Shiva Gupta Date: Wed, 19 Nov 2025 09:09:48 +0530 Subject: [PATCH] feat(tasks): highlight overdue tasks and show overdue badge in dialog - Added red background styling to task IDs when due date is past - Added "Overdue" badge inside task details dialog for pending + overdue tasks - Implemented sorting logic to always display overdue tasks at the top - Updated search and filter flows to keep overdue-first sorting consistent - Added tests to verify overdue styling and badge rendering Fixes: #152 --- .../components/HomeComponents/Tasks/Tasks.tsx | 59 ++++++++++++++++++- .../Tasks/__tests__/Tasks.test.tsx | 20 +++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx index 995674ae..2ab48ed5 100644 --- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx +++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx @@ -129,6 +129,27 @@ export const Tasks = ( const [searchTerm, setSearchTerm] = useState(''); const [lastSyncTime, setLastSyncTime] = useState(null); + const isOverdue = (due?: string) => { + if (!due) return false; + + // Taskwarrior format: 20251115T183000Z + const parsed = new Date( + due.replace( + /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/, + '$1-$2-$3T$4:$5:$6Z' + ) + ); + + // Convert to local date (ignore time) + const dueDate = new Date(parsed); + dueDate.setHours(0, 0, 0, 0); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + return dueDate < today; + }; + // Debounced search handler const debouncedSearch = debounce((value: string) => { if (!value) { @@ -149,7 +170,7 @@ export const Tasks = ( (task.tags && task.tags.some((tag) => tag.toLowerCase().includes(lowerValue))) ); - setTempTasks(filtered); + setTempTasks(sortWithOverdueOnTop(filtered)); setCurrentPage(1); }, 300); @@ -536,6 +557,21 @@ export const Tasks = ( tags: newTask.tags.filter((tag) => tag !== tagToRemove), }); }; + + const sortWithOverdueOnTop = (tasks: Task[]) => { + return [...tasks].sort((a, b) => { + const aOverdue = a.status === 'pending' && isOverdue(a.due); + const bOverdue = b.status === 'pending' && isOverdue(b.due); + + // Overdue always on top + if (aOverdue && !bOverdue) return -1; + if (!aOverdue && bOverdue) return 1; + + // Otherwise fall back to ID sort DESC (latest first) + return b.id - a.id; + }); + }; + useEffect(() => { let filteredTasks = tasks; @@ -561,6 +597,8 @@ export const Tasks = ( ); } + filteredTasks = sortWithOverdueOnTop(filteredTasks); + // Sort + set setTempTasks(filteredTasks); }, [selectedProjects, selectedTags, selectedStatuses, tasks]); @@ -987,7 +1025,14 @@ export const Tasks = ( {/* Display task details */} - + {task.id} @@ -1050,7 +1095,15 @@ export const Tasks = ( ID: - {task.id} + + {task.id} + {task.status === 'pending' && + isOverdue(task.due) && ( + + Overdue + + )} + Description: diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx index 4f3492c9..546c14f1 100644 --- a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx +++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx @@ -56,6 +56,7 @@ jest.mock('../hooks', () => ({ project: i % 2 === 0 ? 'ProjectA' : 'ProjectB', tags: i % 3 === 0 ? ['tag1'] : ['tag2'], uuid: `uuid-${i + 1}`, + due: i === 0 ? '20200101T120000Z' : undefined, })) ), })), @@ -144,4 +145,23 @@ describe('Tasks Component', () => { expect(screen.getByTestId('current-page')).toHaveTextContent('1'); }); + + test('shows red background on task ID and Overdue badge for overdue tasks', async () => { + render(); + + await screen.findByText('Task 12'); + + const dropdown = screen.getByLabelText('Show:'); + fireEvent.change(dropdown, { target: { value: '20' } }); + + const task1Description = screen.getByText('Task 1'); + const row = task1Description.closest('tr'); + const idElement = row?.querySelector('span'); + + expect(idElement).toHaveClass('bg-red-600/80'); + fireEvent.click(idElement!); + + const overdueBadge = await screen.findByText('Overdue'); + expect(overdueBadge).toBeInTheDocument(); + }); });