Skip to content

Commit 38bc4f5

Browse files
feat(reports): add "Overdue" as third category in Reports charts (#345)
- Add isOverdue utility function to tasks-utils.ts for shared usage - Update ReportsView to categorize pending tasks with past due dates as overdue - Add overdue bar (red #F33434) to ReportChart component - Update Tasks.tsx to import isOverdue from shared utility - Add comprehensive test coverage for overdue categorization Categorization logic: - status === 'completed' -> Completed (pink) - status === 'pending' && due < today -> Overdue (red) - status === 'pending' && (due >= today || no due) -> Ongoing (blue) Fixes: #341
1 parent 27b6c23 commit 38bc4f5

9 files changed

Lines changed: 195 additions & 24 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ export const ReportChart: React.FC<ReportChartProps> = ({
9898
name="Ongoing"
9999
label={{ position: 'top', fill: 'white', fontSize: 12 }}
100100
/>
101+
<Bar
102+
dataKey="overdue"
103+
fill="#F33434"
104+
name="Overdue"
105+
label={{ position: 'top', fill: 'white', fontSize: 12 }}
106+
/>
101107
</BarChart>
102108
</ResponsiveContainer>
103109
</div>

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { ReportsViewProps } from '../../utils/types';
33
import { getStartOfDay } from '../../utils/utils';
44
import { ReportChart } from './ReportChart';
5-
import { parseTaskwarriorDate } from '../Tasks/tasks-utils';
5+
import { parseTaskwarriorDate, isOverdue } from '../Tasks/tasks-utils';
66

77
export const ReportsView: React.FC<ReportsViewProps> = ({ tasks }) => {
88
const now = new Date();
@@ -14,6 +14,7 @@ export const ReportsView: React.FC<ReportsViewProps> = ({ tasks }) => {
1414
const startOfMonth = getStartOfDay(
1515
new Date(now.getFullYear(), now.getMonth(), 1)
1616
);
17+
1718
const countStatuses = (filterDate: Date) => {
1819
return tasks
1920
.filter((task) => {
@@ -31,11 +32,15 @@ export const ReportsView: React.FC<ReportsViewProps> = ({ tasks }) => {
3132
if (task.status === 'completed') {
3233
acc.completed += 1;
3334
} else if (task.status === 'pending') {
34-
acc.ongoing += 1;
35+
if (isOverdue(task.due)) {
36+
acc.overdue += 1;
37+
} else {
38+
acc.ongoing += 1;
39+
}
3540
}
3641
return acc;
3742
},
38-
{ completed: 0, ongoing: 0 }
43+
{ completed: 0, ongoing: 0, overdue: 0 }
3944
);
4045
};
4146

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
sortTasksById,
3737
getTimeSinceLastSync,
3838
hashKey,
39-
parseTaskwarriorDate,
39+
isOverdue,
4040
} from './tasks-utils';
4141
import Pagination from './Pagination';
4242
import { url } from '@/components/utils/URLs';
@@ -118,19 +118,6 @@ export const Tasks = (
118118

119119
// Handler for dialog open/close
120120

121-
const isOverdue = (due?: string) => {
122-
if (!due) return false;
123-
124-
const dueDate = parseTaskwarriorDate(due);
125-
if (!dueDate) return false;
126-
dueDate.setHours(0, 0, 0, 0);
127-
128-
const today = new Date();
129-
today.setHours(0, 0, 0, 0);
130-
131-
return dueDate < today;
132-
};
133-
134121
const debouncedSearch = debounce((value: string) => {
135122
setDebouncedTerm(value);
136123
setCurrentPage(1);

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jest.mock('../report-download-utils', () => ({
2727
}));
2828

2929
describe('ReportChart', () => {
30-
const mockData = [{ name: 'Today', completed: 5, ongoing: 3 }];
30+
const mockData = [{ name: 'Today', completed: 5, ongoing: 3, overdue: 2 }];
3131

3232
const defaultProps = {
3333
data: mockData,
@@ -87,6 +87,15 @@ describe('ReportChart', () => {
8787
expect(ongoingBar).toHaveAttribute('data-name', 'Ongoing');
8888
});
8989

90+
it('displays overdue bar with correct color', () => {
91+
render(<ReportChart {...defaultProps} />);
92+
93+
const overdueBar = screen.getByTestId('bar-overdue');
94+
95+
expect(overdueBar).toHaveAttribute('data-fill', '#F33434');
96+
expect(overdueBar).toHaveAttribute('data-name', 'Overdue');
97+
});
98+
9099
it('renders chart axes', () => {
91100
render(<ReportChart {...defaultProps} />);
92101
expect(screen.getByTestId('x-axis')).toBeInTheDocument();

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

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,50 @@ describe('ReportsView', () => {
9696
expect(data[0].completed).toBe(0);
9797
});
9898

99+
it('counts pending tasks with past due date as overdue', () => {
100+
const todayDate = new Date();
101+
const today = toTWFormat(todayDate);
102+
103+
const pastDate = new Date();
104+
pastDate.setDate(pastDate.getDate() - 10);
105+
const pastDue = toTWFormat(pastDate);
106+
107+
const tasks = [
108+
createMockTask({ status: 'pending', due: pastDue }),
109+
createMockTask({ status: 'pending', due: pastDue }),
110+
createMockTask({ status: 'pending', due: today }),
111+
createMockTask({ status: 'completed', end: today }),
112+
];
113+
114+
render(<ReportsView tasks={tasks} />);
115+
116+
const monthlyData = screen.getByTestId('monthly-report-chart-data');
117+
const data = JSON.parse(monthlyData.textContent || '[]');
118+
119+
expect(data[0].overdue).toBe(2);
120+
expect(data[0].ongoing).toBe(1);
121+
expect(data[0].completed).toBe(1);
122+
});
123+
124+
it('treats task with no due date as ongoing', () => {
125+
const today = toTWFormat(new Date());
126+
127+
const tasks = [
128+
createMockTask({
129+
status: 'pending',
130+
entry: today,
131+
}),
132+
];
133+
134+
render(<ReportsView tasks={tasks} />);
135+
const dailyData = screen.getByTestId('daily-report-chart-data');
136+
const data = JSON.parse(dailyData.textContent || '[]');
137+
138+
expect(data[0].ongoing).toBe(1);
139+
expect(data[0].overdue).toBe(0);
140+
expect(data[0].completed).toBe(0);
141+
});
142+
99143
it('filters tasks by date range correctly', () => {
100144
const referenceDate = new Date('2024-01-10T12:00:00Z');
101145

@@ -135,7 +179,7 @@ describe('ReportsView', () => {
135179
jest.useRealTimers();
136180
});
137181

138-
it('uses modified date when available', () => {
182+
it('uses end date when available', () => {
139183
const today = toTWFormat(new Date());
140184
const tasks = [
141185
createMockTask({
@@ -191,21 +235,29 @@ describe('ReportsView', () => {
191235
});
192236

193237
it('handles mixed statuses correctly', () => {
194-
const today = toTWFormat(new Date());
238+
const todayDate = new Date();
239+
const today = toTWFormat(todayDate);
240+
241+
const pastDate = new Date();
242+
pastDate.setDate(pastDate.getDate() - 10);
243+
const pastDue = toTWFormat(pastDate);
244+
195245
const tasks = [
196246
createMockTask({ status: 'completed', end: today }),
197247
createMockTask({ status: 'pending', due: today }),
248+
createMockTask({ status: 'pending', due: pastDue }),
198249
createMockTask({ status: 'deleted', end: today }),
199250
createMockTask({ status: 'recurring', due: today }),
200251
];
201252

202253
render(<ReportsView tasks={tasks} />);
203254

204-
const dailyData = screen.getByTestId('daily-report-chart-data');
205-
const data = JSON.parse(dailyData.textContent || '[]');
255+
const monthlyData = screen.getByTestId('monthly-report-chart-data');
256+
const data = JSON.parse(monthlyData.textContent || '[]');
206257

207258
expect(data[0].completed).toBe(1);
208259
expect(data[0].ongoing).toBe(1);
260+
expect(data[0].overdue).toBe(1);
209261
});
210262
});
211263

frontend/src/components/HomeComponents/Tasks/__tests__/__snapshots__/ReportChart.test.tsx.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ exports[`ReportChart Component using Snapshot renders correctly with one data en
128128
data-name="Ongoing"
129129
data-testid="bar-ongoing"
130130
/>
131+
<div
132+
data-fill="#F33434"
133+
data-name="Overdue"
134+
data-testid="bar-overdue"
135+
/>
131136
</div>
132137
</div>
133138
</div>
@@ -262,6 +267,11 @@ exports[`ReportChart Component using Snapshot renders correctly with several dat
262267
data-name="Ongoing"
263268
data-testid="bar-ongoing"
264269
/>
270+
<div
271+
data-fill="#F33434"
272+
data-name="Overdue"
273+
data-testid="bar-overdue"
274+
/>
265275
</div>
266276
</div>
267277
</div>

frontend/src/components/HomeComponents/Tasks/__tests__/__snapshots__/ReportView.test.tsx.snap

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
116116
<span>
117117
ongoing: 1
118118
</span>
119+
<span>
120+
overdue: 0
121+
</span>
119122
</div>
120123
<div
121124
data-stroke="#555"
@@ -153,6 +156,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
153156
data-name="Ongoing"
154157
data-testid="Bar"
155158
/>
159+
<div
160+
data-data-key="overdue"
161+
data-fill="#F33434"
162+
data-name="Overdue"
163+
data-testid="Bar"
164+
/>
156165
</div>
157166
</div>
158167
</div>
@@ -267,6 +276,9 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
267276
<span>
268277
ongoing: 1
269278
</span>
279+
<span>
280+
overdue: 0
281+
</span>
270282
</div>
271283
<div
272284
data-stroke="#555"
@@ -304,6 +316,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
304316
data-name="Ongoing"
305317
data-testid="Bar"
306318
/>
319+
<div
320+
data-data-key="overdue"
321+
data-fill="#F33434"
322+
data-name="Overdue"
323+
data-testid="Bar"
324+
/>
307325
</div>
308326
</div>
309327
</div>
@@ -418,6 +436,9 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
418436
<span>
419437
ongoing: 1
420438
</span>
439+
<span>
440+
overdue: 0
441+
</span>
421442
</div>
422443
<div
423444
data-stroke="#555"
@@ -455,6 +476,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only one ta
455476
data-name="Ongoing"
456477
data-testid="Bar"
457478
/>
479+
<div
480+
data-data-key="overdue"
481+
data-fill="#F33434"
482+
data-name="Overdue"
483+
data-testid="Bar"
484+
/>
458485
</div>
459486
</div>
460487
</div>
@@ -578,6 +605,9 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
578605
<span>
579606
ongoing: 1
580607
</span>
608+
<span>
609+
overdue: 0
610+
</span>
581611
</div>
582612
<div
583613
data-stroke="#555"
@@ -615,6 +645,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
615645
data-name="Ongoing"
616646
data-testid="Bar"
617647
/>
648+
<div
649+
data-data-key="overdue"
650+
data-fill="#F33434"
651+
data-name="Overdue"
652+
data-testid="Bar"
653+
/>
618654
</div>
619655
</div>
620656
</div>
@@ -727,7 +763,10 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
727763
completed: 2
728764
</span>
729765
<span>
730-
ongoing: 2
766+
ongoing: 1
767+
</span>
768+
<span>
769+
overdue: 1
731770
</span>
732771
</div>
733772
<div
@@ -766,6 +805,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
766805
data-name="Ongoing"
767806
data-testid="Bar"
768807
/>
808+
<div
809+
data-data-key="overdue"
810+
data-fill="#F33434"
811+
data-name="Overdue"
812+
data-testid="Bar"
813+
/>
769814
</div>
770815
</div>
771816
</div>
@@ -878,7 +923,10 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
878923
completed: 4
879924
</span>
880925
<span>
881-
ongoing: 4
926+
ongoing: 1
927+
</span>
928+
<span>
929+
overdue: 3
882930
</span>
883931
</div>
884932
<div
@@ -917,6 +965,12 @@ exports[`ReportsView Component using Snapshot renders correctly with only severa
917965
data-name="Ongoing"
918966
data-testid="Bar"
919967
/>
968+
<div
969+
data-data-key="overdue"
970+
data-fill="#F33434"
971+
data-name="Overdue"
972+
data-testid="Bar"
973+
/>
920974
</div>
921975
</div>
922976
</div>

0 commit comments

Comments
 (0)