Files
kidsai/html/rechner_backup/backup/script.js
2025-06-24 15:43:32 +02:00

1097 lines
41 KiB
JavaScript

"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 += `<tr>
<td>${row.day}</td>
<td>${row.date}</td>
<td>${fmt(row.earnings)}</td>
<td>${row.reinvestRate}%</td>
<td>${principalCashCell}</td>
<td>${fmt(row.totalPrincipal)}</td>
<td>${fmt(row.totalCash)}</td>
</tr>`;
});
$('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 = `<tr>
<th>Date</th>
<th>Time</th>
<th>Current Value</th>
<th>Income</th>
<th>Compound</th>
<th>Daily Yield (%)</th>
<th>Actions</th>
</tr>`;
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 = `<small>24h: $${projection24h.toFixed(2)} | 7d: $${projection7d.toFixed(2)} | 30d: $${projection30d.toFixed(2)}</small>`;
} 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 = '<option value="">-- Select --</option>';
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);
});
})();