Initializing…

Support Tracker
Technical support call management
for your organisation

Sign in to continue

Use your Microsoft 365 work account to access your tickets.

All
Open
In Progress
On Hold
Closed
All Priority
πŸ”΄ Critical
🟠 High
🟑 Medium
🟒 Low

New Ticket

Ticket Info
Auto-assigned on save
β€”
Caller Details
Issue Details
Critical
High
Medium
Low
Assignment & Resolution

Ticket

πŸ’¬ Activity & Comments
Loading…
β€”
β€”
orm-view').classList.add('active'); } else { document.getElementById(name + '-view').classList.add('active'); const tab = document.querySelector('.nav-tab[data-tab="' + name + '"]'); if (tab) tab.classList.add('active'); if (name === 'dashboard') renderDashboard(); if (name === 'list') renderList(); } } // ─── Profile panel ──────────────────────────────────────────────────────────── function toggleProfile() { document.getElementById('backdrop').classList.toggle('open'); document.getElementById('profile-menu').classList.toggle('open'); } function closeProfile() { document.getElementById('backdrop').classList.remove('open'); document.getElementById('profile-menu').classList.remove('open'); } async function refreshTickets() { closeProfile(); showToast('Refreshing…'); try { await loadTickets(); renderList(); renderDashboard(); showToast('Tickets refreshed βœ“'); } catch (e) { showToast('Refresh failed: ' + e.message); } } // ─── Filters & search ───────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.chip').forEach(chip => { chip.addEventListener('click', () => { const filter = chip.dataset.filter; const val = chip.dataset.val; document.querySelectorAll('.chip[data-filter="' + filter + '"]').forEach(c => c.classList.remove('active')); chip.classList.add('active'); if (filter === 'status') filterStatus = val; if (filter === 'priority') filterPriority = val; renderList(); }); }); document.getElementById('search-input').addEventListener('input', e => { searchQuery = e.target.value; renderList(); }); document.querySelectorAll('.p-chip').forEach(chip => { chip.addEventListener('click', () => { selectedPriority = chip.dataset.val; const cls = priorityClass(selectedPriority); document.querySelectorAll('.p-chip').forEach(c => { c.className = 'p-chip'; }); chip.className = 'p-chip selected-' + cls; }); }); }); // ─── Comments & Activity ────────────────────────────────────────────────────── async function loadComments(ticketId) { const listEl = document.getElementById('comments-list'); if (!listEl) return; if (!commentsListId) { listEl.innerHTML = '
Set up the SupportTicketComments list in SharePoint to enable comments.
'; return; } try { const data = await graph(`/sites/${siteId}/lists/${commentsListId}/items?$expand=fields&$top=500`); const all = (data.value || []) .map(i => i.fields) .filter(f => String(f.TicketItemId) === String(ticketId)) .sort((a, b) => new Date(a.Created || 0) - new Date(b.Created || 0)); renderComments(all); } catch (e) { listEl.innerHTML = `
Could not load comments: ${esc(e.message)}
`; } } function renderComments(comments) { const listEl = document.getElementById('comments-list'); if (!listEl) return; if (comments.length === 0) { listEl.innerHTML = '
No comments yet. Be the first to add one.
'; return; } listEl.innerHTML = comments.map(c => { const isSystem = c.IsSystem === true || c.IsSystem === 'Yes'; const time = c.Created ? new Date(c.Created).toLocaleString([], { dateStyle: 'short', timeStyle: 'short' }) : ''; return `
${isSystem ? 'βš™ System' : esc(c.CommentBy || 'Unknown')} ${time}
${esc(c.CommentText || '')}
`; }).join(''); } async function postComment() { const input = document.getElementById('comment-input'); const text = (input.value || '').trim(); if (!text) return; if (!commentsListId) { showToast('Comments list not set up yet'); return; } const btn = document.querySelector('.comment-post-btn'); btn.textContent = '…'; btn.disabled = true; try { await graph(`/sites/${siteId}/lists/${commentsListId}/items`, 'POST', { fields: { TicketItemId: detailId, CommentText: text, CommentBy: currentUser.name || currentUser.username, CommentByEmail: currentUser.username, IsSystem: false, } }); input.value = ''; input.style.height = 'auto'; await loadComments(detailId); } catch (e) { showToast('Failed to post: ' + e.message); } finally { btn.textContent = 'Post'; btn.disabled = false; } } async function logActivity(ticketId, text) { if (!commentsListId || !ticketId) return; try { await graph(`/sites/${siteId}/lists/${commentsListId}/items`, 'POST', { fields: { TicketItemId: ticketId, CommentText: text, CommentBy: 'System', CommentByEmail: '', IsSystem: true, } }); } catch (e) { console.warn('Activity log failed:', e.message); } } // ─── Validation helpers ─────────────────────────────────────────────────────── function markInvalid(fieldId, message) { const el = document.getElementById(fieldId); if (!el) return; el.closest('.field').classList.add('field-invalid'); const errDiv = document.createElement('div'); errDiv.className = 'field-error'; errDiv.textContent = '⚠ ' + message; el.closest('.field').appendChild(errDiv); const clear = () => { el.closest('.field').classList.remove('field-invalid'); const e = el.closest('.field').querySelector('.field-error'); if (e) e.remove(); el.removeEventListener('input', clear); el.removeEventListener('change', clear); }; el.addEventListener('input', clear); el.addEventListener('change', clear); } function clearValidationErrors() { document.querySelectorAll('.field-invalid').forEach(el => el.classList.remove('field-invalid')); document.querySelectorAll('.field-error, .chips-invalid-msg').forEach(el => el.remove()); document.querySelectorAll('.chips-invalid').forEach(el => el.classList.remove('chips-invalid')); } // ─── Utility ────────────────────────────────────────────────────────────────── function esc(str) { return String(str || '') .replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } ); } el.removeEventListener('change', clear); }; el.addEventListener('input', clear); el.addEventListener('change', clear); } function clearValidationErrors() { document.querySelectorAll('.field-invalid').forEach(el => el.classList.remove('field-invalid')); document.querySelectorAll('.field-error, .chips-invalid-msg').forEach(el => el.remove()); document.querySelectorAll('.chips-invalid').forEach(el => el.classList.remove('chips-invalid')); } // ─── Utility ────────────────────────────────────────────────────────────────── function esc(str) { return String(str || '') .replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }