feat: creats frontend for the project
This commit is contained in:
107
frontend/src/components/TransactionTable.tsx
Normal file
107
frontend/src/components/TransactionTable.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { Transaction } from '@family-budget/shared';
|
||||
import { formatAmount, formatDateTime } from '../utils/format';
|
||||
|
||||
interface Props {
|
||||
transactions: Transaction[];
|
||||
loading: boolean;
|
||||
onEdit: (tx: Transaction) => void;
|
||||
}
|
||||
|
||||
const DIRECTION_LABELS: Record<string, string> = {
|
||||
income: 'Приход',
|
||||
expense: 'Расход',
|
||||
transfer: 'Перевод',
|
||||
};
|
||||
|
||||
const DIRECTION_CLASSES: Record<string, string> = {
|
||||
income: 'amount-income',
|
||||
expense: 'amount-expense',
|
||||
transfer: 'amount-transfer',
|
||||
};
|
||||
|
||||
export function TransactionTable({ transactions, loading, onEdit }: Props) {
|
||||
if (loading) {
|
||||
return <div className="table-loading">Загрузка операций...</div>;
|
||||
}
|
||||
|
||||
if (transactions.length === 0) {
|
||||
return <div className="table-empty">Операции не найдены</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="table-wrapper">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Счёт</th>
|
||||
<th>Сумма</th>
|
||||
<th>Описание</th>
|
||||
<th>Категория</th>
|
||||
<th className="th-center">Статус</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.map((tx) => (
|
||||
<tr
|
||||
key={tx.id}
|
||||
className={
|
||||
!tx.isCategoryConfirmed && tx.categoryId
|
||||
? 'row-unconfirmed'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<td className="td-nowrap">
|
||||
{formatDateTime(tx.operationAt)}
|
||||
</td>
|
||||
<td className="td-nowrap">{tx.accountAlias || '—'}</td>
|
||||
<td
|
||||
className={`td-nowrap td-amount ${DIRECTION_CLASSES[tx.direction] ?? ''}`}
|
||||
>
|
||||
{formatAmount(tx.amountSigned)}
|
||||
</td>
|
||||
<td className="td-description">
|
||||
<span className="description-text">{tx.description}</span>
|
||||
{tx.comment && (
|
||||
<span className="comment-badge" title={tx.comment}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" />
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="td-nowrap">
|
||||
{tx.categoryName || (
|
||||
<span className="text-muted">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="td-center">
|
||||
{tx.categoryId != null && !tx.isCategoryConfirmed && (
|
||||
<span
|
||||
className="badge badge-warning"
|
||||
title="Категория не подтверждена"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-icon"
|
||||
onClick={() => onEdit(tx)}
|
||||
title="Редактировать"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user