Greasy Fork is available in English.

Fintual Goal Variation

Easily display profit/loss on the profitability chart.

// ==UserScript==
// @name           Fintual Goal Variation
// @name:es        Fintual variación de objetivos
// @namespace      http://tampermonkey.net/
// @version        0.4
// @description    Easily display profit/loss on the profitability chart.
// @description:es Muestra fácilmente la ganancia/pérdida en el gráfico de rentabilidad.
// @author         IgnaV
// @match          https://fintual.cl/app/goals/*
// @icon           http://fintual.cl/favicon.ico
// @grant          none
// ==/UserScript==

(function() {
    'use strict';

    const showHistory = false;

    const containerHeightStyle = document.createElement('style');
    document.head.appendChild(containerHeightStyle);
    const updateContainerHeight = elements => {
        containerHeightStyle.textContent = `div.nvtooltip.performance-tooltip { top: -${22 * elements}px !important }`;
    };
    updateContainerHeight(2);

    const history = {};
    let diff, date, clickDiff, clickDate;

    const calculatePercentage = (value, total) => ((value / total) * 100).toFixed(1);

    const getLegendBgColor = value => (value >= 0 ? 'rgb(43, 214, 0)' : 'rgb(214, 0, 0)');

    const formatNumber = number => number.toLocaleString('es-CL', { useGrouping: true });

    const addRowToTable = (tbody, label, percentage, value, color) => {
        const row = document.createElement('tr');
        row.innerHTML = `
            <td class="legend-color-guide"><div style="background-color: ${color};"></div></td>
            <td class="key">${label}</td>
            <td class="value">(${percentage}%) $ ${value}</td>`;
        tbody.appendChild(row);
    };

    const updateDateContent = (tdDate, esDateStr, weekDayName) => {
        tdDate.textContent = `${esDateStr} | ${weekDayName}`;
        if (clickDate !== undefined) {
            const betweenDays = Math.round((date - clickDate) / (1000 * 60 * 60 * 24));
            tdDate.textContent += ` | ${betweenDays} ${betweenDays === 1 ? "día" : "días"}`;
        }
    };

    const observeTableChanges = targetElement => {
        const tableObserver = new MutationObserver(mutationsList => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    for (const node of mutation.addedNodes) {
                        if (node.tagName === 'TABLE') {
                            const table = node;
                            const tdDate = table.querySelector('thead > tr > td > strong');
                            const tbody = table.querySelector('tbody');
                            const existingRows = tbody.querySelectorAll('tr');
                            const depositAmount = parseFloat(existingRows[0].querySelectorAll('td')[2].textContent.replace(/[^0-9-]+/g, ''));
                            const fintualBalance = parseFloat(existingRows[1].querySelectorAll('td')[2].textContent.replace(/[^0-9-]+/g, ''));
                            diff = fintualBalance - depositAmount;

                            const diffPercentage = calculatePercentage(diff, depositAmount);
                            const legendBgColor = getLegendBgColor(diff);
                            const formattedDiff = formatNumber(diff);

                            addRowToTable(tbody, 'Balance', diffPercentage, formattedDiff, legendBgColor);

                            let [esDateStr, weekDayName] = tdDate.textContent.split(' ');
                            const dateParts = esDateStr.split('/');
                            const year = '20' + dateParts[2];
                            const month = dateParts[1];
                            const day = dateParts[0];
                            const enDateStr = `${year}-${month}-${day}`;

                            if (!weekDayName) {
                                date = new Date(year, month - 1, day);
                                const weekDayNames = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
                                weekDayName = weekDayNames[date.getDay()];
                                updateDateContent(tdDate, esDateStr, weekDayName);
                            }

                            history[enDateStr] = diff;

                            if (showHistory) {
                                console.log(Object.keys(history).sort().reduce((acc, key) => (acc[key] = history[key], acc), {}));
                            }

                            const previousDate = Object.keys(history).sort().reverse().find(key => key < enDateStr);
                            if (previousDate !== undefined) {
                                const previousDiff = history[previousDate];
                                const balanceDiff = diff - previousDiff;
                                const balanceDiffPercentage = calculatePercentage(balanceDiff, depositAmount);
                                const balanceLegendBgColor = getLegendBgColor(balanceDiff);
                                const balanceFormattedDiff = formatNumber(balanceDiff);

                                addRowToTable(tbody, 'Diferencia', balanceDiffPercentage, balanceFormattedDiff, balanceLegendBgColor);
                            }

                            if (clickDiff !== undefined) {
                                const balanceDiff = diff - clickDiff;
                                const balanceDiffPercentage = calculatePercentage(balanceDiff, depositAmount);
                                const balanceLegendBgColor = getLegendBgColor(balanceDiff);
                                const balanceFormattedDiff = formatNumber(balanceDiff);

                                addRowToTable(tbody, 'Diferencia click', balanceDiffPercentage, balanceFormattedDiff, balanceLegendBgColor);
                                updateContainerHeight(3);
                            } else {
                                updateContainerHeight(2);
                            }
                        }
                    }
                }
            }
        });

        const tableObserverOptions = {
            childList: true,
            subtree: true
        };
        tableObserver.observe(targetElement, tableObserverOptions);
        return tableObserver;
    };

    let prev;
    const rootObserver = new MutationObserver(mutationsList => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                const targetElement = document.querySelector('div.nvtooltip.performance-tooltip');
                if (targetElement) {
                    if (prev) prev.disconnect();
                    prev = observeTableChanges(targetElement);
                    rootObserver.disconnect();
                }
            }
        }
    });

    const rootObserverOptions = {
        childList: true,
        subtree: true
    };
    rootObserver.observe(document.body, rootObserverOptions);

    const removeLastClick = () => {
        clickDiff = clickDate = undefined;
        const lastClickElement = document.querySelector('.last-click');
        if (lastClickElement) {
            lastClickElement.remove();
        }
    };

    document.addEventListener('click', event => {
        if (event.target.matches('g.nv-focus, g.nv-focus *')) {
            removeLastClick();
            clickDiff = diff;
            clickDate = date;
            const guideLine = document.querySelector('.nv-interactiveGuideLine');
            const clonedGuideLine = guideLine.cloneNode(true);
            clonedGuideLine.classList.add('last-click');
            guideLine.parentNode.insertBefore(clonedGuideLine, guideLine.nextSibling);
        }
    });

    document.addEventListener('dblclick', removeLastClick);
})();