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,117 @@
import { useState, useEffect } from 'react';
import type { Account } from '@family-budget/shared';
import { getAccounts, updateAccount } from '../api/accounts';
export function AccountsList() {
const [accounts, setAccounts] = useState<Account[]>([]);
const [loading, setLoading] = useState(true);
const [editingId, setEditingId] = useState<number | null>(null);
const [editAlias, setEditAlias] = useState('');
useEffect(() => {
setLoading(true);
getAccounts()
.then(setAccounts)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
const handleEdit = (account: Account) => {
setEditingId(account.id);
setEditAlias(account.alias || '');
};
const handleSave = async (id: number) => {
try {
const updated = await updateAccount(id, {
alias: editAlias.trim(),
});
setAccounts((prev) =>
prev.map((a) => (a.id === id ? updated : a)),
);
setEditingId(null);
} catch {
// error handled globally
}
};
if (loading) {
return <div className="section-loading">Загрузка...</div>;
}
return (
<div className="settings-section">
<table className="data-table">
<thead>
<tr>
<th>Банк</th>
<th>Номер счёта</th>
<th>Валюта</th>
<th>Алиас</th>
<th></th>
</tr>
</thead>
<tbody>
{accounts.map((a) => (
<tr key={a.id}>
<td>{a.bank}</td>
<td>{a.accountNumberMasked}</td>
<td>{a.currency}</td>
<td>
{editingId === a.id ? (
<input
type="text"
value={editAlias}
onChange={(e) => setEditAlias(e.target.value)}
maxLength={50}
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleSave(a.id);
if (e.key === 'Escape') setEditingId(null);
}}
/>
) : (
a.alias || (
<span className="text-muted">не задан</span>
)
)}
</td>
<td>
{editingId === a.id ? (
<div className="btn-group">
<button
className="btn btn-sm btn-primary"
onClick={() => handleSave(a.id)}
>
Сохранить
</button>
<button
className="btn btn-sm btn-secondary"
onClick={() => setEditingId(null)}
>
Отмена
</button>
</div>
) : (
<button
className="btn btn-sm btn-secondary"
onClick={() => handleEdit(a)}
>
Изменить
</button>
)}
</td>
</tr>
))}
{accounts.length === 0 && (
<tr>
<td colSpan={5} className="td-center text-muted">
Нет счетов. Импортируйте выписку.
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}