v1.43.19 - Revisão completa do modal de editar template recorrente
This commit is contained in:
parent
777fb32626
commit
e92cc8cf1f
14
CHANGELOG.md
14
CHANGELOG.md
@ -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/).
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
### Changed
|
||||
|
||||
@ -1069,26 +1069,46 @@ const ReconcileModal = ({ show, onClose, instance, candidates, onSubmit, formatC
|
||||
const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSubmit, t }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
amount: '',
|
||||
transaction_description: '',
|
||||
planned_amount: '',
|
||||
frequency: 'monthly',
|
||||
type: 'expense',
|
||||
type: 'debit',
|
||||
account_id: '',
|
||||
category_id: '',
|
||||
cost_center_id: '',
|
||||
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(() => {
|
||||
if (template) {
|
||||
setFormData({
|
||||
name: template.name || '',
|
||||
amount: template.amount || '',
|
||||
transaction_description: template.transaction_description || '',
|
||||
planned_amount: template.planned_amount || '',
|
||||
frequency: template.frequency || 'monthly',
|
||||
type: template.type || 'expense',
|
||||
type: template.type || 'debit',
|
||||
account_id: template.account_id || '',
|
||||
category_id: template.category_id || '',
|
||||
cost_center_id: template.cost_center_id || '',
|
||||
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]);
|
||||
@ -1097,90 +1117,211 @@ const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSu
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
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 (
|
||||
<Modal show={show} onClose={onClose} title={t('recurring.editTemplate')} size="lg">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
{/* Nome e Descrição */}
|
||||
<div className="row">
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">{t('recurring.name')}</label>
|
||||
<div className="col-md-12 mb-3">
|
||||
<label className="form-label">
|
||||
<i className="bi bi-tag me-2"></i>
|
||||
{t('recurring.name')} <span className="text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData(f => ({ ...f, name: e.target.value }))}
|
||||
required
|
||||
/>
|
||||
</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 }))}
|
||||
placeholder="Ex: Recibo Luz, Salário, etc."
|
||||
required
|
||||
/>
|
||||
</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="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
|
||||
className="form-select"
|
||||
value={formData.type}
|
||||
onChange={(e) => setFormData(f => ({ ...f, type: e.target.value }))}
|
||||
required
|
||||
>
|
||||
<option value="expense">{t('transactions.expense')}</option>
|
||||
<option value="income">{t('transactions.income')}</option>
|
||||
<option value="debit">💸 {t('transactions.expense')}</option>
|
||||
<option value="credit">💰 {t('transactions.income')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<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
|
||||
className="form-select"
|
||||
value={formData.frequency}
|
||||
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="biweekly">{t('recurring.frequencies.biweekly')}</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>
|
||||
</div>
|
||||
<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
|
||||
type="number"
|
||||
min="1"
|
||||
max="31"
|
||||
max="12"
|
||||
className="form-control"
|
||||
value={formData.day_of_month}
|
||||
onChange={(e) => setFormData(f => ({ ...f, day_of_month: e.target.value }))}
|
||||
value={formData.frequency_interval}
|
||||
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>
|
||||
|
||||
{/* 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="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
|
||||
className="form-select"
|
||||
value={formData.account_id}
|
||||
onChange={(e) => setFormData(f => ({ ...f, account_id: e.target.value }))}
|
||||
required
|
||||
>
|
||||
<option value="">{t('common.select')}</option>
|
||||
{accounts.map(account => (
|
||||
<option key={account.id} value={account.id}>{account.name}</option>
|
||||
<option key={account.id} value={account.id}>
|
||||
{account.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<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
|
||||
className="form-select"
|
||||
value={formData.category_id}
|
||||
@ -1188,28 +1329,100 @@ const EditTemplateModal = ({ show, onClose, template, accounts, categories, onSu
|
||||
>
|
||||
<option value="">{t('common.select')}</option>
|
||||
{categories.map(category => (
|
||||
<option key={category.id} value={category.id}>{category.name}</option>
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">{t('transactions.notes')}</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
rows={2}
|
||||
value={formData.notes}
|
||||
onChange={(e) => setFormData(f => ({ ...f, notes: e.target.value }))}
|
||||
/>
|
||||
{/* Datas de Início e Fim */}
|
||||
<div className="row">
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">
|
||||
<i className="bi bi-calendar-check me-2"></i>
|
||||
Data de Início
|
||||
</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 className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={onClose}>
|
||||
<i className="bi bi-x-lg me-2"></i>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<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')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user