Keka Log Duration by Jp

try to take over the world

// ==UserScript==
// @name         Keka Log Duration by Jp
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  try to take over the world
// @author       You
// @match        https://ezeetechnosys.keka.com/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=keka.com
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    function calculateTotalDuration(durationsArray) {
        let totalHours = 0;
        let totalMinutes = 0;

        durationsArray.forEach(durationStr => {
            if (durationStr) {
                const [hours, minutes] = durationStr.match(/\d+/g).map(Number);
                totalHours += hours;
                totalMinutes += minutes;
            }
        });

        // Add overflow from minutes to hours
        totalHours += Math.floor(totalMinutes / 60);
        totalMinutes = totalMinutes % 60;

        // Format the total duration string
        const totalDurationStr = `${totalHours} Hr ${totalMinutes} Min`;

        return totalDurationStr;
    }

    function calculateDuration(startTimeSpan, endTimeSpan) {
        const startTimeStr = startTimeSpan.textContent.trim();
        const endTimeStr = endTimeSpan.textContent.trim();

        // If end time is empty, use current time
        const endTime = endTimeStr !== "MISSING" ? endTimeStr : new Date().toLocaleTimeString('en-US', { hour12: true });

        // Parse time strings into Date objects
        const startTime = new Date(`2023-12-27 ${startTimeStr}`); // Assuming today's date
        const endTimeDate = new Date(`2023-12-27 ${endTime}`);

        // Calculate duration in milliseconds
        const durationMillis = endTimeDate.getTime() - startTime.getTime();

        // Convert milliseconds to hours and minutes
        const durationHours = Math.floor(durationMillis / (3600 * 1000));
        const durationMinutes = Math.floor((durationMillis % (3600 * 1000)) / (60 * 1000));

        // Formatted duration string
        const durationStr = `${durationHours} Hr ${durationMinutes} Min`;

        return durationStr;
    }

    function watchForModals() {
        const modals = document.querySelectorAll('body > modal-container > div.modal-dialog.small-modal > div > attendance-adjustment-request > div.modal-body > form > div.mt-20 > div > div:nth-child(2) > div');
        console.log("Modals", modals);

        modals.forEach(modal => {
            const durations = [];
            let N = 1;
            let lastDurationDisplaySpan = null;

            while (true) {
                // Construct XPath expressions
                const startTimeSpanXPath = `/html/body/modal-container/div[2]/div/attendance-adjustment-request/div[2]/form/div[2]/div/div[2]/div/div/div[${N}]/div[1]/div[2]/span[2]`;
                const endTimeSpanXPath = `/html/body/modal-container/div[2]/div/attendance-adjustment-request/div[2]/form/div[2]/div/div[2]/div/div/div[${N}]/div[1]/div[3]/span[2]`;

                // Fetch elements
                const startTimeSpan = document.evaluate(startTimeSpanXPath, modal, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
                const endTimeSpan = document.evaluate(endTimeSpanXPath, modal, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

                if (!startTimeSpan || !endTimeSpan) {
                    // No more durations
                    break;
                }

                // Calculate duration
                const durationStr = calculateDuration(startTimeSpan, endTimeSpan);

                // Store durationStr
                durations.push(durationStr);

                // Create or update the duration display span
                const durationDisplaySpan = document.querySelector(`body > modal-container > div.modal-dialog.small-modal > div > attendance-adjustment-request > div.modal-body > form > div.mt-20 > div > div:nth-child(2) > div > div > div:nth-child(${N}) > div.d-flex.align-items-center.ng-untouched.ng-pristine.ng-valid > div.d-flex.align-items-center.ml-10`);

                if (durationDisplaySpan) {
                    // Check if duration span already exists
                    let existingDurationSpan = durationDisplaySpan.querySelector(`.duration-span-${N}`);
                    if (!existingDurationSpan) {
                        // Create duration span
                        existingDurationSpan = document.createElement('span');
                        existingDurationSpan.classList.add(`duration-span-${N}`);
                        existingDurationSpan.style.fontWeight = 'bold';
                        existingDurationSpan.style.paddingLeft = '17px';
                        existingDurationSpan.style.color = 'darkred';
                        durationDisplaySpan.insertAdjacentElement('afterend', existingDurationSpan);
                    }
                    existingDurationSpan.textContent = durationStr;

                    // Keep track of the last valid durationDisplaySpan
                    lastDurationDisplaySpan = durationDisplaySpan;
                } else {
                    console.warn(`durationDisplaySpan not found for N = ${N}`);
                }

                N++;
            }

            // Decrement N to point to the last valid index
            N--;

            // Only display total duration if more than one duration exists
            if (durations.length > 1 && lastDurationDisplaySpan) {
                const totalDurationStr = calculateTotalDuration(durations);

                // Display total duration
                const totalDurationDisplaySpanSelector = `body > modal-container > div.modal-dialog.small-modal > div > attendance-adjustment-request > div.modal-body > form > div.mt-20 > div > div:nth-child(2) > div > div > div:nth-child(${N + 1}) > div.d-flex.align-items-center.ng-untouched.ng-pristine.ng-valid`;
                let totalDurationDisplaySpan = document.querySelector(totalDurationDisplaySpanSelector);

                // If the element doesn't exist, create it
                if (!totalDurationDisplaySpan) {
                    totalDurationDisplaySpan = document.createElement('div');
                    totalDurationDisplaySpan.classList.add('d-flex', 'align-items-center', 'ng-untouched', 'ng-pristine', 'ng-valid');
                    const parentElementSelector = `body > modal-container > div.modal-dialog.small-modal > div > attendance-adjustment-request > div.modal-body > form > div.mt-20 > div > div:nth-child(2) > div > div`;
                    const parentElement = document.querySelector(parentElementSelector);
                    if (parentElement) {
                        parentElement.appendChild(totalDurationDisplaySpan);
                    } else {
                        console.warn('Parent element for total duration not found.');
                    }
                }

                // Check for existing total duration span:
                let existingTotalDurationSpan = totalDurationDisplaySpan.querySelector('.total-duration-span');
                if (!existingTotalDurationSpan) {
                    existingTotalDurationSpan = document.createElement('span');
                    existingTotalDurationSpan.classList.add('total-duration-span');
                    existingTotalDurationSpan.style.fontWeight = 'bold';
                    existingTotalDurationSpan.style.color = '#0600ff';
                    existingTotalDurationSpan.style.float = 'right';
                    existingTotalDurationSpan.style.padding = '5px';
                    existingTotalDurationSpan.style.marginLeft = '375px'; // Adjusted as per your script
                    existingTotalDurationSpan.style.background = 'lightblue';
                    existingTotalDurationSpan.style.borderRadius = '5px';
                    existingTotalDurationSpan.style.borderTop = '3px solid #888';

                    totalDurationDisplaySpan.appendChild(existingTotalDurationSpan);
                }
                existingTotalDurationSpan.textContent = totalDurationStr;
            } else {
                console.warn('No durations found or only one duration present. Total duration will not be displayed.');
            }

            // Add morphing text effect
            addMorphingTextEffect();
        });
    }

    function addMorphingTextEffect() {
        const parentDivs = document.querySelectorAll('div.mb-30.ng-untouched.ng-pristine.ng-valid');
        if (parentDivs.length > 1) {
            // Targeting the second div as per your requirement
            const targetDiv = parentDivs[1];
            const pElement = targetDiv.querySelector('p.text-large'); // Assuming the span is the one you've added
            if (!pElement) {
                console.warn('p.text-large element not found.');
                return;
            }
            let spanElement = pElement.querySelector('.morphing-text-span');

            if (!spanElement) {
                spanElement = document.createElement('span');
                spanElement.classList.add('morphing-text-span');
                // Starting the text update process
                spanElement.style.fontWeight = 'bold';
                spanElement.style.color = 'rgb(0, 123, 255)';
                spanElement.style.padding = '5px';
                spanElement.style.borderRadius = '5px';
                spanElement.style.fontSize = '16px';
                spanElement.style.marginLeft = '262px';
                pElement.appendChild(spanElement);
            }

            // Texts array for the morphing effect
            const texts = ["Log Duration", "    By @Jaydeep😶"];
            let textIndex = 0;

            // Function to update the text with a morphing effect
            function updateText() {
                const nextText = texts[textIndex % texts.length];
                textIndex++;

                // Applying a fade-out effect
                spanElement.style.opacity = '0';
                spanElement.style.transition = 'opacity 0.5s ease-out';

                // After fade-out, change the text and fade it back in
                setTimeout(() => {
                    spanElement.textContent = nextText;

                    // Applying blur during transition
                    spanElement.style.filter = 'blur(2px)';
                    spanElement.style.opacity = '1';
                    spanElement.style.transition = 'opacity 0.5s ease-in, filter 0.5s ease';

                    // Removing blur after a moment
                    setTimeout(() => {
                        spanElement.style.filter = 'none';
                    }, 500); // Match the transition time
                }, 500); // Wait for the fade-out to complete
            }

            // Initialize the text content
            spanElement.textContent = texts[textIndex % texts.length];
            textIndex++;

            // Start the interval for morphing text
            setInterval(updateText, 2000);
        }
    }

    const observer = new MutationObserver(mutations => {
        console.log("Observing.....");
        const modalTriggerElements = document.querySelectorAll('employee-attendance-list-view .attendance-logs-row');
        modalTriggerElements.forEach(element => {
            element.addEventListener('click', watchForModals);
        });
    });

    observer.observe(document.body, { childList: true, subtree: false });

})();