443 lines
19 KiB
PHP
443 lines
19 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../db/config.php';
|
|
require_once __DIR__ . '/../includes/functions.php';
|
|
|
|
// Ensure user is logged in first
|
|
require_login();
|
|
|
|
$pdo = db();
|
|
require_permission('dashboard_view');
|
|
|
|
// Check if user should see the detailed dashboard or the simplified one
|
|
// We'll use 'dashboard_add' as a proxy for 'detailed' access, or Super Admin (all)
|
|
$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'");
|
|
$stmt->execute([$today]);
|
|
$revenueToday = $stmt->fetchColumn() ?: 0;
|
|
|
|
// Total Orders Today
|
|
$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
|
|
FROM orders o ORDER BY created_at DESC LIMIT 5")->fetchAll();
|
|
}
|
|
|
|
include 'includes/header.php';
|
|
?>
|
|
|
|
<?php if ($isDetailed): ?>
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 class="fw-bold mb-1">Dashboard</h2>
|
|
<p class="text-muted">Welcome back, <?= htmlspecialchars($userName) ?>!</p>
|
|
</div>
|
|
<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>
|
|
</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 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">
|
|
<div class="d-flex align-items-center">
|
|
<div class="icon-box bg-primary bg-opacity-10 text-primary me-3">
|
|
<i class="bi bi-receipt"></i>
|
|
</div>
|
|
<div>
|
|
<h6 class="text-muted mb-0">Orders Today</h6>
|
|
<h3 class="fw-bold mb-0"><?= $ordersToday ?></h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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-calculator"></i>
|
|
</div>
|
|
<div>
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<!-- 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($topItems)): ?>
|
|
<li class="list-group-item text-center py-4 text-muted">No sales data yet.</li>
|
|
<?php endif; ?>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</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">
|
|
<div class="mb-4">
|
|
<?php if ($logoUrl): ?>
|
|
<img src="../<?= htmlspecialchars($logoUrl) ?>" alt="<?= htmlspecialchars($companyName) ?>" style="max-height: 120px; max-width: 100%; filter: drop-shadow(0 10px 15px rgba(0,0,0,0.1));">
|
|
<?php else: ?>
|
|
<div class="bg-primary bg-opacity-10 text-primary p-4 rounded-circle mb-3 shadow-sm" style="width: 120px; height: 120px; display: flex; align-items: center; justify-content: center;">
|
|
<i class="bi bi-shop fs-1"></i>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<h1 class="fw-bold text-center mb-2"><?= htmlspecialchars($companyName) ?></h1>
|
|
<p class="text-muted text-center fs-5 mb-4">Welcome to the Admin Panel, <?= htmlspecialchars($userName) ?>!</p>
|
|
|
|
<div class="d-flex gap-3 mt-4">
|
|
<?php if (has_permission('pos_view')): ?>
|
|
<a href="../pos.php" class="btn btn-primary btn-lg rounded-pill px-5 shadow-sm">
|
|
<i class="bi bi-display me-2"></i> POS Terminal
|
|
</a>
|
|
<?php endif; ?>
|
|
<?php if (has_permission('kitchen_view')): ?>
|
|
<a href="../kitchen.php" class="btn btn-outline-primary btn-lg rounded-pill px-5">
|
|
<i class="bi bi-fire me-2"></i> Kitchen View
|
|
</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|