src/CompanyGroupBundle/Resources/views/pages/tickets/list_tickets.html.twig line 1

Open in your IDE?
  1. {% set page_title = 'Tickets | HoneyBee' %}
  2. {% include '@Application/inc/central_header.html.twig' %}
  3. {% include '@CompanyGroup/pages/admin/_sidebar.html.twig' %}
  4. <style>
  5. /* ── Design tokens ─────────────────────────────────────────────────────────── */
  6. :root {
  7.     --n-cream:     #F7F5F0;
  8.     --n-cream-2:   #F0EDE5;
  9.     --n-white:     #FFFFFF;
  10.     --n-dark:      #1A1D2E;
  11.     --n-dark-2:    #252840;
  12.     --n-amber:     #C07D2A;
  13.     --n-amber-lt:  #D4954A;
  14.     --n-amber-dim: rgba(192,125,42,.10);
  15.     --n-sage:      #3D6B52;
  16.     --n-sage-dim:  rgba(61,107,82,.09);
  17.     --n-muted:     #6B6E7F;
  18.     --n-muted-2:   #9395A5;
  19.     --n-border:    rgba(26,29,46,.07);
  20.     --n-border-md: rgba(26,29,46,.12);
  21.     --n-shadow-sm: 0 2px 12px rgba(26,29,46,.07);
  22.     --n-shadow-md: 0 8px 32px rgba(26,29,46,.09);
  23.     --n-radius:    12px;
  24.     --n-radius-sm: 8px;
  25.     --n-font:      'DM Sans', 'Poppins', system-ui, sans-serif;
  26. }
  27. *, *::before, *::after { box-sizing: border-box; }
  28. body { background: var(--n-cream); font-family: var(--n-font); color: var(--n-dark); text-align: left; }
  29. /* ── Layout ────────────────────────────────────────────────────────────────── */
  30. .t-wrap { max-width: 1100px; margin: 0 auto; padding: 0 28px; }
  31. .t-page  { padding: 40px 0 80px; }
  32. /* ── Page header ───────────────────────────────────────────────────────────── */
  33. .t-page-header {
  34.     display: flex; align-items: flex-end; justify-content: space-between;
  35.     flex-wrap: wrap; gap: 16px; margin-bottom: 28px;
  36.     padding-bottom: 24px; border-bottom: 1px solid var(--n-border-md);
  37. }
  38. .t-eyebrow {
  39.     display: inline-flex; align-items: center; gap: 8px;
  40.     font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  41.     color: var(--n-amber); margin-bottom: 8px;
  42. }
  43. .t-eyebrow::before { content:''; width:18px; height:1.5px; background:currentColor; border-radius:2px; }
  44. .t-page-title {
  45.     font-family: 'Montserrat', sans-serif; font-size: clamp(1.4rem,2.5vw,2rem);
  46.     font-weight: 800; color: var(--n-dark); letter-spacing: -.02em; margin: 0 0 4px;
  47. }
  48. .t-page-sub { font-size: .88rem; color: var(--n-muted); margin: 0; }
  49. /* ── Summary chips ─────────────────────────────────────────────────────────── */
  50. .t-summary-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
  51. .t-chip {
  52.     display: inline-flex; align-items: center; gap: 6px;
  53.     padding: 5px 14px; border-radius: 100px;
  54.     font-size: .75rem; font-weight: 700; letter-spacing: .03em;
  55.     border: 1px solid transparent;
  56. }
  57. .t-chip-open     { background: rgba(192,125,42,.10); color: var(--n-amber); border-color: rgba(192,125,42,.2); }
  58. .t-chip-progress { background: rgba(26,29,46,.06);   color: var(--n-dark);  border-color: var(--n-border-md); }
  59. .t-chip-resolved { background: var(--n-sage-dim);    color: var(--n-sage);  border-color: rgba(61,107,82,.2); }
  60. .t-chip-closed   { background: rgba(26,29,46,.04);   color: var(--n-muted); border-color: var(--n-border); }
  61. .t-chip .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
  62. /* ── Filter bar ────────────────────────────────────────────────────────────── */
  63. .t-filter-bar {
  64.     background: var(--n-white); border: 1px solid var(--n-border-md);
  65.     border-radius: var(--n-radius); padding: 18px 20px; margin-bottom: 20px;
  66.     box-shadow: var(--n-shadow-sm);
  67. }
  68. .t-filter-bar .form-select,
  69. .t-filter-bar .form-control {
  70.     border-radius: var(--n-radius-sm); font-size: .875rem;
  71.     border: 1.5px solid var(--n-border-md); background: var(--n-cream);
  72.     color: var(--n-dark);
  73. }
  74. .t-filter-bar .form-select:focus,
  75. .t-filter-bar .form-control:focus {
  76.     border-color: var(--n-amber);
  77.     box-shadow: 0 0 0 3px var(--n-amber-dim);
  78. }
  79. .t-filter-bar label { font-size: .78rem; font-weight: 600; color: var(--n-muted); margin-bottom: 4px; display: block; }
  80. /* ── Ticket card ───────────────────────────────────────────────────────────── */
  81. .t-card {
  82.     background: var(--n-white); border: 1px solid var(--n-border-md);
  83.     border-radius: var(--n-radius); padding: 16px 20px;
  84.     margin-bottom: 10px; display: flex; align-items: flex-start; gap: 16px;
  85.     transition: box-shadow .2s, border-color .2s;
  86. }
  87. .t-card:hover { box-shadow: var(--n-shadow-md); border-color: rgba(192,125,42,.22); }
  88. .t-card-icon { padding-top: 3px; flex-shrink: 0; font-size: 1.1rem; }
  89. .t-card-icon.emergency { color: #c0392b; }
  90. .t-card-icon.important { color: var(--n-amber); }
  91. .t-card-icon.normal    { color: var(--n-muted-2); }
  92. .t-card-body { flex: 1; min-width: 0; }
  93. .t-card-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
  94. .t-pill {
  95.     font-family: monospace; font-size: .65rem; font-weight: 700;
  96.     letter-spacing: .06em; text-transform: uppercase;
  97.     padding: 3px 9px; border-radius: 100px; display: inline-block;
  98. }
  99. .t-pill-num      { background: var(--n-amber-dim);  color: var(--n-amber); }
  100. .t-pill-open     { background: var(--n-amber-dim);  color: var(--n-amber); }
  101. .t-pill-progress { background: rgba(26,29,46,.07);  color: var(--n-dark); }
  102. .t-pill-resolved { background: var(--n-sage-dim);   color: var(--n-sage); }
  103. .t-pill-closed   { background: rgba(26,29,46,.04);  color: var(--n-muted-2); }
  104. .t-pill-emerg    { background: rgba(192,57,43,.09); color: #c0392b; }
  105. .t-pill-import   { background: rgba(192,125,42,.10);color: var(--n-amber); }
  106. .t-pill-normal   { background: rgba(26,29,46,.05);  color: var(--n-muted); }
  107. .t-pill-system   { background: rgba(111,66,193,.09);color: #6f42c1; }
  108. .t-pill-manual   { background: var(--n-sage-dim);   color: var(--n-sage); }
  109. .t-pill-api      { background: rgba(26,29,46,.06);  color: var(--n-muted-2); }
  110. .t-count-chip {
  111.     display: inline-flex; align-items: center; gap: 4px;
  112.     font-size: .72rem; color: var(--n-amber); background: var(--n-amber-dim);
  113.     border-radius: 50px; padding: 2px 9px; font-weight: 700; font-family: monospace;
  114. }
  115. .t-card-title { font-size: .95rem; font-weight: 600; color: var(--n-dark); margin-bottom: 4px; }
  116. .t-card-meta  { font-size: .8rem; color: var(--n-muted-2); display: flex; gap: 16px; flex-wrap: wrap; }
  117. .t-card-meta .unassigned { color: #c0392b; }
  118. .t-card-actions { flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; text-align: right; }
  119. .t-card-actions a {
  120.     font-size: .78rem; font-weight: 600; color: var(--n-muted);
  121.     text-decoration: none; padding: 4px 10px; border-radius: 6px;
  122.     border: 1px solid var(--n-border-md); background: var(--n-cream);
  123.     white-space: nowrap; transition: all .15s;
  124. }
  125. .t-card-actions a:hover { border-color: var(--n-amber); color: var(--n-amber); background: var(--n-amber-dim); }
  126. /* ── Buttons ───────────────────────────────────────────────────────────────── */
  127. .t-btn-primary {
  128.     display: inline-flex; align-items: center; gap: 7px;
  129.     padding: 10px 22px; background: var(--n-dark); color: #fff;
  130.     border: none; border-radius: var(--n-radius-sm);
  131.     font-family: var(--n-font); font-size: .88rem; font-weight: 700;
  132.     cursor: pointer; text-decoration: none; transition: background .18s;
  133. }
  134. .t-btn-primary:hover { background: var(--n-amber); color: #fff; }
  135. .t-btn-ghost {
  136.     display: inline-flex; align-items: center; gap: 6px;
  137.     padding: 10px 16px; background: transparent; color: var(--n-muted);
  138.     border: 1.5px solid var(--n-border-md); border-radius: var(--n-radius-sm);
  139.     font-family: var(--n-font); font-size: .88rem; font-weight: 600;
  140.     cursor: pointer; text-decoration: none; transition: all .18s;
  141. }
  142. .t-btn-ghost:hover { border-color: var(--n-dark); color: var(--n-dark); }
  143. /* ── Empty state ───────────────────────────────────────────────────────────── */
  144. .t-empty {
  145.     text-align: center; padding: 72px 0; color: var(--n-muted-2);
  146. }
  147. .t-empty-icon { font-size: 2.4rem; margin-bottom: 16px; opacity: .4; }
  148. .t-empty p { font-size: .95rem; margin-bottom: 20px; }
  149. /* ── Pagination ────────────────────────────────────────────────────────────── */
  150. .t-pagination { display: flex; justify-content: center; gap: 4px; margin-top: 28px; flex-wrap: wrap; }
  151. .t-pagination a, .t-pagination span {
  152.     display: inline-flex; align-items: center; justify-content: center;
  153.     width: 36px; height: 36px; border-radius: var(--n-radius-sm);
  154.     font-size: .85rem; font-weight: 600; text-decoration: none;
  155.     border: 1px solid var(--n-border-md); color: var(--n-muted);
  156.     background: var(--n-white); transition: all .15s;
  157. }
  158. .t-pagination a:hover { border-color: var(--n-amber); color: var(--n-amber); }
  159. .t-pagination .active { background: var(--n-dark); color: #fff; border-color: var(--n-dark); }
  160. </style>
  161. <div class="hb-admin-layout">
  162. <div class="t-page">
  163.     <div class="t-wrap">
  164.         {# ── Page header ── #}
  165.         <div class="t-page-header">
  166.             <div>
  167.                 <div class="t-eyebrow">Support</div>
  168.                 <h1 class="t-page-title">Tickets</h1>
  169.                 <p class="t-page-sub">Manage and track all support requests across your workspace</p>
  170.             </div>
  171.             <a href="{{ path('ticket_create') }}" class="t-btn-primary">
  172.                 <i class="fa fa-plus" style="font-size:.75rem"></i> New Ticket
  173.             </a>
  174.         </div>
  175.         {# ── Summary chips ── #}
  176.         <div class="t-summary-row">
  177.             <span class="t-chip t-chip-open">
  178.                 <span class="dot"></span> Open &nbsp;<b>{{ summary.open ?? 0 }}</b>
  179.             </span>
  180.             <span class="t-chip t-chip-progress">
  181.                 <span class="dot"></span> In Progress &nbsp;<b>{{ summary.inProgress ?? 0 }}</b>
  182.             </span>
  183.             <span class="t-chip t-chip-resolved">
  184.                 <span class="dot"></span> Resolved &nbsp;<b>{{ summary.resolved ?? 0 }}</b>
  185.             </span>
  186.             <span class="t-chip t-chip-closed">
  187.                 <span class="dot"></span> Closed &nbsp;<b>{{ summary.closed ?? 0 }}</b>
  188.             </span>
  189.         </div>
  190.         {# ── Filter bar ── #}
  191.         <form method="GET" action="{{ path('ticket_list') }}" class="t-filter-bar">
  192.             <div class="row g-2 align-items-end">
  193.                 <div class="col-12 col-md-3">
  194.                     <label>Status</label>
  195.                     <select name="status" class="form-select form-select-sm">
  196.                         <option value="">All Statuses</option>
  197.                         <option value="1" {{ filters.status == 1 ? 'selected' }}>Open</option>
  198.                         <option value="2" {{ filters.status == 2 ? 'selected' }}>In Progress</option>
  199.                         <option value="3" {{ filters.status == 3 ? 'selected' }}>Resolved</option>
  200.                         <option value="4" {{ filters.status == 4 ? 'selected' }}>Closed</option>
  201.                     </select>
  202.                 </div>
  203.                 <div class="col-12 col-md-2">
  204.                     <label>Priority</label>
  205.                     <select name="priority" class="form-select form-select-sm">
  206.                         <option value="">All Priorities</option>
  207.                         <option value="1" {{ filters.priority == 1 ? 'selected' }}>Emergency</option>
  208.                         <option value="2" {{ filters.priority == 2 ? 'selected' }}>Important</option>
  209.                         <option value="3" {{ filters.priority == 3 ? 'selected' }}>Normal</option>
  210.                     </select>
  211.                 </div>
  212.                 <div class="col-12 col-md-2">
  213.                     <label>Source</label>
  214.                     <select name="source" class="form-select form-select-sm">
  215.                         <option value="">All Sources</option>
  216.                         <option value="1" {{ filters.source == 1 ? 'selected' }}>Manual</option>
  217.                         <option value="2" {{ filters.source == 2 ? 'selected' }}>System</option>
  218.                         <option value="3" {{ filters.source == 3 ? 'selected' }}>API</option>
  219.                     </select>
  220.                 </div>
  221.                 <div class="col-12 col-md-3">
  222.                     <label>Search</label>
  223.                     <input type="text" name="q" class="form-control form-control-sm"
  224.                            placeholder="Ticket number or keyword…"
  225.                            value="{{ filters.q ?? '' }}">
  226.                 </div>
  227.                 <div class="col-12 col-md-2 d-flex gap-2">
  228.                     <button type="submit" class="t-btn-primary w-100" style="padding:8px 12px;justify-content:center;">
  229.                         <i class="fa fa-filter" style="font-size:.75rem"></i> Filter
  230.                     </button>
  231.                     <a href="{{ path('ticket_list') }}" class="t-btn-ghost" style="padding:8px 12px;">
  232.                         <i class="fa fa-times" style="font-size:.75rem"></i>
  233.                     </a>
  234.                 </div>
  235.             </div>
  236.         </form>
  237.         {# ── Ticket list ── #}
  238.         {% if tickets is defined and tickets|length > 0 %}
  239.             {% for ticket in tickets %}
  240.                 {% set statusLabel = {1: 'Open', 2: 'In Progress', 3: 'Resolved', 4: 'Closed'} %}
  241.                 {% set statusPill  = {1: 't-pill-open', 2: 't-pill-progress', 3: 't-pill-resolved', 4: 't-pill-closed'} %}
  242.                 {% set priorityLabel = {1: 'Emergency', 2: 'Important', 3: 'Normal'} %}
  243.                 {% set priorityPill  = {1: 't-pill-emerg', 2: 't-pill-import', 3: 't-pill-normal'} %}
  244.                 {% set iconClass     = {1: 'emergency', 2: 'important', 3: 'normal'} %}
  245.                 <div class="t-card">
  246.                     {# Priority icon #}
  247.                     <div class="t-card-icon {{ iconClass[ticket.urgency] ?? 'normal' }}">
  248.                         {% if ticket.urgency == 1 %}
  249.                             <i class="fa fa-exclamation-circle"></i>
  250.                         {% elseif ticket.urgency == 2 %}
  251.                             <i class="fa fa-exclamation-triangle"></i>
  252.                         {% else %}
  253.                             <i class="fa fa-ticket-alt"></i>
  254.                         {% endif %}
  255.                     </div>
  256.                     {# Main body #}
  257.                     <div class="t-card-body">
  258.                         <div class="t-card-pills">
  259.                             <span class="t-pill t-pill-num">{{ ticket.ticketNumber }}</span>
  260.                             <span class="t-pill {{ statusPill[ticket.status] ?? '' }}">{{ statusLabel[ticket.status] ?? 'Unknown' }}</span>
  261.                             <span class="t-pill {{ priorityPill[ticket.urgency] ?? 't-pill-normal' }}">{{ priorityLabel[ticket.urgency] ?? '' }}</span>
  262.                             {% if ticket.systemCreated %}
  263.                                 <span class="t-pill t-pill-system"><i class="fa fa-robot fa-xs"></i> System</span>
  264.                             {% else %}
  265.                                 <span class="t-pill t-pill-manual"><i class="fa fa-user fa-xs"></i> Manual</span>
  266.                             {% endif %}
  267.                             {% if ticket.reportCount > 1 %}
  268.                                 <span class="t-count-chip">
  269.                                     <i class="fa fa-redo fa-xs"></i> {{ ticket.reportCount }}× reported
  270.                                 </span>
  271.                             {% endif %}
  272.                         </div>
  273.                         <div class="t-card-title">{{ ticket.title ?? '(No title)' }}</div>
  274.                         <div class="t-card-meta">
  275.                             <span><i class="fa fa-calendar-alt fa-xs" style="margin-right:4px"></i>{{ ticket.createdAt ? ticket.createdAt|date('d M Y') : '—' }}</span>
  276.                             {% if ticket.deadlineDate %}
  277.                                 <span><i class="fa fa-clock fa-xs" style="margin-right:4px"></i>Due: {{ ticket.deadlineDate|date('d M Y') }}</span>
  278.                             {% endif %}
  279.                             {% if ticket.assignedToUserId %}
  280.                                 <span><i class="fa fa-user-check fa-xs" style="margin-right:4px"></i>Assigned: #{{ ticket.assignedToUserId }}</span>
  281.                             {% else %}
  282.                                 <span class="unassigned"><i class="fa fa-user-times fa-xs" style="margin-right:4px"></i>Unassigned</span>
  283.                             {% endif %}
  284.                             {% if ticket.serverIdentifier %}
  285.                                 <span><i class="fa fa-server fa-xs" style="margin-right:4px"></i>{{ ticket.serverIdentifier }}</span>
  286.                             {% endif %}
  287.                         </div>
  288.                     </div>
  289.                     {# Actions #}
  290.                     <div class="t-card-actions">
  291.                         <a href="{{ path('ticket_view', {id: ticket.id}) }}">
  292.                             <i class="fa fa-eye fa-xs"></i> View
  293.                         </a>
  294.                         <a href="{{ path('ticket_edit', {id: ticket.id}) }}">
  295.                             <i class="fa fa-edit-alt fa-xs"></i> Edit
  296.                         </a>
  297.                     </div>
  298.                 </div>
  299.             {% endfor %}
  300.         {% else %}
  301.             <div class="t-empty">
  302.                 <div class="t-empty-icon"><i class="fa fa-inbox"></i></div>
  303.                 <p>No tickets found matching your filters.</p>
  304.                 <a href="{{ path('ticket_create') }}" class="t-btn-primary">
  305.                     <i class="fa fa-plus" style="font-size:.75rem"></i> Create First Ticket
  306.                 </a>
  307.             </div>
  308.         {% endif %}
  309.         {# ── Pagination ── #}
  310.         {% if totalPages is defined and totalPages > 1 %}
  311.             <div class="t-pagination">
  312.                 {% for p in 1..totalPages %}
  313.                     {% if p == currentPage %}
  314.                         <span class="active">{{ p }}</span>
  315.                     {% else %}
  316.                         <a href="{{ path('ticket_list', filters|merge({page: p})) }}">{{ p }}</a>
  317.                     {% endif %}
  318.                 {% endfor %}
  319.             </div>
  320.         {% endif %}
  321.     </div>
  322. </div>
  323. </div>{# /hb-admin-layout #}
  324. {% include '@Application/footer/central_footer.html.twig' %}