import { createContext, type ReactNode, useCallback, useContext, useEffect, useRef, useState, } from "react"; import type { CreateNonPeriodicExpenseInput, CreatePeriodicExpenseInput, Expense, ImportExpensesResult, ImportResult, MonthlyExpensesTotal, MonthlyTotals, PaginatedResponse, PayExpenseInput, PeriodicExpense, UpdateExpenseInput, } from "@/lib/api"; import { useCreateNonPeriodicExpense, useCreatePeriodicExpense, useDeletePeriodicExpense, useExpenses, useGenerateMonthlyExpense, useImportExpenses, useImportPeriodicExpenses, useMonthlyTotals, usePayExpense, usePeriodicExpenses, useTotalPending, useUpdateExpense, useUpdatePeriodicExpense, } from "@/lib/queries"; interface ExpensesContextValue { periodicExpenses: PeriodicExpense[]; isLoadingPeriodic: boolean; expenses: PaginatedResponse; isLoadingExpenses: boolean; statusFilter: string; setStatusFilter: (status: string) => void; page: number; setPage: (page: number) => void; pageSize: number; periodicExpenseFilter: number | undefined; setPeriodicExpenseFilter: (id: number | undefined) => void; searchInput: string; setSearchInput: (value: string) => void; monthlyTotals: MonthlyTotals | undefined; isLoadingTotals: boolean; totalPending: MonthlyExpensesTotal | undefined; isLoadingTotalPending: boolean; createPeriodicExpense: ( data: CreatePeriodicExpenseInput, ) => Promise; updatePeriodicExpense: ( id: number, data: CreatePeriodicExpenseInput, ) => Promise; deletePeriodicExpense: (id: number) => Promise; generateMonthlyExpense: (id: number) => Promise; createNonPeriodicExpense: ( data: CreateNonPeriodicExpenseInput, ) => Promise; payExpense: (id: number, data: PayExpenseInput) => Promise; updateExpense: (id: number, data: UpdateExpenseInput) => Promise; importPeriodicExpenses: (file: File) => Promise; importExpenses: (file: File) => Promise; } const ExpensesContext = createContext(null); export function ExpensesProvider({ children, initialPeriodicExpenseId, }: { children: ReactNode; initialPeriodicExpenseId?: number; }) { const [statusFilter, setStatusFilter] = useState("PENDING"); const [page, setPage] = useState(1); const pageSize = 10; const [periodicExpenseFilter, setPeriodicExpenseFilter] = useState< number | undefined >(initialPeriodicExpenseId); const [searchInput, setSearchInput] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const searchTimerRef = useRef>(undefined); useEffect(() => { if (searchTimerRef.current) clearTimeout(searchTimerRef.current); searchTimerRef.current = setTimeout(() => { setSearchQuery(searchInput); }, 300); return () => { if (searchTimerRef.current) clearTimeout(searchTimerRef.current); }; }, [searchInput]); useEffect(() => { setPage(1); }, []); const { data: periodicExpenses = [], isLoading: isLoadingPeriodic } = usePeriodicExpenses(); const { data: expenses = { data: [], total: 0, page: 1, pageSize: 10 }, isLoading: isLoadingExpenses, } = useExpenses( statusFilter, page, pageSize, periodicExpenseFilter, searchQuery || undefined, ); const now = new Date(); const { data: monthlyTotals, isLoading: isLoadingTotals } = useMonthlyTotals( now.getFullYear(), now.getMonth() + 1, ); const { data: totalPending, isLoading: isLoadingTotalPending } = useTotalPending(); const createPeriodicMutation = useCreatePeriodicExpense(); const updatePeriodicMutation = useUpdatePeriodicExpense(); const deletePeriodicMutation = useDeletePeriodicExpense(); const generateMonthlyMutation = useGenerateMonthlyExpense(); const createExpenseMutation = useCreateNonPeriodicExpense(); const payExpenseMutation = usePayExpense(); const updateExpenseMutation = useUpdateExpense(); const importPeriodicMutation = useImportPeriodicExpenses(); const importExpensesMutation = useImportExpenses(); const importPeriodicExpensesFn = useCallback( async (file: File): Promise => { return importPeriodicMutation.mutateAsync(file); }, [importPeriodicMutation], ); const importExpensesFn = useCallback( async (file: File): Promise => { return importExpensesMutation.mutateAsync(file); }, [importExpensesMutation], ); const createPeriodicExpense = useCallback( async (data: CreatePeriodicExpenseInput): Promise => { return createPeriodicMutation.mutateAsync(data); }, [createPeriodicMutation], ); const updatePeriodicExpenseFn = useCallback( async (id: number, data: CreatePeriodicExpenseInput) => { await updatePeriodicMutation.mutateAsync({ id, data }); }, [updatePeriodicMutation], ); const deletePeriodicExpenseFn = useCallback( async (id: number) => { await deletePeriodicMutation.mutateAsync(id); }, [deletePeriodicMutation], ); const generateMonthlyExpenseFn = useCallback( async (id: number): Promise => { return generateMonthlyMutation.mutateAsync(id); }, [generateMonthlyMutation], ); const createNonPeriodicExpenseFn = useCallback( async (data: CreateNonPeriodicExpenseInput) => { await createExpenseMutation.mutateAsync(data); }, [createExpenseMutation], ); const payExpenseFn = useCallback( async (id: number, data: PayExpenseInput) => { await payExpenseMutation.mutateAsync({ id, data }); }, [payExpenseMutation], ); const updateExpenseFn = useCallback( async (id: number, data: UpdateExpenseInput) => { await updateExpenseMutation.mutateAsync({ id, data }); }, [updateExpenseMutation], ); return ( {children} ); } export function useExpensesContext() { const ctx = useContext(ExpensesContext); if (!ctx) throw new Error("useExpensesContext must be used within ExpensesProvider"); return ctx; }