first commit
This commit is contained in:
Коммит
30367fca13
|
@ -0,0 +1,48 @@
|
|||
Copyright 2012 Zendesk
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
ADDITIONAL TERMS AND CONDITIONS
|
||||
|
||||
Capitalized terms used herein have the meaning set forth in the
|
||||
Zendesk, Inc. (“Zendesk”) Terms of Service (available at www.zendesk.com/company/terms)
|
||||
(the “Terms”). The software made available herein constitutes the source code for a
|
||||
Zendesk Application (“Application Software”) which may be implemented to
|
||||
enable features or functionality to be utilized in connection with a subscription
|
||||
to the Service. Notwithstanding anything to the contrary set forth in the Terms
|
||||
or any other agreement by and between you and Zendesk, Application Software is
|
||||
provided “AS IS” and on an “AS AVAILABLE” basis, and that Zendesk makes no warranty
|
||||
as to the Application Software. ZENDESK DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND THOSE ARISING FROM A COURSE
|
||||
OF DEALING OR USAGE OF TRADE RELATED TO THE APPLICATION SOFTWARE, ITS USE OR ANY INABILITY
|
||||
TO USE IT OR THE RESULTS OF ITS USE.
|
||||
|
||||
Zendesk makes Zendesk Applications available for use through the user interface of
|
||||
the Service in the manner described (for each Zendesk Application) at www.zendesk.com/apps
|
||||
(“Service Implementation”). Use of Application Software, other than in connection with a
|
||||
Service Implementation is subject to the following risks and conditions: (i) the Application
|
||||
Software may contain errors, design flaws or other problems; and (ii) use of the Application
|
||||
Software may result in unexpected results, loss of data, project delays or other unpredictable
|
||||
damage or loss. Zendesk only supports the Application Software currently available and installed
|
||||
through the Service Implementation. By utilizing Application Software other than in connection
|
||||
with the Service Implementation, you are agreeing that Zendesk shall have no obligation to
|
||||
correct any bugs, defects or errors in the Application Software you have utilized or
|
||||
otherwise to support or maintain the Application Software you have utilized.
|
||||
|
||||
If you elect to utilize any Application Software other than through a Service Implementation,
|
||||
you are agreeing to release Zendesk from any claim with regard to the Application Software,
|
||||
its operation, availability or its failure to operate or be available.
|
||||
Without limiting the generality of the foregoing, You acknowledge and agree that neither the use,
|
||||
availability nor operation of any Application Software shall be subject to any service level
|
||||
commitment applicable to the Service.
|
|
@ -0,0 +1,46 @@
|
|||
:warning: *Use of this software is subject to important terms and conditions as set forth in the License file* :warning:
|
||||
|
||||
# Time Tracking App
|
||||
|
||||
## Description:
|
||||
|
||||
Helps you track time on your tickets. You'll be able to submit custom time and restrict time submission.
|
||||
|
||||
## App location:
|
||||
|
||||
* Ticket sidebar
|
||||
* New Ticket sidebar
|
||||
|
||||
## Features:
|
||||
|
||||
* Track current spent time on a ticket.
|
||||
* Track total spent time on a ticket.
|
||||
* Ability to select in which unit to report the total time spent (milliseconds, seconds, minutes).
|
||||
* Log every time submission (time submitted, agent name, status submitted, submission date). *Can be turned off*.
|
||||
* Download time logs as a csv file.
|
||||
* Ability to auto-pause/resume the timer when the agent isn't focused on the ticket. *Can be turned off*.
|
||||
* Ability for the agent to manually pause/resume the timer. *Can be turned off*.
|
||||
* Ability for the agent to restart the timer. *Can be turned off*.
|
||||
* Ability for the agent to submit his own spent time. *Can be turned off*.
|
||||
|
||||
## Set-up/installation instructions:
|
||||
|
||||
You will need to create 2 ticket fields:
|
||||
* A Numeric ticket field that will contain the total time spent (used to report on GoodData).
|
||||
* A Multi-line text ticket field that will store the app configuration and time logs.
|
||||
|
||||
After installation, on the app settings page:
|
||||
* Put the previously create Numeric ticket field ID in the "Time Field ID" setting.
|
||||
* Put the previously create Multi-line ticket field ID in the "Config Field ID" setting.
|
||||
* Enable/Disable settings to customise as you need it.
|
||||
|
||||
## Contribution:
|
||||
|
||||
Pull requests are welcome.
|
||||
|
||||
## Screenshot(s):
|
||||
|
||||
Default view of the app:
|
||||
|
||||
![](http://i.imgur.com/V1x1coZ.png)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
.current-timer {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.live-timer {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: inherit;
|
||||
font-size: 20px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.timelogs-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: none;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: inline-block;
|
||||
padding-top: 7px;
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
(function() {
|
||||
|
||||
'use_strict';
|
||||
|
||||
return {
|
||||
events: {
|
||||
'app.activated' : 'onAppActivated',
|
||||
'app.deactivated' : 'onAppFocusOut',
|
||||
'app.willDestroy' : 'onAppWillDestroy',
|
||||
'ticket.save' : 'onTicketSave',
|
||||
'ticket.form.id.changed' : 'onTicketFormChanged',
|
||||
'click .pause' : 'onPauseClicked',
|
||||
'click .resume' : 'onResumeClicked',
|
||||
'click .reset' : 'onResetClicked',
|
||||
'click .modal-save' : 'onModalSaveClicked',
|
||||
'click .timelogs-opener' : 'onTimeLogsContainerClicked',
|
||||
'shown .modal' : 'onModalShown',
|
||||
'hidden .modal' : 'onModalHidden'
|
||||
},
|
||||
|
||||
/*
|
||||
*
|
||||
* EVENT CALLBACKS
|
||||
*
|
||||
*/
|
||||
onAppActivated: function(app) {
|
||||
if (app.firstLoad) {
|
||||
_.defer(this.initialize.bind(this));
|
||||
} 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;
|
||||
}
|
||||
},
|
||||
|
||||
onPauseClicked: function(e) {
|
||||
var $el = this.$(e.currentTarget);
|
||||
|
||||
$el.removeClass('pause').addClass('resume');
|
||||
$el.find('i').prop('class', 'icon-play');
|
||||
|
||||
this.manuallyPaused = this.paused = true;
|
||||
},
|
||||
|
||||
onResumeClicked: function(e) {
|
||||
var $el = this.$(e.currentTarget);
|
||||
|
||||
$el.removeClass('resume').addClass('pause');
|
||||
$el.find('i').prop('class', 'icon-pause');
|
||||
|
||||
this.manuallyPaused = this.paused = false;
|
||||
},
|
||||
|
||||
onResetClicked: function() {
|
||||
this.elapsedTime = 0;
|
||||
},
|
||||
|
||||
onTimeLogsContainerClicked: function(e) {
|
||||
var $el = this.$(e.currentTarget);
|
||||
|
||||
if (!this.$('.timelogs-container').is(':visible')) {
|
||||
$el.addClass('active');
|
||||
this.$('.timelogs-container').show();
|
||||
} else {
|
||||
$el.removeClass('active');
|
||||
this.$('.timelogs-container').hide();
|
||||
}
|
||||
},
|
||||
|
||||
onModalSaveClicked: function() {
|
||||
var timeString = this.$('.modal-time').val();
|
||||
|
||||
try {
|
||||
this.updateTime(this.TimeHelper.timeStringToMs(timeString));
|
||||
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.time = this.getTimeObject();
|
||||
this.timeLoopID = this.setTimeLoop();
|
||||
var timelogs = this.TimeHelper.prettyTimeLogs(this.time.logs);
|
||||
|
||||
this.switchTo('main', {
|
||||
manual_pause_resume: this.setting('manual_pause_resume'),
|
||||
timelogs: timelogs,
|
||||
display_reset: this.setting('reset'),
|
||||
display_timer: this.setting('display_timer'),
|
||||
display_timelogs: this.setting('display_timelogs'),
|
||||
timelogs_csv_filename: helpers.fmt('ticket-timelogs-%@',
|
||||
this.ticket().id()),
|
||||
timelogs_csv_string: encodeURI(this.timelogsToCsvString(timelogs))
|
||||
});
|
||||
|
||||
this.$('tr').tooltip({ placement: 'left', html: true });
|
||||
},
|
||||
|
||||
updateMainView: function(time) {
|
||||
this.$('.live-timer').html(this.TimeHelper.msToTime(time));
|
||||
this.$('.live-totaltimer').html(this.TimeHelper.msToTime(
|
||||
this.time.value + time
|
||||
));
|
||||
},
|
||||
|
||||
hideFields: function() {
|
||||
_.each([this.timeFieldLabel(), this.timeObjectFieldLabel()], function(f) {
|
||||
var field = this.ticketFields(f);
|
||||
|
||||
if (field) {
|
||||
field.hide();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
/*
|
||||
* TIME RELATED
|
||||
*/
|
||||
|
||||
setTimeLoop: function() {
|
||||
var interval = 1000;
|
||||
|
||||
this.elapsedTime = 0;
|
||||
|
||||
return setInterval(function() {
|
||||
if (!this.paused) {
|
||||
this.elapsedTime += interval;
|
||||
|
||||
this.updateMainView(this.elapsedTime);
|
||||
}
|
||||
}.bind(this), interval);
|
||||
},
|
||||
|
||||
updateTime: function(time) {
|
||||
this.time.logs.push({
|
||||
time: time,
|
||||
submitter_id: this.currentUser().id(),
|
||||
submitter_name: this.currentUser().name(),
|
||||
submitted_at: new Date().getTime(),
|
||||
status: this.ticket().status()
|
||||
});
|
||||
|
||||
this.time.value += time;
|
||||
|
||||
this.saveTimeObject();
|
||||
},
|
||||
|
||||
saveTimeObject: function() {
|
||||
this.ticket().customField(
|
||||
this.timeFieldLabel(),
|
||||
String(this.normalizeTimeForSave(this.time.value))
|
||||
);
|
||||
|
||||
this.ticket().customField(
|
||||
this.timeObjectFieldLabel(),
|
||||
JSON.stringify(this.time)
|
||||
);
|
||||
},
|
||||
|
||||
autoResume: function() {
|
||||
this.paused = false;
|
||||
},
|
||||
|
||||
autoPause: function() {
|
||||
this.paused = true;
|
||||
},
|
||||
|
||||
renderTimeModal: function() {
|
||||
this.$('.modal-time').val(this.TimeHelper.msToTime(this.elapsedTime));
|
||||
this.$('.modal').modal('show');
|
||||
},
|
||||
|
||||
/*
|
||||
*
|
||||
* HELPERS
|
||||
*
|
||||
*/
|
||||
|
||||
timelogsToCsvString: function(logs) {
|
||||
return _.reduce(logs, function(memo, log) {
|
||||
return memo + helpers.fmt('%@\n', [ log.time, log.submitter_name, log.date_submitted_at, log.status ]);
|
||||
}, 'Time,Submitter,Submitted At,status\n', this);
|
||||
},
|
||||
|
||||
// Returns a new time in unit specified by the setting (mm|ss|ms)
|
||||
normalizeTimeForSave: function(time) {
|
||||
var timeUnits = {
|
||||
"minute": 60000,
|
||||
"second": 1000,
|
||||
"millisecond": 1
|
||||
},
|
||||
exponent = timeUnits[this.setting('time_unit')] || timeUnits.second;
|
||||
|
||||
|
||||
return Math.floor(time / exponent);
|
||||
},
|
||||
|
||||
getTimeObject: function() {
|
||||
var timeObject = this.ticket().customField(this.timeObjectFieldLabel());
|
||||
|
||||
if (timeObject) {
|
||||
return JSON.parse(timeObject);
|
||||
} else {
|
||||
return { value: 0, logs: [] };
|
||||
}
|
||||
},
|
||||
|
||||
timeObjectFieldLabel: function() {
|
||||
return this.buidFieldLabel(this.setting('time_object_field_id'));
|
||||
},
|
||||
|
||||
timeFieldLabel: function() {
|
||||
return this.buidFieldLabel(this.setting('time_field_id'));
|
||||
},
|
||||
|
||||
buidFieldLabel: function(id) {
|
||||
return helpers.fmt('custom_field_%@', id);
|
||||
},
|
||||
|
||||
TimeHelper: {
|
||||
msToTime: function(millis) {
|
||||
var time = parseInt(millis / 1000, 10),
|
||||
seconds = time % 60;
|
||||
|
||||
time = parseInt(time / 60, 10);
|
||||
|
||||
var minutes = time % 60,
|
||||
hours = parseInt(time / 60, 10) % 24;
|
||||
|
||||
return helpers.fmt('%@:%@:%@',
|
||||
this.addInsignificantZero(hours),
|
||||
this.addInsignificantZero(minutes),
|
||||
this.addInsignificantZero(seconds));
|
||||
},
|
||||
|
||||
timeStringToMs: function(str) {
|
||||
var re = /^([\d]{2}):([\d]{2}):([\d]{2})$/,
|
||||
result = re.exec(str);
|
||||
|
||||
if (!result ||
|
||||
result.length != 4) {
|
||||
throw { message: 'bad_time_format' };
|
||||
} else {
|
||||
return (parseInt(result[1], 10) * 3600000) +
|
||||
(parseInt(result[2], 10) * 60000) +
|
||||
(parseInt(result[3], 10) * 1000); // hours + minutes + seconds in milliseconds
|
||||
}
|
||||
},
|
||||
|
||||
addInsignificantZero: function(n) {
|
||||
return ( n < 10 ? '0' : '') + n;
|
||||
},
|
||||
|
||||
prettyTimeLogs: function(logs) {
|
||||
return _.reduce(logs, function(memo, log) {
|
||||
var logDecorator = _.clone(log),
|
||||
submitted_at = new Date(log.submitted_at);
|
||||
|
||||
logDecorator.date_submitted_at = submitted_at.toLocaleString();
|
||||
logDecorator.time = this.msToTime(log.time);
|
||||
|
||||
memo.push(logDecorator);
|
||||
|
||||
return memo;
|
||||
}, [], this);
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.6 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 44 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 22 KiB |
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"name": "Time Tracking",
|
||||
"author": {
|
||||
"name": "Zendesk",
|
||||
"email": "support@zendesk.com",
|
||||
"url": "https://www.zendesk.com"
|
||||
},
|
||||
|
||||
"defaultLocale": "en",
|
||||
"private": false,
|
||||
"singleInstall": true,
|
||||
"location": [ "new_ticket_sidebar", "ticket_sidebar" ],
|
||||
"frameworkVersion": "1.0",
|
||||
"version": "0.1",
|
||||
|
||||
"parameters": [
|
||||
{
|
||||
"name": "time_field_id",
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "time_object_field_id",
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "display_timer",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "display_timelogs",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "auto_pause_resume",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "manual_pause_resume",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "reset",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "time_submission",
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "time_unit",
|
||||
"type": "text",
|
||||
"default": "second"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<header>
|
||||
<span class="logo"/>
|
||||
<h3>{{setting "name"}}</h3>
|
||||
</header>
|
||||
<section data-main/>
|
|
@ -0,0 +1,81 @@
|
|||
<div class="timers {{#unless display_timer }}hidden{{/unless}}">
|
||||
<div class="row-fluid current-timer">
|
||||
<div class="span8">
|
||||
{{t "views.main.current_time_spent"}}:
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="live-timer pull-right">00:00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
{{t "views.main.total_time_spent"}}:
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="live-totaltimer pull-right">00:00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="btn-group">
|
||||
{{#if manual_pause_resume}}
|
||||
<a class="btn pause" title="{{t "views.main.pause_resume"}}"><i class="icon-pause"></i></a>
|
||||
{{/if}}
|
||||
{{#if display_reset}}
|
||||
<a class="btn reset" title="{{t "views.main.reset"}}"><i class="icon-repeat"></i></a>
|
||||
{{/if}}
|
||||
{{#if display_timelogs}}
|
||||
{{#if timelogs}}
|
||||
<a class="btn timelogs-opener"><strong>{{t "views.main.timelogs"}}</strong></a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if timelogs}}
|
||||
<div class="row-fluid timelogs-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>{{t "views.main.timelogs_table.agent"}}</th>
|
||||
<th>{{t "views.main.timelogs_table.time"}}</th>
|
||||
<th>{{t "views.main.timelogs_table.status"}}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each timelogs}}
|
||||
<tr data-toggle="tooltip" title="{{date_submitted_at}}">
|
||||
<td>{{submitter_name}}</td>
|
||||
<td>{{time}}</td>
|
||||
<td>
|
||||
<span class="ticket_status_label {{status}}">
|
||||
<strong>{{status}}</strong>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-center">
|
||||
<a download="{{timelogs_csv_filename}}" href="data:text/csv;charset=utf-8,{{timelogs_csv_string}}" class="btn"><i class="icon-file"></i> CSV Timelogs</a>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">×</button>
|
||||
<h3>{{setting "name"}}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{t "views.modal.body"}}</p>
|
||||
|
||||
<div class="modal-time-container text-center">
|
||||
<input class="modal-time" type="text" value="HH:MM:SS"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" aria-hidden="true" data-dismiss="modal">{{t "views.modal.close"}} (<span class="modal-timer">15</span>)</button>
|
||||
<button class="btn btn-primary modal-save">{{t "views.modal.save"}}</button>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"app": {
|
||||
"description": "Helps you track time spent on tickets.",
|
||||
"name": "Time Tracking",
|
||||
"parameters": {
|
||||
"time_field_id": {
|
||||
"label": "Time Field ID",
|
||||
"helpText": "The ID of a custom numeric field that will hold the total time."
|
||||
},
|
||||
"time_object_field_id": {
|
||||
"label": "Config Field ID",
|
||||
"helpText": "The ID of a custom multi-line field that will hold the config/timelogs."
|
||||
},
|
||||
"display_timer": {
|
||||
"label": "Display time",
|
||||
"helpText": "Agent can see the timers (current/total)"
|
||||
},
|
||||
"display_timelogs": {
|
||||
"label": "Display Timelogs",
|
||||
"helpText": "Agent can see timelogs."
|
||||
},
|
||||
"auto_pause_resume": {
|
||||
"label": "Auto Pause/Resume",
|
||||
"helpText": "The time will pause when the focus on the ticket is lost and resume itself as soon as the agent is back on the ticket."
|
||||
},
|
||||
"manual_pause_resume": {
|
||||
"label": "Manual Pause/Resume/Reset",
|
||||
"helpText": "Agent can manually pause/resume/reset the timer."
|
||||
},
|
||||
"reset": {
|
||||
"label": "Reset current time",
|
||||
"helpText": "Agent can reset his current time spent on a ticket."
|
||||
},
|
||||
"time_submission": {
|
||||
"label": "Time submission",
|
||||
"helpText": "Agent submit his own time."
|
||||
},
|
||||
"time_unit": {
|
||||
"label": "Unit of time",
|
||||
"helpText": "Choose in which unit the time will be saved. You can choose millisecond/second/minute. Defaults to second."
|
||||
}
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"main": {
|
||||
"pause": "Pause",
|
||||
"current_time_spent": "Current Time Spent",
|
||||
"total_time_spent": "Total Time Spent",
|
||||
"pause_resume": "Pause/Resume",
|
||||
"reset": "Reset",
|
||||
"timelogs": "Timelogs",
|
||||
"timelogs_table": {
|
||||
"agent": "Agent",
|
||||
"time": "Time",
|
||||
"status": "Status"
|
||||
}
|
||||
},
|
||||
"modal": {
|
||||
"body": "The system shows that you spent the following time on this ticket, you can either edit it or leave it as is and click \"Save Ticket\".",
|
||||
"close": "Close",
|
||||
"save": "Save Ticket"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"save_hook": "You need to submit a time before saving this ticket!",
|
||||
"bad_time_format": "Bad time format (HH:MM:SS)."
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче