Files
family_budget/frontend/src/components/EditTransactionModal.tsx
2026-03-02 00:33:09 +03:00

198 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, type FormEvent } from 'react';
import type {
Transaction,
Category,
CreateCategoryRuleRequest,
} from '@family-budget/shared';
import { updateTransaction } from '../api/transactions';
import { createCategoryRule } from '../api/rules';
import { formatAmount, formatDateTime } from '../utils/format';
interface Props {
transaction: Transaction;
categories: Category[];
onClose: () => void;
onSave: () => void;
}
function extractPattern(description: string): string {
return description
.replace(/Оплата товаров и услуг\.\s*/i, '')
.replace(/\s*по карте\s*\*\d+.*/i, '')
.replace(/\s*Перевод средств.*/i, '')
.trim()
.slice(0, 50);
}
export function EditTransactionModal({
transaction,
categories,
onClose,
onSave,
}: Props) {
const [categoryId, setCategoryId] = useState<string>(
transaction.categoryId != null ? String(transaction.categoryId) : '',
);
const [comment, setComment] = useState(transaction.comment || '');
const [createRule, setCreateRule] = useState(true);
const [pattern, setPattern] = useState(
extractPattern(transaction.description),
);
const [requiresConfirmation, setRequiresConfirmation] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setSaving(true);
setError('');
try {
await updateTransaction(transaction.id, {
categoryId: categoryId ? Number(categoryId) : null,
comment: comment || null,
});
if (createRule && categoryId && pattern.trim()) {
const ruleData: CreateCategoryRuleRequest = {
pattern: pattern.trim(),
matchType: 'contains',
categoryId: Number(categoryId),
priority: 100,
requiresConfirmation,
};
await createCategoryRule(ruleData);
}
onSave();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Ошибка сохранения';
setError(msg);
} finally {
setSaving(false);
}
};
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>Редактирование операции</h2>
<button className="btn-close" onClick={onClose}>
&times;
</button>
</div>
<form onSubmit={handleSubmit}>
<div className="modal-body">
{error && <div className="alert alert-error">{error}</div>}
<div className="modal-tx-info">
<div className="modal-tx-row">
<span className="modal-tx-label">Дата</span>
<span>{formatDateTime(transaction.operationAt)}</span>
</div>
<div className="modal-tx-row">
<span className="modal-tx-label">Сумма</span>
<span>{formatAmount(transaction.amountSigned)}</span>
</div>
<div className="modal-tx-row">
<span className="modal-tx-label">Описание</span>
<span className="modal-tx-description">
{transaction.description}
</span>
</div>
</div>
<div className="form-group">
<label htmlFor="edit-category">Категория</label>
<select
id="edit-category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
>
<option value=""> Без категории </option>
{categories.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
</div>
<div className="form-group">
<label htmlFor="edit-comment">Комментарий</label>
<textarea
id="edit-comment"
rows={2}
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Комментарий к операции..."
/>
</div>
<div className="form-divider" />
<div className="form-group form-group-checkbox">
<label>
<input
type="checkbox"
checked={createRule}
onChange={(e) => setCreateRule(e.target.checked)}
/>
Создать правило для похожих транзакций
</label>
</div>
{createRule && (
<>
<div className="form-group">
<label htmlFor="edit-pattern">
Шаблон (ключевая строка)
</label>
<input
id="edit-pattern"
type="text"
value={pattern}
onChange={(e) => setPattern(e.target.value)}
maxLength={200}
/>
</div>
<div className="form-group form-group-checkbox">
<label>
<input
type="checkbox"
checked={requiresConfirmation}
onChange={(e) =>
setRequiresConfirmation(e.target.checked)
}
/>
Требовать подтверждения категории
</label>
</div>
</>
)}
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={onClose}
>
Отмена
</button>
<button
type="submit"
className="btn btn-primary"
disabled={saving}
>
{saving ? 'Сохранение...' : 'Сохранить'}
</button>
</div>
</form>
</div>
</div>
);
}