import { useState, useEffect } from 'react'; import { useOverview } from '../../hooks/useOverview'; import { useStreakCalendar } from '../../hooks/useStreakCalendar'; import { HeroStats } from './HeroStats'; import { StreakCalendar } from './StreakCalendar'; import { RecentSessions } from './RecentSessions'; import { TrackingSnapshot } from './TrackingSnapshot'; import { TrendChart } from '../trends/TrendChart'; import { buildOverviewSummary, buildStreakCalendar } from '../../lib/dashboard-data'; import { apiClient } from '../../lib/api-client'; import { getStatsClient } from '../../hooks/useStatsApi'; import { confirmSessionDelete, confirmDayGroupDelete, confirmAnimeGroupDelete, } from '../../lib/delete-confirm'; import type { SessionSummary } from '../../types/stats'; interface OverviewTabProps { onNavigateToMediaDetail: (videoId: number, sessionId?: number | null) => void; onNavigateToSession: (sessionId: number) => void; } export function OverviewTab({ onNavigateToMediaDetail, onNavigateToSession }: OverviewTabProps) { const { data, sessions, setSessions, loading, error } = useOverview(); const { calendar, loading: calLoading } = useStreakCalendar(90); const [deleteError, setDeleteError] = useState(null); const [deletingIds, setDeletingIds] = useState>(new Set()); const [knownWordsSummary, setKnownWordsSummary] = useState<{ totalUniqueWords: number; knownWordCount: number; } | null>(null); useEffect(() => { let cancelled = false; getStatsClient() .getKnownWordsSummary() .then((data) => { if (!cancelled) setKnownWordsSummary(data); }) .catch(() => { if (!cancelled) setKnownWordsSummary(null); }); return () => { cancelled = true; }; }, []); const handleDeleteSession = async (session: SessionSummary) => { if (!confirmSessionDelete()) return; setDeleteError(null); setDeletingIds((prev) => new Set(prev).add(session.sessionId)); try { await apiClient.deleteSession(session.sessionId); setSessions((prev) => prev.filter((s) => s.sessionId !== session.sessionId)); } catch (err) { setDeleteError(err instanceof Error ? err.message : 'Failed to delete session.'); } finally { setDeletingIds((prev) => { const next = new Set(prev); next.delete(session.sessionId); return next; }); } }; const handleDeleteDayGroup = async (dayLabel: string, daySessions: SessionSummary[]) => { if (!confirmDayGroupDelete(dayLabel, daySessions.length)) return; setDeleteError(null); const ids = daySessions.map((s) => s.sessionId); setDeletingIds((prev) => { const next = new Set(prev); for (const id of ids) next.add(id); return next; }); try { await apiClient.deleteSessions(ids); const idSet = new Set(ids); setSessions((prev) => prev.filter((s) => !idSet.has(s.sessionId))); } catch (err) { setDeleteError(err instanceof Error ? err.message : 'Failed to delete sessions.'); } finally { setDeletingIds((prev) => { const next = new Set(prev); for (const id of ids) next.delete(id); return next; }); } }; const handleDeleteAnimeGroup = async (groupSessions: SessionSummary[]) => { const title = groupSessions[0]?.animeTitle ?? groupSessions[0]?.canonicalTitle ?? 'Unknown Media'; if (!confirmAnimeGroupDelete(title, groupSessions.length)) return; setDeleteError(null); const ids = groupSessions.map((s) => s.sessionId); setDeletingIds((prev) => { const next = new Set(prev); for (const id of ids) next.add(id); return next; }); try { await apiClient.deleteSessions(ids); const idSet = new Set(ids); setSessions((prev) => prev.filter((s) => !idSet.has(s.sessionId))); } catch (err) { setDeleteError(err instanceof Error ? err.message : 'Failed to delete sessions.'); } finally { setDeletingIds((prev) => { const next = new Set(prev); for (const id of ids) next.delete(id); return next; }); } }; if (loading) return
Loading...
; if (error) return
Error: {error}
; if (!data) return null; const summary = buildOverviewSummary(data); const streakData = buildStreakCalendar(calendar); const showTrackedCardNote = summary.totalTrackedCards === 0 && summary.activeDays > 0; return (
{!calLoading && }
{deleteError ?
{deleteError}
: null}
); }