Skip to content

Instantly share code, notes, and snippets.

@leonardAlbert
Last active June 28, 2024 21:39
Show Gist options
  • Save leonardAlbert/2ce80fc3cadb1da5c9efcc9a11c799da to your computer and use it in GitHub Desktop.
Save leonardAlbert/2ce80fc3cadb1da5c9efcc9a11c799da to your computer and use it in GitHub Desktop.
Script for updating the consumed/paused hours in Time Control.
// ==UserScript==
// @name Custom Timesheet Script
// @namespace custom-timesheet-script
// @version 0.1
// @description Script for updating the consumed/paused hours in Time Control.
// @author Leonard A M Pedro
// @author Randler Ferraz Leal
// @grant none
//
// @note Instructions:
// @note * Paste the script into the browser console to apply the changes.
// @note ** For best use, install the chrome extension for the timesheet domain.
// @chrome extension https://chrome.google.com/webstore/detail/user-javascript-and-css/nbhcbdghjpllgmfilhnhkllmkecfmpld
// ==/UserScript==
(function() {
'use strict';
const appV = {
//
enabled: true,
//
debugMode: false,
//
onLoad: false,
// Session Key
sessionStorageKey: 'appV-custom-timesheet-script',
// Preview Values
previewValues: {
label: false,
time: false,
},
//
working: {
goals: 8,
status: false,
totalTime: '00:00',
totalTimestamp: 0,
},
//
breaking: {
goals: 1,
status: false,
totalTime: '00:00',
totalTimestamp: 0,
},
//
timeEntries: [{
start: false,
end: false,
}, ],
//
api: {
domain: 'https://' + window.location.hostname,
token: false,
urls: {
day: '/employee-days/day',
daily: '/employee-days/daily',
profile: '/employee/current-profile',
delay: '/tasks/delay-average',
period: '/period',
entries: '/day-entries',
},
},
news: {
api: {
domain: 'https://www.tabnews.com.br/api/v1',
token: false,
urls: {
last: '/contents?page=1&per_page=15&strategy=relevant',
},
},
},
//
notifications: {
endWorkDay: {
title: 'Controle de Horas - Fim do Expediente',
body: {
body: 'Hora de marcar o ponto!',
},
alert: false,
snooze: false,
snoozeMinutes: 5,
},
endBreakTime: {
title: 'Controle de Horas - Pausa',
body: {
body: 'Hora de marcar o ponto!',
},
alert: false,
snooze: false,
snoozeMinutes: 1,
},
},
// Run tasks.
run: function() {
appV.scriptApiSettings();
appV.scriptViewHome();
appV.scriptAdjustPageStyle();
appV.addCardTabNews();
},
// ============================================================
// === SESSION STORAGE
// ============================================================
//
getSessionValue: function(pointer) {
let key = appV.sessionStorageKey;
const storedData = sessionStorage.getItem(key);
if (!storedData) return undefined;
try {
const parsedData = JSON.parse(storedData);
const keys = pointer.split('.');
let value = parsedData;
for (let key of keys) {
value = value[key];
if (value === undefined) return undefined;
}
return value;
} catch (error) {
console.error('Error parsing JSON from sessionStorage:', error);
return undefined;
}
},
//
setSessionValue: function(pointer, newValue) {
let key = appV.sessionStorageKey;
let storedData = sessionStorage.getItem(key);
let dataToUpdate = {};
if (storedData) {
try {
dataToUpdate = JSON.parse(storedData);
} catch (error) {
console.error(
'Error parsing JSON from sessionStorage:',
error
);
return;
}
}
let currentLevel = dataToUpdate;
const keys = pointer.split('.');
for (let i = 0; i < keys.length - 1; i++) {
if (!currentLevel[keys[i]]) {
currentLevel[keys[i]] = {};
}
currentLevel = currentLevel[keys[i]];
}
currentLevel[keys[keys.length - 1]] = newValue;
sessionStorage.setItem(key, JSON.stringify(dataToUpdate));
},
// ============================================================
// === SCRIPT API SETTINGS
// ============================================================
//
scriptApiSettings: function() {
// Loop schedule - 1 second.
setInterval(function() {
appV.getApiToken();
}, 1000);
},
getApiToken: function() {
// Check if it already exists.
if (appV.api.token != false) return;
for (let i = 0; i < sessionStorage.length; i++) {
let key = sessionStorage.key(i);
let value = sessionStorage.getItem(key);
try {
let jsonValue = JSON.parse(value);
if (
jsonValue.tokenType === 'Bearer' &&
jsonValue.target &&
jsonValue.target.startsWith('api://')
) {
appV.api.token = jsonValue.secret;
}
} catch (e) {
continue;
}
}
if (appV.debugMode) console.log(appV.api.token); // @DEBUG
},
// ============================================================
// === SCRIPT ADJUST PAGE STYLE
// ============================================================
//
scriptAdjustPageStyle: function() {
// Loop schedule - 1 millisecond.
setInterval(function() {
appV.adjustStyle();
appV.onLoad =
document.querySelector('.entry-container .entry-loader') !=
null;
}, 1);
},
// Adjust Page Style
adjustStyle: function() {
// Background color
document
.querySelectorAll('.mat-drawer-container')
.forEach(function(element, index) {
element.style.backgroundColor = '#383838';
});
// Link color
document
.querySelectorAll(
'.main-container .mat-tab-link, .main-container .mat-tab-link.active-link'
)
.forEach(function(element, index) {
element.style.color = '#ffffff';
});
},
// ============================================================
// === SCRIPT VIEW HOME
// ============================================================
// Run script on home page.
scriptViewHome: function() {
// Enable Notifications.
Notification.requestPermission();
// Loop schedule - 1 second.
setInterval(function() {
// Run only home page.
if (!/^\/$/.test(window.location.pathname)) return;
if (appV.onLoad) {
appV.previewValues.label = false;
appV.previewValues.time = false;
return;
}
if (appV.debugMode) console.log('DEBUG: ', appV); // @DEBUG
appV.savePreviewValues();
appV.addRemaingAppointmentToday();
appV.addOnOffButton();
if (!appV.enabled) return;
appV.getTimeEntries();
appV.updateTotalHoursLabel();
appV.updateHoursWorked();
appV.notificationEndWorkDay();
appV.updateHoursBreak();
appV.notificationBreakTime();
}, 1000);
// Loop schedule - 1 minute.
setInterval(function() {
// Run only home page.
if (!/^\/$/.test(window.location.pathname)) return;
appV.reloadPage();
}, 60000);
},
// Reload pate at midnight.
reloadPage: function() {
let dateNow = new Date();
if (dateNow.getHours() == 0 && dateNow.getMinutes() == 0)
location.reload();
},
// Save preview value of Total.
savePreviewValues: function() {
if (appV.previewValues.time && appV.previewValues.title) return;
appV.previewValues.time = document.querySelector(
'.entry-container .entry-actions .container-hours-label .hours-label'
).innerHTML;
appV.previewValues.title = document.querySelector(
'.entry-container .entry-actions .total-hours .hours-title'
).innerHTML;
},
//
addRemaingAppointmentToday: function() {
if (!appV.enabled) {
if (document.getElementById('remaining-hours')) {
document.getElementById('remaining-hours').remove();
}
return;
}
let totalAppointmentToday = document.querySelector(
'.amount-hours > .value'
).innerText;
let hours = parseInt(totalAppointmentToday.split(':')[0]);
let minutes = parseInt(totalAppointmentToday.split(':')[1]);
// Calculating remaining hours and minutes
let remainingHoursAppointment = 8 - hours;
let remainingMinutesAppointment = 60 - minutes;
if (remainingMinutesAppointment === 60) {
remainingMinutesAppointment = 0;
} else {
remainingHoursAppointment -= 1;
}
// Adjusting if minutes are negative
if (remainingMinutesAppointment < 0) {
remainingMinutesAppointment += 60;
remainingHoursAppointment -= 1;
}
// Ensuring that the remainingHoursAppointment value is at most 8
if (remainingHoursAppointment > 8) {
remainingHoursAppointment = 8;
remainingMinutesAppointment = 0;
}
// Ensuring that the minutes format is always two digits
let remainingMinutesString =
remainingMinutesAppointment < 10 ?
'0' + remainingMinutesAppointment :
remainingMinutesAppointment;
let remainingHoursString =
remainingHoursAppointment < 10 ?
'0' + remainingHoursAppointment :
remainingHoursAppointment;
// Adjusting if remainingHoursAppointment is less than 0
if (remainingHoursAppointment < 0) {
remainingHoursAppointment = 0;
remainingMinutesAppointment = 0;
remainingMinutesString = '00';
remainingHoursString = '00';
}
let remainingTimeString = `${remainingHoursString}:${remainingMinutesString}`;
let contentRemaining = document.getElementById('remaining-hours');
if (!contentRemaining) {
let newHtml = document.createElement('div');
newHtml.className = 'amount-hours';
newHtml.id = 'remaining-hours';
newHtml.style = `align-items: center;
background-color: #fceeda;
border-radius: 8px;
box-sizing: border-box;
display: flex;
height: 40px;
justify-content: space-between;
margin: 16px 0 0 10px;
padding: 12px 16px 12px 10px;
width: 240px;`;
newHtml.innerHTML = `<br />
<span class="label"
style="color: #ab5710;
font-size: 14px;
font-weight: 600;">
Restantes para apontar:
</span>
<span id="total-remaining"
style="font-size: 16px;font-weight: 600;color: #a75a0a;"
class="value">
${remainingTimeString}
</span>`;
let container =
document.querySelector('.amount-hours').parentNode;
container.appendChild(newHtml);
} else {
document.getElementById('total-remaining').innerText =
remainingTimeString;
}
},
// Add Card with news.
addCardTabNews: function() {
// Loop schedule - 1 millisecond.
setInterval(function() {
let html = `<app-generic-card size="large" id="card-tab-news">
<mat-card class="mat-card mat-focus-indicator mat-generic-card large">
<label class="title">📰 Tech Notícias: &nbsp;</label><em id="content-news">carregando ...</em>
</mat-card>
</app-generic-card>`;
if (!document.getElementById('card-tab-news') && document
.querySelectorAll('.main-content .card-section')[1]) {
document
.querySelectorAll('.main-content .card-section')[1]
.insertAdjacentHTML('afterbegin', html);
appV.populateTabNewsContent();
}
}, 1);
// appV.getContentNews();
// Loop schedule 30 minutes.
// setInterval(function () {
// appV.getContentNews();
// }, 1800000);
},
//
populateTabNewsContent: function() {
let currentIndex = 0;
const newsList = appV.getSessionValue('appV.news.content');
const newsContainer = document.getElementById("content-news");
const updateNews = function() {
if (typeof newsList === 'undefined')
return;
newsContainer.textContent = '<a target="_blank">' + newsList[currentIndex]["title"] + '</a>';
const linkElement = document.createElement('a');
linkElement.textContent = newsList[currentIndex]["title"];
linkElement.setAttribute('href', 'https://www.tabnews.com.br/' + newsList[currentIndex]["owner_username"] + '/' + newsList[currentIndex]["slug"]);
linkElement.setAttribute('target', '_blank');
linkElement.style.color = 'inherit';
// linkElement.style.textDecoration = 'none';
newsContainer.innerHTML = '';
newsContainer.appendChild(linkElement);
currentIndex = (currentIndex + 1) % newsList.length;
}
// Loop schedule 15 minutes.
setInterval(appV.getContentApiNews(true), 900000);
// Loop schedule 8 secconds.
setInterval(updateNews, 8000);
updateNews();
},
//
getContentApiNews: function(forceUpdate = false) {
let hasContent = appV.getSessionValue(
'appV.news.content');
if (typeof hasContent !== 'undefined' && forceUpdate == false)
return;
fetch(
appV.news.api.domain + appV.news.api.urls.last, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error('Error: ' + response.status);
}
})
.then((data) => {
try {
let content = JSON.parse(JSON.stringify(data));
appV.setSessionValue(
'appV.news.content', content);
if (appV.debugMode) console.log('getContentApiNews', content); // @DEBUG
} catch (e) {
console.error('Error invalid JSON:', e);
}
})
.catch((error) => console.error('Error: ', error));
},
// Add On-Off button on page.
addOnOffButton: function() {
if (
document.querySelector(
'.entry-container .entry-actions .mat-slide-toggle-bar #appV-enabled'
)
)
return;
let classChecked = appV.enabled ? 'mat-checked' : '';
let html = `<span id="appV-buttom" class="mat-slide-toggle-label mat-slide-toggle ${classChecked} mat-slide-toggle-bar">
<input type="checkbox" role="switch" class="mat-slide-toggle-input cdk-visually-hidden" id="appV-enabled" tabindex="0" aria-checked="${appV.enabled}">
<span class="mat-slide-toggle-thumb-container">
<span class="mat-slide-toggle-thumb"></span>
<span mat-ripple="" class="mat-ripple mat-slide-toggle-ripple mat-focus-indicator">
<span class="mat-ripple-element mat-slide-toggle-persistent-ripple"></span>
</span>
</span>
</span>`;
document
.querySelector('.entry-container .entry-actions')
.insertAdjacentHTML('afterbegin', html.concat('&nbsp;&nbsp;'));
document
.querySelector('.entry-container .entry-actions #appV-buttom')
.addEventListener('click', function(element, index) {
appV.enabled = !appV.enabled;
document.querySelector(
'.entry-container .entry-actions #appV-enabled'
).ariaChecked = appV.enabled;
document
.querySelector(
'.entry-container .entry-actions #appV-buttom'
)
.classList.remove('mat-checked');
if (appV.enabled) {
document
.querySelector(
'.entry-container .entry-actions #appV-buttom'
)
.classList.add('mat-checked');
} else {
document.querySelector(
'.entry-container .entry-actions .container-hours-label .hours-label'
).innerHTML = appV.previewValues.time;
document.querySelector(
'.entry-container .entry-actions .total-hours .hours-title'
).innerHTML = appV.previewValues.title;
}
});
return;
},
// Populate entries time
getTimeEntries: function() {
appV.timeEntries = [{
start: false,
end: false,
}, ];
document
.querySelectorAll('.entry .entries-container')
.forEach(function(element, index) {
let startTime = element.querySelector(
'.entry-time-container .entry-time-label'
).textContent;
let endTime = element.querySelector(
'.exit-time-container .exit-time-label'
).textContent;
let entrie = {
start: false,
end: false,
};
if (startTime.match(/([0-9]{2}\:[0-9]{2})/gm) != null) {
entrie.start = startTime;
}
if (endTime.match(/([0-9]{2}\:[0-9]{2})/gm) != null) {
entrie.end = endTime;
}
appV.timeEntries[index] = entrie;
});
// Update status.
appV.working.status = false;
appV.breaking.status = false;
let entriesLength = appV.timeEntries.length;
if (
(entriesLength == 1 &&
appV.timeEntries[0].start &&
!appV.timeEntries[0].end) ||
(entriesLength > 1 &&
appV.timeEntries[entriesLength - 1].start &&
!appV.timeEntries[entriesLength - 1].end)
) {
appV.working.status = true;
appV.breaking.status = false;
} else if (
entriesLength == 1 &&
appV.timeEntries[0].start &&
appV.timeEntries[0].end
) {
appV.working.status = false;
appV.breaking.status = true;
}
return;
},
// Send notification break time.
notificationBreakTime: function() {
let hoursBreakTimestamp = Math.floor(appV.breaking.goals * 3600000);
if (
appV.breaking.status &&
appV.breaking.totalTimestamp >= hoursBreakTimestamp
) {
if (!appV.notifications.endBreakTime.alert) {
appV.notifications.endBreakTime.alert = true;
new Notification(
appV.notifications.endBreakTime.title,
appV.notifications.endBreakTime.body
);
} else if (!appV.notifications.endBreakTime.snooze) {
appV.notifications.endBreakTime.snooze = true;
setTimeout(function() {
appV.notifications.endBreakTime.snooze = false;
new Notification(
appV.notifications.endBreakTime.title,
appV.notifications.endBreakTime.body
);
}, appV.notifications.endBreakTime.snoozeMinutes * 60000);
}
}
return;
},
// Send notification end work day.
notificationEndWorkDay: function() {
let hoursDailyGoalTimestamp = Math.floor(
appV.working.goals * 3600000
);
if (
appV.working.status &&
appV.working.totalTimestamp >= hoursDailyGoalTimestamp
) {
if (!appV.notifications.endWorkDay.alert) {
appV.notifications.endWorkDay.alert = true;
new Notification(
appV.notifications.endWorkDay.title,
appV.notifications.endWorkDay.body
);
} else if (!appV.notifications.endWorkDay.snooze) {
appV.notifications.endWorkDay.snooze = true;
setTimeout(function() {
appV.notifications.endWorkDay.snooze = false;
new Notification(
appV.notifications.endWorkDay.title,
appV.notifications.endWorkDay.body
);
}, appV.notifications.endWorkDay.snoozeMinutes * 60000);
}
}
return;
},
// Update Label Total Worked.
updateTotalHoursLabel: function(label = 'Total', emogi = '&#128515') {
//
if (appV.working.status) {
label = 'Worked';
emogi = '&#128640';
} else if (appV.breaking.status) {
label = 'Paused';
emogi = '&#9749';
}
document.querySelector(
'.entry-container .entry-actions .total-hours .hours-title'
).innerHTML = ''.concat(emogi, ';&nbsp;&nbsp', label);
return;
},
// Update Hours Worked.
updateHoursWorked: function() {
appV.working.totalTimestamp = 0;
if (!appV.working.status) return;
appV.timeEntries.forEach(function(element, index) {
let startDate = new Date();
let endDate = new Date();
let startTime = element.start;
let endTime = element.end;
if (!startTime && !endTime) return;
if (!startTime) {
startTime = ''.concat(
startDate.getHours(),
':',
startDate.getMinutes()
);
}
if (!endTime) {
endTime = ''.concat(
endDate.getHours(),
':',
endDate.getMinutes()
);
}
startDate.setHours(startTime.split(':')[0]);
startDate.setMinutes(startTime.split(':')[1]);
endDate.setHours(endTime.split(':')[0]);
endDate.setMinutes(endTime.split(':')[1]);
appV.working.totalTimestamp += endDate - startDate;
});
let diffHours = Math.floor(
(appV.working.totalTimestamp % 86400000) / 3600000
);
let diffMinutes = Math.round(
((appV.working.totalTimestamp % 86400000) % 3600000) / 60000
);
appV.working.totalTime = ''.concat(
'0'.concat(diffHours).slice(-2),
':',
'0'.concat(diffMinutes).slice(-2)
);
document.querySelector(
'.entry-container .entry-actions .container-hours-label .hours-label'
).innerHTML = appV.working.totalTime;
return;
},
// Update Hours Paused.
updateHoursBreak: function() {
appV.breaking.totalTimestamp = 0;
if (!appV.breaking.status) return;
let entriesLength = appV.timeEntries.length;
let startDate = new Date();
let endDate = new Date();
let startTime = appV.timeEntries[entriesLength - 1].end;
let endTime = ''.concat(
endDate.getHours(),
':',
endDate.getMinutes()
);
startDate.setHours(startTime.split(':')[0]);
startDate.setMinutes(startTime.split(':')[1]);
endDate.setHours(endTime.split(':')[0]);
endDate.setMinutes(endTime.split(':')[1]);
appV.breaking.totalTimestamp += endDate - startDate;
let diffHours = Math.floor(
(appV.breaking.totalTimestamp % 86400000) / 3600000
);
let diffMinutes = Math.round(
((appV.breaking.totalTimestamp % 86400000) % 3600000) / 60000
);
appV.breaking.totalTime = ''.concat(
'0'.concat(diffHours).slice(-2),
':',
'0'.concat(diffMinutes).slice(-2)
);
document.querySelector(
'.entry-container .entry-actions .container-hours-label .hours-label'
).innerHTML = appV.breaking.totalTime;
return;
},
};
// Run script.
appV.run();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment