feat: novas categorias e melhorias UI transações
- Adicionadas categorias para Marco Leite: - Gastos Trabajo (com subcategorias: Máquina de Vending, Café/Snacks, etc.) - Tabaco/Vaper (com subcategorias: Cigarros, Vaper/Pod, Líquidos, etc.) - Beleza (com subcategorias: Cabeleireiro, Barbearia, Skincare, etc.) - Subscrições (com subcategorias: Streaming, Software, Cloud, etc.) - Bono Transporte (subcategoria de Transporte) - Descarga de Passivo (subcategoria de Finanças) - Lista de transações filtradas agora exibe menu completo de ações (igual à listagem por semana): editar, duplicar, dividir, etc.
This commit is contained in:
parent
d1ab280997
commit
99a68f4520
@ -228,7 +228,7 @@ public function update(Request $request, $id)
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
// Don't allow changing main admin's admin status
|
||||
if ($user->email === 'marco@cnxifly.com' && $request->has('is_admin') && !$request->is_admin) {
|
||||
if ($user->email === 'marcoitaloesp@icloud.com' && $request->has('is_admin') && !$request->is_admin) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No se puede remover permisos del administrador principal',
|
||||
@ -288,7 +288,7 @@ public function destroy($id)
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
// Don't allow deleting admin
|
||||
if ($user->email === 'marco@cnxifly.com') {
|
||||
if ($user->email === 'marcoitaloesp@icloud.com') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No se puede eliminar el usuario administrador',
|
||||
|
||||
@ -8,19 +8,15 @@
|
||||
|
||||
class AdminOnly
|
||||
{
|
||||
/**
|
||||
* Admin email - only this user can access restricted features
|
||||
*/
|
||||
private const ADMIN_EMAIL = 'marco@cnxifly.com';
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
* Only users with is_admin = true can access admin routes.
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (!$user || $user->email !== self::ADMIN_EMAIL) {
|
||||
if (!$user || !$user->is_admin) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Access denied. This feature is not available.',
|
||||
|
||||
@ -14,7 +14,7 @@ const Layout = ({ children }) => {
|
||||
const { date } = useFormatters();
|
||||
|
||||
// Admin email - only this user can see business module
|
||||
const ADMIN_EMAIL = 'marco@cnxifly.com';
|
||||
const ADMIN_EMAIL = 'marcoitaloesp@icloud.com';
|
||||
const isAdmin = user?.email === ADMIN_EMAIL;
|
||||
|
||||
// Mobile: sidebar oculta por padrão | Desktop: expandida
|
||||
|
||||
@ -568,6 +568,7 @@
|
||||
"description": "Description",
|
||||
"originalDescription": "Original Bank Description",
|
||||
"notes": "Notes",
|
||||
"notesPlaceholder": "Add notes about this transaction (optional)",
|
||||
"reference": "Reference",
|
||||
"referencePlaceholder": "Document number, invoice, etc.",
|
||||
"date": "Date",
|
||||
|
||||
@ -576,6 +576,7 @@
|
||||
"description": "Descripción",
|
||||
"originalDescription": "Descripción Original del Banco",
|
||||
"notes": "Observaciones",
|
||||
"notesPlaceholder": "Añade observaciones sobre esta transacción (opcional)",
|
||||
"reference": "Referencia",
|
||||
"referencePlaceholder": "Nº de documento, factura, etc.",
|
||||
"date": "Fecha",
|
||||
|
||||
@ -578,6 +578,7 @@
|
||||
"description": "Descrição",
|
||||
"originalDescription": "Descrição Original do Banco",
|
||||
"notes": "Observações",
|
||||
"notesPlaceholder": "Adicione observações sobre esta transação (opcional)",
|
||||
"reference": "Referência",
|
||||
"referencePlaceholder": "Nº do documento, fatura, etc.",
|
||||
"date": "Data",
|
||||
|
||||
@ -179,8 +179,18 @@ export default function Transactions() {
|
||||
category_id: '',
|
||||
cost_center_id: '',
|
||||
add_keyword: true,
|
||||
notes: '',
|
||||
});
|
||||
const [savingQuickCategorize, setSavingQuickCategorize] = useState(false);
|
||||
// Estados para criar categoria/subcategoria inline no modal de categorização
|
||||
const [showInlineCategoryForm, setShowInlineCategoryForm] = useState(false);
|
||||
const [inlineCategoryData, setInlineCategoryData] = useState({
|
||||
name: '',
|
||||
parent_id: '',
|
||||
type: 'expense',
|
||||
icon: 'bi-tag',
|
||||
});
|
||||
const [savingInlineCategory, setSavingInlineCategory] = useState(false);
|
||||
|
||||
// Calcular se há filtros ativos (excluindo date_field que é sempre preenchido)
|
||||
const hasActiveFilters = Object.entries(filters).some(([key, v]) => key !== 'date_field' && v !== '');
|
||||
@ -615,10 +625,53 @@ export default function Transactions() {
|
||||
category_id: transaction.category_id || '',
|
||||
cost_center_id: transaction.cost_center_id || '',
|
||||
add_keyword: true,
|
||||
notes: transaction.notes || '',
|
||||
});
|
||||
setShowInlineCategoryForm(false);
|
||||
setInlineCategoryData({
|
||||
name: '',
|
||||
parent_id: '',
|
||||
type: transaction.type === 'credit' ? 'income' : 'expense',
|
||||
icon: 'bi-tag',
|
||||
});
|
||||
setShowQuickCategorizeModal(true);
|
||||
};
|
||||
|
||||
// Criar categoria/subcategoria inline no modal de categorização
|
||||
const handleInlineCategorySubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!inlineCategoryData.name.trim()) return;
|
||||
|
||||
try {
|
||||
setSavingInlineCategory(true);
|
||||
const result = await categoryService.create({
|
||||
...inlineCategoryData,
|
||||
parent_id: inlineCategoryData.parent_id ? parseInt(inlineCategoryData.parent_id) : null,
|
||||
});
|
||||
const newCategory = result.data || result;
|
||||
|
||||
// Recarregar categorias
|
||||
const categoriesRes = await categoryService.getAll({ flat: true });
|
||||
setCategories(Array.isArray(categoriesRes) ? categoriesRes : (categoriesRes.data || []));
|
||||
|
||||
// Selecionar a nova categoria no modal
|
||||
setQuickCategorizeData(prev => ({ ...prev, category_id: newCategory.id }));
|
||||
|
||||
showToast(t('categories.createSuccess'), 'success');
|
||||
setShowInlineCategoryForm(false);
|
||||
setInlineCategoryData({
|
||||
name: '',
|
||||
parent_id: '',
|
||||
type: quickCategorizeData.transaction?.type === 'credit' ? 'income' : 'expense',
|
||||
icon: 'bi-tag',
|
||||
});
|
||||
} catch (err) {
|
||||
showToast(err.response?.data?.message || t('categories.createError'), 'danger');
|
||||
} finally {
|
||||
setSavingInlineCategory(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuickCategorizeSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
@ -627,6 +680,7 @@ export default function Transactions() {
|
||||
const updateData = {
|
||||
category_id: quickCategorizeData.category_id || null,
|
||||
cost_center_id: quickCategorizeData.cost_center_id || null,
|
||||
notes: quickCategorizeData.notes || null,
|
||||
};
|
||||
|
||||
// Se add_keyword está ativo e há descrição original, criar keyword
|
||||
@ -638,8 +692,12 @@ export default function Transactions() {
|
||||
cost_center_id: updateData.cost_center_id,
|
||||
add_keyword: true,
|
||||
});
|
||||
// Atualizar notas separadamente se houver
|
||||
if (updateData.notes) {
|
||||
await transactionService.update(quickCategorizeData.transaction.id, { notes: updateData.notes });
|
||||
}
|
||||
} else {
|
||||
// Apenas atualizar a transação
|
||||
// Atualizar a transação com todos os dados
|
||||
await transactionService.update(quickCategorizeData.transaction.id, updateData);
|
||||
}
|
||||
|
||||
@ -1526,12 +1584,122 @@ export default function Transactions() {
|
||||
{transaction.type === 'credit' ? '+' : '-'}
|
||||
{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={() => openQuickCategorizeModal(transaction)}
|
||||
>
|
||||
<i className="bi bi-tags"></i>
|
||||
</button>
|
||||
<div className="dropdown" onClick={(e) => e.stopPropagation()}>
|
||||
<button className="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i className="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end shadow-sm">
|
||||
{/* Ações de Status */}
|
||||
{transaction.status === 'pending' && (
|
||||
<>
|
||||
<li className="dropdown-header small text-muted">{t('transactions.status.label')}</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openCompleteModal(transaction)}>
|
||||
<i className="bi bi-check-circle text-success me-2"></i>
|
||||
{t('transactions.markComplete')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleQuickComplete(transaction)}>
|
||||
<i className="bi bi-lightning-fill text-warning me-2"></i>
|
||||
{t('transactions.quickComplete')}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
</>
|
||||
)}
|
||||
{transaction.status === 'completed' && (
|
||||
<>
|
||||
<li className="dropdown-header small text-muted">{t('transactions.status.label')}</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleRevert(transaction)}>
|
||||
<i className="bi bi-arrow-counterclockwise text-warning me-2"></i>
|
||||
{t('transactions.revert')}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Ações Principais */}
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openQuickCategorizeModal(transaction)}>
|
||||
<i className="bi bi-tags text-success me-2"></i>
|
||||
{t('transactions.quickCategorize')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openEditModal(transaction)}>
|
||||
<i className="bi bi-pencil text-primary me-2"></i>
|
||||
{t('common.edit')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleDuplicate(transaction)}>
|
||||
<i className="bi bi-copy text-info me-2"></i>
|
||||
{t('transactions.duplicate')}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{/* Dividir transação */}
|
||||
{!transaction.is_transfer && !transaction.split_parent_id && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openSplitModal(transaction)}>
|
||||
<i className="bi bi-diagram-3 text-secondary me-2"></i>
|
||||
{t('transactions.split')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Converter em transferência */}
|
||||
{!transaction.is_transfer && !transaction.is_split_child && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openConvertTransferModal(transaction)}>
|
||||
<i className="bi bi-arrow-left-right text-purple me-2"></i>
|
||||
{t('transactions.convertToTransfer')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Conciliar com passivo */}
|
||||
{transaction.type === 'debit' && !transaction.is_reconciled && !transaction.is_transfer && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openReconcileLiabilityModal(transaction)}>
|
||||
<i className="bi bi-link-45deg text-purple me-2"></i>
|
||||
{t('transactions.reconcileWithLiability')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Criar Recorrência */}
|
||||
{!transaction.is_transfer && !transaction.recurring_instance_id && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openRecurrenceModal(transaction)}>
|
||||
<i className="bi bi-calendar-check text-info me-2"></i>
|
||||
{t('recurring.makeRecurring')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Cancelar */}
|
||||
{transaction.status === 'pending' && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleCancel(transaction)}>
|
||||
<i className="bi bi-x-circle text-secondary me-2"></i>
|
||||
{t('transactions.cancel')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
<li>
|
||||
<button className="dropdown-item text-danger" onClick={() => handleDelete(transaction)}>
|
||||
<i className="bi bi-trash me-2"></i>
|
||||
{t('common.delete')}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1631,13 +1799,122 @@ export default function Transactions() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-center col-actions">
|
||||
<button
|
||||
className="txn-actions-btn"
|
||||
onClick={() => openQuickCategorizeModal(transaction)}
|
||||
title={t('transactions.quickCategorize')}
|
||||
>
|
||||
<i className="bi bi-tags"></i>
|
||||
</button>
|
||||
<div className="dropdown">
|
||||
<button className="txn-actions-btn" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i className="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end shadow-sm">
|
||||
{/* Ações de Status */}
|
||||
{transaction.status === 'pending' && (
|
||||
<>
|
||||
<li className="dropdown-header small text-muted">{t('transactions.status.label')}</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openCompleteModal(transaction)}>
|
||||
<i className="bi bi-check-circle text-success me-2"></i>
|
||||
{t('transactions.markComplete')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleQuickComplete(transaction)}>
|
||||
<i className="bi bi-lightning-fill text-warning me-2"></i>
|
||||
{t('transactions.quickComplete')}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
</>
|
||||
)}
|
||||
{transaction.status === 'completed' && (
|
||||
<>
|
||||
<li className="dropdown-header small text-muted">{t('transactions.status.label')}</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleRevert(transaction)}>
|
||||
<i className="bi bi-arrow-counterclockwise text-warning me-2"></i>
|
||||
{t('transactions.revert')}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Ações Principais */}
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openQuickCategorizeModal(transaction)}>
|
||||
<i className="bi bi-tags text-success me-2"></i>
|
||||
{t('transactions.quickCategorize')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openEditModal(transaction)}>
|
||||
<i className="bi bi-pencil text-primary me-2"></i>
|
||||
{t('common.edit')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleDuplicate(transaction)}>
|
||||
<i className="bi bi-copy text-info me-2"></i>
|
||||
{t('transactions.duplicate')}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
{/* Dividir transação */}
|
||||
{!transaction.is_transfer && !transaction.split_parent_id && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openSplitModal(transaction)}>
|
||||
<i className="bi bi-diagram-3 text-secondary me-2"></i>
|
||||
{t('transactions.split')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Converter em transferência */}
|
||||
{!transaction.is_transfer && !transaction.is_split_child && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openConvertTransferModal(transaction)}>
|
||||
<i className="bi bi-arrow-left-right text-purple me-2"></i>
|
||||
{t('transactions.convertToTransfer')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Conciliar com passivo */}
|
||||
{transaction.type === 'debit' && !transaction.is_reconciled && !transaction.is_transfer && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openReconcileLiabilityModal(transaction)}>
|
||||
<i className="bi bi-link-45deg text-purple me-2"></i>
|
||||
{t('transactions.reconcileWithLiability')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Criar Recorrência */}
|
||||
{!transaction.is_transfer && !transaction.recurring_instance_id && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => openRecurrenceModal(transaction)}>
|
||||
<i className="bi bi-calendar-check text-info me-2"></i>
|
||||
{t('recurring.makeRecurring')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
{/* Cancelar */}
|
||||
{transaction.status === 'pending' && (
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleCancel(transaction)}>
|
||||
<i className="bi bi-x-circle text-secondary me-2"></i>
|
||||
{t('transactions.cancel')}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li><hr className="dropdown-divider" /></li>
|
||||
<li>
|
||||
<button className="dropdown-item text-danger" onClick={() => handleDelete(transaction)}>
|
||||
<i className="bi bi-trash me-2"></i>
|
||||
{t('common.delete')}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@ -3810,7 +4087,10 @@ export default function Transactions() {
|
||||
{/* Modal de Categorização Rápida Individual */}
|
||||
<Modal
|
||||
show={showQuickCategorizeModal}
|
||||
onClose={() => setShowQuickCategorizeModal(false)}
|
||||
onClose={() => {
|
||||
setShowQuickCategorizeModal(false);
|
||||
setShowInlineCategoryForm(false);
|
||||
}}
|
||||
title={t('transactions.quickCategorize')}
|
||||
size="md"
|
||||
>
|
||||
@ -3836,14 +4116,81 @@ export default function Transactions() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Seção Categoria com botão de criar */}
|
||||
<div className="mb-3">
|
||||
<label className="form-label">{t('transactions.category')}</label>
|
||||
<CategorySelector
|
||||
categories={categories}
|
||||
value={quickCategorizeData.category_id}
|
||||
onChange={(e) => setQuickCategorizeData(prev => ({ ...prev, category_id: e.target.value }))}
|
||||
placeholder={t('transactions.selectCategory')}
|
||||
/>
|
||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||
<label className="form-label mb-0">{t('transactions.category')}</label>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link btn-sm p-0 text-decoration-none"
|
||||
onClick={() => setShowInlineCategoryForm(!showInlineCategoryForm)}
|
||||
>
|
||||
<i className={`bi ${showInlineCategoryForm ? 'bi-x-circle' : 'bi-plus-circle'} me-1`}></i>
|
||||
{showInlineCategoryForm ? t('common.cancel') : t('categories.newCategory')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showInlineCategoryForm ? (
|
||||
<div className="card border-primary mb-2" style={{ backgroundColor: '#0f172a' }}>
|
||||
<div className="card-body p-3">
|
||||
<div className="mb-2">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
value={inlineCategoryData.name}
|
||||
onChange={(e) => setInlineCategoryData(prev => ({ ...prev, name: e.target.value }))}
|
||||
placeholder={t('categories.categoryName')}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-2 mb-2">
|
||||
<div className="col-6">
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
value={inlineCategoryData.parent_id}
|
||||
onChange={(e) => setInlineCategoryData(prev => ({ ...prev, parent_id: e.target.value }))}
|
||||
>
|
||||
<option value="">{t('categories.noParent')}</option>
|
||||
{parentCategories.map(cat => (
|
||||
<option key={cat.id} value={cat.id}>{cat.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
value={inlineCategoryData.type}
|
||||
onChange={(e) => setInlineCategoryData(prev => ({ ...prev, type: e.target.value }))}
|
||||
>
|
||||
<option value="expense">{t('categories.types.expense')}</option>
|
||||
<option value="income">{t('categories.types.income')}</option>
|
||||
<option value="both">{t('categories.types.both')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<small className="text-muted d-block mb-2">{t('transactions.quickCategory.parentHelp')}</small>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm w-100"
|
||||
onClick={handleInlineCategorySubmit}
|
||||
disabled={savingInlineCategory || !inlineCategoryData.name.trim()}
|
||||
>
|
||||
{savingInlineCategory ? (
|
||||
<><span className="spinner-border spinner-border-sm me-1"></span>{t('common.saving')}</>
|
||||
) : (
|
||||
<><i className="bi bi-plus-lg me-1"></i>{inlineCategoryData.parent_id ? t('categories.createSubcategory') : t('categories.newCategory')}</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<CategorySelector
|
||||
categories={categories}
|
||||
value={quickCategorizeData.category_id}
|
||||
onChange={(e) => setQuickCategorizeData(prev => ({ ...prev, category_id: e.target.value }))}
|
||||
placeholder={t('transactions.selectCategory')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
@ -3860,6 +4207,18 @@ export default function Transactions() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Campo de Observações */}
|
||||
<div className="mb-3">
|
||||
<label className="form-label">{t('transactions.notes')}</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
rows="2"
|
||||
value={quickCategorizeData.notes}
|
||||
onChange={(e) => setQuickCategorizeData(prev => ({ ...prev, notes: e.target.value }))}
|
||||
placeholder={t('transactions.notesPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{quickCategorizeData.transaction?.original_description && (
|
||||
<div className="form-check mb-3">
|
||||
<input
|
||||
@ -3877,7 +4236,10 @@ export default function Transactions() {
|
||||
)}
|
||||
|
||||
<div className="d-flex justify-content-end gap-2">
|
||||
<button type="button" className="btn btn-secondary" onClick={() => setShowQuickCategorizeModal(false)}>
|
||||
<button type="button" className="btn btn-secondary" onClick={() => {
|
||||
setShowQuickCategorizeModal(false);
|
||||
setShowInlineCategoryForm(false);
|
||||
}}>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button type="submit" className="btn btn-success" disabled={savingQuickCategorize}>
|
||||
|
||||
@ -515,7 +515,7 @@ function Users() {
|
||||
setSelectedUser(user);
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
disabled={user.email === 'marco@cnxifly.com'}
|
||||
disabled={user.email === 'marcoitaloesp@icloud.com'}
|
||||
className="btn btn-sm btn-outline-danger"
|
||||
title="Eliminar usuario"
|
||||
>
|
||||
@ -1117,7 +1117,7 @@ function Users() {
|
||||
</div>
|
||||
|
||||
{/* Admin Toggle - Only show if not the main admin */}
|
||||
{editingUser.email !== 'marco@cnxifly.com' && (
|
||||
{editingUser.email !== 'marcoitaloesp@icloud.com' && (
|
||||
<div className="form-check form-switch mb-3">
|
||||
<input
|
||||
className="form-check-input"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user