Skip to content

Instantly share code, notes, and snippets.

@AitorAstorga
Created November 5, 2024 18:49
Show Gist options
  • Save AitorAstorga/dba22f8be294c3f36e28830259670053 to your computer and use it in GitHub Desktop.
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.
// ==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