339 lines
17 KiB
PHP
339 lines
17 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/includes/layout.php'; require_role('admin');
|
|
|
|
$errors = [];
|
|
$flash = null;
|
|
|
|
// Check permission
|
|
if (!has_permission('manage_shippers')) {
|
|
render_header(t('shippers'), 'shippers');
|
|
echo '<div class="container py-5"><div class="alert alert-danger">Access Denied. You do not have permission to manage shippers.</div></div>';
|
|
render_footer();
|
|
exit;
|
|
}
|
|
|
|
// Handle action (Approve / Reject / Delete if necessary)
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'], $_POST['user_id'])) {
|
|
$userId = (int)$_POST['user_id'];
|
|
$action = $_POST['action'];
|
|
|
|
if ($action === 'approve') {
|
|
db()->prepare("UPDATE users SET status = 'active' WHERE id = ? AND role = 'shipper'")->execute([$userId]);
|
|
$flash = 'Shipper approved successfully.';
|
|
} elseif ($action === 'reject') {
|
|
db()->prepare("UPDATE users SET status = 'rejected' WHERE id = ? AND role = 'shipper'")->execute([$userId]);
|
|
$flash = 'Shipper rejected.';
|
|
} elseif ($action === 'delete') {
|
|
db()->prepare("DELETE FROM shipper_profiles WHERE user_id = ?")->execute([$userId]);
|
|
db()->prepare("DELETE FROM users WHERE id = ? AND role = 'shipper'")->execute([$userId]);
|
|
$flash = 'Shipper deleted.';
|
|
}
|
|
}
|
|
|
|
// Search and Pagination parameters
|
|
$q = trim($_GET['q'] ?? '');
|
|
$status = trim($_GET['status'] ?? '');
|
|
$page = max(1, (int)($_GET['page'] ?? 1));
|
|
$limit = 10;
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$whereClause = "u.role = 'shipper'";
|
|
$params = [];
|
|
|
|
if ($q !== '') {
|
|
$whereClause .= " AND (u.full_name LIKE ? OR u.email LIKE ? OR p.company_name LIKE ?)";
|
|
$likeQ = "%$q%";
|
|
$params = array_merge($params, [$likeQ, $likeQ, $likeQ]);
|
|
}
|
|
|
|
if ($status !== '' && in_array($status, ['active', 'pending', 'rejected'])) {
|
|
$whereClause .= " AND u.status = ?";
|
|
$params[] = $status;
|
|
}
|
|
|
|
// Total count
|
|
$countSql = "
|
|
SELECT COUNT(*)
|
|
FROM users u
|
|
LEFT JOIN shipper_profiles p ON u.id = p.user_id
|
|
WHERE $whereClause
|
|
";
|
|
$stmt = db()->prepare($countSql);
|
|
$stmt->execute($params);
|
|
$total = (int)$stmt->fetchColumn();
|
|
$totalPages = (int)ceil($total / $limit);
|
|
|
|
// Fetch shippers
|
|
$sql = "
|
|
SELECT u.id, u.email, u.full_name, u.status, u.created_at,
|
|
p.company_name, p.phone, p.address_line,
|
|
c.name_en AS country_name,
|
|
ci.name_en AS city_name
|
|
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 $whereClause
|
|
ORDER BY u.created_at DESC
|
|
LIMIT $limit OFFSET $offset
|
|
";
|
|
$stmt = db()->prepare($sql);
|
|
$stmt->execute($params);
|
|
$shippers = $stmt->fetchAll();
|
|
|
|
render_header(t('manage_shippers'), 'admin', true);
|
|
?>
|
|
|
|
<div class="row g-0">
|
|
<div class="col-md-2 bg-white border-end min-vh-100">
|
|
<?php render_admin_sidebar('shippers'); ?>
|
|
</div>
|
|
<div class="col-md-10 p-4">
|
|
<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"><?= e(t('shippers')) ?></h1>
|
|
<p class="muted mb-0"><?= e(t('manage_registered_shippers')) ?></p>
|
|
</div>
|
|
<div class="mt-3 mt-md-0">
|
|
<a href="admin_user_create.php?role=shipper"
|
|
class="btn btn-primary rounded-pill fw-bold px-4 shadow-sm ajax-modal-trigger"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#editModal">
|
|
<i class="bi bi-plus-lg me-2"></i><?= e(t('create_shipper')) ?>
|
|
</a>
|
|
</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-8">
|
|
<label class="form-label small text-muted"><?= e(t('search_placeholder_shipper')) ?></label>
|
|
<input type="text" name="q" class="form-control" placeholder="<?= e(t('search_placeholder_shipper')) ?>" value="<?= e($q) ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small text-muted"><?= e(t('status')) ?></label>
|
|
<select name="status" class="form-select">
|
|
<option value=""><?= e(t('all_statuses')) ?></option>
|
|
<option value="active" <?= $status === 'active' ? 'selected' : '' ?>><?= e(t('active')) ?></option>
|
|
<option value="pending" <?= $status === 'pending' ? 'selected' : '' ?>><?= e(t('pending')) ?></option>
|
|
<option value="rejected" <?= $status === 'rejected' ? 'selected' : '' ?>><?= e(t('rejected')) ?></option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-1 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary w-100"><?= e(t('filter')) ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="panel p-0">
|
|
<?php if (!$shippers && ($q || $status)): ?>
|
|
<div class="p-4"><p class="muted mb-0"><?= e(t('no_shippers_criteria')) ?></p></div>
|
|
<?php elseif (!$shippers): ?>
|
|
<div class="p-4"><p class="muted mb-0"><?= e(t('no_shippers_registered')) ?></p></div>
|
|
<?php else: ?>
|
|
<div class="table-responsive">
|
|
<table class="table mb-0 align-middle table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th class="ps-4">ID</th>
|
|
<th><?= e(t('name_company')) ?></th>
|
|
<th><?= e(t('contact')) ?></th>
|
|
<th><?= e(t('location')) ?></th>
|
|
<th><?= e(t('status')) ?></th>
|
|
<th class="text-end pe-4"><?= e(t('action')) ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($shippers as $shipper): ?>
|
|
<tr>
|
|
<td class="ps-4"><?= e((string)$shipper['id']) ?></td>
|
|
<td>
|
|
<div class="fw-bold text-dark"><?= e($shipper['full_name']) ?></div>
|
|
<div class="text-muted small"><?= e((string)$shipper['company_name']) ?></div>
|
|
</td>
|
|
<td>
|
|
<div><a href="mailto:<?= e($shipper['email']) ?>" class="text-decoration-none"><?= e($shipper['email']) ?></a></div>
|
|
<div class="text-muted small"><?= e((string)$shipper['phone']) ?></div>
|
|
</td>
|
|
<td>
|
|
<?= e((string)$shipper['city_name']) ?>, <?= e((string)$shipper['country_name']) ?>
|
|
</td>
|
|
<td>
|
|
<?php if ($shipper['status'] === 'active'): ?>
|
|
<span class="badge bg-success-subtle text-success"><?= e(t('active')) ?></span>
|
|
<?php elseif ($shipper['status'] === 'pending'): ?>
|
|
<span class="badge bg-warning-subtle text-warning"><?= e(t('pending')) ?></span>
|
|
<?php else: ?>
|
|
<span class="badge bg-danger-subtle text-danger"><?= e(ucfirst($shipper['status'] ?? 'unknown')) ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-end pe-4">
|
|
<div class="d-inline-flex gap-1 align-items-center">
|
|
<a href="admin_shipper_edit.php?id=<?= e((string)$shipper['id']) ?>"
|
|
class="btn btn-sm p-1 border-0 bg-transparent text-primary ajax-modal-trigger"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#editModal"
|
|
title="<?= e(t('edit_shipper')) ?>">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<form method="post" class="d-inline m-0 p-0"> <?= csrf_field() ?>
|
|
<input type="hidden" name="user_id" value="<?= e((string)$shipper['id']) ?>">
|
|
<?php if ($shipper['status'] !== 'active'): ?>
|
|
<button type="submit" name="action" value="approve" class="btn btn-sm p-1 border-0 bg-transparent text-success" title="<?= e(t('approve')) ?>">
|
|
<i class="bi bi-check-lg"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
<?php if ($shipper['status'] !== 'rejected'): ?>
|
|
<button type="submit" name="action" value="reject" class="btn btn-sm p-1 border-0 bg-transparent text-warning" title="<?= e(t('reject')) ?>">
|
|
<i class="bi bi-x-lg"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
<button type="submit" name="action" value="delete" class="btn btn-sm p-1 border-0 bg-transparent text-danger" onclick="return confirm('<?= e(t('delete_confirm_shipper')) ?>');" title="<?= e(t('delete')) ?>">
|
|
<i class="bi bi-trash"></i>
|
|
</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"><?= e(t('shown')) ?> <?= count($shippers) ?> of <?= $total ?> <?= e(t('shippers')) ?></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) ?>&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) ?>&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) ?>&page=<?= $page + 1 ?>">Next</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit/Create Modal -->
|
|
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="editModalLabel"><?= e(t('loading')) ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body text-center p-5">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden"><?= e(t('loading')) ?></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const editModal = document.getElementById('editModal');
|
|
if (!editModal) return;
|
|
|
|
editModal.addEventListener('show.bs.modal', function (event) {
|
|
const button = event.relatedTarget;
|
|
if (!button.classList.contains('ajax-modal-trigger')) return;
|
|
|
|
const url = button.getAttribute('href') + '&ajax=1';
|
|
const modalContent = editModal.querySelector('.modal-content');
|
|
|
|
modalContent.innerHTML = `
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><?= e(t('loading')) ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body text-center p-5">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
</div>
|
|
`;
|
|
|
|
fetch(url)
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
if (html.startsWith('{')) {
|
|
const data = JSON.parse(html);
|
|
modalContent.innerHTML = `<div class="modal-header"><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="alert alert-danger">${data.message}</div></div>`;
|
|
return;
|
|
}
|
|
|
|
modalContent.innerHTML = html;
|
|
|
|
modalContent.querySelectorAll('script').forEach(script => {
|
|
const newScript = document.createElement('script');
|
|
if (script.src) newScript.src = script.src;
|
|
newScript.textContent = script.textContent;
|
|
document.body.appendChild(newScript);
|
|
});
|
|
|
|
const form = modalContent.querySelector('form');
|
|
if (form) {
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
let originalBtnText = '';
|
|
if(submitBtn) {
|
|
originalBtnText = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <?= e(t('loading')) ?>';
|
|
}
|
|
|
|
const formData = new FormData(form);
|
|
fetch(form.action, { method: 'POST', body: formData })
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
if(submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalBtnText || '<?= e(t('save_changes')) ?>';
|
|
}
|
|
const errDiv = form.querySelector('#form-errors');
|
|
if(errDiv) {
|
|
errDiv.classList.remove('d-none');
|
|
errDiv.innerHTML = data.message;
|
|
} else {
|
|
alert(data.message || 'An error occurred');
|
|
}
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
if(submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalBtnText || '<?= e(t('save_changes')) ?>';
|
|
}
|
|
alert('An error occurred while saving.');
|
|
});
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
modalContent.innerHTML = `<div class="modal-body text-danger">Failed to load form.</div>`;
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php render_footer(); ?>
|