AtoZ Working time calculator

Working time calculator for AtoZ - English

  1. // ==UserScript==
  2. // @name AtoZ Working time calculator
  3. // @namespace https://atoz.amazon.work
  4. // @version 1.0
  5. // @description Working time calculator for AtoZ - English
  6. // @author @celvip
  7. // @match https://atoz.amazon.work/timecard*
  8. // @match https://atoz.amazon.work/schedule*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Helper functions for localStorage
  16. function setValue(key, value) {
  17. try {
  18. localStorage.setItem(key, JSON.stringify(value));
  19. } catch (e) {
  20. console.error('Error saving to localStorage:', e);
  21. }
  22. }
  23.  
  24. function getValue(key, defaultValue) {
  25. try {
  26. const value = localStorage.getItem(key);
  27. return value ? JSON.parse(value) : defaultValue;
  28. } catch (e) {
  29. console.error('Error reading from localStorage:', e);
  30. return defaultValue;
  31. }
  32. }
  33.  
  34. let breakTime = getValue('breakTime', 30);
  35. let workTime = getValue('workTime', 8);
  36. const FIXED_MAX_BREAK = 45; // Fixed break time for maximum working time
  37.  
  38. // Extended translations for different languages and variations
  39. const TRANSLATIONS = {
  40. IN: ['Einstempeln', 'Clock in', 'Punch in', 'In'],
  41. OUT: ['Ausstempeln', 'Clock out', 'Punch out', 'Out'],
  42. MISSING: ['Fehlende Stempelzeit', 'Missing punch', 'Missed Punch', 'Missed punch', '--:--']
  43. };
  44.  
  45. const COLORS = {
  46. GREEN: {
  47. bg: '#c6efce',
  48. text: '#006100'
  49. },
  50. YELLOW: {
  51. bg: '#ffeb9c',
  52. text: '#9c6500'
  53. },
  54. RED: {
  55. bg: '#ffc7ce',
  56. text: '#9c0006'
  57. }
  58. };
  59.  
  60. function findPunchTimes() {
  61. let punchInTime = null;
  62. let punchOutTime = null;
  63. let missingPunch = false;
  64.  
  65. console.log("Searching for punch times...");
  66.  
  67. // Define search terms for both languages
  68. const searchTerms = {
  69. punchIn: ['Einstempeln', 'Clock In', 'Punch In', 'Time In', 'In'],
  70. punchOut: ['Ausstempeln', 'Clock Out', 'Punch Out', 'Time Out', 'Out'],
  71. missing: ['Fehlende Stempelzeit', 'Missing Punch', 'Missed Punch', '--:--']
  72. };
  73.  
  74. // Search for all elements containing text
  75. const elements = document.getElementsByTagName('*');
  76.  
  77. for (const element of elements) {
  78. const text = element.textContent.trim();
  79.  
  80. // Search for Clock In
  81. if (searchTerms.punchIn.some(term => text.includes(term))) {
  82. const timeMatch = text.match(/\d{1,2}:\d{2}/);
  83. if (timeMatch) {
  84. punchInTime = timeMatch[0].padStart(5, '0');
  85. console.log("Punch in time found:", punchInTime);
  86. }
  87. }
  88.  
  89. // Search for Missing Punch
  90. if (searchTerms.missing.some(term => text.includes(term))) {
  91. missingPunch = true;
  92. console.log("Missing punch found");
  93. }
  94.  
  95. // If no missing punch, search for Clock Out
  96. if (!missingPunch && searchTerms.punchOut.some(term => text.includes(term))) {
  97. const timeMatch = text.match(/\d{1,2}:\d{2}/);
  98. if (timeMatch) {
  99. punchOutTime = timeMatch[0].padStart(5, '0');
  100. console.log("Punch out time found:", punchOutTime);
  101. }
  102. }
  103. }
  104.  
  105. // Alternative search method for more complex cases
  106. if (!punchInTime) {
  107. const timeNodes = document.evaluate(
  108. "//*[contains(text(), ':')]",
  109. document,
  110. null,
  111. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
  112. null
  113. );
  114.  
  115. for (let i = 0; i < timeNodes.snapshotLength; i++) {
  116. const node = timeNodes.snapshotItem(i);
  117. const text = node.textContent.trim();
  118. const timeMatch = text.match(/\d{1,2}:\d{2}/);
  119.  
  120. if (timeMatch) {
  121. const parentText = node.parentElement?.textContent.toLowerCase() || '';
  122. if (searchTerms.punchIn.some(term => parentText.toLowerCase().includes(term.toLowerCase()))) {
  123. punchInTime = timeMatch[0].padStart(5, '0');
  124. console.log("Alternative method - Punch in time found:", punchInTime);
  125. } else if (!missingPunch && searchTerms.punchOut.some(term => parentText.toLowerCase().includes(term.toLowerCase()))) {
  126. punchOutTime = timeMatch[0].padStart(5, '0');
  127. console.log("Alternative method - Punch out time found:", punchOutTime);
  128. }
  129. }
  130. }
  131. }
  132.  
  133. // If "Missing Punch" was found, reset punch out time
  134. if (missingPunch) {
  135. punchOutTime = null;
  136. }
  137.  
  138. console.log("Final times - In:", punchInTime, "Out:", punchOutTime, "Missing:", missingPunch);
  139. return { punchInTime, punchOutTime };
  140. }
  141.  
  142. function getCurrentWorktime(startTime, endTime) {
  143. if (endTime) {
  144. return getTimeDifference(startTime, endTime);
  145. }
  146.  
  147. const now = new Date();
  148. const nowHours = now.getHours();
  149. const nowMinutes = now.getMinutes();
  150. const [startHours, startMinutes] = startTime.split(':').map(Number);
  151.  
  152. let hours = nowHours - startHours;
  153. let minutes = nowMinutes - startMinutes;
  154.  
  155. if (minutes < 0) {
  156. hours--;
  157. minutes += 60;
  158. }
  159. if (hours < 0) {
  160. hours += 24;
  161. }
  162.  
  163. return hours + (minutes / 60);
  164. }
  165.  
  166. function getTimeDifference(startTime, endTime) {
  167. const [startHours, startMinutes] = startTime.split(':').map(Number);
  168. const [endHours, endMinutes] = endTime.split(':').map(Number);
  169.  
  170. let hours = endHours - startHours;
  171. let minutes = endMinutes - startMinutes;
  172.  
  173. if (minutes < 0) {
  174. hours--;
  175. minutes += 60;
  176. }
  177. if (hours < 0) {
  178. hours += 24;
  179. }
  180.  
  181. return hours + (minutes / 60);
  182. }
  183.  
  184. function getBackgroundColor(hours) {
  185. if (hours <= 8.5) return COLORS.GREEN;
  186. if (hours <= 10) return COLORS.YELLOW;
  187. return COLORS.RED;
  188. }
  189.  
  190. function calculateEndTime(startTime, workHours, breakMinutes) {
  191. const [hours, minutes] = startTime.split(':').map(Number);
  192. let endHours = hours + workHours;
  193. let endMinutes = minutes + breakMinutes;
  194.  
  195. if (endMinutes >= 60) {
  196. endHours += Math.floor(endMinutes / 60);
  197. endMinutes = endMinutes % 60;
  198. }
  199. if (endHours >= 24) {
  200. endHours -= 24;
  201. }
  202.  
  203. return `${String(endHours).padStart(2, '0')}:${String(endMinutes).padStart(2, '0')}`;
  204. }
  205.  
  206. function formatHoursAndMinutes(hours) {
  207. if (isNaN(hours)) return "0:00";
  208.  
  209. const fullHours = Math.floor(hours);
  210. const minutes = Math.round((hours - fullHours) * 60);
  211. return `${fullHours}h ${minutes.toString().padStart(2, '0')}min`;
  212. }
  213.  
  214. function updateDisplay(punchInTime = null, punchOutTime = null) {
  215. const display = document.getElementById('timeCalculator');
  216.  
  217. if (display) {
  218. let content;
  219. const currentTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
  220.  
  221. if (punchInTime) {
  222. const currentWorktime = getCurrentWorktime(punchInTime, punchOutTime);
  223. const colors = getBackgroundColor(currentWorktime);
  224. const maxEndTime = calculateEndTime(punchInTime, 10, FIXED_MAX_BREAK);
  225.  
  226. display.style.backgroundColor = colors.bg;
  227. content = `
  228. <div style="margin-bottom: 12px; font-weight: bold; color: ${colors.text}; font-size: 20px; text-align: center;"><u>Working time calculator</u></div>
  229. <div style="margin-bottom: 8px; color: ${colors.text};">Current time: <strong>${currentTime}</strong></div>
  230. <div style="margin-bottom: 8px; color: ${colors.text};">Stamp IN: <strong>${punchInTime}</strong></div>
  231. ${punchOutTime ? `<div style="margin-bottom: 8px; color: ${colors.text};">Stamp OUT: <strong>${punchOutTime}</strong></div>` : ''}
  232. <div style="margin-bottom: 8px; color: ${colors.text};">
  233. Working time:
  234. <select id="workTimeSelect" style="margin-left: 5px; background-color: white;">
  235. <option value="6" ${workTime === 6 ? 'selected' : ''}>6 hours</option>
  236. <option value="8" ${workTime === 8 ? 'selected' : ''}>8 hours</option>
  237. <option value="9" ${workTime === 9 ? 'selected' : ''}>9 hours</option>
  238. <option value="10" ${workTime === 10 ? 'selected' : ''}>10 hours</option>
  239. </select>
  240. </div>
  241. <div style="margin-bottom: 8px; color: ${colors.text};">
  242. Break time:
  243. <select id="breakTimeSelect" style="margin-left: 5px; background-color: white;">
  244. <option value="0" ${breakTime === 0 ? 'selected' : ''}>No break</option>
  245. <option value="30" ${breakTime === 30 ? 'selected' : ''}>30 minutes</option>
  246. <option value="45" ${breakTime === 45 ? 'selected' : ''}>45 minutes</option>
  247. <option value="60" ${breakTime === 60 ? 'selected' : ''}>60 minutes</option>
  248. </select>
  249. </div>
  250. ${!punchOutTime ? `
  251. <div style="margin-top: 12px; font-weight: bold; color: ${colors.text};">
  252. Planned end of work: <strong>${calculateEndTime(punchInTime, workTime, breakTime)}</strong>
  253. </div>
  254. <div style="margin-top: 4px; font-weight: bold; color: red;">
  255. Latest end of work: <span style="color: red; font-weight: bold; background-color: #ffdddd; padding: 2px 5px; border-radius: 3px;">❗${maxEndTime}❗</span>
  256. </div>
  257. ` : ''}
  258. <div style="font-size: 12px; color: ${colors.text}; margin-top: 8px;">
  259. ${punchOutTime ? 'Total' : 'Current'} working time: ${formatHoursAndMinutes(currentWorktime)}
  260. </div>
  261. ${!punchOutTime ? `
  262. <div style="font-size: 12px; color: ${colors.text}; margin-top: 4px;">
  263. Total planned time: ${workTime}h ${breakTime}min
  264. </div>
  265. <div style="font-size: 12px; color: ${colors.text}; margin-top: 4px;">
  266. Maximum total time: 10h ${FIXED_MAX_BREAK}min
  267. </div>
  268. ` : ''}
  269. `;
  270. } else {
  271. display.style.backgroundColor = 'white';
  272. content = `
  273. <div style="margin-bottom: 12px; font-weight: bold; color: #666;">Working time calculator</div>
  274. <div style="margin-bottom: 8px; color: #666;">Current time: <strong>${currentTime}</strong></div>
  275. <div style="margin-bottom: 8px; color: #666;">Stamp IN: <strong>No punch time found</strong></div>
  276. <div style="margin-bottom: 8px; color: #666;">
  277. Working time:
  278. <select id="workTimeSelect" style="margin-left: 5px; background-color: white;">
  279. <option value="6" ${workTime === 6 ? 'selected' : ''}>6 hours</option>
  280. <option value="8" ${workTime === 8 ? 'selected' : ''}>8 hours</option>
  281. <option value="9" ${workTime === 9 ? 'selected' : ''}>9 hours</option>
  282. <option value="10" ${workTime === 10 ? 'selected' : ''}>10 hours</option>
  283. </select>
  284. </div>
  285. <div style="margin-bottom: 8px; color: #666;">
  286. Break time:
  287. <select id="breakTimeSelect" style="margin-left: 5px; background-color: white;">
  288. <option value="30" ${breakTime === 30 ? 'selected' : ''}>30 minutes</option>
  289. <option value="45" ${breakTime === 45 ? 'selected' : ''}>45 minutes</option>
  290. <option value="60" ${breakTime === 60 ? 'selected' : ''}>60 minutes</option>
  291. </select>
  292. </div>
  293. <div style="font-size: 12px; color: #666; margin-top: 8px;">
  294. Waiting for punch time...
  295. </div>
  296. `;
  297. }
  298.  
  299. display.innerHTML = content;
  300.  
  301. // Event Listeners
  302. const breakSelect = document.getElementById('breakTimeSelect');
  303. if (breakSelect) {
  304. breakSelect.addEventListener('change', function() {
  305. breakTime = parseInt(this.value);
  306. setValue('breakTime', breakTime);
  307. updateDisplay(punchInTime, punchOutTime);
  308. });
  309. }
  310.  
  311. const workSelect = document.getElementById('workTimeSelect');
  312. if (workSelect) {
  313. workSelect.addEventListener('change', function() {
  314. workTime = parseInt(this.value);
  315. setValue('workTime', workTime);
  316. updateDisplay(punchInTime, punchOutTime);
  317. });
  318. }
  319. }
  320. }
  321.  
  322. function addTimeCalculator() {
  323. let display = document.getElementById('timeCalculator');
  324. if (!display) {
  325. display = document.createElement('div');
  326. display.id = 'timeCalculator';
  327. display.style.cssText = `
  328. position: fixed;
  329. bottom: 100px;
  330. right: 10px;
  331. padding: 15px;
  332. border: 1px solid #ccc;
  333. border-radius: 5px;
  334. z-index: 9999;
  335. font-family: Arial, sans-serif;
  336. font-size: 14px;
  337. box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  338. min-width: 220px;
  339. transition: background-color 0.3s ease;
  340. background-color: white;
  341. `;
  342. document.body.appendChild(display);
  343. }
  344.  
  345. const { punchInTime, punchOutTime } = findPunchTimes();
  346. updateDisplay(punchInTime, punchOutTime);
  347. }
  348.  
  349. // Initial execution
  350. setTimeout(() => {
  351. addTimeCalculator();
  352. setInterval(addTimeCalculator, 5000);
  353. }, 1000);
  354.  
  355. })();