export type DailyCountRow = { day: Date | string; count: number }; export type DailyActivityPoint = { date: string; count: number; }; export type DailyActivityResponse = { days: number; total: number; items: DailyActivityPoint[]; }; /** Local calendar day at 00:00:00, `offsetDays` before today (0 = today). */ export function startOfLocalDay(offsetDays = 0): Date { const date = new Date(); date.setHours(0, 0, 0, 0); date.setDate(date.getDate() - offsetDays); return date; } /** Start of the current local week. `weekStartsOn`: 0 Sun … 6 Sat. Default Saturday (Iran). */ export function startOfLocalWeek(weekStartsOn = 6): Date { const today = startOfLocalDay(0); const diff = (today.getDay() - weekStartsOn + 7) % 7; return startOfLocalDay(diff); } /** First day of the current local month at 00:00:00. */ export function startOfLocalMonth(): Date { const date = new Date(); date.setDate(1); date.setHours(0, 0, 0, 0); return date; } function pad2(n: number) { return String(n).padStart(2, '0'); } /** Calendar date key from a PG `date` (UTC midnight) or ISO string. */ function toDateKey(value: Date | string): string { if (typeof value === 'string') { return value.slice(0, 10); } return `${value.getUTCFullYear()}-${pad2(value.getUTCMonth() + 1)}-${pad2(value.getUTCDate())}`; } function toLocalDateKey(date: Date): string { return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`; } /** Fill a continuous series of the last `days` local calendar days. */ export function buildDailyActivitySeries( days: number, rows: DailyCountRow[], ): DailyActivityResponse { const byDay = new Map(); for (const row of rows) { byDay.set(toDateKey(row.day), Number(row.count) || 0); } const items: DailyActivityPoint[] = []; let total = 0; const end = startOfLocalDay(0); for (let i = days - 1; i >= 0; i -= 1) { const date = new Date(end); date.setDate(end.getDate() - i); const key = toLocalDateKey(date); const count = byDay.get(key) ?? 0; total += count; items.push({ date: key, count }); } return { days, total, items }; } export type DualDailyActivityResponse = { days: number; primary: DailyActivityResponse; secondary: DailyActivityResponse; }; export function buildDualDailyActivitySeries( days: number, primaryRows: DailyCountRow[], secondaryRows: DailyCountRow[], ): DualDailyActivityResponse { return { days, primary: buildDailyActivitySeries(days, primaryRows), secondary: buildDailyActivitySeries(days, secondaryRows), }; }