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
| ID | Description | Status | Budget Impact | Timeline Impact | Date Submitted |
|---|
Log a New Change Request
All Change Requests
| ID | Description | Status | Action |
|---|
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 += `