All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m11s
227 lines
6.3 KiB
TypeScript
227 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { useTransactions } from "@/lib/hooks";
|
|
import { useBankingMetadata } from "@/lib/hooks";
|
|
import type { TransactionsPaginatedParams } from "@/services/banking.service";
|
|
import type { Category } from "@/lib/types";
|
|
|
|
interface MonthlyChartData {
|
|
month: string;
|
|
revenus: number;
|
|
depenses: number;
|
|
solde: number;
|
|
}
|
|
|
|
interface CategoryChartData {
|
|
name: string;
|
|
value: number;
|
|
color: string;
|
|
icon: string;
|
|
categoryId: string | null;
|
|
}
|
|
|
|
interface UseTransactionsChartDataParams {
|
|
selectedAccounts: string[];
|
|
selectedCategories: string[];
|
|
period: "1month" | "3months" | "6months" | "12months" | "custom" | "all";
|
|
customStartDate?: Date;
|
|
customEndDate?: Date;
|
|
showReconciled: string;
|
|
searchQuery: string;
|
|
}
|
|
|
|
export function useTransactionsChartData({
|
|
selectedAccounts,
|
|
selectedCategories,
|
|
period,
|
|
customStartDate,
|
|
customEndDate,
|
|
showReconciled,
|
|
searchQuery,
|
|
}: UseTransactionsChartDataParams) {
|
|
const { data: metadata } = useBankingMetadata();
|
|
|
|
// Calculate start date based on period
|
|
const startDate = useMemo(() => {
|
|
const now = new Date();
|
|
switch (period) {
|
|
case "1month":
|
|
return new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
case "3months":
|
|
return new Date(now.getFullYear(), now.getMonth() - 3, 1);
|
|
case "6months":
|
|
return new Date(now.getFullYear(), now.getMonth() - 6, 1);
|
|
case "12months":
|
|
return new Date(now.getFullYear(), now.getMonth() - 12, 1);
|
|
case "custom":
|
|
return customStartDate || new Date(0);
|
|
default:
|
|
return new Date(0);
|
|
}
|
|
}, [period, customStartDate]);
|
|
|
|
// Calculate end date (only for custom period)
|
|
const endDate = useMemo(() => {
|
|
if (period === "custom" && customEndDate) {
|
|
return customEndDate;
|
|
}
|
|
return undefined;
|
|
}, [period, customEndDate]);
|
|
|
|
// Build params for fetching all transactions (no pagination)
|
|
const chartParams = useMemo<TransactionsPaginatedParams>(() => {
|
|
const params: TransactionsPaginatedParams = {
|
|
limit: 10000, // Large limit to get all transactions
|
|
offset: 0,
|
|
sortField: "date",
|
|
sortOrder: "asc",
|
|
};
|
|
|
|
if (startDate && period !== "all") {
|
|
params.startDate = startDate.toISOString().split("T")[0];
|
|
}
|
|
if (endDate) {
|
|
params.endDate = endDate.toISOString().split("T")[0];
|
|
}
|
|
if (!selectedAccounts.includes("all")) {
|
|
params.accountIds = selectedAccounts;
|
|
}
|
|
if (!selectedCategories.includes("all")) {
|
|
if (selectedCategories.includes("uncategorized")) {
|
|
params.includeUncategorized = true;
|
|
} else {
|
|
params.categoryIds = selectedCategories;
|
|
}
|
|
}
|
|
if (searchQuery) {
|
|
params.search = searchQuery;
|
|
}
|
|
if (showReconciled !== "all") {
|
|
params.isReconciled = showReconciled === "reconciled";
|
|
}
|
|
|
|
return params;
|
|
}, [
|
|
startDate,
|
|
endDate,
|
|
selectedAccounts,
|
|
selectedCategories,
|
|
searchQuery,
|
|
showReconciled,
|
|
period,
|
|
]);
|
|
|
|
// Fetch all filtered transactions for chart
|
|
const { data: transactionsData, isLoading } = useTransactions(
|
|
chartParams,
|
|
!!metadata
|
|
);
|
|
|
|
// Calculate monthly chart data
|
|
const monthlyData = useMemo(() => {
|
|
if (!transactionsData || !metadata) {
|
|
return [];
|
|
}
|
|
|
|
const transactions = transactionsData.transactions;
|
|
|
|
// Monthly breakdown
|
|
const monthlyMap = new Map<string, { income: number; expenses: number }>();
|
|
transactions.forEach((t) => {
|
|
const monthKey = t.date.substring(0, 7);
|
|
const current = monthlyMap.get(monthKey) || { income: 0, expenses: 0 };
|
|
if (t.amount >= 0) {
|
|
current.income += t.amount;
|
|
} else {
|
|
current.expenses += Math.abs(t.amount);
|
|
}
|
|
monthlyMap.set(monthKey, current);
|
|
});
|
|
|
|
// Format months with year: use short format for better readability
|
|
const sortedMonths = Array.from(monthlyMap.entries()).sort((a, b) =>
|
|
a[0].localeCompare(b[0])
|
|
);
|
|
|
|
const monthlyChartData: MonthlyChartData[] = sortedMonths.map(
|
|
([monthKey, values]) => {
|
|
const date = new Date(monthKey + "-01");
|
|
|
|
// Format: "janv. 24" instead of "janv. 2024" for compactness
|
|
const monthLabel = date.toLocaleDateString("fr-FR", {
|
|
month: "short",
|
|
});
|
|
const yearShort = date.getFullYear().toString().slice(-2);
|
|
|
|
return {
|
|
month: `${monthLabel} ${yearShort}`,
|
|
revenus: values.income,
|
|
depenses: values.expenses,
|
|
solde: values.income - values.expenses,
|
|
};
|
|
}
|
|
);
|
|
|
|
return monthlyChartData;
|
|
}, [transactionsData, metadata]);
|
|
|
|
// Calculate category chart data (expenses only)
|
|
const categoryData = useMemo(() => {
|
|
if (!transactionsData || !metadata) {
|
|
return [];
|
|
}
|
|
|
|
const transactions = transactionsData.transactions;
|
|
const categoryTotals = new Map<string, number>();
|
|
|
|
transactions
|
|
.filter((t) => t.amount < 0)
|
|
.forEach((t) => {
|
|
const catId = t.categoryId || "uncategorized";
|
|
const current = categoryTotals.get(catId) || 0;
|
|
categoryTotals.set(catId, current + Math.abs(t.amount));
|
|
});
|
|
|
|
const categoryChartData: CategoryChartData[] = Array.from(
|
|
categoryTotals.entries()
|
|
)
|
|
.map(([categoryId, total]) => {
|
|
const category = metadata.categories.find((c: Category) => c.id === categoryId);
|
|
return {
|
|
name: category?.name || "Non catégorisé",
|
|
value: Math.round(total),
|
|
color: category?.color || "#94a3b8",
|
|
icon: category?.icon || "HelpCircle",
|
|
categoryId: categoryId === "uncategorized" ? null : categoryId,
|
|
};
|
|
})
|
|
.sort((a, b) => b.value - a.value);
|
|
|
|
return categoryChartData;
|
|
}, [transactionsData, metadata]);
|
|
|
|
// Calculate total amount and count from all filtered transactions
|
|
const totalAmount = useMemo(() => {
|
|
if (!transactionsData) return 0;
|
|
return transactionsData.transactions.reduce(
|
|
(sum, t) => sum + t.amount,
|
|
0
|
|
);
|
|
}, [transactionsData]);
|
|
|
|
const totalCount = useMemo(() => {
|
|
return transactionsData?.total || 0;
|
|
}, [transactionsData?.total]);
|
|
|
|
return {
|
|
monthlyData,
|
|
categoryData,
|
|
isLoading,
|
|
totalAmount,
|
|
totalCount,
|
|
transactions: transactionsData?.transactions || [],
|
|
};
|
|
}
|
|
|