Dispatch Dashboard

Dispatch Dashboard

Total Dispatches

0

Active Dispatches

0

Completed Dispatches

0

Avg. Response Time (min)

0.0

Dispatches by Status

Dispatch Overview

Task Description Status Priority Assigned To Start Time End Time Response Time (min) Delivery Outcome

No data to visualize.

`; return; } const statusCounts = { 'Pending': 0, 'En Route': 0, 'Completed': 0, 'Cancelled': 0 }; dispatches.forEach(item => { if (statusCounts.hasOwnProperty(item.status)) { statusCounts[item.status]++; } }); const maxCount = Math.max(...Object.values(statusCounts)); // Order of statuses for display const orderedStatuses = ['Pending', 'En Route', 'Completed', 'Cancelled']; orderedStatuses.forEach(status => { const count = statusCounts[status]; const percentage = maxCount > 0 ? (count / maxCount) * 100 : 0; const barItem = document.createElement('div'); barItem.classList.add('bar-item'); barItem.innerHTML = ` ${status}:
${count} `; statusBarChartEl.appendChild(barItem); }); } /** * Renders the dispatch items into the data configuration table with edit/delete actions. */ function renderManageDispatchesTable() { if (!manageDispatchesTableBody) { console.error('Manage dispatches table body not found.'); return; } manageDispatchesTableBody.innerHTML = ''; // Clear existing rows if (dispatches.length === 0) { manageDispatchesTableBody.innerHTML = `No dispatch tasks to manage.`; return; } dispatches.forEach(dispatch => { const startDate = dispatch.startTime ? dispatch.startTime.split('T')[0] : ''; const startTime = dispatch.startTime ? dispatch.startTime.split('T')[1] : ''; const endDate = dispatch.endTime ? dispatch.endTime.split('T')[0] : ''; const endTime = dispatch.endTime ? dispatch.endTime.split('T')[1] : ''; const row = manageDispatchesTableBody.insertRow(); row.setAttribute('data-dispatch-id', dispatch.id); // Set data attribute for easy lookup row.classList.add('hover:bg-gray-50'); row.innerHTML = ` `; }); } /** * Adds a new dispatch item from the input fields. */ function addDispatch() { if (!newTaskDescriptionInput || !newStatusSelect || !newPrioritySelect || !newAssignedToInput || !newStartDateInput || !newStartTimeInput || !newDeliveryOutcomeSelect) { console.error('New dispatch input elements not found.'); return; } const description = newTaskDescriptionInput.value.trim(); const status = newStatusSelect.value; const priority = newPrioritySelect.value; const assignedTo = newAssignedToInput.value.trim(); const startDate = newStartDateInput.value; const startTime = newStartTimeInput.value; const endDate = newEndDateInput.value; const endTime = newEndTimeInput.value; const deliveryOutcome = newDeliveryOutcomeSelect.value; if (!description || !status || !priority || !assignedTo || !startDate || !startTime) { alert('Please fill in all mandatory fields: Task Description, Status, Priority, Assigned To, Start Date, and Start Time.'); return; } const fullStartTime = `${startDate}T${startTime}`; const fullEndTime = (endDate && endTime) ? `${endDate}T${endTime}` : ''; dispatches.push({ id: generateUniqueId(), description: description, status: status, priority: priority, assignedTo: assignedTo, startTime: fullStartTime, endTime: fullEndTime, deliveryOutcome: deliveryOutcome }); // Clear input fields newTaskDescriptionInput.value = ''; newStatusSelect.value = 'Pending'; newPrioritySelect.value = 'Low'; newAssignedToInput.value = ''; newStartDateInput.value = ''; newStartTimeInput.value = ''; newEndDateInput.value = ''; newEndTimeInput.value = ''; newDeliveryOutcomeSelect.value = 'N/A'; // Re-render tables and update metrics updateAllViews(); } /** * Updates an existing dispatch item based on changes in the manage table. * This function is exposed globally for `onclick` attributes. * @param {string} dispatchId - The ID of the dispatch to update. * @param {HTMLElement} buttonElement - The button element that triggered the update. */ window.updateDispatch = function(dispatchId, buttonElement) { const row = buttonElement.closest('tr'); if (!row) { console.error('Could not find row for dispatchId:', dispatchId); return; } const descriptionInput = row.querySelector('[data-field="description"]'); const statusSelect = row.querySelector('[data-field="status"]'); const prioritySelect = row.querySelector('[data-field="priority"]'); const assignedToInput = row.querySelector('[data-field="assignedTo"]'); const startDateInput = row.querySelector('[data-field="startDate"]'); const startTimeInput = row.querySelector('[data-field="startTime"]'); const endDateInput = row.querySelector('[data-field="endDate"]'); const endTimeInput = row.querySelector('[data-field="endTime"]'); const deliveryOutcomeSelect = row.querySelector('[data-field="deliveryOutcome"]'); if (!descriptionInput || !statusSelect || !prioritySelect || !assignedToInput || !startDateInput || !startTimeInput || !deliveryOutcomeSelect) { console.error('Input fields not found in row for dispatchId:', dispatchId); return; } const updatedDescription = descriptionInput.value.trim(); const updatedStatus = statusSelect.value; const updatedPriority = prioritySelect.value; const updatedAssignedTo = assignedToInput.value.trim(); const updatedStartDate = startDateInput.value; const updatedStartTime = startTimeInput.value; const updatedEndDate = endDateInput.value; const updatedEndTime = endTimeInput.value; const updatedDeliveryOutcome = deliveryOutcomeSelect.value; if (!updatedDescription || !updatedStatus || !updatedPriority || !updatedAssignedTo || !updatedStartDate || !updatedStartTime) { alert('Please ensure all mandatory fields are valid before saving.'); return; } const updatedFullStartTime = `${updatedStartDate}T${updatedStartTime}`; const updatedFullEndTime = (updatedEndDate && updatedEndTime) ? `${updatedEndDate}T${updatedEndTime}` : ''; const dispatchIndex = dispatches.findIndex(d => d.id === dispatchId); if (dispatchIndex !== -1) { dispatches[dispatchIndex] = { ...dispatches[dispatchIndex], description: updatedDescription, status: updatedStatus, priority: updatedPriority, assignedTo: updatedAssignedTo, startTime: updatedFullStartTime, endTime: updatedFullEndTime, deliveryOutcome: updatedDeliveryOutcome }; updateAllViews(); } else { console.warn('Dispatch item not found for update:', dispatchId); } }; /** * Deletes a dispatch item from the list. * This function is exposed globally for `onclick` attributes. * @param {string} dispatchId - The ID of the dispatch to delete. */ window.deleteDispatch = function(dispatchId) { const initialLength = dispatches.length; dispatches = dispatches.filter(dispatch => dispatch.id !== dispatchId); if (dispatches.length < initialLength) { updateAllViews(); } else { console.warn('Dispatch item not found for deletion:', dispatchId); } }; /** * Updates all relevant views (metrics, dashboard table, manage table, bar chart). */ function updateAllViews() { updateDashboardMetrics(); renderDispatchesTable(); renderStatusBarChart(); renderManageDispatchesTable(); } /** * Generates and downloads a PDF of the dashboard content. */ function downloadDashboardPdf() { // Ensure jsPDF is available if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { console.error("jsPDF library not loaded. Cannot generate PDF."); alert("PDF generation library not loaded. Please try again later."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('landscape'); // Use landscape for wider tables // Set font and color for consistency doc.setFont('helvetica'); doc.setTextColor('#1f2937'); // Dark gray // Title doc.setFontSize(24); doc.text('Dispatch Dashboard', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' }); // Add a small separator doc.setDrawColor('#d1d5db'); doc.line(20, 25, doc.internal.pageSize.getWidth() - 20, 25); // Dashboard Metrics doc.setFontSize(14); let yPos = 40; const metrics = [ { label: 'Total Dispatches:', value: totalDispatchesEl ? totalDispatchesEl.textContent : 'N/A' }, { label: 'Active Dispatches:', value: activeDispatchesEl ? activeDispatchesEl.textContent : 'N/A' }, { label: 'Completed Dispatches:', value: completedDispatchesEl ? completedDispatchesEl.textContent : 'N/A' }, { label: 'Avg. Response Time (min):', value: avgResponseTimeEl ? avgResponseTimeEl.textContent : 'N/A' } ]; metrics.forEach(metric => { doc.text(`${metric.label} ${metric.value}`, 20, yPos); yPos += 10; }); yPos += 15; // Extra space before table // Dispatches by Status (as a table in PDF) doc.setFontSize(16); doc.text('Dispatches by Status', 20, yPos); yPos += 10; const statusTableColumn = ["Status", "Count"]; const statusTableRows = []; const statusCountsForPdf = { 'Pending': 0, 'En Route': 0, 'Completed': 0, 'Cancelled': 0 }; dispatches.forEach(item => { if (statusCountsForPdf.hasOwnProperty(item.status)) { statusCountsForPdf[item.status]++; } }); ['Pending', 'En Route', 'Completed', 'Cancelled'].forEach(status => { statusTableRows.push([status, statusCountsForPdf[status]]); }); doc.autoTable({ head: [statusTableColumn], body: statusTableRows, startY: yPos, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: '#1f2937', lineColor: '#e5e7eb', lineWidth: 0.1 }, headStyles: { fillColor: '#f3f4f6', textColor: '#4b5563', fontStyle: 'bold' }, alternateRowStyles: { fillColor: '#ffffff' }, margin: { left: 20, right: 20 }, didDrawPage: function(data) { let str = "Page " + doc.internal.getNumberOfPages(); doc.setFontSize(10); doc.setTextColor('#6b7280'); doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); } }); yPos = doc.autoTable.previous.finalY + 15; // Continue from where the last table ended // Dispatch Overview Table doc.setFontSize(16); doc.text('Dispatch Overview', 20, yPos); yPos += 10; const overviewTableColumn = ["Task Description", "Status", "Priority", "Assigned To", "Start Time", "End Time", "Response Time (min)", "Delivery Outcome"]; const overviewTableRows = []; dispatches.forEach(dispatch => { const responseTime = calculateTimeDifferenceInMinutes(dispatch.startTime, dispatch.endTime); overviewTableRows.push([ dispatch.description, dispatch.status, dispatch.priority, dispatch.assignedTo, dispatch.startTime ? new Date(dispatch.startTime).toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'short' }) : 'N/A', dispatch.endTime ? new Date(dispatch.endTime).toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'short' }) : 'N/A', typeof responseTime === 'number' ? responseTime : 'N/A', dispatch.deliveryOutcome ]); }); doc.autoTable({ head: [overviewTableColumn], body: overviewTableRows, startY: yPos, theme: 'grid', styles: { fontSize: 6, // Further reduced font size for more columns in landscape cellPadding: 1, textColor: '#1f2937', lineColor: '#e5e7eb', lineWidth: 0.1 }, headStyles: { fillColor: '#f3f4f6', textColor: '#4b5563', fontStyle: 'bold' }, alternateRowStyles: { fillColor: '#ffffff' }, margin: { left: 10, right: 10 }, // Adjusted margins to fit more content didDrawPage: function(data) { let str = "Page " + doc.internal.getNumberOfPages(); doc.setFontSize(10); doc.setTextColor('#6b7280'); doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); } }); // Save the PDF doc.save('dispatch_dashboard.pdf'); } // --- Event Listeners --- // Tab button clicks if (dashboardTabButton) { dashboardTabButton.addEventListener('click', function() { switchTab(0); }); } if (dataConfigTabButton) { dataConfigTabButton.addEventListener('click', function() { switchTab(1); }); } // Navigation button clicks if (prevTabButton) { prevTabButton.addEventListener('click', function() { if (currentTabIndex > 0) { switchTab(currentTabIndex - 1); } }); } if (nextTabButton) { nextTabButton.addEventListener('click', function() { if (currentTabIndex < tabs.length - 1) { switchTab(currentTabIndex + 1); } }); } // Add Dispatch button click if (addDispatchButton) { addDispatchButton.addEventListener('click', addDispatch); } // PDF Download button click if (downloadPdfButton) { downloadPdfButton.addEventListener('click', downloadDashboardPdf); } // --- Initial Render --- // Set initial tab to Dashboard switchTab(0); // Populate data and update views updateAllViews(); });
Scroll to Top