Files
app_02_bill/components/Dashboard.tsx
karlcarlo c06e74bd01 feat(app): 实现完整的财务管家记账聊天与统计功能
- 新增App主组件,实现聊天、账本、统计三大功能模块切换
- 支持语音及文本输入,自动解析记账信息并生成交易记录
- 交易记录支持编辑、删除、提醒等操作,数据本地持久化
- 管家性格预设和自定义设定,包含头像上传和语气调整
- 会话展示支持关联交易显示,样式区分收入与支出
- 账本页添加日期分组交易列表及饼图统计展示
- 消息列表实现渐变背景和交互动画,提升用户体验
- 支持图片上传并同步发送给AI进行解析记账
- 底部导航实现三标签切换并同步显示对应内容
- 全局事务编辑弹窗支持直接调用和删除操作
- 增加环境变量示例和.gitignore规则,完善工程配置
2026-01-19 20:20:54 +08:00

114 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useMemo } from 'react';
import { Transaction, TransactionType, ExpenseCategory } from '../types';
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts';
interface DashboardProps {
expenses: Transaction[]; // Kept prop name as expenses for compatibility but type is Transaction
}
const COLORS = [
'#6366f1', // Indigo
'#ec4899', // Pink
'#10b981', // Emerald
'#f59e0b', // Amber
'#8b5cf6', // Violet
'#ef4444', // Red
'#3b82f6', // Blue
'#14b8a6', // Teal
'#f97316', // Orange
'#84cc16', // Lime
'#06b6d4', // Cyan
'#d946ef', // Fuchsia
'#64748b', // Slate
'#a855f7', // Purple
'#fbbf24' // Yellow
];
const Dashboard: React.FC<DashboardProps> = ({ expenses }) => {
const today = new Date().toISOString().split('T')[0];
// Filter only expenses for the dashboard visuals
const expenseTransactions = useMemo(() => expenses.filter(e => e.type === TransactionType.EXPENSE), [expenses]);
const todaysExpenses = useMemo(() => {
return expenseTransactions.filter(e => e.date === today);
}, [expenseTransactions, today]);
const totalToday = useMemo(() => {
return todaysExpenses.reduce((sum, e) => sum + e.amount, 0);
}, [todaysExpenses]);
const categoryData = useMemo(() => {
const map = new Map<string, number>();
todaysExpenses.forEach(e => {
map.set(e.category, (map.get(e.category) || 0) + e.amount);
});
return Array.from(map.entries()).map(([name, value], index) => ({
name,
value,
fill: COLORS[index % COLORS.length]
}));
}, [todaysExpenses]);
const weeklyData = useMemo(() => {
const data: { date: string; total: number }[] = [];
for (let i = 6; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - i);
const dateStr = d.toISOString().split('T')[0];
const dailySum = expenseTransactions
.filter(e => e.date === dateStr)
.reduce((sum, e) => sum + e.amount, 0);
data.push({ date: dateStr.slice(5), total: dailySum });
}
return data;
}, [expenseTransactions]);
return (
<div className="flex flex-col h-full bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div className="p-6 bg-gradient-to-r from-indigo-500 to-purple-600 text-white">
<h2 className="text-xl font-bold mb-1">今日消费统计</h2>
<div className="flex items-end gap-2">
<span className="text-4xl font-extrabold tracking-tight">¥{totalToday.toFixed(2)}</span>
<span className="text-indigo-100 mb-1 text-sm">{today}</span>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-6 no-scrollbar">
<div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-gray-700 font-semibold mb-4 text-sm uppercase tracking-wider">今日分类占比</h3>
{categoryData.length > 0 ? (
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={categoryData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={5}
dataKey="value"
>
{categoryData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<Tooltip formatter={(value: number) => `¥${value.toFixed(2)}`} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
) : (
<div className="h-32 flex items-center justify-center text-gray-400 text-sm">
今天还没有消费哦,小王表现不错!
</div>
)}
</div>
</div>
</div>
);
};
export default Dashboard;