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