Skip to content

Instantly share code, notes, and snippets.

@K4CZP3R
Created November 18, 2023 23:23
Show Gist options
  • Save K4CZP3R/729d89ac6af3ed966e6bce0220f4aca4 to your computer and use it in GitHub Desktop.
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.
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