"use client"; import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { JournalEntryModal } from "@/components/journal/journal-entry-modal"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { HABIT_PET_DATA_UPDATED_EVENT } from "@/lib/app-events"; import type { DailyQuizSubmission } from "@/lib/avatar-state"; import { getDailyEntryByDate, getDailyEntrySummariesForMonth, getTodayDateKey, type DailyEntrySummary, } from "@/lib/daily-quiz-storage"; import { cn } from "@/lib/utils"; const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; type CalendarCell = { dateKey: string; day: number; inMonth: boolean; }; function buildMonthGrid(year: number, month: number): CalendarCell[] { const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const grid: CalendarCell[] = []; for (let index = 0; index < firstDay.getDay(); index += 1) { const date = new Date(year, month, index - firstDay.getDay() + 1); grid.push({ dateKey: date.toLocaleDateString("en-CA"), day: date.getDate(), inMonth: false, }); } for (let day = 1; day <= lastDay.getDate(); day += 1) { const date = new Date(year, month, day); grid.push({ dateKey: date.toLocaleDateString("en-CA"), day, inMonth: true, }); } let trailingDay = 1; while (grid.length % 7 !== 0) { const date = new Date(year, month + 1, trailingDay); grid.push({ dateKey: date.toLocaleDateString("en-CA"), day: date.getDate(), inMonth: false, }); trailingDay += 1; } return grid; } function formatMonthLabel(year: number, month: number) { return new Date(year, month, 1).toLocaleDateString(undefined, { month: "long", year: "numeric", }); } export function JournalCalendar() { const todayKey = getTodayDateKey(); const todayDate = new Date(); const [visibleYear, setVisibleYear] = useState(todayDate.getFullYear()); const [visibleMonth, setVisibleMonth] = useState(todayDate.getMonth()); const [summaries, setSummaries] = useState([]); const [selectedDateKey, setSelectedDateKey] = useState(null); const [selectedEntry, setSelectedEntry] = useState( null, ); const [isExpanded, setIsExpanded] = useState(false); const [isLoadingMonth, setIsLoadingMonth] = useState(false); const [isLoadingEntry, setIsLoadingEntry] = useState(false); const [error, setError] = useState(null); const summaryByDate = useMemo( () => new Map(summaries.map((summary) => [summary.date, summary])), [summaries], ); const monthGrid = useMemo( () => buildMonthGrid(visibleYear, visibleMonth), [visibleYear, visibleMonth], ); const loadMonth = useCallback(async () => { setIsLoadingMonth(true); setError(null); try { setSummaries( await getDailyEntrySummariesForMonth(visibleYear, visibleMonth), ); } catch (loadError) { setError( loadError instanceof Error ? loadError.message : "Could not load journal history.", ); } finally { setIsLoadingMonth(false); } }, [visibleMonth, visibleYear]); useEffect(() => { if (!isExpanded) { return; } void loadMonth(); }, [isExpanded, loadMonth]); useEffect(() => { if (!isExpanded) { return; } const handleDataUpdated = () => { void loadMonth(); }; window.addEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated); return () => { window.removeEventListener(HABIT_PET_DATA_UPDATED_EVENT, handleDataUpdated); }; }, [isExpanded, loadMonth]); const goToPreviousMonth = () => { if (visibleMonth === 0) { setVisibleYear((current) => current - 1); setVisibleMonth(11); return; } setVisibleMonth((current) => current - 1); }; const goToNextMonth = () => { if (visibleMonth === 11) { setVisibleYear((current) => current + 1); setVisibleMonth(0); return; } setVisibleMonth((current) => current + 1); }; const handleDaySelect = async (dateKey: string) => { const summary = summaryByDate.get(dateKey); if (!summary) { return; } setSelectedDateKey(dateKey); setIsLoadingEntry(true); setError(null); try { setSelectedEntry(await getDailyEntryByDate(dateKey)); } catch (loadError) { setSelectedDateKey(null); setError( loadError instanceof Error ? loadError.message : "Could not load that journal entry.", ); } finally { setIsLoadingEntry(false); } }; const closeEntryModal = () => { setSelectedEntry(null); setSelectedDateKey(null); }; return ( <>
{isExpanded ? (

{formatMonthLabel(visibleYear, visibleMonth)}

{WEEKDAY_LABELS.map((label) => (
{label}
))}
{monthGrid.map((cell) => { const summary = summaryByDate.get(cell.dateKey); const hasEntry = Boolean(summary); const isToday = cell.dateKey === todayKey; const isSelected = cell.dateKey === selectedDateKey; return ( ); })}
Journal written Check-in only
{isLoadingMonth ? (

Loading calendar...

) : null} {error ?

{error}

: null}
) : null}
); }