Scope Change Dashboard

Total Requests

0

Budget Impact

$0

Timeline Impact

0 days

Approval Rate

0%

Change Request Funnel

Impact of Approved Changes

Change Request Log

IDDescriptionStatusBudget ImpactTimeline ImpactDate Submitted

Log a New Change Request

All Change Requests

IDDescriptionStatusAction

Error: A required library is missing.

'; return; } // --- DATA MANAGEMENT --- let changeRequests = JSON.parse(localStorage.getItem('scd_requests')) || []; const getSampleData = () => { const today = new Date('2025-07-09'); const d = (daysAgo) => new Date(today.getTime() - daysAgo * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; return [ { id: 'CR-001', desc: 'Add two-factor authentication to user login flow.', status: 'Implemented', reason: 'Technical Requirement', budget: 15000, timeline: 10, date: d(20) }, { id: 'CR-002', desc: 'Change primary brand color from blue to purple.', status: 'Implemented', reason: 'Client Request', budget: 2500, timeline: 2, date: d(15) }, { id: 'CR-003', desc: 'Integrate with third-party payment gateway (Stripe).', status: 'Approved', reason: 'Client Request', budget: 25000, timeline: 14, date: d(10) }, { id: 'CR-004', desc: 'Develop an admin-only user management dashboard.', status: 'Pending Review', reason: 'Design Improvement', budget: 40000, timeline: 20, date: d(5) }, { id: 'CR-005', desc: 'Migrate database from MySQL to PostgreSQL for scalability.', status: 'Rejected', reason: 'Technical Requirement', budget: 75000, timeline: 30, date: d(2) }, ]; }; if (changeRequests.length === 0) changeRequests = getSampleData(); const saveState = () => localStorage.setItem('scd_requests', JSON.stringify(changeRequests)); // --- CHART INSTANCES & UTILITIES --- let statusChart, impactChart; const tabButtons = document.querySelectorAll('.scd-tab-button'); const tabContents = document.querySelectorAll('.scd-tab-content'); const nextBtn = document.getElementById('scd-next-btn'); const prevBtn = document.getElementById('scd-prev-btn'); const formatCurrency = (val) => val.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); // --- RENDER FUNCTIONS --- const renderAll = () => { try { renderKPIs(); renderStatusChart(); renderImpactChart(); renderLogTable(); renderManageTable(); updateNavButtons(); } catch(error) { console.error("Dashboard rendering failed:", error); } }; const renderKPIs = () => { const approved = changeRequests.filter(cr => cr.status === 'Approved' || cr.status === 'Implemented'); const totalBudgetImpact = approved.reduce((sum, cr) => sum + cr.budget, 0); const totalTimelineImpact = approved.reduce((sum, cr) => sum + cr.timeline, 0); const totalConsidered = changeRequests.filter(cr => cr.status !== 'Pending Review'); const approvalRate = totalConsidered.length > 0 ? (approved.length / totalConsidered.length) * 100 : 0; document.getElementById('scd-total-requests-kpi').textContent = changeRequests.length; document.getElementById('scd-budget-impact-kpi').textContent = formatCurrency(totalBudgetImpact); document.getElementById('scd-timeline-impact-kpi').textContent = `${totalTimelineImpact} days`; document.getElementById('scd-approval-rate-kpi').textContent = `${approvalRate.toFixed(1)}%`; }; const renderStatusChart = () => { const funnelData = [ { x: 'Pending Review', y: changeRequests.filter(cr => cr.status === 'Pending Review').length }, { x: 'Approved', y: changeRequests.filter(cr => cr.status === 'Approved').length }, { x: 'Implemented', y: changeRequests.filter(cr => cr.status === 'Implemented').length }, { x: 'Rejected', y: changeRequests.filter(cr => cr.status === 'Rejected').length }, ]; const options = { chart: { type: 'funnel', height: 350 }, series: [{ name: 'Requests', data: funnelData.map(d => d.y) }], plotOptions: { funnel: { distributed: true } }, xaxis: { categories: funnelData.map(d => d.x) }, legend: { show: false }, colors: ['var(--scd-warning-color)', 'var(--scd-secondary-color)', 'var(--scd-success-color)', 'var(--scd-danger-color)'] }; if(statusChart) statusChart.destroy(); document.querySelector("#scd-status-chart").innerHTML = ''; statusChart = new ApexCharts(document.querySelector("#scd-status-chart"), options); statusChart.render(); }; const renderImpactChart = () => { const approved = changeRequests.filter(cr => cr.status === 'Approved' || cr.status === 'Implemented'); const impactByReason = approved.reduce((acc, cr) => { if (!acc[cr.reason]) acc[cr.reason] = { budget: 0, timeline: 0 }; acc[cr.reason].budget += cr.budget; acc[cr.reason].timeline += cr.timeline; return acc; }, {}); const options = { chart: { type: 'bar', height: 350, stacked: true }, series: [ { name: 'Budget Impact ($)', data: Object.values(impactByReason).map(v => v.budget) }, { name: 'Timeline Impact (Days)', data: Object.values(impactByReason).map(v => v.timeline) } ], xaxis: { categories: Object.keys(impactByReason) }, plotOptions: { bar: { horizontal: false } }, dataLabels: { enabled: false }, colors: ['var(--scd-primary-color)', 'var(--scd-warning-color)'], tooltip: { y: { formatter: function(val, { seriesIndex }) { return seriesIndex === 0 ? formatCurrency(val) : `${val} days`; } } } }; if(impactChart) impactChart.destroy(); document.querySelector("#scd-impact-chart").innerHTML = ''; impactChart = new ApexCharts(document.querySelector("#scd-impact-chart"), options); impactChart.render(); }; const renderLogTable = () => { const tbody = document.getElementById('scd-log-tbody'); tbody.innerHTML = ''; changeRequests.sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(cr => { const statusClass = `status-${cr.status.toLowerCase().replace(/ /g, '-')}`; tbody.innerHTML += ` ${cr.id} ${cr.desc} ${cr.status} ${formatCurrency(cr.budget)} ${cr.timeline} days ${cr.date} `; }); }; const renderManageTable = () => { const tbody = document.getElementById('scd-manage-tbody'); tbody.innerHTML = ''; changeRequests.forEach(cr => { tbody.innerHTML += `${cr.id}${cr.desc}${cr.status}`; }); }; // --- EVENT HANDLING --- const switchTab = (tabId) => { tabContents.forEach(c => c.style.display = 'none'); tabButtons.forEach(b => b.classList.remove('active')); const activeContent = document.getElementById(tabId); const activeButton = document.querySelector(`.scd-tab-button[data-tab="${tabId}"]`); if (activeContent && activeButton) { activeContent.style.display = 'block'; activeButton.classList.add('active'); } updateNavButtons(); }; const updateNavButtons = () => { const i = [...tabButtons].findIndex(b => b.classList.contains('active')); prevBtn.disabled = i === 0; nextBtn.disabled = i === tabButtons.length - 1; }; tabButtons.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.tab))); nextBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i < tabButtons.length - 1) switchTab(tabButtons[i+1].dataset.tab); }); prevBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i > 0) switchTab(tabButtons[i-1].dataset.tab); }); document.getElementById('scd-cr-form').addEventListener('submit', e => { e.preventDefault(); const newCR = { id: 'CR-' + String(Math.floor(Math.random() * 900) + 100).padStart(3, '0'), desc: document.getElementById('scd-cr-desc').value, status: document.getElementById('scd-cr-status').value, reason: document.getElementById('scd-cr-reason').value, budget: parseFloat(document.getElementById('scd-cr-budget').value), timeline: parseInt(document.getElementById('scd-cr-timeline').value), date: new Date().toISOString().split('T')[0] }; changeRequests.unshift(newCR); saveState(); renderAll(); e.target.reset(); switchTab('scd-dashboard-tab'); }); document.getElementById('scd-manage-tbody').addEventListener('click', e => { if(e.target.tagName === 'BUTTON') { const id = e.target.dataset.id; if(confirm('Are you sure you want to delete this change request?')) { changeRequests = changeRequests.filter(cr => cr.id !== id); saveState(); renderAll(); } } }); // --- PDF EXPORT --- document.getElementById('scd-download-pdf-btn').addEventListener('click', function() { const btn = this; btn.textContent = 'Generating...'; btn.disabled = true; const pdfCaptureArea = document.getElementById('scd-pdf-capture-area'); html2canvas(pdfCaptureArea, { scale: 2 }).then(canvas => { const doc = new jsPDF(); const imgData = canvas.toDataURL('image/png'); doc.setFontSize(18); doc.text('Scope Change Report', 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Generated on: ${new Date().toLocaleDateString('en-US')}`, 14, 29); const imgWidth = doc.internal.pageSize.getWidth() - 28; const imgHeight = (canvas.height * imgWidth) / canvas.width; doc.addImage(imgData, 'PNG', 14, 40, imgWidth, imgHeight); doc.addPage(); doc.autoTable({ html: '.scd-log-table', startY: 15, theme: 'striped', headStyles: { fillColor: [44, 62, 80] } }); doc.save('Scope_Change_Report.pdf'); }).finally(() => { btn.textContent = 'Download Report'; btn.disabled = false; }); }); // --- INITIALIZATION --- renderAll(); });
Scroll to Top