Files
Novault-Frontend-web/src/components/transaction/TransactionItem/TransactionItem.tsx

181 lines
5.4 KiB
TypeScript

/**
* Transaction Item Component
* 交易项组件 - 重构版
* Feature: ui-visual-redesign
*/
import React from 'react';
import { Icon } from '@iconify/react';
import type { Transaction, Category } from '../../../types';
import { isIconifyIcon } from '../../../utils/iconUtils';
import './TransactionItem.css';
interface TransactionItemProps {
/** 交易数据 */
transaction: Transaction;
/** 分类数据 */
category?: Category;
/** 账户数据 */
account?: import('../../../types').Account;
/** 点击回调 */
onClick?: (transaction: Transaction) => void;
/** 编辑回调 */
onEdit?: (transaction: Transaction) => void;
/** 删除回调 */
onDelete?: (transaction: Transaction) => void;
/** 是否选中 */
selected?: boolean;
/** 是否显示日期 */
showDate?: boolean;
/** 是否紧凑模式 */
compact?: boolean;
}
const TransactionItem: React.FC<TransactionItemProps> = ({
transaction,
category,
account: _account,
onClick,
onEdit: _onEdit,
onDelete: _onDelete,
selected = false,
showDate = true,
compact = false,
}) => {
// 格式化金额
const formatAmount = (amount: number): string => {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: transaction.currency || 'CNY',
minimumFractionDigits: 2,
}).format(Math.abs(amount));
};
// 格式化日期
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === today.toDateString()) {
return '今天';
} else if (date.toDateString() === yesterday.toDateString()) {
return '昨天';
} else {
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' });
}
};
// 格式化时间
const formatTime = (dateString: string, timeString?: string): string => {
if (timeString) {
return timeString.slice(0, 5);
}
const date = new Date(dateString);
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
};
// 获取交易类型图标
const getTypeIcon = () => {
switch (transaction.type) {
case 'income':
return 'solar:graph-up-bold-duotone';
case 'expense':
return 'solar:graph-down-bold-duotone';
case 'transfer':
return 'solar:transfer-horizontal-bold-duotone';
default:
return 'solar:document-text-bold-duotone';
}
};
// 获取分类图标
const getCategoryIcon = () => {
if (category?.icon) {
return category.icon;
}
return getTypeIcon();
};
const handleClick = () => {
onClick?.(transaction);
};
return (
<div
className={`transaction-item ${transaction.type} ${compact ? 'compact' : ''} ${onClick ? 'clickable' : ''} ${selected ? 'selected' : ''}`}
onClick={handleClick}
>
{/* 图标 */}
<div className={`transaction-item-icon ${transaction.type}`}>
{isIconifyIcon(getCategoryIcon()) ? (
<Icon icon={getCategoryIcon()} width={compact ? 18 : 22} />
) : (
<span style={{ fontSize: compact ? 18 : 22 }}>{getCategoryIcon()}</span>
)}
</div>
{/* 主要信息 */}
<div className="transaction-item-main">
<div className="transaction-item-title">
<span className="transaction-item-category">
{category?.name || '未分类'}
</span>
{transaction.note && !compact && (
<span className="transaction-item-note">{transaction.note}</span>
)}
</div>
{showDate && (
<div className="transaction-item-meta">
<span className="transaction-item-date">
{formatDate(transaction.transactionDate)}
</span>
{transaction.transactionTime && (
<span className="transaction-item-time">
{formatTime(transaction.transactionDate, transaction.transactionTime)}
</span>
)}
</div>
)}
</div>
{/* 金额 */}
<div className="transaction-item-amount-wrapper">
<span className={`transaction-item-amount ${transaction.type}`}>
{transaction.type === 'income' ? '+' : transaction.type === 'expense' ? '-' : ''}
{formatAmount(transaction.amount)}
</span>
{/* 状态标签 */}
{(transaction.reimbursementStatus !== 'none' || transaction.refundStatus !== 'none') && (
<div className="transaction-item-badges">
{transaction.reimbursementStatus === 'pending' && (
<span className="badge badge-warning"></span>
)}
{transaction.reimbursementStatus === 'completed' && (
<span className="badge badge-success"></span>
)}
{transaction.refundStatus === 'partial' && (
<span className="badge badge-primary">退</span>
)}
{transaction.refundStatus === 'full' && (
<span className="badge badge-success">退</span>
)}
</div>
)}
</div>
{/* 箭头指示器 */}
{onClick && !compact && (
<div className="transaction-item-arrow">
<Icon icon="solar:alt-arrow-right-linear" width={16} />
</div>
)}
</div>
);
};
export default TransactionItem;