mirror of
https://github.com/myronblair/tomsjavajive-app
synced 2026-06-30 17:50:56 -05:00
v1.0.0 - Initial backup
This commit is contained in:
@@ -0,0 +1,714 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Advanced Analytics Dashboard
|
||||
*/
|
||||
|
||||
$pageTitle = 'Advanced Analytics';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
|
||||
// Get date range from query params or default to last 30 days
|
||||
$endDate = $_GET['end_date'] ?? date('Y-m-d');
|
||||
$startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
|
||||
$period = $_GET['period'] ?? '30';
|
||||
|
||||
if ($period === '7') {
|
||||
$startDate = date('Y-m-d', strtotime('-7 days'));
|
||||
} elseif ($period === '30') {
|
||||
$startDate = date('Y-m-d', strtotime('-30 days'));
|
||||
} elseif ($period === '90') {
|
||||
$startDate = date('Y-m-d', strtotime('-90 days'));
|
||||
} elseif ($period === '365') {
|
||||
$startDate = date('Y-m-d', strtotime('-1 year'));
|
||||
}
|
||||
|
||||
try {
|
||||
// Sales Overview
|
||||
$salesOverview = db()->fetch(
|
||||
"SELECT
|
||||
COUNT(*) as total_orders,
|
||||
COALESCE(SUM(total), 0) as total_revenue,
|
||||
COALESCE(AVG(total), 0) as avg_order_value,
|
||||
COUNT(DISTINCT customer_id) as unique_customers
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
|
||||
// Ensure defaults
|
||||
if (!$salesOverview) {
|
||||
$salesOverview = ['total_orders' => 0, 'total_revenue' => 0, 'avg_order_value' => 0, 'unique_customers' => 0];
|
||||
} else {
|
||||
$salesOverview['total_orders'] = (int)($salesOverview['total_orders'] ?? 0);
|
||||
$salesOverview['total_revenue'] = (float)($salesOverview['total_revenue'] ?? 0);
|
||||
$salesOverview['avg_order_value'] = (float)($salesOverview['avg_order_value'] ?? 0);
|
||||
$salesOverview['unique_customers'] = (int)($salesOverview['unique_customers'] ?? 0);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$salesOverview = ['total_orders' => 0, 'total_revenue' => 0, 'avg_order_value' => 0, 'unique_customers' => 0];
|
||||
}
|
||||
|
||||
// Previous period for comparison
|
||||
$daysDiff = (strtotime($endDate) - strtotime($startDate)) / 86400;
|
||||
$prevEndDate = date('Y-m-d', strtotime($startDate . ' -1 day'));
|
||||
$prevStartDate = date('Y-m-d', strtotime($prevEndDate . " -{$daysDiff} days"));
|
||||
|
||||
try {
|
||||
$prevSalesOverview = db()->fetch(
|
||||
"SELECT
|
||||
COUNT(*) as total_orders,
|
||||
COALESCE(SUM(total), 0) as total_revenue
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'",
|
||||
['start' => $prevStartDate, 'end' => $prevEndDate]
|
||||
);
|
||||
|
||||
if (!$prevSalesOverview) {
|
||||
$prevSalesOverview = ['total_orders' => 0, 'total_revenue' => 0];
|
||||
} else {
|
||||
$prevSalesOverview['total_orders'] = (int)($prevSalesOverview['total_orders'] ?? 0);
|
||||
$prevSalesOverview['total_revenue'] = (float)($prevSalesOverview['total_revenue'] ?? 0);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$prevSalesOverview = ['total_orders' => 0, 'total_revenue' => 0];
|
||||
}
|
||||
|
||||
try {
|
||||
// Daily Sales Data for chart
|
||||
$dailySales = db()->fetchAll(
|
||||
"SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as orders,
|
||||
COALESCE(SUM(total), 0) as revenue
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$dailySales = [];
|
||||
}
|
||||
|
||||
// Top Selling Products (from orders JSON items field)
|
||||
$topProducts = [];
|
||||
try {
|
||||
$orders = db()->fetchAll(
|
||||
"SELECT items, total FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
|
||||
$productCounts = [];
|
||||
foreach ($orders as $order) {
|
||||
$items = json_decode($order['items'], true) ?? [];
|
||||
foreach ($items as $item) {
|
||||
$name = $item['name'] ?? 'Unknown';
|
||||
if (!isset($productCounts[$name])) {
|
||||
$productCounts[$name] = ['name' => $name, 'total_sold' => 0, 'total_revenue' => 0];
|
||||
}
|
||||
$productCounts[$name]['total_sold'] += $item['quantity'] ?? 1;
|
||||
$productCounts[$name]['total_revenue'] += $item['total'] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
usort($productCounts, fn($a, $b) => $b['total_sold'] - $a['total_sold']);
|
||||
$topProducts = array_slice(array_values($productCounts), 0, 10);
|
||||
} catch (Exception $e) {
|
||||
$topProducts = [];
|
||||
}
|
||||
|
||||
// Sales by Category (from orders JSON - more reliable)
|
||||
$categoryStats = [];
|
||||
try {
|
||||
foreach ($orders ?? [] as $order) {
|
||||
$items = json_decode($order['items'], true) ?? [];
|
||||
foreach ($items as $item) {
|
||||
$cat = 'General';
|
||||
if (!isset($categoryStats[$cat])) {
|
||||
$categoryStats[$cat] = ['category' => $cat, 'orders' => 0, 'items_sold' => 0, 'revenue' => 0];
|
||||
}
|
||||
$categoryStats[$cat]['items_sold'] += $item['quantity'] ?? 1;
|
||||
$categoryStats[$cat]['revenue'] += $item['total'] ?? 0;
|
||||
}
|
||||
}
|
||||
$categoryStats = array_values($categoryStats);
|
||||
} catch (Exception $e) {
|
||||
$categoryStats = [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Customer Acquisition
|
||||
$newCustomers = db()->fetch(
|
||||
"SELECT COUNT(*) as count FROM customers WHERE DATE(created_at) BETWEEN :start AND :end",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
)['count'] ?? 0;
|
||||
} catch (Exception $e) {
|
||||
$newCustomers = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$returningCustomers = db()->fetch(
|
||||
"SELECT COUNT(DISTINCT customer_id) as count
|
||||
FROM orders o
|
||||
WHERE DATE(o.created_at) BETWEEN :start AND :end
|
||||
AND payment_status = 'paid'
|
||||
AND customer_id IN (
|
||||
SELECT customer_id FROM orders
|
||||
WHERE DATE(created_at) < :start2 AND payment_status = 'paid'
|
||||
)",
|
||||
['start' => $startDate, 'end' => $endDate, 'start2' => $startDate]
|
||||
)['count'] ?? 0;
|
||||
} catch (Exception $e) {
|
||||
$returningCustomers = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
// Payment Methods Distribution
|
||||
$paymentMethods = db()->fetchAll(
|
||||
"SELECT
|
||||
COALESCE(payment_method, 'Unknown') as method,
|
||||
COUNT(*) as count,
|
||||
SUM(total) as revenue
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'
|
||||
GROUP BY payment_method
|
||||
ORDER BY count DESC",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$paymentMethods = [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Abandoned Carts
|
||||
$abandonedCarts = db()->fetch(
|
||||
"SELECT COUNT(*) as count, COALESCE(SUM(subtotal), 0) as value
|
||||
FROM abandoned_carts
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND recovered = 0",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
|
||||
if (!$abandonedCarts) {
|
||||
$abandonedCarts = ['count' => 0, 'value' => 0];
|
||||
} else {
|
||||
$abandonedCarts['count'] = (int)($abandonedCarts['count'] ?? 0);
|
||||
$abandonedCarts['value'] = (float)($abandonedCarts['value'] ?? 0);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$abandonedCarts = ['count' => 0, 'value' => 0];
|
||||
}
|
||||
|
||||
try {
|
||||
// Order Status Distribution
|
||||
$orderStatuses = db()->fetchAll(
|
||||
"SELECT order_status, COUNT(*) as count
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end
|
||||
GROUP BY order_status",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$orderStatuses = [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Hourly Sales Pattern
|
||||
$hourlySales = db()->fetchAll(
|
||||
"SELECT
|
||||
HOUR(created_at) as hour,
|
||||
COUNT(*) as orders,
|
||||
COALESCE(SUM(total), 0) as revenue
|
||||
FROM orders
|
||||
WHERE DATE(created_at) BETWEEN :start AND :end AND payment_status = 'paid'
|
||||
GROUP BY HOUR(created_at)
|
||||
ORDER BY hour",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$hourlySales = [];
|
||||
}
|
||||
|
||||
// Top Customers
|
||||
try {
|
||||
$topCustomers = db()->fetchAll(
|
||||
"SELECT
|
||||
c.customer_id,
|
||||
c.name,
|
||||
c.email,
|
||||
COUNT(o.order_id) as order_count,
|
||||
COALESCE(SUM(o.total), 0) as total_spent
|
||||
FROM customers c
|
||||
JOIN orders o ON c.customer_id = o.customer_id
|
||||
WHERE DATE(o.created_at) BETWEEN :start AND :end AND o.payment_status = 'paid'
|
||||
GROUP BY c.customer_id
|
||||
ORDER BY total_spent DESC
|
||||
LIMIT 10",
|
||||
['start' => $startDate, 'end' => $endDate]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$topCustomers = [];
|
||||
}
|
||||
|
||||
// Inventory Stats
|
||||
try {
|
||||
$lowStockCount = db()->count('products', 'stock <= low_stock_threshold AND stock > 0');
|
||||
$outOfStockCount = db()->count('products', 'stock = 0 AND is_active = 1');
|
||||
} catch (Exception $e) {
|
||||
$lowStockCount = 0;
|
||||
$outOfStockCount = 0;
|
||||
}
|
||||
|
||||
// Calculate percentage changes
|
||||
$revenueChange = $prevSalesOverview['total_revenue'] > 0
|
||||
? (($salesOverview['total_revenue'] - $prevSalesOverview['total_revenue']) / $prevSalesOverview['total_revenue']) * 100
|
||||
: 0;
|
||||
$ordersChange = $prevSalesOverview['total_orders'] > 0
|
||||
? (($salesOverview['total_orders'] - $prevSalesOverview['total_orders']) / $prevSalesOverview['total_orders']) * 100
|
||||
: 0;
|
||||
?>
|
||||
|
||||
<style>
|
||||
.analytics-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.date-filter {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-filter .btn {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.date-filter .btn.active {
|
||||
background: var(--admin-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--admin-surface);
|
||||
border-radius: var(--admin-radius);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-card-title {
|
||||
color: var(--admin-text-muted);
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stat-card-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.stat-card-icon.primary {
|
||||
background: rgba(255, 94, 26, 0.1);
|
||||
color: var(--admin-primary);
|
||||
}
|
||||
|
||||
.stat-card-icon.success {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: var(--admin-success);
|
||||
}
|
||||
|
||||
.stat-card-icon.warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--admin-warning);
|
||||
}
|
||||
|
||||
.stat-card-icon.info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.stat-card-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-card-change {
|
||||
font-size: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-card-change.positive {
|
||||
color: var(--admin-success);
|
||||
}
|
||||
|
||||
.stat-card-change.negative {
|
||||
color: var(--admin-error);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: var(--admin-surface);
|
||||
border-radius: var(--admin-radius);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.analytics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.analytics-grid-equal {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
background: var(--admin-bg);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: var(--admin-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mini-chart {
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.mini-chart-bar {
|
||||
flex: 1;
|
||||
background: rgba(255, 94, 26, 0.3);
|
||||
border-radius: 4px 4px 0 0;
|
||||
min-height: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.mini-chart-bar:hover {
|
||||
background: var(--admin-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.analytics-grid,
|
||||
.analytics-grid-equal {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="analytics-header">
|
||||
<div>
|
||||
<h1 class="page-title">Advanced Analytics</h1>
|
||||
<p class="text-muted"><?= date('M d, Y', strtotime($startDate)) ?> - <?= date('M d, Y', strtotime($endDate)) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="date-filter">
|
||||
<a href="?period=7" class="btn <?= $period === '7' ? 'btn-primary active' : 'btn-secondary' ?>">7 Days</a>
|
||||
<a href="?period=30" class="btn <?= $period === '30' ? 'btn-primary active' : 'btn-secondary' ?>">30 Days</a>
|
||||
<a href="?period=90" class="btn <?= $period === '90' ? 'btn-primary active' : 'btn-secondary' ?>">90 Days</a>
|
||||
<a href="?period=365" class="btn <?= $period === '365' ? 'btn-primary active' : 'btn-secondary' ?>">1 Year</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overview Stats -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<h3 class="stat-card-title">Total Revenue</h3>
|
||||
<div class="stat-card-icon primary"><i class="fas fa-dollar-sign"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= formatCurrency($salesOverview['total_revenue'] ?? 0) ?></div>
|
||||
<div class="stat-card-change <?= $revenueChange >= 0 ? 'positive' : 'negative' ?>">
|
||||
<i class="fas fa-<?= $revenueChange >= 0 ? 'arrow-up' : 'arrow-down' ?>"></i>
|
||||
<?= abs(round($revenueChange, 1)) ?>% vs previous period
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<h3 class="stat-card-title">Total Orders</h3>
|
||||
<div class="stat-card-icon success"><i class="fas fa-shopping-bag"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= number_format($salesOverview['total_orders'] ?? 0) ?></div>
|
||||
<div class="stat-card-change <?= $ordersChange >= 0 ? 'positive' : 'negative' ?>">
|
||||
<i class="fas fa-<?= $ordersChange >= 0 ? 'arrow-up' : 'arrow-down' ?>"></i>
|
||||
<?= abs(round($ordersChange, 1)) ?>% vs previous period
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<h3 class="stat-card-title">Average Order Value</h3>
|
||||
<div class="stat-card-icon warning"><i class="fas fa-receipt"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= formatCurrency($salesOverview['avg_order_value'] ?? 0) ?></div>
|
||||
<div class="stat-card-change" style="color: var(--admin-text-muted);">
|
||||
<?= $salesOverview['unique_customers'] ?? 0 ?> unique customers
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<h3 class="stat-card-title">Abandoned Carts</h3>
|
||||
<div class="stat-card-icon info"><i class="fas fa-cart-arrow-down"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= number_format($abandonedCarts['count'] ?? 0) ?></div>
|
||||
<div class="stat-card-change" style="color: var(--admin-warning);">
|
||||
<?= formatCurrency($abandonedCarts['value'] ?? 0) ?> potential revenue
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revenue Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Revenue Trend</h3>
|
||||
</div>
|
||||
<div class="mini-chart" id="revenueChart">
|
||||
<?php
|
||||
$maxRevenue = max(array_column($dailySales, 'revenue') ?: [1]);
|
||||
foreach ($dailySales as $day):
|
||||
$height = $maxRevenue > 0 ? ($day['revenue'] / $maxRevenue) * 100 : 0;
|
||||
?>
|
||||
<div class="mini-chart-bar"
|
||||
style="height: <?= max(4, $height) ?>%;"
|
||||
title="<?= date('M d', strtotime($day['date'])) ?>: <?= formatCurrency($day['revenue']) ?>"></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($dailySales)): ?>
|
||||
<div style="width: 100%; text-align: center; padding: 2rem; color: var(--admin-text-muted);">
|
||||
No sales data for this period
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analytics-grid">
|
||||
<!-- Top Products -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Top Selling Products</h3>
|
||||
</div>
|
||||
<?php if (empty($topProducts)): ?>
|
||||
<p class="text-muted text-center">No product data available</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$maxSold = max(array_column($topProducts, 'total_sold') ?: [1]);
|
||||
foreach ($topProducts as $i => $product):
|
||||
?>
|
||||
<div class="list-item">
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.25rem;">
|
||||
<span style="font-weight: 500;"><?= $i + 1 ?>.</span>
|
||||
<span style="font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<?= htmlspecialchars(truncate($product['name'], 30)) ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="progress-bar" style="width: 200px;">
|
||||
<div class="progress-bar-fill" style="width: <?= ($product['total_sold'] / $maxSold) * 100 ?>%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 600;"><?= number_format($product['total_sold']) ?> sold</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;"><?= formatCurrency($product['total_revenue']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Sales by Category -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Sales by Category</h3>
|
||||
</div>
|
||||
<?php if (empty($categoryStats)): ?>
|
||||
<p class="text-muted text-center">No category data available</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$totalCatRevenue = array_sum(array_column($categoryStats, 'revenue')) ?: 1;
|
||||
$colors = ['#FF5E1A', '#10B981', '#F59E0B', '#3B82F6', '#8B5CF6', '#EC4899'];
|
||||
foreach ($categoryStats as $i => $cat):
|
||||
$percentage = ($cat['revenue'] / $totalCatRevenue) * 100;
|
||||
?>
|
||||
<div class="list-item">
|
||||
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
||||
<div style="width: 12px; height: 12px; border-radius: 3px; background: <?= $colors[$i % count($colors)] ?>;"></div>
|
||||
<span style="font-weight: 500;"><?= htmlspecialchars($cat['category']) ?></span>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 600;"><?= formatCurrency($cat['revenue']) ?></div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;"><?= round($percentage, 1) ?>%</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analytics-grid-equal">
|
||||
<!-- Customer Insights -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Customer Insights</h3>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div style="text-align: center; padding: 1.5rem; background: var(--admin-bg); border-radius: var(--admin-radius);">
|
||||
<div style="font-size: 2rem; font-weight: 700; color: var(--admin-success);"><?= $newCustomers ?></div>
|
||||
<div class="text-muted" style="font-size: 0.875rem;">New Customers</div>
|
||||
</div>
|
||||
<div style="text-align: center; padding: 1.5rem; background: var(--admin-bg); border-radius: var(--admin-radius);">
|
||||
<div style="font-size: 2rem; font-weight: 700; color: var(--admin-primary);"><?= $returningCustomers ?></div>
|
||||
<div class="text-muted" style="font-size: 0.875rem;">Returning Customers</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="margin: 0 0 0.75rem; font-size: 0.875rem;">Top Customers</h4>
|
||||
<?php foreach (array_slice($topCustomers, 0, 5) as $customer): ?>
|
||||
<div class="list-item" style="padding: 0.5rem 0;">
|
||||
<div>
|
||||
<div style="font-weight: 500;"><?= htmlspecialchars($customer['name'] ?? $customer['email']) ?></div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;"><?= $customer['order_count'] ?> orders</div>
|
||||
</div>
|
||||
<div style="font-weight: 600; color: var(--admin-success);"><?= formatCurrency($customer['total_spent']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Payment & Inventory -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Payment Methods</h3>
|
||||
</div>
|
||||
<?php if (empty($paymentMethods)): ?>
|
||||
<p class="text-muted text-center">No payment data available</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$totalPayments = array_sum(array_column($paymentMethods, 'count')) ?: 1;
|
||||
foreach ($paymentMethods as $method):
|
||||
?>
|
||||
<div class="list-item" style="padding: 0.5rem 0;">
|
||||
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
||||
<i class="fas fa-<?= match($method['method']) {
|
||||
'card', 'stripe' => 'credit-card',
|
||||
'cash' => 'money-bill',
|
||||
'wallet' => 'wallet',
|
||||
default => 'money-check'
|
||||
} ?>" style="color: var(--admin-text-muted);"></i>
|
||||
<span style="font-weight: 500;"><?= ucfirst($method['method']) ?></span>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-weight: 600;"><?= round(($method['count'] / $totalPayments) * 100, 1) ?>%</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;"><?= formatCurrency($method['revenue']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<h4 style="margin: 1.5rem 0 0.75rem; font-size: 0.875rem;">Inventory Alerts</h4>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
|
||||
<div style="padding: 1rem; background: rgba(245, 158, 11, 0.1); border-radius: var(--admin-radius); text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 700; color: var(--admin-warning);"><?= $lowStockCount ?></div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">Low Stock</div>
|
||||
</div>
|
||||
<div style="padding: 1rem; background: rgba(239, 68, 68, 0.1); border-radius: var(--admin-radius); text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 700; color: var(--admin-error);"><?= $outOfStockCount ?></div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">Out of Stock</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hourly Sales Pattern -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Sales by Hour of Day</h3>
|
||||
</div>
|
||||
<div class="mini-chart" style="height: 120px;">
|
||||
<?php
|
||||
// Fill in missing hours
|
||||
$hourlyData = array_fill(0, 24, 0);
|
||||
foreach ($hourlySales as $h) {
|
||||
$hourlyData[$h['hour']] = $h['orders'];
|
||||
}
|
||||
$maxHourly = max($hourlyData) ?: 1;
|
||||
|
||||
for ($h = 0; $h < 24; $h++):
|
||||
$height = ($hourlyData[$h] / $maxHourly) * 100;
|
||||
$label = $h === 0 ? '12am' : ($h < 12 ? "{$h}am" : ($h === 12 ? '12pm' : ($h - 12) . 'pm'));
|
||||
?>
|
||||
<div class="mini-chart-bar"
|
||||
style="height: <?= max(4, $height) ?>%;"
|
||||
title="<?= $label ?>: <?= $hourlyData[$h] ?> orders"></div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.7rem; color: var(--admin-text-muted);">
|
||||
<span>12am</span>
|
||||
<span>6am</span>
|
||||
<span>12pm</span>
|
||||
<span>6pm</span>
|
||||
<span>12am</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
Reference in New Issue
Block a user