Skip to content

Instantly share code, notes, and snippets.

@okkdev
Last active January 22, 2026 10:04
Show Gist options
  • Select an option

  • Save okkdev/3499d3ead23be42933ff12d5e4b81204 to your computer and use it in GitHub Desktop.

Select an option

Save okkdev/3499d3ead23be42933ff12d5e4b81204 to your computer and use it in GitHub Desktop.
MyAbacus Better Time Display
// ==UserScript==
// @name Better Time Display
// @namespace Violentmonkey Scripts
// @match https://abaprod01.sympasol.com/portal/myabacus/proj_inandout*
// @grant none
// @version 3.0
// @author js
// @run-at document-end
// ==/UserScript==
;(function () {
"use strict"
const selectors = [
".hours-container > .va-label > .va-label-text",
".total-label > .va-label-text",
'[movie-id="id_pnl_workingTime"] .va-label-text',
'[movie-id="id_pnl_overTime"] .va-label-text',
]
let baseTotal = null
function convertHours() {
document.querySelectorAll(selectors.join(", ")).forEach((el) => {
const match = el.textContent.trim().match(/^(-?)(\d+),(\d+)( Std)?$/)
if (!match) return
const negative = match[1] === "-"
const hours = parseInt(match[2], 10)
const minutes = Math.round(parseFloat("0." + match[3]) * 60)
if (el.closest(".total-label")) {
baseTotal = hours * 60 + minutes
}
const sign = negative ? "-" : ""
el.textContent = `${sign}${hours}h ${minutes}m`
})
}
function updateLiveHours() {
const toInputs = [
...document.querySelectorAll('[id^="id_history_to_"] input'),
]
const toInput = toInputs.find((el) => el.value.trim() === "")
if (!toInput) return
const row = toInput.closest("vaadin-horizontal-layout")
const fromInput = row.querySelector('[id^="id_history_from_"] input')
const hoursLabel = row.querySelector(".hours-container .va-label-text")
const totalLabel = document.querySelector(".total-label > .va-label-text")
if (!fromInput || fromInput.value.trim() === "") return
const fromMatch = fromInput.value.match(/(\d{1,2})\D(\d{2})/)
if (!fromMatch) return
const fromMinutes =
parseInt(fromMatch[1], 10) * 60 + parseInt(fromMatch[2], 10)
const now = new Date()
const diffMinutes = now.getHours() * 60 + now.getMinutes() - fromMinutes
if (diffMinutes < 0) return
hoursLabel.textContent = `${Math.floor(diffMinutes / 60)}h ${diffMinutes % 60}m`
if (totalLabel && baseTotal !== null) {
const newTotal = baseTotal + diffMinutes
totalLabel.textContent = `${Math.floor(newTotal / 60)}h ${newTotal % 60}m`
}
}
function addNowButton() {
const inputSelectors = [
'[id^="id_history_from_"] input',
'[id^="id_history_to_"] input',
]
inputSelectors.forEach((selector) => {
const inputs = [...document.querySelectorAll(selector)]
const emptyInput = inputs.find((el) => el.value.trim() === "")
if (!emptyInput) return
const vaadinField = emptyInput.closest("vaadin-text-field")
if (!vaadinField) return
if (vaadinField.parentElement.querySelector(".now-btn")) return
const btn = document.createElement("button")
btn.textContent = "Now"
btn.className = "now-btn va-button-secondary"
btn.style.cssText = `
position: absolute;
transform: translate(-105%, 25%);
`
btn.addEventListener("click", (e) => {
e.preventDefault()
const now = new Date()
const hours = String(now.getHours()).padStart(2, "0")
const minutes = String(now.getMinutes()).padStart(2, "0")
emptyInput.value = `${hours}:${minutes}`
emptyInput.dispatchEvent(new Event("input", { bubbles: true }))
emptyInput.dispatchEvent(new Event("change", { bubbles: true }))
btn.remove()
})
vaadinField.insertAdjacentElement("afterend", btn)
})
}
function setupEnterToSave() {
const inOut = document.querySelector('#inOut')
if (!inOut || inOut.dataset.enterListenerAdded) return
inOut.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && e.target.matches('input')) {
e.preventDefault()
this.querySelector('[movie-id="save"]')?.click()
}
})
inOut.dataset.enterListenerAdded = 'true'
}
convertHours()
updateLiveHours()
addNowButton()
setupEnterToSave()
setInterval(() => {
convertHours()
updateLiveHours()
addNowButton()
setupEnterToSave()
}, 1000)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment