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
362 lines
11 KiB
HTML
362 lines
11 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Ticket Dashboard - BMC Hub{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.stat-card {
|
|
text-align: center;
|
|
padding: 2rem 1.5rem;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: var(--accent);
|
|
transform: scaleX(0);
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.stat-card:hover::before {
|
|
transform: scaleX(1);
|
|
}
|
|
|
|
.stat-card h3 {
|
|
font-size: 3rem;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.stat-card p {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
margin: 0;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.stat-card .icon {
|
|
font-size: 2rem;
|
|
opacity: 0.3;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.stat-card.status-open h3 { color: #17a2b8; }
|
|
.stat-card.status-in-progress h3 { color: #ffc107; }
|
|
.stat-card.status-resolved h3 { color: #28a745; }
|
|
.stat-card.status-closed h3 { color: #6c757d; }
|
|
|
|
.ticket-list {
|
|
background: var(--bg-card);
|
|
}
|
|
|
|
.ticket-list 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-list 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;
|
|
}
|
|
|
|
.worklog-stats {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
padding: 1.5rem;
|
|
background: linear-gradient(135deg, var(--accent-light) 0%, var(--bg-card) 100%);
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.worklog-stat {
|
|
text-align: center;
|
|
}
|
|
|
|
.worklog-stat h4 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
margin: 0;
|
|
}
|
|
|
|
.worklog-stat p {
|
|
color: var(--text-secondary);
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 4rem 2rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.section-header h2 {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-4">
|
|
<!-- Page Header -->
|
|
<div class="section-header">
|
|
<div>
|
|
<h1 class="mb-2">
|
|
<i class="bi bi-speedometer2"></i> Ticket Dashboard
|
|
</h1>
|
|
<p class="text-muted">Oversigt over alle tickets og worklog aktivitet</p>
|
|
</div>
|
|
<div class="quick-actions">
|
|
<a href="/ticket/tickets/new" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Ny Ticket
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ticket Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card stat-card status-open" onclick="filterTickets('open')">
|
|
<div class="icon"><i class="bi bi-inbox"></i></div>
|
|
<h3>{{ stats.open_count or 0 }}</h3>
|
|
<p>Nye Tickets</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card status-in-progress" onclick="filterTickets('in_progress')">
|
|
<div class="icon"><i class="bi bi-arrow-repeat"></i></div>
|
|
<h3>{{ stats.in_progress_count or 0 }}</h3>
|
|
<p>I Gang</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card status-resolved" onclick="filterTickets('resolved')">
|
|
<div class="icon"><i class="bi bi-check-circle"></i></div>
|
|
<h3>{{ stats.resolved_count or 0 }}</h3>
|
|
<p>Løst</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card status-closed" onclick="filterTickets('closed')">
|
|
<div class="icon"><i class="bi bi-archive"></i></div>
|
|
<h3>{{ stats.closed_count or 0 }}</h3>
|
|
<p>Lukket</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Worklog Statistics -->
|
|
<div class="worklog-stats">
|
|
<div class="worklog-stat">
|
|
<h4>{{ worklog_stats.draft_count or 0 }}</h4>
|
|
<p>Draft Worklog</p>
|
|
</div>
|
|
<div class="worklog-stat">
|
|
<h4>{{ "%.1f"|format(worklog_stats.draft_hours or 0) }}t</h4>
|
|
<p>Udraft Timer</p>
|
|
</div>
|
|
<div class="worklog-stat">
|
|
<h4>{{ worklog_stats.billable_count or 0 }}</h4>
|
|
<p>Billable Entries</p>
|
|
</div>
|
|
<div class="worklog-stat">
|
|
<h4>{{ "%.1f"|format(worklog_stats.billable_hours or 0) }}t</h4>
|
|
<p>Billable Timer</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Tickets -->
|
|
<div class="section-header">
|
|
<h2>
|
|
<i class="bi bi-clock-history"></i> Seneste Tickets
|
|
</h2>
|
|
<a href="/ticket/tickets" class="btn btn-outline-secondary">
|
|
<i class="bi bi-list-ul"></i> Se Alle
|
|
</a>
|
|
</div>
|
|
|
|
{% if recent_tickets %}
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="table ticket-list mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Ticket</th>
|
|
<th>Kunde</th>
|
|
<th>Status</th>
|
|
<th>Prioritet</th>
|
|
<th>Oprettet</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for ticket in recent_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>
|
|
{{ ticket.created_at.strftime('%d-%m-%Y %H:%M') if ticket.created_at else '-' }}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="empty-state">
|
|
<i class="bi bi-inbox"></i>
|
|
<h3>Ingen tickets endnu</h3>
|
|
<p>Opret din første ticket for at komme i gang</p>
|
|
<a href="/ticket/tickets/new" class="btn btn-primary mt-3">
|
|
<i class="bi bi-plus-circle"></i> Opret Ticket
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Filter tickets by status
|
|
function filterTickets(status) {
|
|
window.location.href = `/ticket/tickets?status=${status}`;
|
|
}
|
|
|
|
// Auto-refresh every 5 minutes
|
|
setTimeout(() => {
|
|
location.reload();
|
|
}, 300000);
|
|
</script>
|
|
{% endblock %}
|