Autosave: 20260223-150636

This commit is contained in:
Flatlogic Bot 2026-02-23 15:06:36 +00:00
parent b98ef1276a
commit 4bd6115a47
6 changed files with 437 additions and 143 deletions

View File

@ -472,15 +472,15 @@ function can_view($module) {
<?php if (can_view('user_groups')): ?> <?php if (can_view('user_groups')): ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php"> <a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php">
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
</a>
</li>
<?php endif; ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= isActive('attendance.php') ?>" href="attendance.php"> <a class="nav-link <?= isActive('attendance.php') ?>" href="attendance.php">
<i class="bi bi-calendar-check me-2"></i> Attendance <i class="bi bi-calendar-check me-2"></i> Attendance
</a> </a>
</li> </li>
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
</a>
</li>
<?php endif; ?>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -12,6 +12,7 @@ $isDetailed = has_permission('dashboard_add') || has_permission('all');
if ($isDetailed) { if ($isDetailed) {
// Fetch Dashboard Stats // Fetch Dashboard Stats
$today = date('Y-m-d'); $today = date('Y-m-d');
$thisMonth = date('Y-m');
// Total Revenue Today // Total Revenue Today
$stmt = $pdo->prepare("SELECT SUM(total_amount) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'"); $stmt = $pdo->prepare("SELECT SUM(total_amount) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'");
@ -19,16 +20,75 @@ if ($isDetailed) {
$revenueToday = $stmt->fetchColumn() ?: 0; $revenueToday = $stmt->fetchColumn() ?: 0;
// Total Orders Today // Total Orders Today
$stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = ?"); $stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = ? AND status != 'cancelled'");
$stmt->execute([$today]); $stmt->execute([$today]);
$ordersToday = $stmt->fetchColumn(); $ordersToday = $stmt->fetchColumn();
// Total Revenue This Month
$stmt = $pdo->prepare("SELECT SUM(total_amount) FROM orders WHERE DATE_FORMAT(created_at, '%Y-%m') = ? AND status != 'cancelled'");
$stmt->execute([$thisMonth]);
$revenueThisMonth = $stmt->fetchColumn() ?: 0;
// Total Expenses This Month
$stmt = $pdo->prepare("SELECT SUM(amount) FROM expenses WHERE DATE_FORMAT(expense_date, '%Y-%m') = ?");
$stmt->execute([$thisMonth]);
$expensesThisMonth = $stmt->fetchColumn() ?: 0;
// Estimated Net Profit This Month
$netProfitThisMonth = $revenueThisMonth - $expensesThisMonth;
// Active Outlets // Active Outlets
$outletsCount = $pdo->query("SELECT COUNT(*) FROM outlets")->fetchColumn(); $outletsCount = $pdo->query("SELECT COUNT(*) FROM outlets")->fetchColumn();
// Total Products // Total Products
$productsCount = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn(); $productsCount = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn();
// 1. Sales Trend (Last 12 Months)
$salesTrendQuery = "
SELECT DATE_FORMAT(created_at, '%b %Y') as month_label, SUM(total_amount) as total, DATE_FORMAT(created_at, '%Y-%m') as sort_key
FROM orders
WHERE status != 'cancelled'
AND created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY month_label, sort_key
ORDER BY sort_key ASC";
$salesTrend = $pdo->query($salesTrendQuery)->fetchAll();
// 2. Sales by Category (Pie Chart)
$salesByCategoryQuery = "
SELECT c.name as category_name, SUM(oi.quantity * oi.unit_price) as total_sales
FROM order_items oi
JOIN products p ON oi.product_id = p.id
JOIN categories c ON p.category_id = c.id
JOIN orders o ON oi.order_id = o.id
WHERE o.status != 'cancelled'
GROUP BY c.name
ORDER BY total_sales DESC";
$salesByCategory = $pdo->query($salesByCategoryQuery)->fetchAll();
// 3. Sales by Order Type (Pie Chart)
$salesByTypeQuery = "
SELECT order_type, SUM(total_amount) as total
FROM orders
WHERE status != 'cancelled'
GROUP BY order_type";
$salesByType = $pdo->query($salesByTypeQuery)->fetchAll();
// 4. Top 5 Items Sold
$topItemsQuery = "
SELECT p.name, SUM(oi.quantity) as total_qty
FROM order_items oi
JOIN products p ON oi.product_id = p.id
JOIN orders o ON oi.order_id = o.id
WHERE o.status != 'cancelled'
GROUP BY p.name
ORDER BY total_qty DESC
LIMIT 5";
$topItems = $pdo->query($topItemsQuery)->fetchAll();
// 5. Value Add: Average Order Value
$stmt = $pdo->query("SELECT AVG(total_amount) FROM orders WHERE status != 'cancelled'");
$avgOrderValue = $stmt->fetchColumn() ?: 0;
// Recent Orders // Recent Orders
$recentOrders = $pdo->query("SELECT o.*, $recentOrders = $pdo->query("SELECT o.*,
(SELECT GROUP_CONCAT(p.name SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items (SELECT GROUP_CONCAT(p.name SEPARATOR ', ') FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = o.id) as items
@ -44,17 +104,21 @@ include 'includes/header.php';
<h2 class="fw-bold mb-1">Dashboard</h2> <h2 class="fw-bold mb-1">Dashboard</h2>
<p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p> <p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p>
</div> </div>
<?php if (has_permission('orders_add')): ?> <div class="d-flex gap-2">
<div> <?php if (has_permission('orders_view')): ?>
<a href="../pos.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a> <a href="reports.php" class="btn btn-outline-primary"><i class="bi bi-file-earmark-bar-graph me-1"></i> Reports</a>
</div>
<?php endif; ?> <?php endif; ?>
<?php if (has_permission('orders_add')): ?>
<a href="../pos.php" class="btn btn-primary shadow-sm"><i class="bi bi-plus-lg me-1"></i> New Order</a>
<?php endif; ?>
</div>
</div> </div>
<div class="row g-4 mb-4"> <div class="row g-4 mb-4">
<!-- Revenue Card --> <!-- Revenue Card -->
<div class="col-md-3"> <div class="col-md-3">
<div class="card stat-card h-100 p-3"> <div class="card stat-card h-100 p-3">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="icon-box bg-success bg-opacity-10 text-success me-3"> <div class="icon-box bg-success bg-opacity-10 text-success me-3">
<i class="bi bi-currency-dollar"></i> <i class="bi bi-currency-dollar"></i>
@ -66,6 +130,22 @@ include 'includes/header.php';
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Net Profit Card (Value Add) -->
<div class="col-md-3">
<div class="card stat-card h-100 p-3">
<div class="d-flex align-items-center">
<div class="icon-box bg-info bg-opacity-10 text-info me-3">
<i class="bi bi-bank"></i>
</div>
<div>
<h6 class="text-muted mb-0">Profit (This Month)</h6>
<h3 class="fw-bold mb-0 <?= $netProfitThisMonth >= 0 ? 'text-success' : 'text-danger' ?>"><?= format_currency($netProfitThisMonth) ?></h3>
</div>
</div>
</div>
</div>
<!-- Orders Card --> <!-- Orders Card -->
<div class="col-md-3"> <div class="col-md-3">
@ -82,41 +162,105 @@ include 'includes/header.php';
</div> </div>
</div> </div>
<!-- Outlets Card --> <!-- Average Order Value -->
<div class="col-md-3"> <div class="col-md-3">
<div class="card stat-card h-100 p-3"> <div class="card stat-card h-100 p-3">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="icon-box bg-warning bg-opacity-10 text-warning me-3"> <div class="icon-box bg-warning bg-opacity-10 text-warning me-3">
<i class="bi bi-shop"></i> <i class="bi bi-calculator"></i>
</div> </div>
<div> <div>
<h6 class="text-muted mb-0">Active Outlets</h6> <h6 class="text-muted mb-0">Avg. Order Value</h6>
<h3 class="fw-bold mb-0"><?= $outletsCount ?></h3> <h3 class="fw-bold mb-0"><?= format_currency($avgOrderValue) ?></h3>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Products Card --> <div class="row g-4 mb-4">
<div class="col-md-3"> <!-- Sales Trend Graph -->
<div class="card stat-card h-100 p-3"> <div class="col-lg-8">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">Sales Trend</h5>
<span class="badge bg-light text-dark border">Last 12 Months</span>
</div>
<div class="card-body">
<canvas id="salesTrendChart" style="min-height: 350px;"></canvas>
</div>
</div>
</div>
<!-- Sales Distribution (Two Pie Charts) -->
<div class="col-lg-4">
<div class="row g-4 h-100">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">Category Distribution</h5>
</div>
<div class="card-body">
<?php if (empty($salesByCategory)): ?>
<p class="text-muted text-center py-4">No data available</p>
<?php else: ?>
<canvas id="categoryPieChart" style="max-height: 180px;"></canvas>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">Order Type Distribution</h5>
</div>
<div class="card-body">
<?php if (empty($salesByType)): ?>
<p class="text-muted text-center py-4">No data available</p>
<?php else: ?>
<canvas id="typePieChart" style="max-height: 180px;"></canvas>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Top 5 Items Sold -->
<div class="col-lg-5">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">Top 5 Items Sold (Qty)</h5>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
<?php foreach ($topItems as $index => $item): ?>
<li class="list-group-item d-flex justify-content-between align-items-center py-3 border-0 border-bottom">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="icon-box bg-info bg-opacity-10 text-info me-3"> <span class="badge bg-primary bg-opacity-10 text-primary rounded-circle me-3 d-flex align-items-center justify-content-center" style="width: 30px; height: 30px;"><?= $index + 1 ?></span>
<i class="bi bi-box-seam"></i> <span class="fw-medium text-dark"><?= htmlspecialchars($item['name']) ?></span>
</div>
<div>
<h6 class="text-muted mb-0">Total Products</h6>
<h3 class="fw-bold mb-0"><?= $productsCount ?></h3>
</div>
</div> </div>
<span class="badge bg-light text-dark border fw-bold"><?= $item['total_qty'] ?> Sold</span>
</li>
<?php endforeach; ?>
<?php if (empty($topItems)): ?>
<li class="list-group-item text-center py-4 text-muted">No sales data yet.</li>
<?php endif; ?>
</ul>
</div> </div>
</div> </div>
</div> </div>
<!-- Recent Orders Table --> <!-- Recent Orders Table -->
<div class="card border-0 shadow-sm rounded-3"> <div class="col-lg-7">
<div class="card-header bg-white border-bottom py-3"> <div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">Recent Orders</h5> <h5 class="mb-0 fw-bold">Recent Orders</h5>
<?php if (has_permission('orders_view')): ?>
<a href="orders.php" class="btn btn-sm btn-link text-decoration-none p-0">View All</a>
<?php endif; ?>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<div class="table-responsive"> <div class="table-responsive">
@ -124,8 +268,6 @@ include 'includes/header.php';
<thead class="bg-light"> <thead class="bg-light">
<tr> <tr>
<th class="ps-4">ID</th> <th class="ps-4">ID</th>
<th>Type</th>
<th>Table/Customer</th>
<th>Total</th> <th>Total</th>
<th>Status</th> <th>Status</th>
<th>Date</th> <th>Date</th>
@ -135,25 +277,6 @@ include 'includes/header.php';
<?php foreach ($recentOrders as $order): ?> <?php foreach ($recentOrders as $order): ?>
<tr> <tr>
<td class="ps-4 fw-medium">#<?= $order['id'] ?></td> <td class="ps-4 fw-medium">#<?= $order['id'] ?></td>
<td>
<?php
$badge = match($order['order_type']) {
'dine-in' => 'bg-info',
'takeaway' => 'bg-success',
'delivery' => 'bg-warning',
'drive-thru' => 'bg-purple',
default => 'bg-secondary'
};
?>
<span class="badge <?= $badge ?> text-dark bg-opacity-25 border border-<?= str_replace('bg-', '', $badge) ?>"><?= ucfirst($order['order_type']) ?></span>
</td>
<td>
<?php if ($order['table_number']): ?>
Table <?= htmlspecialchars($order['table_number']) ?>
<?php else: ?>
<?= htmlspecialchars($order['customer_name'] ?? 'Guest') ?>
<?php endif; ?>
</td>
<td class="fw-bold"><?= format_currency($order['total_amount']) ?></td> <td class="fw-bold"><?= format_currency($order['total_amount']) ?></td>
<td> <td>
<span class="status-badge status-<?= $order['status'] ?> badge rounded-pill"> <span class="status-badge status-<?= $order['status'] ?> badge rounded-pill">
@ -164,18 +287,126 @@ include 'includes/header.php';
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($recentOrders)): ?> <?php if (empty($recentOrders)): ?>
<tr><td colspan="6" class="text-center py-4 text-muted">No recent orders found.</td></tr> <tr><td colspan="4" class="text-center py-4 text-muted">No recent orders found.</td></tr>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<?php if (has_permission('orders_view')): ?>
<div class="card-footer bg-white text-center py-3">
<a href="orders.php" class="text-decoration-none fw-medium">View All Orders</a>
</div> </div>
</div>
</div>
<!-- Chart.js and Graph Logic -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const colors = ['#0d6efd', '#198754', '#ffc107', '#0dcaf0', '#6610f2', '#fd7e14', '#20c997', '#d63384', '#6f42c1', '#adb5bd'];
// 1. Sales Trend Chart
const salesTrendCtx = document.getElementById('salesTrendChart').getContext('2d');
new Chart(salesTrendCtx, {
type: 'line',
data: {
labels: <?= json_encode(array_column($salesTrend, 'month_label')) ?>,
datasets: [{
label: 'Monthly Revenue',
data: <?= json_encode(array_column($salesTrend, 'total')) ?>,
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.4,
borderWidth: 3,
pointBackgroundColor: '#fff',
pointBorderColor: '#0d6efd',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumSignificantDigits: 3 });
}
}
}
}
}
});
// 2. Category Pie Chart
<?php if (!empty($salesByCategory)): ?>
const categoryPieCtx = document.getElementById('categoryPieChart').getContext('2d');
new Chart(categoryPieCtx, {
type: 'doughnut',
data: {
labels: <?= json_encode(array_column($salesByCategory, 'category_name')) ?>,
datasets: [{
data: <?= json_encode(array_column($salesByCategory, 'total_sales')) ?>,
backgroundColor: colors,
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
position: 'right',
labels: {
usePointStyle: true,
padding: 15,
font: { size: 10 }
}
}
}
}
});
<?php endif; ?> <?php endif; ?>
</div>
// 3. Order Type Pie Chart
<?php if (!empty($salesByType)): ?>
const typePieCtx = document.getElementById('typePieChart').getContext('2d');
new Chart(typePieCtx, {
type: 'doughnut',
data: {
labels: <?= json_encode(array_map('ucfirst', array_column($salesByType, 'order_type'))) ?>,
datasets: [{
data: <?= json_encode(array_column($salesByType, 'total')) ?>,
backgroundColor: ['#6610f2', '#198754', '#fd7e14', '#ffc107'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
position: 'right',
labels: {
usePointStyle: true,
padding: 15,
font: { size: 10 }
}
}
}
}
});
<?php endif; ?>
});
</script>
<?php else: ?> <?php else: ?>
<!-- Simplified Dashboard --> <!-- Simplified Dashboard -->
<div class="d-flex flex-column align-items-center justify-content-center py-5 mt-5"> <div class="d-flex flex-column align-items-center justify-content-center py-5 mt-5">

View File

@ -25,6 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name']; $name = $_POST['name'];
$category_id = $_POST['category_id']; $category_id = $_POST['category_id'];
$price = $_POST['price']; $price = $_POST['price'];
$cost_price = $_POST['cost_price'] ?: 0;
$description = $_POST['description']; $description = $_POST['description'];
$image_url = $product['image_url']; // Default to existing $image_url = $product['image_url']; // Default to existing
@ -59,8 +60,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
if (empty($message)) { if (empty($message)) {
$stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?"); $stmt = $pdo->prepare("UPDATE products SET name = ?, category_id = ?, price = ?, cost_price = ?, description = ?, image_url = ?, promo_discount_percent = ?, promo_date_from = ?, promo_date_to = ? WHERE id = ?");
if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id])) { if ($stmt->execute([$name, $category_id, $price, $cost_price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $id])) {
$message = '<div class="alert alert-success">Product updated successfully!</div>'; $message = '<div class="alert alert-success">Product updated successfully!</div>';
// Refresh product data // Refresh product data
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?"); $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
@ -91,12 +92,12 @@ include 'includes/header.php';
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Name</label> <label class="form-label text-muted small fw-bold">PRODUCT NAME</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($product['name']) ?>" required> <input type="text" name="name" class="form-control" value="<?= htmlspecialchars($product['name']) ?>" required>
</div> </div>
<div class="row"> <div class="row g-3">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label">Category</label> <label class="form-label text-muted small fw-bold">CATEGORY</label>
<select name="category_id" class="form-select" required> <select name="category_id" class="form-select" required>
<?php foreach ($categories as $cat): ?> <?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= $cat['id'] == $product['category_id'] ? 'selected' : '' ?>> <option value="<?= $cat['id'] ?>" <?= $cat['id'] == $product['category_id'] ? 'selected' : '' ?>>
@ -106,10 +107,16 @@ include 'includes/header.php';
</select> </select>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label">Price ($)</label> <label class="form-label text-muted small fw-bold">SELLING PRICE ($)</label>
<input type="number" step="0.01" name="price" class="form-control" value="<?= htmlspecialchars($product['price']) ?>" required> <input type="number" step="0.01" name="price" class="form-control" value="<?= htmlspecialchars($product['price']) ?>" required>
</div> </div>
</div> </div>
<div class="row g-3">
<div class="col-md-6 mb-3">
<label class="form-label text-muted small fw-bold">COST PRICE ($)</label>
<input type="number" step="0.01" name="cost_price" class="form-control" value="<?= htmlspecialchars($product['cost_price'] ?? '0.00') ?>">
</div>
</div>
<div class="card bg-light border-0 mb-3"> <div class="card bg-light border-0 mb-3">
<div class="card-body"> <div class="card-body">
@ -133,19 +140,19 @@ include 'includes/header.php';
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Description</label> <label class="form-label text-muted small fw-bold">DESCRIPTION</label>
<textarea name="description" class="form-control" rows="5"><?= htmlspecialchars($product['description']) ?></textarea> <textarea name="description" class="form-control" rows="5"><?= htmlspecialchars($product['description']) ?></textarea>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Current Image</label> <label class="form-label text-muted small fw-bold">CURRENT IMAGE</label>
<div class="mb-2"> <div class="mb-2">
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" class="img-fluid rounded border" alt="Product Image"> <img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" class="img-fluid rounded border" alt="Product Image">
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Upload New Image</label> <label class="form-label text-muted small fw-bold">UPLOAD NEW IMAGE</label>
<input type="file" name="image" class="form-control" accept="image/*"> <input type="file" name="image" class="form-control" accept="image/*">
<div class="form-text">Leave empty to keep current image.</div> <div class="form-text">Leave empty to keep current image.</div>
</div> </div>

View File

@ -15,6 +15,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
$name = $_POST['name']; $name = $_POST['name'];
$category_id = $_POST['category_id']; $category_id = $_POST['category_id'];
$price = $_POST['price']; $price = $_POST['price'];
$cost_price = $_POST['cost_price'] ?: 0;
$description = $_POST['description']; $description = $_POST['description'];
$promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null; $promo_discount_percent = $_POST['promo_discount_percent'] !== '' ? $_POST['promo_discount_percent'] : null;
@ -44,8 +45,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
} }
} }
$stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, cost_price, description, image_url, promo_discount_percent, promo_date_from, promo_date_to) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) { if ($stmt->execute([$name, $category_id, $price, $cost_price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) {
$message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>'; $message = '<div class="alert alert-success border-0 shadow-sm rounded-3"><i class="bi bi-check-circle-fill me-2"></i>Product added successfully!</div>';
} else { } else {
$message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>'; $message = '<div class="alert alert-danger border-0 shadow-sm rounded-3"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error adding product.</div>';
@ -158,7 +159,8 @@ include 'includes/header.php';
<tr> <tr>
<th>Product</th> <th>Product</th>
<th>Category</th> <th>Category</th>
<th>Price</th> <th>Cost Price</th>
<th>Selling Price</th>
<th>Promotion</th> <th>Promotion</th>
<th class="text-end">Actions</th> <th class="text-end">Actions</th>
</tr> </tr>
@ -186,6 +188,9 @@ include 'includes/header.php';
<td> <td>
<span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span> <span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span>
</td> </td>
<td>
<span class="text-muted"><?= format_currency($product['cost_price'] ?? 0) ?></span>
</td>
<td> <td>
<?php if ($is_promo_active): ?> <?php if ($is_promo_active): ?>
<?php <?php
@ -270,13 +275,22 @@ include 'includes/header.php';
</select> </select>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label text-muted small fw-bold">PRICE ($)</label> <label class="form-label text-muted small fw-bold">SELLING PRICE ($)</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-text bg-light border-end-0" style="border-radius: 10px 0 0 10px;">$</span> <span class="input-group-text bg-light border-end-0" style="border-radius: 10px 0 0 10px;">$</span>
<input type="number" step="0.01" name="price" class="form-control border-start-0" placeholder="0.00" required style="border-radius: 0 10px 10px 0;"> <input type="number" step="0.01" name="price" class="form-control border-start-0" placeholder="0.00" required style="border-radius: 0 10px 10px 0;">
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label text-muted small fw-bold">COST PRICE ($)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0" style="border-radius: 10px 0 0 10px;">$</span>
<input type="number" step="0.01" name="cost_price" class="form-control border-start-0" placeholder="0.00" style="border-radius: 0 10px 10px 0;">
</div>
</div>
</div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label text-muted small fw-bold">DESCRIPTION</label> <label class="form-label text-muted small fw-bold">DESCRIPTION</label>
<textarea name="description" class="form-control" rows="3" placeholder="Brief description..." style="border-radius: 10px;"></textarea> <textarea name="description" class="form-control" rows="3" placeholder="Brief description..." style="border-radius: 10px;"></textarea>

View File

@ -12,12 +12,24 @@ if ($id) {
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC); $user = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$user) { if (!$user) {
header('Location: users.php'); header('Location: users.php');
exit; exit;
} }
} else {
// Default values for new user
$user = [
'id' => null,
'username' => '',
'full_name' => '',
'email' => '',
'group_id' => '',
'employee_id' => '',
'is_active' => 1,
'profile_pic' => '',
'created_at' => date('Y-m-d H:i:s')
];
}
$message = ''; $message = '';
@ -29,11 +41,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$employee_id = $_POST['employee_id'] ?? null; $employee_id = $_POST['employee_id'] ?? null;
$is_active = isset($_POST['is_active']) ? 1 : 0; $is_active = isset($_POST['is_active']) ? 1 : 0;
$assigned_outlets = $_POST['outlets'] ?? []; $assigned_outlets = $_POST['outlets'] ?? [];
$password = $_POST['password'] ?? '';
// Check if username changed and if new one exists // Validation
if ($username !== $user['username']) { if (!$id && empty($password)) {
$message = '<div class="alert alert-danger">Password is required for new users.</div>';
} else {
// Check if username already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? AND id != ?"); $stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
$stmt->execute([$username, $id]); $stmt->execute([$username, (int)$id]);
if ($stmt->fetch()) { if ($stmt->fetch()) {
$message = '<div class="alert alert-danger">Username already taken.</div>'; $message = '<div class="alert alert-danger">Username already taken.</div>';
} }
@ -42,24 +58,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$message) { if (!$message) {
$pdo->beginTransaction(); $pdo->beginTransaction();
try { try {
if ($id) {
// Update
$sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?"; $sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?";
$params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id]; $params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id];
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
// Update password if provided if (!empty($password)) {
if (!empty($_POST['password'])) { $hashed_password = password_hash($password, PASSWORD_DEFAULT);
$password = password_hash($_POST['password'], PASSWORD_DEFAULT); $pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$hashed_password, $id]);
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]); }
$user_id = $id;
} else {
// Insert
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (full_name, username, email, group_id, is_active, employee_id, password) VALUES (?, ?, ?, ?, ?, ?, ?)";
$params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $hashed_password];
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$user_id = $pdo->lastInsertId();
} }
// Update assigned outlets // Update assigned outlets
$pdo->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$id]); $pdo->prepare("DELETE FROM user_outlets WHERE user_id = ?")->execute([$user_id]);
if (!empty($assigned_outlets)) { if (!empty($assigned_outlets)) {
$stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)"); $stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
foreach ($assigned_outlets as $outlet_id) { foreach ($assigned_outlets as $outlet_id) {
$stmt_outlet->execute([$id, $outlet_id]); $stmt_outlet->execute([$user_id, $outlet_id]);
} }
} }
@ -76,7 +102,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; $allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (in_array($file_ext, $allowed_exts)) { if (in_array($file_ext, $allowed_exts)) {
$new_file_name = 'user_' . $id . '_' . uniqid() . '.' . $file_ext; $new_file_name = 'user_' . $user_id . '_' . uniqid() . '.' . $file_ext;
$upload_path = $upload_dir . $new_file_name; $upload_path = $upload_dir . $new_file_name;
if (move_uploaded_file($file_tmp, $upload_path)) { if (move_uploaded_file($file_tmp, $upload_path)) {
@ -86,30 +112,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
$profile_pic_path = 'assets/images/users/' . $new_file_name; $profile_pic_path = 'assets/images/users/' . $new_file_name;
$pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?")->execute([$profile_pic_path, $id]); $pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?")->execute([$profile_pic_path, $user_id]);
} }
} }
} }
$pdo->commit(); $pdo->commit();
if ($id) {
$message = '<div class="alert alert-success">User updated successfully!</div>'; $message = '<div class="alert alert-success">User updated successfully!</div>';
} else {
header("Location: user_edit.php?id=$user_id&success=1");
exit;
}
// Refresh user data // Refresh user data
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC); $user = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (Exception $e) { } catch (Exception $e) {
$pdo->rollBack(); $pdo->rollBack();
$message = '<div class="alert alert-danger">Error updating user: ' . $e->getMessage() . '</div>'; $message = '<div class="alert alert-danger">Error saving user: ' . $e->getMessage() . '</div>';
} }
} }
} }
if (isset($_GET['success'])) {
$message = '<div class="alert alert-success">User created successfully!</div>';
}
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll(); $groups = $pdo->query("SELECT * FROM user_groups ORDER BY name")->fetchAll();
$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll(); $all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
$assigned_outlet_ids = [];
if ($id) {
$user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?"); $user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
$user_outlets->execute([$id]); $user_outlets->execute([$id]);
$assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN); $assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN);
}
include 'includes/header.php'; include 'includes/header.php';
?> ?>
@ -117,7 +156,7 @@ include 'includes/header.php';
<div class="mb-4"> <div class="mb-4">
<a href="users.php" class="text-decoration-none text-muted small"><i class="bi bi-arrow-left"></i> Back to Users</a> <a href="users.php" class="text-decoration-none text-muted small"><i class="bi bi-arrow-left"></i> Back to Users</a>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-2">
<h2 class="fw-bold mb-0">Edit User: <?= htmlspecialchars($user['username']) ?></h2> <h2 class="fw-bold mb-0"><?= $id ? 'Edit' : 'Add' ?> User<?= $user['username'] ? ': ' . htmlspecialchars($user['username']) : '' ?></h2>
</div> </div>
</div> </div>
@ -146,9 +185,10 @@ include 'includes/header.php';
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-bold text-muted">EMPLOYEE / BIOMETRIC ID</label> <label class="form-label small fw-bold text-muted">EMPLOYEE / BIOMETRIC ID</label>
<input type="text" name="employee_id" class="form-control" value="<?= htmlspecialchars($user['employee_id'] ?? '') ?>" placeholder="e.g. 101"> <input type="text" name="employee_id" class="form-control mb-3" value="<?= htmlspecialchars($user['employee_id'] ?? '') ?>" placeholder="e.g. 101">
<label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label> <label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label>
<select name="group_id" class="form-select" required> <select name="group_id" class="form-select" required>
<option value="">Select Group</option>
<?php foreach ($groups as $group): ?> <?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>" <?= $user['group_id'] == $group['id'] ? 'selected' : '' ?>><?= htmlspecialchars($group['name']) ?></option> <option value="<?= $group['id'] ?>" <?= $user['group_id'] == $group['id'] ? 'selected' : '' ?>><?= htmlspecialchars($group['name']) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
@ -164,7 +204,7 @@ include 'includes/header.php';
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm" style="width: 80px; height: 80px; object-fit: cover;"> <img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm" style="width: 80px; height: 80px; object-fit: cover;">
<?php else: ?> <?php else: ?>
<div class="bg-primary bg-gradient text-white rounded-circle d-flex align-items-center justify-content-center shadow-sm" style="width: 80px; height: 80px; font-weight: 700; font-size: 1.5rem;"> <div class="bg-primary bg-gradient text-white rounded-circle d-flex align-items-center justify-content-center shadow-sm" style="width: 80px; height: 80px; font-weight: 700; font-size: 1.5rem;">
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?> <?= strtoupper(substr($user['full_name'] ?: $user['username'] ?: 'U', 0, 1)) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="flex-grow-1"> <div class="flex-grow-1">
@ -176,8 +216,8 @@ include 'includes/header.php';
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="form-label small fw-bold text-muted">NEW PASSWORD (LEAVE BLANK TO KEEP CURRENT)</label> <label class="form-label small fw-bold text-muted"><?= $id ? 'NEW PASSWORD (LEAVE BLANK TO KEEP CURRENT)' : 'PASSWORD' ?></label>
<input type="password" name="password" class="form-control" placeholder="******"> <input type="password" name="password" class="form-control" placeholder="<?= $id ? '******' : 'Enter password' ?>" <?= $id ? '' : 'required' ?>>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -208,7 +248,7 @@ include 'includes/header.php';
<div class="d-flex justify-content-end gap-2"> <div class="d-flex justify-content-end gap-2">
<a href="users.php" class="btn btn-light rounded-pill px-4">Cancel</a> <a href="users.php" class="btn btn-light rounded-pill px-4">Cancel</a>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold">Update User</button> <button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold"><?= $id ? 'Update' : 'Create' ?> User</button>
</div> </div>
</form> </form>
</div> </div>
@ -224,17 +264,17 @@ include 'includes/header.php';
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm border border-2 border-white me-3" style="width: 60px; height: 60px; object-fit: cover;"> <img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm border border-2 border-white me-3" style="width: 60px; height: 60px; object-fit: cover;">
<?php else: ?> <?php else: ?>
<div class="bg-white text-primary rounded-circle d-flex align-items-center justify-content-center me-3" style="width:60px;height:60px; font-weight:700; font-size:1.5rem;"> <div class="bg-white text-primary rounded-circle d-flex align-items-center justify-content-center me-3" style="width:60px;height:60px; font-weight:700; font-size:1.5rem;">
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?> <?= strtoupper(substr($user['full_name'] ?: $user['username'] ?: 'U', 0, 1)) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div> <div>
<div class="fw-bold"><?= htmlspecialchars($user['full_name']) ?></div> <div class="fw-bold"><?= htmlspecialchars($user['full_name'] ?: 'New User') ?></div>
<div class="small opacity-75">Member since <?= date('M Y', strtotime($user['created_at'])) ?></div> <div class="small opacity-75">Member since <?= date('M Y', strtotime($user['created_at'])) ?></div>
</div> </div>
</div> </div>
<div class="small"> <div class="small">
<div class="mb-1"><i class="bi bi-envelope me-2"></i> <?= htmlspecialchars($user['email']) ?></div> <div class="mb-1"><i class="bi bi-envelope me-2"></i> <?= htmlspecialchars($user['email'] ?: 'Email not set') ?></div>
<div><i class="bi bi-person-badge me-2"></i> User ID: #<?= $user['id'] ?></div> <div><i class="bi bi-person-badge me-2"></i> User ID: <?= $id ? '#' . $id : '<i>New</i>' ?></div>
</div> </div>
<hr class="opacity-25 my-3"> <hr class="opacity-25 my-3">

View File

@ -0,0 +1,2 @@
-- Add cost_price to products table
ALTER TABLE products ADD COLUMN cost_price DECIMAL(10, 2) DEFAULT 0.00;