209 lines
7.6 KiB
PHP
209 lines
7.6 KiB
PHP
<?php
|
|
require_once __DIR__ . '/includes/auth.php';
|
|
require_login();
|
|
|
|
require_once __DIR__ . '/db/config.php';
|
|
|
|
// Run migrations on first load
|
|
if (session_status() == PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
if (!isset($_SESSION['migrated'])) {
|
|
run_migrations();
|
|
$_SESSION['migrated'] = true;
|
|
}
|
|
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
// Fetch some stats for the dashboard
|
|
$total_products = db()->query('SELECT COUNT(*) FROM products')->fetchColumn();
|
|
|
|
// Today's Sales
|
|
$today = date('Y-m-d');
|
|
$stmt_today = db()->prepare("SELECT COUNT(*) as num_transactions, SUM(total_amount) as total_sales FROM sales WHERE DATE(sale_date) = ?");
|
|
$stmt_today->execute([$today]);
|
|
$today_sales = $stmt_today->fetch(PDO::FETCH_ASSOC);
|
|
|
|
// All-Time Sales
|
|
$stmt_all_time = db()->query("SELECT COUNT(*) as num_transactions, SUM(total_amount) as total_sales FROM sales");
|
|
$all_time_sales = $stmt_all_time->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
|
// Low Stock
|
|
$low_stock_threshold = 5;
|
|
$stmt_low_stock = db()->prepare('SELECT COUNT(*) FROM products WHERE stock <= ?');
|
|
$stmt_low_stock->execute([$low_stock_threshold]);
|
|
$low_stock_count = $stmt_low_stock->fetchColumn();
|
|
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Dashboard</h1>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-primary mb-3">
|
|
<div class="card-header">Total Products</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo $total_products; ?></h5>
|
|
<p class="card-text">items in inventory.</p>
|
|
<a href="products.php" class="text-white">View details →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-success mb-3">
|
|
<div class="card-header">Today's Sales</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title">Rp <?php echo number_format($today_sales['total_sales'] ?? 0, 2); ?></h5>
|
|
<p class="card-text">from <?php echo $today_sales['num_transactions'] ?? 0; ?> transactions.</p>
|
|
<a href="reports.php?start_date=<?php echo $today; ?>&end_date=<?php echo $today; ?>" class="text-white">View details →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-info mb-3">
|
|
<div class="card-header">All-Time Sales</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title">Rp <?php echo number_format($all_time_sales['total_sales'] ?? 0, 2); ?></h5>
|
|
<p class="card-text">from <?php echo $all_time_sales['num_transactions'] ?? 0; ?> transactions.</p>
|
|
<a href="reports.php" class="text-white">View details →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white bg-warning mb-3">
|
|
<div class="card-header">Low Stock Alerts</div>
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo $low_stock_count; ?></h5>
|
|
<p class="card-text">items need restocking.</p>
|
|
<a href="products.php?filter_low_stock=1" class="text-white">View details →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
Sales Trend (Last 7 Days)
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="salesChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
Best-Selling Products
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="bestSellingChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Sales Trend Chart
|
|
fetch('_get_sales_data.php')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const salesCtx = document.getElementById('salesChart').getContext('2d');
|
|
new Chart(salesCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels,
|
|
datasets: [{
|
|
label: 'Total Sales (Rp)',
|
|
data: data.data,
|
|
backgroundColor: 'rgba(13, 110, 253, 0.2)',
|
|
borderColor: 'rgba(13, 110, 253, 1)',
|
|
borderWidth: 1,
|
|
fill: true,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
callback: function(value) {
|
|
return 'Rp ' + value.toLocaleString();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
let label = context.dataset.label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if (context.parsed.y !== null) {
|
|
label += 'Rp ' + context.parsed.y.toLocaleString();
|
|
}
|
|
return label;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Best-Selling Products Chart
|
|
fetch('_get_best_selling_products.php')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const bestSellingCtx = document.getElementById('bestSellingChart').getContext('2d');
|
|
new Chart(bestSellingCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.map(p => p.name),
|
|
datasets: [{
|
|
label: 'Quantity Sold',
|
|
data: data.map(p => p.total_quantity),
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.5)',
|
|
'rgba(54, 162, 235, 0.5)',
|
|
'rgba(255, 206, 86, 0.5)',
|
|
'rgba(75, 192, 192, 0.5)',
|
|
'rgba(153, 102, 255, 0.5)'
|
|
],
|
|
borderColor: [
|
|
'rgba(255, 99, 132, 1)',
|
|
'rgba(54, 162, 235, 1)',
|
|
'rgba(255, 206, 86, 1)',
|
|
'rgba(75, 192, 192, 1)',
|
|
'rgba(153, 102, 255, 1)'
|
|
],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'y',
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|