|
// ==UserScript== |
|
// @name Autofill OneView Timesheet |
|
// @namespace http://tampermonkey.net/ |
|
// @version 1.1.3 |
|
// @description Help you save some time filling out your timesheet |
|
// @author mbruno-kr, scottw-kr, cmcculloh-kr |
|
// @match http://oneview.kroger.com/niku/nu |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=kroger.com |
|
// @grant none |
|
// ==/UserScript== |
|
|
|
(function () { |
|
("use strict"); |
|
|
|
const taskTypes = [ |
|
{ |
|
id: "work", |
|
description: "Analytics Enablement", |
|
guidance: "This is your primary billable category.", |
|
}, |
|
{ |
|
id: "plan", |
|
description: "OKR Discovery and Ceremonies", |
|
guidance: "Product activities for planning and nuturing the delivery of work.", |
|
}, |
|
{ |
|
id: "admin", |
|
description: "General Admin", |
|
guidance: |
|
"Reading organizational emails/newsletters, Installing new PC applications/updating PC, On-boarding activities, Resource Manager miscellaneous tasks, Town Halls, Kroger sponsored meetings, Benefits sessions, Company Webinars, 1:1, Interviewing, Quarterly/Annual review, Time spent volunteering during normal business hours (pending manager approval), Time spent performing community service during normal business hours (pending manager approval), United Way, Arts Wave, Jury Duty, Travel between locations during the workday for non-project meetings, Travel time to conferences", |
|
}, |
|
{ |
|
id: "train", |
|
description: "Non Project Training", |
|
guidance: |
|
"Receiving/Delivering training, Annual training (KnowMe), General skills training (PM, Java), Mentoring, Career development/coaching", |
|
}, |
|
{ |
|
id: "pto", |
|
description: "PTO/ Vacation/ Approved Absences", |
|
guidance: |
|
"Health & Wellness days, Vacation, Approve Absence, Personal days, Bereavement.", |
|
}, |
|
{ |
|
id: "hol", |
|
description: "Holiday", |
|
guidance: "Corporate Holidays", |
|
}, |
|
]; |
|
|
|
// Fill in your default week here. "A" will be "Auto" assigned any remaining hours, assuming an 8 hour day. |
|
const defaultWeek = { |
|
work: ["A", "A", "A", "A", "A"], |
|
plan: [1.0, 2.0, 2.0, 2.0, 2.0], |
|
admin: [2.0, 2, 2, 2, 1], |
|
train: [0, 0, 0, 0, 0], |
|
pto: [0, 0, 0, 0, 0], |
|
hol: [0, 0, 0, 0, 0], |
|
}; |
|
|
|
const expectedOutcome = { |
|
work: [3, 5.5, 5, 4.5, 0], |
|
plan: [5, 1, 0, 3, 1], |
|
admin: [0, 1.5, 1, 0.5, 1.5], |
|
train: [0, 0, 0, 0, 0], |
|
pto: [0, 0, 0, 0, 0], |
|
hol: [0, 0, 0, 0, 6], |
|
}; |
|
|
|
const appendGuidance = () => { |
|
taskTypes.forEach((taskType) => { |
|
const link = document.querySelector(`a[title*="${taskType.description}"]`); |
|
if (link) { |
|
link.innerHTML += ` - ${taskType.guidance}`; |
|
} |
|
}); |
|
} |
|
|
|
const totalForDayAcrossCategories = (week, day) => { |
|
let total = 0; |
|
Object.keys(week).forEach((category) => { |
|
console.log("week, category", category, day, week[category][day]); |
|
total += +week[category][day] > 0 ? +week[category][day] : 0; |
|
}); |
|
console.log("totalForDayAcrossCategories", total); |
|
return total; |
|
}; |
|
|
|
const doAutoCalculation = (week) => { |
|
const autoWeek = {}; |
|
Object.keys(week).forEach( |
|
(category, i) => { |
|
autoWeek[category] = []; |
|
for (let i = 0; i < week[category].length; i++) { |
|
autoWeek[category][i] = |
|
week[category][i] === "A" |
|
? 8 - totalForDayAcrossCategories(week, i) |
|
: week[category][i]; |
|
} |
|
return autoWeek[category]; |
|
} |
|
); |
|
return autoWeek; |
|
}; |
|
|
|
// UNIT TESTS |
|
// console.log("expectedOutcome", expectedOutcome); |
|
// console.assert(compareWeeks(finalizedWeek, expectedOutcome), "Weeks do not match"); |
|
// console.assert(totalForDayAcrossCategories(finalizedWeek, 0) === 5, "Total for day 0 is not 5"); |
|
// console.assert(false, "This is a test"); |
|
|
|
// const compareWeeks = (week1, week2) => { |
|
// let result = true; |
|
// Object.keys(week1).forEach((category) => { |
|
// for (let i = 0; i < week1[category].length; i++) { |
|
// if (week1[category][i] !== week2[category][i]) { |
|
// console.log( |
|
// "Weeks do not match at " + |
|
// category + |
|
// " " + |
|
// i + |
|
// " " + |
|
// week1[category][i] + |
|
// " " + |
|
// week2[category][i] |
|
// ); |
|
// result = false; |
|
// } |
|
// } |
|
// }); |
|
// return result; |
|
// }; |
|
|
|
function getDays() { |
|
let days = []; |
|
document.querySelectorAll('[data-columnid="day"]').forEach((e) => { |
|
let content = e.innerText; |
|
if (content.length > 0) { |
|
days.push(content.replace(/\n/g, " ").trim().replace(" ", ", ")); |
|
} |
|
}); |
|
return days; |
|
} |
|
|
|
const getCurrentValues = () => { |
|
let days = getDays(); |
|
let weekDays = days.slice(1, 6); |
|
|
|
// For each weekday, fill each task's values |
|
let weekValues = {}; |
|
taskTypes.forEach((task) => { |
|
weekValues[task.id] = []; |
|
}); |
|
weekDays.forEach((dayOfWeek) => { |
|
taskTypes.forEach((task) => { |
|
let day = document.querySelector( |
|
`input[alt*='${dayOfWeek}'][title*='${task.description}']` |
|
); |
|
weekValues[task.id].push(day.value); |
|
}); |
|
}); |
|
console.log('weekValues', weekValues) |
|
return weekValues; |
|
} |
|
|
|
const getWeekValues = () => { |
|
const mergedWeek = getCurrentValues(); |
|
const finalizedWeek = doAutoCalculation(mergedWeek); |
|
|
|
return finalizedWeek; |
|
}; |
|
|
|
const autoFillWithDefaultValues = () => { |
|
autoFill(defaultWeek); |
|
}; |
|
|
|
const autoFillWithOverrides = () => { |
|
const weekValues = getWeekValues(); |
|
autoFill(weekValues); |
|
}; |
|
|
|
function autoFill(weekvalues) { |
|
let days = getDays(); |
|
let weekDays = days.slice(1, 6); |
|
|
|
// For each weekday, fill each task's values |
|
weekDays.forEach((dayOfWeek) => { |
|
taskTypes.forEach((task) => { |
|
let day = document.querySelector( |
|
`input[alt*='${dayOfWeek}'][title*='${task.description}']` |
|
); |
|
if (!day) { |
|
const msg = `Cannot find activity "${task.description}" on ${dayOfWeek}`; |
|
alert(msg); |
|
throw Error(msg); |
|
} |
|
// Add weekvalues value for day & id |
|
const newValueForDay = weekvalues[task.id][weekDays.indexOf(dayOfWeek)]; |
|
day.value = newValueForDay; |
|
}); |
|
}); |
|
|
|
// submitForm("page", "timeadmin.saveTimesheet"); |
|
} |
|
|
|
function addAutofillButton() { |
|
let container = document.querySelector('div[class*="ppm_button_bar"]'); |
|
if (!container) { |
|
setTimeout(main, 200); |
|
return; |
|
} |
|
|
|
if (isEntryPage()) { |
|
// if there is no button |
|
if (!container.querySelector('button[id="auto-fill"]')) { |
|
let button = document.createElement("button"); |
|
button.id = "auto-fill"; |
|
button.innerText = "Auto Fill"; |
|
button.className = "ppm_button"; |
|
button.style = "box-shadow: 0 0 15px 3px rgb(182 148 92, 1);"; |
|
button.onclick = autoFillWithOverrides; |
|
container.appendChild(button); |
|
} else { |
|
const input = document.querySelectorAll('input[name="actuals_hours"]')[1]; |
|
// if the form is still empty |
|
if (input && input.value === "") { |
|
const xpath = "//button[text()='Auto Fill']"; |
|
const matchingElement = document.evaluate( |
|
xpath, |
|
document, |
|
null, |
|
XPathResult.FIRST_ORDERED_NODE_TYPE, |
|
null |
|
).singleNodeValue; |
|
matchingElement.click(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
const isEntryPage = () => document.querySelectorAll('img[title="Delete"]')[0] !== undefined; |
|
|
|
function main() { |
|
const onTimeSheetPage = window.location.hash.indexOf("editTimesheet") !== -1; |
|
|
|
if (onTimeSheetPage) { |
|
setTimeout(() => { |
|
console.log("AutoFill Running"); |
|
setTimeout(appendGuidance, 100); |
|
setTimeout(addAutofillButton, 100); |
|
// autofill with default values |
|
setTimeout(autoFillWithDefaultValues, 100); |
|
}, 500); |
|
} |
|
} |
|
|
|
main(); |
|
})(); |