BigTime Enhancements

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

Versión del día 24/08/2015. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);