61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import type { HabitService } from "../habits/service";
|
|
import type { Schedule } from "../habits/contracts";
|
|
|
|
export type TodayHabit = ReturnType<HabitService["day"]>;
|
|
export type TodayResponse = {
|
|
date: string;
|
|
timezone: string;
|
|
habits: TodayHabit[];
|
|
due: number;
|
|
completed: number;
|
|
};
|
|
|
|
export async function habitRequest<T>(
|
|
path: string,
|
|
options?: RequestInit,
|
|
): Promise<T> {
|
|
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<T>);
|
|
}
|
|
|
|
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 };
|
|
}
|