Support Workflow Dashboard

Support Workflow Dashboard

Workflow Efficiency Overview

Avg. Ticket Lifecycle

0 hrs

FCR Rate

0%

Tickets In Progress

0

Top Bottleneck

-

Ticket Funnel

Average Time in Stage (Hours)

${ticket.subject}

#${ticket.id}

Agent: ${agent ? agent.name : 'Unassigned'}

`; cardContainer.appendChild(card); }); board.appendChild(column); }); } function renderBottleneckAnalysis() { const tableBody = document.getElementById('bottleneck-table'); tableBody.innerHTML = ''; Object.entries(state.avgTimeInStage).forEach(([stage, avgTime]) => { const row = document.createElement('tr'); const ticketCount = state.tickets.filter(t => t.status === stage).length; const isBottleneck = stage === state.topBottleneck; row.innerHTML = ` ${stage} ${avgTime.toFixed(1)} ${ticketCount} ${isBottleneck ? 'Yes' : 'No'} `; tableBody.appendChild(row); }); } function renderConfigForms() { const agentSelect = document.getElementById('ticket-agent'); agentSelect.innerHTML = ''; state.agents.forEach(a => { const option = document.createElement('option'); option.value = a.id; option.textContent = a.name; agentSelect.appendChild(option); }); } // --- EVENT HANDLERS & LOGIC --- // function attachEventListeners() { prevBtn.addEventListener('click', () => changeTab(state.activeTab - 1)); nextBtn.addEventListener('click', () => changeTab(state.activeTab + 1)); downloadPdfBtn.addEventListener('click', handleDownloadPdf); document.getElementById('add-ticket-form').addEventListener('submit', handleAddTicket); } window.changeTab = (tabIndex) => { if (tabIndex < 0 || tabIndex >= tabButtons.length) return; state.activeTab = tabIndex; updateTabUI(); }; function updateTabUI() { tabButtons.forEach((b, i) => b.classList.toggle('active', i === state.activeTab)); tabButtons.forEach((b, i) => b.classList.toggle('inactive', i !== state.activeTab)); tabPanes.forEach((p, i) => p.classList.toggle('hidden', i !== state.activeTab)); prevBtn.disabled = state.activeTab === 0; nextBtn.disabled = state.activeTab === tabButtons.length - 1; prevBtn.classList.toggle('opacity-50', prevBtn.disabled); nextBtn.classList.toggle('opacity-50', nextBtn.disabled); } function handleAddTicket(e) { e.preventDefault(); const form = e.target; const newTicket = { id: Math.max(...state.tickets.map(t => t.id)) + 1, subject: form.elements['ticket-subject'].value, agentId: parseInt(form.elements['ticket-agent'].value) || null, status: 'New', history: [{ stage: 'New', timestamp: new Date(today) }], }; state.tickets.push(newTicket); form.reset(); render(); changeTab(1); } async function handleDownloadPdf() { const downloadBtn = document.getElementById('download-pdf-btn'); if (!downloadBtn || downloadBtn.disabled) return; const originalBtnContent = downloadBtn.innerHTML; downloadBtn.disabled = true; downloadBtn.innerHTML = `Generating PDF...`; try { const { jsPDF } = window.jspdf; const contentToClone = document.getElementById('support-workflow-container'); const contentToExport = contentToClone.cloneNode(true); contentToExport.querySelectorAll('.pdf-hide').forEach(el => el.remove()); contentToExport.querySelectorAll('.tab-pane:not(#tab-0-content)').forEach(el => el.remove()); contentToExport.querySelector('#tab-0-content').classList.remove('hidden'); document.body.appendChild(contentToExport); contentToExport.style.cssText = 'position:absolute; left:0; top:0; width:1024px; z-index:-1; background-color:white; padding:2rem;'; const canvas = await html2canvas(contentToExport, { scale: 2, useCORS: true, logging: false }); document.body.removeChild(contentToExport); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const scaledHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, scaledHeight); pdf.save('Support_Workflow_Summary.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); } finally { downloadBtn.disabled = false; downloadBtn.innerHTML = originalBtnContent; } } // --- UTILITY FUNCTIONS --- // function calculateMetrics() { const resolvedTickets = state.tickets.filter(t => t.status === 'Resolved'); state.avgLifecycleHours = resolvedTickets.length > 0 ? resolvedTickets.reduce((sum, t) => { const start = t.history[0].timestamp; const end = t.history[t.history.length - 1].timestamp; return sum + (end - start); }, 0) / resolvedTickets.length / (1000 * 60 * 60) : 0; const fcrTickets = resolvedTickets.filter(t => t.history.length === 2).length; state.fcrRate = resolvedTickets.length > 0 ? (fcrTickets / resolvedTickets.length) * 100 : 0; const timeInStage = {}; state.stages.forEach(s => timeInStage[s] = { total: 0, count: 0 }); state.tickets.forEach(t => { for (let i = 0; i < t.history.length - 1; i++) { const stage = t.history[i].stage; const duration = t.history[i+1].timestamp - t.history[i].timestamp; timeInStage[stage].total += duration; timeInStage[stage].count++; } // Add time for current stage const currentStage = t.history[t.history.length - 1]; if (t.status !== 'Resolved') { const duration = today - currentStage.timestamp; timeInStage[currentStage.stage].total += duration; timeInStage[currentStage.stage].count++; } }); state.avgTimeInStage = {}; let maxTime = 0; state.topBottleneck = '-'; Object.keys(timeInStage).forEach(stage => { const { total, count } = timeInStage[stage]; const avg = count > 0 ? (total / count) / (1000 * 60 * 60) : 0; state.avgTimeInStage[stage] = avg; if (avg > maxTime && stage !== 'Resolved') { maxTime = avg; state.topBottleneck = stage; } }); } // --- START THE APP --- // initialize(); });
Scroll to Top