diff --git a/Backend/api/category_handler.php b/Backend/api/category_handler.php new file mode 100644 index 0000000..37b794c --- /dev/null +++ b/Backend/api/category_handler.php @@ -0,0 +1,159 @@ + false, 'error' => 'Usuário não autenticado.']); + exit; +} + +$method = $_SERVER['REQUEST_METHOD']; +$pdo = db(); + +switch ($method) { + case 'GET': + handleGet($pdo); + break; + case 'POST': + handlePost($pdo); + break; + case 'PUT': + handlePut($pdo); + break; + case 'PATCH': + handlePatch($pdo); + break; + case 'DELETE': + handleDelete($pdo); + break; + default: + echo json_encode(['success' => false, 'error' => 'Método não suportado.']); + break; +} + +function handleGet($pdo) { + try { + if (isset($_GET['action']) && $_GET['action'] == 'getActiveMacroAreas') { + $stmt = $pdo->prepare("SELECT id, name FROM macro_areas WHERE is_active = 1 AND user_id = :user_id ORDER BY name ASC"); + $stmt->execute(['user_id' => $_SESSION['user_id']]); + $macro_areas = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode($macro_areas); + } elseif (isset($_GET['id'])) { + $stmt = $pdo->prepare("SELECT * FROM categories WHERE id = :id"); + $stmt->execute(['id' => $_GET['id']]); + $category = $stmt->fetch(PDO::FETCH_ASSOC); + echo json_encode($category); + } else { + $searchTerm = isset($_GET['search']) ? '%' . $_GET['search'] . '%' : '%'; + $sql = "SELECT c.id, c.name, c.is_active, c.macro_area_id, ma.name as macro_area_name + FROM categories c + LEFT JOIN macro_areas ma ON c.macro_area_id = ma.id + WHERE c.name LIKE :searchTerm + ORDER BY c.name ASC"; + $stmt = $pdo->prepare($sql); + $stmt->execute(['searchTerm' => $searchTerm]); + $categories = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode($categories); + } + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Erro ao buscar dados: ' . $e->getMessage()]); + } +} + +function handlePost($pdo) { + $data = json_decode(file_get_contents('php://input'), true); + + if (empty($data['name']) || empty($data['macro_area_id'])) { + echo json_encode(['success' => false, 'error' => 'Nome e Macro Área são obrigatórios.']); + return; + } + + try { + $sql = "INSERT INTO categories (name, macro_area_id, is_active) VALUES (:name, :macro_area_id, :is_active)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([ + ':name' => $data['name'], + ':macro_area_id' => $data['macro_area_id'], + ':is_active' => isset($data['is_active']) ? $data['is_active'] : 1 + ]); + echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Erro ao criar categoria: ' . $e->getMessage()]); + } +} + +function handlePut($pdo) { + $id = $_GET['id'] ?? null; + if (!$id) { + echo json_encode(['success' => false, 'error' => 'ID da categoria não fornecido.']); + return; + } + + $data = json_decode(file_get_contents('php://input'), true); + + if (empty($data['name']) || empty($data['macro_area_id'])) { + echo json_encode(['success' => false, 'error' => 'Nome e Macro Área são obrigatórios.']); + return; + } + + try { + $sql = "UPDATE categories SET name = :name, macro_area_id = :macro_area_id, is_active = :is_active WHERE id = :id"; + $stmt = $pdo->prepare($sql); + $stmt->execute([ + ':id' => $id, + ':name' => $data['name'], + ':macro_area_id' => $data['macro_area_id'], + ':is_active' => isset($data['is_active']) ? $data['is_active'] : 1 + ]); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Erro ao atualizar categoria: ' . $e->getMessage()]); + } +} + +function handlePatch($pdo) { + $data = json_decode(file_get_contents('php://input'), true); + $id = $data['id'] ?? null; + $action = $data['action'] ?? null; + + if (!$id || !$action) { + echo json_encode(['success' => false, 'error' => 'Dados inválidos.']); + return; + } + + $newStatus = ($action === 'archive') ? 0 : 1; + + try { + $sql = "UPDATE categories SET is_active = :is_active WHERE id = :id"; + $stmt = $pdo->prepare($sql); + $stmt->execute([ + ':id' => $id, + ':is_active' => $newStatus + ]); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Erro ao atualizar status: ' . $e->getMessage()]); + } +} + +function handleDelete($pdo) { + $id = $_GET['id'] ?? null; + if (!$id) { + echo json_encode(['success' => false, 'error' => 'ID da categoria não fornecido.']); + return; + } + + try { + // Check for dependencies before deleting if necessary + // For example, check if any expenses are using this category + + $sql = "DELETE FROM categories WHERE id = :id"; + $stmt = $pdo->prepare($sql); + $stmt->execute(['id' => $id]); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Erro ao excluir categoria: ' . $e->getMessage()]); + } +} \ No newline at end of file diff --git a/Backend/assets/css/custom.css b/Backend/assets/css/custom.css index 2f5b65f..1059600 100644 --- a/Backend/assets/css/custom.css +++ b/Backend/assets/css/custom.css @@ -455,3 +455,124 @@ body { .form-switch .form-check-label { padding-top: 2px; } + +/* + * Categories Module Styles + * -------------------------------------------------- + */ + +/* Typography Utilities */ +.text-3xl { font-size: 1.875rem; /* 30px */ } +.font-bold { font-weight: 700; } +.text-main { color: #10403B; } +.text-secondary { color: #4C5958; } + +/* Page Header */ +.page-title { + font-size: 1.875rem; /* 30px */ + font-weight: 700; + color: #10403B; +} +.page-subtitle { + color: #10403B; +} + +/* Action Icons */ +.action-icon { + color: #4C5958; + cursor: pointer; + text-decoration: none; +} +.action-icon:hover { + color: #10403B; +} + +/* Table hover */ +.table-hover tbody tr:hover { + background-color: #f8f9fa; /* Corresponds to hover:bg-slate-50 */ +} + +/* Counter Badge */ +.badge-counter { + background-color: #8AA6A3; + color: #FFFFFF; +} + +/* Status Badges (overriding defaults for consistency) */ +.badge-status-ativo { + background-color: #8AA6A3; + color: #FFFFFF; +} +.badge-status-arquivado { + background-color: #e5e7eb; /* bg-gray-100 */ + color: #1f2937; /* text-gray-800 */ +} + +/* Section Icon */ +.section-icon { + color: #005C53; +} + +/* + * Category Modal Form (#formCategoriaModal) + * -------------------------------------------------- + */ +#formCategoriaModal .modal-content { + background-color: #eeeeee; + border: 1px solid #10403B; +} + +#formCategoriaModal .modal-header, +#formCategoriaModal .modal-body label { + color: #10403B; + font-weight: 500; +} + +#formCategoriaModal .modal-header { + border-bottom: 1px solid #dcdcdc; +} + +#formCategoriaModal .modal-title { + color: #10403B; +} + +#formCategoriaModal .form-control, +#formCategoriaModal .form-select { + color: #4C5958; + border: 1px solid #b0b6b5; +} + +#formCategoriaModal .form-control:focus, +#formCategoriaModal .form-select:focus { + border-color: #10403B; + box-shadow: 0 0 0 0.2rem rgba(16, 64, 59, 0.25); +} + +#formCategoriaModal .form-control::placeholder { + color: #4C5958; + opacity: 0.8; +} + +#formCategoriaModal .modal-footer { + border-top: 1px solid #dcdcdc; +} + +/* Modal Buttons */ +#formCategoriaModal .btn-delete-confirm { + background-color: #8AA6A3; + border-color: #8AA6A3; + color: #FFFFFF; +} +#formCategoriaModal .btn-delete-confirm:hover { + background-color: #799592; + border-color: #799592; +} + +/* Info Box */ +.info-box { + background-color: #f0f9ff; + border: 1px solid #bae6fd; + border-radius: 0.375rem; + padding: 1rem; + color: #0c5460; +} diff --git a/Backend/categories.php b/Backend/categories.php new file mode 100644 index 0000000..b74d599 --- /dev/null +++ b/Backend/categories.php @@ -0,0 +1,316 @@ + + +
+ + +
+
+

Categorias

+

Gerencie as categorias de despesas para organizar suas finanças.

+
+ +
+ + +
+
+
+ +
Lista de Categorias
+
+
+ + +
+
+
+
+ + + + + + + + + + + + +
CategoriaMacro ÁreaStatusAções
+
+ +
+ +
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Backend/db/migrations/005_create_categories_table.sql b/Backend/db/migrations/005_create_categories_table.sql new file mode 100644 index 0000000..0b4f619 --- /dev/null +++ b/Backend/db/migrations/005_create_categories_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `categories` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `nome` VARCHAR(255) NOT NULL, + `macro_area_id` INT NOT NULL, + `ativo` BOOLEAN NOT NULL DEFAULT TRUE, + `user_id` INT, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (`macro_area_id`) REFERENCES `macro_areas`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/Backend/includes/header.php b/Backend/includes/header.php index 3e6da47..fd6566c 100644 --- a/Backend/includes/header.php +++ b/Backend/includes/header.php @@ -71,7 +71,7 @@ $unclassifiedCount = 5; // Example value