Created
November 18, 2023 23:23
-
-
Save K4CZP3R/729d89ac6af3ed966e6bce0220f4aca4 to your computer and use it in GitHub Desktop.
Scriptable widget which shows how much you will get on your payday using Timeular.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const CONFIG = { | |
url: "https://api.timeular.com/api/v3/", | |
token: "", | |
loanPerHour: 69, | |
ignoreActivities: ["817556"], // Like Break activity | |
plannedActivities: ["1463431"], // Like personal activity used for planning (not visible by employer) | |
}; | |
const units = { | |
year: 24 * 60 * 60 * 1000 * 365, | |
month: (24 * 60 * 60 * 1000 * 365) / 12, | |
day: 24 * 60 * 60 * 1000, | |
hour: 60 * 60 * 1000, | |
minute: 60 * 1000, | |
second: 1000, | |
}; | |
const rtf = new Intl.RelativeTimeFormat("en", { | |
numeric: "auto", | |
style: "long", | |
}); | |
const getRelativeTime = (d1, d2 = new Date()) => { | |
var elapsed = d1 - d2; | |
// "Math.abs" accounts for both "past" & "future" scenarios | |
for (var u in units) | |
if (Math.abs(elapsed) > units[u] || u == "second") | |
return rtf.format(Math.round(elapsed / units[u]), u); | |
}; | |
function countTotalDuration(entries) { | |
let totalDurationInMinutes = 0; | |
for (const entry of entries) { | |
const start = entry["duration"]["startedAt"]; | |
const end = entry["duration"]["stoppedAt"]; | |
const duration = Math.round((new Date(end) - new Date(start)) / 1000 / 60); | |
totalDurationInMinutes += duration; | |
} | |
return totalDurationInMinutes; | |
} | |
function getCurrentPeriod(future = 0) { | |
// Period starts on the 21st of the current month and ends on the 20th of the next month | |
const today = new Date(); | |
const currentMonth = today.getMonth() + future; | |
const currentYear = today.getFullYear(); | |
const currentDay = today.getDate(); | |
const periodStart = new Date(currentYear, currentMonth, 21); | |
const periodEnd = new Date(currentYear, currentMonth + 1, 21); | |
if (currentDay < 21) { | |
periodStart.setMonth(currentMonth - 1); | |
periodEnd.setMonth(currentMonth); | |
} | |
return { periodStart, periodEnd }; | |
} | |
async function getTimeEntries(from, to) { | |
// Remove Z from the end of the string | |
const fromString = from.toISOString().slice(0, -1); | |
const toString = to.toISOString().slice(0, -1); | |
let req = new Request(`${CONFIG.url}time-entries/${fromString}/${toString}`); | |
req.headers = { | |
"Content-Type": "application/json", | |
Authorization: `Bearer ${CONFIG.token}`, | |
}; | |
const entries = await req.loadJSON(); | |
return entries["timeEntries"].filter( | |
(entry) => !CONFIG.ignoreActivities.includes(entry["activityId"]) | |
); | |
} | |
async function createWidget() { | |
let widget = new ListWidget(); // | |
let stack = widget.addStack(); | |
stack.layoutVertically(); | |
const currentPeriod = getCurrentPeriod(); | |
const nextPeriod = getCurrentPeriod(1); | |
const currentEntries = await getTimeEntries( | |
currentPeriod.periodStart, | |
currentPeriod.periodEnd | |
); | |
const nextEntries = await getTimeEntries( | |
nextPeriod.periodStart, | |
nextPeriod.periodEnd | |
); | |
const curPlannedDuration = countTotalDuration(currentEntries); | |
const curRealDuration = countTotalDuration( | |
currentEntries.filter( | |
(entry) => !CONFIG.plannedActivities.includes(entry["activityId"]) | |
) | |
); | |
const nextPlannedDuration = countTotalDuration(nextEntries); | |
const nextRealDuration = countTotalDuration( | |
nextEntries.filter( | |
(entry) => !CONFIG.plannedActivities.includes(entry["activityId"]) | |
) | |
); | |
const curPlannedLoan = (curPlannedDuration / 60) * CONFIG.loanPerHour; | |
const curRealLoan = (curRealDuration / 60) * CONFIG.loanPerHour; | |
const nextPlannedLoan = (nextPlannedDuration / 60) * CONFIG.loanPerHour; | |
const nextRealLoan = (nextRealDuration / 60) * CONFIG.loanPerHour; | |
let nowText = ""; | |
if (curRealLoan.toFixed() === curPlannedLoan.toFixed()) { | |
nowText = `Now: ${curRealLoan.toFixed()}€`; | |
} else { | |
nowText = `Now: ${curRealLoan.toFixed()}€ out of ${curPlannedLoan.toFixed()}€`; | |
} | |
let now = widget.addText(nowText); | |
now.font = Font.systemFont(12); | |
let nextText = ""; | |
if (nextRealLoan <= 0) { | |
nextText = `Next: ${nextPlannedLoan.toFixed()}€`; | |
} else { | |
nextText = `Next: ${nextRealLoan.toFixed()}€ out of ${nextPlannedLoan.toFixed()}€`; | |
} | |
let next = widget.addText(nextText); | |
next.font = Font.systemFont(12); | |
let text3 = widget.addText( | |
`Ends in ${getRelativeTime(currentPeriod.periodEnd)}` | |
); | |
text3.font = Font.systemFont(12); | |
Script.setWidget(widget); | |
Script.complete(); | |
} | |
createWidget().catch((e) => { | |
console.error(e); | |
Script.complete(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment