39038-vm/admin_truck_owners.php
2026-03-14 13:13:28 +00:00

402 lines
20 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_truck_owners')) {
render_header(t('truck_owners'), 'truck_owners');
echo '<div class="container py-5"><div class="alert alert-danger">Access Denied. You do not have permission to manage truck owners.</div></div>';
render_footer();
exit;
}
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 = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner approved successfully.';
} elseif ($action === 'reject') {
db()->prepare("UPDATE users SET status = 'rejected' WHERE id = ? AND role = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner rejected.';
} elseif ($action === 'delete') {
db()->prepare("DELETE FROM truck_owner_profiles WHERE user_id = ?")->execute([$userId]);
db()->prepare("DELETE FROM users WHERE id = ? AND role = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner 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 = 'truck_owner'";
$params = [];
if ($q !== '') {
$whereClause .= " AND (u.full_name LIKE ? OR u.email LIKE ? OR p.plate_no LIKE ? OR p.truck_type LIKE ?)";
$likeQ = "%$q%";
$params = array_merge($params, [$likeQ, $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 truck_owner_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 truck owners
$sql = "
SELECT u.id, u.email, u.full_name, u.status, u.created_at,
p.phone, p.truck_type, p.load_capacity, p.plate_no,
p.id_card_path, p.truck_pic_path, p.registration_path,
c.name_en AS country_name,
ci.name_en AS city_name
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 $whereClause
ORDER BY u.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$owners = $stmt->fetchAll();
render_header(t('manage_truck_owners'), 'admin', true);
?>
<div class="row g-0">
<div class="col-md-2 bg-white border-end min-vh-100">
<?php render_admin_sidebar('truck_owners'); ?>
</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('truck_owners')) ?></h1>
<p class="muted mb-0"><?= e(t('review_registrations')) ?></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-8">
<label class="form-label small text-muted"><?= e(t('search_placeholder_owner')) ?></label>
<input type="text" name="q" class="form-control" placeholder="<?= e(t('search_placeholder_owner')) ?>" 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 (!$owners && ($q || $status)): ?>
<div class="p-4"><p class="muted mb-0"><?= e(t('no_owners_criteria')) ?></p></div>
<?php elseif (!$owners): ?>
<div class="p-4"><p class="muted mb-0"><?= e(t('no_owners_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_email')) ?></th>
<th><?= e(t('truck_info')) ?></th>
<th><?= e(t('documents')) ?></th>
<th><?= e(t('status')) ?></th>
<th class="text-end pe-4"><?= e(t('action')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($owners as $owner): ?>
<tr>
<td class="ps-4"><?= e((string)$owner['id']) ?></td>
<td>
<div class="fw-bold text-dark"><?= e($owner['full_name']) ?></div>
<div class="text-muted small"><a href="mailto:<?= e($owner['email']) ?>" class="text-decoration-none"><?= e($owner['email']) ?></a></div>
<div class="text-muted small"><?= e((string)$owner['phone']) ?></div>
</td>
<td>
<div><strong><?= e(t('truck_type')) ?>:</strong> <?= e((string)$owner['truck_type']) ?></div>
<div><strong><?= e(t('cap')) ?>:</strong> <?= e((string)$owner['load_capacity']) ?>t</div>
<div><strong><?= e(t('truck_plate')) ?>:</strong> <?= e((string)$owner['plate_no']) ?></div>
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1" data-bs-toggle="modal" data-bs-target="#docsModal<?= $owner['id'] ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16">
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5"/>
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
</svg>
<?= e(t('view_docs')) ?>
</button>
</td>
<td>
<?php if ($owner['status'] === 'active'): ?>
<span class="badge bg-success-subtle text-success"><?= e(t('active')) ?></span>
<?php elseif ($owner['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($owner['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_truck_owner_edit.php?id=<?= e((string)$owner['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_owner')) ?>">
<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)$owner['id']) ?>">
<?php if ($owner['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 ($owner['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_owner')) ?>');" 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($owners) ?> of <?= $total ?> <?= e(t('truck_owners')) ?></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 Modal Placeholder -->
<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('edit_owner')) ?></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>
<?php foreach ($owners as $owner): ?>
<?php
$idCards = json_decode($owner['id_card_path'] ?? '[]', true) ?: [];
$regs = json_decode($owner['registration_path'] ?? '[]', true) ?: [];
$pic = $owner['truck_pic_path'];
?>
<div class="modal fade" id="docsModal<?= $owner['id'] ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><?= e(t('docs_for')) ?> <?= e($owner['full_name']) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6><?= e(t('id_card_front')) ?> / <?= e(t('id_card_back')) ?></h6>
<div class="d-flex gap-2 mb-3 overflow-auto">
<?php foreach ($idCards as $path): ?>
<a href="<?= e('/' . $path) ?>" target="_blank">
<img src="<?= e('/' . $path) ?>" alt="ID Card" class="img-thumbnail" style="max-height: 150px;">
</a>
<?php endforeach; ?>
</div>
<h6><?= e(t('truck_reg')) ?></h6>
<div class="d-flex gap-2 mb-3 overflow-auto">
<?php foreach ($regs as $path): ?>
<a href="<?= e('/' . $path) ?>" target="_blank">
<img src="<?= e('/' . $path) ?>" alt="Registration" class="img-thumbnail" style="max-height: 150px;">
</a>
<?php endforeach; ?>
</div>
<h6><?= e(t('truck_picture')) ?></h6>
<?php if ($pic): ?>
<div>
<a href="<?= e('/' . $pic) ?>" target="_blank">
<img src="<?= e('/' . $pic) ?>" alt="Truck Pic" class="img-thumbnail" style="max-height: 250px;">
</a>
</div>
<?php else: ?>
<span class="text-muted"><?= e(t('no_picture')) ?></span>
<?php endif; ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<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');
// Reset to loading state
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 the response is a JSON error (e.g. invalid ID), we should handle it.
// But simplified: assuming HTML partial.
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;
}
// Inject HTML
// We strip the outer .modal-content if the partial includes it?
// No, the partial returns the BODY and FOOTER usually.
// My partial in edit php returns: <form>...<modal-header>...<modal-body>...<modal-footer>...</form>
// So I should replace .modal-content content.
// Wait, <form> cannot be a child of <div class="modal-content"> directly if it contains modal-header/body/footer?
// Yes, it can. <div class="modal-content"><form>...</form></div> is valid.
modalContent.innerHTML = html;
// Execute scripts in the injected 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);
});
// Bind form submission
const form = modalContent.querySelector('form');
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
const submitBtn = form.querySelector('button[type="submit"]');
if(submitBtn) {
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) {
// Refresh the page to show updates
location.reload();
} else {
if(submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = '<?= 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.textContent = '<?= 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(); ?>