docs: Create vTiger & Simply-CRM integration setup guide with credential requirements feat: Implement ticket system enhancements including relations, calendar events, templates, and AI suggestions refactor: Update ticket system migration to include audit logging and enhanced email metadata
273 lines
11 KiB
HTML
273 lines
11 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Alle Tickets - BMC Hub{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.filter-bar {
|
|
background: var(--bg-card);
|
|
padding: 1.5rem;
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 1.5rem;
|
|
box-shadow: 0 2px 15px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.ticket-table th {
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
border-bottom: 2px solid var(--accent-light);
|
|
padding: 1rem 0.75rem;
|
|
font-size: 0.85rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.ticket-table td {
|
|
padding: 1rem 0.75rem;
|
|
vertical-align: middle;
|
|
border-bottom: 1px solid var(--accent-light);
|
|
}
|
|
|
|
.ticket-row {
|
|
transition: background-color 0.2s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.ticket-row:hover {
|
|
background-color: var(--accent-light);
|
|
}
|
|
|
|
.badge {
|
|
padding: 0.4rem 0.8rem;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.badge-status-open { background-color: #d1ecf1; color: #0c5460; }
|
|
.badge-status-in_progress { background-color: #fff3cd; color: #856404; }
|
|
.badge-status-pending_customer { background-color: #e2e3e5; color: #383d41; }
|
|
.badge-status-resolved { background-color: #d4edda; color: #155724; }
|
|
.badge-status-closed { background-color: #f8d7da; color: #721c24; }
|
|
|
|
.badge-priority-low { background-color: var(--accent-light); color: var(--accent); }
|
|
.badge-priority-normal { background-color: #e2e3e5; color: #383d41; }
|
|
.badge-priority-high { background-color: #fff3cd; color: #856404; }
|
|
.badge-priority-urgent, .badge-priority-critical { background-color: #f8d7da; color: #721c24; }
|
|
|
|
.ticket-number {
|
|
font-family: 'Monaco', 'Courier New', monospace;
|
|
background: var(--accent-light);
|
|
padding: 0.2rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.85rem;
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.search-box {
|
|
position: relative;
|
|
}
|
|
|
|
.search-box input {
|
|
padding-left: 2.5rem;
|
|
background: var(--bg-body);
|
|
border: 1px solid var(--accent-light);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.search-box i {
|
|
position: absolute;
|
|
left: 1rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 4rem 2rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.meta-info {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.meta-info i {
|
|
margin-right: 0.25rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-4">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="mb-2">
|
|
<i class="bi bi-ticket-detailed"></i> Alle Tickets
|
|
</h1>
|
|
<p class="text-muted">Oversigt over alle tickets i systemet</p>
|
|
</div>
|
|
<a href="/api/v1/ticket/tickets" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Ny Ticket
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Filter Bar -->
|
|
<div class="filter-bar">
|
|
<form method="get" action="/ticket/tickets" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label for="status" class="form-label">Status:</label>
|
|
<select name="status" id="status" class="form-select" onchange="this.form.submit()">
|
|
<option value="">Alle</option>
|
|
<option value="open" {% if selected_status == 'open' %}selected{% endif %}>Open</option>
|
|
<option value="in_progress" {% if selected_status == 'in_progress' %}selected{% endif %}>In Progress</option>
|
|
<option value="pending_customer" {% if selected_status == 'pending_customer' %}selected{% endif %}>Pending Customer</option>
|
|
<option value="resolved" {% if selected_status == 'resolved' %}selected{% endif %}>Resolved</option>
|
|
<option value="closed" {% if selected_status == 'closed' %}selected{% endif %}>Closed</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="priority" class="form-label">Prioritet:</label>
|
|
<select name="priority" id="priority" class="form-select" onchange="this.form.submit()">
|
|
<option value="">Alle</option>
|
|
<option value="low" {% if selected_priority == 'low' %}selected{% endif %}>Low</option>
|
|
<option value="normal" {% if selected_priority == 'normal' %}selected{% endif %}>Normal</option>
|
|
<option value="high" {% if selected_priority == 'high' %}selected{% endif %}>High</option>
|
|
<option value="urgent" {% if selected_priority == 'urgent' %}selected{% endif %}>Urgent</option>
|
|
<option value="critical" {% if selected_priority == 'critical' %}selected{% endif %}>Critical</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="customer_id" class="form-label">Kunde:</label>
|
|
<select name="customer_id" id="customer_id" class="form-select" onchange="this.form.submit()">
|
|
<option value="">Alle kunder</option>
|
|
{% for customer in customers %}
|
|
<option value="{{ customer.id }}" {% if customer.id == selected_customer_id %}selected{% endif %}>
|
|
{{ customer.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="search" class="form-label">Søg:</label>
|
|
<div class="search-box">
|
|
<i class="bi bi-search"></i>
|
|
<input
|
|
type="text"
|
|
name="search"
|
|
id="search"
|
|
class="form-control"
|
|
placeholder="Ticket nummer eller emne..."
|
|
value="{{ search_query or '' }}">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Tickets Table -->
|
|
{% if tickets %}
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="table ticket-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Ticket</th>
|
|
<th>Kunde</th>
|
|
<th>Status</th>
|
|
<th>Prioritet</th>
|
|
<th>Tildelt</th>
|
|
<th>Aktivitet</th>
|
|
<th>Oprettet</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for ticket in tickets %}
|
|
<tr class="ticket-row" onclick="window.location='/ticket/tickets/{{ ticket.id }}'">
|
|
<td>
|
|
<span class="ticket-number">{{ ticket.ticket_number }}</span>
|
|
<br>
|
|
<strong>{{ ticket.subject }}</strong>
|
|
</td>
|
|
<td>
|
|
{% if ticket.customer_name %}
|
|
{{ ticket.customer_name }}
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-status-{{ ticket.status }}">
|
|
{{ ticket.status.replace('_', ' ').title() }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-priority-{{ ticket.priority }}">
|
|
{{ ticket.priority.title() }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if ticket.assigned_to_name %}
|
|
{{ ticket.assigned_to_name }}
|
|
{% else %}
|
|
<span class="text-muted">Ikke tildelt</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="meta-info">
|
|
<i class="bi bi-chat"></i> {{ ticket.comment_count }}
|
|
<i class="bi bi-clock ms-2"></i> {{ ticket.worklog_count }}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{{ ticket.created_at.strftime('%d-%m-%Y') if ticket.created_at else '-' }}
|
|
<br>
|
|
<small class="text-muted">{{ ticket.created_at.strftime('%H:%M') if ticket.created_at else '' }}</small>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="text-muted mt-3">
|
|
<small>Viser {{ tickets|length }} tickets</small>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="empty-state">
|
|
<i class="bi bi-inbox"></i>
|
|
<h3>Ingen tickets fundet</h3>
|
|
<p>Prøv at justere dine filtre eller opret en ny ticket</p>
|
|
{% if selected_status or selected_priority or selected_customer_id or search_query %}
|
|
<a href="/ticket/tickets" class="btn btn-outline-secondary mt-3">
|
|
<i class="bi bi-x-circle"></i> Ryd filtre
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Submit search on Enter
|
|
document.getElementById('search').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
this.form.submit();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|