Autosave: 20260223-150636
This commit is contained in:
parent
b98ef1276a
commit
4bd6115a47
@ -472,15 +472,15 @@ function can_view($module) {
|
||||
<?php if (can_view('user_groups')): ?>
|
||||
<li class="nav-item">
|
||||
<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">
|
||||
<a class="nav-link <?= isActive('attendance.php') ?>" href="attendance.php">
|
||||
<i class="bi bi-calendar-check me-2"></i> Attendance
|
||||
</a>
|
||||
</li>
|
||||
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
405
admin/index.php
405
admin/index.php
@ -12,6 +12,7 @@ $isDetailed = has_permission('dashboard_add') || has_permission('all');
|
||||
if ($isDetailed) {
|
||||
// Fetch Dashboard Stats
|
||||
$today = date('Y-m-d');
|
||||
$thisMonth = date('Y-m');
|
||||
|
||||
// Total Revenue Today
|
||||
$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;
|
||||
|
||||
// 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]);
|
||||
$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
|
||||
$outletsCount = $pdo->query("SELECT COUNT(*) FROM outlets")->fetchColumn();
|
||||
|
||||
// Total Products
|
||||
$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
|
||||
$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
|
||||
@ -44,29 +104,49 @@ include 'includes/header.php';
|
||||
<h2 class="fw-bold mb-1">Dashboard</h2>
|
||||
<p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p>
|
||||
</div>
|
||||
<?php if (has_permission('orders_add')): ?>
|
||||
<div>
|
||||
<a href="../pos.php" class="btn btn-primary"><i class="bi bi-plus-lg me-1"></i> New Order</a>
|
||||
<div class="d-flex gap-2">
|
||||
<?php if (has_permission('orders_view')): ?>
|
||||
<a href="reports.php" class="btn btn-outline-primary"><i class="bi bi-file-earmark-bar-graph me-1"></i> Reports</a>
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Revenue Card -->
|
||||
<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-success bg-opacity-10 text-success me-3">
|
||||
<i class="bi bi-currency-dollar"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Today's Revenue</h6>
|
||||
<h3 class="fw-bold mb-0"><?= format_currency($revenueToday) ?></h3>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-box bg-success bg-opacity-10 text-success me-3">
|
||||
<i class="bi bi-currency-dollar"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Today's Revenue</h6>
|
||||
<h3 class="fw-bold mb-0"><?= format_currency($revenueToday) ?></h3>
|
||||
</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 -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card h-100 p-3">
|
||||
@ -82,100 +162,251 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outlets Card -->
|
||||
<!-- Average Order Value -->
|
||||
<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-warning bg-opacity-10 text-warning me-3">
|
||||
<i class="bi bi-shop"></i>
|
||||
<i class="bi bi-calculator"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Active Outlets</h6>
|
||||
<h3 class="fw-bold mb-0"><?= $outletsCount ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Card -->
|
||||
<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-box-seam"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Total Products</h6>
|
||||
<h3 class="fw-bold mb-0"><?= $productsCount ?></h3>
|
||||
<h6 class="text-muted mb-0">Avg. Order Value</h6>
|
||||
<h3 class="fw-bold mb-0"><?= format_currency($avgOrderValue) ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Orders Table -->
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<h5 class="mb-0 fw-bold">Recent Orders</h5>
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Sales Trend Graph -->
|
||||
<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>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Type</th>
|
||||
<th>Table/Customer</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<tr>
|
||||
<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>
|
||||
<span class="status-badge status-<?= $order['status'] ?> badge rounded-pill">
|
||||
<?= ucfirst($order['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('M d, H:i', strtotime($order['created_at'])) ?></td>
|
||||
</tr>
|
||||
|
||||
<!-- 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">
|
||||
<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>
|
||||
<span class="fw-medium text-dark"><?= htmlspecialchars($item['name']) ?></span>
|
||||
</div>
|
||||
<span class="badge bg-light text-dark border fw-bold"><?= $item['total_qty'] ?> Sold</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted">No recent orders found.</td></tr>
|
||||
<?php if (empty($topItems)): ?>
|
||||
<li class="list-group-item text-center py-4 text-muted">No sales data yet.</li>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<!-- Recent Orders Table -->
|
||||
<div class="col-lg-7">
|
||||
<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>
|
||||
<?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 class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">#<?= $order['id'] ?></td>
|
||||
<td class="fw-bold"><?= format_currency($order['total_amount']) ?></td>
|
||||
<td>
|
||||
<span class="status-badge status-<?= $order['status'] ?> badge rounded-pill">
|
||||
<?= ucfirst($order['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted small"><?= date('M d, H:i', strtotime($order['created_at'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($recentOrders)): ?>
|
||||
<tr><td colspan="4" class="text-center py-4 text-muted">No recent orders found.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</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; ?>
|
||||
|
||||
// 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: ?>
|
||||
<!-- Simplified Dashboard -->
|
||||
<div class="d-flex flex-column align-items-center justify-content-center py-5 mt-5">
|
||||
|
||||
@ -25,6 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = $_POST['name'];
|
||||
$category_id = $_POST['category_id'];
|
||||
$price = $_POST['price'];
|
||||
$cost_price = $_POST['cost_price'] ?: 0;
|
||||
$description = $_POST['description'];
|
||||
$image_url = $product['image_url']; // Default to existing
|
||||
|
||||
@ -59,8 +60,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
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 = ?");
|
||||
if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to, $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, $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>';
|
||||
// Refresh product data
|
||||
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
|
||||
@ -91,12 +92,12 @@ include 'includes/header.php';
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<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>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row g-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>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<option value="<?= $cat['id'] ?>" <?= $cat['id'] == $product['category_id'] ? 'selected' : '' ?>>
|
||||
@ -106,10 +107,16 @@ include 'includes/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
</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-body">
|
||||
@ -133,19 +140,19 @@ include 'includes/header.php';
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<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">
|
||||
<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 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/*">
|
||||
<div class="form-text">Leave empty to keep current image.</div>
|
||||
</div>
|
||||
@ -160,4 +167,4 @@ include 'includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -15,6 +15,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
$name = $_POST['name'];
|
||||
$category_id = $_POST['category_id'];
|
||||
$price = $_POST['price'];
|
||||
$cost_price = $_POST['cost_price'] ?: 0;
|
||||
$description = $_POST['description'];
|
||||
|
||||
$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 (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$name, $category_id, $price, $description, $image_url, $promo_discount_percent, $promo_date_from, $promo_date_to])) {
|
||||
$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, $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>';
|
||||
} 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>';
|
||||
@ -158,7 +159,8 @@ include 'includes/header.php';
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Category</th>
|
||||
<th>Price</th>
|
||||
<th>Cost Price</th>
|
||||
<th>Selling Price</th>
|
||||
<th>Promotion</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
@ -186,6 +188,9 @@ include 'includes/header.php';
|
||||
<td>
|
||||
<span class="badge-soft"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-muted"><?= format_currency($product['cost_price'] ?? 0) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($is_promo_active): ?>
|
||||
<?php
|
||||
@ -270,13 +275,22 @@ include 'includes/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<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">
|
||||
<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;">
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -12,11 +12,23 @@ if ($id) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
header('Location: users.php');
|
||||
exit;
|
||||
if (!$user) {
|
||||
header('Location: users.php');
|
||||
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 = '';
|
||||
@ -29,11 +41,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$employee_id = $_POST['employee_id'] ?? null;
|
||||
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||
$assigned_outlets = $_POST['outlets'] ?? [];
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// Check if username changed and if new one exists
|
||||
if ($username !== $user['username']) {
|
||||
// Validation
|
||||
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->execute([$username, $id]);
|
||||
$stmt->execute([$username, (int)$id]);
|
||||
if ($stmt->fetch()) {
|
||||
$message = '<div class="alert alert-danger">Username already taken.</div>';
|
||||
}
|
||||
@ -42,24 +58,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!$message) {
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$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];
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Update password if provided
|
||||
if (!empty($_POST['password'])) {
|
||||
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]);
|
||||
if ($id) {
|
||||
// Update
|
||||
$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];
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
if (!empty($password)) {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$hashed_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
|
||||
$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)) {
|
||||
$stmt_outlet = $pdo->prepare("INSERT INTO user_outlets (user_id, outlet_id) VALUES (?, ?)");
|
||||
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'];
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
$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();
|
||||
$message = '<div class="alert alert-success">User updated successfully!</div>';
|
||||
|
||||
if ($id) {
|
||||
$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
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$stmt->execute([$user_id]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
$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();
|
||||
$all_outlets = $pdo->query("SELECT * FROM outlets ORDER BY name")->fetchAll();
|
||||
$user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
|
||||
$user_outlets->execute([$id]);
|
||||
$assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN);
|
||||
$assigned_outlet_ids = [];
|
||||
if ($id) {
|
||||
$user_outlets = $pdo->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
|
||||
$user_outlets->execute([$id]);
|
||||
$assigned_outlet_ids = $user_outlets->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
@ -117,7 +156,7 @@ include 'includes/header.php';
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@ -146,9 +185,10 @@ include 'includes/header.php';
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<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>
|
||||
<select name="group_id" class="form-select" required>
|
||||
<option value="">Select Group</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>" <?= $user['group_id'] == $group['id'] ? 'selected' : '' ?>><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?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;">
|
||||
<?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;">
|
||||
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||
<?= strtoupper(substr($user['full_name'] ?: $user['username'] ?: 'U', 0, 1)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="flex-grow-1">
|
||||
@ -176,8 +216,8 @@ include 'includes/header.php';
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold text-muted">NEW PASSWORD (LEAVE BLANK TO KEEP CURRENT)</label>
|
||||
<input type="password" name="password" class="form-control" placeholder="******">
|
||||
<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="<?= $id ? '******' : 'Enter password' ?>" <?= $id ? '' : 'required' ?>>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
@ -208,7 +248,7 @@ include 'includes/header.php';
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<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>
|
||||
</form>
|
||||
</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;">
|
||||
<?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;">
|
||||
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||
<?= strtoupper(substr($user['full_name'] ?: $user['username'] ?: 'U', 0, 1)) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<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>
|
||||
</div>
|
||||
<div class="small">
|
||||
<div class="mb-1"><i class="bi bi-envelope me-2"></i> <?= htmlspecialchars($user['email']) ?></div>
|
||||
<div><i class="bi bi-person-badge me-2"></i> User ID: #<?= $user['id'] ?></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: <?= $id ? '#' . $id : '<i>New</i>' ?></div>
|
||||
</div>
|
||||
|
||||
<hr class="opacity-25 my-3">
|
||||
|
||||
2
db/migrations/018_add_cost_price_to_products.sql
Normal file
2
db/migrations/018_add_cost_price_to_products.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add cost_price to products table
|
||||
ALTER TABLE products ADD COLUMN cost_price DECIMAL(10, 2) DEFAULT 0.00;
|
||||
Loading…
x
Reference in New Issue
Block a user