Investment Portfolio Dashboard

Total Value

$0

Day's Gain/Loss

$0 (0%)

Total Gain/Loss

$0

Portfolio Value (Live)

Asset Allocation

Holdings

TickerQuantityCurrent PriceDay's ChangeMarket ValueTotal Gain/Loss

Add New Holding

Manage All Holdings

TickerQuantityPurchase PriceAction

Error: A required library is missing.

'; return; } // --- DATA MANAGEMENT --- let holdings = JSON.parse(localStorage.getItem('ipd_holdings')) || []; const getSampleData = () => [ { id: 1, ticker: 'AAPL', class: 'US Equity', qty: 50, purchasePrice: 150.25, currentPrice: 195.50, prevPrice: 194.80 }, { id: 2, ticker: 'MSFT', class: 'US Equity', qty: 30, purchasePrice: 300.50, currentPrice: 420.75, prevPrice: 422.10 }, { id: 3, ticker: 'VOO', class: 'US Equity', qty: 20, purchasePrice: 400.00, currentPrice: 480.25, prevPrice: 479.90 }, { id: 4, ticker: 'VXUS', class: 'International Equity', qty: 100, purchasePrice: 55.75, currentPrice: 62.50, prevPrice: 62.60 }, { id: 5, ticker: 'BND', class: 'Bonds', qty: 150, purchasePrice: 75.10, currentPrice: 72.50, prevPrice: 72.45 }, ]; if (holdings.length === 0) holdings = getSampleData(); const saveState = () => localStorage.setItem('ipd_holdings', JSON.stringify(holdings)); // --- CHART INSTANCES & UTILITIES --- let valueChart, allocChart; const tabButtons = document.querySelectorAll('.ipd-tab-button'); const tabContents = document.querySelectorAll('.ipd-tab-content'); const nextBtn = document.getElementById('ipd-next-btn'); const prevBtn = document.getElementById('ipd-prev-btn'); const formatCurrency = (val, dec=2) => val.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: dec, maximumFractionDigits: dec }); // --- RENDER FUNCTIONS --- const renderAll = () => { try { const processedData = holdings.map(h => ({ ...h, marketValue: h.qty * h.currentPrice, totalGainLoss: (h.currentPrice - h.purchasePrice) * h.qty, dayChange: (h.currentPrice - h.prevPrice) * h.qty })); renderKPIs(processedData); renderValueChart(processedData); renderAllocationChart(processedData); renderHoldingsTable(processedData); renderManageTable(); updateNavButtons(); } catch(error) { console.error("Dashboard rendering failed:", error); } }; const renderKPIs = (data) => { const totalValue = data.reduce((sum, d) => sum + d.marketValue, 0); const totalDayChange = data.reduce((sum, d) => sum + d.dayChange, 0); const totalGainLoss = data.reduce((sum, d) => sum + d.totalGainLoss, 0); const dayChangePercent = totalValue > 0 ? (totalDayChange / (totalValue - totalDayChange)) * 100 : 0; document.getElementById('ipd-value-kpi').textContent = formatCurrency(totalValue); const dayChangeEl = document.getElementById('ipd-day-change-kpi'); dayChangeEl.innerHTML = `${formatCurrency(totalDayChange)} (${dayChangePercent.toFixed(2)}%)`; dayChangeEl.className = totalDayChange >= 0 ? 'positive' : 'negative'; const totalGainEl = document.getElementById('ipd-total-gain-kpi'); totalGainEl.textContent = formatCurrency(totalGainLoss); totalGainEl.className = totalGainLoss >= 0 ? 'positive' : 'negative'; }; const renderValueChart = (data) => { // This is a simplified simulation const totalValue = data.reduce((sum, d) => sum + d.marketValue, 0); const seriesData = [totalValue * 0.995, totalValue * 0.998, totalValue * 0.997, totalValue * 1.002, totalValue]; const options = { chart: { type: 'area', height: 350, toolbar: { show: false }, sparkline: { enabled: true } }, series: [{ name: 'Portfolio Value', data: seriesData }], stroke: { curve: 'smooth', width: 2 }, fill: { type: 'gradient', gradient: { opacityFrom: 0.6, opacityTo: 0, stops: [0, 100] } }, colors: ['var(--ipd-primary-color)'] }; if(valueChart) valueChart.destroy(); document.querySelector("#ipd-value-chart").innerHTML = ''; valueChart = new ApexCharts(document.querySelector("#ipd-value-chart"), options); valueChart.render(); }; const renderAllocationChart = (data) => { const alloc = data.reduce((acc, h) => { acc[h.class] = (acc[h.class] || 0) + h.marketValue; return acc; }, {}); const options = { chart: { type: 'donut', height: 350 }, series: Object.values(alloc), labels: Object.keys(alloc), legend: { position: 'bottom' } }; if(allocChart) allocChart.destroy(); document.querySelector("#ipd-allocation-chart").innerHTML = ''; allocChart = new ApexCharts(document.querySelector("#ipd-allocation-chart"), options); allocChart.render(); }; const renderHoldingsTable = (data) => { const tbody = document.getElementById('ipd-holdings-tbody'); tbody.innerHTML = ''; data.sort((a,b) => b.marketValue - a.marketValue).forEach(h => { const dayChangeClass = h.dayChange >= 0 ? 'positive' : 'negative'; const totalGainClass = h.totalGainLoss >= 0 ? 'positive' : 'negative'; tbody.innerHTML += `
${h.ticker}
${h.qty.toLocaleString()} ${formatCurrency(h.currentPrice)} ${formatCurrency(h.dayChange)} ${formatCurrency(h.marketValue)} ${formatCurrency(h.totalGainLoss)} `; }); }; const renderManageTable = () => { const tbody = document.getElementById('ipd-manage-tbody'); tbody.innerHTML = ''; holdings.forEach(h => { tbody.innerHTML += `${h.name}${h.ticker}${formatCurrency(h.purchasePrice)} `; }); }; // --- 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(`.ipd-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('ipd-holding-form').addEventListener('submit', e => { e.preventDefault(); const purchasePrice = parseFloat(document.getElementById('ipd-form-purchase-price').value); holdings.push({ id: Date.now(), ticker: document.getElementById('ipd-form-ticker').value.toUpperCase(), class: document.getElementById('ipd-form-class').value, qty: parseFloat(document.getElementById('ipd-form-qty').value), purchasePrice: purchasePrice, currentPrice: purchasePrice, // Start with current price = purchase price prevPrice: purchasePrice }); saveState(); renderAll(); e.target.reset(); switchTab('ipd-dashboard-tab'); }); document.getElementById('ipd-manage-tbody').addEventListener('click', e => { if(e.target.tagName === 'BUTTON') { if(confirm('Are you sure?')) { holdings = holdings.filter(h => h.id != e.target.dataset.id); saveState(); renderAll(); } } }); // --- LIVE SIMULATION --- setInterval(() => { holdings.forEach(h => { h.prevPrice = h.currentPrice; const changePercent = (Math.random() - 0.5) * 0.01; // Max 0.5% change per tick h.currentPrice = h.currentPrice * (1 + changePercent); }); renderAll(); }, 3000); // Update every 3 seconds // --- PDF EXPORT --- document.getElementById('ipd-download-pdf-btn').addEventListener('click', function() { const btn = this; btn.textContent = 'Generating...'; btn.disabled = true; const pdfCaptureArea = document.getElementById('ipd-pdf-capture-area'); html2canvas(pdfCaptureArea, { scale: 2 }).then(canvas => { const doc = new jsPDF(); const imgData = canvas.toDataURL('image/png'); doc.setFontSize(18); doc.text('Investment Portfolio Summary', 14, 22); const imgWidth = doc.internal.pageSize.getWidth() - 28; const imgHeight = (canvas.height * imgWidth) / canvas.width; doc.addImage(imgData, 'PNG', 14, 30, imgWidth, imgHeight); doc.addPage(); doc.autoTable({ html: '.ipd-table', startY: 15, theme: 'striped' }); doc.save('Investment_Portfolio_Summary.pdf'); }).finally(() => { btn.textContent = 'Download Summary'; btn.disabled = false; }); }); // --- INITIALIZATION --- renderAll(); });
Scroll to Top