feat: creats frontend for the project

This commit is contained in:
vakabunga
2026-03-02 00:33:09 +03:00
parent 4d67636633
commit cd56e2bf9d
37 changed files with 3762 additions and 0 deletions

View 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>
);
}