38682-vm/admin/index.php
2026-02-25 17:24:28 +00:00

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'; ?>