// ==UserScript==
// @name Esketit - Add Net Return to Statement
// @namespace http://esketit.com/
// @version 2025-05-01
// @description Proving that it's important
// @author rs232
// @match https://*esketit.com/investor/account-statement
// @icon https://www.google.com/s2/favicons?sz=32&domain_url=https%3A%2F%2Fwww.esketit.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Helper: parse a currency string (e.g. "€2 089,10") to a number.
function parseCurrency(value) {
return parseFloat(value.replace('€', '').replace(/\s/g, '').replace(',', '.')) || 0;
}
// Calculate Net Return from the 5 rows in the Summary table.
function calculateNetReturn() {
const rows = [
"Interest received",
"Bonus received",
"Referral bonus received",
"Secondary market income",
"Secondary market expense"
];
let total = 0;
rows.forEach(rowLabel => {
const row = document.evaluate(
`//tr[td[text()='${rowLabel}']]`,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (row) {
const valueCell = row.querySelector('td:nth-child(2)');
if (valueCell) {
const valueText = valueCell.textContent.replace('€', '').replace(/\s/g, '').replace(',', '.');
total += parseCurrency(valueText);
}
}
});
return total;
}
// Update the Net Return row below the Closing Balance row.
function addNetReturnRow() {
const closingBalanceRow = document.evaluate(
"//tr[td[text()='Closing balance']]",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (closingBalanceRow) {
const netReturn = calculateNetReturn();
const formattedNetReturn = `€${netReturn.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.')}`;
let netReturnRow = document.getElementById('net-return-row');
if (!netReturnRow) {
netReturnRow = document.createElement('tr');
netReturnRow.id = 'net-return-row';
netReturnRow.setAttribute('data-v-344f568a', '');
closingBalanceRow.parentNode.insertBefore(netReturnRow, closingBalanceRow.nextSibling);
}
netReturnRow.innerHTML = `
<td data-v-344f568a="" style="font-weight: bold; color: green;">Net return</td>
<td data-v-344f568a="" style="text-align: right; font-weight: bold; color: green;">${formattedNetReturn}</td>
`;
// Always display the Net Return row with a pale green background.
netReturnRow.style.backgroundColor = "#e6ffe6";
// Colorize the Summary rows after updating the Net Return value.
colorizeSummaryRows();
} else {
console.warn("Closing Balance row not found.");
}
}
// Colorizes each Summary row:
// - For "Interest received", "Bonus received", "Referral bonus received", and "Secondary market income":
// if their value > 0, set the label and value text to green and the row background to a pale green.
// - For "Secondary market expense": if its value is negative, set the label and value text to red and the row background to a pale red.
function colorizeSummaryRows() {
const greenRows = [
"Interest received",
"Bonus received",
"Referral bonus received",
"Secondary market income"
];
greenRows.forEach(label => {
const row = document.evaluate(
`//tr[td[text()='${label}']]`,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (row) {
const valueCell = row.querySelector('td:nth-child(2)');
if (valueCell) {
const value = parseCurrency(valueCell.textContent);
if (value > 0) {
row.querySelector('td:nth-child(1)').style.color = "green";
row.querySelector('td:nth-child(2)').style.color = "green";
row.style.backgroundColor = "#e6ffe6"; // pale green background
} else {
row.querySelector('td:nth-child(1)').style.color = "";
row.querySelector('td:nth-child(2)').style.color = "";
row.style.backgroundColor = "";
}
}
}
});
// For "Secondary market expense"
const redLabel = "Secondary market expense";
const redRow = document.evaluate(
`//tr[td[text()='${redLabel}']]`,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (redRow) {
const valueCell = redRow.querySelector('td:nth-child(2)');
if (valueCell) {
const value = parseCurrency(valueCell.textContent);
if (value < 0) {
redRow.querySelector('td:nth-child(1)').style.color = "red";
redRow.querySelector('td:nth-child(2)').style.color = "red";
redRow.style.backgroundColor = "#ffe6e6"; // pale red background
} else {
redRow.querySelector('td:nth-child(1)').style.color = "";
redRow.querySelector('td:nth-child(2)').style.color = "";
redRow.style.backgroundColor = "";
}
}
}
}
// Debounce helper to avoid multiple rapid recalculations.
let recalcTimeout;
function scheduleRecalculation() {
if (recalcTimeout) clearTimeout(recalcTimeout);
recalcTimeout = setTimeout(addNetReturnRow, 50);
}
// Monitor the value cells for changes.
function monitorValueChanges() {
const rows = [
"Interest received",
"Bonus received",
"Referral bonus received",
"Secondary market income",
"Secondary market expense"
];
rows.forEach(rowLabel => {
const row = document.evaluate(
`//tr[td[text()='${rowLabel}']]`,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (row) {
const valueCell = row.querySelector('td:nth-child(2)');
if (valueCell) {
const observer = new MutationObserver(scheduleRecalculation);
observer.observe(valueCell, { childList: true, characterData: true, subtree: true });
}
}
});
}
// On page load, add the Net Return row and start monitoring.
window.addEventListener('load', () => {
addNetReturnRow();
monitorValueChanges();
});
})();