feat(analytics): Fix transaction limits and improve chart legends

Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-13 19:18:36 +00:00
committed by Elisiário Couto
parent 692bee574e
commit e136fc4b75
15 changed files with 691 additions and 180 deletions

View File

@@ -8,10 +8,11 @@ import {
ResponsiveContainer,
Legend,
} from "recharts";
import type { Balance } from "../../types/api";
import type { Balance, Account } from "../../types/api";
interface BalanceChartProps {
data: Balance[];
accounts: Account[];
className?: string;
}
@@ -26,7 +27,34 @@ interface AggregatedDataPoint {
[key: string]: string | number;
}
export default function BalanceChart({ data, className }: BalanceChartProps) {
export default function BalanceChart({ data, accounts, className }: BalanceChartProps) {
// Create a lookup map for account info
const accountMap = accounts.reduce((map, account) => {
map[account.id] = account;
return map;
}, {} as Record<string, Account>);
// Helper function to get bank name from institution_id
const getBankName = (institutionId: string): string => {
const bankMapping: Record<string, string> = {
'REVOLUT_REVOLT21': 'Revolut',
'NUBANK_NUPBBR25': 'Nu Pagamentos',
'BANCOBPI_BBPIPTPL': 'Banco BPI',
// Add more mappings as needed
};
return bankMapping[institutionId] || institutionId.split('_')[0];
};
// Helper function to create display name for account
const getAccountDisplayName = (accountId: string): string => {
const account = accountMap[accountId];
if (account) {
const bankName = getBankName(account.institution_id);
const accountName = account.name || `Account ${accountId.split('-')[1]}`;
return `${bankName} - ${accountName}`;
}
return `Account ${accountId.split('-')[1]}`;
};
// Process balance data for the chart
const chartData = data
.filter((balance) => balance.balance_type === "closingBooked")
@@ -116,7 +144,7 @@ export default function BalanceChart({ data, className }: BalanceChartProps) {
stroke={colors[index % colors.length]}
strokeWidth={2}
dot={{ r: 4 }}
name={`Account ${accountId.split('-')[1]}`}
name={getAccountDisplayName(accountId)}
/>
))}
</LineChart>

View File

@@ -32,18 +32,12 @@ interface TooltipProps {
}
export default function MonthlyTrends({ className }: MonthlyTrendsProps) {
// Get transactions for the last 12 months
// Get transactions for the last 12 months using analytics endpoint
const { data: transactions, isLoading } = useQuery({
queryKey: ["transactions", "monthly-trends"],
queryFn: async () => {
const response = await apiClient.getTransactions({
startDate: new Date(
Date.now() - 365 * 24 * 60 * 60 * 1000
).toISOString().split("T")[0],
endDate: new Date().toISOString().split("T")[0],
perPage: 1000,
});
return response.data;
// Get last 365 days of transactions for monthly trends
return await apiClient.getTransactionsForAnalytics(365);
},
});
@@ -54,7 +48,7 @@ export default function MonthlyTrends({ className }: MonthlyTrendsProps) {
const monthlyMap: { [key: string]: MonthlyData } = {};
transactions.forEach((transaction) => {
const date = new Date(transaction.transaction_date);
const date = new Date(transaction.date);
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
if (!monthlyMap[monthKey]) {
@@ -69,10 +63,10 @@ export default function MonthlyTrends({ className }: MonthlyTrendsProps) {
};
}
if (transaction.transaction_value > 0) {
monthlyMap[monthKey].income += transaction.transaction_value;
if (transaction.amount > 0) {
monthlyMap[monthKey].income += transaction.amount;
} else {
monthlyMap[monthKey].expenses += Math.abs(transaction.transaction_value);
monthlyMap[monthKey].expenses += Math.abs(transaction.amount);
}
monthlyMap[monthKey].net = monthlyMap[monthKey].income - monthlyMap[monthKey].expenses;

View File

@@ -30,6 +30,24 @@ export default function TransactionDistribution({
accounts,
className,
}: TransactionDistributionProps) {
// Helper function to get bank name from institution_id
const getBankName = (institutionId: string): string => {
const bankMapping: Record<string, string> = {
'REVOLUT_REVOLT21': 'Revolut',
'NUBANK_NUPBBR25': 'Nu Pagamentos',
'BANCOBPI_BBPIPTPL': 'Banco BPI',
// Add more mappings as needed
};
return bankMapping[institutionId] || institutionId.split('_')[0];
};
// Helper function to create display name for account
const getAccountDisplayName = (account: Account): string => {
const bankName = getBankName(account.institution_id);
const accountName = account.name || `Account ${account.id.split('-')[1]}`;
return `${bankName} - ${accountName}`;
};
// Create pie chart data from account balances
const pieData: PieDataPoint[] = accounts.map((account, index) => {
const closingBalance = account.balances.find(
@@ -39,7 +57,7 @@ export default function TransactionDistribution({
const colors = ["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6"];
return {
name: account.name || `Account ${account.id.split('-')[1]}`,
name: getAccountDisplayName(account),
value: closingBalance?.amount || 0,
color: colors[index % colors.length],
};

View File

@@ -154,6 +154,17 @@ export const apiClient = {
);
return response.data.data;
},
// Get all transactions for analytics (no pagination)
getTransactionsForAnalytics: async (days?: number): Promise<Transaction[]> => {
const queryParams = new URLSearchParams();
if (days) queryParams.append("days", days.toString());
const response = await api.get<ApiResponse<Transaction[]>>(
`/transactions/analytics?${queryParams.toString()}`
);
return response.data.data;
},
};
export default apiClient;

View File

@@ -126,7 +126,7 @@ function AnalyticsDashboard() {
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="bg-white rounded-lg shadow p-6 border border-gray-200">
<BalanceChart data={balances || []} />
<BalanceChart data={balances || []} accounts={accounts || []} />
</div>
<div className="bg-white rounded-lg shadow p-6 border border-gray-200">
<TransactionDistribution accounts={accounts || []} />