"use strict"; (() => { // Helper functions const $ = id => document.getElementById(id); const adjustValue = (id, delta) => { const input = $(id); const currentValue = parseFloat(input.value) || 0; const step = parseFloat(input.getAttribute('data-step')) || 1; input.value = currentValue + delta * step; calculate(); }; // Register Chart.js plugins Chart.register(ChartDataLabels); let chart; let investedChart; let incomeChart; // Global variable for income chart let coinTrackerCounter = 0; let investedHistory = { labels: [], data: [] }; // Update coin trackers on the backend with the given data const updateCoinTrackersToBackend = async (trackers) => { try { const response = await fetch("/api/coinTrackers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: trackers }) }); if (!response.ok) { console.error("Failed to update backend:", response.statusText); } } catch (error) { console.error("Error updating backend:", error); } }; // Main calculation for the compounding interest chart, left panel summary, and analysis table const calculate = () => { const initialPrincipal = parseFloat($('initialPrincipal').value) || 0; const dailyRate = parseFloat($('dailyRate').value) || 0; const termDays = parseInt($('termDays').value, 10) || 0; const reinvestRate = parseFloat($('reinvestRate').value) || 0; const labels = []; const reinvestArray = []; const cashArray = []; const earningsDaily = []; const tableRows = []; let runningPrincipal = initialPrincipal; let cumulativeCash = 0; let currentDate = new Date(); currentDate.setDate(currentDate.getDate() + 1); for (let i = 0; i < termDays; i++) { const dateStr = `${currentDate.getMonth()+1}/${currentDate.getDate()}/${currentDate.getFullYear()}`; labels.push(`Day ${i + 1}`); const earnings = runningPrincipal * (dailyRate / 100); const reinvestment = earnings * (reinvestRate / 100); const cashFlow = earnings - reinvestment; reinvestArray.push(Number(reinvestment.toFixed(2))); cashArray.push(Number(cashFlow.toFixed(2))); earningsDaily.push(Number(earnings.toFixed(2))); runningPrincipal += reinvestment; cumulativeCash += cashFlow; tableRows.push({ day: i + 1, date: dateStr, earnings: earnings, reinvestment: reinvestment, cashFlow: cashFlow, totalPrincipal: runningPrincipal, totalCash: cumulativeCash, reinvestRate: reinvestRate }); currentDate.setDate(currentDate.getDate() + 1); } // Update left panel summary $('sumInvestment').innerText = `Initial Investment: $${initialPrincipal.toFixed(2)}`; $('sumRate').innerText = `Interest Rate: ${dailyRate.toFixed(2)}%`; $('sumDays').innerText = `Days: ${termDays}`; $('sumInterest').innerText = `Interest Earned: $${((runningPrincipal - initialPrincipal) + cumulativeCash).toFixed(2)}`; $('sumNewBalance').innerText = `New Balance: $${runningPrincipal.toFixed(2)}`; $('sumCash').innerText = `Cash Withdrawals: $${cumulativeCash.toFixed(2)}`; // Draw compounding interest chart const ctx = $('interestChart').getContext('2d'); if (chart) { chart.destroy(); } const gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'rgba(0,123,255,0.8)'); gradient.addColorStop(1, 'rgba(0,123,255,0.2)'); chart = new Chart(ctx, { type: 'bar', data: { labels, datasets: [ { label: 'Re-investment', data: reinvestArray, backgroundColor: '#fd7e14', stack: 'combined', datalabels: { anchor: 'center', align: 'center', formatter: (value, context) => reinvestArray[context.dataIndex].toFixed(2), color: '#e0e0e0', font: { weight: 'bold', size: 14 } } }, { label: 'Cash Flow', data: cashArray, backgroundColor: '#28a745', stack: 'combined', datalabels: { labels: { cashFlowLabel: { anchor: 'center', align: 'center', formatter: (value, context) => cashArray[context.dataIndex].toFixed(2), color: '#e0e0e0', font: { weight: 'bold', size: 14 } }, totalEarningsLabel: { anchor: 'end', align: 'end', offset: 10, formatter: (value, context) => earningsDaily[context.dataIndex].toFixed(2), color: '#e0e0e0', font: { weight: 'bold', size: 14 } } } } } ] }, options: { responsive: true, animation: { duration: 0 }, scales: { x: { stacked: true, grid: { display: false }, ticks: { color: '#e0e0e0' } }, y: { stacked: true, grid: { color: '#555' }, ticks: { color: '#e0e0e0' } } }, plugins: { legend: { display: true, labels: { color: '#e0e0e0' } }, tooltip: { backgroundColor: '#333', titleColor: '#e0e0e0', bodyColor: '#e0e0e0', borderColor: '#007bff', borderWidth: 1, cornerRadius: 4, padding: 10 } } } }); // Populate the Analysis Table for Compounding Interest let tableHTML = ''; const fmt = num => '$' + num.toFixed(2); tableRows.forEach(row => { const principalCashCell = `${fmt(row.reinvestment)} / ${fmt(row.cashFlow)}`; tableHTML += ` ${row.day} ${row.date} ${fmt(row.earnings)} ${row.reinvestRate}% ${principalCashCell} ${fmt(row.totalPrincipal)} ${fmt(row.totalCash)} `; }); $('analysisTable').querySelector('tbody').innerHTML = tableHTML; updateTotalInvested(); }; // Percentage Calculators const calculatePercentage = () => { const total = parseFloat($('totalAmountPercent').value) || 0; const given = parseFloat($('givenAmount').value) || 0; if (total > 0) { const perc = (given / total) * 100; $('percentageResult').innerText = `${given.toFixed(2)} is ${perc.toFixed(2)}% of ${total.toFixed(2)}`; } else { $('percentageResult').innerText = "Enter a valid total amount"; } }; const calculatePercentage2 = () => { const total2 = parseFloat($('totalAmountPercent2').value) || 0; const perc2 = parseFloat($('percentInput2').value) || 0; if (total2 > 0) { const dollarValue = (perc2 / 100) * total2; $('percentageResult2').innerText = `${perc2}% of $${total2.toFixed(2)} = $${dollarValue.toFixed(2)}`; } else { $('percentageResult2').innerText = "Enter a valid total amount"; } }; // Revert setupCollapsibles to original behavior: const setupCollapsibles = () => { document.querySelectorAll('.collapsible-header').forEach(header => { header.addEventListener("click", () => { const body = header.nextElementSibling; if (body) { // Toggle display manually body.style.display = (body.style.display === "none" || body.style.display === "") ? "block" : "none"; } }); }); }; // Date/Time helpers const getToday = () => new Date().toISOString().split('T')[0]; const getBerlinTime = () => { const options = { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Europe/Berlin' }; return new Date().toLocaleTimeString('en-GB', options); }; const sortCoinTrackerRows = trackerDiv => { const table = trackerDiv.querySelector("table.coin-table"); const tbody = table.querySelector("tbody"); const rows = Array.from(tbody.rows); rows.sort((a, b) => { const dateA = new Date(a.cells[0].querySelector("input").value); const dateB = new Date(b.cells[0].querySelector("input").value); return dateA - dateB; }); rows.forEach(row => tbody.appendChild(row)); saveCoinTrackers(); updateCoinTrackerSummary(trackerDiv.id); }; // Create coin tracker element with a toggle for chart display. // Persist the hide/show state by using a "showInChart" property. const addCoinTracker = data => { let trackerId; if (data && data.id) { trackerId = data.id; const num = parseInt(trackerId.replace("coinTracker", "")); if (!isNaN(num) && num > coinTrackerCounter) { coinTrackerCounter = num; } } else { coinTrackerCounter++; trackerId = `coinTracker${coinTrackerCounter}`; } const trackerDiv = document.createElement("div"); trackerDiv.className = "coin-tracker"; trackerDiv.id = trackerId; // Set persistent show/hide state if provided, otherwise default to "true" if (data && data.showInChart !== undefined) { trackerDiv.dataset.showInChart = data.showInChart; } else { trackerDiv.dataset.showInChart = "true"; } // Enable manual sorting by default trackerDiv.dataset.manualSort = "true"; const headerDiv = document.createElement("div"); headerDiv.classList.add("coin-tracker-header"); const coinNameInput = document.createElement("input"); coinNameInput.type = "text"; coinNameInput.value = data && data.coinName ? data.coinName : "Unnamed Coin"; coinNameInput.classList.add("coin-name-input"); coinNameInput.placeholder = "Coin/Coin pair"; coinNameInput.addEventListener("click", e => e.stopPropagation()); coinNameInput.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); updateTransferDropdown(); }); headerDiv.appendChild(coinNameInput); const platformInput = document.createElement("input"); platformInput.type = "text"; platformInput.setAttribute("list", "platformListDatalist"); // <-- add this platformInput.placeholder = "Platform"; platformInput.value = data && data.platform ? data.platform : ""; platformInput.addEventListener("change", () => { // Could save the platform if desired saveCoinTrackers(); updatePlatformDatalist(platformInput.value); }); headerDiv.appendChild(platformInput); const lastValueSpan = document.createElement("span"); lastValueSpan.className = "last-value"; lastValueSpan.textContent = "Current Value: $0.00"; // Add label "Current Value: " lastValueSpan.style.marginLeft = "10px"; // adjust spacing as needed headerDiv.appendChild(lastValueSpan); // Add new span for total income const totalIncomeSpan = document.createElement("span"); totalIncomeSpan.className = "total-income"; totalIncomeSpan.textContent = "Total Income: $0.00"; totalIncomeSpan.style.marginLeft = "10px"; headerDiv.appendChild(totalIncomeSpan); const pnlDiv = document.createElement("div"); pnlDiv.className = "pnl-div"; pnlDiv.style.marginLeft = "10px"; const pnlCurrentValueSpan = document.createElement("span"); pnlCurrentValueSpan.className = "pnl-current-value"; pnlCurrentValueSpan.textContent = "PnL (Current Value): $0.00"; pnlDiv.appendChild(pnlCurrentValueSpan); const pnlTotalValueSpan = document.createElement("span"); pnlTotalValueSpan.className = "pnl-total-value"; pnlTotalValueSpan.textContent = "PnL (Total Value): $0.00"; pnlDiv.appendChild(pnlTotalValueSpan); headerDiv.appendChild(pnlDiv); const interestSpan = document.createElement("span"); interestSpan.className = "tracker-interest"; headerDiv.appendChild(interestSpan); const deleteTrackerBtn = document.createElement("button"); deleteTrackerBtn.textContent = "Delete Tracker"; deleteTrackerBtn.className = "deleteTracker"; deleteTrackerBtn.addEventListener("click", e => { e.stopPropagation(); if (confirm("Are you sure you want to delete this coin tracker?")) { trackerDiv.remove(); saveCoinTrackers(); updateTransferDropdown(); } }); headerDiv.appendChild(deleteTrackerBtn); // Toggle button for chart display with persistent state const toggleChartBtn = document.createElement("button"); toggleChartBtn.style.marginLeft = "10px"; toggleChartBtn.textContent = trackerDiv.dataset.showInChart === "true" ? "Hide from Chart" : "Show in Chart"; toggleChartBtn.addEventListener("click", () => { if (trackerDiv.dataset.showInChart === "false") { trackerDiv.dataset.showInChart = "true"; toggleChartBtn.textContent = "Hide from Chart"; } else { trackerDiv.dataset.showInChart = "false"; toggleChartBtn.textContent = "Show from Chart"; } updateInvestedChart(); saveCoinTrackers(); // persist the new state }); headerDiv.appendChild(toggleChartBtn); const toggleActiveBtn = document.createElement("button"); toggleActiveBtn.style.marginLeft = "10px"; toggleActiveBtn.textContent = trackerDiv.dataset.active === "false" ? "Activate" : "Deactivate"; toggleActiveBtn.addEventListener("click", () => { if (trackerDiv.dataset.active === "false") { trackerDiv.dataset.active = "true"; toggleActiveBtn.textContent = "Deactivate"; } else { trackerDiv.dataset.active = "false"; toggleActiveBtn.textContent = "Activate"; } updateTotalInvested(); updateIncomeChart(); saveCoinTrackers(); }); headerDiv.appendChild(toggleActiveBtn); headerDiv.addEventListener("click", e => { if (!["INPUT", "BUTTON", "SPAN"].includes(e.target.tagName)) { const contentDiv = trackerDiv.querySelector(".content"); contentDiv.style.display = (contentDiv.style.display === "none") ? "block" : "none"; } }); trackerDiv.appendChild(headerDiv); const contentDiv = document.createElement("div"); contentDiv.className = "content"; contentDiv.style.display = "none"; // Create initialCapitalDiv const initialCapitalDiv = document.createElement("div"); initialCapitalDiv.style.marginBottom = "10px"; const initialCapitalLabel = document.createElement("label"); initialCapitalLabel.textContent = 'Initial Capital: '; const initialCapitalInput = document.createElement("input"); initialCapitalInput.type = "number"; initialCapitalInput.step = "0.01"; initialCapitalInput.value = data && data.initialCapital ? data.initialCapital : "0.00"; initialCapitalDiv.appendChild(initialCapitalLabel); initialCapitalDiv.appendChild(initialCapitalInput); contentDiv.appendChild(initialCapitalDiv); // Create a container with positioning context for the table const tableWrapper = document.createElement("div"); tableWrapper.className = "table-wrapper"; tableWrapper.style.position = "relative"; const table = document.createElement("table"); table.className = "income-table coin-table"; const thead = document.createElement("thead"); thead.innerHTML = ` Date Time Current Value Income Compound Daily Yield (%) Actions `; table.appendChild(thead); const tbody = document.createElement("tbody"); table.appendChild(tbody); if (data && data.rows && data.rows.length > 0) { data.rows.forEach(row => { addDynamicEntry(tbody, row, trackerId); }); } // Add the table to the wrapper tableWrapper.appendChild(table); // No need for a separate yield projection box anymore // We're removing the summaryDiv creation and positioning code tableWrapper.style.marginBottom = "20px"; // Add the wrapper to the content div contentDiv.appendChild(tableWrapper); const addRowBtn = document.createElement("button"); addRowBtn.textContent = "Add Row"; addRowBtn.className = "add-row-btn"; // Add a class for styling addRowBtn.style.display = "block"; // Ensure it's a block element addRowBtn.style.marginTop = "5px"; // Position right at the edge of the box addRowBtn.style.marginBottom = "0"; // Remove any bottom margin addRowBtn.addEventListener("click", () => { addDynamicEntry(tbody, { date: getToday(), time: getBerlinTime(), income: "", dailyYield: "", currentValue: 0 }, trackerId); saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); contentDiv.appendChild(addRowBtn); trackerDiv.appendChild(contentDiv); $('coinTrackerContainer').appendChild(trackerDiv); // Initialize Sortable on this table by default if (window.Sortable) { const tbody = table.querySelector("tbody"); Sortable.create(tbody, { animation: 150, onEnd: () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); } }); } updateCoinTrackerSummary(trackerId); updateTransferDropdown(); }; // Modified addDynamicEntry: assign a creation timestamp so we can track insertion order const addDynamicEntry = (tbody, rowData, trackerId) => { const tr = document.createElement("tr"); // Use existing created value if provided, otherwise use current timestamp tr.dataset.created = rowData.created || Date.now(); const tdDate = document.createElement("td"); const inputDate = document.createElement("input"); inputDate.type = "date"; inputDate.value = rowData.date || getToday(); inputDate.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); const trackerDiv = $(trackerId); if (!trackerDiv.dataset.manualSort || trackerDiv.dataset.manualSort === "false") { sortCoinTrackerRows(trackerDiv); } }); tdDate.appendChild(inputDate); tr.appendChild(tdDate); const tdTime = document.createElement("td"); const inputTime = document.createElement("input"); inputTime.type = "time"; inputTime.value = rowData.time || getBerlinTime(); inputTime.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); tdTime.appendChild(inputTime); tr.appendChild(tdTime); const tdCurrent = document.createElement("td"); tdCurrent.innerHTML = "$"; const inputCurrent = document.createElement("input"); inputCurrent.type = "number"; inputCurrent.step = "0.01"; inputCurrent.value = rowData.currentValue ? Number(rowData.currentValue).toFixed(2) : "0.00"; inputCurrent.addEventListener("input", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); inputCurrent.addEventListener("keyup", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); inputCurrent.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); tdCurrent.appendChild(inputCurrent); tr.appendChild(tdCurrent); const tdIncome = document.createElement("td"); const inputIncome = document.createElement("input"); inputIncome.type = "number"; inputIncome.step = "0.01"; inputIncome.value = rowData.income || ""; inputIncome.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); tdIncome.appendChild(inputIncome); tr.appendChild(tdIncome); const tdCompound = document.createElement("td"); const inputCompound = document.createElement("input"); inputCompound.type = "number"; inputCompound.step = "0.01"; inputCompound.value = rowData.compound || ""; inputCompound.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); tdCompound.appendChild(inputCompound); tr.appendChild(tdCompound); const tdYield = document.createElement("td"); const inputYield = document.createElement("input"); inputYield.type = "number"; inputYield.step = "0.01"; inputYield.value = rowData.dailyYield || ""; inputYield.addEventListener("change", () => { saveCoinTrackers(); updateCoinTrackerSummary(trackerId); updateRowYieldProjections(tr); }); tdYield.appendChild(inputYield); // Add a div to hold the yield projections within the cell const projectionDiv = document.createElement("div"); projectionDiv.className = "inline-yield-projection"; tdYield.appendChild(projectionDiv); tr.appendChild(tdYield); const tdActions = document.createElement("td"); const delBtn = document.createElement("button"); delBtn.textContent = "Delete"; delBtn.className = "deleteRowBtn"; delBtn.addEventListener("click", () => { tr.remove(); saveCoinTrackers(); updateCoinTrackerSummary(trackerId); }); tdActions.appendChild(delBtn); tr.appendChild(tdActions); tbody.appendChild(tr); updateRowYieldProjections(tr); }; // New function to update the projections for a single row const updateRowYieldProjections = (row) => { const currentVal = parseFloat(row.cells[2].querySelector("input").value) || 0; const yieldVal = parseFloat(row.cells[5].querySelector("input").value) || 0; const projectionDiv = row.cells[5].querySelector(".inline-yield-projection"); if (currentVal > 0 && yieldVal > 0) { const dailyIncome = currentVal * (yieldVal / 100); const projection24h = dailyIncome; const projection7d = dailyIncome * 7; const projection30d = dailyIncome * 30; projectionDiv.innerHTML = `24h: $${projection24h.toFixed(2)} | 7d: $${projection7d.toFixed(2)} | 30d: $${projection30d.toFixed(2)}`; } else { projectionDiv.innerHTML = ""; } }; // Update Invested Value Chart const updateInvestedChart = () => { let persistedHistory = JSON.parse(localStorage.getItem("persistedInvestedHistory") || "{}"); const container = $('coinTrackerContainer'); const trackerDivs = container.getElementsByClassName("coin-tracker"); let currentTotals = {}; Array.from(trackerDivs).forEach(div => { if (div.dataset.showInChart === "false") return; const table = div.querySelector("table.coin-table"); if (table) { const tbody = table.querySelector("tbody"); let trackerDaily = {}; // { date -> { time, current } } Array.from(tbody.rows).forEach(tr => { const dateInput = tr.cells[0].querySelector("input"); const timeInput = tr.cells[1].querySelector("input"); if (dateInput && dateInput.value) { const date = dateInput.value; const time = (timeInput && timeInput.value) ? timeInput.value : "00:00"; const currentInput = tr.cells[2].querySelector("input"); const currentVal = parseFloat(currentInput.value.replace("$", "")) || 0; if (!trackerDaily[date] || time > trackerDaily[date].time) { trackerDaily[date] = { time, current: currentVal }; } } }); for (const date in trackerDaily) { currentTotals[date] = (currentTotals[date] || 0) + trackerDaily[date].current; } } }); if (Object.keys(currentTotals).length === 0) { persistedHistory = {}; } else { for (const date in currentTotals) { persistedHistory[date] = currentTotals[date]; } } localStorage.setItem("persistedInvestedHistory", JSON.stringify(persistedHistory)); const dates = Object.keys(persistedHistory).sort(); let labels = dates.map(date => new Date(date).toLocaleDateString()); let data = dates.map(date => persistedHistory[date]); if (labels.length > 20) { labels = labels.slice(-20); data = data.slice(-20); } investedHistory.labels = labels; investedHistory.data = data; if (investedChart) { investedChart.data.labels = labels; investedChart.data.datasets[0].data = data; investedChart.update(); } else { const ctx = $('investedChart').getContext('2d'); investedChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Total Invested ($)', data: data, fill: true, backgroundColor: function(context) { const ctx = context.chart.ctx; const gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, 'rgba(0,123,255,0.4)'); gradient.addColorStop(1, 'rgba(0,123,255,0.1)'); return gradient; }, borderColor: '#007bff', borderWidth: 2, tension: 0.3 }] }, options: { responsive: true, animation: { duration: 500, easing: 'easeOutQuart' }, scales: { y: { beginAtZero: true, ticks: { callback: value => '$' + value } } } } }); } }; // Update Income Chart (sums total income for each coin tracker) const updateIncomeChart = () => { console.log("updateIncomeChart called"); const container = $('coinTrackerContainer'); const trackerDivs = container.getElementsByClassName("coin-tracker"); const labels = []; const incomeData = []; const compoundData = []; Array.from(trackerDivs).forEach(div => { if (div.dataset.active === "false") return; const coinNameInput = div.querySelector(".coin-tracker-header input.coin-name-input"); const coinName = coinNameInput ? coinNameInput.value : "Unnamed Coin"; let totalIncome = 0; let totalCompound = 0; const table = div.querySelector("table.coin-table"); if (table) { const tbody = table.querySelector("tbody"); Array.from(tbody.rows).forEach(tr => { if (tr.cells.length > 3) { const incomeInput = tr.cells[3].querySelector("input"); const incomeVal = parseFloat(incomeInput.value || "0"); totalIncome += isNaN(incomeVal) ? 0 : incomeVal; const compoundInput = tr.cells[4].querySelector("input"); const compoundVal = parseFloat(compoundInput.value || "0"); totalCompound += isNaN(compoundVal) ? 0 : compoundVal; } }); } labels.push(coinName); incomeData.push(totalIncome); compoundData.push(totalCompound); }); console.log("Income Chart labels:", labels); console.log("Income Chart data:", incomeData); const ctx = $('incomeChart').getContext('2d'); if (incomeChart) { incomeChart.data.labels = labels; incomeChart.data.datasets[0].data = incomeData; incomeChart.data.datasets[1].data = compoundData; incomeChart.update(); } else { incomeChart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [ { label: 'Income', data: incomeData, backgroundColor: 'rgba(255, 99, 132, 0.5)', stack: 'combined', borderColor: 'rgba(255, 99, 132, 1)', borderWidth: 1 }, { label: 'Compound', data: compoundData, backgroundColor: 'rgba(54, 162, 235, 0.5)', stack: 'combined', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1 } ] }, options: { responsive: true, scales: { x: { stacked: true }, y: { stacked: true, beginAtZero: true, ticks: { callback: value => '$' + value } } } } }); } }; // Update Total Invested display (using last row's current value from each tracker) const updateTotalInvested = () => { const container = $('coinTrackerContainer'); const trackerDivs = container.getElementsByClassName("coin-tracker"); let totalInvested = 0; Array.from(trackerDivs).forEach(div => { if (div.dataset.active === "false") return; const table = div.querySelector("table.coin-table"); if (table) { const tbody = table.querySelector("tbody"); if (tbody.rows.length > 0) { const lastRow = tbody.rows[tbody.rows.length - 1]; const inputCurrent = lastRow.cells[2].querySelector("input"); const value = parseFloat(inputCurrent.value.replace("$", "")) || 0; totalInvested += value; } } }); let displaySpan = $('totalInvestedDisplay'); if (!displaySpan) { displaySpan = document.createElement("span"); displaySpan.id = "totalInvestedDisplay"; const addBtn = $('addCoinTrackerBtn'); addBtn.insertAdjacentElement('afterend', displaySpan); } // Updated label text: displaySpan.innerText = `Total Investment Value: $${totalInvested.toFixed(2)}`; updateInvestedChart(); updateIncomeChart(); }; // Save coin trackers by reading the DOM and updating the backend. const saveCoinTrackers = () => { const trackers = []; const container = $('coinTrackerContainer'); const trackerDivs = container.getElementsByClassName("coin-tracker"); Array.from(trackerDivs).forEach(div => { const trackerId = div.id; const headerDiv = div.querySelector("div"); const coinNameInput = headerDiv.querySelector("input.coin-name-input"); const coinName = coinNameInput.value; // Get the platform value const platformInput = headerDiv.querySelector("input[list='platformListDatalist']"); const platform = platformInput ? platformInput.value : ""; const showInChart = div.dataset.showInChart; // persist the state here // Get initialCapital value const initialCapitalInput = div.querySelector(".content input[type='number']"); const initialCapital = initialCapitalInput ? initialCapitalInput.value : "0.00"; const table = div.querySelector("table.coin-table"); const tbody = table.querySelector("tbody"); const rows = []; Array.from(tbody.rows).forEach(tr => { const date = tr.cells[0].querySelector("input").value; const time = tr.cells[1].querySelector("input").value; const currentValue = parseFloat(tr.cells[2].querySelector("input").value.replace("$", "")) || 0; const income = parseFloat(tr.cells[3].querySelector("input").value) || 0; const compound = parseFloat(tr.cells[4].querySelector("input").value) || 0; const dailyYield = parseFloat(tr.cells[5].querySelector("input").value) || 0; const created = tr.dataset.created; rows.push({ date, time, currentValue, income, compound, dailyYield, created }); }); const hasData = rows.some(row => row.currentValue !== 0 || row.income !== 0 || row.dailyYield !== 0); if (hasData) { trackers.push({ id: trackerId, coinName, platform, initialCapital, rows, showInChart }); } }); updateCoinTrackersToBackend(trackers); }; // Load coin trackers exclusively from the backend. const loadCoinTrackersFromBackend = async () => { try { const response = await fetch("/api/coinTrackers"); if (!response.ok) { console.error("Error loading data from backend:", response.statusText); return; } const result = await response.json(); const trackers = result.data || []; const container = $("coinTrackerContainer"); container.innerHTML = ""; if (trackers.length === 0) { console.log("No coin trackers found."); } else { trackers.forEach(tracker => addCoinTracker(tracker)); } } catch (error) { console.error("Fetch error while loading from backend:", error); } }; // Update coin tracker summary. // Now, to determine the “last entered” daily yield, we use the row with the highest creation timestamp. const updateCoinTrackerSummary = trackerId => { const trackerDiv = $(trackerId); if (!trackerDiv) return; const table = trackerDiv.querySelector("table.coin-table"); const tbody = table.querySelector("tbody"); // Use the last row (in reverse order) that has a nonzero current value. let trackerValue = 0; let lastDailyYield = 0; const rowsReversed = Array.from(tbody.rows).reverse(); for (const row of rowsReversed) { const currentVal = parseFloat(row.cells[2].querySelector("input").value) || 0; if (currentVal > 0) { lastDailyYield = parseFloat(row.cells[5].querySelector("input").value) || 0; trackerValue = currentVal; break; } } // Update header "Current Value: $" display. const lastValueSpan = trackerDiv.querySelector('.last-value'); if (lastValueSpan) { lastValueSpan.textContent = "Current Value: $" + trackerValue.toFixed(2); } // Calculate and update total income let totalIncome = 0; let totalCompound = 0; Array.from(tbody.rows).forEach(row => { const incomeVal = parseFloat(row.cells[3].querySelector("input").value) || 0; totalIncome += incomeVal; const compoundVal = parseFloat(row.cells[4].querySelector("input").value) || 0; totalCompound += compoundVal; }); // Update total income display const totalIncomeSpan = trackerDiv.querySelector('.total-income'); if (totalIncomeSpan) { totalIncomeSpan.textContent = `Total Income: $${totalIncome.toFixed(2)}`; } // Update all rows' yield projections Array.from(tbody.rows).forEach(row => { updateRowYieldProjections(row); }); // Calculate and update PnL const initialCapitalInput = trackerDiv.querySelector(".content input[type='number']"); const initialCapital = parseFloat(initialCapitalInput.value) || 0; const pnlCurrentValue = trackerValue - initialCapital; const pnlTotalValue = (trackerValue + totalIncome + totalCompound) - initialCapital; const pnlCurrentValueSpan = trackerDiv.querySelector('.pnl-current-value'); if (pnlCurrentValueSpan) { pnlCurrentValueSpan.textContent = `PnL (Current Value): $${pnlCurrentValue.toFixed(2)}`; pnlCurrentValueSpan.style.color = pnlCurrentValue >= 0 ? 'green' : 'red'; } const pnlTotalValueSpan = trackerDiv.querySelector('.pnl-total-value'); if (pnlTotalValueSpan) { pnlTotalValueSpan.textContent = `PnL (Total Value): $${pnlTotalValue.toFixed(2)}`; pnlTotalValueSpan.style.color = pnlTotalValue >= 0 ? 'green' : 'red'; } updateTotalInvested(); }; const updateTransferDropdown = () => { const select = $('transferSelect'); // If element is not found, log a warning and exit. if (!select) { console.warn("transferSelect element is not found in the DOM."); return; } select.innerHTML = ''; const container = $('coinTrackerContainer'); const trackerDivs = container.getElementsByClassName("coin-tracker"); Array.from(trackerDivs).forEach(div => { const id = div.id; // Use the specific header element (using a more specific selector) const headerDiv = div.querySelector("div.coin-tracker-header"); if (!headerDiv) return; const coinNameInput = headerDiv.querySelector("input.coin-name-input"); if (!coinNameInput) return; const option = document.createElement("option"); option.value = id; option.text = coinNameInput.value; select.appendChild(option); }); const createOption = document.createElement("option"); createOption.value = "create"; createOption.text = "Create New Coin Tracker"; select.appendChild(createOption); }; const transferSettings = () => { const select = $('transferSelect'); const selection = select.value; const currentValue = $('initialPrincipal').value; const rateValue = $('dailyRate').value; if (!selection) { alert("Please select a coin tracker or create a new one."); return; } let trackerDiv; if (selection === "create") { addCoinTracker(); const container = $('coinTrackerContainer'); trackerDiv = container.lastElementChild; } else { trackerDiv = $(selection); } if (trackerDiv) { const table = trackerDiv.querySelector("table.coin-table"); const tbody = table.querySelector("tbody"); Array.from(tbody.rows).forEach(tr => { const currentCellInput = tr.cells[2].querySelector("input"); const currentVal = parseFloat(currentCellInput.value.replace("$", "")) || 0; if (currentVal === 0) { currentCellInput.value = parseFloat(currentValue).toFixed(2); } }); const headerDiv = trackerDiv.querySelector("div"); const interestSpan = headerDiv.querySelector(".tracker-interest"); interestSpan.innerText = `(Interest Rate: ${rateValue}%)`; const earnedValue = (parseFloat(currentValue) * parseFloat(rateValue) / 100).toFixed(2); addDynamicEntry(tbody, { date: getToday(), time: getBerlinTime(), income: earnedValue, dailyYield: rateValue, currentValue: parseFloat(currentValue) }, trackerDiv.id); alert("Settings transferred!"); saveCoinTrackers(); updateCoinTrackerSummary(trackerDiv.id); updateTransferDropdown(); } }; // New function: reset (subtract) a specified dollar amount from a specific day in the graph. const resetGraphValue = () => { const resetDate = $('resetDate').value; const resetAmount = parseFloat($('resetAmount').value) || 0; console.log('Reset clicked:', resetDate, resetAmount); if (!resetDate || resetAmount <= 0) { alert("Please enter a valid date and amount."); return; } let persistedHistory = JSON.parse(localStorage.getItem("persistedInvestedHistory") || "{}"); console.log('Persisted history before update:', persistedHistory); if (persistedHistory.hasOwnProperty(resetDate)) { persistedHistory[resetDate] = Math.max(0, persistedHistory[resetDate] - resetAmount); localStorage.setItem("persistedInvestedHistory", JSON.stringify(persistedHistory)); updateInvestedChart(); alert(`Updated value for ${resetDate}.`); } else { alert("No value exists for that date."); } }; // New function: delete all datapoints for a specific date from the graph. const deleteGraphDataPoint = () => { const resetDate = $('resetDate').value; if (!resetDate) { alert("Please select a valid date."); return; } let persistedHistory = JSON.parse(localStorage.getItem("persistedInvestedHistory") || "{}"); if (persistedHistory.hasOwnProperty(resetDate)) { delete persistedHistory[resetDate]; localStorage.setItem("persistedInvestedHistory", JSON.stringify(persistedHistory)); updateInvestedChart(); alert(`Deleted data for ${resetDate}.`); } else { alert("No data exists for that date."); } }; function updatePlatformDatalist(platform) { const list = document.getElementById("platformListDatalist"); if (!list) return; const existingOption = Array.from(list.options).find(opt => opt.value === platform); if (!existingOption && platform.trim().length > 0) { const newOption = document.createElement("option"); newOption.value = platform; list.appendChild(newOption); } } document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".number-input button").forEach(button => { const handleInteraction = e => { e.preventDefault(); const targetId = button.getAttribute("data-target"); const delta = parseFloat(button.getAttribute("data-delta")); adjustValue(targetId, delta); }; if (window.PointerEvent) { button.addEventListener("pointerup", handleInteraction, false); } else { button.addEventListener("click", handleInteraction, false); button.addEventListener("touchend", handleInteraction, false); } }); ['initialPrincipal', 'dailyRate', 'termDays', 'reinvestRate'].forEach(id => { $(id).addEventListener("input", calculate); }); $('totalAmountPercent').addEventListener("input", calculatePercentage); $('givenAmount').addEventListener("input", calculatePercentage); calculatePercentage(); $('totalAmountPercent2').addEventListener("input", calculatePercentage2); $('percentInput2').addEventListener("input", calculatePercentage2); calculatePercentage2(); setupCollapsibles(); $('transferSettingsBtn').addEventListener("click", transferSettings); $('addCoinTrackerBtn').addEventListener("click", () => addCoinTracker()); calculate(); loadCoinTrackersFromBackend(); updateTransferDropdown(); // Chart toggle buttons const showInvestedBtn = $('showInvestedChartBtn'); const showIncomeBtn = $('showIncomeChartBtn'); showInvestedBtn.addEventListener("click", () => { $('investedChart').style.display = "block"; $('incomeChart').style.display = "none"; if (investedChart) investedChart.resize(); }); showIncomeBtn.addEventListener("click", () => { $('investedChart').style.display = "none"; $('incomeChart').style.display = "block"; updateIncomeChart(); // This calls our updateIncomeChart function. }); // Event listeners for the new reset and delete graph value buttons $('resetGraphValueBtn').addEventListener("click", resetGraphValue); $('deleteGraphDataPointBtn').addEventListener("click", deleteGraphDataPoint); }); })();