';
$('backupCalendarContainer').style.display = 'block';
});
};
// Function to restore from a backup
const restoreFromBackup = (filename) => {
if (!filename) {
alert('No backup selected');
return;
}
if (!confirm(`Are you sure you want to restore from backup "${filename}"? This will replace all current data.`)) {
return;
}
fetch(`/api/backup/restore/${filename}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
alert('Backup restored successfully! The page will reload.');
window.location.reload();
})
.catch(error => {
console.error('Error restoring backup:', error);
alert('Failed to restore backup. See console for details.');
});
};
// Function to log conversions to history
const logConversion = (conversionData) => {
// Get existing history
let conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
// Add new conversion with timestamp
conversionData.timestamp = Date.now();
conversionHistory.push(conversionData);
// Save back to localStorage
localStorage.setItem("conversionHistory", JSON.stringify(conversionHistory));
// Update the conversion history table - with error handling
try {
updateConversionHistoryTable();
} catch (error) {
console.error("Error updating conversion history table:", error);
// Don't show alert to user as this is not critical
}
};
// Function to update the conversion history table
const updateConversionHistoryTable = () => {
const tableBody = document.querySelector("#conversionHistoryTable tbody");
if (!tableBody) {
console.warn("Conversion history table tbody not found in the DOM");
return;
}
// Get conversion history
const conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
// Sort by date, newest first
conversionHistory.sort((a, b) => new Date(b.date) - new Date(a.date));
// Clear existing rows
tableBody.innerHTML = "";
// Add rows for each conversion
conversionHistory.forEach((conversion, index) => {
// Create row elements only if conversion data exists
if (!conversion) return;
const row = document.createElement("tr");
row.dataset.index = index; // Store the index for easy deletion
const dateCell = document.createElement("td");
dateCell.textContent = `${conversion.date} ${conversion.time}`;
row.appendChild(dateCell);
const fromCell = document.createElement("td");
fromCell.textContent = conversion.fromName;
row.appendChild(fromCell);
const amountCell = document.createElement("td");
// For backward compatibility with existing data
const dollarAmount = conversion.dollarAmount || conversion.sentAmount || 0;
// If this is an income transfer, show that in the cell
if (conversion.transferType === 'income') {
amountCell.textContent = `$${dollarAmount.toFixed(2)} (Income)`;
amountCell.style.color = '#4CAF50'; // Green for income transfers
} else {
amountCell.textContent = `$${dollarAmount.toFixed(2)}`;
}
row.appendChild(amountCell);
const toCell = document.createElement("td");
toCell.textContent = conversion.toName;
row.appendChild(toCell);
const receivedCell = document.createElement("td");
// Same dollar amount, preserves consistency with existing data
receivedCell.textContent = `$${dollarAmount.toFixed(2)}`;
row.appendChild(receivedCell);
// Add delete button
const actionsCell = document.createElement("td");
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.style.padding = "3px 8px";
deleteBtn.style.background = "#ff4444";
deleteBtn.style.color = "white";
deleteBtn.style.border = "none";
deleteBtn.style.borderRadius = "3px";
deleteBtn.style.cursor = "pointer";
deleteBtn.addEventListener("click", function() {
if (confirm("CONFIRM DELETION: This will:\n\n1. REMOVE the destination entry (the target coin purchase)\n2. KEEP the source entry but clear its transfer note\n\nSource rows will NEVER be deleted, only their transfer notes will be cleared.")) {
// Always re-read localStorage to avoid index bugs
let conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
// CRITICAL FIX: The table displays sorted history, but we need to find the record by its actual properties, not index
// because the displayed index might not match the storage array index due to sorting
const displayedConversions = [...conversionHistory].sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
const conversionRecord = displayedConversions[index];
// Find the actual index in the original unsorted array
const actualIndex = conversionHistory.findIndex(record =>
record.transactionId === conversionRecord.transactionId ||
(record.date === conversionRecord.date &&
record.time === conversionRecord.time &&
record.fromName === conversionRecord.fromName &&
record.toName === conversionRecord.toName &&
record.dollarAmount === conversionRecord.dollarAmount)
);
console.log("=== SCRIPT.JS DELETION DEBUG START ===");
console.log("Displayed index (from sorted array):", index);
console.log("Actual index (in storage array):", actualIndex);
console.log("Conversion record being deleted:", conversionRecord);
console.log("Total conversion history length:", conversionHistory.length);
console.log("=== SCRIPT.JS DELETION DEBUG END ===");
// If the conversion has a transaction ID, find and handle related tracker entries with protection
if (conversionRecord && conversionRecord.transactionId) {
const transactionId = conversionRecord.transactionId;
const fromName = conversionRecord.fromName;
const toName = conversionRecord.toName;
console.log(`Processing deletion for transaction ${transactionId}: ${fromName} β ${toName}`);
// Get all coin trackers
const trackers = document.querySelectorAll('.coin-tracker');
// STEP 1: First find and process ONLY the destination entries across all trackers
trackers.forEach(tracker => {
const tbody = tracker.querySelector('table.coin-table tbody');
if (!tbody) return;
// Find destination entries first - we ONLY delete these
const destRows = tbody.querySelectorAll(`tr[data-transaction-id="${transactionId}"][data-transaction-type="conversion-in"], tr[data-transaction-id="${transactionId}"][data-transaction-type="income-transfer-in"]`);
console.log(`Found ${destRows.length} destination rows in tracker ${tracker.id}`);
// Delete ONLY destination rows with extreme caution
destRows.forEach(row => {
// TRIPLE-CHECK that this is NOT a source row under any circumstances
const isSafeToDelete =
// 1. Check it does not have the source marker
row.dataset.sourceRow !== 'true' &&
// 2. Check it is explicitly a destination type
(row.dataset.transactionType === 'conversion-in' || row.dataset.transactionType === 'income-transfer-in');
// Make sure this is a tracker for the destination asset before deleting
const trackerNameInput = tracker.querySelector(".coin-tracker-header .coin-name-input");
const trackerName = trackerNameInput ? trackerNameInput.value : "";
if (!isSafeToDelete) {
console.log(`SAFETY BLOCK: Prevented deletion of row that might be a source row`);
} else if (trackerName === toName) {
console.log(`Removing destination row in ${toName}`);
row.remove(); // ONLY destination rows are deleted
} else {
console.log(`NOT removing destination row in ${trackerName || tracker.id} because it's not the target asset ${toName}`);
}
});
});
// STEP 2: Now find and modify ONLY source entries across all trackers
console.log("STEP 2: Processing source entries - These should NEVER be deleted, only notes cleared");
trackers.forEach(tracker => {
const tbody = tracker.querySelector('table.coin-table tbody');
if (!tbody) return;
// Get all rows that might be source entries
const allRows = tbody.querySelectorAll('tr');
allRows.forEach(row => {
// Method 1: Check by explicit source row marker (highest priority)
const isSourceByMarker = row.dataset && row.dataset.sourceRow === 'true';
// Method 2: Check by transaction ID and transaction type
const isSourceByID = row.dataset &&
row.dataset.transactionId === transactionId &&
row.dataset.transactionType !== 'conversion-in' &&
row.dataset.transactionType !== 'income-transfer-in';
// Method 3: Check by specific source transaction types
const isSourceByType = row.dataset &&
row.dataset.transactionType &&
(row.dataset.transactionType === 'conversion-out' ||
row.dataset.transactionType === 'income-source' ||
row.dataset.transactionType === 'income-transfer-note' ||
row.dataset.transactionType === 'source-recovered');
// Method 4: Check by notes content
let isSourceByNotes = false;
const notesCell = row.cells && row.cells[7] ? row.cells[7] : null;
const notesInput = notesCell ? notesCell.querySelector("input") : null;
const notes = notesInput ? notesInput.value : "";
if (notes.includes(`TRANSFERRED: $`) && notes.includes(`to buy ${toName}`)) {
isSourceByNotes = true;
}
if (notes.includes(`Moved $`) && notes.includes(`to ${toName}`)) {
isSourceByNotes = true;
}
// Combine all source detection methods
const isSourceRowFound = isSourceByMarker || isSourceByID || isSourceByType || isSourceByNotes;
// CRITICAL PROTECTION: Immediately mark any potential source row to prevent deletion
if (isSourceRowFound) {
// Add the source marker BEFORE doing anything else to protect this row
row.dataset.sourceRow = "true";
console.log(`PROTECTED SOURCE ROW FOUND in ${tracker.id}: ${
isSourceByMarker ? "by explicit marker" :
isSourceByID ? "by transaction ID" :
isSourceByType ? "by transaction type" :
"by notes content"
}`);
// NEVER delete source rows, just clear the transfer note
if (notesInput) {
// Only modify notes if they contain transfer information relating to this transaction
const containsRelevantTransfer =
(notes.includes(`TRANSFERRED: $`) && notes.includes(`to buy ${toName}`)) ||
(notes.includes(`Moved $`) && notes.includes(`to ${toName}`));
if (containsRelevantTransfer) {
const transferTextPattern = /TRANSFERRED: \$[\d.]+( to buy [^;]+)?/;
const movedTextPattern = /Moved \$[\d.]+( to [^;]+)?/;
let newNotes = notes;
newNotes = newNotes.replace(transferTextPattern, "");
newNotes = newNotes.replace(movedTextPattern, "");
// Clean up any trailing or leading semicolons and double semicolons
newNotes = newNotes.replace(/^;\s*/, "").replace(/;\s*$/, "").replace(/;\s*;/g, ";");
notesInput.value = newNotes;
console.log(`Source row notes after: "${newNotes}"`);
console.log(`KEPT source row - only cleared transfer note`);
} else {
console.log(`Source row notes don't contain relevant transfer info for ${toName}, keeping unchanged`);
}
// Tag this row if it wasn't already tagged
if (!isSourceByID) {
row.dataset.transactionId = transactionId;
row.dataset.transactionType = "source-recovered";
console.log("Tagged source row for future operations");
}
}
}
});
});
// Update all trackers after deletion
document.querySelectorAll('.coin-tracker').forEach(tracker => {
const trackerId = tracker.id;
updateCoinTrackerSummary(trackerId);
});
// Save all trackers
saveCoinTrackers();
}
// Remove from the array using the actual index, not the display index
if (actualIndex !== -1) {
conversionHistory.splice(actualIndex, 1);
console.log(`Removed conversion record at actual index ${actualIndex}`);
} else {
console.error("Could not find actual index for conversion record, deletion skipped");
}
// Save back to local storage
localStorage.setItem("conversionHistory", JSON.stringify(conversionHistory));
// Remove the row from the table
row.remove();
// Update conversion stats
updateConversionStats();
// Update total investment display
if (typeof updateTotalInvested === 'function') {
updateTotalInvested();
}
}
});
actionsCell.appendChild(deleteBtn);
row.appendChild(actionsCell);
// Append the row to the table body
tableBody.appendChild(row);
});
};
// Function to update conversion statistics
const updateConversionStats = () => {
try {
const statsTable = document.querySelector("#conversionStatsTable tbody");
if (!statsTable) {
console.warn("Conversion stats table tbody not found in the DOM");
return;
}
// Get conversion history
const conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
// Track flows by asset
const assetFlows = {};
// Process all conversions
conversionHistory.forEach(conversion => {
// Skip invalid conversions
if (!conversion || !conversion.fromName || !conversion.toName) return;
// Initialize asset records if they don't exist
if (!assetFlows[conversion.fromName]) {
assetFlows[conversion.fromName] = { in: 0, out: 0 };
}
if (!assetFlows[conversion.toName]) {
assetFlows[conversion.toName] = { in: 0, out: 0 };
}
// Determine the dollar amount (support both new and old data format)
const dollarAmount = conversion.dollarAmount || conversion.sentAmount || 0;
// Record the flow (same dollar amount for both directions)
assetFlows[conversion.fromName].out += dollarAmount;
assetFlows[conversion.toName].in += dollarAmount;
});
// Clear existing rows
statsTable.innerHTML = "";
// Create rows for each asset
Object.keys(assetFlows).sort().forEach(asset => {
const flow = assetFlows[asset];
const netFlow = flow.in - flow.out;
const row = document.createElement("tr");
const assetCell = document.createElement("td");
assetCell.textContent = asset;
row.appendChild(assetCell);
const inCell = document.createElement("td");
inCell.textContent = `$${flow.in.toFixed(2)}`;
row.appendChild(inCell);
const outCell = document.createElement("td");
outCell.textContent = `$${flow.out.toFixed(2)}`;
row.appendChild(outCell);
const netCell = document.createElement("td");
netCell.textContent = `$${netFlow.toFixed(2)}`;
netCell.style.color = netFlow >= 0 ? 'green' : 'red';
row.appendChild(netCell);
statsTable.appendChild(row);
});
} catch (error) {
console.error("Error updating conversion stats:", error);
// Don't show alert to user as this is not critical
}
};
// Add these functions after your existing functions but before the DOMContentLoaded event
// Record asset transfer between trackers - dollar value based
const recordAssetTransfer = (fromId, toId, dollarAmount, transferType = 'principal', selectedIncomeData = null) => {
console.log("Recording asset transfer:", {fromId, toId, dollarAmount, transferType, selectedIncomeData});
console.log("fromId type:", typeof fromId, "toId type:", typeof toId);
// Make sure we can find the tracker elements
console.log("Looking for element with ID:", fromId);
const fromElement = document.getElementById(fromId);
console.log("Direct document.getElementById result:", fromElement);
const fromTracker = $(fromId);
const toTracker = $(toId);
console.log("Tracker elements:", {fromTracker, toTracker});
if (!fromTracker || !toTracker || dollarAmount <= 0) {
alert("Please select valid source and destination trackers and enter a positive dollar amount");
console.error("Invalid transfer parameters:", {fromTracker, toTracker, dollarAmount});
return;
}
// Get coin names for logging
const fromName = fromTracker.querySelector(".coin-tracker-header input.coin-name-input").value;
const toName = toTracker.querySelector(".coin-tracker-header input.coin-name-input").value;
const sourceTable = fromTracker.querySelector("table.coin-table");
const sourceTbody = sourceTable.querySelector("tbody");
const today = getToday();
const currentTime = getBerlinTime();
// Record the transaction ID for linked entries (used for deletion)
const transactionId = `transfer_${Date.now()}`;
if (transferType === 'income') {
// For income transfer - ALWAYS try to add note to a source row instead of creating a negative entry
if (selectedIncomeData) {
// User has selected a specific income row to use
const incomeRowIndex = selectedIncomeData.rowIndex;
const sourceRows = Array.from(sourceTbody.rows);
if (incomeRowIndex >= 0 && incomeRowIndex < sourceRows.length) {
const incomeRow = sourceRows[incomeRowIndex];
// Update the notes field in the income row
const notesCell = incomeRow.cells[7]; // Notes cell
const notesInput = notesCell ? notesCell.querySelector("input") : null;
if (notesInput) {
// Make very visible notes about the transfer
const currentNotes = notesInput.value;
const transferNote = `TRANSFERRED: $${dollarAmount.toFixed(2)} to buy ${toName}`;
notesInput.value = currentNotes ?
`${currentNotes}; ${transferNote}` :
transferNote;
// Set data attribute to track this transaction
incomeRow.dataset.transactionId = transactionId;
incomeRow.dataset.incomeUsed = dollarAmount;
incomeRow.dataset.usedForTracker = toId;
// Optionally update the row style to indicate it was used in a transfer
incomeRow.style.backgroundColor = "rgba(255, 165, 0, 0.1)"; // Light orange background
}
}
} else {
// No specific row selected - try to find any positive income row to add note to
// Get rows as an array and reverse to start with the newest rows first
const sourceRows = Array.from(sourceTbody.rows).reverse();
let foundRow = false;
console.log(`Checking ${sourceRows.length} source rows for income to transfer`);
// Try to find a row with positive income, starting with the newest rows
for (const row of sourceRows) {
const incomeInput = row.cells[3].querySelector("input");
const income = parseFloat(incomeInput?.value) || 0;
console.log(`Checking row with income: ${income}, need: ${dollarAmount}`);
if (income > 0 && income >= dollarAmount) {
// Found a suitable income row, update its notes
const notesCell = row.cells[7]; // Notes cell
const notesInput = notesCell ? notesCell.querySelector("input") : null;
if (notesInput) {
const currentNotes = notesInput.value;
const transferNote = `TRANSFERRED: $${dollarAmount.toFixed(2)} to buy ${toName}`;
notesInput.value = currentNotes ?
`${currentNotes}; ${transferNote}` :
transferNote;
console.log(`Added transfer note to row with income ${income}`);
// Mark this row as involved in the transaction
row.dataset.transactionId = transactionId;
row.dataset.incomeUsed = dollarAmount;
row.dataset.usedForTracker = toId;
// Make the row visually distinct
row.style.backgroundColor = "rgba(255, 165, 0, 0.1)"; // Light orange background
foundRow = true;
break;
}
}
}
// Only if we couldn't find a suitable income row, create a new entry
if (!foundRow) {
console.log("No suitable income row found, creating a note row without negative income");
// Create a note entry rather than a negative income entry
addDynamicEntry(sourceTbody, {
date: today,
time: currentTime,
currentValue: 0, // Don't change the principal amount
income: 0, // No negative income - just a note
compound: 0,
dailyYield: 0,
notes: `TRANSFERRED: $${dollarAmount.toFixed(2)} to buy ${toName}`,
transactionType: "income-transfer-note",
transactionId: transactionId // Add transaction ID for linked entries
}, fromId);
}
}
} else {
// Regular principal transfer - subtract from current value of source
addDynamicEntry(sourceTbody, {
date: today,
time: currentTime,
currentValue: -dollarAmount, // Use negative value to represent money leaving this tracker
income: 0,
compound: 0,
dailyYield: 0,
notes: `Moved $${dollarAmount.toFixed(2)} to ${toName}`,
transactionType: "conversion-out",
transactionId: transactionId // Add transaction ID for linked entries
}, fromId);
}
// 2. Add a "conversion in" row to destination tracker
const destTable = toTracker.querySelector("table.coin-table");
const destTbody = destTable.querySelector("tbody");
// Get the last current value from the destination tracker
// This is key to the first improvement - add to the last known value instead of standalone value
let lastDestValue = 0;
const destRows = Array.from(destTbody.querySelectorAll("tr"));
if (destRows.length > 0) {
// Get the value from the last row in the destination tracker
const lastRow = destRows[destRows.length - 1];
const currentValueInput = lastRow.cells[2].querySelector("input");
if (currentValueInput) {
lastDestValue = parseFloat(currentValueInput.value) || 0;
}
}
// Calculate the new destination value
const newDestValue = lastDestValue + dollarAmount;
// For destination, add to the last current value
addDynamicEntry(destTbody, {
date: today,
time: currentTime,
currentValue: newDestValue, // Add to the last destination value
income: 0,
compound: 0,
dailyYield: 0,
notes: transferType === 'income'
? `BOUGHT: ${toName} using $${dollarAmount.toFixed(2)} income from ${fromName} (previous value: $${lastDestValue.toFixed(2)})`
: `BOUGHT: ${toName} using $${dollarAmount.toFixed(2)} from ${fromName} (previous value: $${lastDestValue.toFixed(2)})`,
transactionType: transferType === 'income' ? "income-transfer-in" : "conversion-in",
transactionId: transactionId // Add transaction ID for linked entries
}, toId);
// Log the conversion to history with additional details for better tracking
logConversion({
date: today,
time: currentTime,
fromName: fromName,
toName: toName,
dollarAmount: dollarAmount,
transferType: transferType,
destPreviousValue: lastDestValue, // Store the previous value for reference
destNewValue: newDestValue, // Store the new combined value
transactionId: transactionId // Store the transaction ID for linked deletion
});
// Update both trackers
updateCoinTrackerSummary(fromId);
updateCoinTrackerSummary(toId);
saveCoinTrackers();
// Update conversion stats - wrap in try/catch to prevent errors
try {
updateConversionStats();
} catch (error) {
console.error("Error updating conversion stats:", error);
// Don't show alert to user as this is not critical
}
// Update asset dropdowns in case there are new trackers
updateAssetDropdowns();
alert(`Transferred $${dollarAmount.toFixed(2)} from ${fromName} to ${toName}`);
};
// Update asset dropdowns to show available coin trackers
const updateAssetDropdowns = () => {
console.log("Updating asset dropdowns");
const fromSelect = document.getElementById('fromAsset');
const toSelect = document.getElementById('toAsset');
if (!fromSelect || !toSelect) {
console.warn("Asset dropdown elements not found, make sure the HTML is correctly set up");
return;
}
// Clear and initialize dropdowns with default option
fromSelect.innerHTML = '';
toSelect.innerHTML = '';
// Get all active coin trackers
const container = document.getElementById('coinTrackerContainer');
if (!container) {
console.warn("Coin tracker container not found");
return;
}
const trackerDivs = container.getElementsByClassName("coin-tracker");
console.log(`Found ${trackerDivs.length} coin trackers for dropdown population`);
Array.from(trackerDivs).forEach(div => {
if (div.dataset.active === "false") return;
const trackerId = div.id;
const coinNameInput = div.querySelector(".coin-tracker-header input.coin-name-input");
const coinName = coinNameInput ? coinNameInput.value : "Unnamed";
console.log(`Adding ${coinName} (${trackerId}) to asset dropdowns`);
// Add to source dropdown
const sourceOption = document.createElement("option");
sourceOption.value = trackerId;
sourceOption.textContent = coinName;
fromSelect.appendChild(sourceOption);
// Add to destination dropdown
const destOption = document.createElement("option");
destOption.value = trackerId;
destOption.textContent = coinName;
toSelect.appendChild(destOption);
});
};
// Function to update the income rows dropdown with available income entries from the selected tracker
const updateIncomeRowsDropdown = (trackerId) => {
console.log("Updating income rows dropdown for tracker:", trackerId);
const incomeSelect = document.getElementById('incomeRowSelection');
if (!incomeSelect) {
console.warn("Income rows dropdown not found");
return;
}
// Clear existing options
incomeSelect.innerHTML = '';
// If no tracker is selected, exit
if (!trackerId) return;
const tracker = $(trackerId);
if (!tracker) return;
const table = tracker.querySelector("table.coin-table");
if (!table) return;
const tbody = table.querySelector("tbody");
if (!tbody) return;
// Find rows with positive income values
const rows = Array.from(tbody.rows);
// Track if we found any income rows
let foundIncomeRows = false;
rows.forEach((row, index) => {
const dateInput = row.cells[0].querySelector("input");
const timeInput = row.cells[1].querySelector("input");
const incomeInput = row.cells[3].querySelector("input");
const income = parseFloat(incomeInput.value) || 0;
// Only add entries with positive income
if (income > 0) {
foundIncomeRows = true;
const date = dateInput.value;
const time = timeInput.value;
const option = document.createElement("option");
// Store index and income value in the option value as JSON
option.value = JSON.stringify({
rowIndex: index,
income: income,
date: date,
time: time
});
// Display date and income value
option.textContent = `${date} ${time} - $${income.toFixed(2)}`;
incomeSelect.appendChild(option);
}
});
// If no income rows found, add an option indicating this
if (!foundIncomeRows) {
const option = document.createElement("option");
option.value = "";
option.textContent = "No income entries found";
option.disabled = true;
incomeSelect.appendChild(option);
}
};
// Add event listeners for the conversion form controls
const conversionTypeSelect = $('conversionType');
const fromAssetSelect = $('fromAsset');
// Show/hide income row selection based on conversion type
if (conversionTypeSelect) {
conversionTypeSelect.addEventListener('change', () => {
const incomeSelectionDiv = $('incomeRowSelectionDiv');
if (incomeSelectionDiv) {
incomeSelectionDiv.style.display = conversionTypeSelect.value === 'income' ? 'block' : 'none';
// If switching to income and a source is already selected, update the income rows dropdown
if (conversionTypeSelect.value === 'income' && fromAssetSelect.value) {
updateIncomeRowsDropdown(fromAssetSelect.value);
}
}
});
// Trigger change event to set initial state
conversionTypeSelect.dispatchEvent(new Event('change'));
}
// Update income rows dropdown when source asset changes
if (fromAssetSelect) {
fromAssetSelect.addEventListener('change', () => {
const conversionTypeSelect = $('conversionType');
if (conversionTypeSelect && conversionTypeSelect.value === 'income') {
updateIncomeRowsDropdown(fromAssetSelect.value);
}
});
}
// Immediate debug logging
console.log("π₯ Script loaded, document ready state:", document.readyState);
console.log("π₯ Setting up DOMContentLoaded listener...");
document.addEventListener("DOMContentLoaded", () => {
console.log("π₯ DOM Content Loaded - Setting up event handlers");
console.log("π₯ Starting analysis event listener setup...");
console.log("π₯ Location:", window.location.href);
console.log("π₯ Document ready state:", document.readyState);
// Make sure to update asset dropdowns when page loads
updateAssetDropdowns();
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();
// No need to call saveCoinTrackers() here as it's now called inside addCoinTracker
});
// Add event listener for the Export Coin Trackers button
$('exportTrackersBtn').addEventListener("click", exportCoinTrackers);
calculate();
loadCoinTrackers();
updateTransferDropdown();
// Simplified chart display code
const showInvestedBtn = $('showInvestedChartBtn');
const showIncomeBtn = $('showIncomeChartBtn');
// Default display - only show invested chart
$('investedChart').style.display = "block";
$('incomeChart').style.display = "none";
// Button click handlers
showInvestedBtn.addEventListener("click", () => {
$('investedChart').style.display = "block";
$('incomeChart').style.display = "none";
});
showIncomeBtn.addEventListener("click", () => {
$('investedChart').style.display = "none";
$('incomeChart').style.display = "block";
});
// Event listeners for the new reset and delete graph value buttons
$('resetGraphValueBtn').addEventListener("click", resetGraphValue);
$('deleteGraphDataPointBtn').addEventListener("click", deleteGraphDataPoint);
['investedChart','incomeChart','dailyYieldChart'].forEach(chartId => {
const chartEl = document.getElementById(chartId);
chartEl.addEventListener('click', () => {
chartEl.classList.toggle('chart-fullscreen');
// Resize chart if needed
if (window[chartId]) {
window[chartId].resize();
}
});
});
// Add event listener for reinvest rate changes
$('reinvestRate').addEventListener("input", updateAnalysisTable);
// Add event listeners for analysis controls - live updates
console.log("π₯ Setting up analysis event listeners...");
const reinvestRateEl = $('analysisReinvestRate');
const analysisDaysEl = $('analysisDays');
console.log("π₯ Analysis elements found:", !!reinvestRateEl, !!analysisDaysEl);
console.log("π₯ Reinvest element:", reinvestRateEl);
console.log("π₯ Days element:", analysisDaysEl);
console.log("π₯ Current values:", reinvestRateEl?.value, analysisDaysEl?.value);
if (reinvestRateEl) {
console.log("π₯ Adding event listeners to reinvest rate element");
const inputHandler = () => {
console.log("π₯ Reinvest rate INPUT event - new value:", reinvestRateEl.value);
updateAnalysisTable();
};
const keyupHandler = () => {
console.log("π₯ Reinvest rate KEYUP event - new value:", reinvestRateEl.value);
updateAnalysisTable();
};
const changeHandler = () => {
console.log("π₯ Reinvest rate CHANGE event - new value:", reinvestRateEl.value);
updateAnalysisTable();
};
reinvestRateEl.addEventListener("input", inputHandler);
reinvestRateEl.addEventListener("keyup", keyupHandler);
reinvestRateEl.addEventListener("change", changeHandler);
console.log("π₯ Event listeners attached to reinvest rate element");
} else {
console.log("π₯ analysisReinvestRate element not found");
}
if (analysisDaysEl) {
console.log("π₯ Adding event listeners to analysis days element");
const inputHandler = () => {
console.log("π₯ Analysis days INPUT event - new value:", analysisDaysEl.value);
updateAnalysisTable();
};
const keyupHandler = () => {
console.log("π₯ Analysis days KEYUP event - new value:", analysisDaysEl.value);
updateAnalysisTable();
};
const changeHandler = () => {
console.log("π₯ Analysis days CHANGE event - new value:", analysisDaysEl.value);
updateAnalysisTable();
};
analysisDaysEl.addEventListener("input", inputHandler);
analysisDaysEl.addEventListener("keyup", keyupHandler);
analysisDaysEl.addEventListener("change", changeHandler);
console.log("π₯ Event listeners attached to analysis days element");
} else {
console.log("π₯ analysisDays element not found");
}
// Backup and restore functionality
$('createBackupBtn').addEventListener('click', createManualBackup);
$('restoreBackupBtn').addEventListener('click', () => {
const file = $('restoreBackupBtn').dataset.file;
restoreFromBackup(file);
});
// Set up conversion button - MOVED HERE to ensure proper initialization
const executeBtn = $('executeConversion');
console.log("Finding executeConversion button:", executeBtn);
if (executeBtn) {
executeBtn.addEventListener('click', () => {
console.log("Execute conversion button clicked");
// Check if element exists
const amountInput = document.getElementById('conversionAmount');
console.log("Amount input element:", amountInput);
console.log("Amount input value:", amountInput ? amountInput.value : "ELEMENT NOT FOUND");
const fromAsset = $('fromAsset').value;
const toAsset = $('toAsset').value;
const dollarAmount = parseFloat(amountInput ? amountInput.value : "0") || 0;
const transferType = $('conversionType').value;
// Get selected income row data if applicable
let selectedIncomeData = null;
if (transferType === 'income') {
const incomeSelect = $('incomeRowSelection');
if (incomeSelect && incomeSelect.value) {
try {
selectedIncomeData = JSON.parse(incomeSelect.value);
console.log("Selected income data:", selectedIncomeData);
} catch (e) {
console.error("Error parsing selected income data:", e);
}
}
// If income transfer is selected but no income row is selected, show an error
if (!selectedIncomeData && document.getElementById('incomeRowSelectionDiv').style.display !== 'none') {
alert("Please select an income row to use for the transfer.");
return;
}
}
console.log("Transfer values:", {fromAsset, toAsset, dollarAmount, transferType, selectedIncomeData});
console.log("Dollar amount is a number?", !isNaN(dollarAmount));
console.log("Dollar amount > 0?", dollarAmount > 0);
if (fromAsset && toAsset && dollarAmount > 0) {
console.log("All validation passed, calling recordAssetTransfer");
try {
recordAssetTransfer(fromAsset, toAsset, dollarAmount, transferType, selectedIncomeData);
// Reset the amount field after successful conversion
$('conversionAmount').value = '';
// Update dropdowns after conversion in case names have changed
updateAssetDropdowns();
} catch (error) {
console.error("Error during asset transfer:", error);
alert("Error during transfer: " + error.message);
}
} else {
alert("Please fill in all fields with valid values.");
console.warn("Invalid form values:", {fromAsset, toAsset, dollarAmount});
}
});
} else {
console.error("Could not find executeConversion button");
}
// Load backup list when page loads
loadBackupList();
// Enable fullscreen toggle for each chart canvas
["investedChart", "incomeChart"].forEach(id => {
// Removed "dailyYieldChart" from this array
const chartCanvas = document.getElementById(id);
if (!chartCanvas) return;
chartCanvas.style.cursor = "pointer";
chartCanvas.addEventListener("click", () => {
chartCanvas.classList.toggle("chart-fullscreen");
chartCanvas.style.cursor = chartCanvas.classList.contains("chart-fullscreen")
? "zoom-out"
: "pointer";
});
});
updateAnalysisTable();
// Populate asset dropdown menus
updateAssetDropdowns();
// Set up conversion button - this was moved to the DOMContentLoaded event handler
// to ensure proper initialization and avoid duplicate event listeners
// Also add these calls to update dropdowns when trackers change
const addCoinTrackerOriginal = addCoinTracker;
addCoinTracker = function(data) {
const result = addCoinTrackerOriginal(data);
if (typeof updateAssetDropdowns === 'function') {
updateAssetDropdowns();
}
return result;
};
const saveCoinTrackersOriginal = saveCoinTrackers;
saveCoinTrackers = function() {
const result = saveCoinTrackersOriginal();
if (typeof updateAssetDropdowns === 'function') {
updateAssetDropdowns();
}
return result;
};
});
// Expose the updateCoinTrackerSummary function globally
window.updateCoinTrackerSummary = updateCoinTrackerSummary;
// Expose updateConversionStats globally for use by direct-converter.js
window.updateConversionStats = updateConversionStats;
// Expose recordAssetTransfer globally for use by direct-converter.js
window.recordAssetTransfer = recordAssetTransfer;
// Helper function to update all trackers
const updateAllTrackers = () => {
const container = $('coinTrackerContainer');
if (!container) return;
const trackerDivs = container.getElementsByClassName("coin-tracker");
Array.from(trackerDivs).forEach(div => {
updateCoinTrackerSummary(div.id);
});
};
// Expose the updateAllTrackers function globally
window.updateAllTrackers = updateAllTrackers;
// Export coin trackers to CSV file with notes included
const exportCoinTrackers = () => {
console.log("Exporting coin trackers to CSV...");
const container = $('coinTrackerContainer');
const trackerDivs = container.getElementsByClassName("coin-tracker");
// CSV header
let csvContent = "Coin,Platform,Date,Time,Current Value ($),Income ($),Compound ($),Daily Yield (%),Notes\n";
// Collect data from all active trackers
Array.from(trackerDivs).forEach(div => {
// Skip inactive trackers
if (div.dataset.active === "false") return;
// Get coin name and platform
const headerDiv = div.querySelector("div.coin-tracker-header");
const coinNameInput = headerDiv.querySelector("input.coin-name-input");
const coinName = coinNameInput ? coinNameInput.value.replace(/,/g, " ") : "Unnamed";
const platformInput = headerDiv.querySelector("input[list='platformListDatalist']");
const platform = platformInput ? platformInput.value.replace(/,/g, " ") : "";
// Get table data
const table = div.querySelector("table.coin-table");
if (!table) return;
const tbody = table.querySelector("tbody");
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;
// Get notes (added to fix issue with notes not being included)
const notesCell = tr.cells[7]; // Notes is the 8th cell (index 7)
const notesInput = notesCell ? notesCell.querySelector("input") : null;
const notes = notesInput ? notesInput.value.replace(/,/g, " ").replace(/\n/g, " ") : "";
// Add row to CSV
csvContent += `"${coinName}","${platform}","${date}","${time}",${currentValue.toFixed(2)},${income.toFixed(2)},${compound.toFixed(2)},${dailyYield.toFixed(2)},"${notes}"\n`;
});
});
// Create a download link for the CSV
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement("a");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
link.setAttribute("href", URL.createObjectURL(blob));
link.setAttribute("download", `coin-trackers-export-${timestamp}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log("Export completed");
};
// Expose the exportCoinTrackers function globally
window.exportCoinTrackers = exportCoinTrackers;
// Fallback event listener setup for analysis inputs
console.log("π₯ Setting up fallback event listeners...");
setTimeout(() => {
console.log("π₯ Fallback: Setting up analysis event listeners...");
const reinvestRateEl = document.getElementById('analysisReinvestRate');
const analysisDaysEl = document.getElementById('analysisDays');
console.log("π₯ Fallback: Analysis elements found:", !!reinvestRateEl, !!analysisDaysEl);
if (reinvestRateEl) {
console.log("π₯ Fallback: Adding event listeners to reinvest rate element");
const inputHandler = () => {
console.log("π₯ Reinvest rate INPUT event (fallback) - new value:", reinvestRateEl.value);
updateAnalysisTable();
};
reinvestRateEl.addEventListener("input", inputHandler);
reinvestRateEl.addEventListener("change", inputHandler);
console.log("π₯ Fallback: Event listeners attached to reinvest rate element");
}
if (analysisDaysEl) {
console.log("π₯ Fallback: Adding event listeners to analysis days element");
const inputHandler = () => {
console.log("π₯ Analysis days INPUT event (fallback) - new value:", analysisDaysEl.value);
updateAnalysisTable();
};
analysisDaysEl.addEventListener("input", inputHandler);
analysisDaysEl.addEventListener("change", inputHandler);
console.log("π₯ Fallback: Event listeners attached to analysis days element");
}
}, 1000); // Wait 1 second to ensure DOM is ready
})();