import { useState, useEffect, useCallback } from 'react' import * as api from '@/services/api' import { useLatestRequest } from './useLatestRequest' import { useWorkspace } from '@/context' import type { WorkspaceOverview } from '@/types' interface UseAnalyticsResult { data: WorkspaceOverview | null isLoading: boolean error: string | null refresh: () => Promise } export function useAnalytics(workspaceId: string | null, childId?: string): UseAnalyticsResult { const [data, setData] = useState(null) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const { start, isCurrent } = useLatestRequest() const { planVersion } = useWorkspace() const fetch = useCallback(async () => { const requestId = start() if (!workspaceId || !childId) { // The identity this data belongs to is gone (e.g. a logout cleared // the workspace/child selection) — clear rather than leave the // previous account's figures rendered until a new id arrives. setData(null) setError(null) return } setIsLoading(true) setError(null) try { const response = await api.getWorkspaceOverview(workspaceId, childId) if (!isCurrent(requestId)) return setData(response) } catch (err) { if (!isCurrent(requestId)) return setError(err instanceof Error ? err.message : 'Failed to fetch analytics') setData(null) } finally { // Unguarded: `isCurrent` decides which response's DATA wins, not // whether a superseded call's own busy flag gets cleared — a stale // call must still release the flag IT raised, or it can strand that // flag true forever (see usePlannedDays.ts for the hang this exact // guard caused there). setIsLoading(false) } }, [workspaceId, childId, start, isCurrent]) // `planVersion` (bumped by usePlannedDays/useDayOverrides mutations) is // included so a plan edit made through a different hook instance — e.g. // the Calendar's `usePlannedDays` — refreshes the at-risk figures here // too, without this hook knowing anything about who mutated. See // `WorkspaceContext.planVersion`. useEffect(() => { fetch() }, [fetch, planVersion]) return { data, isLoading, error, refresh: fetch } }