import type { HabitService } from "../habits/service"; import type { Schedule } from "../habits/contracts"; export type TodayHabit = ReturnType; export type TodayResponse = { date: string; timezone: string; habits: TodayHabit[]; due: number; completed: number; }; export async function habitRequest( path: string, options?: RequestInit, ): Promise { const response = await fetch(`/api${path}`, options); if (!response.ok) { if (response.status === 401) throw new Error("Your session has expired. Sign in again to continue."); const body = await response.json().catch(() => null); throw new Error( (body?.error || "Could not reach your workspace. Please try again.") + (response.status >= 500 && body?.requestId ? ` Reference: ${body.requestId}` : ""), ); } return response.status === 204 ? (undefined as T) : (response.json() as Promise); } export function scheduleLabel(schedule?: Schedule) { if (!schedule || schedule.type === "daily") return "Every day"; if (schedule.type === "weekdays") return schedule.days .map((day) => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][day]) .join(", "); if (schedule.type === "interval") return `Every ${schedule.every} days`; return `Every ${schedule.every === 1 ? "week" : `${schedule.every} weeks`} ยท ${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][schedule.weekday]}`; } export function formatTrackingDate(date: string) { return new Date(`${date}T12:00:00Z`).toLocaleDateString("en", { weekday: "long", month: "long", day: "numeric", timeZone: "UTC", }); } /** Preserve unchanged row references when a background refresh returns new JSON objects. */ export function reconcileToday(current: TodayResponse | null, next: TodayResponse): TodayResponse { if (!current || current.date !== next.date || current.timezone !== next.timezone) return next; const previous = new Map(current.habits.map(habit => [habit.habitId, habit])); const habits = next.habits.map(habit => { const old = previous.get(habit.habitId); return old && JSON.stringify(old) === JSON.stringify(habit) ? old : habit; }); if (current.due === next.due && current.completed === next.completed && habits.length === current.habits.length && habits.every((habit, index) => habit === current.habits[index])) return current; return { ...next, habits }; }