Esketit - Add Net Return to Statement

Proving that it's important

  1. // ==UserScript==
  2. // @name Esketit - Add Net Return to Statement
  3. // @namespace http://esketit.com/
  4. // @version 2025-05-01
  5. // @description Proving that it's important
  6. // @author rs232
  7. // @match https://*esketit.com/investor/account-statement
  8. // @icon https://www.google.com/s2/favicons?sz=32&domain_url=https%3A%2F%2Fwww.esketit.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Helper: parse a currency string (e.g. "€2 089,10") to a number.
  16. function parseCurrency(value) {
  17. return parseFloat(value.replace('€', '').replace(/\s/g, '').replace(',', '.')) || 0;
  18. }
  19.  
  20. // Calculate Net Return from the 5 rows in the Summary table.
  21. function calculateNetReturn() {
  22. const rows = [
  23. "Interest received",
  24. "Bonus received",
  25. "Referral bonus received",
  26. "Secondary market income",
  27. "Secondary market expense"
  28. ];
  29. let total = 0;
  30. rows.forEach(rowLabel => {
  31. const row = document.evaluate(
  32. `//tr[td[text()='${rowLabel}']]`,
  33. document,
  34. null,
  35. XPathResult.FIRST_ORDERED_NODE_TYPE,
  36. null
  37. ).singleNodeValue;
  38. if (row) {
  39. const valueCell = row.querySelector('td:nth-child(2)');
  40. if (valueCell) {
  41. const valueText = valueCell.textContent.replace('€', '').replace(/\s/g, '').replace(',', '.');
  42. total += parseCurrency(valueText);
  43. }
  44. }
  45. });
  46. return total;
  47. }
  48.  
  49. // Update the Net Return row below the Closing Balance row.
  50. function addNetReturnRow() {
  51. const closingBalanceRow = document.evaluate(
  52. "//tr[td[text()='Closing balance']]",
  53. document,
  54. null,
  55. XPathResult.FIRST_ORDERED_NODE_TYPE,
  56. null
  57. ).singleNodeValue;
  58. if (closingBalanceRow) {
  59. const netReturn = calculateNetReturn();
  60. const formattedNetReturn = `€${netReturn.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.')}`;
  61. let netReturnRow = document.getElementById('net-return-row');
  62. if (!netReturnRow) {
  63. netReturnRow = document.createElement('tr');
  64. netReturnRow.id = 'net-return-row';
  65. netReturnRow.setAttribute('data-v-344f568a', '');
  66. closingBalanceRow.parentNode.insertBefore(netReturnRow, closingBalanceRow.nextSibling);
  67. }
  68. netReturnRow.innerHTML = `
  69. <td data-v-344f568a="" style="font-weight: bold; color: green;">Net return</td>
  70. <td data-v-344f568a="" style="text-align: right; font-weight: bold; color: green;">${formattedNetReturn}</td>
  71. `;
  72. // Always display the Net Return row with a pale green background.
  73. netReturnRow.style.backgroundColor = "#e6ffe6";
  74. // Colorize the Summary rows after updating the Net Return value.
  75. colorizeSummaryRows();
  76. } else {
  77. console.warn("Closing Balance row not found.");
  78. }
  79. }
  80.  
  81. // Colorizes each Summary row:
  82. // - For "Interest received", "Bonus received", "Referral bonus received", and "Secondary market income":
  83. // if their value > 0, set the label and value text to green and the row background to a pale green.
  84. // - 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.
  85. function colorizeSummaryRows() {
  86. const greenRows = [
  87. "Interest received",
  88. "Bonus received",
  89. "Referral bonus received",
  90. "Secondary market income"
  91. ];
  92. greenRows.forEach(label => {
  93. const row = document.evaluate(
  94. `//tr[td[text()='${label}']]`,
  95. document,
  96. null,
  97. XPathResult.FIRST_ORDERED_NODE_TYPE,
  98. null
  99. ).singleNodeValue;
  100. if (row) {
  101. const valueCell = row.querySelector('td:nth-child(2)');
  102. if (valueCell) {
  103. const value = parseCurrency(valueCell.textContent);
  104. if (value > 0) {
  105. row.querySelector('td:nth-child(1)').style.color = "green";
  106. row.querySelector('td:nth-child(2)').style.color = "green";
  107. row.style.backgroundColor = "#e6ffe6"; // pale green background
  108. } else {
  109. row.querySelector('td:nth-child(1)').style.color = "";
  110. row.querySelector('td:nth-child(2)').style.color = "";
  111. row.style.backgroundColor = "";
  112. }
  113. }
  114. }
  115. });
  116. // For "Secondary market expense"
  117. const redLabel = "Secondary market expense";
  118. const redRow = document.evaluate(
  119. `//tr[td[text()='${redLabel}']]`,
  120. document,
  121. null,
  122. XPathResult.FIRST_ORDERED_NODE_TYPE,
  123. null
  124. ).singleNodeValue;
  125. if (redRow) {
  126. const valueCell = redRow.querySelector('td:nth-child(2)');
  127. if (valueCell) {
  128. const value = parseCurrency(valueCell.textContent);
  129. if (value < 0) {
  130. redRow.querySelector('td:nth-child(1)').style.color = "red";
  131. redRow.querySelector('td:nth-child(2)').style.color = "red";
  132. redRow.style.backgroundColor = "#ffe6e6"; // pale red background
  133. } else {
  134. redRow.querySelector('td:nth-child(1)').style.color = "";
  135. redRow.querySelector('td:nth-child(2)').style.color = "";
  136. redRow.style.backgroundColor = "";
  137. }
  138. }
  139. }
  140. }
  141.  
  142. // Debounce helper to avoid multiple rapid recalculations.
  143. let recalcTimeout;
  144. function scheduleRecalculation() {
  145. if (recalcTimeout) clearTimeout(recalcTimeout);
  146. recalcTimeout = setTimeout(addNetReturnRow, 50);
  147. }
  148.  
  149. // Monitor the value cells for changes.
  150. function monitorValueChanges() {
  151. const rows = [
  152. "Interest received",
  153. "Bonus received",
  154. "Referral bonus received",
  155. "Secondary market income",
  156. "Secondary market expense"
  157. ];
  158. rows.forEach(rowLabel => {
  159. const row = document.evaluate(
  160. `//tr[td[text()='${rowLabel}']]`,
  161. document,
  162. null,
  163. XPathResult.FIRST_ORDERED_NODE_TYPE,
  164. null
  165. ).singleNodeValue;
  166. if (row) {
  167. const valueCell = row.querySelector('td:nth-child(2)');
  168. if (valueCell) {
  169. const observer = new MutationObserver(scheduleRecalculation);
  170. observer.observe(valueCell, { childList: true, characterData: true, subtree: true });
  171. }
  172. }
  173. });
  174. }
  175.  
  176. // On page load, add the Net Return row and start monitoring.
  177. window.addEventListener('load', () => {
  178. addNetReturnRow();
  179. monitorValueChanges();
  180. });
  181. })();