Files
Novault-Frontend-web/src/components/charts/SpendingTrendChart.tsx

136 lines
4.2 KiB
TypeScript

import React, { useMemo } from 'react';
import ReactECharts from 'echarts-for-react';
import type { Transaction } from '../../types';
import { formatCurrency } from '../../utils/format';
interface SpendingTrendChartProps {
transactions: Transaction[];
days?: number;
}
export const SpendingTrendChart: React.FC<SpendingTrendChartProps> = ({
transactions,
days = 7
}) => {
// Process data for the chart
const chartData = useMemo(() => {
const today = new Date();
const data: { date: string; amount: number; fullDate: string }[] = [];
for (let i = days - 1; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
const dateStr = d.toISOString().split('T')[0];
const displayDate = `${d.getMonth() + 1}/${d.getDate()}`;
const dailyTotal = transactions
.filter(t => t.type === 'expense' && t.transactionDate.startsWith(dateStr))
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
data.push({
date: displayDate,
amount: dailyTotal,
fullDate: dateStr
});
}
return data;
}, [transactions, days]);
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#e2e8f0',
textStyle: {
color: '#1e293b'
},
formatter: (params: any) => {
const item = params[0];
return `${item.name}<br/>支出: ${formatCurrency(item.value, 'CNY')}`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: chartData.map(d => d.date),
axisLine: {
lineStyle: {
color: '#94a3b8'
}
},
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
color: '#f1f5f9',
type: 'dashed'
}
},
axisLabel: {
color: '#94a3b8',
formatter: (value: number) => {
if (value >= 1000) return `${(value / 1000).toFixed(1)}k`;
return value;
}
}
},
series: [
{
data: chartData.map(d => d.amount),
type: 'bar', // Changed to bar for cleaner daily comparison, or line for trend
smooth: true,
name: '支出',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#f59e0b' }, // Amber 500
{ offset: 1, color: '#d97706' } // Amber 600
]
},
borderRadius: [4, 4, 0, 0]
},
showBackground: true,
backgroundStyle: {
color: 'rgba(241, 245, 249, 0.5)'
},
animationDuration: 1000,
animationEasing: 'cubicOut'
}
]
};
return (
<div style={{ width: '100%', height: '100%' }}>
<div className="section-header" style={{ marginBottom: '1rem', borderBottom: 'none', paddingBottom: 0 }}>
<h2 style={{
fontSize: '1.125rem',
fontWeight: 700,
margin: 0,
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
color: 'var(--color-text)'
}}>
7
</h2>
</div>
<ReactECharts option={option} style={{ height: '300px', width: '100%' }} />
</div>
);
};