{% extends "base.html.twig" %} {% block localstyle %} #timer-task { width:300px; } #timer-desc { width:300px; } .select2-results__group { font-size:12px !important; } .select2-results__option{ font-size: 14px; padding-left:15px } {% endblock %} {% block body %}

SUIVI HORAIRE

Créer un Timer {% if user %} Lancer un Timer : {% else %} Veuillez choisir un Intervenant pour lancer un Timer. {% endif %}
Voir le calendrier

{%for timer in timers %} {% endfor %}
Tâche Description Début Fin Durée Actions
{{ timer.task.displayname }} {{ timer.activepenalty ? " Astreinte active" : "" }} {{ timer.additionalhour ? " Heures supplémentaires" : "" }}

{{ timer.description }}

{{ timer.start|date("d/m/Y H:i") }} {{ timer.end|date("d/m/Y H:i") }} {{ timer.duration|date("H:i") }}   
{% endblock %} {% block localjavascript %} /* Creates a new Task object. */ function Task(id ,name, description) { this._name = name; this._taskid = id; this._description = description; this._state = Task.State.STOPPED; this._timeSpentInPreviousIterations = 0; this._startDate = 0; this._endDate = 0; this._currentIterationStartTime = 0; this._customTimeSpent = 0; this._onChange = null; } /* Possible task states. */ Task.State = { STOPPED: "stopped", RUNNING: "running" } Task.prototype = { /* Returns the task name. */ getName: function() { return this._name; }, /* Returns the task description. */ getDescription: function() { if (this._description == "NaN") { this._description = "" } return this._description; }, /* Returns the task state. */ getState: function() { return this._state; }, /* Is the task stopped? */ isStopped: function() { return this._state == Task.State.STOPPED; }, /* Is the task running? */ isRunning: function() { return this._state == Task.State.RUNNING; }, /* * Sets the "onChange" event handler. The "onChange" event is fired when the * task is started, stopped, or reset. */ setOnChange: function(onChange) { this._onChange = onChange; }, /* * Returns the time spent on the task in the current work iteration. Works * correctly only when the task is running. */ _getCurrentIterationTime: function() { return (new Date).getTime() - this._currentIterationStartTime; }, /* * Returns the total time spent on the task. This includes time spent in * the current work iteration if the task is running. */ getTimeSpent: function() { var result = this._timeSpentInPreviousIterations; if (this._state == Task.State.RUNNING) { result += this._getCurrentIterationTime(); } return result; }, /* Calls the "onChange" event handler if set. */ _callOnChange: function() { if (typeof this._onChange == "function") { this._onChange(); } }, /* Starts a new task work iteration. */ start: function() { if (this._state == Task.State.RUNNING) { return }; if (this._startDate == 0) {this._startDate = new Date()} this._state = Task.State.RUNNING; this._currentIterationStartTime = (new Date).getTime(); this._callOnChange(); }, /* Stops the current task work iteration. */ stop: function() { if (this._state == Task.State.STOPPED) { return }; this._state = Task.State.STOPPED; this._timeSpentInPreviousIterations += this._getCurrentIterationTime(); this._currentIterationStartTime = 0; this._endDate = new Date(); this._callOnChange(); }, /* Stops the current task work iteration and resets the time data. */ reset: function() { this.stop(); this._timeSpentInPreviousIterations = 0; this._callOnChange(); }, save: function(task){ console.log(task) $.ajax({ type: "POST", data: { taskname: task._name, taskid: task._taskid, description: task._description, start: task._startDate, end: task._endDate, duration: task._timeSpentInPreviousIterations, }, url: "{{ path('app_timer_create') }}", success: function (response) { response=JSON.parse(response); if(response.return=="KO") { $("#dataTable_wrapper").append("
"+response.error+"
"); }else{ location.reload(); } } }); }, /* Serializes the task into a string. */ serialize: function() { /* * Originally, I wanted to use "toSource" and "eval" for serialization and * deserialization, but "toSource" is not supported by WebKit, so I resorted * to ugly hackery... */ return [ encodeURIComponent(this._name), this._state, this._timeSpentInPreviousIterations, this._currentIterationStartTime, this._startDate, this._endDate, this._taskid, this._description, ].join("&"); }, /* Deserializes the task from a string. */ deserialize: function(serialized) { var parts = serialized.split("&"); this._name = decodeURIComponent(parts[0]); this._state = parts[1]; this._timeSpentInPreviousIterations = parseInt(parts[2]); this._currentIterationStartTime = parseInt(parts[3]); this._startDate = parseInt(parts[4]); this._endDate = parseInt(parts[5]); this._taskid = parseInt(parts[6]); this._description = parseInt(parts[7]); } } /* ===== Tasks ===== */ /* The Tasks class represents a list of tasks. */ /* Creates a new Tasks object. */ function Tasks() { this._tasks = []; this._onAdd = null; this._onRemove = null; } Tasks.prototype = { /* * Sets the "onAdd" event handler. The "onAdd" event is fired when a task * is added to the list. */ setOnAdd: function(onAdd) { this._onAdd = onAdd; }, /* * Sets the "onRemove" event handler. The "onRemove" event is fired when a * task is removed from the list. */ setOnRemove: function(onRemove) { this._onRemove = onRemove; }, /* Returns the length of the task list. */ length: function() { return this._tasks.length }, /* * Returns index-th task in the list, or "undefined" if the index is out of * bounds. */ get: function(index) { return this._tasks[index]; }, /* * Calls the callback function for each task in the list. The function is * called with three parameters - the task, its index and the task list * object. This is modeled after "Array.forEach" in JavaScript 1.6. */ forEach: function(callback) { for (var i = 0; i < this._tasks.length; i++) { callback(this._tasks[i], i, this); } }, /* Calls the "onAdd" event handler if set. */ _callOnAdd: function(task) { if (typeof this._onAdd == "function") { this._onAdd(task); } }, /* Adds a new task to the end of the list. */ add: function(task) { this._tasks.push(task); this._callOnAdd(task); }, /* Calls the "onRemove" event handler if set. */ _callOnRemove: function(index) { if (typeof this._onRemove == "function") { this._onRemove(index); } }, /* * Removes index-th task from the list. Does not do anything if the index * is out of bounds. */ remove: function(index) { this._callOnRemove(index); this._tasks.splice(index, 1); }, /* Serializes the list of tasks into a string. */ serialize: function() { var serializedTasks = []; this.forEach(function(task) { serializedTasks.push(task.serialize()); }); return serializedTasks.join("|"); }, /* Deserializes the list of tasks from a string. */ deserialize: function(serialized) { /* * Repeatedly use "remove" so the "onRemove" event is triggered. Do the same * with the "add" method below. */ while (this._tasks.length > 0) { this.remove(0); } var serializedTasks = serialized.split("|"); for (var i = 0; i < serializedTasks.length; i++) { var task = new Task(0 ,"", ""); task.deserialize(serializedTasks[i]); this.add(task); } } } /* ===== Extensions ===== */ /* * Pads this string with another string on the left until the resulting string * has specified length. If the padding string has more than one character, the * resulting string may be longer than desired (the padding string is not * truncated and it is only prepended as a whole). Bad API, I know, but it's * good enough for me. */ String.prototype.pad = function(length, padding) { var result = this; while (result.length < length) { result = padding + result; } return result; } /* ===== Task List + DOM Storage ===== */ /* The list of tasks. */ var tasks = new Tasks(); /* The last value of the serialized task list string. */ var lastSerializedTasksString; /* * The key under which the serialized task list string is stored in the DOM * Storage. */ var TASKS_DOM_STORAGE_KEY = "timerTasks"; /* * Returns DOM Storage used by the application, or "null" if the browser does * not support DOM Storage. */ function getStorage() { /* * HTML 5 says that the persistent storage is available in the * "window.localStorage" attribute, however Firefox implements older version * of the proposal, which uses "window.globalStorage" indexed by the domain * name. We deal with this situation here as gracefully as possible (i.e. * without concrete browser detection and with forward compatibility). * * For more information, see: * * http://www.whatwg.org/specs/web-apps/current-work/#storage * https://developer.mozilla.org/En/DOM/Storage */ if (window.localStorage !== undefined) { return window.localStorage; } else if (window.globalStorage !== undefined) { return window.globalStorage[location.hostname]; } else { return null; } } /* * Saves the task list into a DOM Storage. Also updates the value of the * "lastSerializedTasksString" variable. */ function saveTasks() { var serializedTasksString = tasks.serialize(); getStorage()[TASKS_DOM_STORAGE_KEY] = serializedTasksString; lastSerializedTasksString = serializedTasksString; } /* * Loads the serialized task list string from the DOM Storage. Returns * "undefined" if the storage does not contain the string (this happens when * running the application for the first time). */ function loadSerializedTasksString() { var storedValue = getStorage()[TASKS_DOM_STORAGE_KEY]; /* * The spec says "null" should be returned when the key is not found, but some * browsers return "undefined" instead. Maybe it was in some earlier version * of the spec (I didn't bother to check). */ if (storedValue !== null && storedValue !== undefined && storedValue.length > 0) { /* * The values retrieved from "globalStorage" use one more level of * indirection. */ return (window.localStorage === undefined) ? storedValue.value : storedValue; } else { return undefined; } } /* * Loads the task list from the DOM Storage. Also updates the value of the * "lastSerializedTasksString" variable. */ function loadTasks() { var serializedTasksString = loadSerializedTasksString(); if (serializedTasksString !== undefined) { tasks.deserialize(serializedTasksString); lastSerializedTasksString = serializedTasksString; } } /* * Was the task list changed outside of the application? Detects the change * by comparing the current serialized task list string in the DOM Storage * with a kept old value. */ function tasksHaveChangedOutsideApplication() { var serializedTasksString = loadSerializedTasksString(); if (serializedTasksString != lastSerializedTasksString) { return true } return false; } /* ===== View ===== */ /* Some time constants. */ var MILISECONDS_IN_SECOND = 1000; var MILISECONDS_IN_MINUTE = 60 * MILISECONDS_IN_SECOND; var MINUTES_IN_HOUR = 60; /* Formats the time in the H:MM format. */ function formatTime(time) { var timeInMinutes = time / MILISECONDS_IN_MINUTE; var hours = Math.floor(timeInMinutes / MINUTES_IN_HOUR); var minutes = Math.floor(timeInMinutes - hours * MINUTES_IN_HOUR); return hours + ":" + String(minutes).pad(2, "0"); } /* * Computes the URL of the image in the start/stop link according to the task * state. */ function computeStartStopLinkImageUrl(state) { switch (state) { case Task.State.STOPPED: return "fa-play"; case Task.State.RUNNING: return "fa-stop"; default: throw "Invalid task state." } } /* * Builds the HTML element of the row in the task table corresponding to the * specified task and index. */ function buildTaskRow(task, index) { var result = $(""); var startStopLink = $( "" + "" ); startStopLink.click(function() { switch (task.getState()) { case Task.State.STOPPED: task.start(); break; case Task.State.RUNNING: task.stop(); break; default: throw "Invalid task state." } saveTasks(); return false; }); var resetLink = $( "" + "" ); resetLink.click(function() { task.reset(); saveTasks(); return false; }); var deleteLink = $( "" + "" ); deleteLink.click(function() { if (confirm("Do you really want to delete task \"" + task.getName() + "\"?")) { tasks.remove(index); saveTasks(); } return false; }); var saveLink = $( "" + "" ); saveLink.click(function() { tasks.remove(index); saveTasks(); task.save(task); return false; }); result .addClass("state-" + task.getState()) .append($("").text(task.getName())) .append($("").text(task.getDescription())) .append($("").text(formatTime(task.getTimeSpent()))) .append($("") .append(startStopLink) .append("  ") .append(resetLink) .append("  ") .append(saveLink) .append("    ") .append(deleteLink) ); return result; } /* Finds row with the specified index in the task table. */ function findRowWithIndex(index) { return $("#task-table").find("tr").slice(1).eq(index); } /* * Updates the row in the task table corresponding to a task according to * its state. */ function updateTaskRow(row, task) { if (task.isStopped()) { row.removeClass("state-running"); row.addClass("state-stopped"); } else if (task.isRunning()) { row.removeClass("state-stopped"); row.addClass("state-running"); } row.find(".task-time").text(formatTime(task.getTimeSpent())) row.find(".start-stop-link i").removeClass(); row.find(".start-stop-link i").addClass("fa "+computeStartStopLinkImageUrl(task.getState())); } /* ===== Initialization ===== */ /* Initializes event handlers on the task list. */ function initializeTasksEventHandlers() { tasks.setOnAdd(function(task) { var row = buildTaskRow(task, tasks.length() - 1); $("#task-table").append(row); task.setOnChange(function() { updateTaskRow(row, task); }); }); tasks.setOnRemove(function(index) { findRowWithIndex(index).remove(); if (tasks.length() == 1 ) { $( "#task-table" ).css({"display":"none"}); } }); } /* Initializes GUI event handlers. */ function initializeGuiEventHandlers() { $( "#addtimer" ).click(function() { displayTaskAdd(); }); $( "#timer-desc" ).keypress(function( event ) { if ( event.which == 13 ) { event.preventDefault(); displayTaskAdd(); } }); } function displayTaskAdd() { $( "#task-table" ).css({"display":"inline"}); var taskName = $("#timer-task option:selected").text(); var taskId = $("#timer-task option:selected").val(); var taskDesc = $("#timer-desc").val(); var task = new Task(taskId, taskName, taskDesc); tasks.add(task); saveTasks(); $("#timer-desc").val(""); } /* * Initializes a timer used to update task times and detect changes in the * data stored in the DOM Storage. */ function initializeTimer() { setInterval(function() { tasks.forEach(function(task, index) { updateTaskRow(findRowWithIndex(index), task); }); if (tasksHaveChangedOutsideApplication()) { loadTasks(); } }, 10 * MILISECONDS_IN_SECOND); } /* Initializes the application. */ $(document).ready(function(){ try { if (!getStorage()) { alert("Timer requires a browser with DOM Storage support, such as Firefox 3+ or Safari 4+."); return; } } catch (e) { alert("Timer does not work with file: URLs in Firefox."); return; } initializeTasksEventHandlers(); loadTasks(); initializeGuiEventHandlers(); initializeTimer(); }); {% endblock %}