35330-vm/Backend/categories.php
Flatlogic Bot 1177896daf versao 20
2025-10-29 19:43:33 +00:00

316 lines
14 KiB
PHP

<?php
require_once __DIR__ . '/includes/session.php';
require_once __DIR__ . '/db/config.php';
include __DIR__ . '/includes/header.php';
?>
<div class="container-fluid">
<!-- Page Title -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="page-title">Categorias</h1>
<p class="page-subtitle text-secondary">Gerencie as categorias de despesas para organizar suas finanças.</p>
</div>
<button class="btn btn-primary" id="btnNovaCategoria">
<i data-lucide="plus" class="me-2"></i>Nova Categoria
</button>
</div>
<!-- Main Card -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<i data-lucide="layers" class="section-icon me-2"></i>
<h5 class="card-title mb-0">Lista de Categorias</h5>
</div>
<div class="input-group" style="width: 300px;">
<span class="input-group-text bg-transparent border-end-0"><i data-lucide="search" style="color: #4C5958;"></i></span>
<input type="text" id="searchInput" class="form-control border-start-0" placeholder="Buscar por nome...">
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Categoria</th>
<th>Macro Área</th>
<th class="text-center">Status</th>
<th class="text-end">Ações</th>
</tr>
</thead>
<tbody id="categoriesTableBody">
<!-- Rows will be inserted by JavaScript -->
</tbody>
</table>
</div>
<div id="noResultsMessage" class="text-center p-4" style="display: none;">
<p class="text-secondary">Nenhuma categoria encontrada.</p>
</div>
</div>
<div class="card-footer d-flex justify-content-between align-items-center">
<div class="text-secondary">
Total de categorias: <span id="totalCategoriesBadge" class="badge badge-counter">0</span>
</div>
<!-- Pagination can be added here if needed -->
</div>
</div>
</div>
<!-- Category Modal -->
<div class="modal fade" id="formCategoriaModal" tabindex="-1" aria-labelledby="formCategoriaModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="formCategoriaModalLabel">Nova Categoria</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="formCategoria">
<input type="hidden" id="categoryId" name="id">
<div class="mb-3">
<label for="categoryName" class="form-label">Nome da Categoria</label>
<input type="text" class="form-control" id="categoryName" name="name" required placeholder="Ex: Supermercado, Transporte">
</div>
<div class="mb-3">
<label for="macroAreaSelect" class="form-label">Macro Área</label>
<select class="form-select" id="macroAreaSelect" name="macro_area_id" required>
<option value="">Carregando...</option>
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="categoryStatus" name="is_active" checked>
<label class="form-check-label" for="categoryStatus">Ativo</label>
</div>
</form>
<div id="archiveInfo" class="info-box mt-3" style="display: none;">
<p class="mb-0"><i data-lucide="info" class="me-2"></i>Categorias arquivadas não podem ser usadas em novos lançamentos, mas permanecem nos registros existentes.</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" form="formCategoria" class="btn btn-primary">Salvar</button>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirmar Exclusão</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Tem certeza de que deseja excluir a categoria "<strong id="deleteCategoryName"></strong>"?</p>
<p class="text-danger"><i data-lucide="alert-triangle" class="me-2"></i>Esta ação não pode ser desfeita.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" id="confirmDeleteBtn" class="btn btn-danger">Excluir</button>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/includes/footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const modalElement = document.getElementById('formCategoriaModal');
const modal = new bootstrap.Modal(modalElement);
const deleteModalElement = document.getElementById('deleteConfirmModal');
const deleteModal = new bootstrap.Modal(deleteModalElement);
const form = document.getElementById('formCategoria');
const categoryIdInput = document.getElementById('categoryId');
const categoryNameInput = document.getElementById('categoryName');
const macroAreaSelect = document.getElementById('macroAreaSelect');
const categoryStatusSwitch = document.getElementById('categoryStatus');
const modalTitle = document.getElementById('formCategoriaModalLabel');
const archiveInfo = document.getElementById('archiveInfo');
const searchInput = document.getElementById('searchInput');
let macroAreas = [];
function loadMacroAreas() {
fetch('/Backend/api/category_handler.php?action=getActiveMacroAreas')
.then(response => response.json())
.then(data => {
macroAreas = data;
const select = document.getElementById('macroAreaSelect');
select.innerHTML = '<option value="">Selecione uma Macro Área</option>'; // Clear existing options
macroAreas.forEach(ma => {
const option = new Option(ma.name, ma.id);
select.add(option);
});
})
.catch(error => {
console.error('Erro ao carregar macro áreas:', error);
const select = document.getElementById('macroAreaSelect');
select.innerHTML = '<option value="">Erro ao carregar</option>';
});
}
function fetchCategories(searchTerm = '') {
fetch(`/Backend/api/category_handler.php?search=${encodeURIComponent(searchTerm)}`)
.then(response => response.json())
.then(data => {
const tableBody = document.getElementById('categoriesTableBody');
const noResultsMessage = document.getElementById('noResultsMessage');
tableBody.innerHTML = '';
if (data.length === 0) {
noResultsMessage.style.display = 'block';
} else {
noResultsMessage.style.display = 'none';
}
data.forEach(category => {
const statusBadge = category.is_active
? `<span class="badge badge-status-ativo">Ativo</span>`
: `<span class="badge badge-status-arquivado">Arquivado</span>`;
const row = `
<tr id="category-row-${category.id}">
<td>${escapeHTML(category.name)}</td>
<td>${escapeHTML(category.macro_area_name || 'N/A')}</td>
<td class="text-center">${statusBadge}</td>
<td class="text-end">
<a href="#" class="action-icon me-2" data-id="${category.id}" onclick="handleEdit(event)"><i data-lucide="edit"></i></a>
<a href="#" class="action-icon me-2" data-id="${category.id}" data-active="${category.is_active}" onclick="handleArchive(event)"><i data-lucide="archive"></i></a>
<a href="#" class="action-icon" data-id="${category.id}" data-name="${escapeHTML(category.name)}" onclick="handleDelete(event)"><i data-lucide="trash-2"></i></a>
</td>
</tr>`;
tableBody.insertAdjacentHTML('beforeend', row);
});
document.getElementById('totalCategoriesBadge').textContent = data.length;
lucide.createIcons();
});
}
function handleFormSubmit(event) {
event.preventDefault();
const id = categoryIdInput.value;
const url = id ? `/Backend/api/category_handler.php?id=${id}` : '/Backend/api/category_handler.php';
const method = id ? 'PUT' : 'POST';
const formData = new FormData(form);
const payload = Object.fromEntries(formData.entries());
payload.is_active = categoryStatusSwitch.checked ? 1 : 0;
fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
modal.hide();
fetchCategories(searchInput.value);
} else {
alert('Erro ao salvar categoria: ' + data.error);
}
});
}
window.handleEdit = function(event) {
event.preventDefault();
const id = event.currentTarget.dataset.id;
fetch(`/Backend/api/category_handler.php?id=${id}`)
.then(response => response.json())
.then(category => {
modalTitle.textContent = 'Editar Categoria';
categoryIdInput.value = category.id;
categoryNameInput.value = category.name;
macroAreaSelect.value = category.macro_area_id;
categoryStatusSwitch.checked = category.is_active == 1;
archiveInfo.style.display = category.is_active == 1 ? 'none' : 'block';
modal.show();
});
}
window.handleArchive = function(event) {
event.preventDefault();
const id = event.currentTarget.dataset.id;
const isActive = event.currentTarget.dataset.active == 1;
const action = isActive ? 'archive' : 'unarchive';
if (!confirm(`Tem certeza de que deseja ${isActive ? 'arquivar' : 'reativar'} esta categoria?`)) return;
fetch(`/Backend/api/category_handler.php`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: id, action: action })
})
.then(response => response.json())
.then(data => {
if (data.success) {
fetchCategories(searchInput.value);
} else {
alert('Erro: ' + data.error);
}
});
}
window.handleDelete = function(event) {
event.preventDefault();
const id = event.currentTarget.dataset.id;
const name = event.currentTarget.dataset.name;
document.getElementById('deleteCategoryName').textContent = name;
deleteModal.show();
document.getElementById('confirmDeleteBtn').onclick = function() {
fetch(`/Backend/api/category_handler.php?id=${id}`, { method: 'DELETE' })
.then(response => response.json())
.then(data => {
if (data.success) {
deleteModal.hide();
fetchCategories(searchInput.value);
} else {
alert('Erro ao excluir: ' + data.error);
}
});
}
}
document.getElementById('btnNovaCategoria').addEventListener('click', function() {
modalTitle.textContent = 'Nova Categoria';
form.reset();
categoryIdInput.value = '';
categoryStatusSwitch.checked = true;
archiveInfo.style.display = 'none';
modal.show();
});
searchInput.addEventListener('input', () => fetchCategories(searchInput.value));
form.addEventListener('submit', handleFormSubmit);
function escapeHTML(str) {
if (typeof str !== 'string') return '';
return str.replace(/[&<>'"/]/g, function (tag) {
var chars = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;'
};
return chars[tag] || tag;
});
}
// Initial load
fetchCategories();
loadMacroAreas();
});
</script>