248 lines
6.9 KiB
TypeScript
248 lines
6.9 KiB
TypeScript
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<Expense>;
|
|
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<PeriodicExpense>;
|
|
updatePeriodicExpense: (
|
|
id: number,
|
|
data: CreatePeriodicExpenseInput,
|
|
) => Promise<void>;
|
|
deletePeriodicExpense: (id: number) => Promise<void>;
|
|
generateMonthlyExpense: (id: number) => Promise<Expense>;
|
|
createNonPeriodicExpense: (
|
|
data: CreateNonPeriodicExpenseInput,
|
|
) => Promise<void>;
|
|
payExpense: (id: number, data: PayExpenseInput) => Promise<void>;
|
|
updateExpense: (id: number, data: UpdateExpenseInput) => Promise<void>;
|
|
importPeriodicExpenses: (file: File) => Promise<ImportResult>;
|
|
importExpenses: (file: File) => Promise<ImportExpensesResult>;
|
|
}
|
|
|
|
const ExpensesContext = createContext<ExpensesContextValue | null>(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<ReturnType<typeof setTimeout>>(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<ImportResult> => {
|
|
return importPeriodicMutation.mutateAsync(file);
|
|
},
|
|
[importPeriodicMutation],
|
|
);
|
|
|
|
const importExpensesFn = useCallback(
|
|
async (file: File): Promise<ImportExpensesResult> => {
|
|
return importExpensesMutation.mutateAsync(file);
|
|
},
|
|
[importExpensesMutation],
|
|
);
|
|
|
|
const createPeriodicExpense = useCallback(
|
|
async (data: CreatePeriodicExpenseInput): Promise<PeriodicExpense> => {
|
|
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<Expense> => {
|
|
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 (
|
|
<ExpensesContext
|
|
value={{
|
|
periodicExpenses,
|
|
isLoadingPeriodic,
|
|
expenses,
|
|
isLoadingExpenses,
|
|
statusFilter,
|
|
setStatusFilter,
|
|
page,
|
|
setPage,
|
|
pageSize,
|
|
periodicExpenseFilter,
|
|
setPeriodicExpenseFilter,
|
|
searchInput,
|
|
setSearchInput,
|
|
monthlyTotals,
|
|
isLoadingTotals,
|
|
totalPending,
|
|
isLoadingTotalPending,
|
|
createPeriodicExpense,
|
|
updatePeriodicExpense: updatePeriodicExpenseFn,
|
|
deletePeriodicExpense: deletePeriodicExpenseFn,
|
|
generateMonthlyExpense: generateMonthlyExpenseFn,
|
|
createNonPeriodicExpense: createNonPeriodicExpenseFn,
|
|
payExpense: payExpenseFn,
|
|
updateExpense: updateExpenseFn,
|
|
importPeriodicExpenses: importPeriodicExpensesFn,
|
|
importExpenses: importExpensesFn,
|
|
}}
|
|
>
|
|
{children}
|
|
</ExpensesContext>
|
|
);
|
|
}
|
|
|
|
export function useExpensesContext() {
|
|
const ctx = useContext(ExpensesContext);
|
|
if (!ctx)
|
|
throw new Error("useExpensesContext must be used within ExpensesProvider");
|
|
return ctx;
|
|
}
|