{% set page_title = (ticket.ticketNumber ?? 'Ticket') ~ ' | HoneyBee' %}
{% include '@Application/inc/central_header.html.twig' %}
{% include '@CompanyGroup/pages/admin/_sidebar.html.twig' %}
<style>
.ticket-view-section { padding: 2rem 0 4rem; }
/* Breadcrumb */
.breadcrumb-bar { font-size: 0.82rem; color: #888; margin-bottom: 1.5rem; }
.breadcrumb-bar a { color: #1d5b9e; text-decoration: none; }
.breadcrumb-bar a:hover { text-decoration: underline; }
/* Header card */
.ticket-header-card {
background: #fff;
border: 1px solid #e3eaf3;
border-radius: 14px;
padding: 1.5rem 1.75rem;
margin-bottom: 1.5rem;
}
.tk-number {
font-size: 0.8rem; font-weight: 700;
color: #1d5b9e; background: #e8f0fb;
border-radius: 6px; padding: 3px 10px;
display: inline-block; margin-bottom: 0.6rem;
}
.tk-title {
font-size: 1.4rem; font-weight: 700; color: #1a1a2e; margin-bottom: 0.5rem;
}
/* Badges */
.badge-pill { font-size: 0.72rem; font-weight: 600; border-radius: 50px; padding: 3px 10px; display: inline-block; }
.badge-status-1 { background: #e3f0ff; color: #1d5b9e; }
.badge-status-2 { background: #fff3cd; color: #856404; }
.badge-status-3 { background: #d1f7e7; color: #1a7a4a; }
.badge-status-4 { background: #e9ecef; color: #6c757d; }
.badge-priority-1 { background: #fde8e8; color: #c0392b; }
.badge-priority-2 { background: #fff3cd; color: #856404; }
.badge-priority-3 { background: #e9ecef; color: #555; }
.badge-source-system { background: #f0e6ff; color: #6f42c1; }
.badge-source-manual { background: #e3f0ff; color: #1d5b9e; }
/* Info grid */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
margin-top: 1.25rem;
}
.info-item label {
font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: #aaa; display: block; margin-bottom: 3px;
}
.info-item span { font-size: 0.9rem; color: #333; font-weight: 500; }
/* Detail card */
.detail-card {
background: #fff;
border: 1px solid #e3eaf3;
border-radius: 14px;
padding: 1.5rem 1.75rem;
margin-bottom: 1.5rem;
}
.detail-card h5 {
font-size: 0.88rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.05em; color: #1d5b9e; margin-bottom: 1rem;
padding-bottom: 0.5rem; border-bottom: 1px solid #e3eaf3;
}
.description-body {
font-size: 0.92rem; color: #444; line-height: 1.7;
white-space: pre-wrap;
}
/* System tracking card */
.system-card {
background: #fcfaff;
border: 1px solid #e0d4f7;
border-radius: 14px;
padding: 1.5rem 1.75rem;
margin-bottom: 1.5rem;
}
.system-card h5 {
font-size: 0.88rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.05em; color: #6f42c1; margin-bottom: 1rem;
padding-bottom: 0.5rem; border-bottom: 1px solid #e0d4f7;
}
.hash-code {
font-family: monospace; font-size: 0.8rem;
background: #f4eeff; color: #6f42c1;
border-radius: 6px; padding: 4px 10px;
word-break: break-all;
}
.payload-pre {
background: #1a1a2e; color: #a8d8a8;
border-radius: 8px; padding: 1rem;
font-size: 0.78rem; overflow-x: auto;
max-height: 300px; font-family: monospace;
}
/* Timeline */
.timeline { position: relative; padding-left: 1.75rem; margin-top: 0.5rem; }
.timeline::before {
content: ''; position: absolute;
left: 7px; top: 0; bottom: 0;
width: 2px; background: #e3eaf3;
}
.tl-item { position: relative; margin-bottom: 1.25rem; }
.tl-dot {
position: absolute; left: -1.75rem; top: 3px;
width: 14px; height: 14px; border-radius: 50%;
background: #1d5b9e; border: 2px solid #fff;
box-shadow: 0 0 0 2px #1d5b9e;
}
.tl-dot.error { background: #c0392b; box-shadow: 0 0 0 2px #c0392b; }
.tl-time { font-size: 0.75rem; color: #aaa; margin-bottom: 2px; }
.tl-server { font-size: 0.75rem; color: #888; }
.tl-message { font-size: 0.875rem; color: #333; font-weight: 500; }
.tl-code {
display: inline-block; font-size: 0.72rem; font-weight: 600;
background: #fde8e8; color: #c0392b;
border-radius: 4px; padding: 1px 7px; margin-top: 3px;
}
.tl-payload-toggle {
font-size: 0.75rem; color: #1d5b9e;
cursor: pointer; text-decoration: underline;
margin-top: 4px; display: inline-block;
}
.tl-payload {
display: none; background: #1a1a2e; color: #a8d8a8;
border-radius: 6px; padding: 0.75rem; font-size: 0.75rem;
font-family: monospace; margin-top: 6px; overflow-x: auto;
}
/* Action bar */
.action-bar {
background: #fff; border: 1px solid #e3eaf3;
border-radius: 12px; padding: 1rem 1.5rem;
margin-bottom: 1.5rem;
display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center;
}
.btn-hb {
border-radius: 50px; font-size: 0.82rem; font-weight: 600;
padding: 0.4rem 1.1rem; border: none; cursor: pointer;
text-decoration: none; display: inline-block;
}
.btn-hb-primary { background: #1d5b9e; color: #fff; }
.btn-hb-primary:hover { background: #174f8a; color: #fff; }
.btn-hb-success { background: #1a7a4a; color: #fff; }
.btn-hb-success:hover { background: #155d39; color: #fff; }
.btn-hb-danger { background: #c0392b; color: #fff; }
.btn-hb-danger:hover { background: #a93226; color: #fff; }
.btn-hb-outline { background: transparent; color: #1d5b9e; border: 1.5px solid #1d5b9e; }
.btn-hb-outline:hover { background: #e8f0fb; }
.count-chip {
display: inline-flex; align-items: center; gap: 4px;
font-size: 0.78rem; color: #c0392b; background: #fde8e8;
border-radius: 50px; padding: 3px 10px; font-weight: 600;
}
</style>
<div class="hb-admin-layout">
<section class="ticket-view-section">
<div class="container">
{# ── Breadcrumb ── #}
<div class="breadcrumb-bar">
<a href="{{ path('ticket_list') }}"><i class="fa fa-ticket-alt fa-xs me-1"></i>Tickets</a>
/
<span>{{ ticket.ticketNumber ?? 'View' }}</span>
</div>
{# ── Action bar ── #}
<div class="action-bar">
<a href="{{ path('ticket_edit', {id: ticket.id}) }}" class="btn-hb btn-hb-outline">
<i class="fa fa-edit-alt me-1"></i>Edit
</a>
{% if ticket.status == 1 or ticket.status == 2 %}
<a href="{{ path('ticket_resolve', {id: ticket.id}) }}" class="btn-hb btn-hb-success">
<i class="fa fa-check-circle me-1"></i>Mark Resolved
</a>
<a href="{{ path('ticket_close', {id: ticket.id}) }}" class="btn-hb btn-hb-danger">
<i class="fa fa-times-circle me-1"></i>Close
</a>
{% endif %}
{% if ticket.status == 1 %}
<a href="{{ path('ticket_assign', {id: ticket.id}) }}" class="btn-hb btn-hb-primary">
<i class="fa fa-user-check me-1"></i>Assign
</a>
{% endif %}
<a href="{{ path('ticket_list') }}" class="btn-hb btn-hb-outline ms-auto">
<i class="fa fa-arrow-left me-1"></i>Back to List
</a>
</div>
{# ── Header card ── #}
<div class="ticket-header-card">
<div class="tk-number">{{ ticket.ticketNumber }}</div>
<div class="tk-title">{{ ticket.title ?? '(No title)' }}</div>
<div class="d-flex gap-2 flex-wrap align-items-center">
{% set statusLabel = {1: 'Open', 2: 'In Progress', 3: 'Resolved', 4: 'Closed'} %}
{% set priorityLabel = {1: '🔴 Emergency', 2: '🟡 Important', 3: '🟢 Normal'} %}
<span class="badge-pill badge-status-{{ ticket.status ?? 1 }}">
{{ statusLabel[ticket.status] ?? 'Unknown' }}
</span>
<span class="badge-pill badge-priority-{{ ticket.urgency ?? 3 }}">
{{ priorityLabel[ticket.urgency] ?? 'Normal' }}
</span>
{% if ticket.systemCreated %}
<span class="badge-pill badge-source-system">
<i class="fa fa-robot fa-xs me-1"></i>System Generated
</span>
{% else %}
<span class="badge-pill badge-source-manual">
<i class="fa fa-user fa-xs me-1"></i>Manual
</span>
{% endif %}
{% if ticket.reportCount > 1 %}
<span class="count-chip">
<i class="fa fa-redo fa-xs"></i>
Reported {{ ticket.reportCount }} times
</span>
{% endif %}
</div>
<div class="info-grid">
<div class="info-item">
<label>Created</label>
<span>{{ ticket.createdAt ? ticket.createdAt|date('d M Y, H:i') : '—' }}</span>
</div>
<div class="info-item">
<label>First Reported</label>
<span>{{ ticket.firstReportedAt ? ticket.firstReportedAt|date('d M Y, H:i') : '—' }}</span>
</div>
<div class="info-item">
<label>Last Reported</label>
<span>{{ ticket.lastReportedAt ? ticket.lastReportedAt|date('d M Y, H:i') : '—' }}</span>
</div>
{% if ticket.deadlineDate %}
<div class="info-item">
<label>Deadline</label>
<span>{{ ticket.deadlineDate|date('d M Y') }}</span>
</div>
{% endif %}
{% if ticket.resolvedAt %}
<div class="info-item">
<label>Resolved At</label>
<span style="color:#1a7a4a">{{ ticket.resolvedAt|date('d M Y, H:i') }}</span>
</div>
{% endif %}
{% if ticket.closedAt %}
<div class="info-item">
<label>Closed At</label>
<span style="color:#6c757d">{{ ticket.closedAt|date('d M Y, H:i') }}</span>
</div>
{% endif %}
<div class="info-item">
<label>Reported By</label>
<span>{{ ticket.name ?? ('User #' ~ (ticket.userId ?? '—')) }}</span>
</div>
<div class="info-item">
<label>Assigned To</label>
<span>{{ ticket.assignedToUserId ? 'User #' ~ ticket.assignedToUserId : '—' }}</span>
</div>
{% if ticket.serverIdentifier %}
<div class="info-item">
<label>Server</label>
<span><i class="fa fa-server fa-xs me-1"></i>{{ ticket.serverIdentifier }}</span>
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-lg-8">
{# ── Description ── #}
<div class="detail-card">
<h5><i class="fa fa-align-left me-2"></i>Description</h5>
<div class="description-body">
{{ ticket.ticketBody ?? 'No description provided.' }}
</div>
</div>
{# ── Internal note ── #}
{% if ticket.note %}
<div class="detail-card">
<h5><i class="fa fa-sticky-note me-2"></i>Internal Note</h5>
<div class="description-body">{{ ticket.note }}</div>
</div>
{% endif %}
{# ── System payload (last) ── #}
{% if ticket.lastPayloadJson %}
<div class="system-card">
<h5><i class="fa fa-code me-2"></i>Last System Payload</h5>
<pre class="payload-pre">{{ ticket.lastPayloadJson }}</pre>
</div>
{% endif %}
{# ── Occurrence timeline ── #}
{% if reports is defined and reports|length > 0 %}
<div class="detail-card">
<h5>
<i class="fa fa-history me-2"></i>Occurrence Log
<span class="count-chip ms-2">{{ reports|length }} entries</span>
</h5>
<div class="timeline">
{% for report in reports %}
<div class="tl-item">
<div class="tl-dot {{ report.errorCode ? 'error' }}"></div>
<div class="tl-time">
<i class="fa fa-clock fa-xs me-1"></i>
{{ report.reportedAt ? report.reportedAt|date('d M Y, H:i:s') : '—' }}
</div>
{% if report.serverIdentifier %}
<div class="tl-server">
<i class="fa fa-server fa-xs me-1"></i>{{ report.serverIdentifier }}
</div>
{% endif %}
<div class="tl-message">{{ report.message ?? '(No message)' }}</div>
{% if report.errorCode %}
<span class="tl-code">{{ report.errorCode }}</span>
{% endif %}
{% if report.payloadJson %}
<span class="tl-payload-toggle"
onclick="togglePayload(this)">
<i class="fa fa-code fa-xs me-1"></i>Show payload
</span>
<pre class="tl-payload">{{ report.payloadJson }}</pre>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<div class="col-lg-4">
{# ── System tracking card ── #}
{% if ticket.problemHash %}
<div class="system-card">
<h5><i class="fa fa-fingerprint me-2"></i>Problem Hash</h5>
<div class="hash-code">{{ ticket.problemHash }}</div>
<div class="mt-2" style="font-size:0.78rem;color:#888">
Used for deduplication — tickets with the same hash
reported within 1 hour are merged into this record.
</div>
</div>
{% endif %}
{# ── Contact info ── #}
{% if ticket.email or ticket.phone or ticket.name %}
<div class="detail-card">
<h5><i class="fa fa-address-card me-2"></i>Reporter Contact</h5>
{% if ticket.name %}
<div class="info-item mb-2">
<label>Name</label>
<span>{{ ticket.name }}</span>
</div>
{% endif %}
{% if ticket.email %}
<div class="info-item mb-2">
<label>Email</label>
<span><a href="mailto:{{ ticket.email }}">{{ ticket.email }}</a></span>
</div>
{% endif %}
{% if ticket.phone %}
<div class="info-item mb-2">
<label>Phone</label>
<span>{{ ticket.phone }}</span>
</div>
{% endif %}
{% if ticket.preferredContactMethod %}
<div class="info-item">
<label>Preferred Contact</label>
<span>{{ ticket.preferredContactMethod == 1 ? 'Email' : 'Phone' }}</span>
</div>
{% endif %}
</div>
{% endif %}
{# ── Feedback ── #}
{% if ticket.feedback %}
<div class="detail-card">
<h5><i class="fa fa-comment me-2"></i>Feedback</h5>
<div style="font-size:0.9rem; color:#444;">{{ ticket.feedback }}</div>
</div>
{% endif %}
</div>
</div>
</div>
</section>
<script>
function togglePayload(el) {
var pre = el.nextElementSibling;
if (pre.style.display === 'block') {
pre.style.display = 'none';
el.textContent = '⌄ Show payload';
} else {
pre.style.display = 'block';
el.innerHTML = '<i class="fa fa-code fa-xs me-1"></i>Hide payload';
}
}
</script>
</div>{# /hb-admin-layout #}
{% include '@Application/footer/central_footer.html.twig' %}