Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7e40bdd09 |
70
admin/assets/css/style.css
Normal file
70
admin/assets/css/style.css
Normal 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
11
admin/assets/js/main.js
Normal 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
88
admin/companies.php
Normal 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
23
admin/company_delete.php
Normal 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
113
admin/company_edit.php
Normal 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'; ?>
|
||||||
7
admin/includes/session.php
Normal file
7
admin/includes/session.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: ../login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
100
admin/index.php
Normal file
100
admin/index.php
Normal 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
3
admin/logout.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../auth.php';
|
||||||
|
logout();
|
||||||
23
admin/project_delete.php
Normal file
23
admin/project_delete.php
Normal 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
171
admin/project_edit.php
Normal 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
90
admin/projects.php
Normal 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'; ?>
|
||||||
5
admin/templates/_footer.php
Normal file
5
admin/templates/_footer.php
Normal 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>
|
||||||
16
admin/templates/_header.php
Normal file
16
admin/templates/_header.php
Normal 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">
|
||||||
40
admin/templates/_sidebar.php
Normal file
40
admin/templates/_sidebar.php
Normal 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
19
admin/user_delete.php
Normal 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
97
admin/user_edit.php
Normal 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
64
admin/users.php
Normal 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
61
api/auth.php
Normal 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
140
api/companies.php
Normal 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
160
api/projects.php
Normal 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
85
api/users.php
Normal 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
145
auth.php
Normal 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]);
|
||||||
|
}
|
||||||
73
db/migrations/001_initial_schema.sql
Normal file
73
db/migrations/001_initial_schema.sql
Normal 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);
|
||||||
|
|
||||||
12
db/migrations/002_create_companies_table.sql
Normal file
12
db/migrations/002_create_companies_table.sql
Normal 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;
|
||||||
15
db/migrations/003_create_projects_table.sql
Normal file
15
db/migrations/003_create_projects_table.sql
Normal 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
42
login.php
Normal 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
50
register.php
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user