Compare commits

..

1 Commits

Author SHA1 Message Date
Flatlogic Bot
c7e40bdd09 1.1 2026-01-08 17:14:09 +00:00
27 changed files with 1723 additions and 0 deletions

View File

@ -0,0 +1,70 @@
:root {
--primary-color: #4F46E5;
--secondary-color: #6B7280;
--bg-light: #F9FAFB;
--surface-color: #FFFFFF;
--border-color: #E5E7EB;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-light);
}
#wrapper {
display: flex;
min-height: 100vh;
}
#sidebar-wrapper {
min-width: 250px;
max-width: 250px;
background-color: var(--surface-color) !important;
border-right: 1px solid var(--border-color) !important;
transition: margin .25s ease-out;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: -250px;
}
#page-content-wrapper {
flex: 1;
min-width: 0;
padding: 1.5rem;
}
.sidebar-heading {
padding: 1rem 1.25rem;
font-size: 1.2rem;
font-weight: 600;
color: var(--primary-color);
}
.list-group-item {
border: 0 !important;
color: var(--secondary-color);
font-weight: 500;
}
.list-group-item.active {
background-color: #EEF2FF !important;
color: var(--primary-color) !important;
border-right: 3px solid var(--primary-color) !important;
}
.list-group-item-action:hover, .list-group-item-action:focus {
color: var(--primary-color);
background-color: #EEF2FF !important;
}
.card {
border-radius: 0.75rem;
border: 1px solid var(--border-color);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.card-title {
font-weight: 600;
}

11
admin/assets/js/main.js Normal file
View File

@ -0,0 +1,11 @@
document.addEventListener("DOMContentLoaded", function() {
const menuToggle = document.getElementById('menu-toggle');
const wrapper = document.getElementById('wrapper');
if (menuToggle) {
menuToggle.addEventListener('click', function (e) {
e.preventDefault();
wrapper.classList.toggle('toggled');
});
}
});

88
admin/companies.php Normal file
View File

@ -0,0 +1,88 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
$pdo = db();
// Handle delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("DELETE FROM companies WHERE id = :id");
$stmt->execute(['id' => $id]);
$_SESSION['success_message'] = 'Company deleted successfully!';
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error deleting company: ' . $e->getMessage();
}
header('Location: companies.php');
exit();
}
// Fetch all companies
$companies = [];
try {
$stmt = $pdo->query("SELECT * FROM companies ORDER BY name");
$companies = $stmt->fetchAll();
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error fetching companies: ' . $e->getMessage();
}
$title = 'Companies';
include __DIR__ . '/../templates/_header.php';
?>
<h1 class="mb-4">Companies</h1>
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo htmlspecialchars($_SESSION['success_message']); unset($_SESSION['success_message']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($_SESSION['error_message']); unset($_SESSION['error_message']); ?>
</div>
<?php endif; ?>
<div class="d-flex justify-content-end mb-3">
<a href="company_edit.php" class="btn btn-primary">Add New Company</a>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (count($companies) > 0): ?>
<?php foreach ($companies as $company): ?>
<tr>
<td><?php echo htmlspecialchars($company['id']); ?></td>
<td><?php echo htmlspecialchars($company['name']); ?></td>
<td><?php echo htmlspecialchars($company['email']); ?></td>
<td><?php echo htmlspecialchars($company['phone']); ?></td>
<td><?php echo htmlspecialchars($company['address']); ?></td>
<td>
<a href="company_edit.php?id=<?php echo htmlspecialchars($company['id']); ?>" class="btn btn-sm btn-info">Edit</a>
<a href="companies.php?action=delete&id=<?php echo htmlspecialchars($company['id']); ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this company?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6">No companies found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php include __DIR__ . '/../templates/_footer.php'; ?>

23
admin/company_delete.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
if (isset($_GET['id'])) {
$id = $_GET['id'];
$pdo = db();
try {
$stmt = $pdo->prepare("DELETE FROM companies WHERE id = :id");
$stmt->execute(['id' => $id]);
$_SESSION['success_message'] = 'Company deleted successfully!';
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error deleting company: ' . $e->getMessage();
}
} else {
$_SESSION['error_message'] = 'No company ID provided for deletion.';
}
header('Location: companies.php');
exit();

113
admin/company_edit.php Normal file
View File

@ -0,0 +1,113 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
$pdo = db();
$company = null;
$errors = [];
$is_edit = false;
// Fetch company data if ID is provided (for editing)
if (isset($_GET['id'])) {
$is_edit = true;
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("SELECT * FROM companies WHERE id = :id");
$stmt->execute(['id' => $id]);
$company = $stmt->fetch();
if (!$company) {
$_SESSION['error_message'] = 'Company not found.';
header('Location: companies.php');
exit();
}
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error fetching company details: ' . $e->getMessage();
header('Location: companies.php');
exit();
}
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$phone = trim($_POST['phone']);
$address = trim($_POST['address']);
if (empty($name)) {
$errors[] = 'Company Name is required.';
}
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid Email Address.';
}
if (empty($errors)) {
try {
if ($is_edit) {
$stmt = $pdo->prepare("UPDATE companies SET name = :name, email = :email, phone = :phone, address = :address WHERE id = :id");
$stmt->execute([
'name' => $name,
'email' => $email,
'phone' => $phone,
'address' => $address,
'id' => $id
]);
$_SESSION['success_message'] = 'Company updated successfully!';
} else {
$stmt = $pdo->prepare("INSERT INTO companies (name, email, phone, address) VALUES (:name, :email, :phone, :address)");
$stmt->execute([
'name' => $name,
'email' => $email,
'phone' => $phone,
'address' => $address
]);
$_SESSION['success_message'] = 'Company added successfully!';
}
header('Location: companies.php');
exit();
} catch (PDOException $e) {
$errors[] = 'Database error: ' . $e->getMessage();
}
}
}
$title = ($is_edit ? 'Edit Company' : 'Add Company');
include __DIR__ . '/../templates/_header.php';
?>
<h1 class="mb-4"><?php echo htmlspecialchars($title); ?></h1>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger" role="alert">
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form action="company_edit.php<?php echo $is_edit ? '?id=' . htmlspecialchars($id) : ''; ?>" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Company Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($company['name'] ?? $_POST['name'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($company['email'] ?? $_POST['email'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="phone" class="form-label">Phone</label>
<input type="text" class="form-control" id="phone" name="phone" value="<?php echo htmlspecialchars($company['phone'] ?? $_POST['phone'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="address" class="form-label">Address</label>
<textarea class="form-control" id="address" name="address"><?php echo htmlspecialchars($company['address'] ?? $_POST['address'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary"><?php echo $is_edit ? 'Update Company' : 'Add Company'; ?></button>
<a href="companies.php" class="btn btn-secondary">Cancel</a>
</form>
<?php include __DIR__ . '/../templates/_footer.php'; ?>

View File

@ -0,0 +1,7 @@
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}

100
admin/index.php Normal file
View File

@ -0,0 +1,100 @@
<?php require_once 'templates/_header.php'; ?>
<?php require_once 'templates/_sidebar.php'; ?>
<!-- Page Content -->
<div id="page-content-wrapper">
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom mb-4">
<div class="container-fluid">
<button class="btn btn-primary" id="menu-toggle"><i class="bi bi-list"></i></button>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mt-2 mt-lg-0">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="bi bi-person-circle me-1"></i>Super Admin
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#!">Profile</a>
<a class="dropdown-item" href="#!">Settings</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#!">Logout</a>
</div>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<h1 class="h3 mb-4">Dashboard</h1>
<div class="row">
<!-- Example Widget -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<div class="row align-items-center">
<div class="col mr-2">
<div class="text-xs fw-bold text-uppercase mb-1">Logged-in Users</div>
<div class="h5 mb-0 fw-bold">15</div>
</div>
<div class="col-auto">
<i class="bi bi-person-check-fill h2 text-secondary"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Example Widget -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs fw-bold text-uppercase mb-1">Server Load</div>
<div class="h5 mb-0 fw-bold">34%</div>
</div>
<div class="col-auto">
<i class="bi bi-cpu h2 text-secondary"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 mb-4">
<!-- Last 10 Logs -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold">Last 10 Logs</h6>
</div>
<div class="card-body">
<p>Log data will be displayed here.</p>
<p class="mb-0 text-muted small">[Log Entry Placeholder]</p>
</div>
</div>
</div>
<div class="col-lg-4 mb-4">
<!-- Calendar -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold">Calendar</h6>
</div>
<div class="card-body">
<p>A calendar widget will be here.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'templates/_footer.php'; ?>

3
admin/logout.php Normal file
View File

@ -0,0 +1,3 @@
<?php
require_once __DIR__ . '/../auth.php';
logout();

23
admin/project_delete.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
if (isset($_GET['id'])) {
$id = $_GET['id'];
$pdo = db();
try {
$stmt = $pdo->prepare("DELETE FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
$_SESSION['success_message'] = 'Project deleted successfully!';
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error deleting project: ' . $e->getMessage();
}
} else {
$_SESSION['error_message'] = 'No project ID provided for deletion.';
}
header('Location: projects.php');
exit();

171
admin/project_edit.php Normal file
View File

@ -0,0 +1,171 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
$pdo = db();
$project = null;
$errors = [];
$is_edit = false;
// Fetch companies for dropdown
$companies = [];
try {
$stmt = $pdo->query("SELECT id, name FROM companies ORDER BY name");
$companies = $stmt->fetchAll();
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error fetching companies: ' . $e->getMessage();
header('Location: projects.php');
exit();
}
// Fetch project data if ID is provided (for editing)
if (isset($_GET['id'])) {
$is_edit = true;
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("SELECT * FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
$project = $stmt->fetch();
if (!$project) {
$_SESSION['error_message'] = 'Project not found.';
header('Location: projects.php');
exit();
}
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error fetching project details: ' . $e->getMessage();
header('Location: projects.php');
exit();
}
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$company_id = $_POST['company_id'];
$name = trim($_POST['name']);
$description = trim($_POST['description']);
$status = $_POST['status'];
$start_date = $_POST['start_date'];
$end_date = $_POST['end_date'];
if (empty($company_id)) {
$errors[] = 'Company is required.';
}
if (empty($name)) {
$errors[] = 'Project Name is required.';
}
if (empty($status)) {
$errors[] = 'Status is required.';
}
if (!empty($start_date) && !strtotime($start_date)) {
$errors[] = 'Invalid Start Date.';
}
if (!empty($end_date) && !strtotime($end_date)) {
$errors[] = 'Invalid End Date.';
}
if (!empty($start_date) && !empty($end_date) && strtotime($start_date) > strtotime($end_date)) {
$errors[] = 'End Date cannot be before Start Date.';
}
if (empty($errors)) {
try {
if ($is_edit) {
$stmt = $pdo->prepare("UPDATE projects SET company_id = :company_id, name = :name, description = :description, status = :status, start_date = :start_date, end_date = :end_date WHERE id = :id");
$stmt->execute([
'company_id' => $company_id,
'name' => $name,
'description' => $description,
'status' => $status,
'start_date' => empty($start_date) ? null : $start_date,
'end_date' => empty($end_date) ? null : $end_date,
'id' => $id
]);
$_SESSION['success_message'] = 'Project updated successfully!';
} else {
$stmt = $pdo->prepare("INSERT INTO projects (company_id, name, description, status, start_date, end_date) VALUES (:company_id, :name, :description, :status, :start_date, :end_date)");
$stmt->execute([
'company_id' => $company_id,
'name' => $name,
'description' => $description,
'status' => $status,
'start_date' => empty($start_date) ? null : $start_date,
'end_date' => empty($end_date) ? null : $end_date
]);
$_SESSION['success_message'] = 'Project added successfully!';
}
header('Location: projects.php');
exit();
} catch (PDOException $e) {
$errors[] = 'Database error: ' . $e->getMessage();
}
}
}
$title = ($is_edit ? 'Edit Project' : 'Add Project');
include __DIR__ . '/../templates/_header.php';
?>
<h1 class="mb-4"><?php echo htmlspecialchars($title); ?></h1>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger" role="alert">
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form action="project_edit.php<?php echo $is_edit ? '?id=' . htmlspecialchars($id) : ''; ?>" method="POST">
<div class="mb-3">
<label for="company_id" class="form-label">Company</label>
<select class="form-control" id="company_id" name="company_id" required>
<option value="">Select Company</option>
<?php foreach ($companies as $comp): ?>
<option value="<?php echo htmlspecialchars($comp['id']); ?>"
<?php
$selected_company = $project['company_id'] ?? $_POST['company_id'] ?? '';
if ($selected_company == $comp['id']) {
echo 'selected';
}
?>
><?php echo htmlspecialchars($comp['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="name" class="form-label">Project Name</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($project['name'] ?? $_POST['name'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"><?php echo htmlspecialchars($project['description'] ?? $_POST['description'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-control" id="status" name="status" required>
<?php
$statuses = ['active', 'completed', 'on-hold', 'cancelled'];
$current_status = $project['status'] ?? $_POST['status'] ?? 'active';
foreach ($statuses as $s) {
echo '<option value="' . htmlspecialchars($s) . '" ' . ($current_status == $s ? 'selected' : '') . '>' . ucfirst($s) . '</option>';
}
?>
</select>
</div>
<div class="mb-3">
<label for="start_date" class="form-label">Start Date</label>
<input type="date" class="form-control" id="start_date" name="start_date" value="<?php echo htmlspecialchars($project['start_date'] ?? $_POST['start_date'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="end_date" class="form-label">End Date</label>
<input type="date" class="form-control" id="end_date" name="end_date" value="<?php echo htmlspecialchars($project['end_date'] ?? $_POST['end_date'] ?? ''); ?>">
</div>
<button type="submit" class="btn btn-primary"><?php echo $is_edit ? 'Update Project' : 'Add Project'; ?></button>
<a href="projects.php" class="btn btn-secondary">Cancel</a>
</form>
<?php include __DIR__ . '/../templates/_footer.php'; ?>

90
admin/projects.php Normal file
View File

@ -0,0 +1,90 @@
<?php
require_once __DIR__ . '/../includes/session.php';
require_once __DIR__ . '/../../db/config.php';
checkAuth();
$pdo = db();
// Handle delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("DELETE FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
$_SESSION['success_message'] = 'Project deleted successfully!';
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error deleting project: ' . $e->getMessage();
}
header('Location: projects.php');
exit();
}
// Fetch all projects with company name
$projects = [];
try {
$stmt = $pdo->query("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id ORDER BY p.name");
$projects = $stmt->fetchAll();
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Error fetching projects: ' . $e->getMessage();
}
$title = 'Projects';
include __DIR__ . '/../templates/_header.php';
?>
<h1 class="mb-4">Projects</h1>
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert alert-success" role="alert">
<?php echo htmlspecialchars($_SESSION['success_message']); unset($_SESSION['success_message']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($_SESSION['error_message']); unset($_SESSION['error_message']); ?>
</div>
<?php endif; ?>
<div class="d-flex justify-content-end mb-3">
<a href="project_edit.php" class="btn btn-primary">Add New Project</a>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Company</th>
<th>Status</th>
<th>Start Date</th>
<th>End Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (count($projects) > 0): ?>
<?php foreach ($projects as $project): ?>
<tr>
<td><?php echo htmlspecialchars($project['id']); ?></td>
<td><?php echo htmlspecialchars($project['name']); ?></td>
<td><?php echo htmlspecialchars($project['company_name']); ?></td>
<td><?php echo htmlspecialchars($project['status']); ?></td>
<td><?php echo htmlspecialchars($project['start_date']); ?></td>
<td><?php echo htmlspecialchars($project['end_date']); ?></td>
<td>
<a href="project_edit.php?id=<?php echo htmlspecialchars($project['id']); ?>" class="btn btn-sm btn-info">Edit</a>
<a href="projects.php?action=delete&id=<?php echo htmlspecialchars($project['id']); ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this project?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7">No projects found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php include __DIR__ . '/../templates/_footer.php'; ?>

View File

@ -0,0 +1,5 @@
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<?php require_once __DIR__ . '/../includes/session.php'; ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/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="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="d-flex" id="wrapper">

View File

@ -0,0 +1,40 @@
<!-- Sidebar -->
<div class="bg-light border-end" id="sidebar-wrapper">
<div class="sidebar-heading border-bottom bg-light">
<i class="bi bi-shield-shaded me-2"></i>Admin Panel
</div>
<div class="list-group list-group-flush">
<a class="nav-link" href="index.php">
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
Dashboard
</a>
<a class="nav-link" href="users.php">
<div class="sb-nav-link-icon"><i class="fas fa-users"></i></div>
User Management
</a>
<a class="nav-link" href="companies.php">
<div class="sb-nav-link-icon"><i class="fas fa-building"></i></div>
Company Management
</a>
<a class="nav-link" href="projects.php">
<div class="sb-nav-link-icon"><i class="fas fa-project-diagram"></i></div>
Project Management
</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#">
<i class="bi bi-key me-2"></i>Permissions
</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#">
<i class="bi bi-gear me-2"></i>Settings
</a>
</div>
<div class="mt-auto p-3">
<div class="d-flex align-items-center">
<i class="bi bi-person-circle fs-3 me-2"></i>
<div>
<strong><?php echo htmlspecialchars($_SESSION['user_name']); ?></strong>
<br>
<a href="logout.php" class="text-muted small">Logout</a>
</div>
</div>
</div>
</div>

19
admin/user_delete.php Normal file
View File

@ -0,0 +1,19 @@
<?php
require_once 'includes/session.php';
require_once '../db/config.php';
$id = $_GET['id'] ?? null;
if ($id) {
// Prevent deleting the superadmin (user with ID 1)
if ($id == 1) {
// Optionally set a flash message to inform the user
$_SESSION['error_message'] = 'Cannot delete the primary administrator account.';
} else {
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
}
}
header('Location: users.php');
exit;

97
admin/user_edit.php Normal file
View File

@ -0,0 +1,97 @@
<?php
require_once 'includes/session.php';
require_once '../db/config.php';
include_once 'templates/_header.php';
$id = $_GET['id'] ?? null;
$user = null;
$is_edit = false;
if ($id) {
$stmt = db()->prepare("SELECT id, username, email FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
if ($user) {
$is_edit = true;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
// Basic validation
if (empty($username) || empty($email)) {
$error = "Username and email are required.";
} else {
if ($is_edit) {
// Update existing user
if (!empty($password)) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, password = ? WHERE id = ?");
$stmt->execute([$username, $email, $hashed_password, $id]);
} else {
$stmt = db()->prepare("UPDATE users SET username = ?, email = ? WHERE id = ?");
$stmt->execute([$username, $email, $id]);
}
} else {
// Create new user
if (empty($password)) {
$error = "Password is required for new users.";
} else {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->execute([$username, $email, $hashed_password]);
}
}
if (empty($error)) {
header('Location: users.php');
exit;
}
}
}
?>
<div class="container-fluid px-4">
<h1 class="mt-4"><?php echo $is_edit ? 'Edit User' : 'Add New User'; ?></h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
<li class="breadcrumb-item"><a href="users.php">Users</a></li>
<li class="breadcrumb-item active"><?php echo $is_edit ? 'Edit' : 'Add'; ?></li>
</ol>
<div class="card mb-4">
<div class="card-header">
User Details
</div>
<div class="card-body">
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($user['username'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($user['email'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" <?php echo !$is_edit ? 'required' : ''; ?>>
<?php if ($is_edit): ?>
<small class="form-text text-muted">Leave blank to keep the current password.</small>
<?php endif; ?>
</div>
<button type="submit" class="btn btn-primary"><?php echo $is_edit ? 'Update User' : 'Create User'; ?></button>
<a href="users.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
<?php include_once 'templates/_footer.php'; ?>

64
admin/users.php Normal file
View File

@ -0,0 +1,64 @@
<?php
require_once 'includes/session.php';
require_once '../db/config.php';
include_once 'templates/_header.php';
// Fetch all users
$stmt = db()->prepare("SELECT id, username, email, created_at FROM users ORDER BY created_at DESC");
$stmt->execute();
$users = $stmt->fetchAll();
?>
<div class="container-fluid px-4">
<h1 class="mt-4">User Management</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
<li class="breadcrumb-item active">Users</li>
</ol>
<?php
if (isset($_SESSION['error_message'])) {
echo '<div class="alert alert-danger alert-dismissible fade show" role="alert">'.
htmlspecialchars($_SESSION['error_message']) .
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['error_message']);
}
?>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-table me-1"></i> All Users</span>
<a href="user_edit.php" class="btn btn-primary btn-sm">Add New User</a>
</div>
<div class="card-body">
<table id="datatablesSimple" class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['id']); ?></td>
<td><?php echo htmlspecialchars($user['username']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo htmlspecialchars(date('M d, Y', strtotime($user['created_at']))); ?></td>
<td>
<a href="user_edit.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-primary">Edit</a>
<a href="user_delete.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this user?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include_once 'templates/_footer.php'; ?>

61
api/auth.php Normal file
View File

@ -0,0 +1,61 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
function sendJsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
echo json_encode($data);
exit();
}
// Basic API Key check (for demonstration purposes)
// In a real application, use a more robust authentication mechanism (e.g., OAuth2, JWT)
function checkApiKey() {
$headers = getallheaders();
$apiKey = $headers['X-Api-Key'] ?? '';
// Replace 'YOUR_SECRET_API_KEY' with a strong, secret API key
// This should ideally be stored in an environment variable or secure configuration
if ($apiKey !== 'YOUR_SECRET_API_KEY') {
sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid API Key.'], 401);
}
}
checkApiKey();
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
if (!$data || empty($data['email']) || empty($data['password'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Email and password are required.'], 400);
}
$email = $data['email'];
$password = $data['password'];
try {
$stmt = $pdo->prepare("SELECT id, name, email, password FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// For a real API, generate and return a token (e.g., JWT)
// For this example, we'll just return a success message and user info (without password)
unset($user['password']);
sendJsonResponse(['message' => 'Login successful.', 'user' => $user]);
} else {
sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid credentials.'], 401);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
default:
sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405);
break;
}

140
api/companies.php Normal file
View File

@ -0,0 +1,140 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
function sendJsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
echo json_encode($data);
exit();
}
// Basic API Key check (for demonstration purposes)
// In a real application, use a more robust authentication mechanism (e.g., OAuth2, JWT)
function checkApiKey() {
$headers = getallheaders();
$apiKey = $headers['X-Api-Key'] ?? '';
// Replace 'YOUR_SECRET_API_KEY' with a strong, secret API key
// This should ideally be stored in an environment variable or secure configuration
if ($apiKey !== 'YOUR_SECRET_API_KEY') {
sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid API Key.'], 401);
}
}
checkApiKey();
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
// Get single company
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("SELECT * FROM companies WHERE id = :id");
$stmt->execute(['id' => $id]);
$company = $stmt->fetch();
if ($company) {
sendJsonResponse($company);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
} else {
// Get all companies
try {
$stmt = $pdo->query("SELECT * FROM companies ORDER BY name");
$companies = $stmt->fetchAll();
sendJsonResponse($companies);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
if (!$data || empty($data['name'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company name is required.'], 400);
}
$name = $data['name'];
$email = $data['email'] ?? null;
$phone = $data['phone'] ?? null;
$address = $data['address'] ?? null;
try {
$stmt = $pdo->prepare("INSERT INTO companies (name, email, phone, address) VALUES (:name, :email, :phone, :address)");
$stmt->execute([
'name' => $name,
'email' => $email,
'phone' => $phone,
'address' => $address
]);
sendJsonResponse(['message' => 'Company created successfully', 'id' => $pdo->lastInsertId()], 201);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
case 'PUT':
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($_GET['id'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID is required for update.'], 400);
}
if (!$data || empty($data['name'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company name is required.'], 400);
}
$id = $_GET['id'];
$name = $data['name'];
$email = $data['email'] ?? null;
$phone = $data['phone'] ?? null;
$address = $data['address'] ?? null;
try {
$stmt = $pdo->prepare("UPDATE companies SET name = :name, email = :email, phone = :phone, address = :address WHERE id = :id");
$stmt->execute([
'name' => $name,
'email' => $email,
'phone' => $phone,
'address' => $address,
'id' => $id
]);
if ($stmt->rowCount() > 0) {
sendJsonResponse(['message' => 'Company updated successfully.']);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found or no changes made.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
case 'DELETE':
if (!isset($_GET['id'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID is required for deletion.'], 400);
}
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("DELETE FROM companies WHERE id = :id");
$stmt->execute(['id' => $id]);
if ($stmt->rowCount() > 0) {
sendJsonResponse(['message' => 'Company deleted successfully.']);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
default:
sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405);
break;
}

160
api/projects.php Normal file
View File

@ -0,0 +1,160 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
function sendJsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
echo json_encode($data);
exit();
}
// Basic API Key check (for demonstration purposes)
// In a real application, use a more robust authentication mechanism (e.g., OAuth2, JWT)
function checkApiKey() {
$headers = getallheaders();
$apiKey = $headers['X-Api-Key'] ?? '';
// Replace 'YOUR_SECRET_API_KEY' with a strong, secret API key
// This should ideally be stored in an environment variable or secure configuration
if ($apiKey !== 'YOUR_SECRET_API_KEY') {
sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid API Key.'], 401);
}
}
checkApiKey();
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
// Get single project
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id WHERE p.id = :id");
$stmt->execute(['id' => $id]);
$project = $stmt->fetch();
if ($project) {
sendJsonResponse($project);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
} else if (isset($_GET['company_id'])) {
// Get projects by company
$company_id = $_GET['company_id'];
try {
$stmt = $pdo->prepare("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id WHERE p.company_id = :company_id ORDER BY p.name");
$stmt->execute(['company_id' => $company_id]);
$projects = $stmt->fetchAll();
sendJsonResponse($projects);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
}
else {
// Get all projects
try {
$stmt = $pdo->query("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id ORDER BY p.name");
$projects = $stmt->fetchAll();
sendJsonResponse($projects);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
if (!$data || empty($data['company_id']) || empty($data['name'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID and Project Name are required.'], 400);
}
$company_id = $data['company_id'];
$name = $data['name'];
$description = $data['description'] ?? null;
$status = $data['status'] ?? 'active';
$start_date = $data['start_date'] ?? null;
$end_date = $data['end_date'] ?? null;
try {
$stmt = $pdo->prepare("INSERT INTO projects (company_id, name, description, status, start_date, end_date) VALUES (:company_id, :name, :description, :status, :start_date, :end_date)");
$stmt->execute([
'company_id' => $company_id,
'name' => $name,
'description' => $description,
'status' => $status,
'start_date' => $start_date,
'end_date' => $end_date
]);
sendJsonResponse(['message' => 'Project created successfully', 'id' => $pdo->lastInsertId()], 201);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
case 'PUT':
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($_GET['id'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Project ID is required for update.'], 400);
}
if (!$data || empty($data['company_id']) || empty($data['name'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID and Project Name are required.'], 400);
}
$id = $_GET['id'];
$company_id = $data['company_id'];
$name = $data['name'];
$description = $data['description'] ?? null;
$status = $data['status'] ?? 'active';
$start_date = $data['start_date'] ?? null;
$end_date = $data['end_date'] ?? null;
try {
$stmt = $pdo->prepare("UPDATE projects SET company_id = :company_id, name = :name, description = :description, status = :status, start_date = :start_date, end_date = :end_date WHERE id = :id");
$stmt->execute([
'company_id' => $company_id,
'name' => $name,
'description' => $description,
'status' => $status,
'start_date' => $start_date,
'end_date' => $end_date,
'id' => $id
]);
if ($stmt->rowCount() > 0) {
sendJsonResponse(['message' => 'Project updated successfully.']);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found or no changes made.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
case 'DELETE':
if (!isset($_GET['id'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Project ID is required for deletion.'], 400);
}
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("DELETE FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
if ($stmt->rowCount() > 0) {
sendJsonResponse(['message' => 'Project deleted successfully.']);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
default:
sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405);
break;
}

85
api/users.php Normal file
View File

@ -0,0 +1,85 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
function sendJsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
echo json_encode($data);
exit();
}
// Basic API Key check (for demonstration purposes)
// In a real application, use a more robust authentication mechanism (e.g., OAuth2, JWT)
function checkApiKey() {
$headers = getallheaders();
$apiKey = $headers['X-Api-Key'] ?? '';
// Replace 'YOUR_SECRET_API_KEY' with a strong, secret API key
// This should ideally be stored in an environment variable or secure configuration
if ($apiKey !== 'YOUR_SECRET_API_KEY') {
sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid API Key.'], 401);
}
}
checkApiKey();
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
if (isset($_GET['id'])) {
// Get single user
$id = $_GET['id'];
try {
$stmt = $pdo->prepare("SELECT id, name, email, created_at, updated_at FROM users WHERE id = :id");
$stmt->execute(['id' => $id]);
$user = $stmt->fetch();
if ($user) {
sendJsonResponse($user);
} else {
sendJsonResponse(['error' => 'Not Found', 'message' => 'User not found.'], 404);
}
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
} else {
// Get all users
try {
$stmt = $pdo->query("SELECT id, name, email, created_at, updated_at FROM users ORDER BY name");
$users = $stmt->fetchAll();
sendJsonResponse($users);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
if (!$data || empty($data['name']) || empty($data['email']) || empty($data['password'])) {
sendJsonResponse(['error' => 'Bad Request', 'message' => 'Name, email, and password are required.'], 400);
}
$name = $data['name'];
$email = $data['email'];
$password = password_hash($data['password'], PASSWORD_DEFAULT);
try {
$stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (:name, :email, :password)");
$stmt->execute([
'name' => $name,
'email' => $email,
'password' => $password
]);
sendJsonResponse(['message' => 'User created successfully', 'id' => $pdo->lastInsertId()], 201);
} catch (PDOException $e) {
sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500);
}
break;
default:
sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405);
break;
}

145
auth.php Normal file
View File

@ -0,0 +1,145 @@
<?php
session_start();
require_once 'db/config.php';
$action = $_GET['action'] ?? '';
switch ($action) {
case 'register':
register();
break;
case 'login':
login();
break;
case 'logout':
logout();
break;
default:
header('Location: login.php');
exit;
}
function register() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: register.php');
exit;
}
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($name) || empty($email) || empty($password) || empty($confirm_password)) {
header('Location: register.php?error=All fields are required.');
exit;
}
if ($password !== $confirm_password) {
header('Location: register.php?error=Passwords do not match.');
exit;
}
$pdo = db();
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$email]);
if ($stmt->fetch()) {
header('Location: register.php?error=Email already exists.');
exit;
}
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO users (name, email, password) VALUES (?, ?, ?)');
if ($stmt->execute([$name, $email, $hashed_password])) {
// Assign default 'User' group
$user_id = $pdo->lastInsertId();
$stmt = $pdo->prepare('INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)');
$stmt->execute([$user_id, 3]);
header('Location: login.php');
exit;
} else {
header('Location: register.php?error=An error occurred.');
exit;
}
}
function login() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: login.php');
exit;
}
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
header('Location: login.php?error=Email and password are required.');
exit;
}
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
if ($user['is_suspended']) {
log_login_attempt($user['id'], 'failed');
header('Location: login.php?error=Your account is suspended.');
exit;
}
// Prevent multiple logins from different IPs
$session_id = session_id();
$ip_address = $_SERVER['REMOTE_ADDR'];
$stmt = $pdo->prepare('SELECT * FROM active_sessions WHERE user_id = ?');
$stmt->execute([$user['id']]);
$active_session = $stmt->fetch();
if ($active_session && $active_session['ip_address'] !== $ip_address) {
log_login_attempt($user['id'], 'failed');
header('Location: login.php?error=User is already logged in from another IP address.');
exit;
}
// Store session
$stmt = $pdo->prepare('REPLACE INTO active_sessions (session_id, user_id, ip_address) VALUES (?, ?, ?)');
$stmt->execute([$session_id, $user['id'], $ip_address]);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
// Update last login info
$stmt = $pdo->prepare('UPDATE users SET last_login = NOW(), last_login_ip = ? WHERE id = ?');
$stmt->execute([$ip_address, $user['id']]);
log_login_attempt($user['id'], 'success');
header('Location: admin/index.php');
exit;
} else {
$user_id = $user ? $user['id'] : null;
log_login_attempt($user_id, 'failed');
header('Location: login.php?error=Invalid email or password.');
exit;
}
}
function logout() {
$pdo = db();
$stmt = $pdo->prepare('DELETE FROM active_sessions WHERE user_id = ?');
$stmt->execute([$_SESSION['user_id']]);
session_unset();
session_destroy();
header('Location: login.php');
exit;
}
function log_login_attempt($user_id, $status) {
$pdo = db();
$stmt = $pdo->prepare('INSERT INTO login_logs (user_id, ip_address, status) VALUES (?, ?, ?)');
$stmt->execute([$user_id, $_SERVER['REMOTE_ADDR'], $status]);
}

View File

@ -0,0 +1,73 @@
-- Initial Schema for User Management
CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`profile_picture` VARCHAR(255) DEFAULT NULL,
`is_suspended` BOOLEAN DEFAULT FALSE,
`api_allowed` BOOLEAN DEFAULT TRUE,
`last_login` DATETIME DEFAULT NULL,
`last_login_ip` VARCHAR(45) DEFAULT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `groups` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL UNIQUE,
`description` TEXT
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `user_groups` (
`user_id` INT,
`group_id` INT,
PRIMARY KEY (`user_id`, `group_id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `permissions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL UNIQUE,
`description` TEXT
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `group_permissions` (
`group_id` INT,
`permission_id` INT,
PRIMARY KEY (`group_id`, `permission_id`),
FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`permission_id`) REFERENCES `permissions`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `login_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT,
`ip_address` VARCHAR(45),
`login_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`status` ENUM('success', 'failed'),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `active_sessions` (
`session_id` VARCHAR(128) NOT NULL PRIMARY KEY,
`user_id` INT NOT NULL UNIQUE,
`login_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`ip_address` VARCHAR(45),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
-- Default Data
INSERT INTO `groups` (`id`, `name`, `description`) VALUES
(1, 'SuperAdmin', 'Full access to all system features.'),
(2, 'Admin', 'Administrative access to most features.'),
(3, 'User', 'Standard user access.');
-- Default SuperAdmin User (password: password123)
INSERT INTO `users` (`id`, `name`, `email`, `password`) VALUES
(1, 'Super Admin', 'admin@example.com', '$2y$10$fA.o.f8b.5L9zJ/Zc.915eB0GA9gISfCj2L5kI/A5d.4b5I3.f0Fm');
INSERT INTO `user_groups` (`user_id`, `group_id`) VALUES (1, 1);

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS `companies` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`address` VARCHAR(255) DEFAULT NULL,
`phone` VARCHAR(20) DEFAULT NULL,
`email` VARCHAR(255) DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `projects` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`company_id` INT(11) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT DEFAULT NULL,
`status` VARCHAR(50) DEFAULT 'active',
`start_date` DATE DEFAULT NULL,
`end_date` DATE DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_company_id` (`name`, `company_id`),
FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

42
login.php Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="admin/assets/css/style.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="row vh-100 justify-content-center align-items-center">
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body p-5">
<h1 class="card-title text-center mb-4">Login</h1>
<?php if (isset($_GET['error'])): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php endif; ?>
<form action="auth.php?action=login" method="POST">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
<div class="text-center mt-3">
<a href="register.php">Don't have an account? Register</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

50
register.php Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="admin/assets/css/style.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="row vh-100 justify-content-center align-items-center">
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body p-5">
<h1 class="card-title text-center mb-4">Register</h1>
<?php if (isset($_GET['error'])): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php endif; ?>
<form action="auth.php?action=register" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>
<div class="text-center mt-3">
<a href="login.php">Already have an account? Login</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>