Created
November 5, 2024 18:49
-
-
Save AitorAstorga/dba22f8be294c3f36e28830259670053 to your computer and use it in GitHub Desktop.
Calculates and displays total work time for the day in the ATOSS toolbar.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name ATOSS Work Time Total Counter | |
// @namespace http://tampermonkey.net/ | |
// @version 1.0.0 | |
// @description Calculates and displays total work time for the day in the toolbar. | |
// @author Aitor Astorga | |
// @match https://*.atoss.com/* | |
// @grant none | |
// ==/UserScript== | |
// ====================================================== | |
// Constants (These depend on the language configured in ATOSS) | |
// ====================================================== | |
// Account Types | |
const ENTRADA = 'entrada'; // 'entry' in English | |
const TELETRABAJO = 'teletrabajo'; // 'telework' in English | |
const SALIDA = 'salida'; // 'exit' in English | |
const PAUSA = 'pausa'; // 'break' in English | |
const VIAJE_DE_EMPRESA = 'viaje de empresa'; // 'business trip' in English | |
const MEDICO_GENERAL = 'médico general'; // 'general practitioner' in English | |
const MEDICO_ESPECIALISTA = 'médico especialista'; // 'specialist doctor' in English | |
const INFORMES = 'Informes'; // 'Reports' in English | |
(function() { | |
'use strict'; | |
const SCRIPT_NAME = '[WorkTimeTotalCounter]'; | |
// Utility function to log messages with the script name | |
function log(message) { | |
console.log(`${SCRIPT_NAME} ${message}`); | |
} | |
// Utility function to format time as HHh MMm SSs | |
function formatTime(durationInSeconds) { | |
const hours = Math.floor(durationInSeconds / 3600); | |
const minutes = Math.floor((durationInSeconds % 3600) / 60); | |
const seconds = Math.floor(durationInSeconds % 60); | |
return `${hours}h ${minutes}m ${seconds}s`; | |
} | |
// Function to get today's date in "DD-mmm-YYYY" format (e.g., "05-nov-2024") | |
function getTodayFormatted() { | |
const today = new Date(); | |
const day = String(today.getDate()).padStart(2, '0'); | |
const monthNames = ["jan", "feb", "mar", "apr", "may", "jun", | |
"jul", "aug", "sep", "oct", "nov", "dec"]; | |
const month = monthNames[today.getMonth()]; | |
const year = today.getFullYear(); | |
return `${day}-${month}-${year}`; | |
} | |
// Function to parse time string "H:MM" or "HH:MM" into Date object with today's date | |
function parseTime(timeStr) { | |
const [hours, minutes] = timeStr.split(':').map(Number); | |
const now = new Date(); | |
now.setHours(hours, minutes, 0, 0); | |
return new Date(now); | |
} | |
// Function to calculate total work time | |
function calculateTotalWorkTime(entries) { | |
let totalSeconds = 0; | |
let workStart = null; | |
let currentlyWorking = false; | |
entries.forEach(entry => { | |
const type = entry.type.toLowerCase(); | |
if (type === ENTRADA || type === TELETRABAJO) { | |
if (!currentlyWorking) { | |
workStart = entry.time; | |
currentlyWorking = true; | |
log(`Work started at ${entry.time}`); | |
} else { | |
log(`Unexpected ${type} entry while already working at ${entry.time}`); | |
} | |
} else if (type === SALIDA || type === PAUSA || | |
type === VIAJE_DE_EMPRESA || | |
type === MEDICO_GENERAL || | |
type === MEDICO_ESPECIALISTA) { | |
if (currentlyWorking && workStart) { | |
const duration = (entry.time - workStart) / 1000; // in seconds | |
totalSeconds += duration; | |
log(`Work ended at ${entry.time}, duration added: ${formatTime(duration)}`); | |
workStart = null; | |
currentlyWorking = false; | |
} else { | |
log(`Unexpected ${type} entry without a corresponding work start at ${entry.time}`); | |
} | |
} | |
// Add more conditions if there are other account types that affect work time | |
}); | |
// If currently working, add time up to now | |
if (currentlyWorking && workStart) { | |
const now = new Date(); | |
const duration = (now - workStart) / 1000; // in seconds | |
totalSeconds += duration; | |
log(`Currently working. Added ongoing duration: ${formatTime(duration)}`); | |
} | |
return totalSeconds; | |
} | |
// Function to create and insert the total work time display in the toolbar | |
function insertTotalWorkTimeDisplay() { | |
const toolbars = document.querySelectorAll('.ToolBar.toolbar'); | |
let targetToolbar = null; | |
// Identify the toolbar that contains the "Informes" button | |
toolbars.forEach(toolbar => { | |
const informesButton = Array.from(toolbar.querySelectorAll('.ToolItem.toolbar-button')) | |
.find(btn => btn.textContent.trim().includes(INFORMES)); | |
if (informesButton) { | |
targetToolbar = toolbar; | |
} | |
}); | |
if (!targetToolbar) { | |
log('Target toolbar with "Informes" not found!'); | |
return null; | |
} | |
// Check if the display already exists to prevent duplicates | |
if (document.getElementById('totalWorkTimeContainer')) { | |
log('Total Work Time display already exists.'); | |
return document.getElementById('totalWorkTime'); | |
} | |
// Find the "Informes" button within the target toolbar | |
const informesButton = Array.from(targetToolbar.querySelectorAll('.ToolItem.toolbar-button')) | |
.find(btn => btn.textContent.trim().includes(INFORMES)); | |
if (!informesButton) { | |
log('Informes button not found in the target toolbar.'); | |
return null; | |
} | |
// Create a container for the total work time | |
const totalTimeContainer = document.createElement('div'); | |
totalTimeContainer.id = 'totalWorkTimeContainer'; | |
totalTimeContainer.style.position = 'absolute'; | |
// Calculate the position after the "Informes" button | |
const informesRect = informesButton.getBoundingClientRect(); | |
const toolbarRect = targetToolbar.getBoundingClientRect(); | |
// Compute the left position relative to the toolbar | |
const left = informesButton.offsetLeft + informesButton.offsetWidth + 20; // 20px spacing | |
totalTimeContainer.style.left = `${left}px`; | |
totalTimeContainer.style.top = '16px'; | |
totalTimeContainer.style.padding = '5px 10px'; | |
totalTimeContainer.style.background = 'rgba(0, 0, 0, 0.1)'; | |
totalTimeContainer.style.borderRadius = '5px'; | |
totalTimeContainer.style.fontFamily = '"Segoe UI", Arial, Helvetica, sans-serif'; | |
totalTimeContainer.style.fontSize = '14px'; | |
totalTimeContainer.style.color = '#000'; | |
totalTimeContainer.style.zIndex = '1000'; | |
const label = document.createElement('span'); | |
label.textContent = 'Total Work Time: '; | |
const timeDisplay = document.createElement('span'); | |
timeDisplay.id = 'totalWorkTime'; | |
timeDisplay.textContent = '0h 0m 0s'; | |
totalTimeContainer.appendChild(label); | |
totalTimeContainer.appendChild(timeDisplay); | |
targetToolbar.appendChild(totalTimeContainer); | |
log('Total Work Time display inserted into the toolbar.'); | |
return timeDisplay; | |
} | |
// Function to update the total work time | |
function updateTotalWorkTime(timeDisplay) { | |
const todayStr = getTodayFormatted(); | |
const timeCells = document.querySelectorAll('.rwt-cell.TIMESTAMP_TIME'); | |
const dateCells = document.querySelectorAll('.rwt-cell.TIMESTAMP_DATE'); | |
const accountCells = document.querySelectorAll('.rwt-cell.ACCOUNT'); | |
log(`Found ${timeCells.length} time cells, ${dateCells.length} date cells, ${accountCells.length} account cells.`); | |
if (timeCells.length !== accountCells.length || timeCells.length !== dateCells.length) { | |
log('Mismatch in number of time, date, and account cells.'); | |
return; | |
} | |
const entries = []; | |
for (let i = 0; i < timeCells.length; i++) { | |
const dateText = dateCells[i].textContent.trim().toLowerCase(); | |
if (dateText !== todayStr.toLowerCase()) continue; // Skip entries not for today | |
const timeText = timeCells[i].textContent.trim(); | |
const accountText = accountCells[i].textContent.trim(); | |
if (!timeText || !accountText) { | |
log(`Incomplete entry at index ${i}: time="${timeText}", account="${accountText}"`); | |
continue; // Skip incomplete entries | |
} | |
const time = parseTime(timeText); | |
entries.push({ time, type: accountText }); | |
} | |
log(`Parsed ${entries.length} entries for today (${todayStr}).`); | |
// Sort entries by time | |
entries.sort((a, b) => a.time - b.time); | |
// Calculate total work time in seconds | |
const totalSeconds = calculateTotalWorkTime(entries); | |
timeDisplay.textContent = formatTime(totalSeconds); | |
log(`Total Work Time updated: ${formatTime(totalSeconds)}`); | |
} | |
// Helper function to get the last account type | |
function getLastAccountType() { | |
const accountCells = document.querySelectorAll('.rwt-cell.ACCOUNT'); | |
if (accountCells.length === 0) return null; | |
return accountCells[accountCells.length - 1].textContent.trim(); | |
} | |
// Helper function to determine if the account type indicates currently working | |
function isWorkingType(accountType) { | |
if (!accountType) return false; | |
const workingTypes = [ENTRADA, TELETRABAJO]; | |
return workingTypes.includes(accountType.toLowerCase()); | |
} | |
// Main initialization function | |
function init() { | |
log('Initializing Work Time Total Counter script.'); | |
const timeDisplay = insertTotalWorkTimeDisplay(); | |
if (!timeDisplay) { | |
log('Failed to insert Total Work Time display. Exiting script.'); | |
return; | |
} | |
// Function to perform the update | |
const performUpdate = () => { | |
try { | |
updateTotalWorkTime(timeDisplay); | |
} catch (error) { | |
log(`Error during update: ${error}`); | |
} | |
}; | |
// Initial update | |
performUpdate(); | |
// Set up a MutationObserver to watch for changes in the log entries | |
const targetNode = document.body; | |
const config = { childList: true, subtree: true }; | |
const callback = function(mutationsList, observer) { | |
// Debounce updates to avoid excessive processing | |
if (updateTimeout) clearTimeout(updateTimeout); | |
updateTimeout = setTimeout(() => { | |
log('DOM mutation detected. Updating Total Work Time.'); | |
performUpdate(); | |
}, 500); | |
}; | |
let updateTimeout = null; | |
const observer = new MutationObserver(callback); | |
observer.observe(targetNode, config); | |
log('MutationObserver set up to monitor DOM changes.'); | |
// Additionally, set an interval to update every second if currently working | |
setInterval(() => { | |
const lastAccount = getLastAccountType(); | |
if (isWorkingType(lastAccount)) { | |
performUpdate(); | |
log('Currently working. Total Work Time updated.'); | |
} | |
}, 1000); | |
log('Initialization complete.'); | |
} | |
// Function to determine if the current page is the main app (not the login) | |
function isMainAppPage() { | |
// Implement a check to ensure the script runs only on the main app page | |
// For example, check for the presence of the toolbar or other unique elements | |
const toolbar = document.querySelector('.ToolBar.toolbar'); | |
// You can add more specific checks if needed | |
if (toolbar && toolbar.offsetParent !== null) { | |
log('Main app page detected.'); | |
return true; | |
} else { | |
log('Main app page not detected. Possibly on login page.'); | |
return false; | |
} | |
} | |
// Improved initialization method using MutationObserver to wait for the toolbar | |
function waitForToolbar() { | |
log('Waiting for toolbar to appear...'); | |
const observer = new MutationObserver((mutations, obs) => { | |
if (isMainAppPage()) { | |
log('Toolbar found. Starting initialization.'); | |
init(); | |
obs.disconnect(); | |
} | |
}); | |
observer.observe(document.body, { childList: true, subtree: true }); | |
// Timeout after 30 seconds to prevent infinite waiting | |
setTimeout(() => { | |
log('Timeout waiting for toolbar. Stopping observer.'); | |
observer.disconnect(); | |
}, 30000); | |
} | |
// Start the script | |
if (isMainAppPage()) { | |
init(); | |
} else { | |
waitForToolbar(); | |
} | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment