NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Factorial – Clock me in
// @description Automatically fills in the shifts in a calendar month
// @version 1.5
// @copyright 2019, jbrey
// @author jbrey
// @license MIT
// @namespace http://openuserjs.org/users/jbrey
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @match *://app.factorialhr.com/attendance/clock-in/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const workingHoursSeed = 8;
const workingHoursDeltaSeed = 0;
const timeForLunchDeltaSeed = 40;
const timeForLunchMin = 0.75;
const morningStartFromHour = 8.70;
const morningStartDeltaSeed = 15;
const morningEndFromHour = 14;
const morningEndDeltaSeed = 30;
function getRandomDelta(delta) {
const n = Math.floor(Math.random() * delta + 1);
return Math.ceil(n / 5) * 5;
}
function getTimeInMins(time) {
return Number.parseFloat((time / 60).toFixed(2));
}
function getTimeString(hour, min) {
return ('0' + hour).slice(-2) + ':' + ('0' + min).slice(-2);
}
function getMins(time) {
const mins = time - Math.floor(time);
return Math.floor(Number.parseFloat((mins * 60).toFixed(0)) / 5) * 5;
}
function getRandomTimings() {
const workingHoursDelta = getRandomDelta(workingHoursDeltaSeed);
const workingHoursDeltaMins = getTimeInMins(workingHoursDelta);
const workingHours = workingHoursSeed + workingHoursDeltaMins;
const timeForLunchDelta = getRandomDelta(timeForLunchDeltaSeed);
const timeForLunchDeltaMins = getTimeInMins(timeForLunchDelta);
const timeForLunch = timeForLunchMin + timeForLunchDeltaMins;
const morningStartDelta = getRandomDelta(morningStartDeltaSeed);
const morningStartDeltaMins = getTimeInMins(morningStartDelta);
const morningStart = morningStartFromHour + morningStartDeltaMins;
const morningEndDelta = getRandomDelta(morningEndDeltaSeed);
const morningEndDeltaMins = getTimeInMins(morningEndDelta);
const morningWorkingHours = Number.parseFloat(((morningEndFromHour + morningEndDeltaMins) -
(morningStartFromHour + morningStartDeltaMins)).toFixed(2));
const eveStart = morningEndFromHour + morningEndDeltaMins + timeForLunch;
const eveEnd = eveStart + (workingHours - morningWorkingHours);
const eveWorkingHours = Number.parseFloat((eveEnd - eveStart).toFixed(2));
const morningStartStr = getTimeString(Math.floor(morningStart), getMins(morningStart));
const morningEndStr = getTimeString(morningEndFromHour, morningEndDelta);
const eveStartStr = getTimeString(Math.floor(eveStart), getMins(eveStart));
const eveEndStr = getTimeString(Math.floor(eveEnd), getMins(eveEnd));
return [morningStartStr, morningEndStr, eveStartStr, eveEndStr]
}
// Calls the setter for an element in a component oriented framework like react
// and dispatchs the associated event
function setReactValue(element, value) {
const {
set: valueSetter
} = Object.getOwnPropertyDescriptor(element, 'value') || {}
const prototype = Object.getPrototypeOf(element)
const {
set: prototypeValueSetter
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {}
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value)
}
else if (valueSetter) {
valueSetter.call(element, value)
}
// dispatch the event to notify react
element.dispatchEvent(new Event('change', {
bubbles: true
}));
}
// Iterates through the list of days to add the shifts
function addShifts() {
const days = Array.from(document.querySelectorAll('[class^="tr__"]'));
// filter out bank holidays, vacations and weekends
const workDays = days.filter(day => {
return (/tbody/i.test(day.parentNode.tagName) &&
!/disabled/i.test(day.className) &&
!/holiday|vacaciones|permisos retribuidos/i.test(day.innerText));
});
// add shifts where necessary
workDays.forEach(workDay => {
// get button to add a shift
const addShiftButton = workDay.querySelector('button');
// check how many shifts already exist
const inputFields = workDay.querySelectorAll('input');
// if there are less than 2 shifts, add one
if ((addShiftButton) && (inputFields.length < 4)) {
workDay.querySelector('button').click();
}
});
// add values to the shifts
workDays.forEach(workDay => {
// get inputs for a day
const inputFields = workDay.querySelectorAll('input');
const dailySchedule = getRandomTimings();
// fill in shifts
let index = 0;
inputFields.forEach(inputField => {
// enabled?
if ((!inputField.disabled) && (!inputField.value)) {
setReactValue(inputField, dailySchedule[index]);
// TODO: find out why the value must be set twice for react to pick it up... !!!
setReactValue(inputField, dailySchedule[index]);
}
// find the shift
index = (index >= 3) ? 0 : index + 1;
});
});
workDays.forEach(workDay => {
// get all buttons for a workday
const buttons = workDay.querySelectorAll('button');
// cycle through them
buttons.forEach(button => {
// is this the submit button and is it enabled?
if ((button.innerText) &&
(/submit|guardar/i.test(button.innerText)) &&
(!button.disabled)) {
button.click();
}
});
});
}
// Injects into the page a button that calls the logic of this script
function setButton(button) {
if ((button[0].innerText) &&
(/clock in|entrada/i.test(button[0].innerText))) {
const container = button[0].parentNode.parentNode;
const newContainer = button[0].parentNode.cloneNode();
const newButton = button[0].cloneNode();
newButton.insertAdjacentHTML(
'afterbegin',
`<div class="_2U_uz_2-jO"><div class="box___nBFPS width_full___GXPW7 height_s32___1gzeu padding_x_s8___1Pgn7 padding_y_s4___1nJEZ border_radius_abs016___27iFP background_primary1000___YyQFE border_color_lighter000___JTTqK border_width_s2___wC6c4 border_style_solid___1kBV3"><div class="box___nBFPS padding_x_s2___BwA3m flex_direction_row___1rGE2 align_items_center___MjjdX"><div class="box___nBFPS padding_x_s4___2sUGX overflow_x_hidden___vhjK1"><span class="text___2TOkD size_200___2HuYx weight_semibold___2R9kP color_grey000___2seTn"><span class="_29A1e">Clock me in!</span>`
);
newButton.setAttribute('id', 'clockmein');
newButton.addEventListener("click", addShifts, false);
newContainer.appendChild(newButton);
container.appendChild(newContainer);
}
}
waitForKeyElements("button:contains('Entrada'), button:contains('Clock in')", setButton, false);
})();