feat(app): 实现完整的财务管家记账聊天与统计功能
- 新增App主组件,实现聊天、账本、统计三大功能模块切换 - 支持语音及文本输入,自动解析记账信息并生成交易记录 - 交易记录支持编辑、删除、提醒等操作,数据本地持久化 - 管家性格预设和自定义设定,包含头像上传和语气调整 - 会话展示支持关联交易显示,样式区分收入与支出 - 账本页添加日期分组交易列表及饼图统计展示 - 消息列表实现渐变背景和交互动画,提升用户体验 - 支持图片上传并同步发送给AI进行解析记账 - 底部导航实现三标签切换并同步显示对应内容 - 全局事务编辑弹窗支持直接调用和删除操作 - 增加环境变量示例和.gitignore规则,完善工程配置
This commit is contained in:
116
components/EditTransactionModal.tsx
Normal file
116
components/EditTransactionModal.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Transaction, TransactionType, ExpenseCategory, IncomeCategory } from '../types';
|
||||
|
||||
interface EditTransactionModalProps {
|
||||
transaction: Transaction;
|
||||
onSave: (updated: Transaction) => void;
|
||||
onDelete: (id: string) => void;
|
||||
onClose: () => void;
|
||||
hideDelete?: boolean;
|
||||
}
|
||||
|
||||
const EditTransactionModal: React.FC<EditTransactionModalProps> = ({ transaction, onSave, onDelete, onClose, hideDelete = false }) => {
|
||||
const [draft, setDraft] = useState<Transaction>(transaction);
|
||||
|
||||
useEffect(() => {
|
||||
setDraft(transaction);
|
||||
}, [transaction]);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (confirm('确定要删除这条记录吗?崔经理会盯着你的。')) {
|
||||
onDelete(draft.id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[60] flex items-end md:items-center justify-center bg-black/50 backdrop-blur-sm p-4">
|
||||
<div className="bg-white w-full max-w-md rounded-2xl p-6 shadow-2xl animate-float md:animate-none">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-bold text-gray-900">编辑账单</h3>
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
|
||||
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">金额</label>
|
||||
<input
|
||||
type="number"
|
||||
value={draft.amount}
|
||||
onChange={(e) => setDraft({...draft, amount: parseFloat(e.target.value) || 0})}
|
||||
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none font-bold text-lg bg-white shadow-sm transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">内容</label>
|
||||
<input
|
||||
type="text"
|
||||
value={draft.item}
|
||||
onChange={(e) => setDraft({...draft, item: e.target.value})}
|
||||
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none bg-white shadow-sm transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">类型</label>
|
||||
<select
|
||||
value={draft.type}
|
||||
onChange={(e) => setDraft({
|
||||
...draft,
|
||||
type: e.target.value as TransactionType,
|
||||
category: e.target.value === TransactionType.EXPENSE ? ExpenseCategory.MISC : IncomeCategory.OTHER
|
||||
})}
|
||||
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
|
||||
>
|
||||
<option value={TransactionType.EXPENSE}>支出</option>
|
||||
<option value={TransactionType.INCOME}>收入</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">分类</label>
|
||||
<select
|
||||
value={draft.category}
|
||||
onChange={(e) => setDraft({...draft, category: e.target.value})}
|
||||
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
|
||||
>
|
||||
{draft.type === TransactionType.EXPENSE
|
||||
? Object.values(ExpenseCategory).map(c => <option key={c} value={c}>{c}</option>)
|
||||
: Object.values(IncomeCategory).map(c => <option key={c} value={c}>{c}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">日期</label>
|
||||
<input
|
||||
type="date"
|
||||
value={draft.date}
|
||||
onChange={(e) => setDraft({...draft, date: e.target.value})}
|
||||
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 flex gap-3">
|
||||
{!hideDelete && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="flex-1 py-3 px-4 bg-red-50 text-red-600 rounded-xl font-semibold hover:bg-red-100 transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onSave(draft)}
|
||||
className={`py-3 px-4 bg-indigo-600 text-white rounded-xl font-semibold hover:bg-indigo-700 transition-all shadow-md shadow-indigo-200 ${hideDelete ? 'w-full' : 'flex-[2]'}`}
|
||||
>
|
||||
保存修改
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTransactionModal;
|
||||
Reference in New Issue
Block a user