'use client'; import React, { createContext, useContext, useState, useMemo, useCallback, ReactNode } from 'react'; import { DashboardData, DashboardConfig, PipelineRecord, AccountRecord, ActivityRecord, TargetRecord } from '@/types/data'; import { startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfQuarter, endOfQuarter, subDays, parseISO, isWithinInterval } from 'date-fns'; export type DatePreset = 'this_week' | 'last_7' | 'this_month' | 'this_quarter' | 'last_quarter' | 'custom'; interface FilterState { districts: string[]; datePreset: DatePreset; dateRange: { start: string; end: string }; crossFilter: { key: string; value: string } | null; } interface DataContextValue { raw: DashboardData; config: DashboardConfig; filters: FilterState; setDistricts: (d: string[]) => void; setDatePreset: (p: DatePreset) => void; setDateRange: (start: string, end: string) => void; setCrossFilter: (key: string, value: string) => void; clearCrossFilter: () => void; clearAllFilters: () => void; filtered: { pipeline: PipelineRecord[]; accounts: AccountRecord[]; activities: ActivityRecord[]; targets: TargetRecord[]; }; isLoading: boolean; lastRefreshed: string; refresh: () => Promise; } const DataContext = createContext(null); function getDateRange(preset: DatePreset, custom?: { start: string; end: string }): { start: Date; end: Date } { const now = new Date(); switch (preset) { case 'this_week': return { start: startOfWeek(now, { weekStartsOn: 1 }), end: endOfWeek(now, { weekStartsOn: 1 }) }; case 'last_7': return { start: subDays(now, 7), end: now }; case 'this_month': return { start: startOfMonth(now), end: endOfMonth(now) }; case 'this_quarter': return { start: startOfQuarter(now), end: endOfQuarter(now) }; case 'last_quarter': { const lastQ = new Date(now); lastQ.setMonth(lastQ.getMonth() - 3); return { start: startOfQuarter(lastQ), end: endOfQuarter(lastQ) }; } case 'custom': if (custom) return { start: parseISO(custom.start), end: parseISO(custom.end) }; return { start: subDays(now, 30), end: now }; } } export function DataProvider({ children, initialData, config }: { children: ReactNode; initialData: DashboardData; config: DashboardConfig }) { const [data, setData] = useState(initialData); const [isLoading, setIsLoading] = useState(false); const [filters, setFilters] = useState({ districts: [], datePreset: 'this_quarter', dateRange: { start: '', end: '' }, crossFilter: null, }); const setDistricts = useCallback((d: string[]) => { setFilters(f => ({ ...f, districts: d })); }, []); const setDatePreset = useCallback((p: DatePreset) => { setFilters(f => ({ ...f, datePreset: p })); }, []); const setDateRange = useCallback((start: string, end: string) => { setFilters(f => ({ ...f, datePreset: 'custom', dateRange: { start, end } })); }, []); const setCrossFilter = useCallback((key: string, value: string) => { setFilters(f => ({ ...f, crossFilter: { key, value } })); }, []); const clearCrossFilter = useCallback(() => { setFilters(f => ({ ...f, crossFilter: null })); }, []); const clearAllFilters = useCallback(() => { setFilters({ districts: [], datePreset: 'this_quarter', dateRange: { start: '', end: '' }, crossFilter: null }); }, []); const refresh = useCallback(async () => { setIsLoading(true); try { const res = await fetch('/api/sheets?bust=' + Date.now()); if (res.ok) { const newData = await res.json(); setData(newData); } } finally { setIsLoading(false); } }, []); const filtered = useMemo(() => { const { districts: distFilter, datePreset, dateRange, crossFilter } = filters; const range = getDateRange(datePreset, dateRange); let pipeline = data.pipeline; let accounts = data.accounts; let activities = data.activities; let targets = data.targets; if (distFilter.length > 0) { pipeline = pipeline.filter(p => distFilter.includes(p.District_Name)); accounts = accounts.filter(a => distFilter.includes(a.District_Name)); activities = activities.filter(a => distFilter.includes(a.District_Name)); } activities = activities.filter(a => { try { return isWithinInterval(parseISO(a.Activity_Date), range); } catch { return true; } }); if (crossFilter) { const { key, value } = crossFilter; const match = (obj: object) => String((obj as never)[key as never]) === value; pipeline = pipeline.filter(match); accounts = accounts.filter(match); activities = activities.filter(match); targets = targets.filter(match); } if (distFilter.length > 0) { const accountNames = new Set(accounts.map(a => a.Account_Name)); targets = targets.filter(t => accountNames.has(t.Account_Name)); } return { pipeline, accounts, activities, targets }; }, [data, filters]); return ( {children} ); } export function useData() { const ctx = useContext(DataContext); if (!ctx) throw new Error('useData must be used within DataProvider'); return ctx; }