Work Order Completion Rate Dashboard

Work Order Completion Rate

Completion Rate

0%

On-Time Completion

0%

Total Work Orders

0

Avg. Completion Time

0d

Completion Rate Over Time

Work Orders by Status

Work Order Log

No work order data to display.

`; } renderTimeChart(metrics.completionByDate); renderStatusChart(metrics.statusCounts); }; const renderTimeChart = (completionByDate) => { const ctx = elements.timeChartCanvas?.getContext('2d'); if (!ctx) return; const sortedDates = Object.keys(completionByDate).sort((a, b) => new Date(a) - new Date(b)); const chartData = sortedDates.map(date => { const dayData = completionByDate[date]; return dayData.total > 0 ? (dayData.completed / dayData.total) * 100 : 0; }); if (timeChart) timeChart.destroy(); timeChart = new Chart(ctx, { type: 'line', data: { labels: sortedDates, datasets: [{ label: 'Completion Rate', data: chartData, borderColor: '#4f46e5', backgroundColor: 'rgba(79, 70, 229, 0.1)', fill: true, tension: 0.3 }] }, options: { responsive: true, maintainAspectRatio: true, scales: { y: { min: 0, max: 100, ticks: { callback: value => `${value}%` } } } } }); }; const renderStatusChart = (statusCounts) => { const ctx = elements.statusChartCanvas?.getContext('2d'); if (!ctx) return; const labels = ['Open', 'In Progress', 'Completed']; const data = labels.map(label => statusCounts[label] || 0); if (statusChart) statusChart.destroy(); statusChart = new Chart(ctx, { type: 'pie', data: { labels, datasets: [{ label: 'Work Order Status', data, backgroundColor: ['#ef4444', '#f97316', '#22c55e'], hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'top' } } } }); }; const renderConfigPanel = () => { const container = elements.editableList; if (!container) return; container.innerHTML = ''; workOrderData.forEach(d => { const card = document.createElement('div'); card.className = 'grid grid-cols-1 md:grid-cols-4 gap-4 items-center bg-white p-3 rounded-lg border'; card.innerHTML = `
`; container.appendChild(card); }); }; const handleAddLog = (e) => { e.preventDefault(); try { const newLog = { id: workOrderData.length > 0 ? Math.max(...workOrderData.map(d => d.id)) + 1 : 1, orderId: document.getElementById('orderId').value, assignedTo: document.getElementById('assignedTo').value, created: document.getElementById('createdDate').value, due: document.getElementById('dueDate').value, status: document.getElementById('status').value, completed: document.getElementById('completedDate').value || null, }; workOrderData.push(newLog); elements.addForm.reset(); renderAll(); showToast('Work order added!'); } catch (error) { console.error("Error adding record:", error); showToast("Error: Could not add record."); } }; const handleConfigListClick = (e) => { const target = e.target.closest('button'); if (!target) return; const action = target.dataset.action; const id = parseInt(target.dataset.id, 10); if (action === 'update') { const index = workOrderData.findIndex(d => d.id === id); if (index === -1) return; const inputs = target.closest('.grid').querySelectorAll('input'); inputs.forEach(input => { workOrderData[index][input.dataset.field] = input.value; }); } else if (action === 'delete') { workOrderData = workOrderData.filter(d => d.id !== id); } renderAll(); showToast(`Record ${action}d successfully!`); }; const handleGeneratePDF = () => { try { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const metrics = calculateMetrics(); doc.setFontSize(20); doc.text("Work Order Completion Report", 105, 20, null, null, 'center'); doc.setFontSize(10); doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, 26, null, null, 'center'); doc.autoTable({ startY: 35, head: [['Metric', 'Value']], body: [ ['Completion Rate', `${metrics.completionRate.toFixed(1)}%`], ['On-Time Completion', `${metrics.onTimeRate.toFixed(1)}%`], ['Total Work Orders', metrics.totalOrders], ['Avg. Completion Time', `${metrics.avgTime.toFixed(1)} days`] ], theme: 'grid' }); let finalY = doc.lastAutoTable.finalY || 60; doc.setFontSize(14); doc.text("Completion Rate Over Time", 14, finalY + 15); doc.addImage(elements.timeChartCanvas.toDataURL('image/png', 1.0), 'PNG', 14, finalY + 20, 180, 80); finalY += 95; doc.setFontSize(14); doc.text("Work Order Log", 14, finalY); const sortedData = [...workOrderData].sort((a, b) => new Date(b.created) - new Date(a.created)); const tableBody = sortedData.slice(0, 20).map(d => { const isOnTime = d.status === 'Completed' ? (new Date(d.completed) <= new Date(d.due) ? 'Yes' : 'No') : 'N/A'; return [d.orderId, d.assignedTo, d.status, new Date(d.due).toLocaleDateString(), isOnTime]; }); doc.autoTable({ head: [['ID', 'Assigned To', 'Status', 'Due Date', 'On Time']], body: tableBody, startY: finalY + 5, theme: 'striped', headStyles: { fillColor: [79, 70, 229] } }); doc.save('Work-Order-Completion-Report.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); showToast("Error: Could not generate PDF."); } }; const getStatusColor = (status) => { if (status === 'Completed') return 'bg-green-100 text-green-800'; if (status === 'In Progress') return 'bg-yellow-100 text-yellow-800'; return 'bg-red-100 text-red-800'; }; const switchTab = (tabName) => { currentTab = tabName; Object.values(elements.tabs).forEach(tab => tab.classList.add('hidden')); Object.values(elements.tabButtons).forEach(btn => btn.classList.remove('active')); elements.tabs[tabName].classList.remove('hidden'); elements.tabButtons[tabName].classList.add('active'); updateNavButtons(); }; const navigateTabs = (direction) => { const currentIndex = tabOrder.indexOf(currentTab); const newIndex = direction === 'next' ? Math.min(currentIndex + 1, tabOrder.length - 1) : Math.max(currentIndex - 1, 0); if (newIndex !== currentIndex) switchTab(tabOrder[newIndex]); }; const updateNavButtons = () => { const currentIndex = tabOrder.indexOf(currentTab); elements.navButtons.prev.disabled = currentIndex === 0; elements.navButtons.next.disabled = currentIndex === tabOrder.length - 1; }; const showToast = (message) => { if (!elements.toast) return; elements.toast.textContent = message; elements.toast.classList.add('show'); setTimeout(() => { elements.toast.classList.remove('show'); }, 3000); } elements.addForm.addEventListener('submit', handleAddLog); elements.editableList.addEventListener('click', handleConfigListClick); elements.pdfButton.addEventListener('click', handleGeneratePDF); elements.tabButtons.dashboard.addEventListener('click', () => switchTab('dashboard')); elements.tabButtons.config.addEventListener('click', () => switchTab('config')); elements.navButtons.prev.addEventListener('click', () => navigateTabs('prev')); elements.navButtons.next.addEventListener('click', () => navigateTabs('next')); renderAll(); updateNavButtons(); });
Scroll to Top