976 lines
44 KiB
JavaScript
976 lines
44 KiB
JavaScript
// Direct conversion handler script
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
console.log("Direct conversion handler loaded");
|
|
|
|
// Initialize conversion history table on page load
|
|
initializeConversionHistory();
|
|
|
|
// Give the main script time to load, then add our direct handler
|
|
setTimeout(function() {
|
|
// Get the conversion button
|
|
const button = document.getElementById('executeConversion');
|
|
if (!button) {
|
|
console.error("Could not find conversion button");
|
|
return;
|
|
}
|
|
|
|
// Replace the button with a clone to remove any existing event listeners
|
|
const newButton = button.cloneNode(true);
|
|
button.parentNode.replaceChild(newButton, button);
|
|
|
|
// Add our direct event handler
|
|
newButton.addEventListener('click', function(event) {
|
|
event.preventDefault(); // Prevent form submission or default button behavior
|
|
console.log("Direct handler: Convert button clicked");
|
|
|
|
// Get form values
|
|
const fromAsset = document.getElementById('fromAsset').value;
|
|
const toAsset = document.getElementById('toAsset').value;
|
|
const dollarAmountInput = document.getElementById('conversionAmount');
|
|
const dollarAmount = parseFloat(dollarAmountInput ? dollarAmountInput.value : '0') || 0;
|
|
const transferType = document.getElementById('conversionType').value;
|
|
|
|
console.log("Form values:", {fromAsset, toAsset, dollarAmount, transferType});
|
|
|
|
// Validate inputs
|
|
if (!fromAsset || !toAsset || dollarAmount <= 0) {
|
|
alert("Please fill in all fields with valid values.");
|
|
console.warn("Invalid form values:", {fromAsset, toAsset, dollarAmount});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// If the main script's recordAssetTransfer function exists, use that instead
|
|
// This prevents duplication of logic and potential conflicts
|
|
if (typeof window.recordAssetTransfer === 'function') {
|
|
console.log("Using main script's recordAssetTransfer function");
|
|
// Pass null as the selectedIncomeData parameter since we don't have income selection in direct-converter
|
|
window.recordAssetTransfer(fromAsset, toAsset, dollarAmount, transferType, null);
|
|
|
|
// Clear the input after successful transfer
|
|
if (dollarAmountInput) {
|
|
dollarAmountInput.value = '';
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Get the tracker elements directly
|
|
const fromTracker = document.getElementById(fromAsset);
|
|
const toTracker = document.getElementById(toAsset);
|
|
|
|
if (!fromTracker || !toTracker) {
|
|
console.error("Could not find tracker elements:", {fromTracker, toTracker});
|
|
alert("Error: Could not find one or both asset trackers.");
|
|
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;
|
|
|
|
// Get table elements
|
|
const sourceTable = fromTracker.querySelector("table.coin-table");
|
|
const destTable = toTracker.querySelector("table.coin-table");
|
|
|
|
if (!sourceTable || !destTable) {
|
|
console.error("Could not find tables:", {sourceTable, destTable});
|
|
alert("Error: Could not find tracker tables.");
|
|
return;
|
|
}
|
|
|
|
// Get tbody elements
|
|
const sourceTbody = sourceTable.querySelector("tbody");
|
|
const destTbody = destTable.querySelector("tbody");
|
|
|
|
if (!sourceTbody || !destTbody) {
|
|
console.error("Could not find tbody elements:", {sourceTbody, destTbody});
|
|
alert("Error: Could not find tracker table bodies.");
|
|
return;
|
|
}
|
|
|
|
// Get current date and time
|
|
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
const options = { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Europe/Berlin' };
|
|
const currentTime = new Date().toLocaleTimeString('en-GB', options);
|
|
|
|
// Generate a transaction ID for linking entries
|
|
const transactionId = `transfer_${Date.now()}`;
|
|
|
|
// Process the transfer based on type
|
|
if (transferType === 'income') {
|
|
// For income transfer, try to find an income row to add a note to
|
|
let foundIncomeRow = false;
|
|
|
|
// Get all rows as an array and reverse to check newest rows first
|
|
const sourceRows = Array.from(sourceTbody.rows).reverse();
|
|
|
|
console.log(`Checking ${sourceRows.length} source rows for income to transfer`);
|
|
|
|
// Try to find a suitable income row, starting with the newest (last) 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.transactionType = "income-source"; // Add explicit transaction type for income source
|
|
row.dataset.sourceRow = "true"; // Extra marker to ensure it's recognized as a source row
|
|
row.dataset.incomeUsed = dollarAmount;
|
|
row.dataset.usedForTracker = toAsset;
|
|
|
|
console.log(`Marked existing income row as transaction type: income-source`);
|
|
|
|
// Make the row visually distinct
|
|
row.style.backgroundColor = "rgba(255, 165, 0, 0.1)"; // Light orange background
|
|
|
|
foundIncomeRow = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only if we couldn't find a suitable income row, create a new entry
|
|
if (!foundIncomeRow) {
|
|
// Create a note entry rather than a negative income entry
|
|
addTableRow(sourceTbody, {
|
|
date: today,
|
|
time: currentTime,
|
|
transactionId: transactionId,
|
|
transactionType: "income-transfer-note",
|
|
sourceRow: true, // Extra marker to ensure it's recognized as a source row
|
|
currentValue: 0, // Don't change principal
|
|
income: 0, // No negative income - just a note
|
|
compound: 0,
|
|
dailyYield: 0,
|
|
notes: 'TRANSFERRED: $' + dollarAmount.toFixed(2) + ' to buy ' + toName
|
|
});
|
|
}
|
|
|
|
console.log("Added source row for income transfer");
|
|
} else {
|
|
// Regular principal transfer - subtract from current value of source
|
|
addTableRow(sourceTbody, {
|
|
date: today,
|
|
time: currentTime,
|
|
transactionId: transactionId,
|
|
transactionType: "conversion-out",
|
|
sourceRow: true, // Extra marker to ensure it's recognized as a source row
|
|
currentValue: -dollarAmount, // Reduce current value by transfer amount
|
|
income: 0,
|
|
compound: 0,
|
|
dailyYield: 0,
|
|
notes: 'Moved $' + dollarAmount.toFixed(2) + ' to ' + toName
|
|
});
|
|
|
|
console.log("Added source row for principal transfer");
|
|
}
|
|
|
|
// Get the last current value from the destination tracker
|
|
// This is key to the fix: instead of just transferring the amount as a standalone value,
|
|
// we add it to the last known value of the destination asset
|
|
let lastDestValue = 0;
|
|
const destRows = Array.from(destTbody.rows);
|
|
if (destRows.length > 0) {
|
|
// Always use the value from the last row in the destination tracker
|
|
const lastRow = destRows[destRows.length - 1];
|
|
lastDestValue = parseFloat(lastRow.cells[2].querySelector("input").value) || 0;
|
|
}
|
|
|
|
// Add row to destination tracker (add to the last current value)
|
|
const newDestValue = lastDestValue + dollarAmount;
|
|
addTableRow(destTbody, {
|
|
date: today,
|
|
time: currentTime,
|
|
transactionId: transactionId,
|
|
transactionType: transferType === 'income' ? "income-transfer-in" : "conversion-in",
|
|
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) + ')'
|
|
});
|
|
|
|
console.log("Added destination row with accumulated value:", { lastDestValue: lastDestValue, dollarAmount: dollarAmount, newDestValue: newDestValue });
|
|
|
|
// Log the transfer to conversion history
|
|
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 the tracker summaries
|
|
if (typeof updateCoinTrackerSummary === 'function') {
|
|
updateCoinTrackerSummary(fromAsset);
|
|
updateCoinTrackerSummary(toAsset);
|
|
|
|
// Update the header display values directly
|
|
const fromTracker = document.getElementById(fromAsset); // Remove 'var' to avoid redeclaration
|
|
const toTracker = document.getElementById(toAsset);
|
|
|
|
if (fromTracker) {
|
|
const fromValueSpan = fromTracker.querySelector(".last-value");
|
|
if (fromValueSpan) {
|
|
// Always use the value from the last row in the source tracker
|
|
let lastSourceValue = 0;
|
|
const sourceRows = Array.from(sourceTbody.rows);
|
|
if (sourceRows.length > 0) {
|
|
const lastRow = sourceRows[sourceRows.length - 1];
|
|
lastSourceValue = parseFloat(lastRow.cells[2].querySelector("input").value) || 0;
|
|
}
|
|
fromValueSpan.textContent = 'Current Value: $' + lastSourceValue.toFixed(2);
|
|
}
|
|
}
|
|
|
|
if (toTracker) {
|
|
const toValueSpan = toTracker.querySelector(".last-value");
|
|
if (toValueSpan) {
|
|
// Always use the value from the last row in the destination tracker
|
|
let lastDestValueHeader = 0;
|
|
const destRowsHeader = Array.from(destTbody.rows);
|
|
if (destRowsHeader.length > 0) {
|
|
const lastRow = destRowsHeader[destRowsHeader.length - 1];
|
|
lastDestValueHeader = parseFloat(lastRow.cells[2].querySelector("input").value) || 0;
|
|
}
|
|
toValueSpan.textContent = 'Current Value: $' + lastDestValueHeader.toFixed(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Explicitly trigger the header update logic after adding rows
|
|
updateCoinTrackerSummary(fromAsset);
|
|
updateCoinTrackerSummary(toAsset);
|
|
|
|
// Save tracker changes
|
|
if (typeof saveCoinTrackers === 'function') {
|
|
saveCoinTrackers();
|
|
}
|
|
|
|
// Update conversion statistics
|
|
if (typeof updateConversionStats === 'function') {
|
|
updateConversionStats();
|
|
}
|
|
|
|
// Update total investment display
|
|
if (typeof updateTotalInvested === 'function') {
|
|
updateTotalInvested();
|
|
}
|
|
|
|
// Clear the amount input field
|
|
if (dollarAmountInput) {
|
|
dollarAmountInput.value = '';
|
|
}
|
|
|
|
// Remove the alert message after transfer
|
|
// alert('Transferred $' + dollarAmount.toFixed(2) + ' from ' + fromName + ' to ' + toName);
|
|
|
|
} catch (error) {
|
|
console.error("Error during asset transfer:", error);
|
|
alert("Error during transfer: " + error.message);
|
|
}
|
|
});
|
|
|
|
console.log("Event listener attached to the new button.");
|
|
console.log("Direct conversion handler added successfully");
|
|
|
|
}, 1000); // Wait 1 second for the page to fully load
|
|
});
|
|
|
|
// Function to add a row to a tracker table
|
|
function addTableRow(tbody, data) {
|
|
if (!tbody) {
|
|
console.error("Could not find tbody to add row");
|
|
return;
|
|
}
|
|
|
|
const tr = document.createElement("tr");
|
|
tr.dataset.created = Date.now();
|
|
|
|
// Store transaction ID if provided (for linked entries)
|
|
if (data.transactionId) {
|
|
tr.dataset.transactionId = data.transactionId;
|
|
console.log(`Setting transaction ID on new row: ${data.transactionId}`);
|
|
}
|
|
|
|
// Store transaction type if provided
|
|
if (data.transactionType) {
|
|
tr.dataset.transactionType = data.transactionType;
|
|
console.log(`Setting transaction type on new row: ${data.transactionType}`);
|
|
}
|
|
|
|
// Add explicit source row marker if this is a source row
|
|
if (data.sourceRow) {
|
|
tr.dataset.sourceRow = "true";
|
|
console.log("Marked row as source row with additional protection");
|
|
} else if (data.transactionType &&
|
|
(data.transactionType === "conversion-out" ||
|
|
data.transactionType === "income-source" ||
|
|
data.transactionType === "income-transfer-note")) {
|
|
tr.dataset.sourceRow = "true";
|
|
console.log("Auto-detected and protected source row based on transaction type");
|
|
}
|
|
|
|
// Date cell
|
|
const tdDate = document.createElement("td");
|
|
const inputDate = document.createElement("input");
|
|
inputDate.type = "date";
|
|
inputDate.value = data.date || "";
|
|
tdDate.appendChild(inputDate);
|
|
tr.appendChild(tdDate);
|
|
|
|
// Time cell
|
|
const tdTime = document.createElement("td");
|
|
const inputTime = document.createElement("input");
|
|
inputTime.type = "time";
|
|
inputTime.value = data.time || "";
|
|
tdTime.appendChild(inputTime);
|
|
tr.appendChild(tdTime);
|
|
|
|
// Current Value cell
|
|
const tdCurrent = document.createElement("td");
|
|
tdCurrent.innerHTML = "$";
|
|
const inputCurrent = document.createElement("input");
|
|
inputCurrent.type = "number";
|
|
inputCurrent.step = "0.01";
|
|
inputCurrent.value = parseFloat(data.currentValue).toFixed(2) || "0.00";
|
|
tdCurrent.appendChild(inputCurrent);
|
|
tr.appendChild(tdCurrent);
|
|
|
|
// Income cell
|
|
const tdIncome = document.createElement("td");
|
|
const inputIncome = document.createElement("input");
|
|
inputIncome.type = "number";
|
|
inputIncome.step = "0.01";
|
|
inputIncome.value = data.income || "";
|
|
tdIncome.appendChild(inputIncome);
|
|
tr.appendChild(tdIncome);
|
|
|
|
// Compound cell
|
|
const tdCompound = document.createElement("td");
|
|
const inputCompound = document.createElement("input");
|
|
inputCompound.type = "number";
|
|
inputCompound.step = "0.01";
|
|
inputCompound.value = data.compound || "";
|
|
tdCompound.appendChild(inputCompound);
|
|
tr.appendChild(tdCompound);
|
|
|
|
// Daily Yield cell
|
|
const tdYield = document.createElement("td");
|
|
const inputYield = document.createElement("input");
|
|
inputYield.type = "number";
|
|
inputYield.step = "0.01";
|
|
inputYield.value = data.dailyYield || "";
|
|
tdYield.appendChild(inputYield);
|
|
tr.appendChild(tdYield);
|
|
|
|
// Actions cell
|
|
const tdActions = document.createElement("td");
|
|
const delBtn = document.createElement("button");
|
|
delBtn.textContent = "Delete";
|
|
delBtn.className = "deleteRowBtn";
|
|
delBtn.addEventListener("click", () => {
|
|
console.log("Delete button clicked for row");
|
|
console.log("Row transaction ID:", tr.dataset.transactionId);
|
|
console.log("Row transaction type:", tr.dataset.transactionType);
|
|
|
|
// For rows that are part of transfers, handle deletion differently
|
|
const isDestinationRow = tr.dataset.transactionId &&
|
|
(tr.dataset.transactionType === 'conversion-in' ||
|
|
tr.dataset.transactionType === 'income-transfer-in');
|
|
|
|
// Triple-check for source rows to protect them from deletion
|
|
const isSourceRow = tr.dataset.sourceRow === 'true' ||
|
|
(tr.dataset.transactionId &&
|
|
(tr.dataset.transactionType === 'conversion-out' ||
|
|
tr.dataset.transactionType === 'income-source' ||
|
|
tr.dataset.transactionType === 'income-transfer-note' ||
|
|
tr.dataset.transactionType === 'source-recovered'));
|
|
|
|
console.log("Is destination row:", isDestinationRow);
|
|
console.log("Is source row (protected from deletion):", isSourceRow);
|
|
|
|
if (isSourceRow) {
|
|
// Never delete source rows - block the deletion attempt
|
|
console.log("BLOCKED attempt to delete a source row directly");
|
|
alert("This row is a source entry for a transfer and cannot be deleted directly. To remove it, please delete the corresponding entry in the conversion history.");
|
|
return; // Exit early without deleting
|
|
} else if (isDestinationRow) {
|
|
// If it's a destination row (conversion-in), we should also clean up the source note
|
|
if (confirm("Delete this transfer entry? This will remove the destination entry and clear the transfer note from the source.")) {
|
|
console.log("Confirmed deletion of destination row");
|
|
const transactionId = tr.dataset.transactionId;
|
|
|
|
// STEP 1: Find source rows across all trackers and clear their notes
|
|
const trackers = document.querySelectorAll('.coin-tracker');
|
|
|
|
// Process all trackers looking for source rows
|
|
trackers.forEach(tracker => {
|
|
// Skip the tracker this row belongs to
|
|
if (tracker === tr.closest('.coin-tracker')) {
|
|
console.log("Skipping current tracker for source rows");
|
|
return;
|
|
}
|
|
|
|
const tbody = tracker.querySelector('table.coin-table tbody');
|
|
if (!tbody) return;
|
|
|
|
// Get all rows in this tracker
|
|
const allRows = tbody.querySelectorAll('tr');
|
|
|
|
// Process each row looking for source rows by multiple methods
|
|
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 : "";
|
|
|
|
// Look for the source pattern in notes
|
|
const trackerNameInput = tracker.querySelector(".coin-tracker-header .coin-name-input");
|
|
const trackerName = trackerNameInput ? trackerNameInput.value : "";
|
|
|
|
if (notesInput && (notes.includes("TRANSFERRED:") || notes.includes("Moved $"))) {
|
|
isSourceByNotes = true;
|
|
}
|
|
|
|
// Combine all detection methods
|
|
const isSourceRowFound = isSourceByMarker || isSourceByID || isSourceByType || isSourceByNotes;
|
|
|
|
// If this is a source row by any method, just clear its notes
|
|
if (isSourceRowFound) {
|
|
console.log(`Found source row in ${trackerName || tracker.id} by ${
|
|
isSourceByMarker ? "explicit marker" :
|
|
isSourceByID ? "transaction ID" :
|
|
isSourceByType ? "transaction type" : "notes"
|
|
}`);
|
|
|
|
console.log(`Source notes before: "${notes}"`);
|
|
|
|
// Add the explicit source marker to protect this row from future deletion attempts
|
|
row.dataset.sourceRow = "true";
|
|
|
|
// Never delete source rows, just clear their notes
|
|
if (notesInput) {
|
|
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 notes after: "${newNotes}"`);
|
|
}
|
|
|
|
// Tag this row if it wasn't already tagged
|
|
if (!isSourceByID) {
|
|
row.dataset.transactionId = transactionId;
|
|
row.dataset.transactionType = "source-recovered";
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// STEP 2: Now that we've processed all source rows, remove ONLY this destination row
|
|
console.log("Removing only this destination row");
|
|
tr.remove();
|
|
}
|
|
} else {
|
|
// Standard deletion for non-transfer rows
|
|
if (tr.dataset.transactionId) {
|
|
// This is a source row of a transfer - don't allow deletion
|
|
console.log("Attempted to delete a source row directly - prevented");
|
|
alert("This row is part of a transfer. To remove it, please delete the corresponding entry in the conversion history.");
|
|
} else {
|
|
// Regular row, can be deleted
|
|
console.log("Removing regular (non-transfer) row");
|
|
tr.remove();
|
|
}
|
|
}
|
|
|
|
if (typeof saveCoinTrackers === 'function') {
|
|
saveCoinTrackers();
|
|
}
|
|
});
|
|
tdActions.appendChild(delBtn);
|
|
tr.appendChild(tdActions);
|
|
|
|
// Notes cell
|
|
const tdNotes = document.createElement("td");
|
|
const notesInput = document.createElement("input");
|
|
notesInput.type = "text";
|
|
notesInput.value = data.notes || "";
|
|
notesInput.placeholder = "Transaction notes";
|
|
notesInput.style.width = "100%";
|
|
tdNotes.appendChild(notesInput);
|
|
tr.appendChild(tdNotes);
|
|
|
|
// Add row to table
|
|
tbody.appendChild(tr);
|
|
}
|
|
|
|
// Helper function to process potential source rows
|
|
function processPotentialSourceRow(row, notesCell, notesInput, transactionId) {
|
|
if (!row) {
|
|
console.error("processPotentialSourceRow called with null row");
|
|
return;
|
|
}
|
|
|
|
// IMMEDIATELY mark this as a source row for protection - do this FIRST before anything else
|
|
row.dataset.sourceRow = "true";
|
|
console.log("PROTECTED: Marked row as source row to prevent deletion");
|
|
|
|
if (notesCell && notesInput) {
|
|
const currentNotes = notesInput.value;
|
|
console.log("Processing potential source row notes:", currentNotes);
|
|
|
|
const transferTextPattern = /TRANSFERRED: \$[\d.]+( to buy [^;]+)?/;
|
|
const movedTextPattern = /Moved \$[\d.]+( to [^;]+)?/;
|
|
let newNotes = currentNotes;
|
|
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("Updated potential source row notes to:", newNotes);
|
|
|
|
// Mark this row as a source row for future operations
|
|
if (transactionId) {
|
|
row.dataset.transactionId = transactionId;
|
|
row.dataset.transactionType = "source-recovered";
|
|
console.log(`Tagged row as source-recovered with transaction ID ${transactionId}`);
|
|
}
|
|
} else {
|
|
console.log("No notes cell/input found for this source row");
|
|
}
|
|
}
|
|
|
|
// Function to log conversions to history
|
|
function logConversion(conversionData) {
|
|
try {
|
|
// Always re-read localStorage to avoid index bugs
|
|
let conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
|
|
conversionData.timestamp = Date.now();
|
|
conversionHistory.push(conversionData);
|
|
localStorage.setItem("conversionHistory", JSON.stringify(conversionHistory));
|
|
updateConversionHistoryTable();
|
|
} catch (error) {
|
|
console.error("Error logging conversion:", error);
|
|
// Don't show alert to user as this is not critical
|
|
}
|
|
}
|
|
|
|
// Function to update the conversion history table
|
|
function updateConversionHistoryTable() {
|
|
try {
|
|
let conversionHistory = JSON.parse(localStorage.getItem("conversionHistory") || "[]");
|
|
const tableBody = document.querySelector("#conversionHistoryTable tbody");
|
|
|
|
if (!tableBody) {
|
|
console.warn("Conversion history table not found in the DOM");
|
|
return; // Exit if the table doesn't exist
|
|
}
|
|
|
|
// Continue only if the table exists
|
|
tableBody.innerHTML = "";
|
|
|
|
// Sort by date, newest first
|
|
conversionHistory.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
|
|
|
|
conversionHistory.forEach((conversion, index) => {
|
|
// Skip if conversion is undefined or null
|
|
if (!conversion) return;
|
|
|
|
const row = document.createElement("tr");
|
|
row.dataset.index = index;
|
|
// Date
|
|
const dateCell = document.createElement("td");
|
|
dateCell.textContent = (conversion.date ? conversion.date : "") + " " + (conversion.time ? conversion.time : "");
|
|
row.appendChild(dateCell);
|
|
// From Asset
|
|
const fromCell = document.createElement("td");
|
|
fromCell.textContent = conversion.fromName;
|
|
row.appendChild(fromCell);
|
|
// Amount
|
|
const amountCell = document.createElement("td");
|
|
const dollarAmount = conversion.dollarAmount || conversion.sentAmount || 0;
|
|
if (conversion.transferType === 'income') {
|
|
amountCell.textContent = '$' + dollarAmount.toFixed(2) + ' (Income)';
|
|
amountCell.style.color = '#4CAF50';
|
|
} else {
|
|
amountCell.textContent = '$' + dollarAmount.toFixed(2);
|
|
}
|
|
row.appendChild(amountCell);
|
|
// To Asset
|
|
const toCell = document.createElement("td");
|
|
toCell.textContent = conversion.toName;
|
|
row.appendChild(toCell);
|
|
// Received
|
|
const receivedCell = document.createElement("td");
|
|
if (conversion.destPreviousValue !== undefined && conversion.destNewValue !== undefined) {
|
|
receivedCell.textContent = '$' + dollarAmount.toFixed(2) + ' → $' + conversion.destNewValue.toFixed(2);
|
|
receivedCell.title = 'Previous value: $' + conversion.destPreviousValue.toFixed(2) + ', Added: $' + dollarAmount.toFixed(2) + ', New value: $' + conversion.destNewValue.toFixed(2);
|
|
} else {
|
|
receivedCell.textContent = '$' + dollarAmount.toFixed(2);
|
|
}
|
|
row.appendChild(receivedCell);
|
|
// Actions
|
|
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() {
|
|
// Make the confirmation message very clear about what will happen
|
|
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("=== 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("=== DELETION DEBUG END ===");
|
|
|
|
// If the conversion has a transaction ID, find and handle related tracker entries
|
|
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(`=== TRACKER ${tracker.id} DEBUG ===`);
|
|
console.log(`Found ${destRows.length} destination rows in tracker ${tracker.id}`);
|
|
console.log("All rows in this tracker's tbody:");
|
|
const allRowsDebug = tbody.querySelectorAll('tr');
|
|
allRowsDebug.forEach((debugRow, idx) => {
|
|
console.log(` Row ${idx}: transactionId=${debugRow.dataset.transactionId}, transactionType=${debugRow.dataset.transactionType}, sourceRow=${debugRow.dataset.sourceRow}`);
|
|
});
|
|
console.log(`=== END TRACKER ${tracker.id} DEBUG ===`);
|
|
|
|
// 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 : "";
|
|
|
|
// Analyze the row before taking any action
|
|
debugAnalyzeRow(row, `Row check before potential deletion in ${trackerName || tracker.id}`);
|
|
|
|
if (!isSafeToDelete) {
|
|
console.log(`SAFETY BLOCK: Prevented deletion of row that might be a source row`);
|
|
} else if (trackerName === toName) {
|
|
// One final check that this is DEFINITELY not a source row
|
|
const analysis = debugAnalyzeRow(row, "FINAL CHECK before deletion");
|
|
if (analysis.isSource) {
|
|
console.error("CRITICAL PROTECTION: Blocked deletion of a source row that was misidentified");
|
|
} else {
|
|
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 => {
|
|
console.log(`Looking for source rows in tracker: ${tracker.id}`);
|
|
const tbody = tracker.querySelector('table.coin-table tbody');
|
|
if (!tbody) {
|
|
console.log("No tbody found in this tracker, skipping");
|
|
return;
|
|
}
|
|
|
|
// Get all rows that might be source entries
|
|
const allRows = tbody.querySelectorAll('tr');
|
|
console.log(`Found ${allRows.length} total rows to check for source entries`);
|
|
|
|
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"
|
|
}`);
|
|
console.log(`Source row notes before: "${notes}"`);
|
|
|
|
// Log detailed row information to help debug
|
|
console.log("Source row data:", {
|
|
id: row.id || "No ID",
|
|
created: row.dataset.created || "No creation timestamp",
|
|
transactionId: row.dataset.transactionId || "No transaction ID",
|
|
transactionType: row.dataset.transactionType || "No transaction type"
|
|
});
|
|
|
|
// 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;
|
|
if (typeof updateCoinTrackerSummary === 'function') {
|
|
updateCoinTrackerSummary(trackerId);
|
|
} else if (typeof window.updateCoinTrackerSummary === 'function') {
|
|
window.updateCoinTrackerSummary(trackerId);
|
|
}
|
|
});
|
|
|
|
// Save all trackers
|
|
if (typeof saveCoinTrackers === 'function') {
|
|
saveCoinTrackers();
|
|
} else if (typeof window.saveCoinTrackers === 'function') {
|
|
window.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");
|
|
}
|
|
localStorage.setItem("conversionHistory", JSON.stringify(conversionHistory));
|
|
updateConversionHistoryTable();
|
|
|
|
// Call updateConversionStats only if it exists (it's defined in script.js)
|
|
try {
|
|
if (typeof updateConversionStats === 'function') {
|
|
updateConversionStats();
|
|
} else if (typeof window.updateConversionStats === 'function') {
|
|
window.updateConversionStats();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error calling updateConversionStats:", error);
|
|
}
|
|
|
|
// Update total investment display if available
|
|
try {
|
|
if (typeof updateTotalInvested === 'function') {
|
|
updateTotalInvested();
|
|
} else if (typeof window.updateTotalInvested === 'function') {
|
|
window.updateTotalInvested();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating total invested:", error);
|
|
}
|
|
}
|
|
});
|
|
actionsCell.appendChild(deleteBtn);
|
|
row.appendChild(actionsCell);
|
|
tableBody.appendChild(row);
|
|
});
|
|
|
|
// Safely call updateConversionStats if it exists
|
|
try {
|
|
if (typeof updateConversionStats === 'function') {
|
|
updateConversionStats();
|
|
} else if (typeof window.updateConversionStats === 'function') {
|
|
window.updateConversionStats();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error calling updateConversionStats after table update:", error);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating conversion history table:", error);
|
|
}
|
|
}
|
|
|
|
// Function to initialize the conversion history table on page load
|
|
function initializeConversionHistory() {
|
|
try {
|
|
updateConversionHistoryTable();
|
|
} catch (error) {
|
|
console.error("Error initializing conversion history:", error);
|
|
}
|
|
}
|
|
|
|
// Debug helper function to analyze a row and determine if it's a source row
|
|
function debugAnalyzeRow(row, label = "Row analysis") {
|
|
if (!row) {
|
|
console.error("debugAnalyzeRow called with null row");
|
|
return {isSource: false, reason: "null row"};
|
|
}
|
|
|
|
const isSourceByMarker = row.dataset && row.dataset.sourceRow === 'true';
|
|
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');
|
|
|
|
const notesCell = row.cells && row.cells[7] ? row.cells[7] : null;
|
|
const notesInput = notesCell ? notesCell.querySelector("input") : null;
|
|
const notes = notesInput ? notesInput.value : "";
|
|
|
|
const isSourceByNotes = notes.includes('TRANSFERRED:') || notes.includes('Moved $');
|
|
|
|
const result = {
|
|
isSource: isSourceByMarker || isSourceByType || isSourceByNotes,
|
|
byMarker: isSourceByMarker,
|
|
byType: isSourceByType,
|
|
byNotes: isSourceByNotes,
|
|
transactionId: row.dataset.transactionId || "none",
|
|
transactionType: row.dataset.transactionType || "none",
|
|
notes: notes
|
|
};
|
|
|
|
console.log(`${label}:`, result);
|
|
return result;
|
|
}
|