Modification plage horaire événement #14

Closed
mlamalle wants to merge 10 commits from updatehourevent into master
17 changed files with 2436 additions and 34 deletions

View File

@@ -17,7 +17,6 @@
###> symfony/webpack-encore-bundle ### ###> symfony/webpack-encore-bundle ###
/node_modules/ /node_modules/
/public/build/
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
###< symfony/webpack-encore-bundle ### ###< symfony/webpack-encore-bundle ###

View File

@@ -0,0 +1,609 @@
/* 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("<div class='alert alert-danger' style='margin: 5px 0px'>"+response.error+"</div>");
}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 = $("<tr />");
var startStopLink = $(
"<a href='#' class='start-stop-link' title='Start/stop'>"
+ "<i class='fa " + computeStartStopLinkImageUrl(task.getState()) + "'></i>"
);
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 = $(
"<a href='#' title='Reset'>"
+ "<i class='fa fa-undo'></i>"
);
resetLink.click(function() {
task.reset();
saveTasks();
return false;
});
var deleteLink = $(
"<a href='#' title='Delete' class='deletetask'>"
+ "<i class='fa fa-trash-alt' class='deletetask'></i>"
);
deleteLink.click(function() {
if (confirm("Do you really want to delete task \"" + task.getName() + "\"?")) {
tasks.remove(index);
saveTasks();
}
return false;
});
var saveLink = $(
"<a href='#' title='Save' class='savetask'>"
+ "<i class='fa fa-download' class='savetask'></i>"
);
saveLink.click(function() {
tasks.remove(index);
saveTasks();
task.save(task);
return false;
});
result
.addClass("state-" + task.getState())
.append($("<td class='task-name' />").text(task.getName()))
.append($("<td class='task-description' />").text(task.getDescription()))
.append($("<td class='task-time' />").text(formatTime(task.getTimeSpent())))
.append($("<td class='task-actions' />")
.append(startStopLink)
.append("&nbsp;&nbsp;")
.append(resetLink)
.append("&nbsp;&nbsp;")
.append(saveLink)
.append("&nbsp;&nbsp;&nbsp;&nbsp;")
.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();
});

View File

@@ -347,6 +347,34 @@ app_validationholiday_activeholiday:
path: /validator/validateholiday/activeholiday path: /validator/validateholiday/activeholiday
defaults: { _controller: App\Controller\ValidationController:activeholiday } defaults: { _controller: App\Controller\ValidationController:activeholiday }
#== Timer ====================================================================================================
app_timer:
path: /timer
defaults: { _controller: App\Controller\TimerController:list }
app_timer_view:
path: /timer/event
defaults: { _controller: App\Controller\TimerController:view }
app_timer_load:
path: /timer/event/load
defaults: { _controller: App\Controller\TimerController:load }
app_timer_submit:
path: /timer/submit
defaults: { _controller: App\Controller\TimerController:submit }
app_timer_create:
path: /timer/create
defaults: { _controller: App\Controller\TimerController:create }
app_timer_update:
path: /timer/update/{id}
defaults: { _controller: App\Controller\TimerController:update }
app_timer_delete:
path: /timer/delete/{id}
defaults: { _controller: App\Controller\TimerController:delete }
#== Customer ====================================================================================================== #== Customer ======================================================================================================
app_customer_report: app_customer_report:

View File

@@ -122,7 +122,7 @@ class EventController extends AbstractController
if($am&&$ap) { if($am&&$ap) {
$datestart->SetTime(0,0,0); $datestart->SetTime(0,0,0);
$dateend->add(new \DateInterval('P1D')); $dateend->add(new \DateInterval('P1D'));
$dateend->SetTime(0,0,0); $dateend->SetTime(0,0,-1);
$duration=$dateend->diff($datestart)->d; $duration=$dateend->diff($datestart)->d;
$allday=true; $allday=true;
} }
@@ -271,6 +271,8 @@ class EventController extends AbstractController
$idevent = str_replace("A","",$request->request->get('idevent')); $idevent = str_replace("A","",$request->request->get('idevent'));
$iduser = $request->request->get('iduser'); $iduser = $request->request->get('iduser');
$idtask = $request->request->get('idtask'); $idtask = $request->request->get('idtask');
$am = ($request->request->get('am')=="true");
$ap = ($request->request->get('ap')=="true");
$fgastreinte = ($request->request->get('fgastreinte')=="true"); $fgastreinte = ($request->request->get('fgastreinte')=="true");
$description = $request->request->get('description'); $description = $request->request->get('description');
@@ -327,8 +329,68 @@ class EventController extends AbstractController
return new Response(json_encode($output)); return new Response(json_encode($output));
} }
$datestart=$event->getStart();
$dateend =$event->getEnd();
if($am&&$ap) {
$datestart->SetTime(0,0,0);
$dateend->add(new \DateInterval('P1D'));
$dateend->SetTime(0,0,-1);
$duration=$dateend->diff($datestart)->d;
$allday=true;
}
else {
$duration=0.5;
$allday=false;
if($am) {
$datestart->SetTime(9,0,0);
$dateend->SetTime(12,0,0);
}
else {
$datestart->SetTime(13,0,0);
$dateend->SetTime(17,0,0);
}
}
// On regarde si une tache ne commence pas pendant une autre intervention ou qui se termine pendant une autre intervention ou qui a une intervention compris dans ses dates
$events = $em->createQueryBuilder('event')
->select('event')
->from('App:Event','event')
->Where('event.user=:user AND event.start<=:start AND event.end >:start')
->orWhere('event.user=:user AND event.start<:end AND event.end >=:end')
->orWhere('event.user=:user AND event.start>:start AND event.end <:end')
->setParameter('user',$iduser)
->setParameter('start',$datestart)
->setParameter('end',$dateend)
->getQuery()->getResult();
dump($event->getId());
if($events) {
$tbevent=[];
foreach($events as $ev) {
if ($event->getId() != $ev->getId()) {
$tmp=[
"id" => $ev->getId(),
"task" => $ev->getTask()->getName(),
"start" => $ev->getStart(),
"end" => $ev->getEnd(),
];
array_push($tbevent,$tmp);
}
}
if (sizeof($tbevent)>0) {
$output=["return"=>"KO","error"=>"Cet intervant a déjà une tache à cette date","start"=>$datestart,"end"=>$dateend,"events"=>$tbevent];
return new Response(json_encode($output));
}
}
// Modification de l'évenement // Modification de l'évenement
$event->setStart($datestart);
$event->setEnd($dateend);
$event->setDescription($description); $event->setDescription($description);
$event->setDuration($duration);
$event->setAllday($allday);
$event->setUser($user); $event->setUser($user);
$event->setTask($task); $event->setTask($task);
@@ -336,8 +398,8 @@ class EventController extends AbstractController
$em->flush(); $em->flush();
$output=$this->formatEvent($event); $output=$this->formatEvent($event);
dump($event);
} }
return new Response(json_encode($output)); return new Response(json_encode($output));
} }

View File

@@ -608,8 +608,50 @@ class ReportController extends AbstractController
]; ];
} }
$tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"]+$event->getDuration(); $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"]+$event->getDuration();
} }
// foreach($eventsbyweek as $event) {
// if(!isset($tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"])){
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [
// "weeknumber" => $event->getStart()->format("W"),
// "users" => [],
// ];
// }
// if(!isset($tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()])){
// $tbuser= [
// "id"=>$event->getUser()->getId(),
// "displayname"=>$event->getUser()->getDisplayname(),
// "cumul"=>0
// ];
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()] = $tbuser;
// }
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()]["cumul"] = $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()]["cumul"]+$event->getDuration();
// }
// foreach($eventsbyweek as $event) {
// if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"])){
// $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [
// "weeknumber" => $event->getStart()->format("W"),
// "tasks" => [],
// ];
// }
// if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()])){
// $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()] = [
// "taskname" => $event->getTask()->getName(),
// "users" => [],
// ];
// }
// if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()])){
// $tbuser= [
// "id"=>$event->getUser()->getId(),
// "displayname"=>$event->getUser()->getDisplayname(),
// "cumul"=>0
// ];
// $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()] = $tbuser;
// }
// $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()]["cumul"] = $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()]["cumul"]+$event->getDuration();
// }
// Somme astreintes validé par semaine // Somme astreintes validé par semaine
$start=new \Datetime('first day of this month'); $start=new \Datetime('first day of this month');
$start->sub(new \DateInterval('P'.$nbmonth.'M')); $start->sub(new \DateInterval('P'.$nbmonth.'M'));
@@ -646,6 +688,24 @@ class ReportController extends AbstractController
} }
$tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"]+$penalty->getDuration(); $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"]+$penalty->getDuration();
} }
foreach($penaltybyweek as $penaltybyweek) {
if(!isset($tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"])){
$tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")] = [
"weeknumber" => $penalty->getStart()->format("W"),
"users" => [],
];
}
if(!isset($tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()])){
$tbuser= [
"id"=>$penalty->getUser()->getId(),
"displayname"=>$penalty->getUser()->getDisplayname(),
"cumul"=>0
];
$tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()] = $tbuser;
}
$tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()]["cumul"] = $tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()]["cumul"]+$penalty->getDuration();
}
// Recap des Commandes // Recap des Commandes
$offers=$em->getRepository("App:Offer")->findBy(["project"=>$project->getId()]); $offers=$em->getRepository("App:Offer")->findBy(["project"=>$project->getId()]);
@@ -1066,6 +1126,8 @@ class ReportController extends AbstractController
$tbproject= []; $tbproject= [];
$tbproject["projectname"] = $project->getDisplayname(); $tbproject["projectname"] = $project->getDisplayname();
$tbproject["name"] = $project->getname();
$tbproject["customer"] = $project->getCustomer()->getName();
// Somme event validé par semaine // Somme event validé par semaine
$start=new \Datetime('first day of this month'); $start=new \Datetime('first day of this month');
$start->sub(new \DateInterval('P'.$nbmonth.'M')); $start->sub(new \DateInterval('P'.$nbmonth.'M'));
@@ -1093,15 +1155,57 @@ class ReportController extends AbstractController
->orderBy('event.start') ->orderBy('event.start')
->getQuery()->getResult(); ->getQuery()->getResult();
// foreach($eventsbyweek as $event) {
// if(!isset($tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")])){
// $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [
// "weeknumber" => $event->getStart()->format("W"),
// "cumul" => 0,
// ];
// }
// $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"]+$event->getDuration();
// }
// foreach($eventsbyweek as $event) {
// if(!isset($tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"])){
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [
// "weeknumber" => $event->getStart()->format("W"),
// "users" => [],
// ];
// }
// if(!isset($tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()])){
// $tbuser= [
// "id"=>$event->getUser()->getId(),
// "displayname"=>$event->getUser()->getDisplayname(),
// "cumul"=>0
// ];
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()] = $tbuser;
// }
// $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()]["cumul"] = $tbproject["weeks_by_name"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["users"][$event->getUser()->getId()]["cumul"]+$event->getDuration();
// }
foreach($eventsbyweek as $event) { foreach($eventsbyweek as $event) {
if(!isset($tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")])){ if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"])){
$tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [ $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")] = [
"weeknumber" => $event->getStart()->format("W"), "weeknumber" => $event->getStart()->format("W"),
"cumul" => 0, "tasks" => [],
]; ];
} }
$tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["cumul"]+$event->getDuration(); if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()])){
$tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()] = [
"taskname" => $event->getTask()->getName(),
"users" => [],
];
}
if(!isset($tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()])){
$tbuser= [
"id"=>$event->getUser()->getId(),
"displayname"=>$event->getUser()->getDisplayname(),
"cumul"=>0
];
$tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()] = $tbuser;
}
$tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()]["cumul"] = $tbproject["weeks_by_task_by_user"][$event->getStart()->format("Y")][$event->getStart()->format("W")]["tasks"][$event->getTask()->getId()]["users"][$event->getUser()->getId()]["cumul"]+$event->getDuration();
} }
// Somme astreintes validé par semaine // Somme astreintes validé par semaine
$start=new \Datetime('first day of this month'); $start=new \Datetime('first day of this month');
@@ -1130,15 +1234,58 @@ class ReportController extends AbstractController
->setParameter('end',$endmonth) ->setParameter('end',$endmonth)
->orderBy('penalty.start') ->orderBy('penalty.start')
->getQuery()->getResult(); ->getQuery()->getResult();
// foreach($penaltybyweek as $penalty) {
// if(!isset($tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")])){
// $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")] = [
// "weeknumber" => $penalty->getStart()->format("W"),
// "cumul" => 0,
// ];
// }
// $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"]+$penalty->getDuration();
// }
// foreach($penaltybyweek as $penalty) {
// if(!isset($tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"])){
// $tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")] = [
// "weeknumber" => $penalty->getStart()->format("W"),
// "users" => [],
// ];
// }
// if(!isset($tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()])){
// $tbuser= [
// "id"=>$penalty->getUser()->getId(),
// "displayname"=>$penalty->getUser()->getDisplayname(),
// "cumul"=>0
// ];
// $tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()] = $tbuser;
// }
// $tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()]["cumul"] = $tbproject["weeks_by_name"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["users"][$penalty->getUser()->getId()]["cumul"]+$penalty->getDuration();
// }
foreach($penaltybyweek as $penalty) { foreach($penaltybyweek as $penalty) {
if(!isset($tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")])){ if(!isset($tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"])){
$tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")] = [ $tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")] = [
"weeknumber" => $penalty->getStart()->format("W"), "weeknumber" => $penalty->getStart()->format("W"),
"cumul" => 0, "tasks" => [],
]; ];
} }
$tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"] = $tbproject["weeks"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["cumul"]+$penalty->getDuration(); if(!isset($tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()])){
$tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()] = [
"taskname" => $penalty->getTask()->getName(),
"users" => [],
];
} }
if(!isset($tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()]["users"][$penalty->getUser()->getId()])){
$tbuser= [
"id"=>$penalty->getUser()->getId(),
"displayname"=>$penalty->getUser()->getDisplayname(),
"cumul"=>0
];
$tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()]["users"][$penalty->getUser()->getId()] = $tbuser;
}
$tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()]["users"][$penalty->getUser()->getId()]["cumul"] = $tbproject["weeks_by_task_by_user"][$penalty->getStart()->format("Y")][$penalty->getStart()->format("W")]["tasks"][$penalty->getTask()->getId()]["users"][$penalty->getUser()->getId()]["cumul"]+$penalty->getDuration();
}
$tbprojects[$project->getId()]=$tbproject; $tbprojects[$project->getId()]=$tbproject;
} }

View File

@@ -0,0 +1,252 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Timer as Entity;
use App\Form\TimerType as Form;
class TimerController extends AbstractController
{
private $data = "timer";
private $route = "app_timer";
private $render = "Timer/";
private $entity = "App:Timer";
public function list(Request $request)
{
$em = $this->getDoctrine()->getManager();
$iduser = $this->get("session")->get("iduser");
$user = $em->getRepository("App:User")->find($iduser);
$tasks = $em->getRepository("App:Task")->findAll();
$timers = $em->getRepository("App:Timer")->findBy(["user"=>$iduser]);
return $this->render($this->render.'list.html.twig',[
"useheader" => true,
"usesidebar" => true,
"user" => $user,
"tasks" => $tasks,
"timers" => $timers,
]);
}
public function view(Request $request)
{
$em = $this->getDoctrine()->getManager();
$iduser = $this->get("session")->get("iduser");
$user = $em->getRepository("App:User")->find($iduser);
$tasks = $em->getRepository("App:Task")->findAll();
$timers = $em->getRepository("App:Timer")->findBy(["user"=>$iduser]);
return $this->render($this->render.'list.cal.html.twig',[
"useheader" => true,
"usesidebar" => true,
"user" => $user,
"tasks" => $tasks,
"timers" => $timers,
]);
}
public function load(Request $request)
{
$em = $this->getDoctrine()->getManager();
$tbtimers=[];
// Evenements
$iduser=$this->get("session")->get("iduser");
if($iduser=="all")
$timers=$em->getRepository("App:Timer")->findAll();
else {
$iduser=$this->get("session")->get("iduser");
$user=$em->getRepository("App:User")->find($iduser);
$timers=$em->getRepository("App:Timer")->findBy(["user"=>$user]);
}
foreach($timers as $timer) {
$tmp=$this->formatTimer($timer);
array_push($tbtimers,$tmp);
}
// Retour
return new Response(json_encode($tbtimers));
}
public function create(Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$iduser = $this->get("session")->get("iduser");
$user = $em->getRepository("App:User")->find($iduser);
$taskid = $request->request->get('taskid');
$description = $request->request->get('description');
$task = $em->getRepository("App:Task")->find($taskid);
$start = \DateTime::createFromFormat('D M d Y H:i:s e+',$request->request->get('start'));
$end = \DateTime::createFromFormat('D M d Y H:i:s e+',$request->request->get('end'));
$duration = new \DateTime(date("H:i:s", ($request->request->get('duration')/1000)));
$duration->sub(new \DateInterval('PT1H'));
$timer = new Entity();
$timer->setUser($user);
$timer->setTask($task);
$timer->setStart($start);
$timer->setEnd($end);
$timer->setDuration($duration);
$timer->setDescription($description);
$em->persist($timer);
$em->flush();
$output=["return"=>"OK"];
return new Response(json_encode($output));
}
public function submit(Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$data = new Entity();
$iduser = $this->get("session")->get("iduser");
$user = $em->getRepository("App:User")->find($iduser);
// Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"submit"));
// Récupération des data du formulaire
$form->handleRequest($request);
// Sur erreur
$this->getErrorForm(null,$form,$request,$data,"submit");
// Sur validation
if ($form->get('submit')->isClicked() && $form->isValid()) {
$data = $form->getData();
$data->setUser($user);
$em->persist($data);
$em->flush();
// Retour à la liste
return $this->redirectToRoute($this->route);
}
// Affichage du formulaire
return $this->render($this->render.'edit.html.twig', [
'useheader' => true,
'usesidebar' => true,
$this->data => $data,
'mode' => 'submit',
'form' => $form->createView()
]);
}
public function update($id,Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id);
// Création du formulaire
$form = $this->createForm(Form::class,$data,array("mode"=>"update"));
// Récupération des data du formulaire
$form->handleRequest($request);
// Sur erreur
$this->getErrorForm(null,$form,$request,$data,"update");
// Sur validation
if ($form->get('submit')->isClicked() && $form->isValid()) {
$data = $form->getData();
$em->persist($data);
$em->flush();
// Retour à la liste
return $this->redirectToRoute($this->route);
}
return $this->render($this->render.'edit.html.twig', [
'useheader' => true,
'usesidebar' => true,
$this->data => $data,
'mode' => 'update',
'form' => $form->createView()
]);
}
public function delete($id, Request $request)
{
// Initialisation de l'enregistrement
$em = $this->getDoctrine()->getManager();
$data=$em->getRepository($this->entity)->find($id);
// Controle avant suppression
$error=false;
if($error)
return $this->redirectToRoute($this->route."_update",["id"=>$id]);
else {
try {
$em->remove($data);
$em->flush();
}
catch(\Doctrine\DBAL\DBALException $e) {
// Création du formulaire
$this->get('session')->getFlashBag()->add('error', 'Impossible de supprimer cet enregistrement');
return $this->redirectToRoute($this->route."_update",["id"=>$id]);
}
// Retour à la liste
return $this->redirectToRoute($this->route);
}
}
protected function getErrorForm($id,$form,$request,$data,$mode) {
if ($form->get('submit')->isClicked()&&$mode=="delete") {
}
if ($form->get('submit')->isClicked() && $mode=="submit") {
}
if ($form->get('submit')->isClicked() && !$form->isValid()) {
$this->get('session')->getFlashBag()->clear();
$errors = $form->getErrors();
foreach( $errors as $error ) {
$request->getSession()->getFlashBag()->add("error", $error->getMessage());
}
}
}
public function formatTimer($timer) {
$tmp= [
"id"=> $timer->getId(),
"title" => $timer->getTask()->getDisplayname(),
"start" => $timer->getStart()->format("Y-m-d H:i"),
"end" => $timer->getEnd()->format("Y-m-d H:i"),
"backgroundColor" => $timer->getTask()->getColor(),
"borderColor" => $timer->getTask()->getColor(),
"textColor" => "#ffffff",
"allDay" => false,
"editable" => true,
"durationEditable" => false,
"extendedProps" => [
"fulldescription" => $timer->getTask()->getDisplayname()."\n\n".$timer->getDescription(),
"description" => $timer->getDescription(),
"userid" => $timer->getUser()->getId(),
"username" => $timer->getUser()->getUsername(),
"taskid" => $timer->getTask()->getId(),
"avatar" => "/".$this->getParameter("appAlias")."/uploads/avatar/".$timer->getUser()->getAvatar(),
"estimate" => $timer->getDuration()->format("H:i"),
"locked" => false,
"editable" => true,
"astreinte" => false
]
];
return $tmp;
}
}

View File

@@ -96,24 +96,24 @@ class Event
public function getStart(): ?\DateTimeInterface public function getStart(): ?\DateTimeInterface
{ {
return $this->start; return clone $this->start;
} }
public function setStart(\DateTimeInterface $start): self public function setStart(\DateTimeInterface $start): self
{ {
$this->start = $start; $this->start = clone $start;
return $this; return $this;
} }
public function getEnd(): ?\DateTimeInterface public function getEnd(): ?\DateTimeInterface
{ {
return $this->end; return clone $this->end;
} }
public function setEnd(\DateTimeInterface $end): self public function setEnd(\DateTimeInterface $end): self
{ {
$this->end = $end; $this->end = clone $end;
return $this; return $this;
} }

View File

@@ -61,6 +61,11 @@ class Task
*/ */
private $events; private $events;
/**
* @ORM\OneToMany(targetEntity="Timer", mappedBy="task", cascade={"persist"}, orphanRemoval=false)
*/
private $timers;
/** /**
* @ORM\OneToMany(targetEntity="Penalty", mappedBy="task", cascade={"persist"}, orphanRemoval=false) * @ORM\OneToMany(targetEntity="Penalty", mappedBy="task", cascade={"persist"}, orphanRemoval=false)
*/ */
@@ -90,6 +95,7 @@ class Task
public function __construct() public function __construct()
{ {
$this->events = new ArrayCollection(); $this->events = new ArrayCollection();
$this->timers = new ArrayCollection();
$this->penaltys = new ArrayCollection(); $this->penaltys = new ArrayCollection();
} }
@@ -191,6 +197,24 @@ class Task
return $this; return $this;
} }
/**
* @return Collection|Timer[]
*/
public function getTimers(): Collection
{
return $this->tasks;
}
public function addTimer(Timer $timer): self
{
if (!$this->timers->contains($timer)) {
$this->timers[] = $timer;
$timer->setTask($this);
}
return $this;
}
public function removeEvent(Event $event): self public function removeEvent(Event $event): self
{ {
if ($this->events->contains($event)) { if ($this->events->contains($event)) {
@@ -235,4 +259,17 @@ class Task
return $this; return $this;
} }
public function removeTimer(Timer $timer): self
{
if ($this->timers->contains($timer)) {
$this->timers->removeElement($timer);
// set the owning side to null (unless already changed)
if ($timer->getTask() === $this) {
$timer->setTask(null);
}
}
return $this;
}
} }

View File

@@ -0,0 +1,142 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Timer
*
* @ORM\Table(name="timer")
* @ORM\Entity(repositoryClass="App\Repository\TimerRepository")
*/
class Timer
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="start", type="datetime")
*
*/
private $start;
/**
* @ORM\Column(name="end", type="datetime")
*
*/
private $end;
/**
* @ORM\Column(name="duration", type="datetime")
*
*/
private $duration;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @ORM\ManyToOne(targetEntity="User")
*/
private $user;
/**
* @ORM\ManyToOne(targetEntity="Task", inversedBy="timers"))
*/
private $task;
public function __construct()
{
$this->start = new \DateTime();
$this->end = new \DateTime();
$this->duration = new \DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getStart(): ?\DateTimeInterface
{
return $this->start;
}
public function setStart(\DateTimeInterface $start): self
{
$this->start = $start;
return $this;
}
public function getEnd(): ?\DateTimeInterface
{
return $this->end;
}
public function setEnd(\DateTimeInterface $end): self
{
$this->end = $end;
return $this;
}
public function getDuration(): ?\DateTimeInterface
{
return $this->duration;
}
public function setDuration(\DateTimeInterface $duration): self
{
$this->duration = $duration;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getTask(): ?Task
{
return $this->task;
}
public function setTask(?Task $task): self
{
$this->task = $task;
return $this;
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\ChoiceList\ChoiceList;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Tetranz\Select2EntityBundle\Form\Type\Select2EntityType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
class TimerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('submit',
SubmitType::class, [
"label" => "Valider",
"attr" => ["class" => "btn btn-success no-print"],
]
);
$builder->add('task',
EntityType::class, [
"label" => "Tâche",
"class" => "App:Task",
"choice_label" => function ($task) {
return $task->getDisplayname();},
"disabled" => false,
"required" => true,
"multiple" => false,
"placeholder" => "Selectionner une Tâche",
]
);
$builder->add('description',
TextType::class, [
"label" => "Description",
"required" => false
]
);
$builder->add('start',
DateTimeType::class, [
"label" =>"Début",
"date_widget" => "single_text",
"time_widget" => "single_text",
"format" => "yyyy-MM-dd HH:mm",
]
);
$builder->add('end',
DateTimeType::class, [
"label" =>"Fin",
"date_widget" => "single_text",
"time_widget" => "single_text",
"format" => "yyyy-MM-dd HH:mm",
]
);
$builder->add('duration',
TimeType::class, [
"label" =>"Durée",
"widget" => "single_text",
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\Entity\Timer',
'mode' => 'string',
));
}
}

View File

@@ -157,7 +157,6 @@
{% endif %} {% endif %}
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label required" for="taskupdate"> <label class="control-label required" for="taskupdate">
Project<span class="mandatory">*</span> Project<span class="mandatory">*</span>
@@ -169,7 +168,19 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="amupdate">
<label class="custom-control-label" for="amupdate">Evènement sur la matinée</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="apupdate">
<label class="custom-control-label" for="apupdate">Evènement sur l'après-midi</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="description" class="control-label"> <label for="description" class="control-label">
Description Description
@@ -301,22 +312,43 @@ function eventSelect(selectionInfo) {
// Formulaire Modification d'un évelement // Formulaire Modification d'un évelement
function eventClick(info) { function eventClick(info) {
if(info.event.extendedProps.editable) { if(info.event.extendedProps.editable) {
console.log(info.event.id); console.log(info.event);
var id=info.event.id; var id=info.event.id;
var description=info.event.extendedProps.description; var description=info.event.extendedProps.description;
var userid=info.event.extendedProps.userid; var userid=info.event.extendedProps.userid;
var taskid=info.event.extendedProps.taskid; var taskid=info.event.extendedProps.taskid;
var fgastreinte=info.event.extendedProps.astreinte; var fgastreinte=info.event.extendedProps.astreinte;
var eventallday = info.event.allDay;
var eventstart = info.event.start;
//var eventend = info.event.end;
$('#userupdate').val(userid).trigger("change"); $('#userupdate').val(userid).trigger("change");
$('#taskupdate').val(taskid).trigger("change"); $('#taskupdate').val(taskid).trigger("change");
$('#modalupdate #idevent').val(id); $('#modalupdate #idevent').val(id);
$('#modalupdate #fgastreinte').val(fgastreinte); $('#modalupdate #fgastreinte').val(fgastreinte);
$('#modalupdate #description').val(description); $('#modalupdate #description').val(description);
$("#modalupdate .alert").remove(); $("#modalupdate .alert").remove();
{# $('#modalupdate #amupdate').attr("disabled",eventallday);
$('#modalupdate #apupdate').attr("disabled",eventallday); #}
if (!eventallday){
eStart = eventstart.toString().split(" ")[4].split(":")[0]
//eEnd = eventend.toString().split(" ")[4].split(":")[0]
if (eStart == 09){
$('#modalupdate #amupdate').prop("checked",true);
$('#modalupdate #apupdate').prop("checked",false);
}
if (eStart == 13){
$('#modalupdate #amupdate').prop("checked",false);
$('#modalupdate #apupdate').prop("checked",true);
}
}else{
$('#modalupdate #amupdate').prop("checked",true);
$('#modalupdate #apupdate').prop("checked",true);
}
// Formulaire de création d'un évènement // Formulaire de création d'un évènement
$('#modalupdate').modal(); $('#modalupdate').modal();
} }
@@ -378,6 +410,8 @@ function eventUpdate() {
idevent: $("#modalupdate #idevent").val(), idevent: $("#modalupdate #idevent").val(),
iduser: $("#userupdate").val(), iduser: $("#userupdate").val(),
idtask: $("#taskupdate").val(), idtask: $("#taskupdate").val(),
am: $("#modalupdate #amupdate").prop("checked"),
ap: $("#modalupdate #apupdate").prop("checked"),
fgastreinte: $("#modalupdate #fgastreinte").val(), fgastreinte: $("#modalupdate #fgastreinte").val(),
description: $("#modalupdate #description").val() description: $("#modalupdate #description").val()
}, },

View File

@@ -1,15 +1,14 @@
{% block body %} {% block body %}
Client;Projet;Tâche;Utilisateur;Année;Semaine;Cumul;
{% for project in projects %} {% for project in projects %}
{{ project.projectname|raw }} {% for year,weeks in project.weeks_by_task_by_user %}
{% for years in project %} {% for week in weeks %}
{% for year,weeks in years %} {% for task in week.tasks%}
{{year}} {% for user in task.users%}
;{% for week in weeks %}S{{week.weeknumber}};{% endfor %} {{project.customer}};{{project.name}};{{task.taskname}};{{user.displayname}};{{year}};S{{week.weeknumber}};{{user.cumul|replace({".": ","})}};
{% endfor %}
;{% for week in weeks %}{{week.cumul}};{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@@ -114,7 +114,7 @@
{% if access!="customer" and app.user %} {% if access!="customer" and app.user %}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<a href="{{ path('app_report_export') }}" class="btn btn-success">Exporter2</a> <a href="{{ path('app_report_export') }}" class="btn btn-success">Exporter</a>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -0,0 +1,110 @@
{% extends "base.html.twig" %}
{% block localstyle %}
td {
padding:5px !important;
}
{% endblock %}
{% block body %}
{{ form_start(form) }}
<h1 class="page-header">
{% if mode=="update" %}
Modification TIMER
{% elseif mode=="submit" %}
Création TIMER
{% endif %}
</h1>
{{ form_widget(form.submit) }}
<a class="btn btn-secondary" href={{ path('app_timer') }}>Annuler</a>
{% if mode=="update" %}
<a href="{{ path('app_timer_delete',{'id':timer.id}) }}"
class="btn btn-danger float-right"
data-method="delete"
data-confirm="Êtes-vous sûr de vouloir supprimer cet entregistrement ?">
Supprimer
</a>
{% endif %}
<br><br>
{% if app.session.flashbag.has('error') %}
<div class='alert alert-danger' style='margin: 5px 0px'>
<strong>Erreur</strong><br>
{% for flashMessage in app.session.flashbag.get('error') %}
{{ flashMessage }}<br>
{% endfor %}
</div>
{% endif %}
{% if app.session.flashbag.has('notice') %}
<div class='alert alert-info' style='margin: 5px 0px'>
<strong>Information</strong><br>
{% for flashMessage in app.session.flashbag.get('notice') %}
{{ flashMessage }}<br>
{% endfor %}
</div>
{% endif %}
<div class="card">
<div class="card-header">
<i class="fa fa-pencil-alt fa-fw"></i> Informations
</div>
<div class="card-body">
{{ form_row(form.task) }}
{{ form_row(form.description) }}
{{ form_row(form.start) }}
{{ form_row(form.end) }}
{{ form_row(form.duration) }}
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% block localjavascript %}
$("#timer_task").addClass("select2entity");
/*
* 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;
}
/* 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 String(hours).pad(2, "0") + ":" + String(minutes).pad(2, "0");
}
$("#timer_start_time,#timer_end_time").change(function(){
console.log($("#timer_start_date").val() +"T"+$("#timer_start_time").val()+":00Z")
console.log($("#timer_end_date").val()+"T"+$("#timer_end_time").val()+":00Z")
var start = Date.parse($("#timer_start_date").val() +"T"+$("#timer_start_time").val()+":00Z");
var end = Date.parse($("#timer_end_date").val()+"T"+$("#timer_end_time").val()+":00Z");
var diff = end - start;
$("#timer_duration").val(formatTime(diff))
})
{% endblock %}

View File

@@ -0,0 +1,186 @@
{% extends "base.html.twig" %}
{% block head_style %}
{{ encore_entry_link_tags('app') }}
{{ encore_entry_link_tags('fullcalendar') }}
{% endblock head_style %}
{% block localstyle %}
.fc-header-toolbar h2 {
text-transform: uppercase;
}
.fc-day-grid-event {
padding:0px;
border-radius:0px;
border: none;
}
.fc-content {
height: 40px;
}
.fc-title {
font-weight: bolder;
font-size: 12px;
}
.eventAvatar {
width: 20px;
margin: 0px 5px 0px 0px;
float: left;
}
.eventInfo{
margin: -5px 5px 0px 0px;
clear: both;
}
.eventUser{
clear: both;
}
.eventEstimate {
margin: -3px 10px;
}
{% endblock %}
{% block body %}
<div id="fullcalendar" style="width:100%; margin-top:10px;"></div>
{{ encore_entry_script_tags('fullcalendar') }}
{% endblock %}
{% block localjavascript %}
$(document).ready(function() {
$("#modalsubmit #user").select2({
theme: 'bootstrap4',
language: "fr"
});
$("#modalsubmit #task").select2({
placeholder: "Selectionnez un projet",
allowClear: true,
theme: 'bootstrap4',
language: "fr"
});
$("#modalupdate #user").select2({
theme: 'bootstrap4',
language: "fr"
});
$("#modalupdate #task").select2({
placeholder: "Selectionnez un projet",
theme: 'bootstrap4',
language: "fr"
});
});
// Rendu d'un évenement
function eventRender(info) {
console.log(info.event.extendedProps);
// Récupération des divers élements du rendu event
var content=$(info.el).children('.fc-content');
var title=$(content).children('.fc-title');
// Ajouter l'avatar
content.prepend("<img src="+info.event.extendedProps.avatar+" class='eventAvatar'>");
content.append("<span class='eventUser float-left small'>"+info.event.extendedProps.username+"</span>");
var eventInfo=$(content).children('.eventUser');
// Ajout container
content.append("<span class='eventInfo float-right'></span>");
var eventInfo=$(content).children('.eventInfo');
// Ajouter le verrou si event non editable
if(info.event.extendedProps.locked) {
eventInfo.append("<i class='fa fa-lock float-right'></i>");
}
// Ajout estimation
eventInfo.append("<span class='eventEstimate float-right small'>"+info.event.extendedProps.estimate+"</span>");
// Description
content.attr("title",info.event.extendedProps.fulldescription);
}
// Formulaire Création d'un évelement
var allDay;
function eventSelect(selectionInfo) {
var start=moment(selectionInfo.start);
var end=moment(selectionInfo.end);
var end=end.subtract(1, 'd');
allDay=(start.format("DD/MM/YYYY") != end.format("DD/MM/YYYY"));
// Controle
if(start.month()!=end.month()) {
alert("Une tâche ne peut être sur deux mois différents");
return false;
}
if(start.week()!=end.week()) {
alert("Une tâche ne peut être sur deux semaines différentes");
return false;
}
// Valeur par défaut
{% if (is_granted('ROLE_ADMIN') or is_granted('ROLE_VALIDATOR') or is_granted('ROLE_MASTER')) and app.session.get('iduser')!="all" %}
$('#usersubmit').val({{app.session.get('iduser')}}).trigger("change");
{% else %}
$('#usersubmit').val({{app.user.id}}).trigger("change");
{% endif %}
// Si jour de fin un samedi ou un dimanche : on est forcement en astreinte
if(moment(end).day()==0||moment(end).day()==6) {
$("#modalsubmit #astreinte").prop("checked",true);
$("#modalsubmit #astreinte").attr("disabled",true);
allDay=true;
}
else {
$("#modalsubmit #astreinte").prop('checked', false);
$("#modalsubmit #astreinte").attr('disabled', false);
}
$('#modalsubmit #amsubmit').prop("checked",true);
$('#modalsubmit #apsubmit').prop("checked",true);
$('#modalsubmit #amsubmit').attr("disabled",allDay);
$('#modalsubmit #apsubmit').attr("disabled",allDay);
$('#modalsubmit #start').val(start.format("YYYY-MM-DD"));
$('#modalsubmit #end').val(end.format("YYYY-MM-DD"));
$('#modalsubmit #description').val("");
$("#modalsubmit .alert").remove();
// Formulaire de création d'un évènement
$('#modalsubmit').modal();
}
// On change astreinte
$("#astreinte").change(function() {
console.log(allDay)
if(this.checked) {
$("#amsubmit").prop("disabled",true);
$("#apsubmit").prop("disabled",true);
$('#modalsubmit #amsubmit').prop("checked",true);
$('#modalsubmit #apsubmit').prop("checked",true);
}
else {
$("#amsubmit").prop("disabled",allDay);
$("#apsubmit").prop("disabled",allDay);
$('#modalsubmit #amsubmit').prop("checked",true);
$('#modalsubmit #apsubmit').prop("checked",true);
}
});
{% endblock %}

View File

@@ -0,0 +1,694 @@
{% extends "base.html.twig" %}
{% block localstyle %}
#timer-task {
width:300px;
}
#timer-desc {
width:300px;
}
{% endblock %}
{% block body %}
<h1 class="page-header">
SUIVI HORAIRE
</h1>
<div id="timer" class="card">
<div class="card-header">
<a class="btn btn-success" style="float:right" href={{ path('app_timer_view') }}>Voir le calendrier</a>
Lancer un Timer :
<select class="select2entity" id="timer-task" name="timer-task">
<option> Tâche </option>
{% for task in tasks %}
<option value="{{task.id}}">{{task.displayname}}</option>
{% endfor %}
</select>
<input id="timer-desc" name="timer-desc" placeholder="Description" />
<a href='#' title='Add' id='addtimer'>
<i class='fas fa-plus-circle'></i>
</a>
</div>
</div>
<div>
<table id="task-table" class="table table-striped table-hover" >
</table>
</div>
<a class="btn btn-success" href={{ path('app_timer_submit') }}>Créer un Timer</a>
<p></p>
<div class="dataTable_wrapper">
<table class="table table-striped table-bordered table-hover small" id="dataTables" style="width:100%">
<thead>
<tr>
<th width="70px" class="no-sort">Tâche</th>
<th width="150px" class="no-sort">Description</th>
<th width="150px">Début</th>
<th width="100px">Fin</th>
<th width="100px">Durée</th>
<th width="100px" class="text-center no-string">Actions</th>
</tr>
</thead>
<tbody>
{%for timer in timers %}
<tr>
<td>{{ timer.task.displayname }}</td>
<td>{{ timer.description }}</td>
<td>{{ timer.start|date("d/m/Y H:i") }}</td>
<td>{{ timer.end|date("d/m/Y H:i") }}</td>
<td>{{ timer.duration|date("H:i") }}</td>
<td>
<a href="{{path("app_timer_update",{id:timer.id})}}">
<i class="fa fa-file"></i>
</a>
&nbsp;&nbsp;
<a href="{{ path('app_timer_delete',{'id':timer.id}) }}"
data-method="delete"
data-confirm="Êtes-vous sûr de vouloir supprimer cet entregistrement ?">
<i class="fa fa-trash-alt"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% 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("<div class='alert alert-danger' style='margin: 5px 0px'>"+response.error+"</div>");
}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 = $("<tr />");
var startStopLink = $(
"<a href='#' class='start-stop-link' title='Start/stop'>"
+ "<i class='fa " + computeStartStopLinkImageUrl(task.getState()) + "'></i>"
);
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 = $(
"<a href='#' title='Reset'>"
+ "<i class='fa fa-undo'></i>"
);
resetLink.click(function() {
task.reset();
saveTasks();
return false;
});
var deleteLink = $(
"<a href='#' title='Delete' class='deletetask'>"
+ "<i class='fa fa-trash-alt' class='deletetask'></i>"
);
deleteLink.click(function() {
if (confirm("Do you really want to delete task \"" + task.getName() + "\"?")) {
tasks.remove(index);
saveTasks();
}
return false;
});
var saveLink = $(
"<a href='#' title='Save' class='savetask'>"
+ "<i class='fa fa-download' class='savetask'></i>"
);
saveLink.click(function() {
tasks.remove(index);
saveTasks();
task.save(task);
return false;
});
result
.addClass("state-" + task.getState())
.append($("<td class='task-name' />").text(task.getName()))
.append($("<td class='task-description' />").text(task.getDescription()))
.append($("<td class='task-time' />").text(formatTime(task.getTimeSpent())))
.append($("<td class='task-actions' />")
.append(startStopLink)
.append("&nbsp;&nbsp;")
.append(resetLink)
.append("&nbsp;&nbsp;")
.append(saveLink)
.append("&nbsp;&nbsp;&nbsp;&nbsp;")
.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 %}

View File

@@ -359,6 +359,12 @@
</a> </a>
</li> </li>
<li>
<a href="{{path("app_timer")}}">
<i class="fa fa-stopwatch"></i>Suivi Horaire
</a>
</li>
<li class="last"> <li class="last">
<a href="{{path("app_holiday")}}"> <a href="{{path("app_holiday")}}">
<i class="fa fa-cocktail"></i>Mes Congés <i class="fa fa-cocktail"></i>Mes Congés