Supplier Relationship Dashboard
Relationship Health Overview
Total Suppliers
0
Avg. Relationship Score
0
At-Risk Suppliers
0
Overdue Communications
0
Supplier Relationship Tiers
Common Issues Logged
Supplier Details
| Supplier | Contact | Relationship Score | Last Contact | Next Follow-up |
|---|
Communication Log
Add New Supplier
Log Communication
${c.notes}
`; list.appendChild(el); }); } function renderConfigForms() { const supplierSelect = document.getElementById('comm-supplier-select'); supplierSelect.innerHTML = ''; state.suppliers.forEach(s => { const option = document.createElement('option'); option.value = s.id; option.textContent = s.name; supplierSelect.appendChild(option); }); } // --- EVENT HANDLERS & LOGIC --- // function attachEventListeners() { prevBtn.addEventListener('click', () => changeTab(state.activeTab - 1)); nextBtn.addEventListener('click', () => changeTab(state.activeTab + 1)); downloadPdfBtn.addEventListener('click', handleDownloadPdf); document.getElementById('add-supplier-form').addEventListener('submit', handleAddSupplier); document.getElementById('add-comm-form').addEventListener('submit', handleAddComm); } window.changeTab = (tabIndex) => { if (tabIndex < 0 || tabIndex >= tabButtons.length) return; state.activeTab = tabIndex; updateTabUI(); }; function updateTabUI() { tabButtons.forEach((b, i) => b.classList.toggle('active', i === state.activeTab)); tabButtons.forEach((b, i) => b.classList.toggle('inactive', i !== state.activeTab)); tabPanes.forEach((p, i) => p.classList.toggle('hidden', i !== state.activeTab)); prevBtn.disabled = state.activeTab === 0; nextBtn.disabled = state.activeTab === tabButtons.length - 1; prevBtn.classList.toggle('opacity-50', prevBtn.disabled); nextBtn.classList.toggle('opacity-50', nextBtn.disabled); } function handleAddSupplier(e) { e.preventDefault(); const form = e.target; state.suppliers.push({ id: Date.now(), name: form.elements['supplier-name'].value, contact: form.elements['supplier-contact'].value, email: form.elements['supplier-email'].value, }); form.reset(); render(); changeTab(1); } function handleAddComm(e) { e.preventDefault(); const form = e.target; state.communications.push({ supplierId: parseInt(form.elements['comm-supplier-select'].value), date: form.elements['comm-date'].value, type: form.elements['comm-type'].value, notes: form.elements['comm-notes'].value, followUp: form.elements['comm-follow-up'].value || null, }); form.reset(); render(); changeTab(2); } async function handleDownloadPdf() { const downloadBtn = document.getElementById('download-pdf-btn'); if (!downloadBtn || downloadBtn.disabled) return; const originalBtnContent = downloadBtn.innerHTML; downloadBtn.disabled = true; downloadBtn.innerHTML = `Generating PDF...`; try { const { jsPDF } = window.jspdf; const contentToClone = document.getElementById('supplier-relationship-container'); const contentToExport = contentToClone.cloneNode(true); contentToExport.querySelectorAll('.pdf-hide').forEach(el => el.remove()); contentToExport.querySelectorAll('.tab-pane:not(#tab-0-content)').forEach(el => el.remove()); contentToExport.querySelector('#tab-0-content').classList.remove('hidden'); document.body.appendChild(contentToExport); contentToExport.style.cssText = 'position:absolute; left:0; top:0; width:1024px; z-index:-1; background-color:white; padding:2rem;'; const canvas = await html2canvas(contentToExport, { scale: 2, useCORS: true, logging: false }); document.body.removeChild(contentToExport); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const scaledHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, scaledHeight); pdf.save('Supplier_Relationship_Summary.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); } finally { downloadBtn.disabled = false; downloadBtn.innerHTML = originalBtnContent; } } // --- UTILITY FUNCTIONS --- // function calculateMetrics() { let totalScore = 0; state.suppliers.forEach(s => { const supplierComms = state.communications.filter(c => c.supplierId === s.id); let score = 100; // Deduct points for issues const issues = supplierComms.filter(c => c.type === 'Issue Logged'); score -= issues.length * 20; // Add points for recent, positive communication const recentComms = supplierComms.filter(c => { const commDate = new Date(c.date); const daysDiff = (today - commDate) / (1000 * 60 * 60 * 24); return daysDiff <= 90 && c.type !== 'Issue Logged'; }); score += recentComms.length * 5; s.score = Math.max(0, Math.min(100, score)); totalScore += s.score; const sortedComms = supplierComms.sort((a,b) => new Date(b.date) - new Date(a.date)); if (sortedComms.length > 0) { s.lastContact = sortedComms[0].date; const followUps = sortedComms.map(c => c.followUp).filter(Boolean).sort((a,b) => new Date(a) - new Date(b)); s.nextFollowUp = followUps.find(d => new Date(d) >= today) || null; } else { s.lastContact = null; s.nextFollowUp = null; } }); state.averageScore = state.suppliers.length > 0 ? totalScore / state.suppliers.length : 0; } // --- START THE APP --- // initialize(); });