Real-Time Net Worth Tracker

Real-Time Net Worth Tracker

Your Assets

Your Liabilities

Net Worth Summary

Total Assets: $0.00
Total Liabilities: $0.00
Net Worth: $0.00

No ${type}s added yet.

`; return; } items.forEach(item => { const itemDiv = document.createElement('div'); itemDiv.id = `${type}-${item.id}`; // Unique ID for each item itemDiv.classList.add('flex', 'flex-col', 'sm:flex-row', 'items-start', 'sm:items-center', 'justify-between', 'bg-white', 'p-4', 'rounded-md', 'shadow-sm', 'border', 'border-gray-200'); itemDiv.innerHTML = `
${item.name} ($${parseFloat(item.value).toFixed(2)})
`; listContainer.appendChild(itemDiv); }); } /** * Adds a new item (asset or liability) to the respective list. * @param {string} type - The type of item ('asset' or 'liability'). * @param {HTMLInputElement} nameInput - The input element for the item's name. * @param {HTMLInputElement} valueInput - The input element for the item's value. * @param {Array} itemList - The array to add the item to. * @param {HTMLElement} listContainer - The DOM container for rendering. */ function addItem(type, nameInput, valueInput, itemList, listContainer) { const name = nameInput.value.trim(); const value = parseFloat(valueInput.value); if (!name || isNaN(value) || value < 0) { alert('Please enter a valid name and a positive numeric value.'); // Use a custom modal in a real app, per spec return; } const newItem = { id: Date.now().toString(), // Unique ID for the item name: name, value: value }; itemList.push(newItem); renderList(itemList, listContainer, type); updateNetWorth(); // Clear inputs nameInput.value = ''; valueInput.value = ''; } // Globally define editItem and deleteItem for onclick attributes window.editItem = function(type, id) { let itemList; let nameInput; let valueInput; if (type === 'asset') { itemList = assets; nameInput = newAssetNameInput; valueInput = newAssetValueInput; } else if (type === 'liability') { itemList = liabilities; nameInput = newLiabilityNameInput; valueInput = newLiabilityValueInput; } else { console.error('Unknown item type for edit:', type); return; } const itemIndex = itemList.findIndex(item => item.id === id); if (itemIndex === -1) { console.error('Item not found for editing:', id); return; } const itemToEdit = itemList[itemIndex]; nameInput.value = itemToEdit.name; valueInput.value = itemToEdit.value; // Remove the item from the list after putting its values into inputs itemList.splice(itemIndex, 1); renderList(itemList, type === 'asset' ? assetsList : liabilitiesList, type); updateNetWorth(); }; window.deleteItem = function(type, id) { let itemList; let listContainer; if (type === 'asset') { itemList = assets; listContainer = assetsList; } else if (type === 'liability') { itemList = liabilities; listContainer = liabilitiesList; } else { console.error('Unknown item type for delete:', type); return; } const initialLength = itemList.length; // Filter out the item to be deleted itemList = itemList.filter(item => item.id !== id); // Update the original array reference (important for let-declared arrays) if (type === 'asset') { assets = itemList; } else { liabilities = itemList; } if (itemList.length === initialLength) { console.warn('Item not found for deletion:', id); return; // Item not found, no change } renderList(itemList, listContainer, type); updateNetWorth(); }; // Event Listeners for Adding Items if (addAssetButton && newAssetNameInput && newAssetValueInput) { addAssetButton.addEventListener('click', function() { addItem('asset', newAssetNameInput, newAssetValueInput, assets, assetsList); }); newAssetValueInput.addEventListener('keypress', function(event) { if (event.key === 'Enter') { addItem('asset', newAssetNameInput, newAssetValueInput, assets, assetsList); } }); } else { console.error('Missing asset input/button elements.'); } if (addLiabilityButton && newLiabilityNameInput && newLiabilityValueInput) { addLiabilityButton.addEventListener('click', function() { addItem('liability', newLiabilityNameInput, newLiabilityValueInput, liabilities, liabilitiesList); }); newLiabilityValueInput.addEventListener('keypress', function(event) { if (event.key === 'Enter') { addItem('liability', newLiabilityNameInput, newLiabilityValueInput, liabilities, liabilitiesList); } }); } else { console.error('Missing liability input/button elements.'); } // Event Listeners for Tab Navigation if (tabButtons.assets) tabButtons.assets.addEventListener('click', () => showTab('assets')); if (tabButtons.liabilities) tabButtons.liabilities.addEventListener('click', () => showTab('liabilities')); if (tabButtons.summary) tabButtons.summary.addEventListener('click', () => showTab('summary')); // Event Listeners for Next/Previous Buttons if (nextButton) { nextButton.addEventListener('click', function() { if (currentTab === 'assets') { showTab('liabilities'); } else if (currentTab === 'liabilities') { showTab('summary'); } }); } if (prevButton) { prevButton.addEventListener('click', function() { if (currentTab === 'summary') { showTab('liabilities'); } else if (currentTab === 'liabilities') { showTab('assets'); } }); } /** * Handles the PDF download functionality. * Captures the 'pdfContent' div and generates a PDF. */ if (downloadPdfButton) { downloadPdfButton.addEventListener('click', function() { if (!pdfContentArea) { console.error('Error: PDF content area not found.'); return; } // Temporarily set display of all tab contents to block for capture, but only for the PDF content area const originalDisplays = {}; for (const key in tabContents) { if (tabContents.hasOwnProperty(key)) { originalDisplays[key] = tabContents[key].style.display; tabContents[key].style.display = 'block'; // Make all tabs visible for capture } } // Remove navigation buttons for PDF capture prevButton.style.display = 'none'; nextButton.style.display = 'none'; downloadPdfButton.style.display = 'none'; html2canvas(pdfContentArea, { scale: 2, // Increase scale for better quality PDF useCORS: true, logging: false // Suppress logging for cleaner console }).then(canvas => { const { jsPDF } = window.jspdf; const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); // Portrait, millimeters, A4 size const imgWidth = 210; // A4 width in mm const pageHeight = 297; // A4 height in mm const imgHeight = canvas.height * imgWidth / canvas.width; let heightLeft = imgHeight; let position = 0; // Add image to PDF, handling multiple pages if content is long pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; while (heightLeft >= 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('Net_Worth_Statement.pdf'); // Save the PDF // Restore original displays and button visibility after PDF generation for (const key in tabContents) { if (tabContents.hasOwnProperty(key)) { tabContents[key].style.display = originalDisplays[key]; } } showTab(currentTab); // Re-activate the current tab prevButton.style.display = 'inline-block'; // Restore display property nextButton.style.display = 'inline-block'; // Restore display property downloadPdfButton.style.display = 'inline-block'; // Restore display property }).catch(error => { console.error('Error generating PDF:', error); // Ensure buttons are restored even if PDF generation fails for (const key in tabContents) { if (tabContents.hasOwnProperty(key)) { tabContents[key].style.display = originalDisplays[key]; } } showTab(currentTab); prevButton.style.display = 'inline-block'; nextButton.style.display = 'inline-block'; downloadPdfButton.style.display = 'inline-block'; }); }); } else { console.error('Download PDF button not found.'); } // Initial rendering and calculation renderList(assets, assetsList, 'asset'); renderList(liabilities, liabilitiesList, 'liability'); updateNetWorth(); showTab('assets'); // Show the default tab on load });
Scroll to Top