Enhance Ticket management functionalities
This commit is contained in:
+84
-24
@@ -442,11 +442,15 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
const socket = io({
|
||||
path: '/socket.io/',
|
||||
transports: ['polling', 'websocket'], // polling first → upgrade to WS after handshake
|
||||
transports: ['polling', 'websocket'], // polling first → upgrade to WS after handshake
|
||||
upgrade: true,
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 2000,
|
||||
reconnectionAttempts: 10,
|
||||
reconnectionDelay: 3000, // wait 3s before first reconnect attempt
|
||||
reconnectionDelayMax: 15000, // cap exponential backoff at 15s
|
||||
timeout: 20000, // connection timeout
|
||||
// Note: pingTimeout and pingInterval are server-side only in Socket.IO v4.
|
||||
// The server controls the heartbeat (ping_interval=25, ping_timeout=60).
|
||||
});
|
||||
socket.on('connect',()=>{console.log('[Socket] connected');});
|
||||
socket.on('new_notification',(n)=>{
|
||||
@@ -466,52 +470,108 @@ function updateBadge(delta, increment=false){
|
||||
|
||||
// ── Notification panel ────────────────────────────────────────────────────────
|
||||
let panelOpen = false;
|
||||
|
||||
function toggleNotifPanel(){
|
||||
const panel = document.getElementById('notif-panel');
|
||||
panelOpen = !panelOpen;
|
||||
panel.classList.toggle('show', panelOpen);
|
||||
if(panelOpen) loadNotifications();
|
||||
}
|
||||
document.addEventListener('click',(e)=>{
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
if(panelOpen && !e.target.closest('#notif-panel') && !e.target.closest('.notif-btn')){
|
||||
panelOpen=false;
|
||||
panelOpen = false;
|
||||
document.getElementById('notif-panel').classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
async function loadNotifications(){
|
||||
try{
|
||||
const r = await fetch('/api/notifications/unread-count');
|
||||
const d = await r.json();
|
||||
updateBadge(d.count);
|
||||
}catch(e){}
|
||||
function _notifItemHtml(n) {
|
||||
const unreadClass = n.is_read ? '' : ' unread';
|
||||
const dot = n.is_read ? '' : '<div class="notif-dot"></div>';
|
||||
return (
|
||||
`<div class="notif-item${unreadClass}" data-id="${n.id}" data-link="${n.link || ''}" ` +
|
||||
`style="cursor:${n.link ? 'pointer' : 'default'}">` +
|
||||
dot +
|
||||
`<div style="flex:1;min-width:0;">` +
|
||||
`<div class="notif-title">${n.title}</div>` +
|
||||
`<div class="notif-msg">${n.message || ''}</div>` +
|
||||
`<div class="notif-time">${n.created_at}</div>` +
|
||||
`</div></div>`
|
||||
);
|
||||
}
|
||||
|
||||
function _bindNotifClick(el) {
|
||||
el.addEventListener('click', async () => {
|
||||
const id = el.dataset.id;
|
||||
const link = el.dataset.link;
|
||||
// Mark as read in DB
|
||||
if(el.classList.contains('unread')){
|
||||
try { await fetch(`/api/notifications/${id}/read`, { method: 'POST' }); } catch(_){}
|
||||
el.classList.remove('unread');
|
||||
const dot = el.querySelector('.notif-dot');
|
||||
if(dot) dot.remove();
|
||||
// Decrement badge
|
||||
const badge = document.getElementById('notif-badge-count');
|
||||
if(badge){
|
||||
const cur = parseInt(badge.textContent) || 0;
|
||||
const next = Math.max(0, cur - 1);
|
||||
badge.textContent = next > 0 ? next : '';
|
||||
badge.classList.toggle('show', next > 0);
|
||||
}
|
||||
}
|
||||
if(link) window.location.href = link;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadNotifications(){
|
||||
const list = document.getElementById('notif-list');
|
||||
list.innerHTML = '<div class="p-3 text-center" style="color:var(--muted);font-size:13px;">Loading…</div>';
|
||||
try{
|
||||
const r = await fetch('/notifications?json=1');
|
||||
// Fallback: show link
|
||||
list.innerHTML='<div class="p-3 text-center" style="color:var(--muted);font-size:13px;">'+
|
||||
'<a href="/notifications" style="color:var(--accent3)">View all notifications</a></div>';
|
||||
const r = await fetch('/api/notifications');
|
||||
if(!r.ok) throw new Error('HTTP ' + r.status);
|
||||
const data = await r.json();
|
||||
const notifs = data.notifications || [];
|
||||
|
||||
// Update badge from live count
|
||||
const unreadCount = notifs.filter(n => !n.is_read).length;
|
||||
updateBadge(unreadCount);
|
||||
|
||||
if(notifs.length === 0){
|
||||
list.innerHTML = '<div class="p-4 text-center" style="color:var(--muted);font-size:13px;">' +
|
||||
'<i class="bi bi-bell-slash" style="font-size:24px;display:block;margin-bottom:8px;"></i>' +
|
||||
'No notifications yet.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = notifs.map(_notifItemHtml).join('');
|
||||
list.querySelectorAll('.notif-item').forEach(_bindNotifClick);
|
||||
|
||||
}catch(e){
|
||||
list.innerHTML='<div class="p-3 text-center" style="color:var(--muted);font-size:13px;">Unable to load</div>';
|
||||
list.innerHTML = '<div class="p-3 text-center" style="color:var(--muted);font-size:13px;">Unable to load notifications.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function prependNotif(n){
|
||||
// Add new real-time notification to the top of the panel if it's open
|
||||
const list = document.getElementById('notif-list');
|
||||
const item = document.createElement('div');
|
||||
item.className='notif-item unread';
|
||||
item.innerHTML=`<div class="notif-dot"></div><div><div class="notif-title">${n.title}</div><div class="notif-msg">${n.message||''}</div><div class="notif-time">Just now</div></div>`;
|
||||
if(n.link) item.onclick=()=>{ window.location=n.link; };
|
||||
list.prepend(item);
|
||||
// Remove "no notifications" placeholder if present
|
||||
if(list.querySelector('.bi-bell-slash')) list.innerHTML = '';
|
||||
const tmp = document.createElement('div');
|
||||
tmp.innerHTML = _notifItemHtml({
|
||||
id: n.id, title: n.title, message: n.message || '',
|
||||
link: n.link || '', is_read: false, created_at: 'Just now'
|
||||
});
|
||||
const el = tmp.firstElementChild;
|
||||
_bindNotifClick(el);
|
||||
list.prepend(el);
|
||||
}
|
||||
|
||||
async function markAllRead(){
|
||||
await fetch('/api/notifications/mark-all-read',{method:'POST'});
|
||||
try { await fetch('/api/notifications/mark-all-read', { method: 'POST' }); } catch(_){}
|
||||
updateBadge(0);
|
||||
document.querySelectorAll('.notif-item.unread').forEach(el=>{
|
||||
document.querySelectorAll('.notif-item.unread').forEach(el => {
|
||||
el.classList.remove('unread');
|
||||
const dot=el.querySelector('.notif-dot');
|
||||
const dot = el.querySelector('.notif-dot');
|
||||
if(dot) dot.remove();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user