'use client'; import { useEffect, useState } from 'react'; import { Counter } from './CounterCard'; import HistoryChart, { DayValue } from './HistoryChart'; import CalendarView, { DayCounter } from './CalendarView'; export interface HistoryData { counter: Counter; dailyActivity: DayValue[]; allTimeTotal: number; } interface Props { counterId: number | null; cache?: Map; onClose: () => void; } function toLocalDateStr(d: Date): string { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } function computeCurrentStreak(dateSet: Set): number { const today = new Date(); let streak = 0; const startOffset = dateSet.has(toLocalDateStr(today)) ? 0 : 1; for (let i = startOffset; i < 400; i++) { const d = new Date(today); d.setDate(d.getDate() - i); if (dateSet.has(toLocalDateStr(d))) streak++; else break; } return streak; } function computeLongestStreak(sortedDates: string[]): number { if (sortedDates.length === 0) return 0; let best = 1, cur = 1; for (let i = 1; i < sortedDates.length; i++) { const prev = new Date(sortedDates[i - 1] + 'T00:00:00'); const curr = new Date(sortedDates[i] + 'T00:00:00'); const diff = (curr.getTime() - prev.getTime()) / 86400000; if (diff === 1) { cur++; if (cur > best) best = cur; } else cur = 1; } return best; } export default function CounterDetailModal({ counterId, cache, onClose }: Props) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { if (!counterId) return; const cached = cache?.get(counterId); if (cached) { setData(cached); setLoading(false); return; } setData(null); setLoading(true); fetch(`/api/counters/${counterId}/history`) .then(r => r.json()) .then((d: HistoryData) => { cache?.set(counterId, d); setData(d); setLoading(false); }) .catch(() => setLoading(false)); }, [counterId, cache]); useEffect(() => { if (!counterId) return; const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [counterId, onClose]); if (!counterId) return null; const dateSet = new Set(data?.dailyActivity.map(d => d.date) ?? []); const sortedDates = [...dateSet].sort(); const currentStreak = computeCurrentStreak(dateSet); const longestStreak = computeLongestStreak(sortedDates); const calendarData: DayCounter[] = (data?.dailyActivity ?? []).map(d => ({ date: d.date, counter_id: data!.counter.id, counter_name: data!.counter.name, image_path: data!.counter.image_path, increments: d.value, })); const stats = data ? [ { label: 'Current value', value: data.counter.value.toLocaleString() }, { label: 'All-time net', value: data.allTimeTotal >= 0 ? `+${data.allTimeTotal}` : String(data.allTimeTotal) }, { label: 'Current streak', value: `${currentStreak}d` }, { label: 'Longest streak', value: `${longestStreak}d` }, ] : []; return (
e.stopPropagation()} > {/* Header */}
{data?.counter.image_path && ( // eslint-disable-next-line @next/next/no-img-element )}

{data?.counter.name ?? '…'}

{/* Body */}
{loading && (

Loading…

)} {!loading && data && ( <> {/* Stat cards */}
{stats.map(({ label, value }) => (

{value}

{label}

))}
{data.dailyActivity.length === 0 ? (

No activity in the last year.

Start tapping + to see history here.

) : ( <> {/* 90-day bar chart */}

Last 90 Days

{/* Calendar */}

Activity Calendar

)} )}
); }