39038-vm/admin_shipments.php
2026-03-08 05:41:10 +00:00

218 lines
13 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$errors = [];
$flash = null;
// Handle action (Delete)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'], $_POST['shipment_id'])) {
$shipmentId = (int)$_POST['shipment_id'];
$action = $_POST['action'];
if ($action === 'delete') {
db()->prepare("DELETE FROM shipments WHERE id = ?")->execute([$shipmentId]);
$flash = 'Shipment deleted successfully.';
}
}
// Search, Filter, Sort and Pagination parameters
$q = trim($_GET['q'] ?? '');
$status = trim($_GET['status'] ?? '');
$sort = trim($_GET['sort'] ?? 'newest');
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$whereClause = "1=1";
$params = [];
if ($q !== '') {
$whereClause .= " AND (shipper_name LIKE ? OR shipper_company LIKE ? OR origin_city LIKE ? OR destination_city LIKE ? OR cargo_description LIKE ?)";
$likeQ = "%$q%";
$params = array_merge($params, array_fill(0, 5, $likeQ));
}
if ($status !== '' && in_array($status, ['posted', 'offered', 'confirmed', 'in_transit', 'delivered'])) {
$whereClause .= " AND status = ?";
$params[] = $status;
}
// Sorting logic
$orderBy = match ($sort) {
'oldest' => 'created_at ASC',
'pickup_asc' => 'pickup_date ASC',
'pickup_desc' => 'pickup_date DESC',
default => 'created_at DESC', // newest
};
// Total count
$countSql = "SELECT COUNT(*) FROM shipments WHERE $whereClause";
$stmt = db()->prepare($countSql);
$stmt->execute($params);
$total = (int)$stmt->fetchColumn();
$totalPages = (int)ceil($total / $limit);
// Fetch shipments
$sql = "
SELECT *
FROM shipments
WHERE $whereClause
ORDER BY $orderBy
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$shipments = $stmt->fetchAll();
render_header('Manage Shipments', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('shipments'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
<div>
<h1 class="section-title mb-1">Shipments</h1>
<p class="muted mb-0">Manage all shipments across the platform.</p>
</div>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<div class="panel p-4 mb-4">
<form method="get" class="row g-3">
<div class="col-md-5">
<label class="form-label small text-muted">Search</label>
<input type="text" name="q" class="form-control" placeholder="Search shipments..." value="<?= e($q) ?>">
</div>
<div class="col-md-3">
<label class="form-label small text-muted">Status</label>
<select name="status" class="form-select">
<option value="">All Statuses</option>
<option value="posted" <?= $status === 'posted' ? 'selected' : '' ?>>Posted</option>
<option value="offered" <?= $status === 'offered' ? 'selected' : '' ?>>Offered</option>
<option value="confirmed" <?= $status === 'confirmed' ? 'selected' : '' ?>>Confirmed</option>
<option value="in_transit" <?= $status === 'in_transit' ? 'selected' : '' ?>>In Transit</option>
<option value="delivered" <?= $status === 'delivered' ? 'selected' : '' ?>>Delivered</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted">Sort By</label>
<select name="sort" class="form-select">
<option value="newest" <?= $sort === 'newest' ? 'selected' : '' ?>>Newest First</option>
<option value="oldest" <?= $sort === 'oldest' ? 'selected' : '' ?>>Oldest First</option>
<option value="pickup_asc" <?= $sort === 'pickup_asc' ? 'selected' : '' ?>>Pickup Date (Soonest)</option>
<option value="pickup_desc" <?= $sort === 'pickup_desc' ? 'selected' : '' ?>>Pickup Date (Latest)</option>
</select>
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Filter</button>
</div>
</form>
</div>
<div class="panel p-0">
<?php if (!$shipments && ($q || $status)): ?>
<div class="p-4"><p class="muted mb-0">No shipments found matching your criteria.</p></div>
<?php elseif (!$shipments): ?>
<div class="p-4"><p class="muted mb-0">No shipments found on the platform yet.</p></div>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0 align-middle table-hover">
<thead class="table-light">
<tr>
<th class="ps-4">ID</th>
<th>Shipper</th>
<th>Route</th>
<th>Dates</th>
<th>Status</th>
<th class="text-end pe-4">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($shipments as $shipment): ?>
<tr>
<td class="ps-4"><?= e((string)$shipment['id']) ?></td>
<td>
<div class="fw-bold text-dark"><?= e($shipment['shipper_name']) ?></div>
<div class="text-muted small"><?= e((string)$shipment['shipper_company']) ?></div>
</td>
<td>
<div><span class="text-muted small">From:</span> <?= e($shipment['origin_city']) ?></div>
<div><span class="text-muted small">To:</span> <?= e($shipment['destination_city']) ?></div>
</td>
<td>
<div class="small"><span class="text-muted">Pick:</span> <?= e($shipment['pickup_date']) ?></div>
<div class="small"><span class="text-muted">Drop:</span> <?= e($shipment['delivery_date']) ?></div>
</td>
<td>
<?php
$statusClass = 'bg-secondary-subtle text-secondary';
if ($shipment['status'] === 'posted') $statusClass = 'bg-primary-subtle text-primary';
elseif ($shipment['status'] === 'offered') $statusClass = 'bg-info-subtle text-info';
elseif ($shipment['status'] === 'confirmed') $statusClass = 'bg-success-subtle text-success';
elseif ($shipment['status'] === 'in_transit') $statusClass = 'bg-warning-subtle text-warning';
elseif ($shipment['status'] === 'delivered') $statusClass = 'bg-dark-subtle text-dark';
?>
<span class="badge <?= $statusClass ?>"><?= e(ucfirst(str_replace('_', ' ', $shipment['status']))) ?></span>
</td>
<td class="text-end pe-4">
<div class="d-inline-flex gap-1 align-items-center">
<a href="shipment_detail.php?id=<?= e((string)$shipment['id']) ?>" class="btn btn-sm btn-light border text-primary" title="View Shipment">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
</svg>
</a>
<a href="admin_shipment_edit.php?id=<?= e((string)$shipment['id']) ?>" class="btn btn-sm btn-light border text-primary" title="Edit Shipment">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>
</svg>
</a>
<form method="post" class="d-inline m-0 p-0">
<input type="hidden" name="shipment_id" value="<?= e((string)$shipment['id']) ?>">
<button type="submit" name="action" value="delete" class="btn btn-sm btn-danger" onclick="return confirm('Delete this shipment?');" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<div class="px-4 py-3 border-top d-flex justify-content-between align-items-center">
<span class="text-muted small">Showing <?= count($shipments) ?> of <?= $total ?> shipments</span>
<ul class="pagination pagination-sm mb-0">
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&status=<?= urlencode($status) ?>&sort=<?= urlencode($sort) ?>&page=<?= $page - 1 ?>">Previous</a>
</li>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&status=<?= urlencode($status) ?>&sort=<?= urlencode($sort) ?>&page=<?= $i ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&status=<?= urlencode($status) ?>&sort=<?= urlencode($sort) ?>&page=<?= $page + 1 ?>">Next</a>
</li>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
<?php render_footer(); ?>