84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import "server-only";
|
|
|
|
import { prisma } from "@/lib/db";
|
|
import { addUTCDays, getWeekStart, startOfUTCDate, toDateKey } from "@/lib/dates";
|
|
import { expandOccurrences } from "@/lib/rrule-utils";
|
|
import type { ScheduledBoardDTO, ScheduledDayDTO, ScheduledOccurrenceDTO } from "@/types/scheduled-todo";
|
|
|
|
// A recurring to-do that's never checked off would otherwise surface a
|
|
// missed occurrence for every day since it was created, forever -- cap how
|
|
// far back "overdue" looks so the column stays meaningful instead of
|
|
// endless.
|
|
const OVERDUE_LOOKBACK_DAYS = 30;
|
|
|
|
/**
|
|
* Fetches one Scheduled board's worth of occurrences, bucketed the way the
|
|
* panel renders them -- shared by the Home board and every Project board,
|
|
* which differ only in which ScheduledTodos they scope to (mirrors
|
|
* lib/board.ts's getBoard).
|
|
*/
|
|
export async function getScheduledTodoBoard(
|
|
where: { userId: string; projectId: null } | { projectId: string }
|
|
): Promise<ScheduledBoardDTO> {
|
|
const today = startOfUTCDate(new Date());
|
|
const todayKey = toDateKey(today);
|
|
const weekStart = getWeekStart(today);
|
|
const weekEnd = addUTCDays(weekStart, 6);
|
|
const windowStart = addUTCDays(today, -OVERDUE_LOOKBACK_DAYS);
|
|
|
|
const scheduledTodos = await prisma.scheduledTodo.findMany({
|
|
where,
|
|
include: { completions: true },
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
|
|
const overdue: ScheduledOccurrenceDTO[] = [];
|
|
const todayOccurrences: ScheduledOccurrenceDTO[] = [];
|
|
// Seeded up front so every remaining weekday gets a header even with
|
|
// nothing scheduled that day.
|
|
const upcomingByDate = new Map<string, ScheduledOccurrenceDTO[]>();
|
|
for (let d = addUTCDays(today, 1); d <= weekEnd; d = addUTCDays(d, 1)) {
|
|
upcomingByDate.set(toDateKey(d), []);
|
|
}
|
|
|
|
for (const todo of scheduledTodos) {
|
|
const completedDates = new Set(todo.completions.map((c) => toDateKey(c.occurrenceDate)));
|
|
const occurrences = expandOccurrences(todo.startDate, todo.rrule, windowStart, weekEnd);
|
|
|
|
for (const occurrenceDate of occurrences) {
|
|
const key = toDateKey(occurrenceDate);
|
|
const dto: ScheduledOccurrenceDTO = {
|
|
scheduledTodoId: todo.id,
|
|
title: todo.title,
|
|
details: todo.details,
|
|
occurrenceDate: key,
|
|
completed: completedDates.has(key),
|
|
isRecurring: !!todo.rrule,
|
|
timeDue: todo.timeDue,
|
|
remindMinutesBefore: todo.remindMinutesBefore,
|
|
};
|
|
|
|
if (key < todayKey) {
|
|
if (!dto.completed) overdue.push(dto);
|
|
} else if (key === todayKey) {
|
|
todayOccurrences.push(dto);
|
|
} else {
|
|
upcomingByDate.get(key)?.push(dto);
|
|
}
|
|
}
|
|
}
|
|
|
|
overdue.sort((a, b) => a.occurrenceDate.localeCompare(b.occurrenceDate));
|
|
|
|
const upcoming: ScheduledDayDTO[] = Array.from(upcomingByDate, ([date, occurrences]) => ({
|
|
date,
|
|
occurrences,
|
|
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
|
|
return {
|
|
overdue,
|
|
today: { date: todayKey, occurrences: todayOccurrences },
|
|
upcoming,
|
|
};
|
|
}
|