add reports

This commit is contained in:
Flatlogic Bot 2026-03-10 17:57:34 +00:00
parent 4b01726905
commit eeb8d0977c
5 changed files with 720 additions and 2 deletions

254
admin_reports_shippers.php Normal file
View File

@ -0,0 +1,254 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
// Check permission
if (!has_permission('view_reports') && !has_permission('manage_shippers')) {
render_header(t('shippers_statements') ?: 'Shippers Statements', 'reports_shippers');
echo '<div class="container py-5"><div class="alert alert-danger">Access Denied. You do not have permission to view reports.</div></div>';
render_footer();
exit;
}
$shipperId = (int)($_GET['shipper_id'] ?? 0);
$startDate = $_GET['start_date'] ?? '';
$endDate = $_GET['end_date'] ?? '';
$shipper = null;
$shipments = [];
$totalAmount = 0.00;
// Fetch all shippers for the dropdown
$allShippers = db()->query("
SELECT u.id, u.full_name, p.company_name
FROM users u
LEFT JOIN shipper_profiles p ON u.id = p.user_id
WHERE u.role = 'shipper'
ORDER BY u.full_name ASC
")->fetchAll();
if ($shipperId) {
// Fetch selected shipper details
$stmt = db()->prepare("
SELECT u.id, u.full_name, u.email, p.company_name, p.phone, p.address_line,
c.name_en as country, ci.name_en as city
FROM users u
LEFT JOIN shipper_profiles p ON u.id = p.user_id
LEFT JOIN countries c ON p.country_id = c.id
LEFT JOIN cities ci ON p.city_id = ci.id
WHERE u.id = ? AND u.role = 'shipper'
");
$stmt->execute([$shipperId]);
$shipper = $stmt->fetch();
if ($shipper) {
// Build Query with Date Filter
$sql = "SELECT *
FROM shipments
WHERE shipper_id = ?
AND status IN ('confirmed', 'in_transit', 'delivered')";
$params = [$shipperId];
if (!empty($startDate)) {
$sql .= " AND created_at >= ?";
$params[] = $startDate . ' 00:00:00';
}
if (!empty($endDate)) {
$sql .= " AND created_at <= ?";
$params[] = $endDate . ' 23:59:59';
}
$sql .= " ORDER BY created_at DESC";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$shipments = $stmt->fetchAll();
foreach ($shipments as $s) {
$totalAmount += (float)($s['total_price'] ?? 0);
}
}
}
render_header(t('shippers_statements') ?: 'Shippers Statements', 'reports_shippers', true);
?>
<style>
@media print {
@page { size: A4; margin: 2cm; }
body { background: white !important; font-family: 'Times New Roman', serif; -webkit-print-color-adjust: exact; }
.no-print, .admin-sidebar, nav, footer, .btn, form.filter-form { display: none !important; }
.col-md-2 { display: none !important; }
.col-md-10 { width: 100% !important; flex: 0 0 100% !important; max-width: 100% !important; padding: 0 !important; margin: 0 !important; }
.row { display: block !important; margin: 0 !important; }
.panel { border: none !important; box-shadow: none !important; padding: 0 !important; }
.print-header { display: block !important; margin-bottom: 30px; border-bottom: 2px solid #000; padding-bottom: 20px; }
.print-header h1 { font-size: 24pt; font-weight: bold; margin-bottom: 5px; text-transform: uppercase; }
.print-header p { margin: 0; font-size: 10pt; color: #555; }
.statement-info { margin-bottom: 30px; display: flex; justify-content: space-between; border-bottom: 1px solid #ddd; padding-bottom: 20px; }
.statement-info div { width: 48%; }
.table-responsive { overflow: visible !important; }
table { width: 100% !important; border-collapse: collapse !important; font-size: 10pt; }
th, td { border: 1px solid #ddd !important; padding: 8px 12px !important; text-align: left; }
th { background-color: #f8f9fa !important; font-weight: bold; text-transform: uppercase; color: #000 !important; }
.text-end { text-align: right !important; }
.total-row td { background-color: #eee !important; font-weight: bold; font-size: 12pt; border-top: 2px solid #000 !important; }
.print-footer { display: block !important; margin-top: 50px; text-align: center; font-size: 9pt; color: #777; border-top: 1px solid #ddd; padding-top: 10px; }
/* Arabic Support */
[dir="rtl"] body { font-family: 'Traditional Arabic', serif; }
[dir="rtl"] th, [dir="rtl"] td { text-align: right; }
[dir="rtl"] .text-end { text-align: left !important; }
}
.print-header, .print-footer { display: none; }
</style>
<div class="row g-0">
<div class="col-md-2 bg-white border-end min-vh-100 no-print">
<?php render_admin_sidebar('reports_shippers'); ?>
</div>
<div class="col-md-10 p-4">
<!-- Screen Only Header -->
<div class="page-intro d-flex justify-content-between align-items-center mb-4 no-print">
<div>
<h1 class="section-title mb-1">Shipper Statements</h1>
<p class="muted mb-0">View and print financial statements for shippers.</p>
</div>
<?php if ($shipper): ?>
<button onclick="window.print()" class="btn btn-dark">
<i class="bi bi-printer me-2"></i>Print Formal Statement
</button>
<?php endif; ?>
</div>
<!-- Filter Form -->
<div class="panel p-4 mb-4 no-print filter-form">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label small text-muted">Select Shipper</label>
<select name="shipper_id" class="form-select" onchange="this.form.submit()">
<option value="">-- Choose a Shipper --</option>
<?php foreach ($allShippers as $s): ?>
<option value="<?= $s['id'] ?>" <?= $shipperId == $s['id'] ? 'selected' : '' ?>>
<?= e($s['full_name']) ?> (<?= e($s['company_name'] ?: 'No Company') ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted">Start Date</label>
<input type="date" name="start_date" class="form-control" value="<?= e($startDate) ?>">
</div>
<div class="col-md-3">
<label class="form-label small text-muted">End Date</label>
<input type="date" name="end_date" class="form-control" value="<?= e($endDate) ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Filter</button>
</div>
</form>
</div>
<?php if ($shipperId && !$shipper): ?>
<div class="alert alert-warning">Shipper not found.</div>
<?php elseif ($shipper): ?>
<div class="panel p-5 bg-white">
<!-- Print Header -->
<div class="print-header text-center">
<h1>Statement of Account</h1>
<h2 style="font-size: 18pt; margin: 0; font-weight: normal;">كشف حساب</h2>
<p class="mt-2"><?= e(get_setting('company_name', 'Logistics Platform')) ?></p>
<p><?= date('F j, Y') ?></p>
</div>
<!-- Info Section -->
<div class="statement-info row mb-4">
<div class="col-6">
<h5 class="fw-bold text-uppercase text-muted small mb-2">Billed To / إلى السيد</h5>
<h4 class="fw-bold mb-1"><?= e($shipper['full_name']) ?></h4>
<div class="text-muted"><?= e($shipper['company_name'] ?: '-') ?></div>
<div class="small text-muted mt-1">
<?= e($shipper['address_line'] ?: '') ?><br>
<?= e($shipper['city'] ?: '') ?>, <?= e($shipper['country'] ?: '') ?><br>
<?= e($shipper['phone'] ?: '') ?>
</div>
</div>
<div class="col-6 text-end">
<h5 class="fw-bold text-uppercase text-muted small mb-2">Summary / ملخص</h5>
<div class="mb-1"><strong>Statement Date:</strong> <?= date('Y-m-d') ?></div>
<?php if ($startDate || $endDate): ?>
<div class="mb-1"><strong>Period:</strong> <?= e($startDate ?: 'Start') ?> to <?= e($endDate ?: 'Now') ?></div>
<?php else: ?>
<div class="mb-1"><strong>Period:</strong> All Time</div>
<?php endif; ?>
<div class="mb-1"><strong>Total Shipments:</strong> <?= count($shipments) ?></div>
<div class="fs-4 fw-bold text-primary mt-2">
<?= number_format($totalAmount, 2) ?> OMR
</div>
</div>
</div>
<!-- Table -->
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="table-light">
<tr>
<th>Date <br> <span class="fw-normal text-muted">التاريخ</span></th>
<th>Ref # <br> <span class="fw-normal text-muted">رقم الشحنة</span></th>
<th>Route <br> <span class="fw-normal text-muted">المسار</span></th>
<th>Status <br> <span class="fw-normal text-muted">الحالة</span></th>
<th class="text-end">Amount (OMR) <br> <span class="fw-normal text-muted">المبلغ</span></th>
</tr>
</thead>
<tbody>
<?php if (empty($shipments)): ?>
<tr><td colspan="5" class="text-center py-4 text-muted">No completed shipments found for this period.</td></tr>
<?php else: ?>
<?php foreach ($shipments as $row): ?>
<tr>
<td><?= date('Y-m-d', strtotime($row['created_at'])) ?></td>
<td><span class="font-monospace">#<?= $row['id'] ?></span></td>
<td>
<?= e($row['origin_city']) ?> <i class="bi bi-arrow-right small text-muted mx-1"></i> <?= e($row['destination_city']) ?>
<div class="small text-muted"><?= e($row['cargo_description']) ?></div>
</td>
<td>
<?= e(ucfirst(str_replace('_', ' ', $row['status']))) ?>
</td>
<td class="text-end fw-bold">
<?= number_format((float)$row['total_price'], 2) ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="total-row table-active">
<td colspan="4" class="text-end text-uppercase">Total Balance / الإجمالي</td>
<td class="text-end text-dark"><?= number_format($totalAmount, 2) ?> OMR</td>
</tr>
</tfoot>
</table>
</div>
<!-- Print Footer -->
<div class="print-footer">
<div class="row">
<div class="col-6 text-start">
Authorized Signature<br>
_______________________
</div>
<div class="col-6 text-end">
Printed on <?= date('Y-m-d H:i') ?><br>
This is a computer-generated document.
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php render_footer(); ?>

View File

@ -0,0 +1,254 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
// Check permission
if (!has_permission('view_reports') && !has_permission('manage_truck_owners')) {
render_header(t('truck_owners_statements') ?: 'Truck Owner Statements', 'reports_truck_owners');
echo '<div class="container py-5"><div class="alert alert-danger">Access Denied. You do not have permission to view reports.</div></div>';
render_footer();
exit;
}
$ownerId = (int)($_GET['owner_id'] ?? 0);
$startDate = $_GET['start_date'] ?? '';
$endDate = $_GET['end_date'] ?? '';
$owner = null;
$shipments = [];
$totalEarnings = 0.00;
// Fetch all truck owners for the dropdown
$allOwners = db()->query("
SELECT u.id, u.full_name, p.plate_no
FROM users u
LEFT JOIN truck_owner_profiles p ON u.id = p.user_id
WHERE u.role = 'truck_owner'
ORDER BY u.full_name ASC
")->fetchAll();
if ($ownerId) {
// Fetch selected owner details
$stmt = db()->prepare("
SELECT u.id, u.full_name, u.email, p.phone, p.plate_no, p.truck_type,
c.name_en as country, ci.name_en as city
FROM users u
LEFT JOIN truck_owner_profiles p ON u.id = p.user_id
LEFT JOIN countries c ON p.country_id = c.id
LEFT JOIN cities ci ON p.city_id = ci.id
WHERE u.id = ? AND u.role = 'truck_owner'
");
$stmt->execute([$ownerId]);
$owner = $stmt->fetch();
if ($owner) {
// Build Query with Date Filter
$sql = "SELECT *
FROM shipments
WHERE truck_owner_id = ?
AND status IN ('confirmed', 'in_transit', 'delivered')";
$params = [$ownerId];
if (!empty($startDate)) {
$sql .= " AND created_at >= ?";
$params[] = $startDate . ' 00:00:00';
}
if (!empty($endDate)) {
$sql .= " AND created_at <= ?";
$params[] = $endDate . ' 23:59:59';
}
$sql .= " ORDER BY created_at DESC";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$shipments = $stmt->fetchAll();
foreach ($shipments as $s) {
$totalEarnings += (float)($s['offer_price'] ?? 0);
}
}
}
render_header(t('truck_owners_statements') ?: 'Truck Owner Statements', 'reports_truck_owners', true);
?>
<style>
@media print {
@page { size: A4; margin: 2cm; }
body { background: white !important; font-family: 'Times New Roman', serif; -webkit-print-color-adjust: exact; }
.no-print, .admin-sidebar, nav, footer, .btn, form.filter-form { display: none !important; }
.col-md-2 { display: none !important; }
.col-md-10 { width: 100% !important; flex: 0 0 100% !important; max-width: 100% !important; padding: 0 !important; margin: 0 !important; }
.row { display: block !important; margin: 0 !important; }
.panel { border: none !important; box-shadow: none !important; padding: 0 !important; }
.print-header { display: block !important; margin-bottom: 30px; border-bottom: 2px solid #000; padding-bottom: 20px; }
.print-header h1 { font-size: 24pt; font-weight: bold; margin-bottom: 5px; text-transform: uppercase; }
.print-header p { margin: 0; font-size: 10pt; color: #555; }
.statement-info { margin-bottom: 30px; display: flex; justify-content: space-between; border-bottom: 1px solid #ddd; padding-bottom: 20px; }
.statement-info div { width: 48%; }
.table-responsive { overflow: visible !important; }
table { width: 100% !important; border-collapse: collapse !important; font-size: 10pt; }
th, td { border: 1px solid #ddd !important; padding: 8px 12px !important; text-align: left; }
th { background-color: #f8f9fa !important; font-weight: bold; text-transform: uppercase; color: #000 !important; }
.text-end { text-align: right !important; }
.total-row td { background-color: #eee !important; font-weight: bold; font-size: 12pt; border-top: 2px solid #000 !important; }
.print-footer { display: block !important; margin-top: 50px; text-align: center; font-size: 9pt; color: #777; border-top: 1px solid #ddd; padding-top: 10px; }
/* Arabic Support */
[dir="rtl"] body { font-family: 'Traditional Arabic', serif; }
[dir="rtl"] th, [dir="rtl"] td { text-align: right; }
[dir="rtl"] .text-end { text-align: left !important; }
}
.print-header, .print-footer { display: none; }
</style>
<div class="row g-0">
<div class="col-md-2 bg-white border-end min-vh-100 no-print">
<?php render_admin_sidebar('reports_truck_owners'); ?>
</div>
<div class="col-md-10 p-4">
<!-- Screen Only Header -->
<div class="page-intro d-flex justify-content-between align-items-center mb-4 no-print">
<div>
<h1 class="section-title mb-1">Truck Owner Statements</h1>
<p class="muted mb-0">View and print financial statements for truck owners.</p>
</div>
<?php if ($owner): ?>
<button onclick="window.print()" class="btn btn-dark">
<i class="bi bi-printer me-2"></i>Print Formal Statement
</button>
<?php endif; ?>
</div>
<!-- Filter Form -->
<div class="panel p-4 mb-4 no-print filter-form">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label small text-muted">Select Truck Owner</label>
<select name="owner_id" class="form-select" onchange="this.form.submit()">
<option value="">-- Choose a Truck Owner --</option>
<?php foreach ($allOwners as $o): ?>
<option value="<?= $o['id'] ?>" <?= $ownerId == $o['id'] ? 'selected' : '' ?>>
<?= e($o['full_name']) ?> (Plate: <?= e($o['plate_no'] ?: '-') ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted">Start Date</label>
<input type="date" name="start_date" class="form-control" value="<?= e($startDate) ?>">
</div>
<div class="col-md-3">
<label class="form-label small text-muted">End Date</label>
<input type="date" name="end_date" class="form-control" value="<?= e($endDate) ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Filter</button>
</div>
</form>
</div>
<?php if ($ownerId && !$owner): ?>
<div class="alert alert-warning">Truck Owner not found.</div>
<?php elseif ($owner): ?>
<div class="panel p-5 bg-white">
<!-- Print Header -->
<div class="print-header text-center">
<h1>Payment Statement</h1>
<h2 style="font-size: 18pt; margin: 0; font-weight: normal;">كشف مدفوعات</h2>
<p class="mt-2"><?= e(get_setting('company_name', 'Logistics Platform')) ?></p>
<p><?= date('F j, Y') ?></p>
</div>
<!-- Info Section -->
<div class="statement-info row mb-4">
<div class="col-6">
<h5 class="fw-bold text-uppercase text-muted small mb-2">Pay To / إلى السيد</h5>
<h4 class="fw-bold mb-1"><?= e($owner['full_name']) ?></h4>
<div class="text-muted">Truck Plate: <?= e($owner['plate_no'] ?: '-') ?></div>
<div class="small text-muted mt-1">
<?= e($owner['truck_type'] ?: '') ?><br>
<?= e($owner['city'] ?: '') ?>, <?= e($owner['country'] ?: '') ?><br>
<?= e($owner['phone'] ?: '') ?>
</div>
</div>
<div class="col-6 text-end">
<h5 class="fw-bold text-uppercase text-muted small mb-2">Summary / ملخص</h5>
<div class="mb-1"><strong>Statement Date:</strong> <?= date('Y-m-d') ?></div>
<?php if ($startDate || $endDate): ?>
<div class="mb-1"><strong>Period:</strong> <?= e($startDate ?: 'Start') ?> to <?= e($endDate ?: 'Now') ?></div>
<?php else: ?>
<div class="mb-1"><strong>Period:</strong> All Time</div>
<?php endif; ?>
<div class="mb-1"><strong>Total Trips:</strong> <?= count($shipments) ?></div>
<div class="fs-4 fw-bold text-primary mt-2">
<?= number_format($totalEarnings, 2) ?> OMR
</div>
</div>
</div>
<!-- Table -->
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="table-light">
<tr>
<th>Date <br> <span class="fw-normal text-muted">التاريخ</span></th>
<th>Ref # <br> <span class="fw-normal text-muted">رقم الشحنة</span></th>
<th>Route <br> <span class="fw-normal text-muted">المسار</span></th>
<th>Status <br> <span class="fw-normal text-muted">الحالة</span></th>
<th class="text-end">Earnings (OMR) <br> <span class="fw-normal text-muted">المبلغ</span></th>
</tr>
</thead>
<tbody>
<?php if (empty($shipments)): ?>
<tr><td colspan="5" class="text-center py-4 text-muted">No completed shipments found for this period.</td></tr>
<?php else: ?>
<?php foreach ($shipments as $row): ?>
<tr>
<td><?= date('Y-m-d', strtotime($row['created_at'])) ?></td>
<td><span class="font-monospace">#<?= $row['id'] ?></span></td>
<td>
<?= e($row['origin_city']) ?> <i class="bi bi-arrow-right small text-muted mx-1"></i> <?= e($row['destination_city']) ?>
<div class="small text-muted"><?= e($row['cargo_description']) ?></div>
</td>
<td>
<?= e(ucfirst(str_replace('_', ' ', $row['status']))) ?>
</td>
<td class="text-end fw-bold">
<?= number_format((float)$row['offer_price'], 2) ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="total-row table-active">
<td colspan="4" class="text-end text-uppercase">Total Earnings / الإجمالي</td>
<td class="text-end text-dark"><?= number_format($totalEarnings, 2) ?> OMR</td>
</tr>
</tfoot>
</table>
</div>
<!-- Print Footer -->
<div class="print-footer">
<div class="row">
<div class="col-6 text-start">
Authorized Signature<br>
_______________________
</div>
<div class="col-6 text-end">
Printed on <?= date('Y-m-d H:i') ?><br>
This is a computer-generated document.
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php render_footer(); ?>

View File

@ -20,6 +20,67 @@
--sidebar-border: rgba(255, 255, 255, 0.1);
}
/* Theme: Dark */
[data-theme="dark"] {
--bg: #0f172a;
--surface: #1e293b;
--text: #f1f5f9;
--muted: #94a3b8;
--border: #334155;
--primary: #60a5fa;
--primary-hover: #3b82f6;
--accent: #38bdf8;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
--sidebar-bg: #0f172a;
--sidebar-text: #e2e8f0;
--sidebar-text-hover: #ffffff;
--sidebar-active-bg: #334155;
--sidebar-border: #334155;
}
/* Theme: Blue (Corporate) */
[data-theme="blue"] {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--sidebar-bg: #1e3a8a;
}
/* Theme: Green (Nature) */
[data-theme="green"] {
--primary: #10b981;
--primary-hover: #059669;
--sidebar-bg: #064e3b;
}
/* Theme: Purple (Creative) */
[data-theme="purple"] {
--primary: #8b5cf6;
--primary-hover: #7c3aed;
--sidebar-bg: #4c1d95;
}
/* Theme: Red (Bold) */
[data-theme="red"] {
--primary: #ef4444;
--primary-hover: #dc2626;
--sidebar-bg: #7f1d1d;
}
/* Theme: Orange (Warm) */
[data-theme="orange"] {
--primary: #f97316;
--primary-hover: #ea580c;
--sidebar-bg: #7c2d12;
}
/* Theme: Teal (Calm) */
[data-theme="teal"] {
--primary: #14b8a6;
--primary-hover: #0d9488;
--sidebar-bg: #134e4a;
}
body.app-body {
background:
radial-gradient(circle at top left, rgba(59, 130, 246, 0.05), transparent 40%),
@ -31,6 +92,66 @@ body.app-body {
line-height: 1.6;
}
/* Dark mode specific adjustments */
[data-theme="dark"] body.app-body {
background: var(--bg); /* Simplify background for dark mode */
}
[data-theme="dark"] .navbar {
background: rgba(30, 41, 59, 0.85) !important;
border-bottom: 1px solid var(--border) !important;
}
[data-theme="dark"] .navbar-brand {
color: var(--text) !important;
}
[data-theme="dark"] .navbar-light .navbar-nav .nav-link {
color: var(--muted);
}
[data-theme="dark"] .navbar-light .navbar-nav .nav-link.active,
[data-theme="dark"] .navbar-light .navbar-nav .nav-link:hover {
color: var(--text);
}
[data-theme="dark"] .bg-white {
background-color: var(--surface) !important;
}
[data-theme="dark"] .text-dark {
color: var(--text) !important;
}
[data-theme="dark"] .text-muted {
color: var(--muted) !important;
}
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select {
background-color: #0f172a;
color: var(--text);
border-color: var(--border);
}
[data-theme="dark"] .form-control:focus,
[data-theme="dark"] .form-select:focus {
background-color: #1e293b;
}
[data-theme="dark"] .dropdown-menu {
background-color: var(--surface);
border-color: var(--border);
}
[data-theme="dark"] .dropdown-item {
color: var(--text);
}
[data-theme="dark"] .dropdown-item:hover {
background-color: var(--border);
}
.navbar {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.85) !important;
@ -195,10 +316,23 @@ body.app-body {
border-bottom: 1px solid #f8fafc;
}
[data-theme="dark"] .table tbody td {
border-bottom-color: var(--border);
}
.table tbody tr:hover {
background: #f8fafc;
}
[data-theme="dark"] .table tbody tr:hover {
background: #334155;
color: #fff;
}
[data-theme="dark"] .table {
color: var(--text);
}
.form-control,
.form-select {
border-radius: 12px;
@ -220,6 +354,10 @@ body.app-body {
margin-bottom: 8px;
}
[data-theme="dark"] .form-label {
color: var(--text);
}
.btn-primary {
background: var(--primary);
border-color: var(--primary);
@ -252,6 +390,16 @@ body.app-body {
border-color: #cbd5e1;
}
[data-theme="dark"] .btn-outline-dark {
color: var(--text);
border-color: var(--border);
}
[data-theme="dark"] .btn-outline-dark:hover {
background-color: var(--border);
color: #fff;
}
.section-title {
font-size: 22px;
font-weight: 700;
@ -370,4 +518,4 @@ body.app-body {
overflow-y: visible;
background-color: var(--sidebar-bg); /* Keep dark on mobile too */
}
}
}

View File

@ -0,0 +1,16 @@
<?php
require_once __DIR__ . '/../../db/config.php';
$pdo = db();
try {
$stmt = $pdo->prepare("UPDATE permissions SET description = ? WHERE slug = ?");
$stmt->execute([
'Access to all reports, including Shipper and Truck Owner statements.',
'view_reports'
]);
echo "Updated 'view_reports' permission description.";
} catch (PDOException $e) {
echo "Error updating permission: " . $e->getMessage();
}

View File

@ -19,6 +19,12 @@ function render_header(string $title, string $active = '', bool $isFluid = false
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= e($title) ?> | <?= e($appName) ?></title>
<script>
(function() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
})();
</script>
<?php if ($faviconPath): ?>
<link rel="icon" href="<?= e($faviconPath) ?>">
<?php endif; ?>
@ -70,6 +76,24 @@ function render_header(string $title, string $active = '', bool $isFluid = false
</li>
</ul>
<div class="d-flex align-items-center gap-3">
<!-- Theme Switcher -->
<div class="dropdown">
<button class="btn btn-sm btn-outline-dark rounded-pill px-3 fw-bold dropdown-toggle" type="button" id="themeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-palette-fill me-1"></i> Theme
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0 mt-3" aria-labelledby="themeDropdown" style="border-radius: 12px;">
<li><button class="dropdown-item py-2" onclick="setTheme('light')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#f4f7f6;border:1px solid #ccc;"></span>Default</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('dark')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#0f172a;"></span>Dark</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('blue')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#2563eb;"></span>Blue</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('green')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#10b981;"></span>Green</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('purple')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#8b5cf6;"></span>Purple</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('red')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#ef4444;"></span>Red</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('orange')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#f97316;"></span>Orange</button></li>
<li><button class="dropdown-item py-2" onclick="setTheme('teal')"><span class="d-inline-block rounded-circle me-2" style="width:12px;height:12px;background:#14b8a6;"></span>Teal</button></li>
</ul>
</div>
<div class="dropdown">
<?php if (isset($_SESSION['user_id'])): ?>
<a class="text-decoration-none text-muted fw-semibold dropdown-toggle" href="#" id="loginDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
@ -208,6 +232,12 @@ function render_footer(bool $showFooter = true): void
</footer>
<?php endif; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
</script>
<script src="/assets/js/main.js?v=<?= time() ?>"></script>
</body>
</html>
@ -220,6 +250,7 @@ function render_admin_sidebar(string $active = 'dashboard'): void
$locationsActive = in_array($active, ['countries', 'cities']);
$usersActive = in_array($active, ['shippers', 'truck_owners', 'register', 'platform_users']);
$pagesActive = in_array($active, ['faqs', 'landing_pages']);
$reportsActive = in_array($active, ['reports_shippers', 'reports_truck_owners']);
?>
<aside class="admin-sidebar d-flex flex-column h-100 py-4 px-3">
<h2 class="h5 fw-bold mb-4 px-2"><i class="bi bi-shield-lock me-2 text-primary"></i><?= e(t('nav_admin')) ?></h2>
@ -231,6 +262,21 @@ function render_admin_sidebar(string $active = 'dashboard'): void
<i class="bi bi-box2-fill me-2"></i><?= e(t('shipments') ?: 'Shipments') ?>
</a>
<a class="nav-link fw-bold text-muted text-uppercase mt-2 d-flex justify-content-between align-items-center p-2 rounded" style="cursor: pointer; font-size: 0.85rem;" data-bs-toggle="collapse" data-bs-target="#collapseReports" aria-expanded="<?= $reportsActive ? 'true' : 'false' ?>">
<span><i class="bi bi-file-earmark-bar-graph-fill me-2"></i><?= e(t('reports') ?: 'Reports') ?></span>
<i class="bi bi-chevron-down small"></i>
</a>
<div class="collapse <?= $reportsActive ? 'show' : '' ?>" id="collapseReports">
<div class="nav flex-column gap-1 ms-3 border-start ps-2 border-2">
<a class="admin-nav-link <?= $active === 'reports_shippers' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_reports_shippers.php')) ?>">
<i class="bi bi-people me-2"></i><?= e(t('shippers_statements') ?: 'Shippers Statements') ?>
</a>
<a class="admin-nav-link <?= $active === 'reports_truck_owners' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_reports_truck_owners.php')) ?>">
<i class="bi bi-truck me-2"></i><?= e(t('truck_owners_statements') ?: 'Truck Owner Statements') ?>
</a>
</div>
</div>
<a class="nav-link fw-bold text-muted text-uppercase mt-2 d-flex justify-content-between align-items-center p-2 rounded" style="cursor: pointer; font-size: 0.85rem;" data-bs-toggle="collapse" data-bs-target="#collapseSettings" aria-expanded="<?= $settingsActive ? 'true' : 'false' ?>">
<span><i class="bi bi-gear-fill me-2"></i><?= e(t('settings')) ?></span>
<i class="bi bi-chevron-down small"></i>
@ -309,4 +355,4 @@ function render_admin_sidebar(string $active = 'dashboard'): void
</div>
</aside>
<?php
}
}