- // ==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();
- });
- })();