0) { $clauses[] = 'h.job_id = ?'; $params[] = $job_filter; } if ($q !== '') { // SQLite LIKE is case-insensitive for ASCII. Escape user wildcards so a // literal "%" or "_" doesn't blow the search open. $needle = '%' . strtr($q, ['\\' => '\\\\', '%' => '\\%', '_' => '\\_']) . '%'; $clauses[] = "(j.job LIKE ? ESCAPE '\\' OR j.material LIKE ? ESCAPE '\\' OR j.description LIKE ? ESCAPE '\\' OR v.name LIKE ? ESCAPE '\\' OR h.actor LIKE ? ESCAPE '\\' OR h.field LIKE ? ESCAPE '\\' OR h.old_value LIKE ? ESCAPE '\\' OR h.new_value LIKE ? ESCAPE '\\')"; array_push($params, $needle, $needle, $needle, $needle, $needle, $needle, $needle, $needle); } $where = $clauses ? implode(' AND ', $clauses) : '1=1'; $sql = " SELECT h.*, j.job AS job_label, j.description AS job_description, v.slug AS vendor_slug, v.name AS vendor_name FROM job_history h LEFT JOIN jobs j ON j.id = h.job_id LEFT JOIN vendors v ON v.id = j.vendor_id WHERE $where ORDER BY h.id DESC LIMIT $limit "; $stmt = db()->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(); function preserve_qs(array $keep): string { if (!$keep) return ''; return '?' . http_build_query($keep); } function fmt_long_date(?string $ts): string { if (!$ts) return ''; try { $dt = new DateTimeImmutable($ts . ' UTC'); return $dt->setTimezone(new DateTimeZone(date_default_timezone_get())) ->format('Y-m-d g:i a'); } catch (Exception $e) { return h($ts); } } function fmt_event(array $h): string { $f = $h['field']; $old = $h['old_value']; $new = $h['new_value']; if ($f === 'created') { return 'Created (vendor: ' . h((string) $new) . ')'; } if ($f === 'deleted') { return 'Deleted'; } if ($f === 'partial_ship') { return 'Partial ship: split off ' . h((string) $new) . ' (was qty ' . h((string) $old) . ')'; } $labels = [ 'ack' => 'Acknowledgement', 'status' => 'Status', 'job' => 'Job #', 'material' => 'Material', 'description' => 'Description', 'qty' => 'Qty', 'due_date' => 'Due date', ]; $label = $labels[$f] ?? $f; $oldDisp = ($old === null || $old === '') ? '∅' : h((string) $old); $newDisp = ($new === null || $new === '') ? '∅' : h((string) $new); return $label . ': ' . $oldDisp . ' → ' . $newDisp; } ?>
0) { $bits[] = 'job #' . (int) $job_filter . ''; } if ($q !== '') { $bits[] = 'matching “' . h($q) . '”'; } if ($bits) { echo count($rows) . ' event' . (count($rows) === 1 ? '' : 's') . ' ' . implode(' · ', $bits) . '.'; if ($job_filter > 0) { echo ' drop job filter'; } if ($q !== '') { echo ' drop search'; } } else { echo 'Latest ' . count($rows) . ' events. Click a job number to filter, or search above.'; } ?>
| When | Who | Job | Description | Event |
|---|---|---|---|---|
| No events recorded. | ||||
| = fmt_long_date($h['changed_at']) ?> | = h($h['actor']) ?> | = $jobLabel ?> | = $desc === '' ? '—' : h($desc) ?> | = fmt_event($h) ?> |