This commit is contained in:
Flatlogic Bot 2025-10-29 14:04:14 +00:00
parent 3be22ccb94
commit a0c63edc92
12 changed files with 1023 additions and 78 deletions

128
budgets.php Normal file
View File

@ -0,0 +1,128 @@
<?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'; ?>

View File

@ -1,31 +1,66 @@
<?php
// Simples script para rodar migrações SQL
// Script de migração aprimorado para rodar múltiplos arquivos SQL de forma idempotente.
require_once __DIR__ . '/config.php';
try {
$pdo = db();
$sql_file = __DIR__ . '/migrations/001_create_users_table.sql';
$sql = file_get_contents($sql_file);
$pdo->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());
}
}

View File

@ -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;

View File

@ -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;

46
delete_expense.php Normal file
View File

@ -0,0 +1,46 @@
<?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;

126
edit_expense.php Normal file
View File

@ -0,0 +1,126 @@
<?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'; ?>

247
expenses.php Normal file
View File

@ -0,0 +1,247 @@
<?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'; ?>

60
export.php Normal file
View File

@ -0,0 +1,60 @@
<?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();

View File

@ -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
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Galilei-finance-v1</title>
<title>Galilei Finance</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="Galilei-finance-v1">
<meta property="og:title" content="Galilei Finance">
<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">
@ -25,3 +41,40 @@ if (session_status() === PHP_SESSION_NONE) {
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand navbar-brand-logo" href="index.php">
<i class="bi bi-safe me-2"></i>
Galilei Finance
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<?php if (isset($_SESSION['user_id'])): ?>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="index.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="expenses.php">Despesas</a>
</li>
<li class="nav-item">
<a class="nav-link" href="budgets.php">Orçamentos</a>
</li>
</ul>
<div class="dropdown">
<a href="#" class="d-flex align-items-center text-dark text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
<span class="me-2">Olá, <?php echo htmlspecialchars($user_name); ?></span>
</a>
<ul class="dropdown-menu dropdown-menu-end text-small" aria-labelledby="dropdownUser1">
<li><a class="dropdown-item" href="profile.php">Meu Perfil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">Sair</a></li>
</ul>
</div>
<?php endif; ?>
</div>
</div>
</nav>

203
index.php
View File

@ -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';
?>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand navbar-brand-logo" href="#">
<i class="bi bi-safe me-2"></i>
Galilei Finance
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle me-1"></i>
<?php echo htmlspecialchars($user['name']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Meu Perfil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="logout.php">Sair</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container mt-4">
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Bem-vindo(a) ao seu Painel Financeiro</h1>
<p class="col-md-8 fs-4">Este é o seu centro de controle. Em breve, você poderá adicionar despesas, criar orçamentos e visualizar relatórios detalhados aqui.</p>
<button class="btn btn-primary btn-lg" type="button">Adicionar Despesa (em breve)</button>
</div>
</div>
<h1 class="mb-4">Dashboard de <?php echo date('F', strtotime('now')); ?></h1>
<div class="row align-items-md-stretch">
<div class="col-md-6">
<div class="h-100 p-5 text-white bg-dark rounded-3">
<h2>Orçamentos</h2>
<p>Acompanhe seus orçamentos mensais e veja onde seu dinheiro está indo. (Funcionalidade futura)</p>
<button class="btn btn-outline-light" type="button">Ver Orçamentos</button>
<!-- Resumo Geral do Mês -->
<div class="card mb-4">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-6">
<h4>Visão Geral do Mês</h4>
<p class="text-muted">Gastos vs. Orçamento Total</p>
<span class="fs-3 fw-bold">R$ <?php echo number_format($total_spent_this_month, 2, ',', '.'); ?></span>
<span class="fs-5 text-muted">/ R$ <?php echo number_format($total_budget_this_month, 2, ',', '.'); ?></span>
</div>
<div class="col-md-6">
<?php
$overall_percentage = ($total_budget_this_month > 0) ? ($total_spent_this_month / $total_budget_this_month) * 100 : 0;
$progress_class = get_progress_bar_class($overall_percentage);
?>
<div class="progress" style="height: 25px;">
<div class="progress-bar <?php echo $progress_class; ?>" role="progressbar"
style="width: <?php echo min(100, $overall_percentage); ?>%;"
aria-valuenow="<?php echo $overall_percentage; ?>" aria-valuemin="0" aria-valuemax="100">
<?php echo number_format($overall_percentage, 1); ?>%
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="h-100 p-5 bg-light border rounded-3">
<h2>Metas</h2>
<p>Defina e acompanhe suas metas de poupança para alcançar seus sonhos. (Funcionalidade futura)</p>
<button class="btn btn-outline-secondary" type="button">Ver Metas</button>
</div>
<!-- Resumo por Categoria -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Resumo do Orçamento por Categoria</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Categoria</th>
<th class="text-end">Gasto</th>
<th class="text-end">Orçamento</th>
<th class="text-end">Restante</th>
<th style="width: 25%;">Progresso</th>
</tr>
</thead>
<tbody>
<?php foreach ($summary as $category => $data): ?>
<tr>
<td><?php echo htmlspecialchars($category); ?></td>
<td class="text-end">R$ <?php echo number_format($data['spent'], 2, ',', '.'); ?></td>
<td class="text-end">R$ <?php echo number_format($data['budget'], 2, ',', '.'); ?></td>
<td class="text-end <?php echo ($data['remaining'] < 0) ? 'text-danger' : 'text-success'; ?>">
R$ <?php echo number_format($data['remaining'], 2, ',', '.'); ?>
</td>
<td>
<?php
$percentage = ($data['budget'] > 0) ? ($data['spent'] / $data['budget']) * 100 : 0;
$progress_class = get_progress_bar_class($percentage);
?>
<div class="progress">
<div class="progress-bar <?php echo $progress_class; ?>" role="progressbar"
style="width: <?php echo min(100, $percentage); ?>%;"
aria-valuenow="<?php echo $percentage; ?>" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Despesas Recentes -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">Despesas Recentes</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<tbody>
<?php if (empty($recent_expenses)): ?>
<tr><td class="text-center">Nenhuma despesa registrada este mês.</td></tr>
<?php else: ?>
<?php foreach ($recent_expenses as $expense): ?>
<tr>
<td><span class="badge bg-secondary me-2"><?php echo htmlspecialchars($expense['category']); ?></span> <?php echo htmlspecialchars($expense['description']); ?></td>
<td class="text-end">R$ <?php echo number_format($expense['amount'], 2, ',', '.'); ?></td>
<td class="text-end text-muted"><?php echo date('d/m/Y', strtotime($expense['expense_date'])); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="text-end mt-2">
<a href="expenses.php">Ver todas as despesas &rarr;</a>
</div>
</div>
</div>

View File

@ -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.';

136
profile.php Normal file
View File

@ -0,0 +1,136 @@
<?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'; ?>