Keka Log Duration by Jp

try to take over the world

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

})();