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,130 @@
import { useState, useEffect } from 'react';
import type { CategoryRule } from '@family-budget/shared';
import {
getCategoryRules,
updateCategoryRule,
applyRule,
} from '../api/rules';
import { formatDate } from '../utils/format';
export function RulesList() {
const [rules, setRules] = useState<CategoryRule[]>([]);
const [loading, setLoading] = useState(true);
const [applyingId, setApplyingId] = useState<number | null>(null);
const [applyResult, setApplyResult] = useState<{
id: number;
count: number;
} | null>(null);
useEffect(() => {
setLoading(true);
getCategoryRules()
.then(setRules)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
const handleToggle = async (rule: CategoryRule) => {
try {
const updated = await updateCategoryRule(rule.id, {
isActive: !rule.isActive,
});
setRules((prev) =>
prev.map((r) => (r.id === rule.id ? updated : r)),
);
} catch {
// error handled globally
}
};
const handleApply = async (id: number) => {
setApplyingId(id);
try {
const resp = await applyRule(id);
setApplyResult({ id, count: resp.applied });
setTimeout(() => setApplyResult(null), 4000);
} catch {
// error handled globally
} finally {
setApplyingId(null);
}
};
if (loading) {
return <div className="section-loading">Загрузка...</div>;
}
return (
<div className="settings-section">
<table className="data-table">
<thead>
<tr>
<th>Шаблон</th>
<th>Категория</th>
<th className="th-center">Приоритет</th>
<th className="th-center">Подтверждение</th>
<th>Создано</th>
<th className="th-center">Активно</th>
<th></th>
</tr>
</thead>
<tbody>
{rules.map((r) => (
<tr
key={r.id}
className={!r.isActive ? 'row-inactive' : ''}
>
<td>
<code>{r.pattern}</code>
</td>
<td>{r.categoryName}</td>
<td className="td-center">{r.priority}</td>
<td className="td-center">
{r.requiresConfirmation ? 'Да' : 'Нет'}
</td>
<td className="td-nowrap">
{formatDate(r.createdAt)}
</td>
<td className="td-center">
<button
className={`toggle ${r.isActive ? 'toggle-on' : 'toggle-off'}`}
onClick={() => handleToggle(r)}
title={
r.isActive ? 'Деактивировать' : 'Активировать'
}
>
{r.isActive ? 'Вкл' : 'Выкл'}
</button>
</td>
<td>
<div className="rules-actions">
{r.isActive && (
<button
className="btn btn-sm btn-secondary"
onClick={() => handleApply(r.id)}
disabled={applyingId === r.id}
>
{applyingId === r.id ? '...' : 'Применить'}
</button>
)}
{applyResult?.id === r.id && (
<span className="apply-result">
Применено: {applyResult.count}
</span>
)}
</div>
</td>
</tr>
))}
{rules.length === 0 && (
<tr>
<td colSpan={7} className="td-center text-muted">
Нет правил
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}