04/07 updated password reset, ticket templates, and satisfaction survey
This commit is contained in:
@@ -5,6 +5,31 @@
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
{% if templates %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><i class="bi bi-layout-text-window-reverse me-2"></i>Start from a Template</div>
|
||||
<div class="card-body">
|
||||
<p style="font-size:13px;color:var(--muted);margin-bottom:14px;">
|
||||
Select a common request type to pre-fill the form, or fill it in manually below.
|
||||
</p>
|
||||
<div class="row g-2">
|
||||
{% for t in templates %}
|
||||
<div class="col-6 col-md-4">
|
||||
<button type="button" class="btn btn-secondary w-100 text-start template-btn"
|
||||
style="padding:10px 14px;font-size:13px;"
|
||||
data-title="{{ t.title_hint }}"
|
||||
data-category="{{ t.category }}"
|
||||
data-priority="{{ t.priority }}"
|
||||
data-description="{{ t.description | e }}">
|
||||
<i class="bi {{ t.icon }} me-2" style="color:var(--accent);"></i>{{ t.name }}
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-plus-circle me-2"></i>New Support Request
|
||||
@@ -108,4 +133,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.querySelectorAll('.template-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const title = btn.dataset.title;
|
||||
const cat = btn.dataset.category;
|
||||
const prio = btn.dataset.priority;
|
||||
const desc = btn.dataset.description;
|
||||
|
||||
if (title) document.querySelector('input[name=title]').value = title;
|
||||
if (desc) document.querySelector('textarea[name=description]').value = desc;
|
||||
|
||||
const catSel = document.querySelector('select[name=category]');
|
||||
if (catSel) {
|
||||
for (const opt of catSel.options) {
|
||||
if (opt.value === cat) { opt.selected = true; break; }
|
||||
}
|
||||
}
|
||||
const prioSel = document.querySelector('select[name=priority]');
|
||||
if (prioSel) {
|
||||
for (const opt of prioSel.options) {
|
||||
if (opt.value === prio) { opt.selected = true; break; }
|
||||
}
|
||||
}
|
||||
// Scroll to form
|
||||
document.querySelector('input[name=title]').scrollIntoView({behavior:'smooth', block:'center'});
|
||||
document.querySelector('input[name=title]').focus();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -752,6 +752,32 @@ function buildCommentEl(c) {
|
||||
</div>
|
||||
|
||||
<!-- History -->
|
||||
<!-- ── Satisfaction rating (visible to creator + IT staff) ──────── -->
|
||||
{% if ticket.satisfaction %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><i class="bi bi-star-half me-2"></i>Customer Satisfaction</div>
|
||||
<div class="card-body" style="padding:16px 20px;">
|
||||
{% if ticket.satisfaction.submitted %}
|
||||
<div style="font-size:28px;color:#f59e0b;margin-bottom:6px;">
|
||||
{% for i in range(ticket.satisfaction.rating) %}★{% endfor %}<span style="color:var(--border2);">{% for i in range(5 - ticket.satisfaction.rating) %}★{% endfor %}</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--muted);">
|
||||
Rated {{ ticket.satisfaction.rating }}/5 on {{ ticket.satisfaction.submitted_at | localtime("%b %d, %Y") }}
|
||||
</div>
|
||||
{% if ticket.satisfaction.comment %}
|
||||
<div style="margin-top:10px;font-size:13px;color:var(--text2);background:var(--surface2);border-radius:6px;padding:10px 12px;border:1px solid var(--border);">
|
||||
"{{ ticket.satisfaction.comment }}"
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div style="font-size:13px;color:var(--muted);">
|
||||
<i class="bi bi-hourglass-split me-2"></i>Survey sent — awaiting response
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if history %}
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="bi bi-clock-history me-2"></i>Change History</div>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<title>How did we do? — TechDesk</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600&display=swap" rel="stylesheet"/>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet"/>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:'DM Sans',sans-serif;background:#f0f4f8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;}
|
||||
.bg-grid{position:fixed;inset:0;background-image:linear-gradient(#e2e8f0 1px,transparent 1px),linear-gradient(90deg,#e2e8f0 1px,transparent 1px);background-size:48px 48px;opacity:.6;pointer-events:none;}
|
||||
.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:40px;width:100%;max-width:500px;position:relative;z-index:1;box-shadow:0 4px 24px rgba(0,0,0,.08);}
|
||||
h1{font-size:22px;font-weight:700;color:#0f172a;margin-bottom:6px;}
|
||||
.sub{font-size:13px;color:#64748b;margin-bottom:24px;}
|
||||
.ticket-ref{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px 16px;margin-bottom:24px;font-size:13px;color:#334155;}
|
||||
.ticket-ref strong{color:#2563eb;font-family:'Courier New',monospace;}
|
||||
/* Star rating */
|
||||
.stars{display:flex;flex-direction:row-reverse;justify-content:center;gap:6px;margin-bottom:28px;}
|
||||
.stars input{display:none;}
|
||||
.stars label{font-size:44px;color:#e2e8f0;cursor:pointer;transition:color .15s;line-height:1;}
|
||||
.stars input:checked ~ label,
|
||||
.stars label:hover,
|
||||
.stars label:hover ~ label{color:#f59e0b;}
|
||||
.stars input:checked + label{color:#f59e0b;}
|
||||
textarea{width:100%;border:1px solid #e2e8f0;border-radius:9px;padding:11px 14px;font-size:14px;font-family:inherit;resize:vertical;min-height:90px;color:#0f172a;transition:border-color .15s,box-shadow .15s;}
|
||||
textarea:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px rgba(37,99,235,.1);}
|
||||
textarea::placeholder{color:#94a3b8;}
|
||||
label.field-label{display:block;font-size:12px;font-weight:600;letter-spacing:.4px;color:#334155;text-transform:uppercase;margin-bottom:7px;}
|
||||
.btn{width:100%;background:#2563eb;border:none;color:#fff;border-radius:9px;padding:12px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s;font-family:inherit;margin-top:16px;box-shadow:0 2px 6px rgba(37,99,235,.3);}
|
||||
.btn:hover{background:#1d4ed8;}
|
||||
.alert{border-radius:8px;padding:10px 14px;font-size:13px;margin-bottom:16px;background:#fef2f2;color:#dc2626;border:1px solid #fecaca;}
|
||||
.rating-hint{text-align:center;font-size:13px;color:#64748b;margin-bottom:4px;min-height:20px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-grid"></div>
|
||||
<div class="card">
|
||||
<h1>⭐ How did we do?</h1>
|
||||
<p class="sub">Your feedback helps us improve our IT support.</p>
|
||||
|
||||
<div class="ticket-ref">
|
||||
<strong>{{ survey.ticket.ticket_number }}</strong> — {{ survey.ticket.title }}
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert"><i class="bi bi-exclamation-circle me-2"></i>{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div style="margin-bottom:8px;">
|
||||
<label class="field-label" style="text-align:center;display:block;">Your Rating *</label>
|
||||
<p class="rating-hint" id="rating-hint">Click a star to rate</p>
|
||||
<div class="stars">
|
||||
{% for i in [5,4,3,2,1] %}
|
||||
<input type="radio" name="rating" id="star{{ i }}" value="{{ i }}"
|
||||
{% if quick_rating == i %}checked{% endif %}
|
||||
onchange="updateHint({{ i }})"/>
|
||||
<label for="star{{ i }}" title="{{ i }} star{{ 's' if i > 1 }}" onmouseover="hintOver({{ i }})" onmouseout="hintOut()">★</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="field-label">Comment <span style="font-weight:400;text-transform:none;font-size:11px;color:#94a3b8;">(optional)</span></label>
|
||||
<textarea name="comment" placeholder="Tell us what went well or how we could improve…"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn"><i class="bi bi-send me-2"></i>Submit Feedback</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
const hints = {1:'Very dissatisfied',2:'Dissatisfied',3:'Neutral',4:'Satisfied',5:'Very satisfied'};
|
||||
function updateHint(n){ document.getElementById('rating-hint').textContent = hints[n] || ''; }
|
||||
function hintOver(n){ document.getElementById('rating-hint').textContent = hints[n] || ''; }
|
||||
function hintOut(){
|
||||
const checked = document.querySelector('.stars input:checked');
|
||||
document.getElementById('rating-hint').textContent = checked ? hints[checked.value] : 'Click a star to rate';
|
||||
}
|
||||
// Auto-submit when star clicked via quick_rating link
|
||||
{% if quick_rating %}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const el = document.getElementById('star{{ quick_rating }}');
|
||||
if(el){ el.checked = true; updateHint({{ quick_rating }}); }
|
||||
});
|
||||
{% endif %}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<title>Thank You — TechDesk</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:'DM Sans',sans-serif;background:#f0f4f8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;}
|
||||
.bg-grid{position:fixed;inset:0;background-image:linear-gradient(#e2e8f0 1px,transparent 1px),linear-gradient(90deg,#e2e8f0 1px,transparent 1px);background-size:48px 48px;opacity:.6;pointer-events:none;}
|
||||
.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:48px 40px;width:100%;max-width:440px;position:relative;z-index:1;box-shadow:0 4px 24px rgba(0,0,0,.08);text-align:center;}
|
||||
.icon{font-size:56px;margin-bottom:16px;}
|
||||
h1{font-size:22px;font-weight:700;color:#0f172a;margin-bottom:8px;}
|
||||
.sub{font-size:14px;color:#64748b;line-height:1.6;}
|
||||
.stars{font-size:32px;color:#f59e0b;margin:20px 0 8px;}
|
||||
.comment{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px 16px;font-size:13px;color:#334155;margin-top:12px;text-align:left;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-grid"></div>
|
||||
<div class="card">
|
||||
<div class="icon">🙏</div>
|
||||
<h1>Thank you for your feedback!</h1>
|
||||
<p class="sub">Your response has been recorded and will help us improve our IT support.</p>
|
||||
|
||||
{% if survey.rating %}
|
||||
<div class="stars">
|
||||
{% for i in range(survey.rating) %}★{% endfor %}
|
||||
<span style="color:#e2e8f0;">{% for i in range(5 - survey.rating) %}★{% endfor %}</span>
|
||||
</div>
|
||||
{% if survey.comment %}
|
||||
<div class="comment">"{{ survey.comment }}"</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p class="sub" style="margin-top:20px;font-size:12px;color:#94a3b8;">
|
||||
You can close this page. If the issue recurs, please submit a new ticket.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<title>Survey Not Found — TechDesk</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:'DM Sans',sans-serif;background:#f0f4f8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;}
|
||||
.bg-grid{position:fixed;inset:0;background-image:linear-gradient(#e2e8f0 1px,transparent 1px),linear-gradient(90deg,#e2e8f0 1px,transparent 1px);background-size:48px 48px;opacity:.6;pointer-events:none;}
|
||||
.card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:48px 40px;width:100%;max-width:440px;position:relative;z-index:1;box-shadow:0 4px 24px rgba(0,0,0,.08);text-align:center;}
|
||||
h1{font-size:22px;font-weight:700;color:#0f172a;margin-bottom:8px;}
|
||||
.sub{font-size:14px;color:#64748b;line-height:1.6;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-grid"></div>
|
||||
<div class="card">
|
||||
<div style="font-size:56px;margin-bottom:16px;">🔗</div>
|
||||
<h1>This survey link is invalid</h1>
|
||||
<p class="sub">The link may have already been used, or it does not exist. No further action is needed.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user