136 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
};
|