Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -1,159 +0,0 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../includes/session.php';
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// Check for user authentication
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => 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()]);
|
||||
}
|
||||
}
|
||||
@ -1,578 +0,0 @@
|
||||
/* General Body & Layout */
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
transition: margin-left .3s;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
#sidebar {
|
||||
width: 280px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
background: #4C5958;
|
||||
color: #fff;
|
||||
transition: all 0.3s;
|
||||
padding-bottom: 0; /* Allow profile to stick to bottom */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#sidebar.mini {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
#sidebar.mini .sidebar-header h3,
|
||||
#sidebar.mini .menu-item-text,
|
||||
#sidebar.mini .profile-text,
|
||||
#sidebar.mini .dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sidebar.mini .menu-item .lucide {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#sidebar.mini .profile-section .lucide-user-circle {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
background: #4C5958;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #5a6867;
|
||||
flex-shrink: 0; /* Prevent header from shrinking */
|
||||
}
|
||||
|
||||
.sidebar-header h3 {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Profile Section */
|
||||
.profile-section {
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #5a6867; /* Moved from bottom */
|
||||
color: white;
|
||||
flex-shrink: 0; /* Prevent profile from shrinking */
|
||||
}
|
||||
.profile-section .dropdown-toggle {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.profile-section .lucide-user-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.profile-section .profile-text span {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.profile-section .profile-text .username {
|
||||
font-weight: bold;
|
||||
}
|
||||
.profile-section .dropdown-menu {
|
||||
background-color: #3f4a49;
|
||||
border: none;
|
||||
}
|
||||
.profile-section .dropdown-item {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.profile-section .dropdown-item:hover {
|
||||
background-color: #5a6867;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
/* Menu List Styles */
|
||||
.menu-list {
|
||||
list-style: none;
|
||||
padding: 10px 0;
|
||||
margin: 0;
|
||||
flex-grow: 1; /* Allow menu to fill space */
|
||||
overflow-y: auto; /* Add scroll if needed */
|
||||
}
|
||||
|
||||
.menu-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-list .lucide {
|
||||
margin-right: 15px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Top Level Items */
|
||||
.menu-section > a {
|
||||
color: #DBF227;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.menu-section > a:hover {
|
||||
background: #3f4a49;
|
||||
}
|
||||
|
||||
/* Sub Items (Level 2) */
|
||||
.sub-menu {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.sub-menu a {
|
||||
/* NOTE: Using white for better contrast against the dark sidebar.
|
||||
The requested #10403B is too dark for this background. */
|
||||
color: #FFFFFF;
|
||||
font-size: 0.9rem;
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
.sub-menu a:hover {
|
||||
background: #3f4a49;
|
||||
}
|
||||
|
||||
.sub-menu .lucide {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Sub-Sub Items (Level 3) */
|
||||
.sub-sub-menu {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.sub-sub-menu a {
|
||||
color: #e0e0e0; /* Slightly dimmer white */
|
||||
font-size: 0.85rem;
|
||||
padding-left: 55px;
|
||||
}
|
||||
|
||||
.sub-sub-menu a:hover {
|
||||
background: #3f4a49;
|
||||
}
|
||||
|
||||
/* Dropdown Arrow for collapsible menus */
|
||||
.dropdown-toggle::after {
|
||||
display: inline-block;
|
||||
margin-left: auto;
|
||||
vertical-align: .255em;
|
||||
content: "";
|
||||
border-top: .3em solid;
|
||||
border-right: .3em solid transparent;
|
||||
border-bottom: 0;
|
||||
border-left: .3em solid transparent;
|
||||
transition: transform .2s ease-in-out;
|
||||
}
|
||||
|
||||
.dropdown-toggle[aria-expanded="true"]::after {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
/* Badge for notifications */
|
||||
.badge-notification {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 0.2em 0.5em;
|
||||
font-size: 0.7rem;
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Main Content Area */
|
||||
#content {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
transition: all 0.3s;
|
||||
margin-left: 280px;
|
||||
}
|
||||
|
||||
#content.full-width {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#sidebar-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Login Form Styles (keep existing) */
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
#sidebar {
|
||||
left: -280px;
|
||||
}
|
||||
#sidebar.active {
|
||||
left: 0;
|
||||
}
|
||||
#content {
|
||||
margin-left: 0;
|
||||
}
|
||||
#content.full-width {
|
||||
margin-left: 0;
|
||||
}
|
||||
body.sidebar-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar Logo */
|
||||
.sidebar-logo {
|
||||
max-width: 80%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
#sidebar.mini .sidebar-logo {
|
||||
display: none; /* Oculta o logo quando o menu está minimizado */
|
||||
}
|
||||
|
||||
#sidebar.mini .sidebar-header {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* Adiciona um ícone para substituir o logo quando minimizado */
|
||||
#sidebar.mini .sidebar-header::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto;
|
||||
background-image: url('../pasted-20251029-150345-2b427067.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/*
|
||||
* Macro Áreas Module Styles
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
/* Titles and Text */
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
color: #10403B;
|
||||
}
|
||||
|
||||
/* Primary Buttons */
|
||||
.btn-primary {
|
||||
background-color: #005C53;
|
||||
border-color: #005C53;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #00443e;
|
||||
border-color: #00443e;
|
||||
}
|
||||
.btn-primary .fas {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Card Styles */
|
||||
.card {
|
||||
border: 1px solid #005C53;
|
||||
}
|
||||
.card-header {
|
||||
background-color: #8AA6A3;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.card-header .m-0.font-weight-bold {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
/* Table Styles */
|
||||
.table thead th {
|
||||
background-color: #8AA6A3;
|
||||
color: #FFFFFF;
|
||||
border-color: #8AA6A3;
|
||||
}
|
||||
|
||||
/* Badge Styles */
|
||||
.badge-status-ativo {
|
||||
background-color: #8AA6A3;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.badge-status-arquivado {
|
||||
background-color: #f0f0f0; /* gray-100 */
|
||||
color: #333; /* gray-800 */
|
||||
}
|
||||
|
||||
/* Action Icons */
|
||||
.table .btn .fas {
|
||||
color: #4C5958;
|
||||
}
|
||||
|
||||
/*
|
||||
* Macro Áreas Modal Form Styles
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
|
||||
#macroAreaModal .modal-content {
|
||||
background-color: #eeeeee;
|
||||
border: 1px solid #10403B;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-header,
|
||||
#macroAreaModal .modal-body label {
|
||||
color: #10403B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-header {
|
||||
border-bottom: 1px solid #dcdcdc; /* Lighter separator */
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-title {
|
||||
color: #10403B;
|
||||
}
|
||||
|
||||
#macroAreaModal .form-control {
|
||||
color: #4C5958;
|
||||
border: 1px solid #b0b6b5;
|
||||
}
|
||||
|
||||
#macroAreaModal .form-control:focus {
|
||||
border-color: #10403B;
|
||||
box-shadow: 0 0 0 0.2rem rgba(16, 64, 59, 0.25);
|
||||
}
|
||||
|
||||
#macroAreaModal .form-control::placeholder {
|
||||
color: #8AA6A3;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-footer {
|
||||
border-top: 1px solid #dcdcdc; /* Lighter separator */
|
||||
}
|
||||
|
||||
/* Modal Buttons */
|
||||
#macroAreaModal .modal-footer .btn-secondary {
|
||||
background-color: #8AA6A3;
|
||||
border-color: #8AA6A3;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-footer .btn-secondary:hover {
|
||||
background-color: #799592;
|
||||
border-color: #799592;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-footer .btn-primary {
|
||||
background-color: #10403B;
|
||||
border-color: #10403B;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
#macroAreaModal .modal-footer .btn-primary:hover {
|
||||
background-color: #0a2926;
|
||||
border-color: #0a2926;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Custom Switch for "Ativo" field */
|
||||
.form-switch {
|
||||
padding-left: 2.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form-switch .form-check-input {
|
||||
width: 2em;
|
||||
height: 1.25em;
|
||||
margin-left: -2.5em;
|
||||
background-color: #8AA6A3;
|
||||
border-color: #8AA6A3;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: left center;
|
||||
border-radius: 2em;
|
||||
transition: background-position .15s ease-in-out;
|
||||
}
|
||||
.form-switch .form-check-input:checked {
|
||||
background-position: right center;
|
||||
background-color: #10403B;
|
||||
border-color: #10403B;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 824 KiB |
@ -1,128 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Proteger a página
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$pdo = db();
|
||||
$error_message = '';
|
||||
$success_message = '';
|
||||
|
||||
// Definir o mês do orçamento (padrão para o mês atual)
|
||||
$budget_month_str = $_GET['month'] ?? date('Y-m');
|
||||
$budget_month_date = date('Y-m-01', strtotime($budget_month_str . '-01'));
|
||||
|
||||
// Lógica para salvar/atualizar orçamentos
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$budgets = $_POST['budgets'] ?? [];
|
||||
$posted_month = $_POST['budget_month'] ?? $budget_month_date;
|
||||
|
||||
try {
|
||||
$sql = "INSERT INTO budgets (user_id, category, amount, budget_month) VALUES (:user_id, :category, :amount, :budget_month)
|
||||
ON DUPLICATE KEY UPDATE amount = :amount";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
foreach ($budgets as $category => $amount) {
|
||||
if (is_numeric($amount) && $amount >= 0) {
|
||||
$stmt->execute([
|
||||
'user_id' => $user_id,
|
||||
'category' => $category,
|
||||
'amount' => $amount,
|
||||
'budget_month' => $posted_month
|
||||
]);
|
||||
}
|
||||
}
|
||||
$success_message = 'Orçamentos salvos com sucesso!';
|
||||
// Redirecionar para o mesmo mês para mostrar a atualização
|
||||
header('Location: budgets.php?month=' . date('Y-m', strtotime($posted_month)));
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
$error_message = 'Erro ao salvar orçamentos: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar orçamentos existentes para o mês selecionado
|
||||
$existing_budgets = [];
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT category, amount FROM budgets WHERE user_id = :user_id AND budget_month = :budget_month");
|
||||
$stmt->execute(['user_id' => $user_id, 'budget_month' => $budget_month_date]);
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($results as $row) {
|
||||
$existing_budgets[$row['category']] = $row['amount'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_message = "Erro ao buscar orçamentos existentes.";
|
||||
}
|
||||
|
||||
|
||||
// Categorias fixas
|
||||
$categories = ['Alimentação', 'Transporte', 'Moradia', 'Lazer', 'Saúde', 'Outros'];
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">Gerenciar Orçamentos</h1>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Seletor de Mês -->
|
||||
<form method="GET" action="budgets.php" class="mb-4">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label for="month" class="form-label">Selecione o Mês</label>
|
||||
<input type="month" class="form-control" id="month" name="month" value="<?php echo htmlspecialchars($budget_month_str); ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Carregar</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['success_message'])) {
|
||||
echo '<div class="alert alert-success">' . htmlspecialchars($_SESSION['success_message']) . '</div>';
|
||||
unset($_SESSION['success_message']);
|
||||
}?>
|
||||
|
||||
<!-- Formulário de Orçamentos -->
|
||||
<form method="POST" action="budgets.php">
|
||||
<input type="hidden" name="budget_month" value="<?php echo htmlspecialchars($budget_month_date); ?>">
|
||||
<h4 class="mb-3">Orçamentos para <?php echo date('F \d\e Y', strtotime($budget_month_date)); ?></h4>
|
||||
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<div class="row mb-2 align-items-center">
|
||||
<div class="col-md-3">
|
||||
<label for="budget_<?php echo htmlspecialchars($category); ?>" class="form-label"><?php echo htmlspecialchars($category); ?></label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">R$</span>
|
||||
<input type="number" step="0.01" class="form-control" id="budget_<?php echo htmlspecialchars($category); ?>"
|
||||
name="budgets[<?php echo htmlspecialchars($category); ?>]"
|
||||
value="<?php echo htmlspecialchars($existing_budgets[$category] ?? '0.00'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="d-grid mt-4">
|
||||
<button type="submit" class="btn btn-primary">Salvar Orçamentos</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -1,316 +0,0 @@
|
||||
<?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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/'
|
||||
};
|
||||
return chars[tag] || tag;
|
||||
});
|
||||
}
|
||||
|
||||
// Initial load
|
||||
fetchCategories();
|
||||
loadMacroAreas();
|
||||
});
|
||||
</script>
|
||||
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
// Script de migração aprimorado para rodar múltiplos arquivos SQL de forma idempotente.
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// 1. Garantir que a tabela de controle de migrações exista
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `migrations` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`migration_name` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
|
||||
// 2. Obter migrações já executadas
|
||||
$executed_migrations_stmt = $pdo->query("SELECT migration_name FROM migrations");
|
||||
$executed_migrations = $executed_migrations_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// 3. Encontrar todos os arquivos de migração
|
||||
$migration_files = glob(__DIR__ . '/migrations/*.sql');
|
||||
sort($migration_files);
|
||||
|
||||
$new_migrations_run = false;
|
||||
|
||||
// 4. Iterar e executar novas migrações
|
||||
foreach ($migration_files as $file) {
|
||||
$migration_name = basename($file);
|
||||
|
||||
if (!in_array($migration_name, $executed_migrations)) {
|
||||
echo "Executando migração: {$migration_name}...\n";
|
||||
|
||||
$sql = file_get_contents($file);
|
||||
$pdo->exec($sql);
|
||||
|
||||
// Registrar a migração como executada
|
||||
$stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (:migration_name)");
|
||||
$stmt->execute(['migration_name' => $migration_name]);
|
||||
|
||||
echo "Migração '{$migration_name}' executada com sucesso!\n";
|
||||
|
||||
$new_migrations_run = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$new_migrations_run) {
|
||||
echo "Nenhuma nova migração para executar. O banco de dados já está atualizado.\n";
|
||||
} else {
|
||||
echo "\nMigrações concluídas.\n";
|
||||
}
|
||||
|
||||
// Lógica de atualização de senha do usuário de teste (executada sempre)
|
||||
// Garante que a senha seja corrigida mesmo que a migração inicial tenha falhado.
|
||||
$password = 'password123';
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
// Força a atualização da senha para o usuário de teste, independentemente do valor atual.
|
||||
$update_hash_sql = "UPDATE users SET password_hash = :hash WHERE email = 'chefe@familia.com'";
|
||||
$stmt_user = $pdo->prepare($update_hash_sql);
|
||||
$stmt_user->execute(['hash' => $hash]);
|
||||
|
||||
if ($stmt_user->rowCount() > 0) {
|
||||
echo "Hash do usuário de teste foi corrigido para o valor padrão.\n";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Erro na migração: " . $e->getMessage());
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`email` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`password_hash` VARCHAR(255) NOT NULL,
|
||||
`role` ENUM('admin', 'family_head', 'family_member', 'guest') NOT NULL DEFAULT 'family_head',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Inserir um usuário de teste para que possamos fazer login
|
||||
-- A senha será definida pelo script de migração
|
||||
INSERT INTO users (name, email, password_hash, role)
|
||||
SELECT 'Chefe da Família Teste', 'chefe@familia.com', 'placeholder', 'family_head'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'chefe@familia.com');
|
||||
@ -1,10 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `expenses` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
`amount` DECIMAL(10, 2) NOT NULL,
|
||||
`category` VARCHAR(100) NOT NULL,
|
||||
`expense_date` DATE NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@ -1,11 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `budgets` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`user_id` INT NOT NULL,
|
||||
`category` VARCHAR(100) NOT NULL,
|
||||
`amount` DECIMAL(10, 2) NOT NULL,
|
||||
`budget_month` DATE NOT NULL, -- Stores the first day of the month, e.g., 2025-10-01
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `user_category_month` (`user_id`, `category`, `budget_month`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@ -1,11 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS `macro_areas` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nome` VARCHAR(255) NOT NULL,
|
||||
`slug` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`descricao` TEXT,
|
||||
`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 (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@ -1,11 +0,0 @@
|
||||
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;
|
||||
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Proteger a página: redirecionar para o login se não estiver logado
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$expense_id = $_GET['id'] ?? null;
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
if (!$expense_id) {
|
||||
// Se não houver ID, redireciona de volta
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
// A cláusula WHERE garante que um usuário só pode deletar suas próprias despesas
|
||||
$sql = "DELETE FROM expenses WHERE id = :id AND user_id = :user_id";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'id' => $expense_id,
|
||||
'user_id' => $user_id
|
||||
]);
|
||||
|
||||
// Opcional: verificar se alguma linha foi afetada para dar um feedback mais preciso
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$_SESSION['success_message'] = 'Despesa excluída com sucesso!';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Não foi possível excluir a despesa. Ela não foi encontrada ou não pertence a você.';
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Erro ao excluir a despesa: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// Redirecionar de volta para a página de despesas
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/session.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Check if ID is provided
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// First, find the macro area to get its slug
|
||||
$stmt = $pdo->prepare("SELECT slug FROM macro_areas WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$macro_area = $stmt->fetch();
|
||||
|
||||
// If the macro area exists, proceed with checks and deletion
|
||||
if ($macro_area) {
|
||||
$slug = $macro_area['slug'];
|
||||
|
||||
// Check for dependent expenses
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM expenses WHERE category = ?");
|
||||
$stmt->execute([$slug]);
|
||||
$expense_count = $stmt->fetchColumn();
|
||||
|
||||
if ($expense_count > 0) {
|
||||
// Dependencies found, set error message and redirect
|
||||
$_SESSION['error_message'] = "Não é possível excluir a macro área. Existem {$expense_count} despesas associadas.";
|
||||
} else {
|
||||
// No dependencies, proceed with deletion
|
||||
$stmt = $pdo->prepare("DELETE FROM macro_areas WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$_SESSION['success_message'] = "Macro área excluída com sucesso.";
|
||||
}
|
||||
}
|
||||
// If the macro area was not found, we just redirect without any message.
|
||||
|
||||
} else {
|
||||
// ID not specified in URL
|
||||
$_SESSION['error_message'] = "ID da macro área não especificado.";
|
||||
}
|
||||
|
||||
// Redirect back to the list in all cases
|
||||
header('Location: /Backend/macro_areas.php');
|
||||
exit;
|
||||
?>
|
||||
@ -1,126 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Proteger a página
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$expense_id = $_GET['id'] ?? null;
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$error_message = '';
|
||||
$expense = null;
|
||||
|
||||
if (!$expense_id) {
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Buscar a despesa para garantir que ela pertence ao usuário
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM expenses WHERE id = :id AND user_id = :user_id");
|
||||
$stmt->execute(['id' => $expense_id, 'user_id' => $user_id]);
|
||||
$expense = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$expense) {
|
||||
$_SESSION['error_message'] = 'Despesa não encontrada.';
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Erro ao buscar despesa.';
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Lógica para atualizar a despesa
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$description = $_POST['description'] ?? '';
|
||||
$amount = $_POST['amount'] ?? '';
|
||||
$category = $_POST['category'] ?? '';
|
||||
$expense_date = $_POST['expense_date'] ?? '';
|
||||
|
||||
if (empty($description) || empty($amount) || empty($category) || empty($expense_date)) {
|
||||
$error_message = 'Todos os campos são obrigatórios.';
|
||||
} else {
|
||||
try {
|
||||
$sql = "UPDATE expenses SET description = :description, amount = :amount, category = :category, expense_date = :expense_date WHERE id = :id AND user_id = :user_id";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'description' => $description,
|
||||
'amount' => $amount,
|
||||
'category' => $category,
|
||||
'expense_date' => $expense_date,
|
||||
'id' => $expense_id,
|
||||
'user_id' => $user_id
|
||||
]);
|
||||
|
||||
$_SESSION['success_message'] = 'Despesa atualizada com sucesso!';
|
||||
header('Location: expenses.php');
|
||||
exit;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$error_message = 'Erro ao atualizar a despesa: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Editar Despesa</h4>
|
||||
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="edit_expense.php?id=<?php echo htmlspecialchars($expense_id); ?>">
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Descrição</label>
|
||||
<input type="text" class="form-control" id="description" name="description" value="<?php echo htmlspecialchars($expense['description']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">Valor (R$)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="amount" name="amount" value="<?php echo htmlspecialchars($expense['amount']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="category" class="form-label">Categoria</label>
|
||||
<select class="form-select" id="category" name="category" required>
|
||||
<option value="">Selecione...</option>
|
||||
<?php
|
||||
$categories = ['Alimentação', 'Transporte', 'Moradia', 'Lazer', 'Saúde', 'Outros'];
|
||||
foreach ($categories as $cat) {
|
||||
$selected = ($expense['category'] === $cat) ? 'selected' : '';
|
||||
echo "<option value=\"".htmlspecialchars($cat)."\" $selected>".htmlspecialchars($cat)."</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="expense_date" class="form-label">Data da Despesa</label>
|
||||
<input type="date" class="form-control" id="expense_date" name="expense_date" value="<?php echo htmlspecialchars($expense['expense_date']); ?>" required>
|
||||
</div>
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="expenses.php" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -1,247 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Proteger a página
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$pdo = db();
|
||||
$error_message = '';
|
||||
$success_message = '';
|
||||
|
||||
// Lógica para ADICIONAR nova despesa (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$description = $_POST['description'] ?? '';
|
||||
$amount = $_POST['amount'] ?? '';
|
||||
$category = $_POST['category'] ?? '';
|
||||
$expense_date = $_POST['expense_date'] ?? '';
|
||||
|
||||
if (empty($description) || empty($amount) || empty($category) || empty($expense_date)) {
|
||||
$error_message = 'Todos os campos são obrigatórios para adicionar uma despesa.';
|
||||
} else {
|
||||
try {
|
||||
$sql = "INSERT INTO expenses (user_id, description, amount, category, expense_date) VALUES (:user_id, :description, :amount, :category, :expense_date)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'user_id' => $user_id,
|
||||
'description' => $description,
|
||||
'amount' => $amount,
|
||||
'category' => $category,
|
||||
'expense_date' => $expense_date
|
||||
]);
|
||||
$_SESSION['success_message'] = 'Despesa registrada com sucesso!';
|
||||
header('Location: expenses.php'); // Redirecionar para limpar o POST
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
$error_message = 'Erro ao registrar a despesa: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lógica para FILTRAR e BUSCAR despesas (GET)
|
||||
$filter_start_date = $_GET['start_date'] ?? '';
|
||||
$filter_end_date = $_GET['end_date'] ?? '';
|
||||
$filter_category = $_GET['category'] ?? '';
|
||||
|
||||
$sql = "SELECT * FROM expenses WHERE user_id = :user_id";
|
||||
$params = ['user_id' => $user_id];
|
||||
|
||||
if ($filter_start_date) {
|
||||
$sql .= " AND expense_date >= :start_date";
|
||||
$params['start_date'] = $filter_start_date;
|
||||
}
|
||||
if ($filter_end_date) {
|
||||
$sql .= " AND expense_date <= :end_date";
|
||||
$params['end_date'] = $filter_end_date;
|
||||
}
|
||||
if ($filter_category) {
|
||||
$sql .= " AND category = :category";
|
||||
$params['category'] = $filter_category;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY expense_date DESC";
|
||||
|
||||
$expenses = [];
|
||||
$total_filtered_amount = 0;
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Calcular total dos itens filtrados
|
||||
foreach ($expenses as $expense) {
|
||||
$total_filtered_amount += $expense['amount'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_message = 'Erro ao buscar despesas: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// Obter todas as categorias para o dropdown do filtro
|
||||
$categories = ['Alimentação', 'Transporte', 'Moradia', 'Lazer', 'Saúde', 'Outros'];
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<!-- Coluna para Adicionar Despesa -->
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Registrar Nova Despesa</h4>
|
||||
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && $error_message): ?>
|
||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="POST" action="expenses.php">
|
||||
<!-- Campos do formulário de adição -->
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Descrição</label>
|
||||
<input type="text" class="form-control" id="description" name="description" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="amount" class="form-label">Valor (R$)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="amount" name="amount" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="category" class="form-label">Categoria</label>
|
||||
<select class="form-select" id="category" name="category" required>
|
||||
<option value="">Selecione...</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?php echo htmlspecialchars($cat); ?>"><?php echo htmlspecialchars($cat); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="expense_date" class="form-label">Data da Despesa</label>
|
||||
<input type="date" class="form-control" id="expense_date" name="expense_date" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Adicionar Despesa</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Coluna para Listar e Filtrar Despesas -->
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Minhas Despesas</h4>
|
||||
|
||||
<!-- Mensagens de feedback -->
|
||||
<?php
|
||||
if (isset($_SESSION['success_message'])) {
|
||||
echo '<div class="alert alert-success">' . htmlspecialchars($_SESSION['success_message']) . '</div>';
|
||||
unset($_SESSION['success_message']);
|
||||
}
|
||||
if (isset($_SESSION['error_message'])) {
|
||||
echo '<div class="alert alert-danger">' . htmlspecialchars($_SESSION['error_message']) . '</div>';
|
||||
unset($_SESSION['error_message']);
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Formulário de Filtro -->
|
||||
<form method="GET" action="expenses.php" class="mb-4 p-3 bg-light rounded">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label for="start_date" class="form-label">De</label>
|
||||
<input type="date" class="form-control" id="start_date" name="start_date" value="<?php echo htmlspecialchars($filter_start_date); ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="end_date" class="form-label">Até</label>
|
||||
<input type="date" class="form-control" id="end_date" name="end_date" value="<?php echo htmlspecialchars($filter_end_date); ?>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="filter_category" class="form-label">Categoria</label>
|
||||
<select class="form-select" id="filter_category" name="category">
|
||||
<option value="">Todas</option>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?php echo htmlspecialchars($cat); ?>" <?php echo ($filter_category === $cat) ? 'selected' : ''; ?>><?php echo htmlspecialchars($cat); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12 d-flex justify-content-end mt-3">
|
||||
<a href="expenses.php" class="btn btn-secondary me-2">Limpar</a>
|
||||
<button type="submit" class="btn btn-primary me-2">Filtrar</button>
|
||||
<a href="#" id="export-csv" class="btn btn-success"><i class="bi bi-download me-2"></i>Exportar CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Resumo dos Filtros -->
|
||||
<div class="alert alert-info">
|
||||
<strong>Total Filtrado:</strong> R$ <?php echo number_format($total_filtered_amount, 2, ',', '.'); ?>
|
||||
</div>
|
||||
|
||||
<!-- Tabela de Despesas -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Descrição</th>
|
||||
<th>Valor</th>
|
||||
<th>Categoria</th>
|
||||
<th>Data</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($expenses)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">Nenhuma despesa encontrada para os filtros aplicados.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($expenses as $expense): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($expense['description']); ?></td>
|
||||
<td>R$ <?php echo number_format($expense['amount'], 2, ',', '.'); ?></td>
|
||||
<td><?php echo htmlspecialchars($expense['category']); ?></td>
|
||||
<td><?php echo date('d/m/Y', strtotime($expense['expense_date'])); ?></td>
|
||||
<td>
|
||||
<a href="edit_expense.php?id=<?php echo $expense['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil-sm"></i></a>
|
||||
<a href="delete_expense.php?id=<?php echo $expense['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Tem certeza que deseja excluir esta despesa?');"><i class="bi bi-trash-sm"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const exportBtn = document.getElementById('export-csv');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const startDate = document.getElementById('start_date').value;
|
||||
const endDate = document.getElementById('end_date').value;
|
||||
const category = document.getElementById('filter_category').value;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (startDate) params.append('start_date', startDate);
|
||||
if (endDate) params.append('end_date', endDate);
|
||||
if (category) params.append('category', category);
|
||||
|
||||
const exportUrl = 'export.php?' + params.toString();
|
||||
window.location.href = exportUrl;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Check if user is logged in
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$pdo = db();
|
||||
|
||||
// Build query with filters
|
||||
$sql = "SELECT * FROM expenses WHERE user_id = :user_id";
|
||||
$params = ['user_id' => $user_id];
|
||||
|
||||
if (!empty($_GET['start_date'])) {
|
||||
$sql .= " AND expense_date >= :start_date";
|
||||
$params['start_date'] = $_GET['start_date'];
|
||||
}
|
||||
if (!empty($_GET['end_date'])) {
|
||||
$sql .= " AND expense_date <= :end_date";
|
||||
$params['end_date'] = $_GET['end_date'];
|
||||
}
|
||||
if (!empty($_GET['category'])) {
|
||||
$sql .= " AND category = :category";
|
||||
$params['category'] = $_GET['category'];
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY expense_date DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Set headers for CSV download
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="despesas.csv"');
|
||||
|
||||
// Open output stream
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// Write CSV header
|
||||
fputcsv($output, ['Data', 'Descricao', 'Valor', 'Categoria']);
|
||||
|
||||
// Write data
|
||||
if ($expenses) {
|
||||
foreach ($expenses as $expense) {
|
||||
fputcsv($output, [
|
||||
$expense['expense_date'],
|
||||
$expense['description'],
|
||||
$expense['amount'],
|
||||
$expense['category']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit();
|
||||
@ -1,23 +0,0 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
if (sidebarToggle && sidebar && content) {
|
||||
sidebarToggle.addEventListener('click', function() {
|
||||
// This handles both mobile (active) and desktop (mini) states
|
||||
// The CSS media queries will apply the correct styles.
|
||||
sidebar.classList.toggle('active');
|
||||
sidebar.classList.toggle('mini');
|
||||
content.classList.toggle('full-width');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,192 +0,0 @@
|
||||
<?php
|
||||
// Buscar o nome do usuário se estiver logado
|
||||
$user_name = '';
|
||||
$first_name = '';
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT name FROM users WHERE id = :id");
|
||||
$stmt->execute(['id' => $_SESSION['user_id']]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($user) {
|
||||
// Pega o primeiro nome para uma saudação mais curta
|
||||
$first_name = explode(' ', $user['name'])[0];
|
||||
$user_name = $user['name'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Em caso de erro, o nome fica em branco
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder for unclassified expenses count
|
||||
$unclassifiedCount = 5; // Example value
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Concilia Fácil</title>
|
||||
<meta name="description" content="Software de controle financeiro familiar para transformar despesas em investimentos.">
|
||||
<meta name="keywords" content="controle financeiro, finanças pessoais, orçamento familiar, investimentos, economizar dinheiro, gestão de despesas, app de finanças, Built with Flatlogic Generator">
|
||||
<meta property="og:title" content="Concilia Fácil">
|
||||
<meta property="og:description" content="Software de controle financeiro familiar para transformar despesas em investimentos.">
|
||||
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/Backend/assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="wrapper">
|
||||
<!-- Sidebar -->
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<nav id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/Backend/assets/pasted-20251029-150345-2b427067.png" alt="Concilia Fácil Logo" class="sidebar-logo">
|
||||
</div>
|
||||
|
||||
<ul class="list-unstyled menu-list">
|
||||
<!-- Cadastros Básicos -->
|
||||
<li class="menu-section">
|
||||
<a href="#cadastrosSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="folder-tree"></i>
|
||||
<span class="menu-item-text">Cadastros Básicos</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-menu" id="cadastrosSubmenu">
|
||||
<!-- Plano de Contas -->
|
||||
<li>
|
||||
<a href="#planoContasSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="book-text"></i>
|
||||
<span class="menu-item-text">Plano de Contas</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-sub-menu" id="planoContasSubmenu">
|
||||
<li><a href="/Backend/macro_areas.php"><span class="menu-item-text">Macro Áreas</span></a></li>
|
||||
<li><a href="/Backend/categories.php"><span class="menu-item-text">Categorias</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Centro de Custo</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Alocação do Plano</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<!-- Bancos -->
|
||||
<li>
|
||||
<a href="#bancosSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="building-2"></i>
|
||||
<span class="menu-item-text">Bancos</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-sub-menu" id="bancosSubmenu">
|
||||
<li><a href="#"><span class="menu-item-text">Bancos</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Tipo de Conta</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Contas Bancárias</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Status de Pagamento</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Portabilidade</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<!-- Famílias -->
|
||||
<li>
|
||||
<a href="#familiasSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="users"></i>
|
||||
<span class="menu-item-text">Famílias</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-sub-menu" id="familiasSubmenu">
|
||||
<li><a href="#"><span class="menu-item-text">Relações Familiares</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Grupos Familiares</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Composição Familiar</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<!-- Cartões de Crédito -->
|
||||
<li>
|
||||
<a href="#cartoesSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="credit-card"></i>
|
||||
<span class="menu-item-text">Cartões de Crédito</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-sub-menu" id="cartoesSubmenu">
|
||||
<li><a href="#"><span class="menu-item-text">Bandeiras</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Cartões de Crédito</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- Despesas -->
|
||||
<li class="menu-section">
|
||||
<a href="#despesasSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="wallet"></i>
|
||||
<span class="menu-item-text">Despesas</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-menu" id="despesasSubmenu">
|
||||
<li><a href="#"><span class="menu-item-text">Vencimento</span></a></li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<span class="menu-item-text">Identificação de Despesas</span>
|
||||
<?php if ($unclassifiedCount > 0): ?>
|
||||
<span class="badge-notification"><?php echo $unclassifiedCount; ?></span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="/Backend/expenses.php"><span class="menu-item-text">Movimentação Bancária</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Despesas no Cartão</span></a></li>
|
||||
<li><a href="#"><span class="menu-item-text">Previsão de Despesas</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- Análise de Dados -->
|
||||
<li class="menu-section">
|
||||
<a href="#analiseSubmenu" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
|
||||
<i data-lucide="bar-chart-2"></i>
|
||||
<span class="menu-item-text">Análise de Dados</span>
|
||||
</a>
|
||||
<ul class="collapse show list-unstyled sub-menu" id="analiseSubmenu">
|
||||
<li><a href="#"><span class="menu-item-text">Gráfico de Despesas por Áreas</span></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="profile-section dropdown">
|
||||
<a href="#" class="dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i data-lucide="user-circle"></i>
|
||||
<div class="profile-text">
|
||||
<span class="username"><?php echo htmlspecialchars($first_name); ?></span>
|
||||
<span>Meu Perfil</span>
|
||||
</div>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" aria-labelledby="dropdownUser1">
|
||||
<li><a class="dropdown-item" href="/Backend/profile.php">Ver Perfil</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/Backend/logout.php">Sair</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div id="content" class="<?php echo !isset($_SESSION['user_id']) ? 'w-100' : '' ?>">
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<div class="content-header">
|
||||
<button type="button" id="sidebar-toggle" class="btn">
|
||||
<i data-lucide="menu"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<?php echo $_SESSION['success_message']; ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<?php echo $_SESSION['error_message']; ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Define um nome de sessão específico para a aplicação
|
||||
$session_name = 'flatlogic_app_session';
|
||||
session_name($session_name);
|
||||
|
||||
// Determina se a conexão é segura, considerando o proxy reverso (Cloudflare)
|
||||
$is_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
|
||||
|
||||
// Configurações de segurança para o cookie da sessão
|
||||
if ($is_secure) {
|
||||
// Configuração para produção sob HTTPS (essencial para proxies)
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'None' // 'None' requer 'secure' => true
|
||||
]);
|
||||
} else {
|
||||
// Configuração para desenvolvimento local (HTTP)
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
}
|
||||
|
||||
// Inicia a sessão
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
require_once __DIR__ . '/includes/session.php';
|
||||
|
||||
// Se já estiver logado, redireciona para o dashboard
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header('Location: /index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$error_message = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
$error_message = 'Por favor, preencha o e-mail e a senha.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE email = :email");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user && password_verify($password, $user['password_hash'])) {
|
||||
// Login bem-sucedido: Redireciona com flag de depuração
|
||||
// Senha correta, inicie a sessão
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_email'] = $user['email'];
|
||||
header("Location: /index.php");
|
||||
exit();
|
||||
} else {
|
||||
// Credenciais inválidas
|
||||
$error_message = 'E-mail ou senha inválidos.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Idealmente, logar o erro em vez de exibir
|
||||
$error_message = 'Erro no banco de dados. Tente novamente mais tarde.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container login-container">
|
||||
<div class="card login-card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-center mb-4">
|
||||
<i class="bi bi-safe me-2"></i>
|
||||
<span class="navbar-brand-logo">Galilei Finance</span>
|
||||
</h3>
|
||||
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<?php echo htmlspecialchars($error_message); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="/Backend/login.php">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">E-mail</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Senha</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Entrar</button>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<a href="#">Esqueceu a senha?</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header('Location: /Backend/login.php');
|
||||
exit;
|
||||
@ -1,114 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/session.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Helper function to generate a slug from a string
|
||||
function generateSlug($string) {
|
||||
$string = iconv('UTF-8', 'ASCII//TRANSLIT', $string);
|
||||
$string = strtolower($string);
|
||||
$string = preg_replace('/[^a-z0-9_\-]+/', '-', $string);
|
||||
$string = preg_replace('/-+/', '-', $string);
|
||||
$string = trim($string, '-');
|
||||
return $string;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$error = null;
|
||||
$macro_area = [
|
||||
'id' => '',
|
||||
'nome' => '',
|
||||
'descricao' => '',
|
||||
'ativo' => 1
|
||||
];
|
||||
$page_title = 'Nova Macro Área';
|
||||
|
||||
// Handle form submission (Create/Update)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = $_POST['id'] ?? null;
|
||||
$nome = trim($_POST['nome'] ?? '');
|
||||
$descricao = trim($_POST['descricao'] ?? '');
|
||||
$ativo = isset($_POST['ativo']) ? 1 : 0;
|
||||
$slug = generateSlug($nome);
|
||||
|
||||
if (empty($nome)) {
|
||||
$error = "O campo Nome é obrigatório.";
|
||||
// Repopulate form data on error
|
||||
$macro_area = $_POST;
|
||||
} else {
|
||||
$stmt = $pdo->prepare('SELECT id FROM macro_areas WHERE (nome = ? OR slug = ?) AND id <> ?');
|
||||
$stmt->execute([$nome, $slug, $id ?: 0]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = "Já existe uma Macro Área com este nome.";
|
||||
// Repopulate form data on error
|
||||
$macro_area = $_POST;
|
||||
} else {
|
||||
if ($id) {
|
||||
// Update
|
||||
$stmt = $pdo->prepare('UPDATE macro_areas SET nome = ?, slug = ?, descricao = ?, ativo = ? WHERE id = ?');
|
||||
$stmt->execute([$nome, $slug, $descricao, $ativo, $id]);
|
||||
$redirect_id = $id;
|
||||
} else {
|
||||
// Create
|
||||
$stmt = $pdo->prepare('INSERT INTO macro_areas (nome, slug, descricao, ativo, user_id) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$nome, $slug, $descricao, $ativo, $_SESSION['user_id'] ?? 1]);
|
||||
$redirect_id = $pdo->lastInsertId();
|
||||
}
|
||||
header("Location: /Backend/macro_area_form.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} elseif (isset($_GET['id'])) {
|
||||
// Handle edit mode (fetch data)
|
||||
$id = $_GET['id'];
|
||||
$stmt = $pdo->prepare('SELECT * FROM macro_areas WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($data) {
|
||||
$macro_area = $data;
|
||||
$page_title = 'Editar Macro Área';
|
||||
}
|
||||
}
|
||||
include_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0"><?php echo $page_title; ?></h1>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?php echo $error; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold" style="color: #005C53;">Detalhes da Macro Área</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="macroAreaForm" method="POST" action="/Backend/macro_area_form.php<?php echo !empty($macro_area['id']) ? '?id='.$macro_area['id'] : ''; ?>">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($macro_area['id']); ?>">
|
||||
<div class="mb-3">
|
||||
<label for="macroAreaNome" class="form-label">Nome *</label>
|
||||
<input type="text" class="form-control" id="macroAreaNome" name="nome" placeholder="Ex: Moradia, Alimentação, Saúde" value="<?php echo htmlspecialchars($macro_area['nome']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="macroAreaDescricao" class="form-label">Descrição</label>
|
||||
<textarea class="form-control" id="macroAreaDescricao" name="descricao" rows="3" placeholder="Descrição opcional da macro área"><?php echo htmlspecialchars($macro_area['descricao']); ?></textarea>
|
||||
</div>
|
||||
<div class="mb-3 form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="macroAreaAtivo" name="ativo" value="1" <?php echo !empty($macro_area['ativo']) && $macro_area['ativo'] ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="macroAreaAtivo">Ativo</label>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="/Backend/macro_areas.php" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">Salvar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include_once 'includes/footer.php'; ?>
|
||||
@ -1,191 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/session.php';
|
||||
require_once 'db/config.php';
|
||||
include_once 'includes/header.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->query('SELECT * FROM macro_areas ORDER BY nome ASC');
|
||||
$macro_areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0">Macro Áreas</h1>
|
||||
<div>
|
||||
<button id="printButton" class="btn btn-secondary btn-icon-split">
|
||||
<span class="icon text-white-50"><i data-lucide="file-text" style="color: #FFFFFF;"></i></span>
|
||||
<span class="text">Imprimir Lista</span>
|
||||
</button>
|
||||
<a href="/Backend/macro_area_form.php" class="btn btn-primary btn-icon-split">
|
||||
<span class="icon text-white-50"><i data-lucide="plus" style="color: #FFFFFF;"></i></span>
|
||||
<span class="text">Nova Macro Área</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['error_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold" style="color: #005C53;">
|
||||
<i data-lucide="layers" class="me-2"></i>Lista de Macro Áreas
|
||||
</h6>
|
||||
<div class="input-group" style="width: 250px;">
|
||||
<span class="input-group-text text-slate-400"><i data-lucide="search"></i></span>
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="Buscar...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Descrição</th>
|
||||
<th>Status</th>
|
||||
<th style="width: 85px;">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
<?php foreach ($macro_areas as $area):
|
||||
$badgeClass = $area['ativo'] ? 'badge-status-ativo' : 'badge-status-arquivado';
|
||||
$statusText = $area['ativo'] ? 'Ativo' : 'Arquivado';
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($area['nome']); ?></td>
|
||||
<td><?php echo htmlspecialchars($area['descricao']); ?></td>
|
||||
<td>
|
||||
<span class="badge <?php echo $badgeClass; ?>"><?php echo $statusText; ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex">
|
||||
<a href="/Backend/macro_area_form.php?id=<?php echo $area['id']; ?>" class="btn btn-sm">
|
||||
<i data-lucide="edit" style="color: #4C5958; width: 18px; height: 18px;"></i>
|
||||
</a>
|
||||
<a href="/Backend/delete_macro_area.php?id=<?php echo $area['id']; ?>" class="btn btn-sm">
|
||||
<i data-lucide="trash-2" style="color: #4C5958; width: 18px; height: 18px;"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($macro_areas)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Nenhuma macro área encontrada.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include_once 'includes/footer.php'; ?>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Search functionality
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const tableBody = document.getElementById('tableBody');
|
||||
const tableRows = tableBody.getElementsByTagName('tr');
|
||||
|
||||
searchInput.addEventListener('keyup', function() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
|
||||
for (let i = 0; i < tableRows.length; i++) {
|
||||
const row = tableRows[i];
|
||||
const cells = row.getElementsByTagName('td');
|
||||
let match = false;
|
||||
// Start search from the first cell, and not the last one (actions)
|
||||
for (let j = 0; j < cells.length - 1; j++) {
|
||||
if (cells[j]) {
|
||||
if (cells[j].textContent.toLowerCase().indexOf(searchTerm) > -1) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Print functionality
|
||||
const printButton = document.getElementById('printButton');
|
||||
printButton.addEventListener('click', function() {
|
||||
fetch('/Backend/print_macro_areas.php')
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(html);
|
||||
printWindow.document.close();
|
||||
printWindow.focus(); // Required for some browsers
|
||||
|
||||
// Use a small timeout to ensure content is loaded before printing
|
||||
setTimeout(() => {
|
||||
printWindow.print();
|
||||
}, 250);
|
||||
|
||||
printWindow.addEventListener('afterprint', () => {
|
||||
printWindow.close();
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error fetching print content:', error));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#printButton {
|
||||
background-color: #005C53;
|
||||
border-color: #005C53;
|
||||
}
|
||||
#printButton:hover {
|
||||
background-color: #004c43;
|
||||
border-color: #004c43;
|
||||
}
|
||||
.input-group-text {
|
||||
background-color: #fff;
|
||||
border-right: 0;
|
||||
}
|
||||
#searchInput {
|
||||
border-left: 0;
|
||||
}
|
||||
.btn-sm i[data-lucide] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
/* Custom styles for status badges */
|
||||
.badge-status-ativo {
|
||||
background-color: #d1e7dd; /* Light green */
|
||||
color: #0f5132; /* Dark green */
|
||||
border: 1px solid #badbcc;
|
||||
}
|
||||
|
||||
.badge-status-arquivado {
|
||||
background-color: #f8d7da; /* Light red */
|
||||
color: #842029; /* Dark red */
|
||||
border: 1px solid #f5c2c7;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/session.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query('SELECT * FROM macro_areas ORDER BY nome ASC');
|
||||
$macro_areas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$total = count($macro_areas);
|
||||
$active = 0;
|
||||
$archived = 0;
|
||||
|
||||
foreach ($macro_areas as $area) {
|
||||
if ($area['ativo']) {
|
||||
$active++;
|
||||
} else {
|
||||
$archived++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lista de Macro Áreas</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.summary {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@page {
|
||||
margin: 10mm;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<img src="assets/pasted-20251029-150345-2b427067.png" alt="Logotipo Galilei" style="max-width: 150px; height: auto;">
|
||||
</div>
|
||||
<h1>Lista de Macro Áreas</h1>
|
||||
<div class="header">
|
||||
Gerado em: <?php echo date('d/m/Y H:i:s'); ?>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Nome</th>
|
||||
<th>Descrição</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $count = 1; foreach ($macro_areas as $area): ?>
|
||||
<tr>
|
||||
<td><?php echo $count++; ?></td>
|
||||
<td><strong><?php echo htmlspecialchars($area['nome']); ?></strong></td>
|
||||
<td><?php echo htmlspecialchars($area['descricao']); ?></td>
|
||||
<td><?php echo $area['ativo'] ? 'Ativo' : 'Arquivado'; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($macro_areas)): ?>
|
||||
<tr>
|
||||
<td colspan="4" style="text-align: center;">Nenhuma macro área encontrada.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="summary">
|
||||
<p>
|
||||
<strong>Total de Macro Áreas:</strong> <?php echo $total; ?> |
|
||||
<strong>Ativas:</strong> <?php echo $active; ?> |
|
||||
<strong>Arquivadas:</strong> <?php echo $archived; ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$pdo = db();
|
||||
$update_name_error = '';
|
||||
$update_name_success = '';
|
||||
$update_password_error = '';
|
||||
$update_password_success = '';
|
||||
|
||||
// Obter dados do usuário
|
||||
$stmt = $pdo->prepare("SELECT name, email FROM users WHERE id = :id");
|
||||
$stmt->execute(['id' => $user_id]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Lógica para lidar com o POST
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Atualizar Nome
|
||||
if (isset($_POST['update_name'])) {
|
||||
$name = $_POST['name'] ?? '';
|
||||
if (empty($name)) {
|
||||
$update_name_error = 'O nome não pode estar em branco.';
|
||||
} else {
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE users SET name = :name WHERE id = :id");
|
||||
$stmt->execute(['name' => $name, 'id' => $user_id]);
|
||||
$user['name'] = $name; // Atualizar o nome na página
|
||||
$update_name_success = 'Nome atualizado com sucesso!';
|
||||
} catch (PDOException $e) {
|
||||
$update_name_error = 'Erro ao atualizar o nome.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atualizar Senha
|
||||
if (isset($_POST['update_password'])) {
|
||||
$current_password = $_POST['current_password'] ?? '';
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
|
||||
$update_password_error = 'Todos os campos de senha são obrigatórios.';
|
||||
} elseif ($new_password !== $confirm_password) {
|
||||
$update_password_error = 'A nova senha e a confirmação não correspondem.';
|
||||
} else {
|
||||
try {
|
||||
// Verificar senha atual
|
||||
$stmt = $pdo->prepare("SELECT password_hash FROM users WHERE id = :id");
|
||||
$stmt->execute(['id' => $user_id]);
|
||||
$hash = $stmt->fetchColumn();
|
||||
|
||||
if (password_verify($current_password, $hash)) {
|
||||
// Se a senha atual estiver correta, criar novo hash e atualizar
|
||||
$new_hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
$stmt_update = $pdo->prepare("UPDATE users SET password_hash = :hash WHERE id = :id");
|
||||
$stmt_update->execute(['hash' => $new_hash, 'id' => $user_id]);
|
||||
$update_password_success = 'Senha atualizada com sucesso!';
|
||||
} else {
|
||||
$update_password_error = 'A senha atual está incorreta.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$update_password_error = 'Erro ao atualizar a senha.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">Meu Perfil</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Atualizar Informações</div>
|
||||
<div class="card-body">
|
||||
<?php if ($update_name_success): ?><div class="alert alert-success"><?php echo $update_name_success; ?></div><?php endif; ?>
|
||||
<?php if ($update_name_error): ?><div class="alert alert-danger"><?php echo $update_name_error; ?></div><?php endif; ?>
|
||||
|
||||
<form method="POST" action="profile.php">
|
||||
<input type="hidden" name="update_name" value="1">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nome</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($user['name']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">E-mail</label>
|
||||
<input type="email" class="form-control" id="email" value="<?php echo htmlspecialchars($user['email']); ?>" disabled>
|
||||
<div class="form-text">O e-mail não pode ser alterado.</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Salvar Nome</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Alterar Senha</div>
|
||||
<div class="card-body">
|
||||
<?php if ($update_password_success): ?><div class="alert alert-success"><?php echo $update_password_success; ?></div><?php endif; ?>
|
||||
<?php if ($update_password_error): ?><div class="alert alert-danger"><?php echo $update_password_error; ?></div><?php endif; ?>
|
||||
|
||||
<form method="POST" action="profile.php">
|
||||
<input type="hidden" name="update_password" value="1">
|
||||
<div class="mb-3">
|
||||
<label for="current_password" class="form-label">Senha Atual</label>
|
||||
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">Nova Senha</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirmar Nova Senha</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Alterar Senha</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||
172
index.php
172
index.php
@ -1,30 +1,150 @@
|
||||
<?php
|
||||
require_once 'Backend/includes/session.php';
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
// Verifica se o usuário está logado, redireciona para o login se não estiver
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: Backend/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'Backend/db/config.php';
|
||||
require_once 'Backend/includes/header.php';
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Dashboard</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Bem-vindo ao seu painel de controle de despesas!</p>
|
||||
<p>Use os links na barra de navegação para gerenciar suas despesas e orçamentos.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'Backend/includes/footer.php'; ?>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user