2014-04-11 05:09:27 +04:00
|
|
|
(function() {
|
|
|
|
|
|
|
|
'use_strict';
|
|
|
|
|
|
|
|
return {
|
2014-04-25 18:23:48 +04:00
|
|
|
requests: {
|
|
|
|
fetchAudits: function() {
|
|
|
|
return {
|
|
|
|
url: helpers.fmt(
|
|
|
|
'/api/v2/tickets/%@/audits.json?include=users',
|
|
|
|
this.ticket().id()
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-04-11 05:09:27 +04:00
|
|
|
events: {
|
|
|
|
'app.activated' : 'onAppActivated',
|
|
|
|
'app.deactivated' : 'onAppFocusOut',
|
|
|
|
'app.willDestroy' : 'onAppWillDestroy',
|
|
|
|
'ticket.save' : 'onTicketSave',
|
|
|
|
'ticket.form.id.changed' : 'onTicketFormChanged',
|
2014-04-25 18:23:48 +04:00
|
|
|
'fetchAudits.done' : 'onFetchAuditsDone',
|
2014-04-11 05:09:27 +04:00
|
|
|
'click .pause' : 'onPauseClicked',
|
2014-04-28 18:59:03 +04:00
|
|
|
'click .play' : 'onPlayClicked',
|
2014-04-11 05:09:27 +04:00
|
|
|
'click .reset' : 'onResetClicked',
|
|
|
|
'click .modal-save' : 'onModalSaveClicked',
|
2014-04-25 18:23:48 +04:00
|
|
|
'click a.timelogs-opener:not([disabled])' : 'onTimeLogsContainerClicked',
|
2014-04-11 05:09:27 +04:00
|
|
|
'shown .modal' : 'onModalShown',
|
2014-04-28 18:59:03 +04:00
|
|
|
'hidden .modal' : 'onModalHidden',
|
|
|
|
'click .expand-bar' : 'onTimelogsClicked'
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* EVENT CALLBACKS
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
onAppActivated: function(app) {
|
|
|
|
if (app.firstLoad) {
|
|
|
|
_.defer(this.initialize.bind(this));
|
2014-04-25 18:23:48 +04:00
|
|
|
|
|
|
|
if (this.ticket().id() && this.setting('display_timelogs')) {
|
|
|
|
this.ajax('fetchAudits');
|
|
|
|
}
|
2014-04-11 05:09:27 +04:00
|
|
|
} else {
|
|
|
|
this.onAppFocusIn();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onAppWillDestroy: function() {
|
|
|
|
clearInterval(this.timeLoopID);
|
|
|
|
},
|
|
|
|
|
|
|
|
onAppFocusOut: function() {
|
|
|
|
if (this.setting('auto_pause_resume')) {
|
|
|
|
this.autoPause();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onAppFocusIn: function() {
|
|
|
|
if (this.setting('auto_pause_resume') &&
|
|
|
|
!this.manuallyPaused) {
|
|
|
|
this.autoResume();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onTicketFormChanged: function() {
|
|
|
|
_.defer(this.hideFields.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
onTicketSave: function() {
|
|
|
|
if (this.setting('time_submission')) {
|
|
|
|
return this.promise(function(done, fail) {
|
|
|
|
this.saveHookPromiseDone = done;
|
|
|
|
this.saveHookPromiseFail = fail;
|
|
|
|
|
|
|
|
this.renderTimeModal();
|
|
|
|
}.bind(this));
|
|
|
|
} else {
|
|
|
|
this.updateTime(this.elapsedTime);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-04-25 18:23:48 +04:00
|
|
|
onFetchAuditsDone: function(data) {
|
2014-04-28 18:59:03 +04:00
|
|
|
var status = "",
|
|
|
|
timelogs = _.reduce(data.audits, function(memo, audit) {
|
|
|
|
var newStatus = _.find(audit.events, function(event) {
|
|
|
|
return event.field_name == 'status';
|
|
|
|
}, this),
|
|
|
|
event = _.find(audit.events, function(event) {
|
|
|
|
return event.field_name == this.setting('time_field_id');
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
if (newStatus){
|
|
|
|
status = newStatus.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event) {
|
|
|
|
memo.push({
|
|
|
|
time: this.TimeHelper.secondsToTimeString(parseInt(event.value, 0)),
|
|
|
|
date: new Date(audit.created_at).toLocaleString(),
|
|
|
|
status: status,
|
|
|
|
localized_status: this.I18n.t(helpers.fmt('statuses.%@', status)),
|
|
|
|
user: _.find(data.users, function(user) {
|
|
|
|
return user.id === audit.author_id;
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return memo;
|
|
|
|
}, [], this);
|
2014-04-25 18:23:48 +04:00
|
|
|
|
|
|
|
this.renderTimelogs(timelogs.reverse());
|
|
|
|
},
|
|
|
|
|
2014-04-11 05:09:27 +04:00
|
|
|
onPauseClicked: function(e) {
|
|
|
|
var $el = this.$(e.currentTarget);
|
|
|
|
|
2014-04-28 18:59:03 +04:00
|
|
|
$el.find('i').addClass('active');
|
|
|
|
this.$('.play i').removeClass('active');
|
2014-04-11 05:09:27 +04:00
|
|
|
|
|
|
|
this.manuallyPaused = this.paused = true;
|
|
|
|
},
|
|
|
|
|
2014-04-28 18:59:03 +04:00
|
|
|
onPlayClicked: function(e) {
|
2014-04-11 05:09:27 +04:00
|
|
|
var $el = this.$(e.currentTarget);
|
|
|
|
|
2014-04-28 18:59:03 +04:00
|
|
|
$el.find('i').addClass('active');
|
|
|
|
this.$('.pause i').removeClass('active');
|
2014-04-11 05:09:27 +04:00
|
|
|
|
|
|
|
this.manuallyPaused = this.paused = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
onResetClicked: function() {
|
|
|
|
this.elapsedTime = 0;
|
|
|
|
},
|
|
|
|
|
2014-04-28 18:59:03 +04:00
|
|
|
onTimelogsClicked: function() {
|
|
|
|
this.$('.timelogs-container').slideToggle();
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
onModalSaveClicked: function() {
|
|
|
|
var timeString = this.$('.modal-time').val();
|
|
|
|
|
|
|
|
try {
|
2014-04-25 16:59:32 +04:00
|
|
|
this.updateTime(this.TimeHelper.timeStringToSeconds(timeString));
|
2014-04-11 05:09:27 +04:00
|
|
|
this.saveHookPromiseIsDone = true; // Flag that saveHookPromiseDone is gonna be called after hiding the modal
|
|
|
|
this.$('.modal').modal('hide');
|
|
|
|
this.saveHookPromiseDone();
|
|
|
|
} catch (e) {
|
|
|
|
if (e.message == 'bad_time_format') {
|
|
|
|
services.notify(this.I18n.t('errors.bad_time_format'), alert);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onModalShown: function() {
|
|
|
|
var timeout = 15,
|
|
|
|
$timeout = this.$('span.modal-timer'),
|
|
|
|
$modal = this.$('.modal');
|
|
|
|
|
|
|
|
this.modalTimeoutID = setInterval(function() {
|
|
|
|
timeout -= 1;
|
|
|
|
|
|
|
|
$timeout.html(timeout);
|
|
|
|
|
|
|
|
if (timeout === 0) {
|
|
|
|
$modal.modal('hide');
|
|
|
|
}
|
|
|
|
}.bind(this), 1000);
|
|
|
|
},
|
|
|
|
|
|
|
|
onModalHidden: function() {
|
|
|
|
clearInterval(this.modalTimeoutID);
|
|
|
|
|
|
|
|
if (!this.saveHookPromiseIsDone) {
|
|
|
|
this.saveHookPromiseFail(this.I18n.t('errors.save_hook'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* METHODS
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
initialize: function() {
|
|
|
|
this.hideFields();
|
|
|
|
|
|
|
|
this.timeLoopID = this.setTimeLoop();
|
|
|
|
|
|
|
|
this.switchTo('main', {
|
|
|
|
manual_pause_resume: this.setting('manual_pause_resume'),
|
|
|
|
display_reset: this.setting('reset'),
|
2014-04-25 18:23:48 +04:00
|
|
|
display_timer: this.setting('display_timer')
|
2014-04-11 05:09:27 +04:00
|
|
|
});
|
|
|
|
|
|
|
|
this.$('tr').tooltip({ placement: 'left', html: true });
|
|
|
|
},
|
|
|
|
|
|
|
|
updateMainView: function(time) {
|
2014-04-25 16:59:32 +04:00
|
|
|
this.$('.live-timer').html(this.TimeHelper.secondsToTimeString(time));
|
|
|
|
this.$('.live-totaltimer').html(this.TimeHelper.secondsToTimeString(
|
|
|
|
this.totalTime() + time
|
2014-04-11 05:09:27 +04:00
|
|
|
));
|
|
|
|
},
|
|
|
|
|
2014-04-25 18:23:48 +04:00
|
|
|
renderTimelogs: function(timelogs) {
|
|
|
|
this.$('.timelogs-container')
|
|
|
|
.html(this.renderTemplate('timelogs', {
|
|
|
|
timelogs: timelogs,
|
|
|
|
csv_filename: helpers.fmt('ticket-timelogs-%@',
|
|
|
|
this.ticket().id()),
|
|
|
|
csv_string: encodeURI(this.timelogsToCsvString(timelogs))
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
2014-04-25 19:10:54 +04:00
|
|
|
this.$('tr').tooltip({ placement: 'left', html: true });
|
|
|
|
|
2014-04-25 18:23:48 +04:00
|
|
|
this.$('.timelogs-opener')
|
|
|
|
.removeAttr('disabled')
|
|
|
|
.removeClass('disabled');
|
|
|
|
},
|
|
|
|
|
2014-04-11 05:09:27 +04:00
|
|
|
hideFields: function() {
|
2014-04-25 16:59:32 +04:00
|
|
|
_.each([this.timeFieldLabel(), this.totalTimeFieldLabel()], function(f) {
|
2014-04-11 05:09:27 +04:00
|
|
|
var field = this.ticketFields(f);
|
|
|
|
|
|
|
|
if (field) {
|
|
|
|
field.hide();
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TIME RELATED
|
|
|
|
*/
|
|
|
|
|
|
|
|
setTimeLoop: function() {
|
|
|
|
this.elapsedTime = 0;
|
|
|
|
|
|
|
|
return setInterval(function() {
|
|
|
|
if (!this.paused) {
|
2014-04-25 16:59:32 +04:00
|
|
|
// Update elapsed time by 1 second
|
|
|
|
this.elapsedTime += 1;
|
2014-04-11 05:09:27 +04:00
|
|
|
|
|
|
|
this.updateMainView(this.elapsedTime);
|
|
|
|
}
|
2014-04-25 16:59:32 +04:00
|
|
|
}.bind(this), 1000);
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
updateTime: function(time) {
|
2014-04-25 16:59:32 +04:00
|
|
|
this.time(time);
|
|
|
|
this.totalTime(this.totalTime() + time);
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
autoResume: function() {
|
|
|
|
this.paused = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
autoPause: function() {
|
|
|
|
this.paused = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
renderTimeModal: function() {
|
2014-04-25 16:59:32 +04:00
|
|
|
this.$('.modal-time').val(this.TimeHelper.secondsToTimeString(this.elapsedTime));
|
2014-04-11 05:09:27 +04:00
|
|
|
this.$('.modal').modal('show');
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* HELPERS
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
time: function(time) {
|
|
|
|
return this.getOrSetField(this.timeFieldLabel(), time);
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
totalTime: function(time) {
|
|
|
|
return this.getOrSetField(this.totalTimeFieldLabel(), time);
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
totalTimeFieldLabel: function() {
|
|
|
|
return this.buidFieldLabel(this.setting('total_time_field_id'));
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
timeFieldLabel: function() {
|
|
|
|
return this.buidFieldLabel(this.setting('time_field_id'));
|
|
|
|
},
|
|
|
|
|
|
|
|
buidFieldLabel: function(id) {
|
|
|
|
return helpers.fmt('custom_field_%@', id);
|
|
|
|
},
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
getOrSetField: function(fieldLabel, value) {
|
|
|
|
if (value) {
|
|
|
|
return this.ticket().customField(fieldLabel, value);
|
|
|
|
}
|
2014-04-11 05:09:27 +04:00
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
return parseInt((this.ticket().customField(fieldLabel) || 0), 0);
|
|
|
|
},
|
2014-04-11 05:09:27 +04:00
|
|
|
|
2014-04-25 18:23:48 +04:00
|
|
|
timelogsToCsvString: function(timelogs) {
|
|
|
|
return _.reduce(timelogs, function(memo, timelog) {
|
|
|
|
return memo + helpers.fmt('%@\n', [ timelog.time, timelog.user.name, timelog.date].join());
|
|
|
|
}, 'Time,Submitter,Submitted At\n', this);
|
|
|
|
},
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
TimeHelper: {
|
|
|
|
secondsToTimeString: function(seconds) {
|
|
|
|
var hours = Math.floor(seconds / 3600),
|
2014-04-25 19:10:54 +04:00
|
|
|
minutes = Math.floor((seconds - (hours * 3600)) / 60),
|
2014-04-25 16:59:32 +04:00
|
|
|
secs = seconds - (hours * 3600) - (minutes * 60);
|
2014-04-11 05:09:27 +04:00
|
|
|
|
|
|
|
return helpers.fmt('%@:%@:%@',
|
|
|
|
this.addInsignificantZero(hours),
|
|
|
|
this.addInsignificantZero(minutes),
|
2014-04-25 16:59:32 +04:00
|
|
|
this.addInsignificantZero(secs));
|
2014-04-11 05:09:27 +04:00
|
|
|
},
|
|
|
|
|
2014-04-25 16:59:32 +04:00
|
|
|
timeStringToSeconds: function(timeString) {
|
2014-04-11 05:09:27 +04:00
|
|
|
var re = /^([\d]{2}):([\d]{2}):([\d]{2})$/,
|
2014-04-25 16:59:32 +04:00
|
|
|
result = re.exec(timeString);
|
2014-04-11 05:09:27 +04:00
|
|
|
|
|
|
|
if (!result ||
|
|
|
|
result.length != 4) {
|
|
|
|
throw { message: 'bad_time_format' };
|
|
|
|
} else {
|
2014-04-25 16:59:32 +04:00
|
|
|
return (parseInt(result[1], 10) * 3600) +
|
|
|
|
(parseInt(result[2], 10) * 60) +
|
|
|
|
(parseInt(result[3], 10));
|
2014-04-11 05:09:27 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addInsignificantZero: function(n) {
|
|
|
|
return ( n < 10 ? '0' : '') + n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}());
|