{% set page_title = 'New Ticket | HoneyBee' %}
{% include '@Application/inc/central_header.html.twig' %}
{% include '@CompanyGroup/pages/admin/_sidebar.html.twig' %}
<style>
/* ── Design tokens ─────────────────────────────────────────────────────────── */
:root {
--n-cream: #F7F5F0;
--n-cream-2: #F0EDE5;
--n-white: #FFFFFF;
--n-dark: #1A1D2E;
--n-dark-2: #252840;
--n-amber: #C07D2A;
--n-amber-lt: #D4954A;
--n-amber-dim: rgba(192,125,42,.10);
--n-sage: #3D6B52;
--n-sage-dim: rgba(61,107,82,.09);
--n-muted: #6B6E7F;
--n-muted-2: #9395A5;
--n-border: rgba(26,29,46,.07);
--n-border-md: rgba(26,29,46,.12);
--n-shadow-sm: 0 2px 12px rgba(26,29,46,.07);
--n-shadow-md: 0 8px 32px rgba(26,29,46,.09);
--n-radius: 12px;
--n-radius-sm: 8px;
--n-font: 'DM Sans', 'Poppins', system-ui, sans-serif;
}
*, *::before, *::after { box-sizing: border-box; }
body { background: var(--n-cream); font-family: var(--n-font); color: var(--n-dark); text-align: left; }
/* ── Layout ────────────────────────────────────────────────────────────────── */
.tc-wrap { max-width: 1100px; margin: 0 auto; padding: 0 28px; }
.tc-page { padding: 40px 0 80px; }
/* ── Breadcrumb ────────────────────────────────────────────────────────────── */
.tc-breadcrumb {
font-size: .8rem; color: var(--n-muted-2); margin-bottom: 20px;
display: flex; align-items: center; gap: 6px;
}
.tc-breadcrumb a { color: var(--n-muted); text-decoration: none; font-weight: 500; }
.tc-breadcrumb a:hover { color: var(--n-amber); }
.tc-breadcrumb .sep { color: var(--n-border-md); }
.tc-breadcrumb .current { color: var(--n-dark); font-weight: 600; }
/* ── Page header ───────────────────────────────────────────────────────────── */
.tc-page-title {
font-family: 'Montserrat', sans-serif; font-size: clamp(1.4rem,2.5vw,1.9rem);
font-weight: 800; color: var(--n-dark); letter-spacing: -.02em; margin: 0 0 5px;
}
.tc-page-sub { font-size: .88rem; color: var(--n-muted); margin: 0 0 28px; }
.required-star { color: #c0392b; }
/* ── Form card ─────────────────────────────────────────────────────────────── */
.tc-card {
background: var(--n-white); border: 1px solid var(--n-border-md);
border-radius: var(--n-radius); padding: 28px 28px 24px;
margin-bottom: 16px; box-shadow: var(--n-shadow-sm);
}
.tc-card-title {
font-family: monospace; font-size: .72rem; font-weight: 700;
letter-spacing: .1em; text-transform: uppercase; color: var(--n-amber);
margin-bottom: 20px; padding-bottom: 12px;
border-bottom: 1px solid var(--n-border); display: flex;
align-items: center; justify-content: space-between; gap: 8px;
}
.tc-card-title i { margin-right: 6px; }
/* ── Form elements ─────────────────────────────────────────────────────────── */
.tc-label {
display: block; font-size: .8rem; font-weight: 600;
color: var(--n-muted); margin-bottom: 5px;
}
.tc-input,
.tc-select,
.tc-textarea {
width: 100%; border-radius: var(--n-radius-sm);
border: 1.5px solid var(--n-border-md); background: var(--n-cream);
color: var(--n-dark); font-family: var(--n-font); font-size: .9rem;
padding: 9px 13px; transition: border-color .15s, box-shadow .15s;
appearance: none;
}
.tc-textarea { resize: vertical; min-height: 130px; }
.tc-input:focus, .tc-select:focus, .tc-textarea:focus {
outline: none; border-color: var(--n-amber);
box-shadow: 0 0 0 3px var(--n-amber-dim);
}
.tc-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236B6E7F' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 13px center; padding-right: 36px; }
.tc-char-count { font-size: .73rem; color: var(--n-muted-2); text-align: right; margin-top: 4px; font-family: monospace; }
.tc-hint { font-size: .75rem; color: var(--n-muted-2); margin-top: 4px; }
/* ── Priority grid ─────────────────────────────────────────────────────────── */
.tc-priority-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; }
.tc-prio-input { display: none; }
.tc-prio-label {
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 4px; border: 1.5px solid var(--n-border-md); border-radius: var(--n-radius);
padding: 16px 8px; cursor: pointer; text-align: center; background: var(--n-cream);
transition: border-color .15s, background .15s;
}
.tc-prio-label:hover { border-color: var(--n-amber-lt); background: var(--n-amber-dim); }
.tc-prio-input:checked + .tc-prio-label { border-color: var(--n-amber); background: var(--n-amber-dim); }
.tc-prio-input[value="1"]:checked + .tc-prio-label { border-color: #c0392b; background: rgba(192,57,43,.07); }
.tc-prio-input[value="2"]:checked + .tc-prio-label { border-color: var(--n-amber-lt); background: var(--n-amber-dim); }
.tc-prio-icon { font-size: 1.4rem; }
.tc-prio-name { font-size: .8rem; font-weight: 700; color: var(--n-dark); }
.tc-prio-desc { font-size: .7rem; color: var(--n-muted-2); }
/* ── Source toggle ─────────────────────────────────────────────────────────── */
.tc-source-toggle {
display: flex; border: 1.5px solid var(--n-border-md);
border-radius: var(--n-radius-sm); overflow: hidden;
}
.tc-source-toggle input { display: none; }
.tc-source-toggle label {
flex: 1; text-align: center; padding: 9px 8px;
font-size: .82rem; font-weight: 600; cursor: pointer;
color: var(--n-muted); background: var(--n-cream); transition: all .15s;
border-right: 1.5px solid var(--n-border-md);
}
.tc-source-toggle label:last-of-type { border-right: none; }
.tc-source-toggle input:checked + label { background: var(--n-dark); color: #fff; }
/* ── Advanced section (collapsible) ───────────────────────────────────────── */
.tc-adv-toggle {
font-size: .78rem; font-weight: 700; color: var(--n-muted);
cursor: pointer; display: flex; align-items: center; gap: 5px;
font-family: monospace; text-transform: uppercase; letter-spacing: .08em;
}
.tc-adv-toggle:hover { color: var(--n-amber); }
.tc-adv-badge {
background: rgba(111,66,193,.09); color: #6f42c1;
font-size: .65rem; font-weight: 700; padding: 2px 8px;
border-radius: 100px; font-family: monospace; letter-spacing: .06em;
}
.tc-adv-body { display: none; margin-top: 16px; }
.tc-adv-body.open { display: block; }
.tc-mono { font-family: monospace !important; font-size: .83rem !important; }
/* ── Action bar ────────────────────────────────────────────────────────────── */
.tc-action-bar {
display: flex; align-items: center; gap: 12px;
padding-top: 8px;
}
.tc-btn-submit {
display: inline-flex; align-items: center; gap: 8px;
padding: 12px 28px; background: var(--n-dark); color: #fff;
border: none; border-radius: var(--n-radius-sm);
font-family: var(--n-font); font-size: .92rem; font-weight: 700;
cursor: pointer; transition: background .18s, transform .15s; text-decoration: none;
}
.tc-btn-submit:hover { background: var(--n-amber); color: #fff; transform: translateY(-1px); }
.tc-btn-cancel {
display: inline-flex; align-items: center; gap: 6px;
padding: 12px 20px; background: transparent; color: var(--n-muted);
border: 1.5px solid var(--n-border-md); border-radius: var(--n-radius-sm);
font-family: var(--n-font); font-size: .9rem; font-weight: 600;
cursor: pointer; text-decoration: none; transition: all .18s;
}
.tc-btn-cancel:hover { border-color: var(--n-dark); color: var(--n-dark); }
</style>
<div class="hb-admin-layout">
<div class="tc-page">
<div class="tc-wrap">
{# ── Breadcrumb ── #}
<div class="tc-breadcrumb">
<a href="{{ path('ticket_list') }}"><i class="fa fa-ticket-alt fa-xs"></i> Tickets</a>
<span class="sep">/</span>
<span class="current">New Ticket</span>
</div>
<h1 class="tc-page-title">Create New Ticket</h1>
<p class="tc-page-sub">Fields marked <span class="required-star">*</span> are required.</p>
<form method="POST"
action="{{ path('ticket_create_submit') }}"
enctype="multipart/form-data"
id="createTicketForm">
<div class="row g-4">
{# ── LEFT column ── #}
<div class="col-lg-8">
{# Core details #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-info-circle"></i>Ticket Details</span>
</div>
<div class="mb-3">
<label class="tc-label">Title <span class="required-star">*</span></label>
<input type="text" name="title" class="tc-input"
placeholder="Brief summary of the issue…"
value="{{ formData.title ?? '' }}"
maxlength="255" required>
</div>
<div class="mb-3">
<label class="tc-label">Description <span class="required-star">*</span></label>
<textarea name="description" class="tc-textarea" rows="6"
id="descriptionField"
placeholder="Describe the issue in detail…"
maxlength="5000"
required>{{ formData.description ?? '' }}</textarea>
<div class="tc-char-count" id="charCount">0 / 5000</div>
</div>
<div class="mb-3">
<label class="tc-label">Internal Note</label>
<textarea name="note" class="tc-textarea" rows="3"
placeholder="Internal notes (not visible to the reporter)…">{{ formData.note ?? '' }}</textarea>
</div>
<div class="mb-0">
<label class="tc-label">Attachments</label>
<input type="file" name="attachments[]" class="tc-input"
multiple accept=".jpg,.jpeg,.png,.pdf,.txt,.log,.zip">
<div class="tc-hint">JPG, PNG, PDF, TXT, LOG, ZIP — max 10 MB per file</div>
</div>
</div>
{# Classification #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-sliders-h"></i>Classification</span>
</div>
<div class="mb-4">
<label class="tc-label">Ticket Type</label>
<select name="ticketType" class="tc-select">
<option value="">— Select type —</option>
<option value="1" {{ formData.ticketType == 1 ? 'selected' }}>Service Ticket</option>
<option value="2" {{ formData.ticketType == 2 ? 'selected' }}>To-Do</option>
<option value="3" {{ formData.ticketType == 3 ? 'selected' }}>To-Do Reply</option>
<option value="4" {{ formData.ticketType == 4 ? 'selected' }}>Query</option>
<option value="5" {{ formData.ticketType == 5 ? 'selected' }}>Development</option>
<option value="6" {{ formData.ticketType == 6 ? 'selected' }}>Supplier</option>
<option value="7" {{ formData.ticketType == 7 ? 'selected' }}>Client</option>
</select>
</div>
<div class="mb-4">
<label class="tc-label">Priority <span class="required-star">*</span></label>
<div class="tc-priority-grid">
<div>
<input type="radio" name="priority" value="1" id="prio1" class="tc-prio-input"
{{ (formData.priority ?? 3) == 1 ? 'checked' }}>
<label for="prio1" class="tc-prio-label">
<span class="tc-prio-icon">🔴</span>
<span class="tc-prio-name">Emergency</span>
<span class="tc-prio-desc">Needs immediate action</span>
</label>
</div>
<div>
<input type="radio" name="priority" value="2" id="prio2" class="tc-prio-input"
{{ (formData.priority ?? 3) == 2 ? 'checked' }}>
<label for="prio2" class="tc-prio-label">
<span class="tc-prio-icon">🟡</span>
<span class="tc-prio-name">Important</span>
<span class="tc-prio-desc">Should be addressed soon</span>
</label>
</div>
<div>
<input type="radio" name="priority" value="3" id="prio3" class="tc-prio-input"
{{ (formData.priority ?? 3) == 3 ? 'checked' }}>
<label for="prio3" class="tc-prio-label">
<span class="tc-prio-icon">🟢</span>
<span class="tc-prio-name">Normal</span>
<span class="tc-prio-desc">Routine issue</span>
</label>
</div>
</div>
</div>
<div class="mb-0">
<label class="tc-label">Source</label>
<div class="tc-source-toggle">
<input type="radio" name="source" value="1" id="src1"
{{ (formData.source ?? 1) == 1 ? 'checked' }}>
<label for="src1"><i class="fa fa-user fa-xs"></i> Manual</label>
<input type="radio" name="source" value="2" id="src2"
{{ (formData.source ?? 1) == 2 ? 'checked' }}>
<label for="src2"><i class="fa fa-robot fa-xs"></i> System</label>
<input type="radio" name="source" value="3" id="src3"
{{ (formData.source ?? 1) == 3 ? 'checked' }}>
<label for="src3"><i class="fa fa-code fa-xs"></i> API</label>
</div>
</div>
</div>
{# System Tracking — collapsible #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-fingerprint"></i>System Tracking</span>
<div style="display:flex;align-items:center;gap:10px;">
<span class="tc-adv-badge">Advanced</span>
<span class="tc-adv-toggle" onclick="toggleSystemFields(this)" id="sysToggleBtn">
<i class="fa fa-chevron-down fa-xs"></i> Expand
</span>
</div>
</div>
<div class="tc-adv-body" id="systemFieldsBlock">
<div class="mb-3">
<label class="tc-label">Problem Hash</label>
<input type="text" name="problemHash" class="tc-input tc-mono"
placeholder="SHA256 — leave blank to auto-compute"
value="{{ formData.problemHash ?? '' }}">
<div class="tc-hint">SHA256 of (errorCode + fileLocation + exceptionClass + appId). Do not include timestamps or user IDs.</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="tc-label">Error Code</label>
<input type="text" name="errorCode" class="tc-input"
placeholder="e.g. 500, DB_CONN_FAIL"
value="{{ formData.errorCode ?? '' }}">
</div>
<div class="col-md-6">
<label class="tc-label">Server Identifier</label>
<input type="text" name="serverIdentifier" class="tc-input"
placeholder="e.g. app-server-01"
value="{{ formData.serverIdentifier ?? '' }}">
</div>
</div>
<div class="mb-0">
<label class="tc-label">Payload JSON</label>
<textarea name="lastPayloadJson" class="tc-textarea tc-mono" rows="5"
placeholder='{"exception":"RuntimeException","file":"src/...","line":42,...}'>{{ formData.lastPayloadJson ?? '' }}</textarea>
</div>
</div>
</div>
</div>
{# ── RIGHT column ── #}
<div class="col-lg-4">
{# Assignment #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-user-check"></i>Assignment</span>
</div>
<div class="mb-3">
<label class="tc-label">Assign To (User ID)</label>
<input type="number" name="assignedToUserId" class="tc-input"
placeholder="User ID…"
value="{{ formData.assignedToUserId ?? '' }}">
</div>
<div class="mb-3">
<label class="tc-label">Deadline</label>
<input type="date" name="deadlineDate" class="tc-input"
value="{{ formData.deadlineDate ?? '' }}">
</div>
<div class="mb-0">
<label class="tc-label">Tagged User IDs</label>
<input type="text" name="taggedUserIds" class="tc-input"
placeholder="Comma-separated, e.g. 12, 45, 78"
value="{{ formData.taggedUserIds ?? '' }}">
</div>
</div>
{# Reporter contact #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-address-card"></i>Reporter Contact</span>
</div>
<div class="mb-3">
<label class="tc-label">Name</label>
<input type="text" name="name" class="tc-input"
placeholder="Reporter's name…"
value="{{ formData.name ?? '' }}">
</div>
<div class="mb-3">
<label class="tc-label">Email</label>
<input type="email" name="email" class="tc-input"
placeholder="reporter@example.com"
value="{{ formData.email ?? '' }}">
</div>
<div class="mb-3">
<label class="tc-label">Phone</label>
<input type="text" name="phone" class="tc-input"
placeholder="+49 …"
value="{{ formData.phone ?? '' }}">
</div>
<div class="mb-0">
<label class="tc-label">Preferred Contact</label>
<select name="preferredContactMethod" class="tc-select">
<option value="">— No preference —</option>
<option value="1" {{ formData.preferredContactMethod == 1 ? 'selected' }}>Email</option>
<option value="2" {{ formData.preferredContactMethod == 2 ? 'selected' }}>Phone</option>
</select>
</div>
</div>
{# Context #}
<div class="tc-card">
<div class="tc-card-title">
<span><i class="fa fa-sitemap"></i>Context</span>
</div>
<div class="mb-3">
<label class="tc-label">Company ID</label>
<input type="number" name="companyId" class="tc-input"
value="{{ formData.companyId ?? '' }}">
</div>
<div class="mb-3">
<label class="tc-label">App ID</label>
<input type="number" name="appId" class="tc-input"
value="{{ formData.appId ?? '' }}">
</div>
<div class="mb-0">
<label class="tc-label">Branch ID</label>
<input type="number" name="branchId" class="tc-input"
value="{{ formData.branchId ?? '' }}">
</div>
</div>
</div>
</div>
{# ── Submit bar ── #}
<div class="tc-action-bar">
<button type="submit" class="tc-btn-submit">
<i class="fa fa-paper-plane" style="font-size:.8rem"></i> Create Ticket
</button>
<a href="{{ path('ticket_list') }}" class="tc-btn-cancel">
Cancel
</a>
</div>
</form>
</div>
</div>
<script>
// Character counter
var descField = document.getElementById('descriptionField');
var charCount = document.getElementById('charCount');
if (descField) {
descField.addEventListener('input', function () {
charCount.textContent = this.value.length + ' / 5000';
});
}
// System fields toggle
function toggleSystemFields(el) {
var block = document.getElementById('systemFieldsBlock');
var isOpen = block.classList.contains('open');
if (isOpen) {
block.classList.remove('open');
el.innerHTML = '<i class="fa fa-chevron-down fa-xs"></i> Expand';
} else {
block.classList.add('open');
el.innerHTML = '<i class="fa fa-chevron-up fa-xs"></i> Collapse';
}
}
// Auto-expand if values are pre-filled
(function () {
var fields = ['[name="problemHash"]', '[name="serverIdentifier"]', '[name="lastPayloadJson"]'];
for (var i = 0; i < fields.length; i++) {
var el = document.querySelector(fields[i]);
if (el && el.value.trim() !== '') {
var block = document.getElementById('systemFieldsBlock');
block.classList.add('open');
var btn = document.getElementById('sysToggleBtn');
if (btn) btn.innerHTML = '<i class="fa fa-chevron-up fa-xs"></i> Collapse';
break;
}
}
})();
</script>
</div>{# /hb-admin-layout #}
{% include '@Application/footer/central_footer.html.twig' %}