Show weekly effective hours in the Keka dashboard
// ==UserScript==
// @name Keka Add-ons
// @namespace https://sundew.keka.com/
// @version 1.0.2
// @description Show weekly effective hours in the Keka dashboard
// @author Riju Ghosh
// @match https://sundew.keka.com/
// @icon https://www.google.com/s2/favicons?sz=64&domain=keka.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
function toHumanHours(hours) {
let h = Math.floor(hours);
let m = Math.round((hours - h) * 60);
if (m === 60) { h += 1; m = 0; }
return `${h}h ${m}m`;
}
function groupByWeekSunSat(data) {
const weeks = {};
data.forEach(item => {
const date = new Date(item.attendanceDate);
// find Sunday of that week
const start = new Date(date);
start.setUTCDate(date.getUTCDate() - date.getUTCDay());
// find Saturday of that week
const end = new Date(start);
end.setUTCDate(start.getUTCDate() + 6);
const key = `${start.toISOString().split('T')[0]}_${end.toISOString().split('T')[0]}`;
if (!weeks[key]) {
weeks[key] = {
break: 0,
effective: 0,
gross: 0,
start: start.toISOString().split('T')[0],
end: end.toISOString().split('T')[0],
days: []
};
}
weeks[key].break += item.totalBreakDuration;
weeks[key].effective += item.totalEffectiveHours;
weeks[key].gross += item.totalGrossHours;
weeks[key].days.push(item.attendanceDate);
});
return Object.values(weeks).map(totals => ({
start: totals.start,
end: totals.end,
totalBreakHours: totals.break,
totalEffectiveHours: totals.effective,
totalGrossHours: totals.gross,
breakHHMM: toHumanHours(totals.break),
effectiveHHMM: toHumanHours(totals.effective),
grossHHMM: toHumanHours(totals.gross),
days: totals.days
}));
}
const open = XMLHttpRequest.prototype.open;
const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
this._url = url;
return open.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
this.addEventListener("load", () => {
try {
if (this._url.includes('attendance/summary')) {
let data;
try {
data = JSON.parse(this.responseText);
console.log("Parsed JSON:", data);
} catch {
console.log("Not valid JSON, raw response:", this.responseText);
}
const payload = data.data;
const weeklyTotals = groupByWeekSunSat(payload);
console.log('Weekly Totals', weeklyTotals);
const el = document.querySelector('employee-attendance-list-view');
if (!el) {
console.log('employee-attendance-list-view missing from DOM');
}
let output = `<div class="employee-attendance-weekly my-10">`;
output += `<div class="card clear-margin">`;
// Header
output += `<div class="card-header py-8 d-flex">
<label class="text-label w-250">Start Date</label>
<label class="text-label w-250">End Date</label>
<label class="text-label w-250">Total Effective Hours</label>
<label class="text-label w-250">Total Break Hours</label>
<label class="text-label w-250">Total Gross Hours</label>
<label class="text-label w-250">Check</label>
</div>`;
// Body
output += `<div class="card-body clear-padding">`;
weeklyTotals.reverse().forEach(wt => {
output += `<div class="attendance-logs-row">
<div class="d-flex align-items-center px-16 py-12 on-hover border-bottom">
<div class="w-250">${wt.start}</div>
<div class="w-250">${wt.end}</div>
<div class="w-250">${wt.effectiveHHMM}</div>
<div class="w-250">${wt.breakHHMM}</div>
<div class="w-250">${wt.grossHHMM}</div>
</div>
</div>`;
});
output += `</div>`;
output += `</div></div>`;
const card = el.querySelector('.employee-attendance-weekly');
if (card) {
card.outerHTML = output;
} else {
el.insertAdjacentHTML('afterbegin', output);
}
}
} catch (e) {
console.error("Error in XHR interception:", e);
}
});
return send.apply(this, arguments);
};
})();