From 89b5ee6f91cbd3df5407bce8eeefcae86a8aeb20 Mon Sep 17 00:00:00 2001 From: Shiva Gupta Date: Wed, 26 Nov 2025 00:13:50 +0530 Subject: [PATCH] feat(tasks): add overdue filter, badge UI, and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added "overdue" to Status filter and integrated with existing logic - Implemented overdue sorting + “O” badge in status column - Kept red ID highlight for overdue tasks - Added comprehensive tests for overdue filtering, badges, and ordering - Fixes: #186 --- .../components/HomeComponents/Tasks/Tasks.tsx | 43 ++++++--- .../Tasks/__tests__/Tasks.test.tsx | 87 ++++++++++++++++++- 2 files changed, 115 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx index 92b0d0e4..4ca4c9df 100644 --- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx +++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx @@ -90,7 +90,7 @@ export const Tasks = ( const [selectedProjects, setSelectedProjects] = useState([]); const [tempTasks, setTempTasks] = useState([]); const [selectedStatuses, setSelectedStatuses] = useState([]); - const status = ['pending', 'completed', 'deleted']; + const status = ['pending', 'completed', 'deleted', 'overdue']; const [currentPage, setCurrentPage] = useState(1); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); const [idSortOrder, setIdSortOrder] = useState<'asc' | 'desc'>('asc'); @@ -623,9 +623,15 @@ export const Tasks = ( // Status filter if (selectedStatuses.length > 0) { - filteredTasks = filteredTasks.filter((task) => - selectedStatuses.includes(task.status) - ); + filteredTasks = filteredTasks.filter((task) => { + const isTaskOverdue = task.status === 'pending' && isOverdue(task.due); + + if (selectedStatuses.includes('overdue') && isTaskOverdue) { + return true; + } + + return selectedStatuses.includes(task.status); + }); } // Tag filter @@ -1116,19 +1122,28 @@ export const Tasks = ( - {task.status === 'completed' - ? 'C' - : task.status === 'deleted' - ? 'D' - : 'P'} + {task.status === 'pending' && + isOverdue(task.due) + ? 'O' + : task.status === 'completed' + ? 'C' + : task.status === 'deleted' + ? 'D' + : 'P'} diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx index e4622342..d708eb22 100644 --- a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx +++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx @@ -283,7 +283,7 @@ describe('Tasks Component', () => { expect(callArg.tags).toEqual(expect.arrayContaining(['newtag', '-tag1'])); }); - test('shows red background on task ID and Overdue badge for overdue tasks', async () => { + test('shows orange background on task ID and Overdue badge for overdue tasks', async () => { render(); await screen.findByText('Task 12'); @@ -323,4 +323,89 @@ describe('Tasks Component', () => { jest.useRealTimers(); }); + + test('shows "overdue" in status filter options', async () => { + render(); + + expect(await screen.findByText('Mocked BottomBar')).toBeInTheDocument(); + + const multiSelectFilter = require('@/components/ui/multiSelect'); + + expect(multiSelectFilter.MultiSelectFilter).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Status', + options: expect.arrayContaining(['overdue']), + }), + {} + ); + }); + + test('filters tasks to show only overdue tasks when status "overdue" is selected', async () => { + const MultiSelectFilter = + require('@/components/ui/multiSelect').MultiSelectFilter; + + MultiSelectFilter.mockImplementation(({ title }: { title: string }) => { + return
Mocked MultiSelect: {title}
; + }); + + render(); + + expect(await screen.findByText('Task 12')).toBeInTheDocument(); + + const lastCall = MultiSelectFilter.mock.calls.find( + (call: any[]) => call[0].title === 'Status' + ); + + const onSelectionChange = lastCall[0].onSelectionChange; + + act(() => { + onSelectionChange(['overdue']); + }); + + const overdueTask = screen.getByText('Task 1'); + expect(overdueTask).toBeInTheDocument(); + expect(screen.queryByText('Task 2')).not.toBeInTheDocument(); + }); + + test('shows "O" badge for overdue tasks in status column', async () => { + render(); + + await screen.findByText('Task 12'); + + const dropdown = screen.getByLabelText('Show:'); + fireEvent.change(dropdown, { target: { value: '20' } }); + + const row = screen.getByText('Task 1').closest('tr')!; + const statusCell = within(row).getByText('O'); + + expect(statusCell).toBeInTheDocument(); + }); + + test('does not show "O" badge for non-overdue pending tasks', async () => { + render(); + + await screen.findByText('Task 12'); + + const dropdown = screen.getByLabelText('Show:'); + fireEvent.change(dropdown, { target: { value: '20' } }); + + expect(await screen.findByText('Task 2')).toBeInTheDocument(); + + const row = screen.getByText('Task 2').closest('tr')!; + const statusCell = within(row).getByText('P'); + + expect(statusCell).toBeInTheDocument(); + }); + + test('overdue tasks appear at the top of the list', async () => { + render(); + + await screen.findByText('Task 12'); + + const dropdown = screen.getByLabelText('Show:'); + fireEvent.change(dropdown, { target: { value: '20' } }); + + const firstRow = screen.getAllByRole('row')[1]; + expect(within(firstRow).getByText('Task 1')).toBeInTheDocument(); + }); });