feat: filter jobcards by technician and status

This commit is contained in:
Marco0300
2026-09-02 00:56:25 +02:00
parent 82ccbc4d54
commit c655701ddc
+8 -4
View File
@@ -119,7 +119,7 @@ if ($route === 'saved_filter_save') {
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { http_response_code(405); exit('Save requires POST'); }
verify_csrf(); $name = trim(scalar_input($_POST['filter_name'] ?? null));
if ($name === '' || mb_strlen($name) > 120) { http_response_code(400); exit('Filter name is required.'); }
$filterJson = json_encode(['q' => trim(scalar_input($_POST['q'] ?? null)), 'status_filter' => scalar_input($_POST['status_filter'] ?? 'open')], JSON_THROW_ON_ERROR);
$filterJson = json_encode(['q' => trim(scalar_input($_POST['q'] ?? null)), 'status_filter' => scalar_input($_POST['status_filter'] ?? 'open'), 'technician_id' => filter_var(scalar_input($_POST['technician_id'] ?? null), FILTER_VALIDATE_INT) ?: null], JSON_THROW_ON_ERROR);
db()->prepare('INSERT INTO saved_filters (user_id, name, route, filter_json) VALUES (:user, :name, :route, :filters) ON DUPLICATE KEY UPDATE filter_json = VALUES(filter_json)')->execute(['user' => $user['id'], 'name' => $name, 'route' => 'jobcards', 'filters' => $filterJson]);
header('Location: /?route=jobcards&saved=1'); exit;
}
@@ -442,7 +442,9 @@ if ($route === 'jobcards') {
$search = trim(scalar_input($_GET['q'] ?? ($_SESSION['jobcard_filters']['q'] ?? null)));
$statusFilter = scalar_input($_GET['status_filter'] ?? ($_SESSION['jobcard_filters']['status_filter'] ?? null), 'open');
if (!in_array($statusFilter, ['open', 'in_progress', 'assigned', 'closed'], true)) $statusFilter = 'open';
$_SESSION['jobcard_filters'] = ['q' => $search, 'status_filter' => $statusFilter];
$technicianFilter = filter_var(scalar_input($_GET['technician_id'] ?? ($_SESSION['jobcard_filters']['technician_id'] ?? null)), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($technicianFilter === false) $technicianFilter = null;
$_SESSION['jobcard_filters'] = ['q' => $search, 'status_filter' => $statusFilter, 'technician_id' => $technicianFilter];
$page = max(1, (int)filter_var(scalar_input($_GET['page'] ?? null), FILTER_VALIDATE_INT));
$offset = ($page - 1) * 25;
$savedFilterStmt = db()->prepare('SELECT id, name, filter_json FROM saved_filters WHERE user_id = :user AND route = :route ORDER BY name'); $savedFilterStmt->execute(['user' => $user['id'], 'route' => 'jobcards']); $savedFilters = $savedFilterStmt->fetchAll();
@@ -452,6 +454,8 @@ if ($route === 'jobcards') {
if ($statusFilter === 'closed') { $conditions[] = "j.status = 'closed'"; } elseif ($statusFilter === 'in_progress') { $conditions[] = "j.status = 'in_progress'"; } elseif ($statusFilter === 'assigned') { $conditions[] = "j.status = 'assigned'"; } else { $conditions[] = "j.status <> 'closed'"; }
$scopeJoin = '';
if ($user['role_name'] === 'Technician') { $scopeJoin = ' JOIN jobcard_assignments ja ON ja.jobcard_id = j.id AND ja.user_id = ?'; array_unshift($params, (int)$user['id']); }
elseif ($technicianFilter !== null) { $scopeJoin = ' JOIN jobcard_assignments ja_filter ON ja_filter.jobcard_id = j.id AND ja_filter.user_id = ?'; array_unshift($params, $technicianFilter); }
$technicianOptions = $user['role_name'] === 'Technician' ? [] : db()->query("SELECT u.id, u.name FROM users u JOIN roles r ON r.id = u.role_id WHERE u.is_active = 1 AND r.name = 'Technician' ORDER BY u.name")->fetchAll();
if ($user['role_name'] === 'Technician') {
$clientListForJobcard = db()->prepare("SELECT DISTINCT c.id, c.name FROM clients c JOIN jobcards j ON j.client_id = c.id JOIN jobcard_assignments ja ON ja.jobcard_id = j.id AND ja.user_id = :user WHERE c.status = 'active' ORDER BY c.name");
$clientListForJobcard->execute(['user' => $user['id']]);
@@ -468,12 +472,12 @@ if ($route === 'jobcards') {
echo '</div>';
if (isset($_GET['created'])) echo '<div class="alert alert-success">Jobcard created successfully.</div>';
if ($errors) echo '<div class="alert alert-danger">' . e(implode(' ', $errors)) . '</div>';
echo '<form class="row g-2 mb-2" method="get"><input type="hidden" name="route" value="jobcards"><div class="col-md-6"><input class="form-control" name="q" value="' . e($search) . '" placeholder="Search reference, client or requested work"></div><div class="col-md-3"><select class="form-select" name="status_filter"><option value="open"' . ($statusFilter === 'open' ? ' selected' : '') . '>Open jobcards</option><option value="in_progress"' . ($statusFilter === 'in_progress' ? ' selected' : '') . '>In progress</option><option value="assigned"' . ($statusFilter === 'assigned' ? ' selected' : '') . '>Assigned</option><option value="closed"' . ($statusFilter === 'closed' ? ' selected' : '') . '>Closed</option></select></div><div class="col-md-3"><button class="btn btn-outline-primary w-100">Search and filter</button></div></form><div class="d-flex flex-wrap gap-2 mb-4"><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=open">My open jobs</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=assigned">Assigned</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=in_progress">In progress</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=closed">Closed</a><form method="post" action="/?route=saved_filter_save" class="d-flex gap-2"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><input type="hidden" name="q" value="' . e($search) . '"><input type="hidden" name="status_filter" value="' . e($statusFilter) . '"><input class="form-control form-control-sm" name="filter_name" placeholder="Save filter as..." maxlength="120" required><button class="btn btn-sm btn-outline-secondary">Save</button></form>'; foreach ($savedFilters as $saved) { $savedData = json_decode((string)$saved['filter_json'], true); if (is_array($savedData)) echo '<a class="btn btn-sm btn-outline-secondary" href="/?route=jobcards&q=' . urlencode((string)($savedData['q'] ?? '')) . '&status_filter=' . urlencode((string)($savedData['status_filter'] ?? 'open')) . '">' . e($saved['name']) . '</a>'; } echo '</div>';
echo '<form class="row g-2 mb-2" method="get"><input type="hidden" name="route" value="jobcards"><div class="col-md-6"><input class="form-control" name="q" value="' . e($search) . '" placeholder="Search reference, client or requested work"></div><div class="col-md-3"><select class="form-select" name="status_filter"><option value="open"' . ($statusFilter === 'open' ? ' selected' : '') . '>Open jobcards</option><option value="in_progress"' . ($statusFilter === 'in_progress' ? ' selected' : '') . '>In progress</option><option value="assigned"' . ($statusFilter === 'assigned' ? ' selected' : '') . '>Assigned</option><option value="closed"' . ($statusFilter === 'closed' ? ' selected' : '') . '>Closed</option></select></div><div class="col-md-3"><button class="btn btn-outline-primary w-100">Search and filter</button></div></form><form class="row g-2 mb-2" method="get"><input type="hidden" name="route" value="jobcards"><input type="hidden" name="q" value="' . e($search) . '"><input type="hidden" name="status_filter" value="' . e($statusFilter) . '"><div class="col-md-6"><select class="form-select" name="technician_id"><option value="">All technicians</option>'; foreach ($technicianOptions as $technicianOption) echo '<option value="' . (int)$technicianOption['id'] . '"' . ((int)($technicianFilter ?? 0) === (int)$technicianOption['id'] ? ' selected' : '') . '>' . e($technicianOption['name']) . '</option>'; echo '</select></div><div class="col-md-3"><button class="btn btn-outline-secondary w-100">Filter technician</button></div></form><div class="d-flex flex-wrap gap-2 mb-4"><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=open">My open jobs</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=assigned">Assigned</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=in_progress">In progress</a><a class="btn btn-sm btn-light" href="/?route=jobcards&status_filter=closed">Closed</a><form method="post" action="/?route=saved_filter_save" class="d-flex gap-2"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><input type="hidden" name="q" value="' . e($search) . '"><input type="hidden" name="status_filter" value="' . e($statusFilter) . '"><input type="hidden" name="technician_id" value="' . e((string)($technicianFilter ?? '')) . '"><input class="form-control form-control-sm" name="filter_name" placeholder="Save filter as..." maxlength="120" required><button class="btn btn-sm btn-outline-secondary">Save</button></form>'; foreach ($savedFilters as $saved) { $savedData = json_decode((string)$saved['filter_json'], true); if (is_array($savedData)) echo '<a class="btn btn-sm btn-outline-secondary" href="/?route=jobcards&q=' . urlencode((string)($savedData['q'] ?? '')) . '&status_filter=' . urlencode((string)($savedData['status_filter'] ?? 'open')) . '">' . e($saved['name']) . '</a>'; } echo '</div>';
if (can('jobcards.manage')) { echo '<div class="collapse mb-4" id="new-jobcard"><div class="card"><div class="card-body"><h2 class="h5">Create jobcard</h2><form method="post" class="row g-3"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><div class="col-md-6"><label class="form-label" for="jobcard-client">Client</label><select class="form-select" id="jobcard-client" name="client_id" required><option value="">Choose client</option>'; foreach ($clients as $client) echo '<option value="' . (int)$client['id'] . '">' . e($client['name']) . '</option>'; echo '</select></div><div class="col-md-3"><label class="form-label" for="jobcard-priority">Priority</label><select class="form-select" id="jobcard-priority" name="priority"><option>low</option><option selected>normal</option><option>high</option><option>critical</option></select></div><div class="col-12"><label class="form-label" for="work-requested">Work requested</label><textarea class="form-control" id="work-requested" name="work_requested" rows="4" maxlength="10000" required></textarea></div><div class="col-12"><button class="btn btn-primary">Create jobcard</button></div></form></div></div></div>'; }
echo '<div class="card"><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead><tr><th>Reference</th><th>Client</th><th>Priority</th><th>Status</th><th>Work requested</th><th>Created</th></tr></thead><tbody>';
if (!$jobcards) echo '<tr><td colspan="6" class="text-center text-muted py-4">No jobcards found.</td></tr>';
foreach ($jobcards as $jobcard) echo '<tr><td class="fw-semibold"><a class="text-decoration-none" href="/?route=jobcard&id=' . (int)$jobcard['id'] . '">' . e($jobcard['reference_no']) . '</a></td><td>' . e($jobcard['client_name']) . '</td><td>' . e(ucfirst($jobcard['priority'])) . '</td><td>' . e(ucwords(str_replace('_', ' ', $jobcard['status']))) . '</td><td class="text-truncate" style="max-width: 320px">' . e($jobcard['work_requested']) . '</td><td>' . e($jobcard['created_at']) . '</td></tr>';
echo '</tbody></table></div></div><div class="d-flex justify-content-between mt-3"><a class="btn btn-sm btn-outline-secondary' . ($page <= 1 ? ' disabled' : '') . '" href="/?route=jobcards&q=' . urlencode($search) . '&status_filter=' . urlencode($statusFilter) . '&page=' . max(1, $page - 1) . '">Previous</a><a class="btn btn-sm btn-outline-secondary' . (count($jobcards) < 25 ? ' disabled' : '') . '" href="/?route=jobcards&q=' . urlencode($search) . '&status_filter=' . urlencode($statusFilter) . '&page=' . ($page + 1) . '">Next</a></div>';
echo '</tbody></table></div></div><div class="d-flex justify-content-between mt-3"><a class="btn btn-sm btn-outline-secondary' . ($page <= 1 ? ' disabled' : '') . '" href="/?route=jobcards&q=' . urlencode($search) . '&status_filter=' . urlencode($statusFilter) . '&technician_id=' . urlencode((string)($technicianFilter ?? '')) . '&page=' . max(1, $page - 1) . '">Previous</a><a class="btn btn-sm btn-outline-secondary' . (count($jobcards) < 25 ? ' disabled' : '') . '" href="/?route=jobcards&q=' . urlencode($search) . '&status_filter=' . urlencode($statusFilter) . '&technician_id=' . urlencode((string)($technicianFilter ?? '')) . '&page=' . ($page + 1) . '">Next</a></div>';
render_footer(); exit;
}