1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 08:02:40 +01:00
invoiceninja/resources/views/tasks/time_tracker_knockout.blade.php

1360 lines
46 KiB
PHP
Raw Normal View History

2017-09-24 14:42:31 +02:00
<script type="text/javascript">
2017-10-07 23:34:58 +02:00
function intToTime(seconds) {
2017-09-30 23:13:41 +02:00
if (seconds === null) {
return null;
}
// calculate seconds, minutes, hours
var duration = seconds*1000
var milliseconds = parseInt((duration%1000)/100)
, seconds = parseInt((duration/1000)%60)
, minutes = parseInt((duration/(1000*60))%60)
, hours = parseInt((duration/(1000*60*60))%24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return new Date(1970, 0, 1, hours, minutes, seconds, 0);
}
2017-10-01 08:55:45 +02:00
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
$(element).datepicker();
$(element).change(function() {
var value = valueAccessor();
value($(element).datepicker('getDate'));
})
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).datepicker('setDate', new Date(value * 1000));
}
}
};
2017-09-30 23:13:41 +02:00
ko.bindingHandlers.timepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = allBindingsAccessor().timepickerOptions || {};
$.extend(options, {
wrapHours: false,
showDuration: true,
step: 15,
2017-10-02 09:12:51 +02:00
lang: {
@foreach(['AM' , 'PM', 'mins', 'hr', 'hrs'] as $field)
"{{ $field }}": "{{ trans('texts.time_' . strtolower($field)) }}",
@endforeach
}
2017-09-30 23:13:41 +02:00
});
$(element).timepicker(options);
ko.utils.registerEventHandler(element, 'change', function () {
var value = valueAccessor();
2017-10-01 21:03:31 +02:00
var field = $(element).attr('name');
2017-10-01 12:04:49 +02:00
var seconds = $(element).timepicker('getSecondsFromMidnight');
2017-10-01 21:03:31 +02:00
2017-10-07 23:34:58 +02:00
// add 24 hours the end time is before the start time/tomorrow
if (field == 'end_time' && seconds !== null) {
2017-10-01 21:03:31 +02:00
$input = $(element).closest('td').prev('td').find('input');
var startTime = $input.timepicker('getSecondsFromMidnight');
if (seconds < startTime) {
seconds += 60 * 60 * 24;
}
}
2017-10-01 12:04:49 +02:00
value(seconds);
2017-09-30 23:13:41 +02:00
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var field = $(element).attr('name');
2017-10-01 12:04:49 +02:00
if (value) {
if (field == 'duration') {
$(element).timepicker('setTime', intToTime(value));
} else {
2017-10-01 08:55:45 +02:00
$(element).timepicker('setTime', new Date(value * 1000));
}
2017-10-01 13:01:37 +02:00
} else {
$(element).val('');
2017-09-30 23:13:41 +02:00
}
if (field == 'start_time') {
2017-10-01 11:01:31 +02:00
setTimeout(function() {
2017-10-01 21:03:31 +02:00
$input = $(element).closest('td').next('td').find('input');
2017-10-07 23:34:58 +02:00
var time = $(element).val();
if (time) {
// round the end time to the next 15 minutes
var seconds = $(element).timepicker('getSecondsFromMidnight');
var minTime = moment.utc(seconds * 1000).seconds(0);
var minutes = minTime.minutes();
if (minutes >= 45) {
minTime.minutes(0).add(1, 'hour');
} else if (minutes >= 30) {
minTime.minutes(45);
} else if (minutes >= 15) {
minTime.minutes(30);
} else {
minTime.minutes(15);
}
$input.timepicker('option', 'minTime', minTime.format("H:mm:ss"));
$input.timepicker('option', 'durationTime', time);
2017-10-02 09:21:19 +02:00
}
2017-10-01 11:01:31 +02:00
}, 1);
2017-09-30 23:13:41 +02:00
}
}
};
2017-10-01 18:43:05 +02:00
var defaultTimes = [];
2017-10-01 19:26:53 +02:00
for (var i=15; i<(15*4*6); i+=15) {
var time = moment.utc(i * 1000 * 60).format("H:mm:ss");
2017-10-01 18:43:05 +02:00
defaultTimes.push(time);
}
var timeMatcher = function(strs) {
return function findMatches(q, cb) {
var matches, substringRegex;
matches = [];
substrRegex = new RegExp(q, 'i');
$.each(strs, function(i, str) {
if (substrRegex.test(str)) {
matches.push(str);
}
});
cb(matches);
};
};
ko.bindingHandlers.typeahead = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var allBindings = allBindingsAccessor();
$element.typeahead({
highlight: true,
minLength: 0,
},
{
name: 'times',
source: timeMatcher(defaultTimes)
2017-10-01 18:57:09 +02:00
}).on('typeahead:change', function(element, datum, name) {
var value = valueAccessor();
2017-10-02 09:00:50 +02:00
if (datum && datum.indexOf(':') >= 0) {
var duration = moment.duration(datum).asSeconds();
} else {
var duration = parseFloat(datum) * 60 * 60;
}
2017-10-01 18:57:09 +02:00
value(duration);
2017-10-01 18:43:05 +02:00
}).on('typeahead:select', function(element, datum, name) {
var value = valueAccessor();
var duration = moment.duration(datum).asSeconds();
value(duration);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
var duration = moment.duration(value * 1000);
var value = Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss")
$(element).typeahead('val', value);
2017-10-07 23:34:58 +02:00
} else {
$(element).typeahead('val', '');
2017-10-01 18:43:05 +02:00
}
}
};
2017-09-30 23:13:41 +02:00
2017-09-24 14:42:31 +02:00
function ViewModel() {
var self = this;
self.tasks = ko.observableArray();
self.filter = ko.observable('');
self.clock = ko.observable(0);
2017-10-02 14:47:26 +02:00
self.sendingRequest = ko.observable(false);
self.sendingBulkRequest = ko.observable(false);
2017-09-24 14:42:31 +02:00
self.selectedTask = ko.observable(false);
self.selectedClient = ko.observable(false);
self.selectedProject = ko.observable(false);
2017-09-28 11:01:27 +02:00
var defaultSortField = 'createdAt';
var defaultSortDirection = 'descending';
if (isStorageSupported()) {
defaultSortField = localStorage.getItem('last:time_tracker:sort_field') || defaultSortField;
defaultSortDirection = localStorage.getItem('last:time_tracker:sort_direction') || defaultSortDirection;
}
2017-09-28 07:57:44 +02:00
self.filterState = ko.observable('all');
2017-12-20 09:41:40 +01:00
self.filterStatusId = ko.observable(false);
2017-09-28 11:01:27 +02:00
self.sortField = ko.observable(defaultSortField);
self.sortDirection = ko.observable(defaultSortDirection);
2017-09-28 07:57:44 +02:00
2017-09-26 11:46:23 +02:00
self.isDesktop = function() {
2017-10-02 09:12:51 +02:00
return navigator.userAgent == "{{ TIME_TRACKER_USER_AGENT }}";
2017-09-26 11:46:23 +02:00
}
2017-10-02 14:47:26 +02:00
self.isStartEnabled = ko.computed(function() {
return ! self.sendingRequest();
});
2017-10-02 17:21:11 +02:00
self.isChanged = ko.computed(function() {
return self.selectedTask() && self.selectedTask().isChanged();
});
2017-10-02 14:47:26 +02:00
self.isSaveEnabled = ko.computed(function() {
2017-10-02 17:21:11 +02:00
return self.selectedTask() && self.selectedTask().isChanged() && ! self.sendingRequest();
2017-10-02 14:47:26 +02:00
});
2017-09-24 14:42:31 +02:00
self.onSaveClick = function() {
2017-10-02 17:21:11 +02:00
if (! model.selectedTask() || ! model.isChanged()) {
2017-09-24 14:42:31 +02:00
return;
}
2017-09-28 12:00:08 +02:00
model.selectedTask().save(true);
2017-09-24 14:42:31 +02:00
}
2017-09-28 10:42:20 +02:00
self.onSortChange = function() {
2017-09-28 11:01:27 +02:00
if (isStorageSupported()) {
localStorage.setItem('last:time_tracker:sort_field', self.sortField());
localStorage.setItem('last:time_tracker:sort_direction', self.sortDirection());
}
2017-09-28 10:42:20 +02:00
}
2017-09-25 13:50:49 +02:00
self.onFilterClick = function(event) {
2017-09-27 22:15:42 +02:00
$('#filterPanel').toggle();
2017-09-24 21:02:15 +02:00
}
2017-10-02 20:22:19 +02:00
self.onClearClick = function() {
self.filter('');
$('#search').focus();
}
2017-09-24 21:02:15 +02:00
self.onRefreshClick = function() {
2017-09-26 11:46:23 +02:00
if (self.isDesktop()) {
2017-10-02 17:21:11 +02:00
if (model.selectedTask() && model.isChanged()) {
2017-09-26 11:46:23 +02:00
swal("{{ trans('texts.save_or_discard') }}");
return false;
} else {
location.reload();
}
} else {
location.reload();
}
2017-09-24 21:02:15 +02:00
}
2017-09-26 09:19:33 +02:00
self.refreshTitle = function() {
var tasks = self.tasks();
var count = 0;
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
if (task.isRunning()) {
count++;
}
}
var title = '';
if (count > 0) {
title = '(' + count + ') ';
}
title += "{{ trans('texts.time_tracker') }} | {{ APP_NAME }}";
document.title = title;
}
2017-09-24 14:42:31 +02:00
self.submitBulkAction = function(action, task) {
if (! task || ! action) {
return false;
}
var data = {
id: task.public_id(),
action: action,
}
2017-10-02 14:47:26 +02:00
self.sendingBulkRequest(true);
2017-09-24 14:42:31 +02:00
$.ajax({
dataType: 'json',
type: 'post',
data: data,
url: '{{ url('/tasks/bulk') }}',
accepts: {
json: 'application/json'
},
success: function(response) {
console.log(response);
if (action == 'archive' || action == 'delete') {
self.removeTask(task);
self.selectTask(false);
2017-09-25 13:15:45 +02:00
$('#search').focus();
2017-09-24 14:42:31 +02:00
}
2017-09-26 09:19:33 +02:00
self.refreshTitle();
2017-09-25 10:52:57 +02:00
if (action == 'archive') {
toastr.success("{{ trans('texts.archived_task') }}");
} else if (action == 'delete') {
toastr.success("{{ trans('texts.deleted_task') }}");
}
2017-09-24 14:42:31 +02:00
},
error: function(error) {
2017-09-25 10:52:57 +02:00
toastr.error("{{ trans('texts.error_refresh_page') }}");
2017-09-24 14:42:31 +02:00
}
2017-09-26 10:54:56 +02:00
}).always(function() {
setTimeout(function() {
2017-10-02 14:47:26 +02:00
model.sendingBulkRequest(false);
2018-01-08 10:55:23 +01:00
}, 1500);
2017-09-24 14:42:31 +02:00
});
}
self.onDeleteClick = function() {
sweetConfirm(function() {
self.submitBulkAction('delete', self.selectedTask());
}, "{{ trans('texts.delete_task') }}");
return false;
}
self.onArchiveClick = function() {
sweetConfirm(function() {
self.submitBulkAction('archive', self.selectedTask());
}, "{{ trans('texts.archive_task') }}");
return false;
}
self.onCancelClick = function() {
sweetConfirm(function() {
var task = self.selectedTask();
if (task.isNew()) {
2017-09-25 13:50:49 +02:00
self.selectedTask(false);
2017-09-24 14:42:31 +02:00
self.removeTask(task);
2017-09-27 13:25:56 +02:00
// wait for it to be re-enabled
setTimeout(function() {
$('#search').focus();
}, 1);
2017-09-24 14:42:31 +02:00
} else {
task.update(task.data);
}
2017-09-27 13:25:56 +02:00
}, "{{ trans('texts.discard_changes') }}");
2017-09-24 14:42:31 +02:00
return false;
}
2017-09-25 13:29:56 +02:00
self.onFilterFocus = function(data, event) {
2017-10-02 17:21:11 +02:00
if (model.selectedTask() && model.isChanged()) {
2017-09-25 13:29:56 +02:00
return;
}
2017-09-25 13:50:49 +02:00
self.selectedTask(false);
2017-09-24 14:42:31 +02:00
}
self.onFilterChanged = function(data) {
2017-09-25 13:50:49 +02:00
self.selectedTask(false);
2017-09-24 14:42:31 +02:00
self.selectedClient(false);
self.selectedProject(false);
}
self.onFilterKeyPress = function(data, event) {
if (event.which == 13) {
self.onStartClick();
}
return true;
}
self.onFormKeyPress = function(data, event) {
if (event.which == 13) {
if (event.target.type == 'textarea') {
return true;
}
self.onSaveClick();
}
return true;
}
self.viewClient = function(task) {
2017-10-02 17:21:11 +02:00
if (model.isChanged()) {
2017-09-25 13:50:49 +02:00
swal("{{ trans('texts.save_or_discard') }}");
return false;
}
2017-09-25 08:59:43 +02:00
var client = task.client();
if (self.selectedClient() && self.selectedClient().public_id() == client.public_id()) {
self.filter('');
self.selectedClient(false);
} else {
self.filter(client.displayName());
self.selectedProject(false);
self.selectedClient(client);
}
2017-09-25 07:52:02 +02:00
$('#search').focus();
2017-09-24 14:42:31 +02:00
return false;
}
self.viewProject = function(task) {
2017-10-02 17:21:11 +02:00
if (model.isChanged()) {
2017-09-25 13:50:49 +02:00
swal("{{ trans('texts.save_or_discard') }}");
return false;
}
2017-09-25 08:59:43 +02:00
var project = task.project();
if (self.selectedProject() && self.selectedProject().public_id() == project.public_id()) {
self.filter('');
self.selectedProject(false);
} else {
self.filter(project.name());
self.selectedClient(false);
self.selectedProject(project);
}
2017-09-25 07:52:02 +02:00
$('#search').focus();
2017-09-24 14:42:31 +02:00
return false;
}
self.onStartClick = function() {
if (self.selectedTask()) {
2017-09-28 12:02:19 +02:00
self.selectedTask().onStartClick();
2017-09-24 14:42:31 +02:00
} else {
var time = new TimeModel();
time.startTime(moment().unix());
var task = new TaskModel();
if (self.selectedProject()) {
task.setProject(self.selectedProject());
} else if (self.selectedClient()) {
task.setClient(self.selectedClient());
2017-10-02 10:54:22 +02:00
setTimeout(function() {
$('select#client_id').trigger('change');
}, 1);
2017-09-24 14:42:31 +02:00
} else {
task.description(self.filter());
}
task.addTime(time);
self.selectedTask(task);
self.addTask(task);
2017-09-26 09:19:33 +02:00
model.refreshTitle();
2017-09-24 14:42:31 +02:00
self.filter('');
2017-09-25 11:30:00 +02:00
task.focus();
2017-09-24 14:42:31 +02:00
}
}
self.tock = function(startTime) {
self.clock(self.clock() + 1);
setTimeout(function() {
model.tock();
}, 1000);
}
2017-09-25 08:53:20 +02:00
self.filterStyle = ko.computed(function() {
2017-09-25 18:59:13 +02:00
return 'background-color: ' + (self.filter() ? '#ffffaa' : 'white') + ' !important';
2017-09-25 08:53:20 +02:00
});
2017-09-24 21:44:20 +02:00
self.statistics = ko.computed(function() {
2017-09-24 22:25:56 +02:00
return '';
2017-09-24 21:44:20 +02:00
});
2017-09-24 14:42:31 +02:00
self.showArchive = ko.computed(function() {
var task = self.selectedTask();
if (! task) {
return false;
}
2017-10-02 17:21:11 +02:00
return task.isCreated() && ! task.isChanged();
2017-09-24 14:42:31 +02:00
});
2017-10-02 19:07:41 +02:00
self.showDiscard = ko.computed(function() {
var task = self.selectedTask();
if (! task) {
return false;
}
return ! task.isCreated();
});
2017-09-24 14:42:31 +02:00
self.showCancel = ko.computed(function() {
var task = self.selectedTask();
if (! task) {
return false;
}
2017-10-02 19:07:41 +02:00
return task.isCreated() && task.isChanged();
2017-09-24 14:42:31 +02:00
});
self.startIcon = ko.computed(function() {
if (self.selectedTask()) {
return self.selectedTask().startIcon();
} else {
return 'glyphicon glyphicon-play';
}
});
self.startLabel = ko.computed(function() {
if (self.selectedTask()) {
if (self.selectedTask().isRunning()) {
return "{{ trans('texts.stop') }}";
2018-02-15 14:03:49 +01:00
} else if (self.selectedTask().seconds() > 0) {
2017-09-24 14:42:31 +02:00
return "{{ trans('texts.resume') }}";
2018-02-15 14:03:49 +01:00
} else {
return "{{ trans('texts.start') }}";
2017-09-24 14:42:31 +02:00
}
} else {
return "{{ trans('texts.start') }}";
}
});
self.startClass = ko.computed(function() {
if (self.selectedTask()) {
return self.selectedTask().startClass();
} else {
return 'btn-success';
}
});
self.placeholder = ko.computed(function() {
if (self.selectedTask()) {
if (self.selectedTask().description()) {
2017-09-25 19:09:23 +02:00
return self.selectedTask().description();
2017-09-24 14:42:31 +02:00
} else {
return "{{ trans('texts.no_description') }}"
}
} else {
return "{{ trans('texts.what_are_you_working_on') }}";
}
});
2017-09-25 12:03:19 +02:00
self.taskById = function(taskId) {
var tasks = self.tasks();
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
if (task.public_id() == taskId) {
return task;
}
}
return false;
}
2017-09-24 14:42:31 +02:00
self.filteredTasks = ko.computed(function() {
2017-09-28 07:57:44 +02:00
var tasks = self.tasks();
var filtered = ko.utils.arrayFilter(tasks, function(task) {
2017-12-20 09:41:40 +01:00
return task.matchesFilter(self.filter(), self.filterState(), self.filterStatusId());
2017-09-28 07:57:44 +02:00
});
if (! self.filter() || filtered.length > 0) {
tasks = filtered;
2017-09-24 14:42:31 +02:00
}
// sort the data
tasks.sort(function (left, right) {
2017-09-28 11:01:27 +02:00
var sortField = self.sortField();
var leftSortValue = left.sortValue(sortField);
var rightSortValue = right.sortValue(sortField);
if (sortField == 'createdAt' || sortField == 'duration') {
2017-09-28 10:42:20 +02:00
if (self.sortDirection() == 'descending') {
return rightSortValue - leftSortValue
} else {
return leftSortValue - rightSortValue;
}
} else {
if (self.sortDirection() == 'ascending') {
return leftSortValue.localeCompare(rightSortValue);
} else {
return rightSortValue.localeCompare(leftSortValue);
}
}
2017-09-24 14:42:31 +02:00
});
return tasks;
});
self.addTask = function(task) {
self.tasks.push(task);
}
self.removeTask = function(task) {
self.tasks.remove(task);
}
self.selectTask = function(task) {
2017-10-02 17:21:11 +02:00
if (model.isChanged()) {
2017-09-24 22:48:32 +02:00
swal("{{ trans('texts.save_or_discard') }}");
return false;
}
2017-09-25 07:52:02 +02:00
2017-09-24 14:42:31 +02:00
if (task == self.selectedTask()) {
task = false;
}
// if a client is selected the project list will be filtered
// this prevents the new task's project from being show
// to fix it we're clearing the list and then firing a
// client change event to re-filter the list
refreshProjectList(true);
self.selectedTask(task);
2017-09-25 11:30:00 +02:00
if (task) {
task.focus();
if (! task.project()) {
2017-09-28 11:21:05 +02:00
// trigger client change to show all projects in autocomplete
2017-09-25 11:30:00 +02:00
$('select#client_id').trigger('change');
}
2017-09-28 11:21:05 +02:00
} else {
$('#search').focus();
2017-09-24 14:42:31 +02:00
}
2017-09-25 12:03:19 +02:00
if (isStorageSupported()) {
2017-09-28 11:01:27 +02:00
localStorage.setItem('last:time_tracker:task_id', task ? task.public_id() : 0);
2017-09-25 12:03:19 +02:00
}
2017-09-24 14:42:31 +02:00
}
}
function TaskModel(data) {
var self = this;
self.public_id = ko.observable();
self.description = ko.observable('');
self.time_log = ko.observableArray();
self.client_id = ko.observable();
self.project_id = ko.observable();
self.client = ko.observable();
self.project = ko.observable();
2017-09-28 11:34:02 +02:00
self.isHovered = ko.observable(false);
2017-10-01 20:32:05 +02:00
self.created_at = ko.observable(moment.utc().format('YYYY-MM-DD HH:mm:ss'));
2017-12-20 09:41:40 +01:00
self.task_status_id = ko.observable();
2018-04-04 21:43:32 +02:00
self.custom_value1 = ko.observable();
self.custom_value2 = ko.observable();
2017-09-24 14:42:31 +02:00
self.mapping = {
'client': {
update: function(data) {
if (! data.data) {
self.client_id(0);
return false;
} else {
self.client_id(data.data.public_id);
return new ClientModel(data.data);
}
}
},
'project': {
update: function(data) {
if (! data.data) {
self.project_id(0);
return false;
} else {
self.project_id(data.data.public_id);
return new ProjectModel(data.data);
}
},
},
'ignore': [
'time_log',
'client_id',
'project_id',
]
}
2017-09-25 11:22:19 +02:00
self.isValid = function() {
var client = self.client();
var project = self.project();
if (client && client.public_id() != self.client_id()) {
return "Client id's don't match " + client.public_id() + " " + self.client_id();
}
if (project) {
if (project.public_id() != self.project_id()) {
return "Project id's don't match " + project.public_id() + " " + self.project_id();
}
2017-10-02 11:22:02 +02:00
if (project.public_id() != -1 && projectMap[project.public_id()]) {
2017-09-25 16:26:22 +02:00
var client = projectMap[project.public_id()].client;
if (client.public_id != self.client_id()) {
return "Client and project id's don't match " + client.public_id + " " + self.client_id();
}
2017-09-25 11:22:19 +02:00
}
}
return true;
}
2017-09-25 11:30:00 +02:00
self.focus = function() {
if (! self.client()) {
$('.client-select input.form-control').focus();
} else if (! self.project()) {
$('.project-select input.form-control').focus();
} else {
$('#description').focus();
}
}
2017-09-28 12:00:08 +02:00
self.save = function(isSelected) {
2017-10-08 16:40:48 +02:00
if (model.sendingRequest()) {
return false;
}
2017-10-01 15:16:27 +02:00
if (! self.checkForOverlaps()) {
swal("{{ trans('texts.task_errors') }}");
return;
}
2017-09-25 11:24:15 +02:00
if (self.isValid() !== true) {
2017-09-25 11:22:19 +02:00
toastr.error("{{ trans('texts.error_refresh_page') }}");
2017-09-25 11:24:15 +02:00
throw self.isValid();
2017-09-25 11:22:19 +02:00
return;
}
2017-09-28 12:00:08 +02:00
var data = 'client_id=' + self.client_id()
+ '&project_id=' + self.project_id()
+ '&project_name=' + encodeURIComponent(self.project() ? self.project().name() : '')
+ '&description=' + encodeURIComponent(self.description())
2018-04-04 21:43:32 +02:00
+ '&custom_value1=' + encodeURIComponent(self.custom_value1())
+ '&custom_value2=' + encodeURIComponent(self.custom_value2())
2017-09-28 12:00:08 +02:00
+ '&time_log=' + JSON.stringify(self.times());
2017-09-28 12:02:19 +02:00
2017-09-24 19:58:10 +02:00
var url = '{{ url('/tasks') }}';
var method = 'post';
if (self.public_id()) {
method = 'put';
url += '/' + self.public_id();
}
2017-09-25 16:34:10 +02:00
if (self.isRunning()) {
data += '&is_running=1';
} else {
data += '&is_running=0';
}
2017-10-02 14:47:26 +02:00
model.sendingRequest(true);
2017-09-24 19:58:10 +02:00
$.ajax({
dataType: 'json',
type: method,
data: data,
url: url,
accepts: {
json: 'application/json'
},
success: function(response) {
if (isSelected) {
2017-09-25 12:53:50 +02:00
var clientId = $('input[name=client_id]').val();
2017-09-25 12:56:00 +02:00
if (clientId == -1 && response.client) {
2017-09-25 12:53:50 +02:00
var client = response.client;
clients.push(client);
addClientToMaps(client);
refreshClientList();
}
2017-09-24 19:58:10 +02:00
var projectId = $('input[name=project_id]').val();
2017-09-25 12:56:00 +02:00
if (projectId == -1 && response.project) {
2017-09-24 19:58:10 +02:00
var project = response.project;
project.client = response.client;
projects.push(project);
addProjectToMaps(project);
2017-09-25 13:50:49 +02:00
refreshProjectList(true);
2017-09-24 19:58:10 +02:00
}
2017-09-25 12:56:00 +02:00
var isNew = !self.public_id();
self.update(response);
2017-09-25 13:50:49 +02:00
if (isStorageSupported()) {
2017-09-28 11:01:27 +02:00
localStorage.setItem('last:time_tracker:task_id', self.public_id());
2017-09-25 13:50:49 +02:00
}
2017-09-25 10:52:57 +02:00
if (isNew) {
toastr.success("{{ trans('texts.created_task') }}");
} else {
toastr.success("{{ trans('texts.updated_task') }}");
}
} else {
2017-09-25 15:30:11 +02:00
self.update(response);
2017-09-25 10:52:57 +02:00
if (self.isRunning()) {
if (self.time_log().length == 1) {
toastr.success("{{ trans('texts.started_task') }}");
} else {
toastr.success("{{ trans('texts.resumed_task') }}");
}
} else {
toastr.success("{{ trans('texts.stopped_task') }}");
}
2017-09-24 19:58:10 +02:00
}
2017-09-26 09:19:33 +02:00
model.refreshTitle();
2017-09-24 19:58:10 +02:00
},
2017-09-25 10:52:57 +02:00
error: function(error) {
toastr.error("{{ trans('texts.error_refresh_page') }}");
},
2017-09-26 10:54:56 +02:00
}).always(function() {
setTimeout(function() {
2017-10-02 14:47:26 +02:00
model.sendingRequest(false);
2018-01-08 10:55:23 +01:00
}, 1500);
2017-09-24 19:58:10 +02:00
});
}
2017-09-24 14:42:31 +02:00
self.update = function(data) {
self.data = data;
2017-09-25 15:30:11 +02:00
var times = data.time_log instanceof Array ? data.time_log : JSON.parse(data.time_log);
2017-09-24 14:42:31 +02:00
ko.mapping.fromJS(data, self.mapping, this);
self.time_log.removeAll();
for (var i=0; i<times.length; i++) {
self.time_log.push(new TimeModel(times[i]));
}
2017-10-01 13:13:26 +02:00
if (! self.isRunning()) {
self.addTime();
}
2017-12-20 09:41:40 +01:00
if (data.task_status) {
self.task_status_id(data.task_status.public_id);
}
2017-10-07 23:34:58 +02:00
// Trigger isChanged to update
self.client_id.valueHasMutated();
self.project_id.valueHasMutated();
self.description.valueHasMutated();
2017-09-24 14:42:31 +02:00
}
2017-10-01 15:16:27 +02:00
self.checkForOverlaps = function() {
var lastTime = 0;
var isValid = true;
2017-10-08 19:54:36 +02:00
var running = [];
2017-10-01 15:16:27 +02:00
for (var i=0; i<self.time_log().length; i++) {
var timeLog = self.time_log()[i];
var startValid = true;
var endValid = true;
if (!timeLog.isEmpty()) {
2017-10-01 15:31:19 +02:00
if ((lastTime && timeLog.startTime() < lastTime) || (timeLog.endTime() && timeLog.startTime() > timeLog.endTime())) {
2017-10-01 15:16:27 +02:00
startValid = false;
}
if (timeLog.endTime() && timeLog.endTime() < Math.min(timeLog.startTime(), lastTime)) {
endValid = false;
}
lastTime = Math.max(lastTime, timeLog.endTime());
2017-10-08 19:54:36 +02:00
if (timeLog.isRunning()) {
running.push(timeLog);
}
2017-10-01 15:16:27 +02:00
}
timeLog.isStartValid(startValid);
timeLog.isEndValid(endValid);
if (! startValid || ! endValid) {
isValid = false;
}
2017-10-08 19:54:36 +02:00
if (running.length > 1) {
$.each(running, function(i, time) {
time.isEndValid(false);
});
isValid = false;
}
2017-10-01 15:16:27 +02:00
}
return isValid;
}
2017-10-01 12:04:49 +02:00
self.checkForEmpty = function() {
2017-10-01 13:01:37 +02:00
setTimeout(function() {
var hasEmpty = false;
var times = self.time_log();
for (var i=0; i<times.length; i++) {
var timeLog = times[i];
if (! timeLog.endTime()) {
hasEmpty = true;
}
2017-10-01 12:04:49 +02:00
}
2017-10-01 13:01:37 +02:00
if (! hasEmpty) {
self.addTime();
}
2017-10-01 13:13:26 +02:00
}, 0);
2017-09-24 14:42:31 +02:00
}
2017-09-28 10:42:20 +02:00
self.sortValue = function(field) {
if (field == 'client') {
return self.client() && self.client().displayName() ? self.client().displayName().toLowerCase() : '';
} else if (field == 'project') {
return self.project() && self.project().name() ? self.project().name().toLowerCase() : '';
} else if (field == 'duration') {
return self.seconds(true);
} else {
return self[field]();
}
}
2017-09-24 14:42:31 +02:00
self.isNew = ko.computed(function() {
return ! self.public_id();
});
self.isCreated = ko.computed(function() {
return self.public_id();
});
2017-10-02 17:21:11 +02:00
self.isChanged = ko.computed(function() {
var data = self.data;
if (! self.public_id()) {
return true;
}
var oldProjectId = data.project ? data.project.public_id : 0;
if (oldProjectId != (self.project_id()||0)) {
return true;
}
var oldClientId = data.client ? data.client.public_id : 0;
if (oldClientId != (self.client_id()||0)) {
return true;
}
if (data.description != self.description()) {
return true;
}
2018-04-04 21:43:32 +02:00
if (data.custom_value1 != self.custom_value1()) {
return true;
}
if (data.custom_value2 != self.custom_value2()) {
return true;
}
2017-10-02 17:21:11 +02:00
var times = data.time_log instanceof Array ? JSON.stringify(data.time_log) : data.time_log;
if (times != JSON.stringify(self.times())) {
return true;
}
return false;
});
2017-10-02 10:54:22 +02:00
self.sortedTimes = ko.computed(function() {
var times = self.time_log();
times.sort(function (left, right) {
if (! left.startTime()) {
return 1;
} else if (! right.startTime()) {
return -1;
}
return left.startTime() - right.startTime();
});
return times;
});
2017-09-28 11:34:02 +02:00
self.isRunning = ko.computed(function() {
var timeLog = self.time_log();
if (! timeLog.length) {
return false;
}
var time = timeLog[timeLog.length-1];
return time.isRunning();
});
self.actionButtonVisible = ko.computed(function() {
return self.isHovered();
});
2017-10-01 13:01:37 +02:00
self.onChange = function() {
2017-10-01 15:16:27 +02:00
self.checkForOverlaps();
2017-10-01 13:01:37 +02:00
self.checkForEmpty();
}
2017-09-28 11:34:02 +02:00
self.onMouseOver = function() {
self.isHovered(true);
2017-09-24 14:42:31 +02:00
}
2017-09-28 11:34:02 +02:00
self.onMouseOut = function() {
self.isHovered(false);
2017-09-24 14:42:31 +02:00
}
self.addTime = function(time) {
2017-10-01 12:04:49 +02:00
if (!time) {
time = new TimeModel();
}
2017-09-24 14:42:31 +02:00
self.time_log.push(time);
}
self.times = function() {
var times = [];
for (var i=0; i<self.time_log().length; i++) {
var timeLog = self.time_log()[i];
if (! timeLog.isEmpty()) {
2017-10-01 13:13:26 +02:00
times.push([timeLog.startTime(), timeLog.endTime()]);
2017-09-24 14:42:31 +02:00
}
}
return times;
}
2017-12-20 09:41:40 +01:00
self.matchesFilter = function(filter, filterState, filterStatusId) {
2017-09-28 10:42:20 +02:00
if (filter) {
2017-09-28 07:57:44 +02:00
filter = model.filter().toLowerCase();
var parts = filter.split(' ');
for (var i=0; i<parts.length; i++) {
var part = parts[i];
var isMatch = false;
if (self.description()) {
if (self.description().toLowerCase().indexOf(part) >= 0) {
isMatch = true;
}
2017-09-25 08:27:55 +02:00
}
2017-09-28 07:57:44 +02:00
if (self.project()) {
var projectName = self.project().name();
if (projectName && projectName.toLowerCase().indexOf(part) >= 0) {
isMatch = true;
}
2017-09-24 14:42:31 +02:00
}
2017-09-28 07:57:44 +02:00
if (self.client()) {
var clientName = self.client().displayName();
if (clientName && clientName.toLowerCase().indexOf(part) >= 0) {
isMatch = true;
}
}
if (! isMatch) {
return false;
2017-09-24 14:42:31 +02:00
}
}
}
2017-09-28 07:57:44 +02:00
2017-09-28 10:42:20 +02:00
if (filterState == 'stopped' && self.isRunning()) {
2017-09-28 07:57:44 +02:00
return false;
2017-09-28 10:42:20 +02:00
} else if (filterState == 'running' && ! self.isRunning()) {
2017-09-28 07:57:44 +02:00
return false;
}
2017-12-20 09:41:40 +01:00
if (filterStatusId) {
if (self.task_status_id() != filterStatusId) {
return false;
}
}
2017-09-24 14:42:31 +02:00
return true;
}
self.onStartClick = function() {
2017-10-02 14:47:26 +02:00
if (model.sendingRequest()) {
2017-09-24 22:25:56 +02:00
return false;
}
2017-10-01 15:16:27 +02:00
if (! self.checkForOverlaps()) {
swal("{{ trans('texts.task_errors') }}");
return;
}
2017-09-24 14:42:31 +02:00
if (self.isRunning()) {
var time = self.lastTime();
time.endTime(moment().unix());
} else {
2017-10-01 13:13:26 +02:00
var lastTime = self.lastTime();
if (lastTime && ! lastTime.startTime()) {
var time = lastTime;
} else {
var time = new TimeModel();
self.addTime(time);
}
2017-09-24 14:42:31 +02:00
time.startTime(moment().unix());
}
2017-09-25 08:27:55 +02:00
if (self.public_id()) {
2017-09-27 13:29:40 +02:00
var selectedTask = model.selectedTask();
2017-10-02 17:21:11 +02:00
if (model.isChanged() && selectedTask && selectedTask.public_id() == self.public_id()) {
2017-09-27 13:29:40 +02:00
model.onSaveClick();
} else {
2017-09-28 12:00:08 +02:00
self.save();
2017-09-27 13:29:40 +02:00
}
2017-09-25 08:27:55 +02:00
}
2017-09-24 14:42:31 +02:00
}
self.listItemState = ko.computed(function() {
var str = '';
if (self == model.selectedTask()) {
str = 'active';
2017-09-25 18:59:13 +02:00
2017-10-02 17:21:11 +02:00
if (self.isChanged()) {
2017-09-25 18:59:13 +02:00
str += ' changed fade-color';
}
2017-09-24 14:42:31 +02:00
}
2017-09-28 11:34:02 +02:00
if (self.isRunning()) {
str += ' list-group-item-running';
}
2017-09-24 14:42:31 +02:00
if (! self.project()) {
return str;
}
var projectId = self.project().public_id();
var colorNum = (projectId-1) % 8;
return str + ' list-group-item-type' + (colorNum+1);
});
self.clientName = ko.computed(function() {
return self.client() ? self.client().displayName() : '';
});
self.projectName = ko.computed(function() {
return self.project() ? self.project().name() : '';
});
2017-10-02 19:07:41 +02:00
self.clientClass = ko.computed(function() {
return self.client() && self.client().deleted_at() ? 'archived-link' : '';
});
self.projectClass = ko.computed(function() {
return self.project() && self.project().deleted_at() ? 'archived-link' : '';
});
2017-09-24 14:42:31 +02:00
self.startClass = ko.computed(function() {
2017-10-02 14:47:26 +02:00
if (model.sendingRequest()) {
2017-09-24 22:25:56 +02:00
return 'disabled';
}
2017-09-24 14:42:31 +02:00
return self.isRunning() ? 'btn-danger' : 'btn-success';
});
self.startIcon = ko.computed(function() {
return self.isRunning() ? 'glyphicon glyphicon-stop' : 'glyphicon glyphicon-play';
});
self.setClient = function(client) {
self.client(client);
self.client_id(client.public_id());
}
self.setProject = function(project) {
2017-10-02 11:17:39 +02:00
if (! projectMap[project.public_id()]) {
return;
}
2017-09-24 14:42:31 +02:00
self.project(project);
self.project_id(project.public_id());
2017-10-02 11:17:39 +02:00
var project = projectMap[project.public_id()];
var client = project.client;
2017-09-24 14:42:31 +02:00
self.setClient(new ClientModel(client));
}
self.createdAt = function() {
return moment(self.created_at()).unix();
}
self.firstTime = function() {
return self.time_log()[0];
}
self.lastTime = function() {
var times = self.time_log();
return times[times.length-1];
}
self.age = ko.computed(function() {
2017-10-01 20:32:05 +02:00
model.clock(); // bind to the clock
return moment.utc(self.created_at()).fromNow();
2017-09-24 14:42:31 +02:00
});
2017-09-28 10:42:20 +02:00
self.seconds = function(total) {
2017-10-01 13:01:37 +02:00
//model.clock(); // bind to the clock
2017-09-24 14:42:31 +02:00
if (! self.time_log().length) {
2017-09-28 10:42:20 +02:00
return moment.duration(0);
2017-09-24 14:42:31 +02:00
}
var time = self.lastTime();
var now = new Date().getTime();
var duration = 0;
2017-09-24 22:35:33 +02:00
if (time.isRunning() && ! total) {
2017-09-24 14:42:31 +02:00
var duration = now - (time.startTime() * 1000);
duration = Math.floor(duration / 100) / 10;
} else {
self.time_log().forEach(function(time){
2017-10-01 19:09:53 +02:00
duration += time.duration.running();
2017-09-24 14:42:31 +02:00
});
}
2017-09-28 10:42:20 +02:00
return moment.duration(duration * 1000);
2017-09-24 22:35:33 +02:00
}
self.totalDuration = ko.computed(function() {
model.clock(); // bind to the clock
2017-09-28 10:42:20 +02:00
var duration = self.seconds(true);
return Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss");
2017-09-24 22:35:33 +02:00
});
self.duration = ko.computed(function() {
model.clock(); // bind to the clock
2017-09-28 10:42:20 +02:00
var duration = self.seconds(false);
return Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss");
2017-09-24 14:42:31 +02:00
});
2017-10-01 11:01:31 +02:00
self.removeTime = function(time) {
self.time_log.remove(time);
2017-10-01 12:04:49 +02:00
}
if (data) {
self.update(data);
2017-10-01 11:01:31 +02:00
}
2017-09-24 14:42:31 +02:00
}
function ProjectModel(data) {
var self = this;
self.name = ko.observable('');
2017-09-25 15:46:02 +02:00
self.public_id = ko.observable(-1);
2017-10-02 19:07:41 +02:00
self.deleted_at = ko.observable();
2017-09-24 14:42:31 +02:00
if (data) {
ko.mapping.fromJS(data, {}, this);
}
}
function ClientModel(data) {
var self = this;
self.name = ko.observable('');
2017-09-25 15:46:02 +02:00
self.public_id = ko.observable(-1);
2017-09-24 14:42:31 +02:00
self.contacts = ko.observableArray();
2017-10-02 19:07:41 +02:00
self.deleted_at = ko.observable();
2017-09-24 14:42:31 +02:00
self.mapping = {
'contacts': {
create: function(options) {
return new ContactModel(options.data);
}
}
}
self.displayName = ko.computed(function() {
if (self.name()) {
return self.name();
}
if (self.contacts().length == 0) return;
var contact = self.contacts()[0];
if (contact.first_name() || contact.last_name()) {
2017-12-31 16:01:16 +01:00
return (contact.first_name() || '') + ' ' + (contact.last_name() || '');
2017-09-24 14:42:31 +02:00
} else {
return contact.email();
}
});
if (data) {
ko.mapping.fromJS(data, self.mapping, this);
}
}
function ContactModel(data) {
var self = this;
self.first_name = ko.observable('');
self.last_name = ko.observable('');
self.email = ko.observable('');
if (data) {
ko.mapping.fromJS(data, {}, this);
}
self.displayName = ko.computed(function() {
if (self.first_name() || self.last_name()) {
return (self.first_name() || '') + ' ' + (self.last_name() || '') + ' ';
} else if (self.email()) {
return self.email();
} else {
return '';
}
});
}
function TimeModel(data) {
var self = this;
self.startTime = ko.observable(0);
self.endTime = ko.observable(0);
self.isStartValid = ko.observable(true);
self.isEndValid = ko.observable(true);
2017-10-01 11:01:31 +02:00
self.isHovered = ko.observable(false);
2017-09-24 14:42:31 +02:00
if (data) {
self.startTime(data[0]);
self.endTime(data[1]);
};
2017-10-01 11:01:31 +02:00
self.actionButtonVisible = ko.computed(function() {
2017-10-02 09:33:02 +02:00
if (! model.selectedTask()) {
return false;
}
if (! self.isHovered()) {
return false;
}
var times = model.selectedTask().time_log();
var count = 0;
for (var i=0; i<times.length; i++) {
var timeLog = times[i];
if (timeLog.isEmpty()) {
count++;
}
}
if (count > 1) {
return true;
}
return ! self.isEmpty() && times.length > 1;
2017-10-01 11:01:31 +02:00
});
self.onMouseOver = function() {
self.isHovered(true);
}
self.onMouseOut = function() {
self.isHovered(false);
}
2017-10-01 12:04:49 +02:00
self.startDateMidnight = function() {
return moment.unix(self.startTime()).set('hours', 0).set('minutes', 0).set('seconds', 0);
}
self.startTimeOfDay = ko.computed({
read: function () {
return self.startTime();
},
write: function(value) {
2017-10-01 15:31:19 +02:00
if (value === null) {
2017-10-01 16:18:56 +02:00
self.startTime(0);
2017-10-01 12:13:50 +02:00
} else {
2017-10-01 15:31:19 +02:00
if (self.startTime()) {
var orig = self.startDateMidnight().unix();
} else {
var orig = moment().set('hours', 0).set('minutes', 0).set('seconds', 0).unix();
}
self.startTime(orig + value);
2017-10-01 12:13:50 +02:00
}
2017-10-01 12:04:49 +02:00
}
});
self.endTimeOfDay = ko.computed({
read: function () {
return self.endTime();
},
write: function(value) {
2017-10-01 15:31:19 +02:00
if (value === null) {
2017-10-01 16:18:56 +02:00
self.endTime(0);
2017-10-01 15:31:19 +02:00
} else {
self.endTime(self.startDateMidnight().unix() + value);
}
2017-10-01 12:04:49 +02:00
}
});
2017-10-01 08:55:45 +02:00
self.startDate = ko.computed({
read: function () {
return self.startTime();
},
write: function(value) {
2017-10-01 12:04:49 +02:00
var origVal = self.startDateMidnight();
2017-10-01 10:26:21 +02:00
var newVal = moment(value).set('hours', 0);
var diff = newVal.diff(origVal, 'days') * 60 * 60 * 24;
2017-10-01 12:04:49 +02:00
if (self.startTime()) {
self.startTime(self.startTime() + diff);
if (self.endTime()) {
self.endTime(self.endTime() + diff);
}
} else {
self.startTime(newVal.unix());
2017-10-01 10:26:21 +02:00
}
2017-10-01 08:55:45 +02:00
}
});
2017-09-24 14:42:31 +02:00
self.order = ko.computed(function() {
return self.startTime();
});
self.isEmpty = ko.computed(function() {
2017-10-01 12:04:49 +02:00
return ! self.startTime() && ! self.endTime();
2017-09-24 14:42:31 +02:00
});
self.isRunning = ko.computed(function() {
2017-10-01 12:04:49 +02:00
return self.startTime() && ! self.endTime();
2017-09-24 14:42:31 +02:00
});
self.age = ko.computed(function() {
2017-09-24 21:09:28 +02:00
model.clock(); // bind to the clock
2017-09-24 14:42:31 +02:00
return moment.unix(self.startTime()).fromNow();
});
2017-09-30 23:13:41 +02:00
self.duration = ko.computed({
2017-10-01 19:09:53 +02:00
read: function () {
model.clock(); // bind to the clock
if (! self.startTime() || ! self.endTime()) {
return false;
}
return self.endTime() - self.startTime();
},
write: function(value) {
self.endTime(self.startTime() + value);
}
});
self.duration.running = ko.computed({
2017-09-30 23:13:41 +02:00
read: function () {
model.clock(); // bind to the clock
2017-10-01 12:04:49 +02:00
if (! self.startTime()) {
return false;
}
2017-09-30 23:13:41 +02:00
var endTime = self.endTime() ? self.endTime() : moment().unix();
return endTime - self.startTime();
},
write: function(value) {
2017-10-01 08:55:45 +02:00
self.endTime(self.startTime() + value);
2017-09-30 23:13:41 +02:00
}
2017-09-24 14:42:31 +02:00
});
2017-10-01 19:09:53 +02:00
2017-09-24 14:42:31 +02:00
}
</script>