feat: due-date reminders
Every hour the API reminds whoever has a ticket, and whoever follows it, the day before its due date, on the day and once when it is overdue, in the app and by e-mail for those who asked. Cards on the board flag what is due soon or late. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
16
libs/web/tracker/domain/src/lib/models/due.spec.ts
Normal file
16
libs/web/tracker/domain/src/lib/models/due.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { StatusCategory } from './enums';
|
||||
import { dueState } from './due';
|
||||
|
||||
describe('due state', () => {
|
||||
const today = '2026-10-08';
|
||||
it('flags late, today and the next two days, never finished tickets', () => {
|
||||
expect(dueState('2026-10-07', StatusCategory.TODO, today)).toBe('overdue');
|
||||
expect(dueState('2026-10-08', StatusCategory.IN_PROGRESS, today)).toBe(
|
||||
'today',
|
||||
);
|
||||
expect(dueState('2026-10-10', StatusCategory.QA, today)).toBe('soon');
|
||||
expect(dueState('2026-10-11', StatusCategory.TODO, today)).toBeNull();
|
||||
expect(dueState('2026-10-01', StatusCategory.DONE, today)).toBeNull();
|
||||
expect(dueState(null, StatusCategory.TODO, today)).toBeNull();
|
||||
});
|
||||
});
|
||||
27
libs/web/tracker/domain/src/lib/models/due.ts
Normal file
27
libs/web/tracker/domain/src/lib/models/due.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { StatusCategory } from './enums';
|
||||
|
||||
export type DueState = 'overdue' | 'today' | 'soon' | null;
|
||||
|
||||
/** `YYYY-MM-DD` of a local date. */
|
||||
export function localDay(date: Date): string {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* How urgent a due date looks: overdue, today, or within the next two days.
|
||||
* Finished tickets are never late.
|
||||
*/
|
||||
export function dueState(
|
||||
dueDate: string | null,
|
||||
category: StatusCategory,
|
||||
today = localDay(new Date()),
|
||||
): DueState {
|
||||
if (!dueDate) return null;
|
||||
if (category === StatusCategory.DONE || category === StatusCategory.CANCELLED)
|
||||
return null;
|
||||
const due = dueDate.slice(0, 10);
|
||||
if (due < today) return 'overdue';
|
||||
if (due === today) return 'today';
|
||||
const days = (Date.parse(due) - Date.parse(today)) / 86_400_000;
|
||||
return days <= 2 ? 'soon' : null;
|
||||
}
|
||||
Reference in New Issue
Block a user