|
|
@@ -0,0 +1,157 @@
|
|
|
+<?php
|
|
|
+require_once __DIR__ . '/lib/identity.php';
|
|
|
+require_once __DIR__ . '/lib/render.php';
|
|
|
+
|
|
|
+$actor = current_actor('ICG');
|
|
|
+
|
|
|
+$job_filter = isset($_GET['job']) ? (int) $_GET['job'] : 0;
|
|
|
+$limit = isset($_GET['limit']) ? max(50, min(1000, (int) $_GET['limit'])) : 200;
|
|
|
+
|
|
|
+$where = '1=1';
|
|
|
+$params = [];
|
|
|
+if ($job_filter > 0) {
|
|
|
+ $where = 'h.job_id = ?';
|
|
|
+ $params[] = $job_filter;
|
|
|
+}
|
|
|
+
|
|
|
+$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 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 === '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;
|
|
|
+}
|
|
|
+?><!doctype html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+<meta charset="utf-8">
|
|
|
+<title>Activity Log</title>
|
|
|
+<link rel="stylesheet" href="assets/app.css">
|
|
|
+<style>
|
|
|
+table.log {
|
|
|
+ border-collapse: collapse;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1100px;
|
|
|
+ font-size: 0.95rem;
|
|
|
+}
|
|
|
+table.log th, table.log td {
|
|
|
+ padding: 0.4rem 0.7rem;
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
+ text-align: left;
|
|
|
+ vertical-align: top;
|
|
|
+}
|
|
|
+table.log th {
|
|
|
+ background: var(--c-accent-strong);
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.05em;
|
|
|
+}
|
|
|
+.log-ts { white-space: nowrap; color: var(--c-muted); font-variant-numeric: tabular-nums; }
|
|
|
+.log-actor { font-weight: 600; }
|
|
|
+.log-actor-icg { color: var(--c-accent-strong); }
|
|
|
+.log-actor-vend { color: var(--c-info); }
|
|
|
+.log-job { font-weight: 600; }
|
|
|
+.log-job a { color: inherit; text-decoration: none; }
|
|
|
+.log-job a:hover { text-decoration: underline; }
|
|
|
+.log-filter {
|
|
|
+ margin: 0.5rem 0 1rem;
|
|
|
+ color: var(--c-muted);
|
|
|
+}
|
|
|
+.log-filter a { color: var(--c-accent-strong); }
|
|
|
+.log-desc { max-width: 24em; }
|
|
|
+.log-muted { color: var(--c-muted); }
|
|
|
+</style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+
|
|
|
+<div class="topbar">
|
|
|
+ <h1>Activity Log</h1>
|
|
|
+ <span class="who"><a href="PDQ.php">← Back to schedule</a></span>
|
|
|
+</div>
|
|
|
+
|
|
|
+<?php if ($job_filter > 0): ?>
|
|
|
+<p class="log-filter">
|
|
|
+ Showing events for job #<?= (int) $job_filter ?>.
|
|
|
+ <a href="log.php">Show everything →</a>
|
|
|
+</p>
|
|
|
+<?php else: ?>
|
|
|
+<p class="log-filter">Latest <?= count($rows) ?> events. Click a job number to filter.</p>
|
|
|
+<?php endif; ?>
|
|
|
+
|
|
|
+<table class="log">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>When</th>
|
|
|
+ <th>Who</th>
|
|
|
+ <th>Job</th>
|
|
|
+ <th>Description</th>
|
|
|
+ <th>Event</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <?php if (!$rows): ?>
|
|
|
+ <tr><td colspan="5" class="empty">No events recorded.</td></tr>
|
|
|
+ <?php endif; ?>
|
|
|
+ <?php foreach ($rows as $h):
|
|
|
+ $actorKlass = $h['actor'] === 'ICG' ? 'log-actor-icg' : 'log-actor-vend';
|
|
|
+ $jobLabel = $h['job_label'] !== null ? '#' . (int) $h['job_id'] . ' ' . h($h['job_label']) : '#' . (int) $h['job_id'] . ' (deleted)';
|
|
|
+ $desc = (string) ($h['job_description'] ?? '');
|
|
|
+ ?>
|
|
|
+ <tr>
|
|
|
+ <td class="log-ts"><?= fmt_long_date($h['changed_at']) ?></td>
|
|
|
+ <td class="log-actor <?= $actorKlass ?>"><?= h($h['actor']) ?></td>
|
|
|
+ <td class="log-job"><a href="?job=<?= (int) $h['job_id'] ?>"><?= $jobLabel ?></a></td>
|
|
|
+ <td class="log-desc"><?= $desc === '' ? '<span class="log-muted">—</span>' : h($desc) ?></td>
|
|
|
+ <td><?= fmt_event($h) ?></td>
|
|
|
+ </tr>
|
|
|
+ <?php endforeach; ?>
|
|
|
+ </tbody>
|
|
|
+</table>
|
|
|
+
|
|
|
+</body>
|
|
|
+</html>
|