'[+]', 'update' => '[~]', 'comment' => '[>]', 'view' => '[.]', 'assign' => '[@]', 'status_change' => '[!]', 'attachment' => '[^]', 'delete' => '[x]', default => '[*]', }; } function formatAction(array $event): string { $det = $event['details'] ?? []; switch ($event['action_type']) { case 'create': if (($event['entity_type'] ?? '') === 'comment') return 'posted a comment'; return 'created this ticket'; case 'comment': return 'posted a comment'; case 'view': return 'viewed this ticket'; case 'attachment': return 'uploaded a file'; case 'delete': return 'deleted a comment'; case 'assign': if (is_array($det) && isset($det['assigned_to']['to'])) { $to = $det['assigned_to']['to'] ?: 'Unassigned'; return 'assigned to ' . htmlspecialchars($to); } return 'assigned this ticket'; case 'status_change': if (is_array($det) && isset($det['status']['from'], $det['status']['to'])) { return htmlspecialchars($det['status']['from']) . ' → ' . htmlspecialchars($det['status']['to']); } return 'changed the status'; case 'update': if (is_array($det)) { $fields = array_keys(array_filter($det, fn($v) => is_array($v) && isset($v['from'], $v['to']))); if ($fields) return 'updated ' . implode(', ', array_map(fn($f) => str_replace('_', ' ', $f), $fields)); } return 'updated this ticket'; default: return htmlspecialchars($event['action_type']); } } // Calculate ticket age from creation (not last update) $ageSeconds = time() - strtotime($ticket['created_at']); $ageDays = floor($ageSeconds / 86400); $ageHours = floor(($ageSeconds % 86400) / 3600); $ageClass = 'lt-text-muted'; if ($ticket['status'] !== 'Closed') { if ($ageDays >= 10) $ageClass = 'lt-text-danger'; elseif ($ageDays >= 5) $ageClass = 'lt-text-amber'; } $ageStr = $ageDays > 0 ? $ageDays . ' day' . ($ageDays !== 1 ? 's' : '') : $ageHours . ' hour' . ($ageHours !== 1 ? 's' : ''); $statusSlug = strtolower(str_replace(' ', '-', $ticket['status'])); $priorityNum = (int)($ticket['priority'] ?? 3); $currentUserId = $GLOBALS['currentUser']['user_id'] ?? null; $isAdmin = $GLOBALS['currentUser']['is_admin'] ?? false; $creator = $ticket['creator_display_name'] ?? $ticket['creator_username'] ?? 'System'; // Visibility $currentVisibility = $ticket['visibility'] ?? 'public'; $currentVisibilityGroups = array_filter(array_map('trim', explode(',', $ticket['visibility_groups'] ?? ''))); require_once __DIR__ . '/../models/UserModel.php'; $visUserModel = new UserModel($conn); $allAvailableGroups = $visUserModel->getAllGroups(); // JSON-encode ticket fields for the inline script $json_ticket_id = json_encode($ticket['ticket_id'], JSON_HEX_TAG); $json_title = json_encode($ticket['title'], JSON_HEX_TAG); $json_status = json_encode($ticket['status'], JSON_HEX_TAG); $json_priority = json_encode($ticket['priority'], JSON_HEX_TAG); $json_category = json_encode($ticket['category'], JSON_HEX_TAG); $json_type = json_encode($ticket['type'], JSON_HEX_TAG); $json_updated_at = json_encode($ticket['updated_at'], JSON_HEX_TAG); $json_total_comments = json_encode((int)$totalComments, JSON_HEX_TAG); $json_comment_page = json_encode((int)$commentPageSize, JSON_HEX_TAG); $json_current_uid = json_encode((int)($currentUser['user_id'] ?? 0), JSON_HEX_TAG); $json_is_admin = json_encode(!empty($currentUser['is_admin']), JSON_HEX_TAG); $pageInlineScript = <<
'lt-dot-up', 'In Progress' => 'lt-dot-warn', 'Pending' => 'lt-dot--orange', 'Closed' => 'lt-dot-idle', default => 'lt-dot-idle', }; ?> EXPORT
8, 2 => 24, default => 72 }; $elapsedSeconds = time() - strtotime($ticket['created_at']); $elapsedHours = round($elapsedSeconds / 3600, 1); $slaPct = min(100, round(($elapsedSeconds / ($slaTargetHours * 3600)) * 100)); $slaBreached = $elapsedSeconds >= ($slaTargetHours * 3600); $alertClass = $priorityNum === 1 ? 'lt-alert--error' : 'lt-alert--warning'; $alertIcon = $priorityNum === 1 ? '[ ! ]' : '[ ~ ]'; $alertLabel = $priorityNum === 1 ? 'CRITICAL — P1 Ticket' : 'HIGH PRIORITY — P2 Ticket'; $progressClass = $slaBreached ? 'lt-progress--red' : ($slaPct >= 75 ? 'lt-progress--red' : 'lt-progress--green'); ?>
Ticket Information

UUID
Priority
Category 'lt-tag--orange','Software'=>'lt-tag--cyan','Network'=>'lt-tag--purple','Security'=>'lt-tag--red',default=>'' }; ?>
Type 'lt-tag--orange','Issue'=>'lt-tag--red','Problem'=>'lt-tag--red','Upgrade'=>'lt-tag--purple','Install'=>'lt-tag--cyan',default=>'' }; ?>
Assigned To
Visibility
Age ago
Created By
Last Updated
No groups available
Description
Add Comment
Comment History
No comments yet. Be the first to comment.
(edited)' : ''; // Avatar initials + color (fallback when no photo) $words = array_filter(explode(' ', $displayName)); $initials = strtoupper(implode('', array_map(fn($w) => $w[0], array_slice($words, 0, 2)))); $avatarColors = ['lt-avatar--orange', 'lt-avatar--green', 'lt-avatar--purple', '']; $avatarColor = $avatarColors[abs(crc32($displayName)) % count($avatarColors)]; $commentUserId = (int)($comment['user_id'] ?? 0); ?>
>
$commentPageSize): ?>
Upload Files

Drag & drop files here or click to browse

Max size:

Attached Files

Loading attachments…

Add Dependency
Current Dependencies

Loading…

Tickets That Depend On This

Loading…

Activity Timeline
No activity recorded yet.
'lt-timeline-item--green', 'status_change' => 'lt-timeline-item--cyan', 'comment' => 'lt-timeline-item--green', 'assign' => 'lt-timeline-item--orange', 'attachment' => 'lt-timeline-item--orange', 'update' => '', 'delete' => 'lt-timeline-item--red', default => 'lt-timeline-item--dim', }; ?>
$v) { if (is_array($v) && isset($v['from'], $v['to'])) { $label = ucfirst(str_replace('_', ' ', $k)); $from = mb_strlen((string)$v['from']) > 60 ? mb_substr((string)$v['from'], 0, 60) . '…' : (string)$v['from']; $to = mb_strlen((string)$v['to']) > 60 ? mb_substr((string)$v['to'], 0, 60) . '…' : (string)$v['to']; $parts[] = '' . htmlspecialchars($label) . ': ' . '' . htmlspecialchars($from) . '' . ' ' . '' . htmlspecialchars($to) . ''; } elseif (!in_array($k, ['old_value', 'new_value'], true)) { $parts[] = '' . htmlspecialchars($k) . ': ' . htmlspecialchars((string)$v); } } if ($parts) echo implode('
', $parts); } ?>