`;
errorWrapper.style.display = 'block'; contentWrapper.style.display = 'none'; return;
}
// --- CUSTOMIZABLE DATA SOURCE ---
const AGENTS = [ {id: 1, name: 'Alex Ray'}, {id: 2, name: 'Jordan Lee'}, {id: 3, name: 'Casey Garcia'}, {id: 4, name: 'Taylor Ward'} ];
let tickets = [
{ id: 1, agentId: 1, customer: 'Innovate LLC', issue: 'API connection failing', createdDate: new Date('2025-06-25T10:00:00Z'), resolvedDate: new Date('2025-06-25T14:30:00Z'), csatScore: 5, wasFCR: true },
{ id: 2, agentId: 2, customer: 'Global Corp', issue: 'Billing question for June', createdDate: new Date('2025-06-24T11:00:00Z'), resolvedDate: new Date('2025-06-24T12:00:00Z'), csatScore: 4, wasFCR: true },
{ id: 3, agentId: 1, customer: 'Solutions Co.', issue: 'Cannot reset password', createdDate: new Date('2025-06-26T09:00:00Z'), resolvedDate: null, csatScore: null, wasFCR: false },
{ id: 4, agentId: 3, customer: 'Tech Forward', issue: 'Feature request: dark mode', createdDate: new Date('2025-06-20T15:00:00Z'), resolvedDate: new Date('2025-06-20T15:30:00Z'), csatScore: 5, wasFCR: true },
{ id: 5, agentId: 2, customer: 'NextGen Inc.', issue: 'Slow loading times on dashboard', createdDate: new Date('2025-06-27T08:00:00Z'), resolvedDate: null, csatScore: null, wasFCR: false },
];
let activeTabIndex = 0;
let resolvedChart, csatChart;
// --- DOM ELEMENTS ---
const tabButtons = document.querySelectorAll('.apd-tab-button');
const tabPanes = document.querySelectorAll('.apd-tab-pane');
const timeFilterEl = document.getElementById('time-filter');
const ticketModal = document.getElementById('ticket-modal');
// --- INITIALIZATION ---
function initialize() {
initCharts();
populateModalDropdowns();
render();
attachEventListeners();
}
function initCharts() {
resolvedChart = new Chart(document.getElementById('resolvedChart'), { type: 'bar', options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Tickets Resolved' }}} });
csatChart = new Chart(document.getElementById('csatChart'), { type: 'bar', options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Average CSAT Score' }}, scales: { y: { beginAtZero: false, min: 1, max: 5 }} } });
}
function populateModalDropdowns() {
const agentSelect = document.getElementById('ticket-agent');
agentSelect.innerHTML = AGENTS.map(a => ``).join('');
}
// --- DATA PROCESSING & RENDERING ---
function render() {
const period = timeFilterEl.value;
const now = new Date();
const cutoffDate = new Date();
if (period === '7d') cutoffDate.setDate(now.getDate() - 7);
if (period === '30d') cutoffDate.setDate(now.getDate() - 30);
const filteredTickets = (period === 'all') ? tickets : tickets.filter(t => new Date(t.createdDate) >= cutoffDate);
const performanceData = AGENTS.map(agent => {
const agentTickets = filteredTickets.filter(t => t.agentId === agent.id);
const resolvedTickets = agentTickets.filter(t => t.resolvedDate);
const ratedTickets = resolvedTickets.filter(t => t.csatScore);
const fcrTickets = resolvedTickets.filter(t => t.wasFCR);
const totalResTime = resolvedTickets.reduce((sum, t) => sum + (new Date(t.resolvedDate) - new Date(t.createdDate)), 0);
const avgResTime = resolvedTickets.length > 0 ? totalResTime / resolvedTickets.length / 3600000 : 0;
const avgCsat = ratedTickets.length > 0 ? ratedTickets.reduce((sum, t) => sum + t.csatScore, 0) / ratedTickets.length : 0;
const fcrRate = resolvedTickets.length > 0 ? fcrTickets.length / resolvedTickets.length : 0;
return { id: agent.id, name: agent.name, ticketsResolved: resolvedTickets.length, avgResTime: avgResTime, avgCsat: avgCsat, fcrRate: fcrRate };
});
renderLeaderboard(performanceData);
renderCharts(performanceData);
renderTicketTable();
}
function renderLeaderboard(data) {
const topCsat = [...data].sort((a,b) => b.avgCsat - a.avgCsat)[0] || {name: 'N/A', avgCsat: 0};
const topResolved = [...data].sort((a,b) => b.ticketsResolved - a.ticketsResolved)[0] || {name: 'N/A', ticketsResolved: 0};
document.getElementById('leaderboard').innerHTML = `
`;
}
function renderCharts(data) {
const labels = data.map(d => d.name);
resolvedChart.data = { labels, datasets: [{ label: 'Tickets Resolved', data: data.map(d => d.ticketsResolved), backgroundColor: '#007bff' }]};
csatChart.data = { labels, datasets: [{ label: 'Average CSAT', data: data.map(d => d.avgCsat), backgroundColor: '#28a745' }]};
resolvedChart.update();
csatChart.update();
}
function renderTicketTable() {
const tableBody = document.getElementById('ticket-table-body');
tableBody.innerHTML = '';
tickets.forEach(ticket => {
const agent = AGENTS.find(a => a.id === ticket.agentId);
const status = ticket.resolvedDate ? 'Resolved' : 'Open';
tableBody.innerHTML += `
${ticket.id}
${ticket.customer}
${agent ? agent.name : 'Unassigned'}
${status}
${ticket.csatScore || 'N/A'}
${status === 'Open' ? '' : ''}
`;
});
}
// --- MODAL & CRUD ---
function openTicketModal(ticketId = null) {
const form = document.getElementById('ticket-form');
form.reset();
document.getElementById('modal-title').textContent = ticketId ? 'Edit Ticket' : 'Add New Ticket';
document.getElementById('ticket-id').value = ticketId || '';
if (ticketId) {
const ticket = tickets.find(t => t.id === ticketId);
if (ticket) {
document.getElementById('ticket-customer').value = ticket.customer;
document.getElementById('ticket-issue').value = ticket.issue;
document.getElementById('ticket-agent').value = ticket.agentId;
document.getElementById('ticket-status').value = ticket.resolvedDate ? 'Resolved' : 'Open';
document.getElementById('ticket-csat').value = ticket.csatScore || '';
document.getElementById('ticket-fcr').value = ticket.wasFCR.toString();
}
}
ticketModal.style.display = 'block';
}
function handleFormSubmit(e) {
e.preventDefault();
const id = parseInt(document.getElementById('ticket-id').value);
const now = new Date();
const ticketData = {
customer: document.getElementById('ticket-customer').value,
issue: document.getElementById('ticket-issue').value,
agentId: parseInt(document.getElementById('ticket-agent').value),
resolvedDate: document.getElementById('ticket-status').value === 'Resolved' ? now : null,
csatScore: parseInt(document.getElementById('ticket-csat').value) || null,
wasFCR: document.getElementById('ticket-fcr').value === 'true',
};
if (id) {
const index = tickets.findIndex(t => t.id === id);
tickets[index] = { ...tickets[index], ...ticketData };
} else {
const newId = tickets.length > 0 ? Math.max(...tickets.map(t => t.id)) + 1 : 1;
tickets.push({ id: newId, createdDate: now, ...ticketData });
}
ticketModal.style.display = 'none';
render();
}
// --- EVENT HANDLERS ---
function attachEventListeners() {
timeFilterEl.addEventListener('change', render);
document.getElementById('add-ticket-btn').addEventListener('click', () => openTicketModal());
document.getElementById('modal-cancel-btn').addEventListener('click', () => ticketModal.style.display = 'none');
document.getElementById('ticket-form').addEventListener('submit', handleFormSubmit);
document.getElementById('download-pdf-btn').addEventListener('click', generatePdf);
document.getElementById('ticket-table-body').addEventListener('click', e => {
const ticketId = parseInt(e.target.closest('tr')?.dataset.ticketId);
if (!ticketId) return;
if (e.target.classList.contains('edit-btn')) {
openTicketModal(ticketId);
} else if (e.target.classList.contains('delete-btn')) {
if (confirm(`Are you sure you want to delete ticket ${ticketId}?`)) {
tickets = tickets.filter(t => t.id !== ticketId);
render();
}
} else if (e.target.classList.contains('resolve-btn')) {
const index = tickets.findIndex(t => t.id === ticketId);
tickets[index].resolvedDate = new Date();
render();
}
});
tabButtons.forEach((button, index) => button.addEventListener('click', () => {
activeTabIndex = index;
tabButtons.forEach(btn => btn.classList.remove('active'));
tabPanes.forEach(pane => pane.classList.remove('active'));
button.classList.add('active');
document.getElementById(`tab-pane-${index}`).classList.add('active');
}));
}
// --- PDF ---
function generatePdf() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(18);
doc.text(`Agent Performance Report`, 14, 22);
if (activeTabIndex === 0) {
doc.autoTable({ startY: 30, html: document.querySelector('.apd-leaderboard') });
} else {
doc.autoTable({ startY: 30, html: document.getElementById('ticket-table-body').parentElement });
}
doc.save(`Agent_Performance_Report.pdf`);
}
initialize();
});
HIGHEST CSAT
${topCsat.name}
${topCsat.avgCsat.toFixed(2)}MOST RESOLVED
${topResolved.name}
${topResolved.ticketsResolved}