04/07 updated timezone, report via email, etc

This commit is contained in:
2026-04-07 11:24:08 -04:00
parent 56c65bfe4c
commit fed51f8c28
14 changed files with 1022 additions and 29 deletions
@@ -69,7 +69,7 @@
<td>{{ t.title[:45] }}{% if t.title|length > 45 %}…{% endif %}</td>
<td><span class="badge badge-{{ t.status }}">{{ t.status.replace('_',' ').upper() }}</span></td>
<td><span class="badge badge-{{ t.priority }}">{{ t.priority.upper() }}</span></td>
<td style="font-size:12px;color:var(--muted);">{{ t.created_at.strftime('%b %d') }}</td>
<td style="font-size:12px;color:var(--muted);">{{ t.created_at | localtime("%b %d") }}</td>
</tr>
{% endfor %}
</tbody>
+7 -7
View File
@@ -25,7 +25,7 @@
</span>
{% endif %}
<span><i class="bi bi-tag me-1"></i>{{ ticket.category.replace('_',' ').title() }}</span>
<span><i class="bi bi-calendar3 me-1"></i>{{ ticket.created_at.strftime('%b %d, %Y %H:%M') }}</span>
<span><i class="bi bi-calendar3 me-1"></i>{{ ticket.created_at | localtime("%b %d, %Y %H:%M") }}</span>
{% if ticket.location %}<span><i class="bi bi-geo-alt me-1"></i>{{ ticket.location }}</span>{% endif %}
{% if ticket.asset_tag %}<span><i class="bi bi-cpu me-1"></i>{{ ticket.asset_tag }}</span>{% endif %}
{% if ticket.ai_generated %}<span style="color:var(--accent3);"><i class="bi bi-robot me-1"></i>AI-generated</span>{% endif %}
@@ -113,7 +113,7 @@
{% endif %}
</div>
<div class="d-flex align-items-center gap-2">
<span class="comment-time">{{ comment.created_at.strftime('%b %d, %Y %H:%M') }}</span>
<span class="comment-time">{{ comment.created_at | localtime("%b %d, %Y %H:%M") }}</span>
{% if current_user.is_it_staff or comment.author_id == current_user.id %}
<form method="POST" action="{{ url_for('tickets.delete_comment', comment_id=comment.id) }}"
onsubmit="return confirm('Delete this comment?');">
@@ -744,10 +744,10 @@ function buildCommentEl(c) {
{{ info_row('bi-person-badge', 'Filed by (IT)', '<span style="color:var(--accent);font-weight:600;">' ~ ticket.filed_by_staff.full_name ~ '</span>') }}
{% endif %}
{{ info_row('bi-person-check', 'Assigned to', ticket.assignee.full_name if ticket.assignee else '—') }}
{{ info_row('bi-calendar3', 'Created', ticket.created_at.strftime('%b %d, %Y')) }}
{{ info_row('bi-calendar-check', 'Updated', ticket.updated_at.strftime('%b %d, %Y')) }}
{% if ticket.due_date %}{{ info_row('bi-alarm', 'Due Date', ticket.due_date.strftime('%b %d, %Y')) }}{% endif %}
{% if ticket.resolved_at %}{{ info_row('bi-check-circle', 'Resolved', ticket.resolved_at.strftime('%b %d, %Y')) }}{% endif %}
{{ info_row('bi-calendar3', 'Created', ticket.created_at | localtime("%b %d, %Y")) }}
{{ info_row('bi-calendar-check', 'Updated', ticket.updated_at | localtime("%b %d, %Y")) }}
{% if ticket.due_date %}{{ info_row('bi-alarm', 'Due Date', ticket.due_date | localtime("%b %d, %Y")) }}{% endif %}
{% if ticket.resolved_at %}{{ info_row('bi-check-circle', 'Resolved', ticket.resolved_at | localtime("%b %d, %Y")) }}{% endif %}
</div>
</div>
@@ -778,7 +778,7 @@ function buildCommentEl(c) {
{% endif %}
changed from <em>{{ old }}</em> to <em>{{ new }}</em>
<br>
<span style="font-size:10px;">by {{ h.changer.full_name }} · {{ h.changed_at.strftime('%b %d %H:%M') }}</span>
<span style="font-size:10px;">by {{ h.changer.full_name }} · {{ h.changed_at | localtime("%b %d %H:%M") }}</span>
</div>
{% endfor %}
</div>
+56 -9
View File
@@ -83,18 +83,65 @@
</div>
</div>
<div class="card mt-3">
<div class="card-body d-flex align-items-center justify-content-between">
<span style="font-size:13px;color:var(--muted);">Did this article solve your issue?</span>
<div class="d-flex gap-2">
<a href="{{ url_for('tickets.knowledge_base') }}" class="btn btn-secondary btn-sm">
<i class="bi bi-arrow-left me-1"></i>Back
</a>
<a href="{{ url_for('tickets.create_ticket') }}" class="btn btn-primary btn-sm">
<i class="bi bi-ticket me-1"></i>Still need help
</a>
<div class="card-body">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
<!-- Feedback widget -->
<div style="font-size:13px;">
<span style="color:var(--muted);margin-right:10px;">Was this article helpful?</span>
<button type="button" id="fb-yes"
onclick="submitFeedback(1)"
class="btn btn-sm {% if user_feedback and user_feedback.is_helpful %}btn-success{% else %}btn-outline-secondary{% endif %}"
style="margin-right:6px;">
<i class="bi bi-hand-thumbs-up me-1"></i>
Yes <span id="fb-yes-count">({{ helpful_count }})</span>
</button>
<button type="button" id="fb-no"
onclick="submitFeedback(0)"
class="btn btn-sm {% if user_feedback and not user_feedback.is_helpful %}btn-danger{% else %}btn-outline-secondary{% endif %}">
<i class="bi bi-hand-thumbs-down me-1"></i>
No <span id="fb-no-count">({{ not_helpful_count }})</span>
</button>
<span id="fb-msg" style="font-size:12px;color:var(--muted);margin-left:10px;"></span>
</div>
<!-- Navigation -->
<div class="d-flex gap-2">
<a href="{{ url_for('tickets.knowledge_base') }}" class="btn btn-secondary btn-sm">
<i class="bi bi-arrow-left me-1"></i>Back
</a>
<a href="{{ url_for('tickets.create_ticket') }}" class="btn btn-primary btn-sm">
<i class="bi bi-ticket me-1"></i>Still need help
</a>
</div>
</div>
</div>
</div>
<script>
const FEEDBACK_URL = '{{ url_for("tickets.kb_feedback", article_id=article.id) }}';
const CSRF_TOKEN = document.querySelector('meta[name=csrf-token]').content;
async function submitFeedback(helpful) {
const fd = new FormData();
fd.append('helpful', helpful);
fd.append('csrf_token', CSRF_TOKEN);
try {
const r = await fetch(FEEDBACK_URL, { method: 'POST', credentials: 'same-origin', body: fd });
const data = await r.json();
if (!data.ok) return;
document.getElementById('fb-yes-count').textContent = `(${data.helpful_count})`;
document.getElementById('fb-no-count').textContent = `(${data.not_helpful_count})`;
const btnYes = document.getElementById('fb-yes');
const btnNo = document.getElementById('fb-no');
const msg = document.getElementById('fb-msg');
btnYes.className = 'btn btn-sm ' + (data.user_vote === 'helpful' ? 'btn-success' : 'btn-outline-secondary');
btnNo.className = 'btn btn-sm ' + (data.user_vote === 'not_helpful' ? 'btn-danger' : 'btn-outline-secondary');
msg.textContent = data.user_vote ? 'Thanks for your feedback!' : 'Vote removed.';
setTimeout(() => msg.textContent = '', 3000);
} catch {
// silent fail
}
}
</script>
</div>
</div>
{% endblock %}
+1 -1
View File
@@ -93,7 +93,7 @@
{% if current_user.is_it_staff %}
<td style="font-size:13px;">{{ t.creator.full_name }}</td>
{% endif %}
<td style="font-size:12px;color:var(--muted);">{{ t.created_at.strftime('%b %d, %Y') }}</td>
<td style="font-size:12px;color:var(--muted);">{{ t.created_at | localtime("%b %d, %Y") }}</td>
<td>
<a href="{{ url_for('tickets.ticket_detail', ticket_id=t.id) }}" class="btn btn-secondary btn-sm">
<i class="bi bi-eye"></i>
+1 -1
View File
@@ -39,7 +39,7 @@
<div style="font-size:12px;color:var(--muted);margin-top:3px;">{{ n.message[:120] }}</div>
{% endif %}
<div style="font-size:11px;color:var(--muted);margin-top:5px;font-family:'Space Mono',monospace;">
{{ n.created_at.strftime('%b %d, %Y at %H:%M') }}
{{ n.created_at | localtime("%b %d, %Y at %H:%M") }}
</div>
</div>
<div style="flex-shrink:0;align-self:center;">