327 lines
18 KiB
PHP
327 lines
18 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/includes/layout.php'; require_role('admin');
|
|
|
|
ensure_schema();
|
|
|
|
$errors = [];
|
|
|
|
$shipments = [];
|
|
try {
|
|
$stmt = db()->query("SELECT * FROM shipments ORDER BY created_at DESC LIMIT 10");
|
|
$shipments = $stmt->fetchAll();
|
|
} catch (Throwable $e) {
|
|
$shipments = [];
|
|
}
|
|
|
|
$stats = [
|
|
'total_shipments' => 0,
|
|
'active_shipments' => 0,
|
|
'total_shippers' => 0,
|
|
'total_truck_owners' => 0,
|
|
'total_revenue' => 0.0,
|
|
];
|
|
|
|
$chartData = [
|
|
'labels' => [],
|
|
'data' => []
|
|
];
|
|
|
|
try {
|
|
$pdo = db();
|
|
$stats['total_shipments'] = (int)$pdo->query("SELECT COUNT(*) FROM shipments")->fetchColumn();
|
|
$stats['active_shipments'] = (int)$pdo->query("SELECT COUNT(*) FROM shipments WHERE status != 'delivered'")->fetchColumn();
|
|
$stats['total_shippers'] = (int)$pdo->query("SELECT COUNT(*) FROM users WHERE role = 'shipper'")->fetchColumn();
|
|
$stats['total_truck_owners'] = (int)$pdo->query("SELECT COUNT(*) FROM users WHERE role = 'truck_owner'")->fetchColumn();
|
|
$stats['total_revenue'] = (float)$pdo->query("SELECT SUM(total_price) FROM shipments WHERE payment_status = 'paid'")->fetchColumn();
|
|
|
|
// Chart Data: Last 30 days
|
|
$stmt = $pdo->query("
|
|
SELECT DATE(created_at) as date, COUNT(*) as count
|
|
FROM shipments
|
|
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
|
GROUP BY DATE(created_at)
|
|
ORDER BY date ASC
|
|
");
|
|
$dailyStats = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
|
|
// Fill in missing days
|
|
$period = new DatePeriod(
|
|
new DateTime('-30 days'),
|
|
new DateInterval('P1D'),
|
|
new DateTime('+1 day')
|
|
);
|
|
|
|
foreach ($period as $date) {
|
|
$d = $date->format('Y-m-d');
|
|
$chartData['labels'][] = $date->format('d M');
|
|
$chartData['data'][] = $dailyStats[$d] ?? 0;
|
|
}
|
|
|
|
} catch (Throwable $e) {
|
|
// Silent fail for stats, defaults are 0
|
|
}
|
|
|
|
$flash = get_flash();
|
|
|
|
render_header(t('admin_dashboard'), 'admin', true);
|
|
?>
|
|
|
|
<div class="row g-0">
|
|
<div class="col-md-2 bg-white border-end min-vh-100">
|
|
<?php render_admin_sidebar('dashboard'); ?>
|
|
</div>
|
|
<div class="col-md-10 p-4">
|
|
<div class="page-intro mb-4">
|
|
<h1 class="section-title mb-1"><?= e(t('admin_dashboard')) ?></h1>
|
|
<p class="muted mb-0"><?= e(t('overview_performance')) ?></p>
|
|
</div>
|
|
|
|
<!-- Stats Row -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-box-seam" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-primary mb-2 position-relative"><i class="bi bi-box-seam fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['total_shipments'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('total_shipments')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-truck" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-warning mb-2 position-relative"><i class="bi bi-truck fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['active_shipments'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('active_shipments')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-people" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-success mb-2 position-relative"><i class="bi bi-people fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['total_shippers'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('total_shippers')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-person-vcard" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-info mb-2 position-relative"><i class="bi bi-person-vcard fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['total_truck_owners'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('total_truck_owners')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-currency-dollar" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-success mb-2 position-relative"><i class="bi bi-currency-dollar fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= format_currency($stats['total_revenue']) ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('total_revenue')) ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<!-- Main Content: Shipments Chart -->
|
|
<div class="col-lg-8">
|
|
<!-- Chart Section -->
|
|
<div class="panel shadow-sm border-0 rounded-4 mb-4">
|
|
<div class="panel-heading">
|
|
<h2 class="h5 mb-0 fw-bold text-white"><i class="bi bi-graph-up text-white-50 me-2"></i><?= e(t('shipments_analytics')) ?></h2>
|
|
</div>
|
|
<div class="p-4">
|
|
<canvas id="shipmentsChart" style="max-height: 300px;"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel shadow-sm border-0 rounded-4 d-flex flex-column">
|
|
<div class="panel-heading d-flex justify-content-between align-items-center">
|
|
<h2 class="h5 mb-0 fw-bold text-white"><i class="bi bi-clock-history text-white-50 me-2"></i><?= e(t('recent_shipments')) ?></h2>
|
|
<a href="<?= e(url_with_lang('admin_shipments.php')) ?>" class="btn btn-sm btn-light text-primary"><?= e(t('view_all')) ?></a>
|
|
</div>
|
|
<div class="p-0 flex-grow-1 d-flex flex-column">
|
|
<?php if ($flash): ?>
|
|
<div class="alert alert-success m-3" data-auto-dismiss="true"><?= e($flash['message']) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($errors): ?>
|
|
<div class="alert alert-warning m-3"><?= e(implode(' ', $errors)) ?></div>
|
|
<?php endif; ?>
|
|
<?php if (!$shipments): ?>
|
|
<div class="text-center p-5 text-muted flex-grow-1 d-flex flex-column justify-content-center">
|
|
<i class="bi bi-inbox fs-1 mb-3 d-block opacity-50"></i>
|
|
<p class="mb-0"><?= e(t('no_shipments')) ?></p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="table-responsive flex-grow-1">
|
|
<table class="table align-middle mb-0 table-hover">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4 text-uppercase small text-muted border-top-0"><?= e(t('shipment')) ?></th>
|
|
<th class="text-uppercase small text-muted border-top-0"><?= e(t('route')) ?></th>
|
|
<th class="text-uppercase small text-muted border-top-0"><?= e(t('status')) ?></th>
|
|
<th class="pe-4 text-uppercase small text-muted border-top-0 text-end"><?= e(t('action')) ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($shipments as $row): ?>
|
|
<tr>
|
|
<td class="ps-4">
|
|
<div class="fw-bold text-dark"><?= e($row['shipper_company']) ?></div>
|
|
<small class="text-muted"><?= e($row['payment_method'] === 'bank_transfer' ? t('payment_bank') : t('payment_thawani')) ?></small>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="fw-medium text-dark"><?= e($row['origin_city']) ?></span>
|
|
<i class="bi bi-arrow-right text-muted small"></i>
|
|
<span class="fw-medium text-dark"><?= e($row['destination_city']) ?></span>
|
|
</div>
|
|
</td>
|
|
<td><span class="badge <?= e($row['status']) ?> rounded-pill px-3 py-2"><?= e(status_label($row['status'])) ?></span></td>
|
|
<td class="text-end pe-4">
|
|
<a href="<?= e(url_with_lang('shipment_detail.php', ['id' => $row['id']])) ?>" class="btn btn-sm btn-light text-primary" title="<?= e(t('view_details')) ?>">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar: Quick Links List -->
|
|
<div class="col-lg-4">
|
|
<div class="panel shadow-sm border-0 h-100 rounded-4" style="background-color: #fafbfc;">
|
|
<div class="panel-heading">
|
|
<h2 class="h5 mb-0 fw-bold text-white"><i class="bi bi-lightning-charge text-warning me-2"></i><?= e(t('quick_links')) ?></h2>
|
|
</div>
|
|
<div class="p-4">
|
|
<div class="list-group list-group-flush bg-transparent">
|
|
<a href="<?= e(url_with_lang('admin_countries.php')) ?>" class="list-group-item list-group-item-action bg-transparent border-bottom d-flex align-items-center py-3 px-0">
|
|
<div class="bg-white rounded p-3 shadow-sm me-3 text-primary d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;"><i class="bi bi-globe2 fs-5"></i></div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold"><?= e(t('manage_countries')) ?></h6>
|
|
<small class="text-muted d-block line-height-sm"><?= e(t('add_remove_countries')) ?></small>
|
|
</div>
|
|
<i class="bi bi-chevron-right ms-auto text-muted small"></i>
|
|
</a>
|
|
<a href="<?= e(url_with_lang('admin_cities.php')) ?>" class="list-group-item list-group-item-action bg-transparent border-bottom d-flex align-items-center py-3 px-0">
|
|
<div class="bg-white rounded p-3 shadow-sm me-3 text-primary d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;"><i class="bi bi-pin-map fs-5"></i></div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold"><?= e(t('manage_cities')) ?></h6>
|
|
<small class="text-muted d-block line-height-sm"><?= e(t('configure_cities')) ?></small>
|
|
</div>
|
|
<i class="bi bi-chevron-right ms-auto text-muted small"></i>
|
|
</a>
|
|
<a href="<?= e(url_with_lang('register.php')) ?>" class="list-group-item list-group-item-action bg-transparent border-bottom d-flex align-items-center py-3 px-0">
|
|
<div class="bg-white rounded p-3 shadow-sm me-3 text-success d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;"><i class="bi bi-person-plus fs-5"></i></div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold"><?= e(t('register_user')) ?></h6>
|
|
<small class="text-muted d-block line-height-sm"><?= e(t('manually_onboard')) ?></small>
|
|
</div>
|
|
<i class="bi bi-chevron-right ms-auto text-muted small"></i>
|
|
</a>
|
|
<a href="<?= e(url_with_lang('admin_company_profile.php')) ?>" class="list-group-item list-group-item-action bg-transparent border-bottom d-flex align-items-center py-3 px-0">
|
|
<div class="bg-white rounded p-3 shadow-sm me-3 text-secondary d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;"><i class="bi bi-buildings fs-5"></i></div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold"><?= e(t('company_profile')) ?></h6>
|
|
<small class="text-muted d-block line-height-sm"><?= e(t('update_branding')) ?></small>
|
|
</div>
|
|
<i class="bi bi-chevron-right ms-auto text-muted small"></i>
|
|
</a>
|
|
<a href="<?= e(url_with_lang('admin_reports_summary.php')) ?>" class="list-group-item list-group-item-action bg-transparent d-flex align-items-center py-3 px-0">
|
|
<div class="bg-white rounded p-3 shadow-sm me-3 text-warning d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;"><i class="bi bi-bar-chart fs-5"></i></div>
|
|
<div>
|
|
<h6 class="mb-1 fw-bold"><?= e(t('summary_report')) ?></h6>
|
|
<small class="text-muted d-block line-height-sm"><?= e(t('view_analytics')) ?></small>
|
|
</div>
|
|
<i class="bi bi-chevron-right ms-auto text-muted small"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const ctx = document.getElementById('shipmentsChart').getContext('2d');
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: <?= json_encode($chartData['labels']) ?>,
|
|
datasets: [{
|
|
label: '<?= t('shipments') ?>',
|
|
data: <?= json_encode($chartData['data']) ?>,
|
|
borderColor: '#0d6efd',
|
|
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.4,
|
|
fill: true,
|
|
pointBackgroundColor: '#ffffff',
|
|
pointBorderColor: '#0d6efd',
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
backgroundColor: '#fff',
|
|
titleColor: '#000',
|
|
bodyColor: '#666',
|
|
borderColor: '#eee',
|
|
borderWidth: 1,
|
|
padding: 10,
|
|
displayColors: false,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return context.parsed.y + ' Shipments';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: '#f0f0f0',
|
|
drawBorder: false
|
|
},
|
|
ticks: {
|
|
stepSize: 1,
|
|
font: {
|
|
size: 11
|
|
}
|
|
}
|
|
},
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
},
|
|
ticks: {
|
|
font: {
|
|
size: 11
|
|
},
|
|
maxTicksLimit: 10
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php render_footer(); ?>
|