// Materials Database Functions async function loadMaterials() { if (typeof window.materialsCollection === 'undefined') { console.error('Firebase materialsCollection is not defined.'); return; } const tableBody = document.getElementById('materialsTableBody'); tableBody.innerHTML = 'Loading...'; let tableHTML = ''; materials = []; try { const snapshot = await window.materialsCollection.get(); if (snapshot.empty) { tableBody.innerHTML = 'No materials found.'; return; } for (const doc of snapshot.docs) { const group = doc.data(); group.id = doc.id; materials.push(group); // Render group row tableHTML += ` ${group.description || 'Group'} `; const itemsSnapshot = await doc.ref.collection('items').get(); itemsSnapshot.forEach(itemDoc => { const item = itemDoc.data(); item.id = itemDoc.id; item.groupId = group.id; materials.push(item); // Render item row tableHTML += ` ${item.partNumber || ''} ${item.description || ''} ${item.oldStyle || ''} ${item.newStyle || ''} ${item.color || ''} $${parseFloat(item.price || 0).toFixed(2)} `; }); } tableBody.innerHTML = tableHTML; attachActionListeners(); updateProductSelect(); } catch (error) { console.error("Error loading materials: ", error); tableBody.innerHTML = 'Error loading materials.'; } } function attachActionListeners() { document.querySelectorAll('.edit-btn').forEach(button => { button.addEventListener('click', function() { const id = this.getAttribute('data-id'); const groupId = this.getAttribute('data-group-id'); editMaterial(id, groupId); }); }); document.querySelectorAll('.delete-btn').forEach(button => { button.addEventListener('click', function() { const id = this.getAttribute('data-id'); const groupId = this.getAttribute('data-group-id'); deleteMaterial(id, groupId); }); }); } function updateProductSelect() { const productSelect = document.getElementById('productSelect'); productSelect.innerHTML = ''; materials.forEach(material => { const option = document.createElement('option'); option.value = material.id; option.textContent = `${material.description} (${material.color}) - $${parseFloat(material.price).toFixed(2)}`; productSelect.appendChild(option); }); // If no materials, add default options if (materials.length === 0) { const defaultOptions = ['Wood Panel', 'Chain Link', 'Vinyl Panel', 'Wrought Iron']; defaultOptions.forEach(option => { const optionElement = document.createElement('option'); optionElement.textContent = option; productSelect.appendChild(optionElement); }); } } function saveMaterial(event) { event.preventDefault(); const materialData = { partNumber: document.getElementById('partNumber').value, description: document.getElementById('description').value, oldStyle: document.getElementById('oldStyle').value, newStyle: document.getElementById('newStyle').value, color: document.getElementById('color').value, price: parseFloat(document.getElementById('price').value) }; let currentEditGroupId = null; let promise; if (currentEditId) { let docRef; if (currentEditGroupId) { docRef = window.materialsCollection.doc(currentEditGroupId).collection('items').doc(currentEditId); } else { docRef = window.materialsCollection.doc(currentEditId); } promise = docRef.update(materialData); } else { promise = window.materialsCollection.add(materialData); } promise.then(() => { resetForm(); loadMaterials(); alert('Material saved successfully!'); }).catch(error => { console.error("Error saving material: ", error); alert("Error saving material. Please try again."); }); } function editMaterial(id, groupId) { const material = materials.find(m => m.id === id); if (material) { document.getElementById('materialId').value = id; document.getElementById('partNumber').value = material.partNumber || ''; document.getElementById('description').value = material.description || ''; document.getElementById('oldStyle').value = material.oldStyle || ''; document.getElementById('newStyle').value = material.newStyle || ''; document.getElementById('color').value = material.color || ''; document.getElementById('price').value = material.price || ''; document.getElementById('formTitle').textContent = 'Edit Material'; document.getElementById('cancelEdit').classList.remove('hidden'); currentEditId = id; currentEditGroupId = groupId; } } function deleteMaterial(id, groupId) { if (confirm('Are you sure you want to delete this material?')) { let docRef; if (groupId) { docRef = window.materialsCollection.doc(groupId).collection('items').doc(id); } else { docRef = window.materialsCollection.doc(id); } docRef.delete() .then(() => { resetForm(); loadMaterials(); alert('Material deleted successfully!'); }) .catch(error => { console.error("Error deleting material: ", error); alert("Error deleting material. Please try again."); }); } } function resetForm() { document.getElementById('materialForm').reset(); document.getElementById('materialId').value = ''; document.getElementById('formTitle').textContent = 'Add New Material'; document.getElementById('cancelEdit').classList.add('hidden'); currentEditId = null; } // Excel Import Functionality const requiredColumns = ['Part Number', 'Description', 'Old Style', 'New Style', 'Color', 'Price']; document.addEventListener('DOMContentLoaded', function() { document.getElementById('importExcelBtn').addEventListener('click', function() { const fileInput = document.getElementById('excelFileInput'); const importStatus = document.getElementById('importStatus'); if (!fileInput.files.length) { alert('Please select an Excel file to import.'); return; } const file = fileInput.files[0]; const reader = new FileReader(); // Show status with more visibility importStatus.classList.remove('hidden'); importStatus.classList.remove('text-red-600', 'text-green-600'); importStatus.classList.add('text-blue-600', 'font-bold', 'p-3', 'bg-blue-50', 'rounded'); importStatus.textContent = '📂 Reading file: ' + file.name; console.log('Starting import process for file:', file.name); reader.onload = async function(e) { try { const data = e.target.result; const workbook = XLSX.read(data, { type: 'binary', cellStyles: true }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); if (!jsonData.length) { importStatus.textContent = '❌ Error: No data found in the Excel file.'; return; } importStatus.innerHTML = `⏳ Processing ${jsonData.length} rows...`; let headerRowIndex = -1; for (let i = 0; i < Math.min(5, jsonData.length); i++) { const row = jsonData[i]; const hasAllHeaders = requiredColumns.every(col => row.some(cell => cell?.toString().toLowerCase().trim() === col.toLowerCase().trim())); if (hasAllHeaders) { headerRowIndex = i; break; } } if (headerRowIndex === -1) { importStatus.innerHTML = `❌ Required columns not found. Please ensure your Excel file contains: ${requiredColumns.join(', ')}.`; return; } const headers = jsonData[headerRowIndex].map(h => h.toString().trim()); const dataRows = jsonData.slice(headerRowIndex + 1); let batches = []; let currentBatch = window.db.batch(); let operationCount = 0; let currentGroupRef = null; let totalSuccessCount = 0; let totalErrorCount = 0; const errorDetails = []; dataRows.forEach((row, index) => { const rowIndexInSheet = headerRowIndex + 1 + index; // Skip empty rows if (row.every(cell => cell === null || cell === '')) { return; } const firstCellRef = XLSX.utils.encode_cell({ r: rowIndexInSheet, c: 0 }); const firstCell = worksheet[firstCellRef]; const hasFillColor = firstCell && firstCell.s && firstCell.s.fgColor && (firstCell.s.fgColor.rgb || firstCell.s.fgColor.theme); const materialData = {}; headers.forEach((header, i) => { const fieldName = { 'part number': 'partNumber', 'description': 'description', 'old style': 'oldStyle', 'new style': 'newStyle', 'color': 'color', 'price': 'price' }[header.trim().toLowerCase()]; if (fieldName) { if (row[i] !== null && row[i] !== undefined) { materialData[fieldName] = row[i].toString(); } else { materialData[fieldName] = ''; } } }); try { const price = parseFloat(materialData.price); if (isNaN(price)) { totalErrorCount++; errorDetails.push(`Row ${rowIndexInSheet + 1}: Invalid or missing 'Price'.`); return; } materialData.price = price; if (!materialData.description) { totalErrorCount++; errorDetails.push(`Row ${rowIndexInSheet + 1}: Missing 'Description'.`); return; } if (operationCount >= 499) { batches.push(currentBatch); currentBatch = window.db.batch(); operationCount = 0; } if (hasFillColor) { currentGroupRef = window.materialsCollection.doc(); currentBatch.set(currentGroupRef, materialData); } else if (currentGroupRef) { const itemRef = currentGroupRef.collection('items').doc(); currentBatch.set(itemRef, materialData); } else { const docRef = window.materialsCollection.doc(); currentBatch.set(docRef, materialData); } operationCount++; } catch (error) { totalErrorCount++; errorDetails.push(`Row ${rowIndexInSheet + 1}: Error - ${error.message}`); } }); if (operationCount > 0) { batches.push(currentBatch); } importStatus.innerHTML = `🔄 Uploading ${batches.length} batch(es) to the database...`; for (const [index, batch] of batches.entries()) { try { await batch.commit(); totalSuccessCount += 1; // Counting successful batches, not individual ops importStatus.innerHTML = `🔄 Uploading batch ${index + 1} of ${batches.length}... Success!`; } catch (error) { totalErrorCount += 1; errorDetails.push(`Batch ${index + 1} failed: ${error.message}`); console.error(`Batch ${index + 1} upload error:`, error); } } let statusHTML = `
✅ Import complete!
${totalSuccessCount} batch(es) imported successfully.
`; if (totalErrorCount > 0) { statusHTML += `
${totalErrorCount} errors occurred.
Error Details:
${errorDetails.join('\n')}
`; } statusHTML += `
Your materials have been imported. Refreshing list...
`; importStatus.innerHTML = statusHTML; loadMaterials(); } catch (error) { importStatus.innerHTML = `❌ Error processing file: ${error.message}`; console.error('Excel processing error:', error); } }; reader.readAsBinaryString(file); }); }); // Set up event listeners for the material form and refresh button document.addEventListener('DOMContentLoaded', function() { document.getElementById('materialForm').addEventListener('submit', saveMaterial); document.getElementById('cancelEdit').addEventListener('click', resetForm); // Add event listener for the refresh materials button document.getElementById('refreshMaterials').addEventListener('click', function() { const button = this; const originalText = button.innerHTML; // Show loading state button.innerHTML = '🔄 Refreshing...'; button.disabled = true; // Add a small delay to make the refresh action more visible setTimeout(function() { loadMaterials(); // Show success state briefly button.innerHTML = ' Refreshed!'; button.classList.remove('bg-indigo-600', 'hover:bg-indigo-700'); button.classList.add('bg-green-600', 'hover:bg-green-700'); // Reset button after a moment setTimeout(function() { button.innerHTML = originalText; button.disabled = false; button.classList.remove('bg-green-600', 'hover:bg-green-700'); button.classList.add('bg-indigo-600', 'hover:bg-indigo-700'); }, 1500); }, 500); }); });