diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..4b44c57 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,14 @@ +body { + font-family: 'Inter', sans-serif; + background-color: #F8F9FA; +} + +.gradient-header { + background: linear-gradient(to right, #0D6EFD, #0A58CA); + color: white; +} + +.card { + border-radius: 0.375rem; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..33eb62a --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,7 @@ +document.addEventListener('DOMContentLoaded', function () { + const toastElList = [].slice.call(document.querySelectorAll('.toast')); + const toastList = toastElList.map(function (toastEl) { + return new bootstrap.Toast(toastEl, { delay: 3000 }); + }); + toastList.forEach(toast => toast.show()); +}); diff --git a/db/config.php b/db/config.php index cc9229f..3b67cac 100644 --- a/db/config.php +++ b/db/config.php @@ -1,17 +1,70 @@ PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - } - return $pdo; + $host = getenv('DB_HOST') ?: '127.0.0.1'; + $port = getenv('DB_PORT') ?: '3306'; + $db = getenv('DB_DATABASE') ?: 'flatlogic'; + $user = getenv('DB_USERNAME') ?: 'flatlogic'; + $pass = getenv('DB_PASSWORD') ?: 'flatlogic'; + $charset = 'utf8mb4'; + + $dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset"; + $options = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + try { + $pdo = new PDO($dsn, $user, $pass, $options); + run_migrations($pdo); + return $pdo; + } catch (\PDOException $e) { + error_log($e->getMessage()); + return null; + } } + +function run_migrations(PDO $pdo): void { + $migrations_dir = __DIR__ . '/migrations'; + if (!is_dir($migrations_dir)) { + return; + } + + try { + $pdot = $pdo->query("SHOW TABLES LIKE 'migrations'"); + $table_exists = $pdot->rowCount() > 0; + + if (!$table_exists) { + $pdo->exec("CREATE TABLE migrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + migration VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + } + + $stmt = $pdo->prepare("SELECT migration FROM migrations"); + $stmt->execute(); + $run_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN); + + $migration_files = glob($migrations_dir . '/*.sql'); + foreach ($migration_files as $file) { + $migration_name = basename($file); + if (!in_array($migration_name, $run_migrations)) { + $sql = file_get_contents($file); + if ($sql === false) continue; + $pdo->exec($sql); + + $stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)"); + $stmt->execute([$migration_name]); + } + } + } catch (\PDOException $e) { + error_log("Migration failed: " . $e->getMessage()); + } +} \ No newline at end of file diff --git a/db/migrations/001_create_initial_tables.sql b/db/migrations/001_create_initial_tables.sql new file mode 100644 index 0000000..07b1e3d --- /dev/null +++ b/db/migrations/001_create_initial_tables.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT IGNORE INTO categories (name) VALUES ('Food'), ('Pharmacy'), ('Transport'), ('Shopping'); + +CREATE TABLE IF NOT EXISTS expenses ( + id INT AUTO_INCREMENT PRIMARY KEY, + amount DECIMAL(10, 2) NOT NULL, + category_id INT NOT NULL, + payment_method VARCHAR(255) NOT NULL, + notes TEXT, + expense_date DATE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES categories(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/index.php b/index.php index 7205f3d..ca233bb 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,225 @@ prepare("DELETE FROM expenses WHERE id = ?"); + $stmt->execute([$id_to_delete]); + $_SESSION['message'] = 'Expense deleted successfully.'; + $_SESSION['message_type'] = 'success'; + } catch (PDOException $e) { + $_SESSION['message'] = 'Error deleting expense: ' . $e->getMessage(); + $_SESSION['message_type'] = 'danger'; + } + } + header("Location: index.php"); + exit; + } + + // Handle Add + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_expense'])) { + $amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_FLOAT); + $category_id = filter_input(INPUT_POST, 'category_id', FILTER_VALIDATE_INT); + $payment_method = htmlspecialchars(trim($_POST['payment_method'])); + $expense_date = $_POST['expense_date']; // Basic validation, consider more robust date validation + $notes = htmlspecialchars(trim($_POST['notes'])); + + if ($amount && $category_id && !empty($payment_method) && !empty($expense_date)) { + try { + $stmt = $pdo->prepare("INSERT INTO expenses (amount, category_id, payment_method, expense_date, notes) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$amount, $category_id, $payment_method, $expense_date, $notes]); + $_SESSION['message'] = 'Expense added successfully!'; + $_SESSION['message_type'] = 'success'; + } catch (PDOException $e) { + $_SESSION['message'] = 'Error adding expense: ' . $e->getMessage(); + $_SESSION['message_type'] = 'danger'; + } + } else { + $_SESSION['message'] = 'Please fill all required fields correctly.'; + $_SESSION['message_type'] = 'warning'; + } + header("Location: index.php"); + exit; + } +} + + +// Flash message handling +if (isset($_SESSION['message'])) { + $message = $_SESSION['message']; + $message_type = $_SESSION['message_type']; + unset($_SESSION['message']); + unset($_SESSION['message_type']); +} + +// Fetch data for display +$categories = []; +$expenses = []; +if ($pdo) { + try { + $categories = $pdo->query("SELECT * FROM categories ORDER BY name ASC")->fetchAll(); + $expenses = $pdo->query("SELECT e.*, c.name as category_name FROM expenses e JOIN categories c ON e.category_id = c.id ORDER BY e.expense_date DESC, e.id DESC LIMIT 20")->fetchAll(); + } catch (PDOException $e) { + $message = "Error fetching data: " . $e->getMessage(); + $message_type = 'danger'; + } +} -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + Personal Expense Tracker + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ + +
+
-
- + + +
+
+

Personal Expense Tracker

+
+
+ +
+
+ +
+
+
+

Add New Expense

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

Recent Transactions

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateAmountCategoryPayment MethodNotesAction
No expenses recorded yet.
$ + + + +
+
+
+
+
+
+
+ + + + + - + \ No newline at end of file