BigTime Enhancements

Adds keyboard shortcuts and displays a running weekly total for each project.

// ==UserScript==
// @name         BigTime Enhancements
// @namespace    http://mikebranski.com/
// @version      2.1.3
// @description  Adds keyboard shortcuts and displays a running weekly total for each project.
// @author       Mike Branski (@mikebranski)
// @match        https://*.bthost.com/*
// @grant        none
// @homepage     https://github.com/mikebranski/userscripts
// ==/UserScript==

(function(window, undefined) {

	WeeklyTotalsCalculator = {

		// An array of all external script dependencies to be loaded.
		dependencies: [
			'https://cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js'
		],

		dependencies_to_load: [],

		timesheet: document.getElementById('tblGrid'),

		rows: null,

		init: function() {
			// Load external resources.
			this.fetchDependencies();

			// Run the initial calculation.
			this.calculateTotals();

			// Bind event listeners.
			this.bindEventListeners();
		},

		fetchDependencies: function() {
			// Identify the injection point for the scripts.
			this.script_target = document.getElementsByTagName('head')[0];

			// Copy the depencies property to a mutable array we'll cycle through.
			this.dependencies_to_load = this.dependencies;

			// Kick over the first domino.
			this.loadNextDependency();
		},

		loadNextDependency: function() {
			var self, script, url;

			// Safety net.
			if (!this.dependencies_to_load.length) {
				return;
			}

			// Needed for the onload callback.
			self = this;

			// Shift the next dependency off the chain for loading.
			url = this.dependencies_to_load.shift();

			// Create the script element we're going to inject.
			script = document.createElement('script');
			script.src = url;

			// Bind the callback to fire once the script has loaded.
			script.onload = function() {
				// If we've finished loading dependencies, let the app know.
				if (!self.dependencies_to_load.length) {
					self.dependenciesLoaded();
					return;
				}

				// Otherwise, load the next dependency.
				self.loadNextDependency();
			}

			// Insert the script.
			this.script_target.appendChild(script);
		},

		/**
		 * This runs when all dependencies have finished loading, typically used
		 * to set up additional app functionality and event bindings.
		 */
		dependenciesLoaded: function() {

			// Override the stopCallback function so that it always returns false.
			// This ensures Mousetrap fires even when the user is in text fields
			// and other content editable elements.
			Mousetrap.stopCallback = function(e, element, combo) {
				return false;
			}

			// Go to the current week's timesheet when Cmd + Shift + C is pressed.
			Mousetrap.bind('command+shift+c', function(e) {
				var submission_uri;

				// @todo: Create a URL generator function so we don't need to
				//        do this parsing silliness every time.

				// Build the URI to the current week's timesheet view.
				current_timesheet_uri = window.location.pathname;

				// If the URI begins with a slash, remove it for now to make
				// splitting on slashes easier.
				if (current_timesheet_uri.substring(0, 1) === '/') {
					current_timesheet_uri = current_timesheet_uri.substring(1);
				}

				// Determine the account URL identifier: split on slashes and grab
				// the first part, and add a leading slash.
				current_timesheet_uri = '/' + current_timesheet_uri.split('/')[0];

				// Tack on the current week's timesheet page.
				current_timesheet_uri += '/EAPSA_MGMT.ASP?WCI=eaMain&WCE=tplTableDef&HTML=TSWKEI.htm&ObjectType=EATimesheet';

				// And away we go!
				window.location.href = current_timesheet_uri;

				// Flerp flerp.
				return false;
			});

			// Save the timesheet when Cmd + S is pressed.
			Mousetrap.bind('command+s', function(e) {
				var save_btn = document.querySelector('img[src="images/buttons/save_sm.gif"]');

				save_btn.click();

				return false;
			});

			// Go to the timesheet submission page when Cmd + Shift + S is pressed.
			Mousetrap.bind('command+shift+s', function(e) {
				var submission_uri;

				// Build the URI we'll send the user to where they can submit their
				// timesheet.
				submission_uri = window.location.pathname;

				// If the URI begins with a slash, remove it for now to make
				// splitting on slashes easier.
				if (submission_uri.substring(0, 1) === '/') {
					submission_uri = submission_uri.substring(1);
				}

				// Determine the account URL identifier: split on slashes and grab
				// the first part, and add a leading slash.
				submission_uri = '/' + submission_uri.split('/')[0];

				// Tack on the timesheet submission page.
				submission_uri += '/EAPSA_MGMT.ASP?WCI=eaMAIN&WCE=tplBasic&HTML=Daily_511.htm';

				// And away we go!
				window.location.href = submission_uri;

				// Flerp flerp.
				return false;
			});
		},

		calculateTotals: function() {
			// Get the current rows that contain timesheet data.
			this.rows = this.timesheet.querySelectorAll('tr[isdatarow="TRUE"]');

			for (var i = 0; i < this.rows.length; i++) {
				var
					row = this.rows[i],

					// These hold the time values for each day.
					entries = row.querySelectorAll('.list-item-frm .ea-input-item-sm'),

					// The total project hours logged for the week.
					total = 0,

					// This is where we'll plop the total, alongside the delete icon. Because that's easier than adding a new th/td to every row.
					total_parent = row.children[9].querySelector('p'),

					total_element = total_parent.querySelector('b.total');

					// Loop over each of the entries and add up the time.
					for (var n = 0; n < entries.length; n++) {
						var entry_value = entries[n].value.trim();

						// Skip empty cells and non-numbers.
						if (!entry_value || Number.isNaN(entry_value)) {
							continue;
						}

						// Add this entry to the running total.
						total += parseFloat(entry_value);
					}

					// Create the total element if it doesn't exist.
					if (!total_element) {
						total_element = document.createElement('b');

						// This is for querying later.
						total_element.classList.add('total');

						// Make it look not terrible.
						total_element.style.display   = 'inline-block';
						total_element.style.width     = '25px';
						total_element.style.textAlign = 'right';

						// Prepend it to the parent, before the delete button.
						total_parent.insertBefore(total_element, total_parent.firstChild);
					}

					// Update the total.
					total_element.innerHTML = total;
				}
		},

		bindEventListeners: function() {
			var self = this;

			// Every time an entry changes, re-calculate.
			self.timesheet.addEventListener('change', function(event) {
				// We're only interested in time entries.
				if (event.target.classList.contains('ea-input-item-sm')) {
						self.calculateTotals();
				}
			});
		}
	};

	WeeklyTotalsCalculator.init();

})(window);
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元