Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
181
accounts.php
181
accounts.php
@ -1,181 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
|
|
||||||
$pdo = db();
|
|
||||||
$message = '';
|
|
||||||
$error = '';
|
|
||||||
$edit_account = null;
|
|
||||||
|
|
||||||
// Fetch users for the dropdown
|
|
||||||
$user_stmt = $pdo->query('SELECT id, name FROM users ORDER BY name');
|
|
||||||
$users = $user_stmt->fetchAll();
|
|
||||||
|
|
||||||
// Handle form submissions for adding/editing an account
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$name = trim($_POST['name']);
|
|
||||||
$user_id = $_POST['user_id'] ?? null;
|
|
||||||
$initial_balance = $_POST['initial_balance'] ?? 0;
|
|
||||||
$currency = $_POST['currency'] ?? 'USD';
|
|
||||||
$id = $_POST['id'] ?? null;
|
|
||||||
|
|
||||||
if (empty($name)) {
|
|
||||||
$error = 'Account name is required.';
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if ($id) {
|
|
||||||
// Update existing account
|
|
||||||
$stmt = $pdo->prepare('UPDATE accounts SET name = ?, user_id = ?, initial_balance = ?, currency = ? WHERE id = ?');
|
|
||||||
$stmt->execute([$name, $user_id, $initial_balance, $currency, $id]);
|
|
||||||
$message = 'Account updated successfully!';
|
|
||||||
} else {
|
|
||||||
// Insert new account
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO accounts (name, user_id, initial_balance, currency) VALUES (?, ?, ?, ?)');
|
|
||||||
$stmt->execute([$name, $user_id, $initial_balance, $currency]);
|
|
||||||
$message = 'Account added successfully!';
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$error = 'Database error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deleting an account
|
|
||||||
if (isset($_GET['delete'])) {
|
|
||||||
$id = $_GET['delete'];
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM accounts WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$message = 'Account deleted successfully!';
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$error = 'Error deleting account. It might be associated with expenses.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle fetching an account for editing
|
|
||||||
if (isset($_GET['edit'])) {
|
|
||||||
$id = $_GET['edit'];
|
|
||||||
$stmt = $pdo->prepare('SELECT * FROM accounts WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$edit_account = $stmt->fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all accounts to display
|
|
||||||
$stmt = $pdo->query('SELECT a.*, u.name as user_name FROM accounts a LEFT JOIN users u ON a.user_id = u.id ORDER BY a.name');
|
|
||||||
$accounts = $stmt->fetchAll();
|
|
||||||
|
|
||||||
$currencies = ['USD', 'EUR', 'COP'];
|
|
||||||
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Manage Accounts</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container mt-5">
|
|
||||||
<header class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1>Manage Accounts</h1>
|
|
||||||
<a href="index.php" class="btn btn-secondary">Back to Dashboard</a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($message): ?>
|
|
||||||
<div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add/Edit Account Form -->
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0"><?php echo $edit_account ? 'Edit Account' : 'Add New Account'; ?></h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="accounts.php" method="POST">
|
|
||||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($edit_account['id'] ?? ''); ?>">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="name" class="form-label">Account Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($edit_account['name'] ?? ''); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="user_id" class="form-label">Owner (User)</label>
|
|
||||||
<select class="form-select" id="user_id" name="user_id">
|
|
||||||
<option value="">None</option>
|
|
||||||
<?php foreach ($users as $user): ?>
|
|
||||||
<option value="<?php echo $user['id']; ?>" <?php echo (isset($edit_account['user_id']) && $edit_account['user_id'] == $user['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($user['name']); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="initial_balance" class="form-label">Initial Balance</label>
|
|
||||||
<input type="number" step="0.01" class="form-control" id="initial_balance" name="initial_balance" value="<?php echo htmlspecialchars($edit_account['initial_balance'] ?? '0.00'); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="currency" class="form-label">Currency</label>
|
|
||||||
<select class="form-select" id="currency" name="currency" required>
|
|
||||||
<?php foreach ($currencies as $currency): ?>
|
|
||||||
<option value="<?php echo $currency; ?>" <?php echo (isset($edit_account['currency']) && $edit_account['currency'] == $currency) ? 'selected' : ''; ?>><?php echo $currency; ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary"><?php echo $edit_account ? 'Update Account' : 'Add Account'; ?></button>
|
|
||||||
<?php if ($edit_account): ?>
|
|
||||||
<a href="accounts.php" class="btn btn-secondary">Cancel Edit</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Accounts List -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Existing Accounts</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Owner</th>
|
|
||||||
<th class="text-end">Balance</th>
|
|
||||||
<th>Currency</th>
|
|
||||||
<th class="text-end">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (empty($accounts)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="text-center">No accounts found.</td>
|
|
||||||
</tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($accounts as $account): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo htmlspecialchars($account['name']); ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($account['user_name'] ?? 'N/A'); ?></td>
|
|
||||||
<td class="text-end font-monospace"><?php echo htmlspecialchars(number_format((float)$account['initial_balance'], 2)); ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($account['currency']); ?></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<a href="accounts.php?edit=<?php echo $account['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil"></i> Edit</a>
|
|
||||||
<a href="accounts.php?delete=<?php echo $account['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this account?');"><i class="bi bi-trash"></i> Delete</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
180
add_expense.php
180
add_expense.php
@ -1,180 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
$message = '';
|
|
||||||
$error = '';
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
// Fetch data for dropdowns
|
|
||||||
$categories = $pdo->query('SELECT * FROM categories ORDER BY name')->fetchAll();
|
|
||||||
$users = $pdo->query('SELECT * FROM users ORDER BY name')->fetchAll();
|
|
||||||
$accounts = $pdo->query('SELECT * FROM accounts ORDER BY name')->fetchAll();
|
|
||||||
|
|
||||||
$expense_types = ['expense', 'income', 'transfer'];
|
|
||||||
$split_types = ['none', 'equally', 'parts', 'amounts'];
|
|
||||||
$currencies = ['USD', 'EUR', 'COP'];
|
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|
||||||
try {
|
|
||||||
// Sanitize and validate input
|
|
||||||
$expense_date = filter_input(INPUT_POST, 'expense_date', FILTER_SANITIZE_STRING);
|
|
||||||
$title = filter_input(INPUT_POST, 'title', FILTER_SANITIZE_STRING);
|
|
||||||
$amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_FLOAT);
|
|
||||||
$currency = filter_input(INPUT_POST, 'currency', FILTER_SANITIZE_STRING);
|
|
||||||
|
|
||||||
$category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT);
|
|
||||||
$user_id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);
|
|
||||||
$account_id = filter_input(INPUT_POST, 'account_id', FILTER_VALIDATE_INT);
|
|
||||||
$expense_type = filter_input(INPUT_POST, 'expense_type', FILTER_SANITIZE_STRING);
|
|
||||||
$split_type = filter_input(INPUT_POST, 'split_type', FILTER_SANITIZE_STRING);
|
|
||||||
|
|
||||||
// Handle file upload
|
|
||||||
$receipt_path = null;
|
|
||||||
if (isset($_FILES['receipt']) && $_FILES['receipt']['error'] == UPLOAD_ERR_OK) {
|
|
||||||
$upload_dir = __DIR__ . '/assets/uploads/';
|
|
||||||
if (!is_dir($upload_dir)) {
|
|
||||||
mkdir($upload_dir, 0775, true);
|
|
||||||
}
|
|
||||||
$filename = uniqid() . '-' . basename($_FILES['receipt']['name']);
|
|
||||||
$receipt_path = '/assets/uploads/' . $filename;
|
|
||||||
move_uploaded_file($_FILES['receipt']['tmp_name'], $upload_dir . $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = "INSERT INTO expenses (expense_date, title, amount, currency, expense_type, split_type, category_id, user_id, account_id, receipt_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
|
|
||||||
if ($stmt->execute([$expense_date, $title, $amount, $currency, $expense_type, $split_type, $category_id, $user_id, $account_id, $receipt_path])) {
|
|
||||||
header("Location: index.php?message=success");
|
|
||||||
exit;
|
|
||||||
} else {
|
|
||||||
$error = "Error saving the expense.";
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("DB Error: " . $e->getMessage());
|
|
||||||
$error = "Database error. Please try again later.";
|
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("File Upload Error: " . $e->getMessage());
|
|
||||||
$error = "Error uploading the file.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Añadir Gasto</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
|
||||||
</head>
|
|
||||||
<body style="background-color: #F3F4F6;">
|
|
||||||
|
|
||||||
<div class="container mt-5">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="card shadow-sm" style="border-radius: 1rem;">
|
|
||||||
<div class="card-body p-4 p-md-5">
|
|
||||||
<div class="d-flex align-items-center mb-4">
|
|
||||||
<a href="index.php" class="btn btn-light me-3"><i class="bi bi-arrow-left"></i></a>
|
|
||||||
<h2 class="mb-0">Añadir Nuevo Gasto</h2>
|
|
||||||
</div>
|
|
||||||
<form id="add-expense-form" method="POST" action="add_expense.php" enctype="multipart/form-data">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="expense-date" class="form-label">Fecha</label>
|
|
||||||
<input type="date" class="form-control" name="expense_date" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="title" class="form-label">Título</label>
|
|
||||||
<input type="text" class="form-control" name="title" placeholder="Ej: Cena con amigos" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="amount" class="form-label">Monto</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text">$</span>
|
|
||||||
<input type="number" class="form-control" name="amount" placeholder="100.00" step="0.01" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="currency" class="form-label">Moneda</label>
|
|
||||||
<select name="currency" class="form-select" required>
|
|
||||||
<?php foreach ($currencies as $currency): ?>
|
|
||||||
<option value="<?php echo $currency; ?>"><?php echo $currency; ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="category_id" class="form-label">Categoría</label>
|
|
||||||
<select name="category_id" class="form-select">
|
|
||||||
<option value="">Elegir...</option>
|
|
||||||
<?php foreach ($categories as $category): ?>
|
|
||||||
<option value="<?php echo $category['id']; ?>"><?php echo htmlspecialchars($category['name']); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="user_id" class="form-label">Usuario</label>
|
|
||||||
<select name="user_id" class="form-select">
|
|
||||||
<option value="">Elegir...</option>
|
|
||||||
<?php foreach ($users as $user): ?>
|
|
||||||
<option value="<?php echo $user['id']; ?>"><?php echo htmlspecialchars($user['name']); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<label for="account_id" class="form-label">Cuenta</label>
|
|
||||||
<select name="account_id" class="form-select">
|
|
||||||
<option value="">Elegir...</option>
|
|
||||||
<?php foreach ($accounts as $account): ?>
|
|
||||||
<option value="<?php echo $account['id']; ?>"><?php echo htmlspecialchars($account['name']); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="expense_type" class="form-label">Tipo de Gasto</label>
|
|
||||||
<select name="expense_type" class="form-select" required>
|
|
||||||
<?php foreach ($expense_types as $type): ?>
|
|
||||||
<option value="<?php echo $type; ?>"><?php echo ucfirst($type); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="split_type" class="form-label">Tipo de División</label>
|
|
||||||
<select name="split_type" class="form-select" required>
|
|
||||||
<?php foreach ($split_types as $type): ?>
|
|
||||||
<option value="<?php echo $type; ?>"><?php echo ucfirst($type); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="receipt" class="form-label">Adjuntar Recibo</label>
|
|
||||||
<input class="form-control" type="file" name="receipt">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="my-4">
|
|
||||||
|
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
|
||||||
<button type="button" class="btn btn-light me-md-2" onclick="window.location.href='index.php'">Cancelar</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Guardar Gasto</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script src="assets/js/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: #4F46E5;
|
|
||||||
border-color: #4F46E5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: #4338CA;
|
|
||||||
border-color: #4338CA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus, .form-select:focus {
|
|
||||||
border-color: #4F46E5;
|
|
||||||
box-shadow: 0 0 0 0.25rem rgba(79, 70, 229, 0.25);
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
document.getElementById('add-expense-form').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
alert('Gasto añadido (simulación)!');
|
|
||||||
// Here you would normally send the data to the server
|
|
||||||
// For now, we just reset the form
|
|
||||||
this.reset();
|
|
||||||
});
|
|
||||||
145
categories.php
145
categories.php
@ -1,145 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
|
|
||||||
$pdo = db();
|
|
||||||
$message = '';
|
|
||||||
$error = '';
|
|
||||||
$edit_category = null;
|
|
||||||
|
|
||||||
// Handle form submissions for adding/editing a category
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$name = trim($_POST['name']);
|
|
||||||
$id = $_POST['id'] ?? null;
|
|
||||||
|
|
||||||
if (empty($name)) {
|
|
||||||
$error = 'Category name is required.';
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if ($id) {
|
|
||||||
// Update existing category
|
|
||||||
$stmt = $pdo->prepare('UPDATE categories SET name = ? WHERE id = ?');
|
|
||||||
$stmt->execute([$name, $id]);
|
|
||||||
$message = 'Category updated successfully!';
|
|
||||||
} else {
|
|
||||||
// Insert new category
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO categories (name) VALUES (?)');
|
|
||||||
$stmt->execute([$name]);
|
|
||||||
$message = 'Category added successfully!';
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
if ($e->errorInfo[1] == 1062) { // Duplicate entry
|
|
||||||
$error = 'Category name already exists.';
|
|
||||||
} else {
|
|
||||||
$error = 'Database error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deleting a category
|
|
||||||
if (isset($_GET['delete'])) {
|
|
||||||
$id = $_GET['delete'];
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$message = 'Category deleted successfully!';
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$error = 'Error deleting category. It might be in use.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle fetching a category for editing
|
|
||||||
if (isset($_GET['edit'])) {
|
|
||||||
$id = $_GET['edit'];
|
|
||||||
$stmt = $pdo->prepare('SELECT * FROM categories WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$edit_category = $stmt->fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all categories to display
|
|
||||||
$stmt = $pdo->query('SELECT * FROM categories ORDER BY name');
|
|
||||||
$categories = $stmt->fetchAll();
|
|
||||||
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Manage Categories</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container mt-5">
|
|
||||||
<header class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1>Manage Categories</h1>
|
|
||||||
<a href="index.php" class="btn btn-secondary">Back to Dashboard</a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($message): ?>
|
|
||||||
<div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add/Edit Category Form -->
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0"><?php echo $edit_category ? 'Edit Category' : 'Add New Category'; ?></h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="categories.php" method="POST">
|
|
||||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($edit_category['id'] ?? ''); ?>">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="name" class="form-label">Category Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($edit_category['name'] ?? ''); ?>" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary"><?php echo $edit_category ? 'Update Category' : 'Add Category'; ?></button>
|
|
||||||
<?php if ($edit_category): ?>
|
|
||||||
<a href="categories.php" class="btn btn-secondary">Cancel Edit</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Categories List -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Existing Categories</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th class="text-end">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (empty($categories)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="text-center">No categories found.</td>
|
|
||||||
</tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($categories as $category): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo htmlspecialchars($category['name']); ?></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<a href="categories.php?edit=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil"></i> Edit</a>
|
|
||||||
<a href="categories.php?delete=<?php echo $category['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this category?');"><i class="bi bi-trash"></i> Delete</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
-- Create categories table
|
|
||||||
CREATE TABLE IF NOT EXISTS `categories` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`name` VARCHAR(255) NOT NULL UNIQUE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- Create users table
|
|
||||||
CREATE TABLE IF NOT EXISTS `users` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`name` VARCHAR(255) NOT NULL,
|
|
||||||
`email` VARCHAR(255) NOT NULL UNIQUE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- Create accounts table
|
|
||||||
CREATE TABLE IF NOT EXISTS `accounts` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`name` VARCHAR(255) NOT NULL,
|
|
||||||
`user_id` INT,
|
|
||||||
`initial_balance` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
`currency` ENUM('USD', 'EUR', 'COP') NOT NULL,
|
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- Step 1: Add new columns to 'expenses' table without dropping old ones yet.
|
|
||||||
ALTER TABLE `expenses`
|
|
||||||
ADD COLUMN `category_id` INT NULL AFTER `receipt_path`,
|
|
||||||
ADD COLUMN `user_id` INT NULL AFTER `category_id`,
|
|
||||||
ADD COLUMN `account_id` INT NULL AFTER `user_id`,
|
|
||||||
ADD COLUMN `expense_type` ENUM('expense', 'income', 'transfer') NOT NULL DEFAULT 'expense' AFTER `amount`,
|
|
||||||
ADD COLUMN `split_type` ENUM('equally', 'parts', 'amounts', 'none') NOT NULL DEFAULT 'none' AFTER `expense_type`;
|
|
||||||
|
|
||||||
-- Step 2: Add foreign key constraints
|
|
||||||
-- Note: We'll add constraints in a separate step after data migration to avoid issues with existing data.
|
|
||||||
-- ALTER TABLE `expenses`
|
|
||||||
-- ADD CONSTRAINT `fk_category` FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON DELETE SET NULL,
|
|
||||||
-- ADD CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
|
||||||
-- ADD CONSTRAINT `fk_account` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
-- Step 3: Modify the currency column
|
|
||||||
ALTER TABLE `expenses`
|
|
||||||
MODIFY COLUMN `currency` ENUM('USD', 'EUR', 'COP') NOT NULL;
|
|
||||||
52
export.php
52
export.php
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
$stmt = $pdo->query("
|
|
||||||
SELECT
|
|
||||||
e.expense_date,
|
|
||||||
e.title,
|
|
||||||
c.name as category_name,
|
|
||||||
a.name as account_name,
|
|
||||||
u.name as user_name,
|
|
||||||
e.expense_type,
|
|
||||||
e.amount,
|
|
||||||
e.currency
|
|
||||||
FROM
|
|
||||||
expenses e
|
|
||||||
LEFT JOIN
|
|
||||||
categories c ON e.category_id = c.id
|
|
||||||
LEFT JOIN
|
|
||||||
users u ON e.user_id = u.id
|
|
||||||
LEFT JOIN
|
|
||||||
accounts a ON e.account_id = a.id
|
|
||||||
ORDER BY
|
|
||||||
e.expense_date DESC
|
|
||||||
");
|
|
||||||
|
|
||||||
$filename = "expenses_" . date('Y-m-d') . ".csv";
|
|
||||||
|
|
||||||
header('Content-Type: text/csv; charset=utf-8');
|
|
||||||
header('Content-Disposition: attachment; filename=' . $filename);
|
|
||||||
|
|
||||||
$output = fopen('php://output', 'w');
|
|
||||||
|
|
||||||
// Add UTF-8 BOM to prevent issues with special characters in Excel
|
|
||||||
fputs($output, "\xEF\xBB\xBF");
|
|
||||||
|
|
||||||
// Header row
|
|
||||||
fputcsv($output, ['Date', 'Title', 'Category', 'Account', 'User', 'Type', 'Amount', 'Currency']);
|
|
||||||
|
|
||||||
// Data rows
|
|
||||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
fputcsv($output, $row);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($output);
|
|
||||||
exit;
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("Export Error: " . $e->getMessage());
|
|
||||||
die("An error occurred during export. Please check the logs.");
|
|
||||||
}
|
|
||||||
123
import.php
123
import.php
@ -1,123 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
$message = '';
|
|
||||||
$error = '';
|
|
||||||
|
|
||||||
function find_or_create($pdo, $table, $name_column, $name_value, $extra_cols = []) {
|
|
||||||
if (empty($name_value)) return null;
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT id FROM `$table` WHERE `$name_column` = ?");
|
|
||||||
$stmt->execute([$name_value]);
|
|
||||||
$id = $stmt->fetchColumn();
|
|
||||||
|
|
||||||
if (!$id) {
|
|
||||||
$cols = '`' . $name_column . '`';
|
|
||||||
$vals = '?';
|
|
||||||
$exec_vals = [$name_value];
|
|
||||||
|
|
||||||
foreach ($extra_cols as $col => $val) {
|
|
||||||
$cols .= ', '`' . $col . '`';
|
|
||||||
$vals .= ', ?';
|
|
||||||
$exec_vals[] = $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO `$table` ($cols) VALUES ($vals)");
|
|
||||||
$stmt->execute($exec_vals);
|
|
||||||
$id = $pdo->lastInsertId();
|
|
||||||
}
|
|
||||||
return $id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES['csv_file'])) {
|
|
||||||
$file = $_FILES['csv_file']['tmp_name'];
|
|
||||||
|
|
||||||
if (($handle = fopen($file, "r")) !== FALSE) {
|
|
||||||
$pdo = db();
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
try {
|
|
||||||
// Skip header row
|
|
||||||
fgetcsv($handle, 1000, ",");
|
|
||||||
|
|
||||||
$imported_count = 0;
|
|
||||||
$sql = "INSERT INTO expenses (expense_date, title, amount, currency, expense_type, category_id, user_id, account_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
|
|
||||||
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
|
|
||||||
// CSV format: Date,Title,Category,Account,User,Type,Amount,Currency
|
|
||||||
$date = $data[0];
|
|
||||||
$title = $data[1];
|
|
||||||
$category_name = $data[2];
|
|
||||||
$account_name = $data[3];
|
|
||||||
$user_name = $data[4];
|
|
||||||
$expense_type = $data[5];
|
|
||||||
$amount = $data[6];
|
|
||||||
$currency = $data[7];
|
|
||||||
|
|
||||||
$category_id = find_or_create($pdo, 'categories', 'name', $category_name);
|
|
||||||
$user_id = find_or_create($pdo, 'users', 'name', $user_name, ['email' => strtolower(str_replace(' ', '.', $user_name)).'@example.com']);
|
|
||||||
$account_id = find_or_create($pdo, 'accounts', 'name', $account_name, ['user_id' => $user_id, 'currency' => $currency]);
|
|
||||||
|
|
||||||
$stmt->execute([$date, $title, $amount, $currency, $expense_type, $category_id, $user_id, $account_id]);
|
|
||||||
$imported_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
$message = "Successfully imported $imported_count expenses!";
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
$error = "Import failed: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
fclose($handle);
|
|
||||||
} else {
|
|
||||||
$error = "Could not open the uploaded file.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Import Expenses</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container mt-5">
|
|
||||||
<header class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1>Import Expenses from CSV</h1>
|
|
||||||
<a href="index.php" class="btn btn-secondary">Back to Dashboard</a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($message): ?>
|
|
||||||
<div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Upload CSV File</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="import.php" method="POST" enctype="multipart/form-data">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="csv_file" class="form-label">Select CSV file to upload</label>
|
|
||||||
<input class="form-control" type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Import</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer text-muted">
|
|
||||||
<small>CSV format should be: <code>Date,Title,Category,Account,User,Type,Amount,Currency</code>. The header row will be skipped.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
228
index.php
228
index.php
@ -4,45 +4,15 @@ declare(strict_types=1);
|
|||||||
@error_reporting(E_ALL);
|
@error_reporting(E_ALL);
|
||||||
@date_default_timezone_set('UTC');
|
@date_default_timezone_set('UTC');
|
||||||
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
$phpVersion = PHP_VERSION;
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$message = $_GET['message'] ?? '';
|
|
||||||
|
|
||||||
$expenses = [];
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
$stmt = $pdo->query("
|
|
||||||
SELECT
|
|
||||||
e.*,
|
|
||||||
c.name as category_name,
|
|
||||||
u.name as user_name,
|
|
||||||
a.name as account_name
|
|
||||||
FROM
|
|
||||||
expenses e
|
|
||||||
LEFT JOIN
|
|
||||||
categories c ON e.category_id = c.id
|
|
||||||
LEFT JOIN
|
|
||||||
users u ON e.user_id = u.id
|
|
||||||
LEFT JOIN
|
|
||||||
accounts a ON e.account_id = a.id
|
|
||||||
ORDER BY
|
|
||||||
e.expense_date DESC
|
|
||||||
");
|
|
||||||
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("DB Error: " . $e->getMessage());
|
|
||||||
// You might want to display a user-friendly error message
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="es">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Gestor de Gastos</title>
|
<title>New Style</title>
|
||||||
<?php
|
<?php
|
||||||
// Read project preview data from environment
|
// Read project preview data from environment
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||||
@ -62,89 +32,119 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|||||||
<!-- Twitter image -->
|
<!-- Twitter image -->
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/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;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
<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>
|
</head>
|
||||||
<body style="background-color: #F3F4F6;">
|
<body>
|
||||||
|
<main>
|
||||||
<main class="container py-5">
|
<div class="card">
|
||||||
<div class="text-center mb-4">
|
<h1>Analyzing your requirements and generating your website…</h1>
|
||||||
<h1 class="display-5 fw-bold">Gestor de Gastos Familiar</h1>
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
<p class="lead text-muted">Bienvenido a tu espacio financiero. Registra y controla tus gastos.</p>
|
<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>
|
||||||
|
</main>
|
||||||
<?php if ($message === 'success'): ?>
|
<footer>
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||||
¡Gasto añadido correctamente!
|
</footer>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="card shadow-sm mb-4">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h2 class="h5 mb-0">Resumen de Gastos</h2>
|
|
||||||
<div class="btn-group">
|
|
||||||
<a href="import.php" class="btn btn-warning">Importar</a>
|
|
||||||
<a href="export.php" class="btn btn-success">Exportar</a>
|
|
||||||
<a href="accounts.php" class="btn btn-info">Cuentas</a>
|
|
||||||
<a href="users.php" class="btn btn-info">Usuarios</a>
|
|
||||||
<a href="categories.php" class="btn btn-info">Categorías</a>
|
|
||||||
<a href="add_expense.php" class="btn btn-primary">Añadir Gasto</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<?php if (empty($expenses)): ?>
|
|
||||||
<div class="text-center p-3">
|
|
||||||
<p>No hay gastos registrados. ¡Empieza añadiendo uno!</p>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle">
|
|
||||||
<thead class="table-light">
|
|
||||||
<tr>
|
|
||||||
<th>Fecha</th>
|
|
||||||
<th>Título</th>
|
|
||||||
<th>Categoría</th>
|
|
||||||
<th>Cuenta</th>
|
|
||||||
<th>Usuario</th>
|
|
||||||
<th>Tipo</th>
|
|
||||||
<th class="text-end">Monto</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($expenses as $expense): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= htmlspecialchars(date("d/m/Y", strtotime($expense['expense_date']))) ?></td>
|
|
||||||
<td><?= htmlspecialchars($expense['title']) ?></td>
|
|
||||||
<td><span class="badge bg-secondary"><?= htmlspecialchars($expense['category_name'] ?? 'N/A') ?></span></td>
|
|
||||||
<td><?= htmlspecialchars($expense['account_name'] ?? 'N/A') ?></td>
|
|
||||||
<td><?= htmlspecialchars($expense['user_name'] ?? 'N/A') ?></td>
|
|
||||||
<td>
|
|
||||||
<?php
|
|
||||||
$type = $expense['expense_type'];
|
|
||||||
$badge_class = 'bg-secondary';
|
|
||||||
if ($type == 'income') $badge_class = 'bg-success';
|
|
||||||
if ($type == 'expense') $badge_class = 'bg-danger';
|
|
||||||
if ($type == 'transfer') $badge_class = 'bg-info';
|
|
||||||
?>
|
|
||||||
<span class="badge <?= $badge_class ?>"><?= ucfirst($type) ?></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-end font-monospace"><?= htmlspecialchars(number_format((float)$expense['amount'], 2)) ?> <?= htmlspecialchars($expense['currency']) ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="text-center text-muted py-3">
|
|
||||||
<small>Page updated: <?= htmlspecialchars($now) ?> (UTC)</small>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
156
users.php
156
users.php
@ -1,156 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
|
|
||||||
$pdo = db();
|
|
||||||
$message = '';
|
|
||||||
$error = '';
|
|
||||||
$edit_user = null;
|
|
||||||
|
|
||||||
// Handle form submissions for adding/editing a user
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$name = trim($_POST['name']);
|
|
||||||
$email = trim($_POST['email']);
|
|
||||||
$id = $_POST['id'] ?? null;
|
|
||||||
|
|
||||||
if (empty($name) || empty($email)) {
|
|
||||||
$error = 'User name and email are required.';
|
|
||||||
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$error = 'Invalid email format.';
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if ($id) {
|
|
||||||
// Update existing user
|
|
||||||
$stmt = $pdo->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
|
|
||||||
$stmt->execute([$name, $email, $id]);
|
|
||||||
$message = 'User updated successfully!';
|
|
||||||
} else {
|
|
||||||
// Insert new user
|
|
||||||
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
|
|
||||||
$stmt->execute([$name, $email]);
|
|
||||||
$message = 'User added successfully!';
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
if ($e->errorInfo[1] == 1062) { // Duplicate entry for email
|
|
||||||
$error = 'Email address already exists.';
|
|
||||||
} else {
|
|
||||||
$error = 'Database error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deleting a user
|
|
||||||
if (isset($_GET['delete'])) {
|
|
||||||
$id = $_GET['delete'];
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare('DELETE FROM users WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$message = 'User deleted successfully!';
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$error = 'Error deleting user. They might be associated with expenses.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle fetching a user for editing
|
|
||||||
if (isset($_GET['edit'])) {
|
|
||||||
$id = $_GET['edit'];
|
|
||||||
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$edit_user = $stmt->fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all users to display
|
|
||||||
$stmt = $pdo->query('SELECT * FROM users ORDER BY name');
|
|
||||||
$users = $stmt->fetchAll();
|
|
||||||
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Manage Users</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/css/custom.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container mt-5">
|
|
||||||
<header class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1>Manage Users</h1>
|
|
||||||
<a href="index.php" class="btn btn-secondary">Back to Dashboard</a>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($message): ?>
|
|
||||||
<div class="alert alert-success"><?php echo htmlspecialchars($message); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($error): ?>
|
|
||||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Add/Edit User Form -->
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0"><?php echo $edit_user ? 'Edit User' : 'Add New User'; ?></h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="users.php" method="POST">
|
|
||||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($edit_user['id'] ?? ''); ?>">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="name" class="form-label">User Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($edit_user['name'] ?? ''); ?>" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="email" class="form-label">Email</label>
|
|
||||||
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($edit_user['email'] ?? ''); ?>" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary"><?php echo $edit_user ? 'Update User' : 'Add User'; ?></button>
|
|
||||||
<?php if ($edit_user): ?>
|
|
||||||
<a href="users.php" class="btn btn-secondary">Cancel Edit</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Users List -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Existing Users</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th class="text-end">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (empty($users)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-center">No users found.</td>
|
|
||||||
</tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($users as $user): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?php echo htmlspecialchars($user['name']); ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($user['email']); ?></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<a href="users.php?edit=<?php echo $user['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil"></i> Edit</a>
|
|
||||||
<a href="users.php?delete=<?php echo $user['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this user?');"><i class="bi bi-trash"></i> Delete</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user