Manually input or edit your mixing batch data below. All changes will automatically update the dashboard.
Batch ID
Product Name
Mix Time (min)
Mix Speed (RPM)
Output Quantity (Units)
Quality Status
Mixing Date
Notes
Actions
${overallMetrics.totalBatches.toLocaleString()}
Batches Passed
${overallMetrics.passBatches.toLocaleString()}
Batches Failed
${overallMetrics.failBatches.toLocaleString()}
Batches with Rework
${overallMetrics.reworkBatches.toLocaleString()}
Total Output Quantity
${overallMetrics.totalOutputQuantity.toLocaleString()} Units
Overall Pass Rate
${overallMetrics.passRate.toFixed(2)}%
`;
}
/**
* Renders the batch details table in the Dashboard tab.
*/
function renderBatchDetailsTable() {
// Null check for the batch details table body
if (!batchDetailsTableBody) {
console.error("Batch details table body not found.");
return;
}
batchDetailsTableBody.innerHTML = ''; // Clear existing rows
mixingData.forEach(batch => {
const row = document.createElement('tr');
row.innerHTML = `
${batch.batchId}
${batch.productName}
${batch.mixTimeMin}
${batch.mixSpeedRPM}
${batch.outputQuantity.toLocaleString()}
${batch.qualityStatus}
${batch.mixingDate || 'N/A'}
${batch.notes || 'N/A'}
`;
batchDetailsTableBody.appendChild(row);
});
}
/**
* Renders the data input table in the Mix Configuration tab.
*/
function renderDataInputTable() {
// Null check for the data input table body
if (!dataInputTableBody) {
console.error("Data input table body not found.");
return;
}
dataInputTableBody.innerHTML = ''; // Clear existing rows
mixingData.forEach(batch => {
const row = document.createElement('tr');
row.innerHTML = `
`;
dataInputTableBody.appendChild(row);
});
// Attach event listeners to newly created input fields and delete buttons
document.querySelectorAll('.batch-input').forEach(input => {
input.addEventListener('change', handleInputChange);
});
document.querySelectorAll('.delete-row-btn').forEach(button => {
button.addEventListener('click', handleDeleteRow);
});
}
/**
* Updates all dashboard and data configuration views.
*/
function updateViews() {
renderMixingSummary();
renderBatchDetailsTable();
renderDataInputTable();
}
// --- Event Handlers ---
/**
* Handles tab button clicks to switch between tabs.
* @param {Event} event - The click event.
*/
function handleTabClick(event) {
// Null check for event target
if (!event.target) {
console.error("Event target is null.");
return;
}
const targetTab = event.target.dataset.tab;
// Remove 'active' class from all tab buttons and content
tabButtons.forEach(button => button.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
// Add 'active' class to the clicked tab button and corresponding content
event.target.classList.add('active');
const activeTabContent = document.getElementById(`${targetTab}-tab`);
if (activeTabContent) { // Null check for activeTabContent
activeTabContent.classList.add('active');
} else {
console.error(`Tab content for ${targetTab} not found.`);
}
updateViews(); // Update views whenever tab changes
}
/**
* Handles changes in data input fields.
* @param {Event} event - The change event.
*/
function handleInputChange(event) {
// Null check for event target
if (!event.target) {
console.error("Event target is null.");
return;
}
const input = event.target;
const id = parseInt(input.dataset.id);
const field = input.dataset.field;
let value = input.value;
// Convert numeric fields to numbers and apply constraints
if (['mixTimeMin', 'mixSpeedRPM', 'outputQuantity'].includes(field)) {
value = parseFloat(value) || 0; // Default to 0 if parsing fails
if (value < 0) value = 0; // Ensure non-negative values
input.value = value; // Update input field with sanitized value
}
const batchIndex = mixingData.findIndex(d => d.id === id);
if (batchIndex !== -1) {
mixingData[batchIndex][field] = value;
updateViews(); // Re-render dashboard and data input tables
} else {
console.error(`Batch with ID ${id} not found.`);
}
}
/**
* Adds a new empty row to the data input table.
*/
function handleAddBatch() {
const newBatch = {
id: nextId++,
batchId: `B-${new Date().toISOString().slice(0, 10)}-${String(nextId - 1).padStart(3, '0')}`,
productName: 'New Product',
mixTimeMin: 0,
mixSpeedRPM: 0,
outputQuantity: 0,
qualityStatus: 'Pass',
mixingDate: new Date().toISOString().slice(0, 10), // Default to today's date
notes: ''
};
mixingData.push(newBatch);
updateViews(); // Re-render data input table
}
/**
* Deletes a row from the data input table.
* @param {Event} event - The click event.
*/
function handleDeleteRow(event) {
// Null check for event target
if (!event.target) {
console.error("Event target is null.");
return;
}
const idToDelete = parseInt(event.target.dataset.id);
mixingData = mixingData.filter(d => d.id !== idToDelete);
updateViews(); // Re-render data input table
}
/**
* Handles navigation to the previous tab.
*/
function handlePrevTab() {
const currentActiveTab = document.querySelector('.tab-button.active');
if (!currentActiveTab) {
console.error("No active tab found.");
return;
}
const currentIndex = Array.from(tabButtons).indexOf(currentActiveTab);
if (currentIndex > 0) {
tabButtons[currentIndex - 1].click(); // Simulate click on previous tab
}
}
/**
* Handles navigation to the next tab.
*/
function handleNextTab() {
const currentActiveTab = document.querySelector('.tab-button.active');
if (!currentActiveTab) {
console.error("No active tab found.");
return;
}
const currentIndex = Array.from(tabButtons).indexOf(currentActiveTab);
if (currentIndex < tabButtons.length - 1) {
tabButtons[currentIndex + 1].click(); // Simulate click on next tab
}
}
/**
* Handles the PDF download functionality.
* It uses html2canvas to capture the dashboard content and jsPDF to generate the PDF.
*/
async function handleDownloadPdf() {
const dashboardContent = document.querySelector('.dashboard-content-for-pdf');
if (!dashboardContent) {
console.error("Dashboard content for PDF not found.");
return;
}
// Add a temporary title for the PDF
const tempTitle = document.createElement('h1');
tempTitle.textContent = "Mixing Dashboard";
tempTitle.style.textAlign = 'center';
tempTitle.style.marginBottom = '20px';
tempTitle.style.fontSize = '2em';
tempTitle.style.fontWeight = 'bold';
tempTitle.style.color = '#333';
dashboardContent.prepend(tempTitle); // Prepend to capture
// Temporarily hide tab navigation and buttons for PDF capture
const elementsToHide = document.querySelectorAll('.tab-nav, .tab-navigation-buttons, .add-row-btn, .delete-row-btn, .pdf-download-section .btn-primary');
elementsToHide.forEach(el => el.style.display = 'none');
// Ensure the dashboard tab is active before capturing for PDF
const dashboardTabButton = document.querySelector('.tab-button[data-tab="dashboard"]');
if (dashboardTabButton && !dashboardTabButton.classList.contains('active')) {
dashboardTabButton.click(); // Activate dashboard tab if not already active
// A small delay might be needed here if content takes time to render after tab switch
await new Promise(resolve => setTimeout(resolve, 100));
}
try {
const canvas = await html2canvas(dashboardContent, {
scale: 2, // Increase scale for better resolution
useCORS: true, // Required if images are from different origin (though we don't use external images)
windowWidth: dashboardContent.scrollWidth,
windowHeight: dashboardContent.scrollHeight
});
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({
orientation: 'landscape', // Landscape for wider tables
unit: 'px',
format: 'a4'
});
const imgWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const imgHeight = canvas.height * imgWidth / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save('mixing_dashboard.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
// Using alert as a fallback for critical error feedback, as per standard specification
alert("Failed to generate PDF. Please try again.");
} finally {
// Restore hidden elements and remove temporary title
elementsToHide.forEach(el => el.style.display = '');
if (tempTitle.parentNode) {
tempTitle.parentNode.removeChild(tempTitle);
}
}
}
// --- Event Listener Attachments ---
tabButtons.forEach(button => {
// Null check for button
if (button) {
button.addEventListener('click', handleTabClick);
}
});
// Null checks before attaching listeners
if (addBatchBtn) {
addBatchBtn.addEventListener('click', handleAddBatch);
} else {
console.error("Add Batch button not found.");
}
if (downloadPdfBtn) {
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
} else {
console.error("Download PDF button not found.");
}
if (prevTabBtn) {
prevTabBtn.addEventListener('click', handlePrevTab);
} else {
console.error("Previous Tab button not found.");
}
if (nextTabBtn) {
nextTabBtn.addEventListener('click', handleNextTab);
} else {
console.error("Next Tab button not found.");
}
// Initial render of the dashboard and data input table
updateViews();
});