04/07 updated password reset, ticket templates, and satisfaction survey
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Satisfaction Report{% endblock %}
|
||||
{% block page_title %}Satisfaction Report{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- KPI row -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-sm-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background:rgba(251,191,36,.1);color:#f59e0b;">
|
||||
<i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-value">
|
||||
{% if avg_rating %}{{ avg_rating }} <span style="font-size:16px;">/ 5</span>{% else %}—{% endif %}
|
||||
</div>
|
||||
<div class="stat-label">Average Rating</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background:rgba(37,99,235,.1);color:var(--accent);">
|
||||
<i class="bi bi-clipboard-check"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-value">{{ total }}</div>
|
||||
<div class="stat-label">Responses Received</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background:rgba(100,116,139,.1);color:var(--muted);">
|
||||
<i class="bi bi-hourglass-split"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-value">{{ pending }}</div>
|
||||
<div class="stat-label">Awaiting Response</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rating distribution -->
|
||||
{% if total %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><i class="bi bi-bar-chart-fill me-2"></i>Rating Distribution</div>
|
||||
<div class="card-body">
|
||||
{% for star in [5,4,3,2,1] %}
|
||||
{% set count = dist[star] %}
|
||||
{% set pct = ((count / total) * 100)|round(1) if total else 0 %}
|
||||
<div class="d-flex align-items-center gap-3 mb-2">
|
||||
<div style="width:60px;text-align:right;font-size:13px;font-weight:600;color:var(--text2);flex-shrink:0;">
|
||||
{{ star }} ★
|
||||
</div>
|
||||
<div style="flex:1;background:var(--surface2);border-radius:4px;height:18px;overflow:hidden;">
|
||||
<div style="width:{{ pct }}%;height:100%;background:{% if star >= 4 %}var(--success){% elif star == 3 %}var(--warning){% else %}var(--danger){% endif %};border-radius:4px;transition:width .4s;"></div>
|
||||
</div>
|
||||
<div style="width:70px;font-size:12px;color:var(--muted);font-family:'Space Mono',monospace;">
|
||||
{{ count }} ({{ pct }}%)
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Individual responses -->
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-chat-square-text me-2"></i>Individual Responses</div>
|
||||
<div class="card-body p-0">
|
||||
{% if surveys %}
|
||||
<table class="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Submitted By</th>
|
||||
<th>Rating</th>
|
||||
<th>Comment</th>
|
||||
<th>Submitted</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in surveys %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('tickets.ticket_detail', ticket_id=s.ticket_id) }}"
|
||||
class="mono" style="font-size:11px;color:var(--accent3);">
|
||||
{{ s.ticket.ticket_number }}
|
||||
</a>
|
||||
<div style="font-size:12px;color:var(--muted);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ s.ticket.title }}
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:13px;">{{ s.user.full_name }}</td>
|
||||
<td>
|
||||
<span style="font-size:18px;color:#f59e0b;">
|
||||
{% for i in range(s.rating) %}★{% endfor %}{% for i in range(5 - s.rating) %}<span style="color:var(--border2);">★</span>{% endfor %}
|
||||
</span>
|
||||
</td>
|
||||
<td style="font-size:12px;color:var(--muted);max-width:220px;">
|
||||
{{ s.comment or '—' }}
|
||||
</td>
|
||||
<td style="font-size:11px;color:var(--muted);">
|
||||
{{ s.submitted_at | localtime("%b %d, %Y") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-5 text-center" style="color:var(--muted);">
|
||||
<i class="bi bi-star" style="font-size:40px;display:block;margin-bottom:12px;"></i>
|
||||
No satisfaction responses yet. Surveys are sent automatically when tickets are resolved.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,119 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ 'Edit' if t else 'New' }} Template{% endblock %}
|
||||
{% block page_title %}{{ 'Edit' if t else 'New' }} Ticket Template{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-7">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-layout-text-window-reverse me-2"></i>
|
||||
{{ 'Edit: ' + t.name if t else 'New Template' }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Template Name *</label>
|
||||
<input type="text" class="form-control" name="name" required
|
||||
value="{{ t.name if t else '' }}"
|
||||
placeholder="e.g. VPN Access Issue"/>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Category</label>
|
||||
<select class="form-select" name="category">
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat }}" {% if t and t.category == cat %}selected{% endif %}>
|
||||
{{ cat.replace('_',' ').title() }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Default Priority</label>
|
||||
<select class="form-select" name="priority">
|
||||
{% for p in priorities %}
|
||||
<option value="{{ p }}" {% if t and t.priority == p %}selected{% elif not t and p == 'medium' %}selected{% endif %}>
|
||||
{{ p.upper() }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title Hint
|
||||
<span style="font-size:11px;color:var(--muted);font-weight:400;">
|
||||
— pre-fills the ticket title field
|
||||
</span>
|
||||
</label>
|
||||
<input type="text" class="form-control" name="title_hint"
|
||||
value="{{ t.title_hint if t else '' }}"
|
||||
placeholder="e.g. Cannot connect to VPN"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description Template
|
||||
<span style="font-size:11px;color:var(--muted);font-weight:400;">
|
||||
— pre-fills the description textarea (use newlines as prompts)
|
||||
</span>
|
||||
</label>
|
||||
<textarea class="form-control" name="description" rows="7"
|
||||
placeholder="Steps to reproduce: 1. Error message: Device / OS:">{{ t.description if t else '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Bootstrap Icon class
|
||||
<span style="font-size:11px;color:var(--muted);font-weight:400;">
|
||||
— e.g. bi-shield-lock
|
||||
</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="icon-preview" style="font-size:18px;color:var(--accent);">
|
||||
<i class="bi {{ t.icon if t else 'bi-file-text' }}" id="icon-el"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" name="icon" id="icon-input"
|
||||
value="{{ t.icon if t else 'bi-file-text' }}"
|
||||
oninput="document.getElementById('icon-el').className='bi '+this.value"/>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Browse icons at
|
||||
<a href="https://icons.getbootstrap.com/" target="_blank" style="color:var(--accent3);">
|
||||
icons.getbootstrap.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Sort Order</label>
|
||||
<input type="number" class="form-control" name="sort_order" min="0"
|
||||
value="{{ t.sort_order if t else 0 }}"/>
|
||||
<div class="form-text">Lower = shown first</div>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-center" style="padding-top:28px;">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
name="is_active" id="is-active"
|
||||
{% if not t or t.is_active %}checked{% endif %}
|
||||
style="width:40px;height:22px;cursor:pointer;"/>
|
||||
<label class="form-check-label ms-2" for="is-active"
|
||||
style="font-size:13px;font-weight:600;">Active</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2 mt-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check2 me-2"></i>{{ 'Save Changes' if t else 'Create Template' }}
|
||||
</button>
|
||||
<a href="{{ url_for('admin.ticket_templates') }}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,78 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Ticket Templates{% endblock %}
|
||||
{% block page_title %}Ticket Templates{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<p style="font-size:13px;color:var(--muted);margin:0;">
|
||||
Pre-filled ticket scaffolds that employees can select on the New Ticket form to speed up submission.
|
||||
</p>
|
||||
<a href="{{ url_for('admin.ticket_template_new') }}" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-plus-lg me-1"></i>New Template
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-layout-text-window-reverse me-2"></i>Templates
|
||||
<span style="font-size:12px;color:var(--muted);font-family:'Space Mono',monospace;">({{ templates|length }})</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if templates %}
|
||||
<table class="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:36px;"></th>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Priority</th>
|
||||
<th>Title Hint</th>
|
||||
<th>Order</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in templates %}
|
||||
<tr>
|
||||
<td style="color:var(--accent);font-size:18px;"><i class="bi {{ t.icon }}"></i></td>
|
||||
<td style="font-weight:600;font-size:13px;">{{ t.name }}</td>
|
||||
<td style="font-size:12px;color:var(--muted);">{{ t.category.replace('_',' ').title() }}</td>
|
||||
<td><span class="badge badge-{{ t.priority }}">{{ t.priority.upper() }}</span></td>
|
||||
<td style="font-size:12px;color:var(--muted);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ t.title_hint or '—' }}
|
||||
</td>
|
||||
<td style="font-size:12px;color:var(--muted);">{{ t.sort_order }}</td>
|
||||
<td>
|
||||
{% if t.is_active %}
|
||||
<span style="font-size:12px;color:var(--success);"><i class="bi bi-check-circle-fill"></i> Active</span>
|
||||
{% else %}
|
||||
<span style="font-size:12px;color:var(--muted);"><i class="bi bi-dash-circle"></i> Inactive</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<a href="{{ url_for('admin.ticket_template_edit', tmpl_id=t.id) }}"
|
||||
class="btn btn-secondary btn-sm" title="Edit"><i class="bi bi-pencil"></i></a>
|
||||
<form method="POST" action="{{ url_for('admin.ticket_template_delete', tmpl_id=t.id) }}"
|
||||
onsubmit="return confirm('Delete template "{{ t.name }}"?');" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<button type="submit" class="btn btn-sm"
|
||||
style="background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.2);color:var(--danger);"
|
||||
title="Delete"><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-5 text-center" style="color:var(--muted);">
|
||||
<i class="bi bi-layout-text-window-reverse" style="font-size:40px;display:block;margin-bottom:12px;"></i>
|
||||
No templates yet. <a href="{{ url_for('admin.ticket_template_new') }}" style="color:var(--accent3);">Create your first template →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user