125 lines
5.2 KiB
HTML
125 lines
5.2 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Notifications{% endblock %}
|
||
{% block page_title %}Notifications{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row justify-content-center">
|
||
<div class="col-lg-8">
|
||
<div class="card">
|
||
<div class="card-header d-flex align-items-center justify-content-between">
|
||
<span><i class="bi bi-bell me-2"></i>All Notifications</span>
|
||
<form method="POST" action="{{ url_for('tickets.mark_notifications_read') }}">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||
<button type="submit" class="btn btn-secondary btn-sm">
|
||
<i class="bi bi-check2-all me-1"></i>Mark All Read
|
||
</button>
|
||
</form>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
{% if notifs.items %}
|
||
{% for n in notifs.items %}
|
||
<a href="{{ n.link or '#' }}"
|
||
data-notif-id="{{ n.id }}"
|
||
data-is-read="{{ 'true' if n.is_read else 'false' }}"
|
||
class="notif-list-item"
|
||
style="display:flex;gap:14px;padding:16px 20px;border-bottom:1px solid var(--border);color:var(--text);
|
||
{% if not n.is_read %}border-left:3px solid var(--accent3);background:rgba(0,180,216,.03);{% endif %}">
|
||
<div style="margin-top:2px;flex-shrink:0;">
|
||
{% if not n.is_read %}
|
||
<div class="notif-unread-dot" style="width:8px;height:8px;border-radius:50%;background:var(--accent3);margin-top:3px;"></div>
|
||
{% else %}
|
||
<i class="bi bi-check2" style="color:var(--muted);font-size:14px;"></i>
|
||
{% endif %}
|
||
</div>
|
||
<div style="flex:1;">
|
||
<div style="font-size:14px;font-weight:{% if not n.is_read %}600{% else %}400{% endif %};">
|
||
{{ n.title }}
|
||
</div>
|
||
{% if n.message %}
|
||
<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') }}
|
||
</div>
|
||
</div>
|
||
<div style="flex-shrink:0;align-self:center;">
|
||
<i class="bi bi-chevron-right" style="color:var(--muted);font-size:12px;"></i>
|
||
</div>
|
||
</a>
|
||
{% endfor %}
|
||
|
||
<!-- Pagination -->
|
||
{% if notifs.pages > 1 %}
|
||
<div class="d-flex justify-content-center py-3">
|
||
<nav><ul class="pagination mb-0">
|
||
{% if notifs.has_prev %}
|
||
<li class="page-item"><a class="page-link" href="{{ url_for('tickets.notifications', page=notifs.prev_num) }}">‹</a></li>
|
||
{% endif %}
|
||
{% for p in notifs.iter_pages() %}
|
||
{% if p %}
|
||
<li class="page-item {% if p==notifs.page %}active{% endif %}">
|
||
<a class="page-link" href="{{ url_for('tickets.notifications', page=p) }}">{{ p }}</a>
|
||
</li>
|
||
{% else %}
|
||
<li class="page-item disabled"><span class="page-link">…</span></li>
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% if notifs.has_next %}
|
||
<li class="page-item"><a class="page-link" href="{{ url_for('tickets.notifications', page=notifs.next_num) }}">›</a></li>
|
||
{% endif %}
|
||
</ul></nav>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% else %}
|
||
<div class="p-5 text-center" style="color:var(--muted);">
|
||
<i class="bi bi-bell-slash" style="font-size:40px;display:block;margin-bottom:12px;"></i>
|
||
No notifications yet.
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
// On page load, fetch the live unread count and sync the bell badge.
|
||
// The server-rendered {{ unread_notifications }} is only accurate at render
|
||
// time — if the user has been clicking around, it can be stale.
|
||
fetch('/api/notifications/unread-count')
|
||
.then(r => r.json())
|
||
.then(d => { if(typeof updateBadge === 'function') updateBadge(d.count); })
|
||
.catch(() => {});
|
||
|
||
// Intercept every notification link click:
|
||
// 1. If the notification is unread, call the mark-read API.
|
||
// 2. Decrement the bell badge immediately (optimistic update).
|
||
// 3. Navigate to the destination.
|
||
document.querySelectorAll('a.notif-list-item').forEach(function(link){
|
||
link.addEventListener('click', async function(e){
|
||
const id = this.dataset.notifId;
|
||
const isRead = this.dataset.isRead === 'true';
|
||
const dest = this.getAttribute('href');
|
||
|
||
if(!isRead && id){
|
||
e.preventDefault();
|
||
try {
|
||
await csrfPost('/api/notifications/' + id + '/read');
|
||
} catch(_){}
|
||
// Update badge — decrement by 1
|
||
if(typeof updateBadge === 'function') updateBadge(-1, true);
|
||
// Mark the row as visually read
|
||
this.dataset.isRead = 'true';
|
||
this.style.borderLeft = '';
|
||
this.style.background = '';
|
||
const dot = this.querySelector('.notif-unread-dot');
|
||
if(dot) dot.remove();
|
||
}
|
||
|
||
if(dest && dest !== '#') window.location.href = dest;
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %} |