Skip to content

Commit 3a4b32f

Browse files
feat(tasks): add overdue filter, badge UI, and tests (#224)
- 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
1 parent 543a4a8 commit 3a4b32f

File tree

2 files changed

+115
-15
lines changed

2 files changed

+115
-15
lines changed

frontend/src/components/HomeComponents/Tasks/Tasks.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const Tasks = (
9292
const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
9393
const [tempTasks, setTempTasks] = useState<Task[]>([]);
9494
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
95-
const status = ['pending', 'completed', 'deleted'];
95+
const status = ['pending', 'completed', 'deleted', 'overdue'];
9696
const [currentPage, setCurrentPage] = useState<number>(1);
9797
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
9898
const [idSortOrder, setIdSortOrder] = useState<'asc' | 'desc'>('asc');
@@ -664,9 +664,15 @@ export const Tasks = (
664664

665665
// Status filter
666666
if (selectedStatuses.length > 0) {
667-
filteredTasks = filteredTasks.filter((task) =>
668-
selectedStatuses.includes(task.status)
669-
);
667+
filteredTasks = filteredTasks.filter((task) => {
668+
const isTaskOverdue = task.status === 'pending' && isOverdue(task.due);
669+
670+
if (selectedStatuses.includes('overdue') && isTaskOverdue) {
671+
return true;
672+
}
673+
674+
return selectedStatuses.includes(task.status);
675+
});
670676
}
671677

672678
// Tag filter
@@ -1238,19 +1244,28 @@ export const Tasks = (
12381244
</TableCell>
12391245
<TableCell className="py-2">
12401246
<Badge
1247+
className={
1248+
task.status === 'pending' &&
1249+
isOverdue(task.due)
1250+
? 'bg-orange-500 text-white'
1251+
: ''
1252+
}
12411253
variant={
1242-
task.status === 'pending'
1243-
? 'secondary'
1244-
: task.status === 'deleted'
1245-
? 'destructive'
1246-
: 'default'
1254+
task.status === 'deleted'
1255+
? 'destructive'
1256+
: task.status === 'completed'
1257+
? 'default'
1258+
: 'secondary'
12471259
}
12481260
>
1249-
{task.status === 'completed'
1250-
? 'C'
1251-
: task.status === 'deleted'
1252-
? 'D'
1253-
: 'P'}
1261+
{task.status === 'pending' &&
1262+
isOverdue(task.due)
1263+
? 'O'
1264+
: task.status === 'completed'
1265+
? 'C'
1266+
: task.status === 'deleted'
1267+
? 'D'
1268+
: 'P'}
12541269
</Badge>
12551270
</TableCell>
12561271
</TableRow>

frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ describe('Tasks Component', () => {
296296
expect(callArg.tags).toEqual(expect.arrayContaining(['newtag', '-tag1']));
297297
});
298298

299-
test('shows red background on task ID and Overdue badge for overdue tasks', async () => {
299+
test('shows orange background on task ID and Overdue badge for overdue tasks', async () => {
300300
render(<Tasks {...mockProps} />);
301301

302302
await screen.findByText('Task 12');
@@ -336,4 +336,89 @@ describe('Tasks Component', () => {
336336

337337
jest.useRealTimers();
338338
});
339+
340+
test('shows "overdue" in status filter options', async () => {
341+
render(<Tasks {...mockProps} />);
342+
343+
expect(await screen.findByText('Mocked BottomBar')).toBeInTheDocument();
344+
345+
const multiSelectFilter = require('@/components/ui/multiSelect');
346+
347+
expect(multiSelectFilter.MultiSelectFilter).toHaveBeenCalledWith(
348+
expect.objectContaining({
349+
title: 'Status',
350+
options: expect.arrayContaining(['overdue']),
351+
}),
352+
{}
353+
);
354+
});
355+
356+
test('filters tasks to show only overdue tasks when status "overdue" is selected', async () => {
357+
const MultiSelectFilter =
358+
require('@/components/ui/multiSelect').MultiSelectFilter;
359+
360+
MultiSelectFilter.mockImplementation(({ title }: { title: string }) => {
361+
return <div data-testid={`ms-${title}`}>Mocked MultiSelect: {title}</div>;
362+
});
363+
364+
render(<Tasks {...mockProps} />);
365+
366+
expect(await screen.findByText('Task 12')).toBeInTheDocument();
367+
368+
const lastCall = MultiSelectFilter.mock.calls.find(
369+
(call: any[]) => call[0].title === 'Status'
370+
);
371+
372+
const onSelectionChange = lastCall[0].onSelectionChange;
373+
374+
act(() => {
375+
onSelectionChange(['overdue']);
376+
});
377+
378+
const overdueTask = screen.getByText('Task 1');
379+
expect(overdueTask).toBeInTheDocument();
380+
expect(screen.queryByText('Task 2')).not.toBeInTheDocument();
381+
});
382+
383+
test('shows "O" badge for overdue tasks in status column', async () => {
384+
render(<Tasks {...mockProps} />);
385+
386+
await screen.findByText('Task 12');
387+
388+
const dropdown = screen.getByLabelText('Show:');
389+
fireEvent.change(dropdown, { target: { value: '20' } });
390+
391+
const row = screen.getByText('Task 1').closest('tr')!;
392+
const statusCell = within(row).getByText('O');
393+
394+
expect(statusCell).toBeInTheDocument();
395+
});
396+
397+
test('does not show "O" badge for non-overdue pending tasks', async () => {
398+
render(<Tasks {...mockProps} />);
399+
400+
await screen.findByText('Task 12');
401+
402+
const dropdown = screen.getByLabelText('Show:');
403+
fireEvent.change(dropdown, { target: { value: '20' } });
404+
405+
expect(await screen.findByText('Task 2')).toBeInTheDocument();
406+
407+
const row = screen.getByText('Task 2').closest('tr')!;
408+
const statusCell = within(row).getByText('P');
409+
410+
expect(statusCell).toBeInTheDocument();
411+
});
412+
413+
test('overdue tasks appear at the top of the list', async () => {
414+
render(<Tasks {...mockProps} />);
415+
416+
await screen.findByText('Task 12');
417+
418+
const dropdown = screen.getByLabelText('Show:');
419+
fireEvent.change(dropdown, { target: { value: '20' } });
420+
421+
const firstRow = screen.getAllByRole('row')[1];
422+
expect(within(firstRow).getByText('Task 1')).toBeInTheDocument();
423+
});
339424
});

0 commit comments

Comments
 (0)