From a0c63edc926beea2bd58b82be6549c4b368cc4e5 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 29 Oct 2025 14:04:14 +0000 Subject: [PATCH] versao 2 --- budgets.php | 128 ++++++++++ db/migrate.php | 67 ++++-- db/migrations/002_create_expenses_table.sql | 10 + db/migrations/003_create_budgets_table.sql | 11 + delete_expense.php | 46 ++++ edit_expense.php | 126 ++++++++++ expenses.php | 247 ++++++++++++++++++++ export.php | 60 +++++ includes/header.php | 57 ++++- index.php | 203 +++++++++++----- login.php | 10 + profile.php | 136 +++++++++++ 12 files changed, 1023 insertions(+), 78 deletions(-) create mode 100644 budgets.php create mode 100644 db/migrations/002_create_expenses_table.sql create mode 100644 db/migrations/003_create_budgets_table.sql create mode 100644 delete_expense.php create mode 100644 edit_expense.php create mode 100644 expenses.php create mode 100644 export.php create mode 100644 profile.php diff --git a/budgets.php b/budgets.php new file mode 100644 index 0000000..cef7f5f --- /dev/null +++ b/budgets.php @@ -0,0 +1,128 @@ +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'; +?> + +
+

Gerenciar Orçamentos

+ +
+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + +
+ + ' . htmlspecialchars($_SESSION['success_message']) . '
'; + unset($_SESSION['success_message']); + }?> + + +
+ +

Orçamentos para

+ + +
+
+ +
+
+
+ R$ + +
+
+
+ + +
+ +
+
+
+
+ + + diff --git a/db/migrate.php b/db/migrate.php index 33d67ce..ebb2912 100644 --- a/db/migrate.php +++ b/db/migrate.php @@ -1,31 +1,66 @@ exec($sql); + // 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;"); - echo "Migração '001_create_users_table.sql' executada com sucesso!\n"; + // 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); - // Criar hash de senha para o usuário de teste + // 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); - - // Atualizar o hash no SQL para garantir que é compatível com a versão do PHP + // 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 = $pdo->prepare($update_hash_sql); - $stmt->execute(['hash' => $hash]); - echo "Hash do usuário de teste atualizado no banco de dados.\n\n"; + $stmt_user = $pdo->prepare($update_hash_sql); + $stmt_user->execute(['hash' => $hash]); - echo "Usuário de teste criado:\n"; - echo "Email: chefe@familia.com\n"; - echo "Senha: " . $password . "\n"; + 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()); -} - +} \ No newline at end of file diff --git a/db/migrations/002_create_expenses_table.sql b/db/migrations/002_create_expenses_table.sql new file mode 100644 index 0000000..c2d8882 --- /dev/null +++ b/db/migrations/002_create_expenses_table.sql @@ -0,0 +1,10 @@ +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; \ No newline at end of file diff --git a/db/migrations/003_create_budgets_table.sql b/db/migrations/003_create_budgets_table.sql new file mode 100644 index 0000000..31584a5 --- /dev/null +++ b/db/migrations/003_create_budgets_table.sql @@ -0,0 +1,11 @@ +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; \ No newline at end of file diff --git a/delete_expense.php b/delete_expense.php new file mode 100644 index 0000000..606f266 --- /dev/null +++ b/delete_expense.php @@ -0,0 +1,46 @@ +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; diff --git a/edit_expense.php b/edit_expense.php new file mode 100644 index 0000000..8870636 --- /dev/null +++ b/edit_expense.php @@ -0,0 +1,126 @@ +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'; +?> + +
+
+
+
+
+

Editar Despesa

+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Cancelar + +
+
+
+
+
+
+
+ + diff --git a/expenses.php b/expenses.php new file mode 100644 index 0000000..899a60f --- /dev/null +++ b/expenses.php @@ -0,0 +1,247 @@ +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'; +?> + +
+
+ +
+
+
+

Registrar Nova Despesa

+ +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + +
+
+
+

Minhas Despesas

+ + + ' . htmlspecialchars($_SESSION['success_message']) . '
'; + unset($_SESSION['success_message']); + } + if (isset($_SESSION['error_message'])) { + echo '
' . htmlspecialchars($_SESSION['error_message']) . '
'; + unset($_SESSION['error_message']); + } + ?> + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ Limpar + + Exportar CSV +
+
+
+ + +
+ Total Filtrado: R$ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
DescriçãoValorCategoriaDataAções
Nenhuma despesa encontrada para os filtros aplicados.
R$ + + +
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/export.php b/export.php new file mode 100644 index 0000000..d724ac3 --- /dev/null +++ b/export.php @@ -0,0 +1,60 @@ + $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(); \ No newline at end of file diff --git a/includes/header.php b/includes/header.php index 727d178..320a6f2 100644 --- a/includes/header.php +++ b/includes/header.php @@ -2,16 +2,32 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } + +// Buscar o nome do usuário se estiver logado +$user_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) { + $user_name = $user['name']; + } + } catch (PDOException $e) { + // Em caso de erro, o nome fica em branco + } +} ?> - Galilei-finance-v1 + Galilei Finance - + @@ -25,3 +41,40 @@ if (session_status() === PHP_SESSION_NONE) { + + diff --git a/index.php b/index.php index a2014e7..f764c0e 100644 --- a/index.php +++ b/index.php @@ -3,7 +3,6 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } -// Proteger a página: se não estiver logado, redireciona para o login if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; @@ -11,76 +10,160 @@ if (!isset($_SESSION['user_id'])) { require_once __DIR__ . '/db/config.php'; -try { - $pdo = db(); - $stmt = $pdo->prepare("SELECT id, name, email, role FROM users WHERE id = :id"); - $stmt->execute(['id' => $_SESSION['user_id']]); - $user = $stmt->fetch(PDO::FETCH_ASSOC); -} catch (PDOException $e) { - // Em caso de erro, desloga o usuário para segurança - header('Location: logout.php'); - exit; +$user_id = $_SESSION['user_id']; +$pdo = db(); +$current_month_date = date('Y-m-01'); + +// --- Coleta de Dados para o Dashboard --- + +// 1. Despesas do Mês Atual +$stmt_expenses = $pdo->prepare( + "SELECT category, SUM(amount) as total_spent FROM expenses + WHERE user_id = :user_id AND MONTH(expense_date) = MONTH(CURRENT_DATE()) AND YEAR(expense_date) = YEAR(CURRENT_DATE()) + GROUP BY category" +); +$stmt_expenses->execute(['user_id' => $user_id]); +$monthly_expenses = $stmt_expenses->fetchAll(PDO::FETCH_KEY_PAIR); + +// 2. Orçamentos do Mês Atual +$stmt_budgets = $pdo->prepare("SELECT category, amount FROM budgets WHERE user_id = :user_id AND budget_month = :budget_month"); +$stmt_budgets->execute(['user_id' => $user_id, 'budget_month' => $current_month_date]); +$monthly_budgets = $stmt_budgets->fetchAll(PDO::FETCH_KEY_PAIR); + +// 3. Consolidar dados de Orçamento e Despesas +$categories = ['Alimentação', 'Transporte', 'Moradia', 'Lazer', 'Saúde', 'Outros']; +$summary = []; +$total_spent_this_month = 0; +$total_budget_this_month = 0; + +foreach ($categories as $category) { + $spent = $monthly_expenses[$category] ?? 0; + $budget = $monthly_budgets[$category] ?? 0; + $summary[$category] = [ + 'spent' => $spent, + 'budget' => $budget, + 'remaining' => $budget - $spent, + ]; + $total_spent_this_month += $spent; + $total_budget_this_month += $budget; } -if (!$user) { - // Se o usuário não for encontrado no DB, desloga - header('Location: logout.php'); - exit; -} +// 4. Últimas 5 despesas +$stmt_recent = $pdo->prepare("SELECT * FROM expenses WHERE user_id = :user_id ORDER BY expense_date DESC LIMIT 5"); +$stmt_recent->execute(['user_id' => $user_id]); +$recent_expenses = $stmt_recent->fetchAll(PDO::FETCH_ASSOC); +// Função para determinar a classe da barra de progresso +function get_progress_bar_class($percentage) { + if ($percentage > 100) return 'bg-danger'; + if ($percentage > 75) return 'bg-warning'; + return 'bg-success'; +} include __DIR__ . '/includes/header.php'; ?> - -
-
-
-

Bem-vindo(a) ao seu Painel Financeiro

-

Este é o seu centro de controle. Em breve, você poderá adicionar despesas, criar orçamentos e visualizar relatórios detalhados aqui.

- -
-
+

Dashboard de

-
-
-
-

Orçamentos

-

Acompanhe seus orçamentos mensais e veja onde seu dinheiro está indo. (Funcionalidade futura)

- + +
+
+
+
+

Visão Geral do Mês

+

Gastos vs. Orçamento Total

+ R$ + / R$ +
+
+ 0) ? ($total_spent_this_month / $total_budget_this_month) * 100 : 0; + $progress_class = get_progress_bar_class($overall_percentage); + ?> +
+
+ % +
+
+
-
-
-

Metas

-

Defina e acompanhe suas metas de poupança para alcançar seus sonhos. (Funcionalidade futura)

- +
+ + +
+
+
Resumo do Orçamento por Categoria
+
+
+
+ + + + + + + + + + + + $data): ?> + + + + + + + + + +
CategoriaGastoOrçamentoRestanteProgresso
R$ R$ + R$ + + 0) ? ($data['spent'] / $data['budget']) * 100 : 0; + $progress_class = get_progress_bar_class($percentage); + ?> +
+
+
+
+
+
+
+
+ + +
+
+
Despesas Recentes
+
+
+
+ + + + + + + + + + + + + + +
Nenhuma despesa registrada este mês.
R$
+
+
diff --git a/login.php b/login.php index 37a4813..c18dd75 100644 --- a/login.php +++ b/login.php @@ -35,6 +35,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Credenciais inválidas $error_message = 'E-mail ou senha inválidos.'; } + + if ($user && password_verify($password, $user['password_hash'])) { + // Login bem-sucedido + $_SESSION['user_id'] = $user['id']; + 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.'; diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..e230392 --- /dev/null +++ b/profile.php @@ -0,0 +1,136 @@ +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'; +?> + +
+

Meu Perfil

+ +
+
+
+
Atualizar Informações
+
+
+
+ +
+ +
+ + +
+
+ + +
O e-mail não pode ser alterado.
+
+ +
+
+
+
+
+
+
Alterar Senha
+
+
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+ +