datetime('now', '-4 days'))"; $params = []; if ($audience === 'vendor') { $where = "j.vendor_id = ? AND ($where)"; $params[] = $vendor_id; } $sql = " SELECT j.*, v.slug AS vendor_slug, v.name AS vendor_name, CASE WHEN j.due_date IS NULL THEN '' ELSE CAST(CAST(strftime('%m', j.due_date) AS INTEGER) AS TEXT) || '-' || CAST(CAST(strftime('%d', j.due_date) AS INTEGER) AS TEXT) END AS due_short, (SELECT MAX(changed_at) FROM job_history WHERE job_id = j.id AND field = 'status' AND new_value = 'Finished') AS finished_at, (SELECT MAX(changed_at) FROM job_history WHERE job_id = j.id AND field = 'status' AND new_value = 'Shipped') AS shipped_at, (SELECT MAX(changed_at) FROM job_history WHERE job_id = j.id AND field = 'status' AND new_value = 'Received') AS received_at FROM jobs j JOIN vendors v ON v.id = j.vendor_id WHERE $where ORDER BY (j.status = '') DESC, (j.status = 'Finished') DESC, (j.status = 'Shipped') DESC, (j.ack = 'new') DESC, j.due_date IS NULL, j.due_date ASC, j.job "; $stmt = $pdo->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(); ob_start(); ?> 9 ? 'smaller' : ''; $statusClass = $r['status'] !== '' ? 'status-' . strtolower($r['status']) : ''; $ackClass = $r['ack'] === 'new' ? 'ack-new' : ''; ?>
Job Material Description Qty Due Vendor Ack Status Action
No active jobs.
' . ($val === '' ? '—' : h($val)) . ''; } function render_due(array $r, bool $editable): string { $display = $r['due_short'] !== '' ? h($r['due_short']) : '—'; if (!$editable) return $display; return '' . $display . ''; } function render_status(array $r, string $audience): string { $out = h($r['status']); $stamp = null; if ($audience === 'ICG') { if ($r['status'] === 'Finished') $stamp = $r['finished_at'] ?? null; elseif ($r['status'] === 'Shipped') $stamp = $r['shipped_at'] ?? null; } else { // vendor if ($r['status'] === 'Received') $stamp = $r['received_at'] ?? null; } if ($stamp) { $out .= '
' . h(format_short_date($stamp)) . '
'; } return $out; } // Convert a UTC 'YYYY-MM-DD HH:MM:SS' from datetime('now') into 'M/D' in the // server's local timezone for display. function format_short_date(?string $ts): string { if (!$ts) return ''; try { $dt = new DateTimeImmutable($ts . ' UTC'); return $dt->setTimezone(new DateTimeZone(date_default_timezone_get()))->format('n/j'); } catch (Exception $e) { return ''; } } function render_actions(array $r, string $audience): string { $btns = []; if ($audience === 'vendor') { if ($r['ack'] === 'new') { $btns[] = ''; } if ($r['status'] === '') { $btns[] = ''; } elseif ($r['status'] === 'Finished') { $btns[] = ''; } if (in_array($r['status'], ['', 'Finished'], true) && (int) $r['qty'] > 1) { $btns[] = ''; } } else { // ICG if ($r['status'] === 'Shipped') { $btns[] = ''; } if ($r['status'] === 'Received') { $btns[] = 'Received'; } } return implode(' ', $btns); } function render_messages(int $vendor_id, ?int $since_id = null): string { $pdo = db(); if ($since_id !== null) { $stmt = $pdo->prepare('SELECT * FROM messages WHERE vendor_id = ? AND id > ? ORDER BY id ASC'); $stmt->execute([$vendor_id, $since_id]); } else { $stmt = $pdo->prepare('SELECT * FROM messages WHERE vendor_id = ? ORDER BY id ASC'); $stmt->execute([$vendor_id]); } $rows = $stmt->fetchAll(); ob_start(); foreach ($rows as $m) { $klass = $m['author'] === 'ICG' ? 'msg msg-icg' : 'msg msg-vendor'; echo '
'; echo '' . h($m['author']) . ''; echo '' . h($m['posted_at']) . ''; echo '
' . nl2br(h($m['body'])) . '
'; echo '
'; } return ob_get_clean(); } function max_message_id(int $vendor_id): int { $stmt = db()->prepare('SELECT COALESCE(MAX(id), 0) FROM messages WHERE vendor_id = ?'); $stmt->execute([$vendor_id]); return (int) $stmt->fetchColumn(); }