v1.43.19 - Revisão completa do modal de editar template recorrente

This commit is contained in:
marcoitaloesp-ai 2025-12-16 18:15:11 +00:00 committed by GitHub
parent 777fb32626
commit e92cc8cf1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 270 additions and 43 deletions

View File

@ -5,6 +5,20 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/). Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
## [1.43.19] - 2025-12-16
### Changed
- **Revisão Completa do Modal de Editar Template Recorrente**
- Adicionados TODOS os campos disponíveis no modelo (17 campos totais)
- Campos adicionados: `transaction_description`, `frequency_interval`, `start_date`, `end_date`, `max_occurrences`, `cost_center_id`, `is_active`
- Interface reorganizada com ícones e labels claros
- Campos condicionais (day_of_month apenas para frequências mensais)
- Validação e parsing corretos dos dados antes do envio
- Placeholders e textos de ajuda para melhor UX
- Informações do template (ID, data criação, ocorrências geradas) exibidas no rodapé
- Suporte a status ativo/pausado com toggle visual
- Campo de intervalo para repetições personalizadas (ex: a cada 2 meses)
## [1.43.18] - 2025-12-16 ## [1.43.18] - 2025-12-16
### Changed ### Changed

View File

@ -1 +1 @@
1.43.18 1.43.19

View File

@ -1069,26 +1069,46 @@ const ReconcileModal = ({ show, onClose, instance, candidates, onSubmit, formatC
const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSubmit, t }) => { const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSubmit, t }) => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
amount: '', transaction_description: '',
planned_amount: '',
frequency: 'monthly', frequency: 'monthly',
type: 'expense', type: 'debit',
account_id: '', account_id: '',
category_id: '', category_id: '',
cost_center_id: '',
day_of_month: '', day_of_month: '',
notes: '' frequency_interval: '1',
start_date: '',
end_date: '',
max_occurrences: '',
notes: '',
is_active: true
}); });
// Helper para converter data ISO para yyyy-MM-dd
const formatDateForInput = (dateStr) => {
if (!dateStr) return '';
return dateStr.split('T')[0];
};
useEffect(() => { useEffect(() => {
if (template) { if (template) {
setFormData({ setFormData({
name: template.name || '', name: template.name || '',
amount: template.amount || '', transaction_description: template.transaction_description || '',
planned_amount: template.planned_amount || '',
frequency: template.frequency || 'monthly', frequency: template.frequency || 'monthly',
type: template.type || 'expense', type: template.type || 'debit',
account_id: template.account_id || '', account_id: template.account_id || '',
category_id: template.category_id || '', category_id: template.category_id || '',
cost_center_id: template.cost_center_id || '',
day_of_month: template.day_of_month || '', day_of_month: template.day_of_month || '',
notes: template.notes || '' frequency_interval: template.frequency_interval || '1',
start_date: formatDateForInput(template.start_date) || '',
end_date: formatDateForInput(template.end_date) || '',
max_occurrences: template.max_occurrences || '',
notes: template.notes || '',
is_active: template.is_active !== undefined ? template.is_active : true
}); });
} }
}, [template]); }, [template]);
@ -1097,90 +1117,211 @@ const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSu
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
onSubmit(formData);
// Preparar dados para envio (remover campos vazios opcionais)
const submitData = {
name: formData.name,
transaction_description: formData.transaction_description,
planned_amount: parseFloat(formData.planned_amount),
frequency: formData.frequency,
type: formData.type,
account_id: formData.account_id,
is_active: formData.is_active,
frequency_interval: formData.frequency_interval ? parseInt(formData.frequency_interval) : 1
};
// Adicionar campos opcionais apenas se preenchidos
if (formData.category_id) submitData.category_id = formData.category_id;
if (formData.cost_center_id) submitData.cost_center_id = formData.cost_center_id;
if (formData.day_of_month) submitData.day_of_month = parseInt(formData.day_of_month);
if (formData.start_date) submitData.start_date = formData.start_date;
if (formData.end_date) submitData.end_date = formData.end_date;
if (formData.max_occurrences) submitData.max_occurrences = parseInt(formData.max_occurrences);
if (formData.notes) submitData.notes = formData.notes;
onSubmit(submitData);
}; };
// Verificar se é frequência baseada em meses ou dias
const isMonthlyBased = ['monthly', 'bimonthly', 'quarterly', 'semiannual', 'annual'].includes(formData.frequency);
const isWeeklyBased = ['weekly', 'biweekly'].includes(formData.frequency);
return ( return (
<Modal show={show} onClose={onClose} title={t('recurring.editTemplate')} size="lg"> <Modal show={show} onClose={onClose} title={t('recurring.editTemplate')} size="lg">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="modal-body"> <div className="modal-body">
{/* Nome e Descrição */}
<div className="row"> <div className="row">
<div className="col-md-6 mb-3"> <div className="col-md-12 mb-3">
<label className="form-label">{t('recurring.name')}</label> <label className="form-label">
<i className="bi bi-tag me-2"></i>
{t('recurring.name')} <span className="text-danger">*</span>
</label>
<input <input
type="text" type="text"
className="form-control" className="form-control"
value={formData.name} value={formData.name}
onChange={(e) => setFormData(f => ({ ...f, name: e.target.value }))} onChange={(e) => setFormData(f => ({ ...f, name: e.target.value }))}
required placeholder="Ex: Recibo Luz, Salário, etc."
/>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">{t('transactions.amount')}</label>
<input
type="number"
step="0.01"
className="form-control"
value={formData.amount}
onChange={(e) => setFormData(f => ({ ...f, amount: e.target.value }))}
required required
/> />
</div> </div>
</div> </div>
<div className="row">
<div className="col-md-12 mb-3">
<label className="form-label">
<i className="bi bi-file-text me-2"></i>
{t('transactions.description')}
</label>
<input
type="text"
className="form-control"
value={formData.transaction_description}
onChange={(e) => setFormData(f => ({ ...f, transaction_description: e.target.value }))}
placeholder="Descrição que aparecerá nas transações geradas"
/>
</div>
</div>
{/* Tipo, Valor e Status */}
<div className="row"> <div className="row">
<div className="col-md-4 mb-3"> <div className="col-md-4 mb-3">
<label className="form-label">{t('recurring.type')}</label> <label className="form-label">
<i className="bi bi-arrow-down-up me-2"></i>
{t('recurring.type')} <span className="text-danger">*</span>
</label>
<select <select
className="form-select" className="form-select"
value={formData.type} value={formData.type}
onChange={(e) => setFormData(f => ({ ...f, type: e.target.value }))} onChange={(e) => setFormData(f => ({ ...f, type: e.target.value }))}
required
> >
<option value="expense">{t('transactions.expense')}</option> <option value="debit">💸 {t('transactions.expense')}</option>
<option value="income">{t('transactions.income')}</option> <option value="credit">💰 {t('transactions.income')}</option>
</select> </select>
</div> </div>
<div className="col-md-4 mb-3"> <div className="col-md-4 mb-3">
<label className="form-label">{t('recurring.frequency')}</label> <label className="form-label">
<i className="bi bi-cash me-2"></i>
{t('transactions.amount')} <span className="text-danger">*</span>
</label>
<input
type="number"
step="0.01"
min="0.01"
className="form-control"
value={formData.planned_amount}
onChange={(e) => setFormData(f => ({ ...f, planned_amount: e.target.value }))}
placeholder="0.00"
required
/>
</div>
<div className="col-md-4 mb-3">
<label className="form-label">
<i className="bi bi-toggle-on me-2"></i>
Status
</label>
<select
className="form-select"
value={formData.is_active ? 'true' : 'false'}
onChange={(e) => setFormData(f => ({ ...f, is_active: e.target.value === 'true' }))}
>
<option value="true"> Ativo</option>
<option value="false"> Pausado</option>
</select>
</div>
</div>
{/* Frequência e Intervalo */}
<div className="row">
<div className="col-md-8 mb-3">
<label className="form-label">
<i className="bi bi-clock-history me-2"></i>
{t('recurring.frequency')} <span className="text-danger">*</span>
</label>
<select <select
className="form-select" className="form-select"
value={formData.frequency} value={formData.frequency}
onChange={(e) => setFormData(f => ({ ...f, frequency: e.target.value }))} onChange={(e) => setFormData(f => ({ ...f, frequency: e.target.value }))}
required
> >
<option value="daily">{t('recurring.frequencies.daily')}</option>
<option value="weekly">{t('recurring.frequencies.weekly')}</option> <option value="weekly">{t('recurring.frequencies.weekly')}</option>
<option value="biweekly">{t('recurring.frequencies.biweekly')}</option>
<option value="monthly">{t('recurring.frequencies.monthly')}</option> <option value="monthly">{t('recurring.frequencies.monthly')}</option>
<option value="yearly">{t('recurring.frequencies.yearly')}</option> <option value="bimonthly">{t('recurring.frequencies.bimonthly')}</option>
<option value="quarterly">{t('recurring.frequencies.quarterly')}</option>
<option value="semiannual">{t('recurring.frequencies.semiannual')}</option>
<option value="annual">{t('recurring.frequencies.annual')}</option>
</select> </select>
</div> </div>
<div className="col-md-4 mb-3"> <div className="col-md-4 mb-3">
<label className="form-label">{t('recurring.dayOfMonth')}</label> <label className="form-label">
<i className="bi bi-arrow-repeat me-2"></i>
Intervalo
</label>
<input <input
type="number" type="number"
min="1" min="1"
max="31" max="12"
className="form-control" className="form-control"
value={formData.day_of_month} value={formData.frequency_interval}
onChange={(e) => setFormData(f => ({ ...f, day_of_month: e.target.value }))} onChange={(e) => setFormData(f => ({ ...f, frequency_interval: e.target.value }))}
placeholder="1"
/> />
<small className="text-muted">A cada X {formData.frequency === 'monthly' ? 'meses' : 'períodos'}</small>
</div> </div>
</div> </div>
{/* Dia do Mês (apenas para mensais) */}
{isMonthlyBased && (
<div className="row">
<div className="col-md-12 mb-3">
<label className="form-label">
<i className="bi bi-calendar-date me-2"></i>
{t('recurring.dayOfMonth')}
</label>
<input
type="number"
min="1"
max="31"
className="form-control"
value={formData.day_of_month}
onChange={(e) => setFormData(f => ({ ...f, day_of_month: e.target.value }))}
placeholder="Ex: 5 para dia 5 de cada mês"
/>
<small className="text-muted">Deixe vazio para usar a data de início</small>
</div>
</div>
)}
{/* Conta e Categoria */}
<div className="row"> <div className="row">
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<label className="form-label">{t('transactions.account')}</label> <label className="form-label">
<i className="bi bi-wallet2 me-2"></i>
{t('transactions.account')} <span className="text-danger">*</span>
</label>
<select <select
className="form-select" className="form-select"
value={formData.account_id} value={formData.account_id}
onChange={(e) => setFormData(f => ({ ...f, account_id: e.target.value }))} onChange={(e) => setFormData(f => ({ ...f, account_id: e.target.value }))}
required
> >
<option value="">{t('common.select')}</option> <option value="">{t('common.select')}</option>
{accounts.map(account => ( {accounts.map(account => (
<option key={account.id} value={account.id}>{account.name}</option> <option key={account.id} value={account.id}>
{account.name}
</option>
))} ))}
</select> </select>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<label className="form-label">{t('transactions.category')}</label> <label className="form-label">
<i className="bi bi-folder me-2"></i>
{t('transactions.category')}
</label>
<select <select
className="form-select" className="form-select"
value={formData.category_id} value={formData.category_id}
@ -1188,28 +1329,100 @@ const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSu
> >
<option value="">{t('common.select')}</option> <option value="">{t('common.select')}</option>
{categories.map(category => ( {categories.map(category => (
<option key={category.id} value={category.id}>{category.name}</option> <option key={category.id} value={category.id}>
{category.name}
</option>
))} ))}
</select> </select>
</div> </div>
</div> </div>
<div className="mb-3"> {/* Datas de Início e Fim */}
<label className="form-label">{t('transactions.notes')}</label> <div className="row">
<textarea <div className="col-md-6 mb-3">
className="form-control" <label className="form-label">
rows={2} <i className="bi bi-calendar-check me-2"></i>
value={formData.notes} Data de Início
onChange={(e) => setFormData(f => ({ ...f, notes: e.target.value }))} </label>
/> <input
type="date"
className="form-control"
value={formData.start_date}
onChange={(e) => setFormData(f => ({ ...f, start_date: e.target.value }))}
/>
<small className="text-muted">Primeira ocorrência</small>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">
<i className="bi bi-calendar-x me-2"></i>
Data de Término
</label>
<input
type="date"
className="form-control"
value={formData.end_date}
onChange={(e) => setFormData(f => ({ ...f, end_date: e.target.value }))}
min={formData.start_date || undefined}
/>
<small className="text-muted">Última ocorrência (opcional)</small>
</div>
</div>
{/* Máximo de Ocorrências */}
<div className="row">
<div className="col-md-12 mb-3">
<label className="form-label">
<i className="bi bi-hash me-2"></i>
Máximo de Ocorrências
</label>
<input
type="number"
min="1"
max="999"
className="form-control"
value={formData.max_occurrences}
onChange={(e) => setFormData(f => ({ ...f, max_occurrences: e.target.value }))}
placeholder="Ex: 12 para um ano mensal"
/>
<small className="text-muted">Deixe vazio para recorrência ilimitada (até a data de término)</small>
</div>
</div>
{/* Notas */}
<div className="row">
<div className="col-md-12 mb-3">
<label className="form-label">
<i className="bi bi-chat-left-text me-2"></i>
{t('transactions.notes')}
</label>
<textarea
className="form-control"
rows={3}
value={formData.notes}
onChange={(e) => setFormData(f => ({ ...f, notes: e.target.value }))}
placeholder="Observações sobre este template..."
/>
</div>
</div>
{/* Informações do Template */}
<div className="alert alert-info mb-0">
<small>
<i className="bi bi-info-circle me-2"></i>
<strong>Template ID:</strong> {template.id} |
<strong className="ms-2">Criado:</strong> {new Date(template.created_at).toLocaleDateString()} |
<strong className="ms-2">Ocorrências geradas:</strong> {template.occurrences_generated || 0}
</small>
</div> </div>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={onClose}> <button type="button" className="btn btn-secondary" onClick={onClose}>
<i className="bi bi-x-lg me-2"></i>
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button type="submit" className="btn btn-primary"> <button type="submit" className="btn btn-primary">
<i className="bi bi-check-lg me-2" /> <i className="bi bi-check-lg me-2"></i>
{t('common.save')} {t('common.save')}
</button> </button>
</div> </div>