Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2681ea74d4 | ||
|
|
d90a7739dc | ||
|
|
929c1ba4df | ||
|
|
348f175175 | ||
|
|
496d2ec7e5 |
419
admin_users.php
Normal file
419
admin_users.php
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
// Check if user is logged in and is an Admin
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'Admin') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle delete action
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
|
||||||
|
$user_id_to_delete = filter_var($_GET['id'], FILTER_VALIDATE_INT);
|
||||||
|
if ($user_id_to_delete && $user_id_to_delete !== $_SESSION['user_id']) { // Prevent admin from deleting themselves
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
|
||||||
|
$stmt->execute([$user_id_to_delete]);
|
||||||
|
$_SESSION['success_message'] = 'User deleted successfully!';
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['error_message'] = 'Error deleting user: ' . htmlspecialchars($e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$_SESSION['error_message'] = 'Invalid user ID or cannot delete your own account.';
|
||||||
|
}
|
||||||
|
header('Location: admin_users.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Admin | User Management";
|
||||||
|
require_once __DIR__ . '/partials/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4">User Management</h1>
|
||||||
|
|
||||||
|
<!-- Placeholder for user list and forms -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
Existing Users
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php
|
||||||
|
$users = [];
|
||||||
|
try {
|
||||||
|
$stmt = db()->query("SELECT id, name, email, role, created_at FROM users ORDER BY created_at DESC");
|
||||||
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo '<div class="alert alert-danger">Error fetching users: ' . htmlspecialchars($e->getMessage()) . '</div>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php if (empty($users)): ?>
|
||||||
|
<p>No users found.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Created At</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['name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['email']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['role']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['created_at']); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="?action=edit&id=<?php echo $user['id']; ?>" class="btn btn-sm btn-primary me-2">Edit</a>
|
||||||
|
<a href="?action=delete&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>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div> </div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
|
||||||
|
<div class="card-header">
|
||||||
|
|
||||||
|
<?php echo isset($edit_user) ? 'Edit User' : 'Add New User'; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$name = $email = $password = $role = '';
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
$edit_user_id = null;
|
||||||
|
|
||||||
|
$edit_user = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Handle edit action - fetch user data
|
||||||
|
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
|
||||||
|
|
||||||
|
$edit_user_id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
|
||||||
|
|
||||||
|
if ($edit_user_id) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT id, name, email, role FROM users WHERE id = ?");
|
||||||
|
|
||||||
|
$stmt->execute([$edit_user_id]);
|
||||||
|
|
||||||
|
$edit_user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($edit_user) {
|
||||||
|
|
||||||
|
$name = $edit_user['name'];
|
||||||
|
|
||||||
|
$email = $edit_user['email'];
|
||||||
|
|
||||||
|
$role = $edit_user['role'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$_SESSION['error_message'] = 'User not found.';
|
||||||
|
|
||||||
|
header('Location: admin_users.php');
|
||||||
|
|
||||||
|
exit();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
|
||||||
|
$_SESSION['error_message'] = 'Error fetching user for edit: ' . htmlspecialchars($e->getMessage());
|
||||||
|
|
||||||
|
header('Location: admin_users.php');
|
||||||
|
|
||||||
|
exit();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Handle form submission for Add or Edit
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
if (isset($_POST['add_user']) || isset($_POST['edit_user'])) {
|
||||||
|
|
||||||
|
$name = trim($_POST['name']);
|
||||||
|
|
||||||
|
$email = trim($_POST['email']);
|
||||||
|
|
||||||
|
$role = $_POST['role'];
|
||||||
|
|
||||||
|
$password = isset($_POST['password']) ? $_POST['password'] : '';
|
||||||
|
|
||||||
|
$confirm_password = isset($_POST['confirm_password']) ? $_POST['confirm_password'] : '';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$current_user_id = isset($_POST['user_id']) ? filter_var($_POST['user_id'], FILTER_VALIDATE_INT) : null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (empty($name)) {
|
||||||
|
|
||||||
|
$errors[] = 'Name is required.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
|
||||||
|
$errors[] = 'Valid email is required.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($role)) {
|
||||||
|
|
||||||
|
$errors[] = 'Role is required.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($_POST['add_user'])) { // For adding new user
|
||||||
|
|
||||||
|
if (empty($password)) {
|
||||||
|
|
||||||
|
$errors[] = 'Password is required.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($password !== $confirm_password) {
|
||||||
|
|
||||||
|
$errors[] = 'Passwords do not match.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (isset($_POST['edit_user'])) { // For editing existing user
|
||||||
|
|
||||||
|
if (!empty($password) && $password !== $confirm_password) {
|
||||||
|
|
||||||
|
$errors[] = 'Passwords do not match.';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (isset($_POST['add_user'])) {
|
||||||
|
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
$stmt = db()->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)");
|
||||||
|
|
||||||
|
$stmt->execute([$name, $email, $hashed_password, $role]);
|
||||||
|
|
||||||
|
$_SESSION['success_message'] = 'User added successfully!';
|
||||||
|
|
||||||
|
} else if (isset($_POST['edit_user'])) {
|
||||||
|
|
||||||
|
$sql = "UPDATE users SET name = ?, email = ?, role = ?";
|
||||||
|
|
||||||
|
$params = [$name, $email, $role];
|
||||||
|
|
||||||
|
if (!empty($password)) {
|
||||||
|
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
$sql .= ", password = ?";
|
||||||
|
|
||||||
|
$params[] = $hashed_password;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " WHERE id = ?";
|
||||||
|
|
||||||
|
$params[] = $current_user_id;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = db()->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$_SESSION['success_message'] = 'User updated successfully!';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: admin_users.php');
|
||||||
|
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
|
||||||
|
if ($e->getCode() === '23000') { // Duplicate entry
|
||||||
|
|
||||||
|
$errors[] = 'User with this email already exists.';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$errors[] = 'Error processing user: ' . htmlspecialchars($e->getMessage());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (!empty($errors)): ?>
|
||||||
|
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
|
||||||
|
<?php foreach ($errors as $error): ?>
|
||||||
|
|
||||||
|
<p class="mb-0"><?php echo htmlspecialchars($error); ?></p>
|
||||||
|
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['success_message'])): ?>
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
|
||||||
|
<?php echo $_SESSION['success_message']; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php unset($_SESSION['success_message']); ?>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['error_message'])): ?>
|
||||||
|
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
|
||||||
|
<?php echo $_SESSION['error_message']; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php unset($_SESSION['error_message']); ?>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<form action="admin_users.php" method="POST">
|
||||||
|
|
||||||
|
<?php if ($edit_user): ?>
|
||||||
|
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo htmlspecialchars($edit_user['id']); ?>">
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
|
||||||
|
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($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($email); ?>" required>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
|
||||||
|
<label for="password" class="form-label"><?php echo isset($edit_user) ? 'New Password (leave blank to keep current)' : 'Password'; ?></label>
|
||||||
|
|
||||||
|
<input type="password" class="form-control" id="password" name="password" <?php echo isset($edit_user) ? '' : 'required'; ?>>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
|
||||||
|
<label for="confirm_password" class="form-label"><?php echo isset($edit_user) ? 'Confirm New Password' : 'Confirm Password'; ?></label>
|
||||||
|
|
||||||
|
<input type="password" class="form-control" id="confirm_password" name="confirm_password" <?php echo isset($edit_user) ? '' : 'required'; ?>>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
|
||||||
|
<label for="role" class="form-label">Role</label>
|
||||||
|
|
||||||
|
<select class="form-select" id="role" name="role" required>
|
||||||
|
|
||||||
|
<option value="">Select Role</option>
|
||||||
|
|
||||||
|
<option value="Admin" <?php echo ($role === 'Admin') ? 'selected' : ''; ?>>Admin</option>
|
||||||
|
|
||||||
|
<option value="Sales Rep" <?php echo ($role === 'Sales Rep') ? 'selected' : ''; ?>>Sales Rep</option>
|
||||||
|
|
||||||
|
<option value="Dispatch" <?php echo ($role === 'Dispatch') ? 'selected' : ''; ?>>Dispatch</option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($edit_user): ?>
|
||||||
|
|
||||||
|
<button type="submit" name="edit_user" class="btn btn-primary">Update User</button>
|
||||||
|
|
||||||
|
<a href="admin_users.php" class="btn btn-secondary">Cancel</a>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<button type="submit" name="add_user" class="btn btn-primary">Add User</button>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/partials/footer.php';
|
||||||
|
?>
|
||||||
31
assets/css/custom.css
Normal file
31
assets/css/custom.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-light-gray {
|
||||||
|
background-color: #F8F9FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #F97316;
|
||||||
|
border-color: #F97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
|
||||||
|
background-color: #EA580C;
|
||||||
|
border-color: #EA580C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: #F97316 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #F97316;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(249, 115, 22, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
color: #F97316 !important;
|
||||||
|
border-bottom: 2px solid #F97316;
|
||||||
|
}
|
||||||
51
auth.php
Normal file
51
auth.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('session.use_strict_mode', 1);
|
||||||
|
ini_set('session.cookie_lifetime', 0);
|
||||||
|
ini_set('session.cookie_secure', 1); // Should be 1 in production
|
||||||
|
ini_set('session.cookie_httponly', 1);
|
||||||
|
ini_set('session.cookie_samesite', 'Lax');
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
function is_logged_in() {
|
||||||
|
return isset($_SESSION['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_login() {
|
||||||
|
if (!is_logged_in()) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle login
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email'], $_POST['password'])) {
|
||||||
|
$email = $_POST['email'];
|
||||||
|
$password = $_POST['password'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
session_regenerate_id(true);
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['user_name'] = $user['name'];
|
||||||
|
$_SESSION['user_role'] = $user['role'];
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$_SESSION['login_error'] = 'Invalid email or password.';
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real app, log this error.
|
||||||
|
$_SESSION['login_error'] = 'An error occurred. Please try again later.';
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
119
create_order.php
Normal file
119
create_order.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/partials/header.php';
|
||||||
|
|
||||||
|
// Check if user is logged in and is a Sales Rep
|
||||||
|
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'Sales Rep') {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = [];
|
||||||
|
$success_message = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// CSRF protection
|
||||||
|
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
|
||||||
|
die('CSRF token validation failed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_text = trim($_POST['order_text'] ?? '');
|
||||||
|
$sales_rep_id = $_SESSION['user_id'];
|
||||||
|
$order_date = date('Y-m-d'); // Auto-set and locked
|
||||||
|
|
||||||
|
if (empty($order_text)) {
|
||||||
|
$errors[] = 'Order Text cannot be empty.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Generate order number (simple placeholder for now, will enhance later)
|
||||||
|
// For now, let's just use a timestamp based simple one, we will improve later.
|
||||||
|
$order_number = 'FMO' . date('YmdHis');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO orders (order_number, order_date, order_text, status, sales_rep_id) VALUES (?, ?, ?, ?, ?)');
|
||||||
|
$stmt->execute([$order_number, $order_date, $order_text, 'Pending', $sales_rep_id]);
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$success_message = 'Order ' . $order_number . ' created successfully!';
|
||||||
|
// Clear the form
|
||||||
|
$order_text = '';
|
||||||
|
|
||||||
|
// Send email notification to Dispatch
|
||||||
|
require_once __DIR__ . '/mail/MailService.php';
|
||||||
|
$dispatch_email = 'info@focuzinternational.com'; // TODO: Make this configurable by Admin
|
||||||
|
$subject = 'New Order: ' . $order_number . ' (' . 'Pending' . ')';
|
||||||
|
$html_body = '<p>A new order has been created:</p>'
|
||||||
|
. '<p><strong>Order Number:</strong> ' . htmlspecialchars($order_number) . '</p>'
|
||||||
|
. '<p><strong>Order Date:</strong> ' . htmlspecialchars($order_date) . '</p>'
|
||||||
|
. '<p><strong>Order Text:</strong> ' . nl2br(htmlspecialchars($order_text)) . '</p>'
|
||||||
|
. '<p><strong>Status:</strong> Pending</p>';
|
||||||
|
$text_body = "A new order has been created:\n\n"
|
||||||
|
. "Order Number: {$order_number}\n"
|
||||||
|
. "Order Date: {$order_date}\n"
|
||||||
|
. "Order Text: {$order_text}\n"
|
||||||
|
. "Status: Pending";
|
||||||
|
|
||||||
|
$mail_result = MailService::sendMail($dispatch_email, $subject, $html_body, $text_body);
|
||||||
|
if (!empty($mail_result['error'])) {
|
||||||
|
// Log the email error, but don't fail the order creation
|
||||||
|
error_log('Email sending failed: ' . $mail_result['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$errors[] = 'Database error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new CSRF token for the form
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Create New Order</h2>
|
||||||
|
|
||||||
|
<?php if (!empty($errors)): ?>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<?php foreach ($errors as $error): ?>
|
||||||
|
<p><?php echo htmlspecialchars($error); ?></p>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success_message)): ?>
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<?php echo htmlspecialchars($success_message); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST" action="create_order.php">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_date" class="form-label">Order Date</label>
|
||||||
|
<input type="text" class="form-control" id="order_date" value="<?php echo date('Y-m-d'); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_number" class="form-label">Order Number (Generated Automatically)</label>
|
||||||
|
<input type="text" class="form-control" id="order_number" value="Will be generated on save" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_text" class="form-label">Order Text</label>
|
||||||
|
<textarea class="form-control" id="order_text" name="order_text" rows="5" required><?php echo htmlspecialchars($order_text); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="status" class="form-label">Status</label>
|
||||||
|
<input type="text" class="form-control" id="status" value="Pending" readonly>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Create Order</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|
||||||
@ -4,14 +4,21 @@ define('DB_HOST', '127.0.0.1');
|
|||||||
define('DB_NAME', 'app_36675');
|
define('DB_NAME', 'app_36675');
|
||||||
define('DB_USER', 'app_36675');
|
define('DB_USER', 'app_36675');
|
||||||
define('DB_PASS', '131b38b5-41c0-4253-a34e-9c92b4bb9911');
|
define('DB_PASS', '131b38b5-41c0-4253-a34e-9c92b4bb9911');
|
||||||
|
require_once __DIR__ . '/setup.php';
|
||||||
|
|
||||||
function db() {
|
function db() {
|
||||||
static $pdo;
|
static $pdo;
|
||||||
if (!$pdo) {
|
if (!$pdo) {
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
try {
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
]);
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
// Run setup
|
||||||
|
setup_database($pdo);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("DB connection failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $pdo;
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
|||||||
46
db/setup.php
Normal file
46
db/setup.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
function setup_database($pdo) {
|
||||||
|
try {
|
||||||
|
$pdo->exec("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,
|
||||||
|
role ENUM('Admin', 'Sales Rep', 'Dispatch') NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
)");
|
||||||
|
|
||||||
|
$pdo->exec("CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
order_number VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
order_text TEXT NOT NULL,
|
||||||
|
status ENUM('Pending', 'Query', 'Query Replied', 'Shipped', 'Cancelled') NOT NULL DEFAULT 'Pending',
|
||||||
|
query_text TEXT DEFAULT NULL,
|
||||||
|
reply_text TEXT DEFAULT NULL,
|
||||||
|
sales_rep_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (sales_rep_id) REFERENCES users(id)
|
||||||
|
)");
|
||||||
|
$pdo->exec("ALTER TABLE orders ADD COLUMN IF NOT EXISTS query_text TEXT DEFAULT NULL;");
|
||||||
|
$pdo->exec("ALTER TABLE orders ADD COLUMN IF NOT EXISTS reply_text TEXT DEFAULT NULL;");
|
||||||
|
|
||||||
|
// Add a default admin user if one doesn't exist
|
||||||
|
$stmt = $pdo->query("SELECT COUNT(*) FROM users WHERE role = 'Admin'");
|
||||||
|
if ($stmt->fetchColumn() == 0) {
|
||||||
|
$admin_email = 'admin@example.com';
|
||||||
|
$admin_name = 'Admin';
|
||||||
|
// In a real app, use a more secure password policy
|
||||||
|
$admin_password = password_hash('password', PASSWORD_DEFAULT);
|
||||||
|
$admin_role = 'Admin';
|
||||||
|
|
||||||
|
$insert_stmt = $pdo->prepare("INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)");
|
||||||
|
$insert_stmt->execute([$admin_name, $admin_email, $admin_password, $admin_role]);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real app, you would log this error.
|
||||||
|
die("Database setup failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
318
index.php
318
index.php
@ -1,150 +1,176 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
$page_title = 'Dashboard';
|
||||||
@ini_set('display_errors', '1');
|
require_once __DIR__ . '/partials/header.php';
|
||||||
@error_reporting(E_ALL);
|
require_once __DIR__ . '/db/config.php';
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$user_role = $_SESSION['user_role'];
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Initialize counts
|
||||||
|
$pending_orders_count = 0;
|
||||||
|
$pending_replies_count = 0;
|
||||||
|
$shipped_orders_month_count = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// --- Fetch Dashboard Counts ---
|
||||||
|
$base_conditions_arr = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($user_role === 'Sales Rep') {
|
||||||
|
$base_conditions_arr[] = "sales_rep_id = :user_id";
|
||||||
|
$params[':user_id'] = $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pending Orders Count
|
||||||
|
$pending_orders_conditions_arr = $base_conditions_arr;
|
||||||
|
$pending_orders_conditions_arr[] = "status = 'Pending'";
|
||||||
|
|
||||||
|
$pending_orders_where_clause = '';
|
||||||
|
if (!empty($pending_orders_conditions_arr)) {
|
||||||
|
$pending_orders_where_clause = " WHERE " . implode(" AND ", $pending_orders_conditions_arr);
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM orders " . $pending_orders_where_clause);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$pending_orders_count = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// Pending Replies Count (Orders with status 'Query' or 'Query Replied')
|
||||||
|
$pending_replies_conditions_arr = $base_conditions_arr;
|
||||||
|
$pending_replies_conditions_arr[] = "(status = 'Query' OR status = 'Query Replied')";
|
||||||
|
|
||||||
|
$pending_replies_where_clause = '';
|
||||||
|
if (!empty($pending_replies_conditions_arr)) {
|
||||||
|
$pending_replies_where_clause = " WHERE " . implode(" AND ", $pending_replies_conditions_arr);
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM orders " . $pending_replies_where_clause);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$pending_replies_count = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// Shipped Orders This Month Count
|
||||||
|
$current_month_start = date('Y-m-01 00:00:00');
|
||||||
|
$current_month_end = date('Y-m-t 23:59:59');
|
||||||
|
|
||||||
|
$shipped_conditions_arr = $base_conditions_arr;
|
||||||
|
$shipped_conditions_arr[] = "status = 'Shipped'";
|
||||||
|
$shipped_conditions_arr[] = "created_at >= :start_date";
|
||||||
|
$shipped_conditions_arr[] = "created_at <= :end_date";
|
||||||
|
|
||||||
|
$shipped_where_clause = '';
|
||||||
|
$shipped_params = $params;
|
||||||
|
$shipped_params[':start_date'] = $current_month_start;
|
||||||
|
$shipped_params[':end_date'] = $current_month_end;
|
||||||
|
|
||||||
|
if (!empty($shipped_conditions_arr)) {
|
||||||
|
$shipped_where_clause = " WHERE " . implode(" AND ", $shipped_conditions_arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM orders " . $shipped_where_clause);
|
||||||
|
$stmt->execute($shipped_params);
|
||||||
|
$shipped_orders_month_count = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// --- Fetch Order List ---
|
||||||
|
$orders = [];
|
||||||
|
$sql = "SELECT o.id, o.order_number, o.order_date, o.order_text, o.status, o.sales_rep_id, u.name as sales_rep_name
|
||||||
|
FROM orders o
|
||||||
|
JOIN users u ON o.sales_rep_id = u.id";
|
||||||
|
|
||||||
|
if ($user_role === 'Sales Rep') {
|
||||||
|
$sql .= " WHERE o.sales_rep_id = :user_id";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
|
||||||
|
} else {
|
||||||
|
// Dispatch and Admin can see all orders
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Database error fetching dashboard data or orders: " . $e->getMessage());
|
||||||
|
echo '<div class="alert alert-danger" role="alert">Error loading dashboard data. Please try again later.</div>';
|
||||||
|
}
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
<div class="row mb-4">
|
||||||
<head>
|
<div class="col-md-4">
|
||||||
<meta charset="utf-8" />
|
<div class="card border-0 shadow-sm text-primary-emphasis bg-primary-subtle">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<div class="card-body">
|
||||||
<title>New Style</title>
|
<h5 class="card-title">Pending Orders</h5>
|
||||||
<?php
|
<p class="card-text h2"><?php echo htmlspecialchars($pending_orders_count); ?></p>
|
||||||
// Read project preview data from environment
|
</div>
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
</div>
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<!-- Meta description -->
|
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="card">
|
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
|
||||||
<span class="sr-only">Loading…</span>
|
|
||||||
</div>
|
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
<div class="col-md-4">
|
||||||
<footer>
|
<div class="card border-0 shadow-sm text-warning-emphasis bg-warning-subtle">
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<div class="card-body">
|
||||||
</footer>
|
<h5 class="card-title">Pending Replies</h5>
|
||||||
</body>
|
<p class="card-text h2"><?php echo htmlspecialchars($pending_replies_count); ?></p>
|
||||||
</html>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm text-success-emphasis bg-success-subtle">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Shipped This Month</h5>
|
||||||
|
<p class="card-text h2"><?php echo htmlspecialchars($shipped_orders_month_count); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="h4 mb-0">Welcome, <?php echo htmlspecialchars($_SESSION['user_name']); ?>!</h1>
|
||||||
|
<p class="text-muted">Your current role: <?php echo htmlspecialchars($user_role); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Order List</h5>
|
||||||
|
<?php if (empty($orders)): ?>
|
||||||
|
<p>No orders found.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order Number</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Sales Rep</th>
|
||||||
|
<th>Text</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($orders as $order): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($order['order_number']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($order['order_date']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($order['sales_rep_name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars(substr($order['order_text'], 0, 50)); ?>...</td>
|
||||||
|
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($order['status']); ?></span></td>
|
||||||
|
<td>
|
||||||
|
<a href="view_order.php?id=<?php echo $order['id']; ?>" class="btn btn-sm btn-outline-primary">View</a>
|
||||||
|
<?php if ($user_role === 'Sales Rep' && $order['sales_rep_id'] == $user_id): ?>
|
||||||
|
<a href="view_order.php?id=<?php echo $order['id']; ?>" class="btn btn-sm btn-outline-secondary">Edit</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|
||||||
|
|||||||
63
login.php
Normal file
63
login.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login - Focuz Order Management</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light-gray">
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row vh-100 justify-content-center align-items-center">
|
||||||
|
<div class="col-11 col-sm-8 col-md-6 col-lg-5 col-xl-4">
|
||||||
|
<div class="card border-0 shadow-sm p-4" style="border-radius: 0.5rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<i class="bi bi-box-seam-fill text-primary" style="font-size: 3rem;"></i>
|
||||||
|
<h1 class="h3 mb-1 fw-bold">Focuz Order Management</h1>
|
||||||
|
<p class="text-muted">Please sign in to continue</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (isset($_SESSION['login_error'])):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<?php
|
||||||
|
echo htmlspecialchars($_SESSION['login_error']);
|
||||||
|
unset($_SESSION['login_error']);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="auth.php" 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">Sign in</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<small class="text-muted">Default credentials: admin@example.com / password</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
9
partials/footer.php
Normal file
9
partials/footer.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="container-fluid text-center text-muted mt-5 py-3">
|
||||||
|
<small>© <?php echo date('Y'); ?> Focuz International. All Rights Reserved.</small>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
partials/header.php
Normal file
56
partials/header.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../auth.php';
|
||||||
|
require_login();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo isset($page_title) ? htmlspecialchars($page_title) : 'Dashboard'; ?> - Focuz Order Management</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light-gray">
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand fw-bold text-primary" href="index.php">
|
||||||
|
<i class="bi bi-box-seam-fill"></i>
|
||||||
|
Focuz Orders
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-nav" aria-controls="main-nav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="main-nav">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="index.php">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<!-- More nav items will go here based on role -->
|
||||||
|
<?php if (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'Sales Rep'): ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="create_order.php">Create Order</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'Admin'): ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="admin_users.php">User Management</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="navbar-text me-3">
|
||||||
|
Welcome, <?php echo htmlspecialchars($_SESSION['user_name']); ?> (<?php echo htmlspecialchars($_SESSION['user_role']); ?>)
|
||||||
|
</span>
|
||||||
|
<a href="logout.php" class="btn btn-outline-secondary btn-sm">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container-fluid mt-4">
|
||||||
206
view_order.php
Normal file
206
view_order.php
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<?php
|
||||||
|
$page_title = 'View/Edit Order';
|
||||||
|
require_once __DIR__ . '/partials/header.php';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$user_role = $_SESSION['user_role'];
|
||||||
|
$pdo = db();
|
||||||
|
$order = null;
|
||||||
|
$edit_mode = false;
|
||||||
|
$errors = [];
|
||||||
|
$success_message = '';
|
||||||
|
|
||||||
|
// Check if order ID is provided
|
||||||
|
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
|
||||||
|
$order_id = $_GET['id'];
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("SELECT o.*, o.query_text, o.reply_text, u.name as sales_rep_name FROM orders o LEFT JOIN users u ON o.sales_rep_id = u.id WHERE o.id = :id");
|
||||||
|
$stmt->bindParam(':id', $order_id, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
$errors[] = "Order not found.";
|
||||||
|
} else {
|
||||||
|
// Determine if user can edit this order
|
||||||
|
if ($user_role === 'Sales Rep' && $order['sales_rep_id'] == $user_id) {
|
||||||
|
$edit_mode = true;
|
||||||
|
} elseif ($user_role === 'Dispatch' || $user_role === 'Admin') {
|
||||||
|
$edit_mode = true; // Dispatch and Admin can always edit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$errors[] = "Error loading order details. Please try again later.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$errors[] = "No order ID provided.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission for updating order
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $edit_mode && $order) {
|
||||||
|
$new_order_number = trim($_POST['order_number'] ?? '');
|
||||||
|
$new_order_date = trim($_POST['order_date'] ?? '');
|
||||||
|
$new_order_text = trim($_POST['order_text'] ?? '');
|
||||||
|
$new_status = trim($_POST['status'] ?? '');
|
||||||
|
$new_query_text = trim($_POST['query_text'] ?? '');
|
||||||
|
$new_reply_text = trim($_POST['reply_text'] ?? '');
|
||||||
|
|
||||||
|
// Preserve existing query/reply if not being updated by a specific status change
|
||||||
|
$query_to_save = $order['query_text'];
|
||||||
|
$reply_to_save = $order['reply_text'];
|
||||||
|
|
||||||
|
if ($new_status === 'Query' && empty($order['query_text'])) {
|
||||||
|
$query_to_save = $new_query_text;
|
||||||
|
} elseif ($new_status === 'Query Replied' && empty($order['reply_text'])) {
|
||||||
|
$reply_to_save = $new_reply_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($new_order_number)) {
|
||||||
|
$errors[] = "Order number cannot be empty.";
|
||||||
|
}
|
||||||
|
if (empty($new_order_date)) {
|
||||||
|
$errors[] = "Order date cannot be empty.";
|
||||||
|
}
|
||||||
|
if (empty($new_order_text)) {
|
||||||
|
$errors[] = "Order text cannot be empty.";
|
||||||
|
}
|
||||||
|
if (empty($new_status)) {
|
||||||
|
$errors[] = "Status cannot be empty.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE orders SET order_number = :order_number, order_date = :order_date, order_text = :order_text, status = :status, query_text = :query_text, reply_text = :reply_text WHERE id = :id");
|
||||||
|
$stmt->bindParam(':order_number', $new_order_number);
|
||||||
|
$stmt->bindParam(':order_date', $new_order_date);
|
||||||
|
$stmt->bindParam(':order_text', $new_order_text);
|
||||||
|
$stmt->bindParam(':status', $new_status);
|
||||||
|
$stmt->bindParam(':query_text', $query_to_save);
|
||||||
|
$stmt->bindParam(':reply_text', $reply_to_save);
|
||||||
|
$stmt->bindParam(':id', $order_id, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$success_message = "Order updated successfully!";
|
||||||
|
// Re-fetch order to display updated data
|
||||||
|
$stmt = $pdo->prepare("SELECT o.*, u.name as sales_rep_name FROM orders o LEFT JOIN users u ON o.sales_rep_id = u.id WHERE o.id = :id");
|
||||||
|
$stmt->bindParam(':id', $order_id, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$order = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Database error updating order: " . $e->getMessage());
|
||||||
|
$errors[] = "Error updating order. Please try again later.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 mx-auto">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h1 class="card-title h4 mb-0"><?php echo htmlspecialchars($page_title); ?></h1>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (!empty($errors)): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<?php foreach ($errors as $error): ?>
|
||||||
|
<p class="mb-0"><?php echo htmlspecialchars($error); ?></p>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($success_message)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<?php echo htmlspecialchars($success_message); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($order): ?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_number" class="form-label">Order Number</label>
|
||||||
|
<input type="text" class="form-control" id="order_number" name="order_number" value="<?php echo htmlspecialchars($order['order_number']); ?>" <?php echo $edit_mode ? '' : 'readonly'; ?>>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_date" class="form-label">Order Date</label>
|
||||||
|
<input type="date" class="form-control" id="order_date" name="order_date" value="<?php echo htmlspecialchars($order['order_date']); ?>" <?php echo $edit_mode ? '' : 'readonly'; ?>>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="order_text" class="form-label">Order Details</label>
|
||||||
|
<textarea class="form-control" id="order_text" name="order_text" rows="5" <?php echo $edit_mode ? '' : 'readonly'; ?>><?php echo htmlspecialchars($order['order_text']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="sales_rep_name" class="form-label">Sales Rep</label>
|
||||||
|
<input type="text" class="form-control" id="sales_rep_name" value="<?php echo htmlspecialchars($order['sales_rep_name']); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="status" class="form-label">Status</label>
|
||||||
|
<?php if ($edit_mode): ?>
|
||||||
|
<select class="form-select" id="status" name="status">
|
||||||
|
<?php $statuses = ['Pending', 'Query', 'Query Replied', 'Shipped', 'Cancelled']; ?>
|
||||||
|
<?php foreach ($statuses as $status_option): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($status_option); ?>" <?php echo ($order['status'] === $status_option) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($status_option); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<?php else: ?>
|
||||||
|
<input type="text" class="form-control" id="status" name="status" value="<?php echo htmlspecialchars($order['status']); ?>" readonly>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="queryTextBox" style="display: none;">
|
||||||
|
<label for="query_text" class="form-label">Query</label>
|
||||||
|
<textarea class="form-control" id="query_text" name="query_text" rows="3" <?php echo ($order['query_text'] && $order['query_text'] !== '') ? 'readonly' : ''; ?>><?php echo htmlspecialchars($order['query_text'] ?? ''); ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="replyTextBox" style="display: none;">
|
||||||
|
<label for="reply_text" class="form-label">Query Reply</label>
|
||||||
|
<textarea class="form-control" id="reply_text" name="reply_text" rows="3" <?php echo ($order['reply_text'] && $order['reply_text'] !== '') ? 'readonly' : ''; ?>><?php echo htmlspecialchars($order['reply_text'] ?? ''); ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($edit_mode): ?>
|
||||||
|
<button type="submit" class="btn btn-primary">Update Order</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="index.php" class="btn btn-secondary">Back to Dashboard</a>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="index.php" class="btn btn-primary">Back to Dashboard</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const statusSelect = document.getElementById('status');
|
||||||
|
const queryTextBox = document.getElementById('queryTextBox');
|
||||||
|
const replyTextBox = document.getElementById('replyTextBox');
|
||||||
|
|
||||||
|
function toggleTextBoxes() {
|
||||||
|
const currentStatus = statusSelect.value;
|
||||||
|
queryTextBox.style.display = 'none';
|
||||||
|
replyTextBox.style.display = 'none';
|
||||||
|
|
||||||
|
if (currentStatus === 'Query') {
|
||||||
|
queryTextBox.style.display = 'block';
|
||||||
|
} else if (currentStatus === 'Query Replied') {
|
||||||
|
replyTextBox.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial call to set correct visibility on page load
|
||||||
|
toggleTextBoxes();
|
||||||
|
|
||||||
|
// Add event listener for status change
|
||||||
|
if (statusSelect) {
|
||||||
|
statusSelect.addEventListener('change', toggleTextBoxes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|
||||||
Loading…
x
Reference in New Issue
Block a user