import { useEffect, useRef, useState, type ReactNode } from "react"; import type { CalendarResponse } from "../shared/calendar"; import { habitRequest, scheduleLabel, type TodayHabit } from "../lib/dashboard"; import { CalendarHeatmap } from "./design-system/CalendarHeatmap"; import { monthWindow } from "./design-system/calendar-model"; import { Button } from "./design-system/primitives"; import { HabitChart } from "./design-system/HabitChart"; import { CalendarLegend } from "./design-system/CalendarLegend"; import { shade } from "../habits/calendar"; import { CheckInEditor } from "./CheckInEditor"; import { Field } from "./design-system/Field"; import { ItemActions } from "./design-system/ItemActions"; export function HabitHistory({ habit, date, revision, onEdit, onDelete, disabled = false, children, tasks, editor, onExpired, onHistorySaved, calendarOnly = false, }: { habit: TodayHabit; date: string; revision: number; onEdit?: (color: string) => void; onDelete?: () => void; disabled?: boolean; children?: ReactNode; tasks?: ReactNode; editor?: ReactNode; onExpired?: () => void; onHistorySaved?: () => void; calendarOnly?: boolean; }) { const [selectedDate, setSelectedDate] = useState(); const [editingDay, setEditingDay] = useState(false); const [calendar, setCalendar] = useState(null); const [error, setError] = useState(""); const [attempt, setAttempt] = useState(0); const expired = useRef(onExpired); expired.current = onExpired; useEffect(() => { const controller = new AbortController(); setError(""); const range = monthWindow(date, 12); habitRequest( `/habits/${habit.habitId}/calendar?${new URLSearchParams(range)}`, { signal: controller.signal }, ) .then((result) => { if (!controller.signal.aborted) setCalendar(result); }) .catch((error) => { if (controller.signal.aborted) return; if (error.message.includes("session has expired") && expired.current) expired.current(); else setError(error.message); }); return () => controller.abort(); }, [habit.habitId, date, revision, attempt]); const days = calendar?.days.map((day) => ({ date: day.date, color: day.color, unit: day.habits[0]?.unit, value: day.habits[0]?.value ?? 0, target: day.habits[0]?.target ?? 0, state: day.future ? ("future" as const) : day.due ? ("due" as const) : ("not-due" as const), })) ?? []; const steps = Math.max( 1, calendar?.days.find((day) => day.date === date)?.shadeCount ?? habit.target ?? 1, ); const shades = calendar ? [ calendar.settings.emptyColor, ...Array.from( { length: Math.min(8, steps) }, (_, index) => shade( Math.ceil(((index + 1) * steps) / Math.min(8, steps)) / steps, steps, calendar.settings, true, ).color, ), ] : []; const chart = error ? (

{error}

) : !calendar ? (

Loading your habit history…

) : ( <> } />
{editingDay &&
{id => { if (event.target.value) setSelectedDate(event.target.value); }} />}

Update the progress recorded for this day.

{ setAttempt(value => value + 1); onHistorySaved?.(); }} />
} ); if (calendarOnly) return chart; return ( onEdit(calendar!.settings.mainColor)} onDelete={onDelete} /> ) : undefined} > {children} {(onEdit || onDelete) && !(onEdit && onDelete) && (
{onEdit && ( )} {onDelete && ( )}
)}
); }