feat: creats frontend for the project
This commit is contained in:
117
frontend/src/components/AccountsList.tsx
Normal file
117
frontend/src/components/AccountsList.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user