Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions frontend/src/components/HomeComponents/Tasks/Tasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const Tasks = (
const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
const [tempTasks, setTempTasks] = useState<Task[]>([]);
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
const status = ['pending', 'completed', 'deleted'];
const status = ['pending', 'completed', 'deleted', 'overdue'];
const [currentPage, setCurrentPage] = useState<number>(1);
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
const [idSortOrder, setIdSortOrder] = useState<'asc' | 'desc'>('asc');
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1116,19 +1122,28 @@ export const Tasks = (
</TableCell>
<TableCell className="py-2">
<Badge
className={
task.status === 'pending' &&
isOverdue(task.due)
? 'bg-orange-500 text-white'
: ''
}
variant={
task.status === 'pending'
? 'secondary'
: task.status === 'deleted'
? 'destructive'
: 'default'
task.status === 'deleted'
? 'destructive'
: task.status === 'completed'
? 'default'
: 'secondary'
}
>
{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'}
</Badge>
</TableCell>
</TableRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Tasks {...mockProps} />);

await screen.findByText('Task 12');
Expand Down Expand Up @@ -323,4 +323,89 @@ describe('Tasks Component', () => {

jest.useRealTimers();
});

test('shows "overdue" in status filter options', async () => {
render(<Tasks {...mockProps} />);

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 <div data-testid={`ms-${title}`}>Mocked MultiSelect: {title}</div>;
});

render(<Tasks {...mockProps} />);

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(<Tasks {...mockProps} />);

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(<Tasks {...mockProps} />);

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(<Tasks {...mockProps} />);

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();
});
});
Loading