2017-12-17 18:54:54 +01:00
|
|
|
|
@extends('header')
|
|
|
|
|
|
|
|
|
|
@section('head')
|
|
|
|
|
@parent
|
|
|
|
|
|
|
|
|
|
<style type="text/css">
|
2017-12-19 09:30:42 +01:00
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
.kanban {
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
white-space: nowrap;
|
2017-12-19 23:26:56 +01:00
|
|
|
|
min-height: 400px;
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 12:05:34 +01:00
|
|
|
|
.kanban input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 21:57:05 +01:00
|
|
|
|
.tt-input {
|
|
|
|
|
background-color: #FFFFFF !important;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
.kanban-column {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
background-color: #E9E9E9;
|
2017-12-17 18:54:54 +01:00
|
|
|
|
padding: 10px;
|
2017-12-19 20:31:23 +01:00
|
|
|
|
padding-bottom: 14px;
|
2017-12-17 18:54:54 +01:00
|
|
|
|
height: 100%;
|
|
|
|
|
width: 230px;
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: top;
|
|
|
|
|
white-space: normal;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 20:31:23 +01:00
|
|
|
|
.kanban-column-last {
|
|
|
|
|
background-color: #F8F8F8;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
.kanban-column-header {
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column-header .pull-left {
|
|
|
|
|
width: 90%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column-header .fa-times {
|
|
|
|
|
color: #888888;
|
|
|
|
|
padding-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column-header input {
|
|
|
|
|
width: 190px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column-header .view {
|
|
|
|
|
padding-top: 3px;
|
|
|
|
|
padding-bottom: 3px;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 19:59:10 +01:00
|
|
|
|
.kanban-column-row {
|
2017-12-18 22:28:07 +01:00
|
|
|
|
margin-bottom: -12px;
|
2017-12-17 19:59:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:10:54 +01:00
|
|
|
|
.kanban-column-row .fa-circle {
|
|
|
|
|
float:right;
|
|
|
|
|
padding-top: 10px;
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 22:28:07 +01:00
|
|
|
|
.kanban-column-row .panel {
|
|
|
|
|
word-break: break-all;
|
2017-12-25 20:01:10 +01:00
|
|
|
|
padding-top: 4px !important;
|
|
|
|
|
padding-bottom: 4px !important;
|
2017-12-18 22:28:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 10:06:46 +01:00
|
|
|
|
.kanban-column-row .panel.hovered {
|
|
|
|
|
xborder-color: #6394e8 !important;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-24 20:03:54 +01:00
|
|
|
|
.kanban-column-row div {
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 21:04:40 +01:00
|
|
|
|
.kanban-column-row .running div {
|
|
|
|
|
border: 2px groove #36c157;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
.kanban-column-row .view div {
|
|
|
|
|
padding: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column textarea {
|
2017-12-17 19:59:10 +01:00
|
|
|
|
resize: vertical;
|
2017-12-17 18:54:54 +01:00
|
|
|
|
width: 100%;
|
|
|
|
|
padding-left: 8px;
|
2017-12-25 20:01:10 +01:00
|
|
|
|
padding-top: 12px;
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column .edit {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column .editing .edit {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.kanban-column .editing .view {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:10:54 +01:00
|
|
|
|
.project-group0 { color: #000000; }
|
|
|
|
|
.project-group1 { color: #1c9f77; }
|
|
|
|
|
.project-group2 { color: #d95d02; }
|
|
|
|
|
.project-group3 { color: #716cb1; }
|
|
|
|
|
.project-group4 { color: #e62a8b; }
|
|
|
|
|
.project-group5 { color: #5fa213; }
|
|
|
|
|
.project-group6 { color: #e6aa04; }
|
|
|
|
|
.project-group7 { color: #a87821; }
|
|
|
|
|
.project-group8 { color: #676767; }
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
@stop
|
|
|
|
|
|
2017-12-17 23:25:30 +01:00
|
|
|
|
@section('top-right')
|
|
|
|
|
<div class="form-group">
|
2017-12-24 11:02:49 +01:00
|
|
|
|
<input type="text" data-bind="value: filter" placeholder="{{ trans('texts.filter') }}" id="filter"
|
2017-12-17 23:25:30 +01:00
|
|
|
|
class="form-control" style="background-color: #FFFFFF !important"/>
|
|
|
|
|
</div>
|
|
|
|
|
@stop
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
@section('content')
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
2017-12-17 19:59:10 +01:00
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
var statuses = {!! $statuses !!};
|
2017-12-17 19:59:10 +01:00
|
|
|
|
var tasks = {!! $tasks !!};
|
2017-12-17 21:10:54 +01:00
|
|
|
|
var projects = {!! $projects !!};
|
|
|
|
|
var clients = {!! $clients !!};
|
|
|
|
|
|
|
|
|
|
var projectMap = {};
|
|
|
|
|
var clientMap = {};
|
2017-12-18 12:05:34 +01:00
|
|
|
|
var statusMap = {};
|
2017-12-17 19:59:10 +01:00
|
|
|
|
|
2017-12-19 21:57:05 +01:00
|
|
|
|
var clientList = [];
|
|
|
|
|
var projectList = [];
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
ko.bindingHandlers.enterkey = {
|
|
|
|
|
init: function (element, valueAccessor, allBindings, viewModel) {
|
|
|
|
|
var callback = valueAccessor();
|
|
|
|
|
$(element).keypress(function (event) {
|
|
|
|
|
var keyCode = (event.which ? event.which : event.keyCode);
|
|
|
|
|
if (keyCode === 13) {
|
|
|
|
|
callback.call(viewModel);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-12-19 20:55:32 +01:00
|
|
|
|
ko.bindingHandlers.escapekey = {
|
|
|
|
|
init: function (element, valueAccessor, allBindings, viewModel) {
|
|
|
|
|
var callback = valueAccessor();
|
|
|
|
|
$(element).keyup(function (event) {
|
|
|
|
|
var keyCode = (event.which ? event.which : event.keyCode);
|
|
|
|
|
if (keyCode === 27) {
|
|
|
|
|
callback.call(viewModel);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
ko.bindingHandlers.selected = {
|
|
|
|
|
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
|
|
|
|
var selected = ko.utils.unwrapObservable(valueAccessor());
|
|
|
|
|
if (selected) element.select();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function ViewModel() {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
self.statuses = ko.observableArray();
|
2017-12-17 23:25:30 +01:00
|
|
|
|
self.is_adding_status = ko.observable(false);
|
2017-12-20 09:10:25 +01:00
|
|
|
|
self.new_status = ko.observable();
|
|
|
|
|
self.filter = ko.observable();
|
|
|
|
|
self.filter_client_id = ko.observable();
|
|
|
|
|
self.filter_project_id = ko.observable();
|
2017-12-18 22:28:07 +01:00
|
|
|
|
self.is_sending_request = ko.observable(false);
|
2017-12-17 23:25:30 +01:00
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
for (var i=0; i<statuses.length; i++) {
|
2017-12-17 19:59:10 +01:00
|
|
|
|
var status = statuses[i];
|
|
|
|
|
var statusModel = new StatusModel(status);
|
2017-12-18 12:05:34 +01:00
|
|
|
|
statusMap[status.public_id] = statusModel;
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.statuses.push(statusModel);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:10:54 +01:00
|
|
|
|
for (var i=0; i<projects.length; i++) {
|
|
|
|
|
var project = projects[i];
|
|
|
|
|
projectMap[project.public_id] = new ProjectModel(project);
|
2017-12-19 21:57:05 +01:00
|
|
|
|
projectList.push({
|
2017-12-20 09:10:25 +01:00
|
|
|
|
type: 'project',
|
2017-12-19 21:57:05 +01:00
|
|
|
|
value: project.name,
|
|
|
|
|
tokens: project.name,
|
2017-12-20 09:10:25 +01:00
|
|
|
|
id: project.public_id,
|
2017-12-19 21:57:05 +01:00
|
|
|
|
})
|
2017-12-17 21:10:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (var i=0; i<clients.length; i++) {
|
|
|
|
|
var client = clients[i];
|
2017-12-17 23:25:30 +01:00
|
|
|
|
clientMap[client.public_id] = new ClientModel(client);
|
2017-12-20 09:10:25 +01:00
|
|
|
|
var tokens = [client.name];
|
|
|
|
|
if (client.contacts.length) {
|
|
|
|
|
$.each(client.contacts, function(i, contact) {
|
|
|
|
|
tokens.push(contact.first_name, contact.last_name, contact.email);
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-12-19 21:57:05 +01:00
|
|
|
|
clientList.push({
|
2017-12-20 09:10:25 +01:00
|
|
|
|
type: 'client',
|
|
|
|
|
value: getClientDisplayName(client),
|
|
|
|
|
tokens: tokens,
|
|
|
|
|
id: client.public_id,
|
2017-12-19 21:57:05 +01:00
|
|
|
|
})
|
2017-12-17 21:10:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 19:59:10 +01:00
|
|
|
|
for (var i=0; i<tasks.length; i++) {
|
|
|
|
|
var task = tasks[i];
|
|
|
|
|
var taskModel = new TaskModel(task);
|
2017-12-19 09:14:26 +01:00
|
|
|
|
var statusModel = false;
|
2017-12-18 12:05:34 +01:00
|
|
|
|
if (task.task_status) {
|
|
|
|
|
var statusModel = statusMap[task.task_status.public_id];
|
|
|
|
|
}
|
2017-12-19 09:14:26 +01:00
|
|
|
|
if (! statusModel) {
|
|
|
|
|
statusModel = self.statuses()[0];
|
|
|
|
|
}
|
|
|
|
|
if (statusModel) {
|
|
|
|
|
statusModel.tasks.push(taskModel);
|
|
|
|
|
}
|
2017-12-17 19:59:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.startNewStatus = function() {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
self.is_adding_status(true);
|
2017-12-17 23:36:55 +01:00
|
|
|
|
$('.kanban-column-last .kanban-column-row.editing textarea').focus();
|
2017-12-17 23:25:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.cancelNewStatus = function() {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
self.is_adding_status(false);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.saveNewStatus = function() {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
var statusModel = new StatusModel({
|
2017-12-19 12:25:13 +01:00
|
|
|
|
name: self.new_status(),
|
|
|
|
|
sort_order: self.statuses().length,
|
2017-12-17 23:25:30 +01:00
|
|
|
|
})
|
2017-12-19 09:14:26 +01:00
|
|
|
|
var url = '{{ url('/task_statuses') }}';
|
2017-12-19 12:25:13 +01:00
|
|
|
|
var data = statusModel.toData();
|
2017-12-19 09:14:26 +01:00
|
|
|
|
self.ajax('post', url, data, function(response) {
|
|
|
|
|
statusModel.public_id(response.public_id);
|
|
|
|
|
self.statuses.push(statusModel);
|
|
|
|
|
self.is_adding_status(false);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.ajax = function(method, url, data, callback) {
|
2017-12-25 20:05:47 +01:00
|
|
|
|
// prevent more than one request per second
|
|
|
|
|
if (model.is_sending_request()) {
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
self.ajax(method, url, data, callback);
|
|
|
|
|
}, 1000);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-12-19 09:14:26 +01:00
|
|
|
|
model.is_sending_request(true);
|
|
|
|
|
$.ajax({
|
|
|
|
|
type: method,
|
|
|
|
|
url: url,
|
|
|
|
|
data: data,
|
|
|
|
|
dataType: 'json',
|
|
|
|
|
accepts: {
|
|
|
|
|
json: 'application/json'
|
|
|
|
|
},
|
|
|
|
|
success: function(response) {
|
|
|
|
|
callback(response);
|
|
|
|
|
},
|
|
|
|
|
error: function(error) {
|
2017-12-19 22:02:05 +01:00
|
|
|
|
swal("{{ trans('texts.error_refresh_page') }}");
|
2017-12-19 09:14:26 +01:00
|
|
|
|
},
|
|
|
|
|
}).always(function() {
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
model.is_sending_request(false);
|
|
|
|
|
}, 1000);
|
|
|
|
|
});
|
2017-12-17 23:25:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 14:44:51 +01:00
|
|
|
|
self.onStatusDragged = function(dragged) {
|
|
|
|
|
var status = dragged.item;
|
|
|
|
|
status.sort_order(dragged.targetIndex);
|
|
|
|
|
|
2017-12-19 14:52:24 +01:00
|
|
|
|
var url = '{{ url('/task_statuses') }}/' + status.public_id();
|
|
|
|
|
var data = status.toData();
|
2017-12-19 14:44:51 +01:00
|
|
|
|
|
|
|
|
|
model.ajax('put', url, data, function(response) {
|
|
|
|
|
// do nothing
|
|
|
|
|
});
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function StatusModel(data) {
|
|
|
|
|
var self = this;
|
|
|
|
|
self.name = ko.observable();
|
2017-12-19 20:55:32 +01:00
|
|
|
|
self.name.orig = ko.observable();
|
2017-12-19 12:25:13 +01:00
|
|
|
|
self.sort_order = ko.observable();
|
2017-12-18 22:28:07 +01:00
|
|
|
|
self.public_id = ko.observable();
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.is_editing_status = ko.observable(false);
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.is_header_hovered = ko.observable(false);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.tasks = ko.observableArray();
|
|
|
|
|
self.new_task = new TaskModel();
|
|
|
|
|
|
2017-12-19 12:25:13 +01:00
|
|
|
|
self.toData = function() {
|
|
|
|
|
return 'name=' + encodeURIComponent(self.name()) +
|
|
|
|
|
'&sort_order=' + self.sort_order();
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.onHeaderMouseOver = function() {
|
|
|
|
|
self.is_header_hovered(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.onHeaderMouseOut = function() {
|
|
|
|
|
self.is_header_hovered(false);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.startEditStatus = function() {
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.is_editing_status(true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.saveEditStatus = function() {
|
2017-12-19 20:55:32 +01:00
|
|
|
|
if (self.name() == self.name.orig()) {
|
2017-12-19 09:27:28 +01:00
|
|
|
|
self.is_editing_status(false);
|
2017-12-19 20:55:32 +01:00
|
|
|
|
} else {
|
|
|
|
|
var url = '{{ url('/task_statuses') }}/' + self.public_id();
|
|
|
|
|
var data = 'name=' + encodeURIComponent(self.name());
|
|
|
|
|
model.ajax('put', url, data, function(response) {
|
|
|
|
|
self.name.orig(self.name());
|
|
|
|
|
self.is_editing_status(false);
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 13:25:44 +01:00
|
|
|
|
self.onTaskDragged = function(dragged) {
|
|
|
|
|
var task = dragged.item;
|
|
|
|
|
task.task_status_sort_order(dragged.targetIndex);
|
|
|
|
|
task.task_status_id(self.public_id());
|
|
|
|
|
|
|
|
|
|
var url = '{{ url('/task_status_order') }}/' + task.public_id();
|
|
|
|
|
var data = task.toData();
|
|
|
|
|
model.ajax('put', url, data, function(response) {
|
2017-12-19 14:38:21 +01:00
|
|
|
|
// do nothing
|
2017-12-19 13:25:44 +01:00
|
|
|
|
});
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.archiveStatus = function() {
|
2017-12-17 21:41:36 +01:00
|
|
|
|
sweetConfirm(function() {
|
2017-12-19 09:14:26 +01:00
|
|
|
|
var url = '{{ url('/task_statuses') }}/' + self.public_id();
|
|
|
|
|
model.ajax('delete', url, null, function(response) {
|
2017-12-19 19:04:29 +01:00
|
|
|
|
model.statuses.remove(self);
|
|
|
|
|
if (model.statuses().length) {
|
|
|
|
|
var firstStatus = model.statuses()[0];
|
|
|
|
|
var adjustment = firstStatus.tasks().length;
|
|
|
|
|
for (var i=0; i<self.tasks().length; i++) {
|
|
|
|
|
var task = self.tasks()[i];
|
|
|
|
|
task.task_status_id(firstStatus.public_id());
|
|
|
|
|
task.task_status_sort_order(task.task_status_sort_order() + adjustment);
|
|
|
|
|
firstStatus.tasks.push(task);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
location.reload();
|
|
|
|
|
}
|
2017-12-19 09:14:26 +01:00
|
|
|
|
})
|
2017-12-17 21:41:36 +01:00
|
|
|
|
}, "{{ trans('texts.archive_status')}}");
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.cancelNewTask = function() {
|
|
|
|
|
if (self.new_task.is_blank()) {
|
|
|
|
|
self.new_task.description('');
|
|
|
|
|
}
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.new_task.is_editing_task(false);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.saveNewTask = function() {
|
2017-12-18 22:28:07 +01:00
|
|
|
|
var task = self.new_task;
|
|
|
|
|
var description = (task.description() || '').trim();
|
2017-12-18 12:16:16 +01:00
|
|
|
|
if (! description) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-12-17 18:54:54 +01:00
|
|
|
|
var task = new TaskModel({
|
2017-12-18 22:28:07 +01:00
|
|
|
|
description: description,
|
2017-12-19 14:38:21 +01:00
|
|
|
|
task_status_sort_order: self.tasks().length,
|
2017-12-17 18:54:54 +01:00
|
|
|
|
})
|
2017-12-19 19:16:03 +01:00
|
|
|
|
task.task_status_id(self.public_id())
|
2017-12-18 22:28:07 +01:00
|
|
|
|
|
2017-12-19 09:14:26 +01:00
|
|
|
|
var url = '{{ url('/tasks') }}';
|
|
|
|
|
var data = task.toData();
|
|
|
|
|
model.ajax('post', url, data, function(response) {
|
|
|
|
|
task.public_id(response.public_id);
|
|
|
|
|
self.tasks.push(task);
|
|
|
|
|
self.new_task.reset();
|
|
|
|
|
})
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
ko.mapping.fromJS(data, {}, this);
|
2017-12-19 20:55:32 +01:00
|
|
|
|
self.name.orig(self.name());
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function TaskModel(data) {
|
|
|
|
|
var self = this;
|
2017-12-17 21:41:36 +01:00
|
|
|
|
self.public_id = ko.observable(0);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.description = ko.observable('');
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.description.orig = ko.observable('');
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.is_blank = ko.observable(false);
|
|
|
|
|
self.is_editing_task = ko.observable(false);
|
2017-12-19 21:04:40 +01:00
|
|
|
|
self.is_running = ko.observable(false);
|
2017-12-17 21:10:54 +01:00
|
|
|
|
self.project = ko.observable();
|
2017-12-17 23:25:30 +01:00
|
|
|
|
self.client = ko.observable();
|
2017-12-18 22:28:07 +01:00
|
|
|
|
self.task_status_id = ko.observable();
|
|
|
|
|
self.task_status_sort_order = ko.observable();
|
2017-12-20 10:06:46 +01:00
|
|
|
|
self.is_panel_hovered = ko.observable(false);
|
2017-12-17 21:10:54 +01:00
|
|
|
|
|
|
|
|
|
self.projectColor = ko.computed(function() {
|
|
|
|
|
if (! self.project()) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
var projectId = self.project().public_id();
|
|
|
|
|
var colorNum = (projectId-1) % 8;
|
|
|
|
|
return 'project-group' + (colorNum+1);
|
|
|
|
|
})
|
2017-12-17 18:54:54 +01:00
|
|
|
|
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.startEditTask = function() {
|
2017-12-17 19:59:10 +01:00
|
|
|
|
self.description.orig(self.description());
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.is_editing_task(true);
|
|
|
|
|
$('.kanban-column-row.editing textarea').focus();
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 10:06:46 +01:00
|
|
|
|
self.onPanelMouseOver = function() {
|
|
|
|
|
self.is_panel_hovered(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.onPanelMouseOut = function() {
|
|
|
|
|
self.is_panel_hovered(false);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 22:28:07 +01:00
|
|
|
|
self.toData = function() {
|
|
|
|
|
return 'description=' + encodeURIComponent(self.description()) +
|
|
|
|
|
'&task_status_id=' + self.task_status_id() +
|
|
|
|
|
'&task_status_sort_order=' + self.task_status_sort_order();
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 09:10:25 +01:00
|
|
|
|
self.matchesFilter = function(filter, clientId, projectId) {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
if (filter) {
|
|
|
|
|
filter = 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (self.project()) {
|
|
|
|
|
var projectName = self.project().name();
|
|
|
|
|
if (projectName && projectName.toLowerCase().indexOf(part) >= 0) {
|
|
|
|
|
isMatch = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (self.client()) {
|
|
|
|
|
var clientName = self.client().displayName();
|
|
|
|
|
if (clientName && clientName.toLowerCase().indexOf(part) >= 0) {
|
|
|
|
|
isMatch = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (! isMatch) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 09:10:25 +01:00
|
|
|
|
if (clientId) {
|
|
|
|
|
if (! self.client() || self.client().public_id() != clientId) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (projectId) {
|
|
|
|
|
if (! self.project() || self.project().public_id() != projectId) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 23:25:30 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.cancelEditTask = function() {
|
2017-12-17 19:59:10 +01:00
|
|
|
|
if (self.is_blank()) {
|
|
|
|
|
self.description('');
|
|
|
|
|
} else {
|
|
|
|
|
self.description(self.description.orig());
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
2017-12-17 19:59:10 +01:00
|
|
|
|
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.is_editing_task(false);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:41:36 +01:00
|
|
|
|
self.saveEditTask = function() {
|
2017-12-18 22:35:43 +01:00
|
|
|
|
var description = (self.description() || '').trim();
|
|
|
|
|
if (! description) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 09:14:26 +01:00
|
|
|
|
var url = '{{ url('/tasks') }}/' + self.public_id();
|
|
|
|
|
var data = self.toData();
|
|
|
|
|
model.ajax('put', url, data, function(response) {
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.is_editing_task(false);
|
2017-12-19 09:14:26 +01:00
|
|
|
|
});
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:41:36 +01:00
|
|
|
|
self.viewTask = function() {
|
2017-12-17 23:25:30 +01:00
|
|
|
|
window.open('{{ url('/tasks') }}/' + self.public_id() + '/edit', 'task');
|
2017-12-17 21:41:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.reset = function() {
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.is_editing_task(false);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
self.is_blank(true);
|
2017-12-19 09:30:42 +01:00
|
|
|
|
self.description('');
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 21:10:54 +01:00
|
|
|
|
self.mapping = {
|
|
|
|
|
'project': {
|
|
|
|
|
create: function(options) {
|
|
|
|
|
return projectMap[options.data.public_id];
|
|
|
|
|
}
|
2017-12-17 23:25:30 +01:00
|
|
|
|
},
|
|
|
|
|
'client': {
|
|
|
|
|
create: function(options) {
|
|
|
|
|
return clientMap[options.data.public_id];
|
|
|
|
|
}
|
2017-12-17 21:10:54 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
if (data) {
|
2017-12-17 21:10:54 +01:00
|
|
|
|
ko.mapping.fromJS(data, self.mapping, this);
|
2017-12-19 19:16:03 +01:00
|
|
|
|
// resolve the private status id to the public value
|
|
|
|
|
self.task_status_id(data.task_status ? data.task_status.public_id : 0);
|
2017-12-17 18:54:54 +01:00
|
|
|
|
} else {
|
|
|
|
|
self.is_blank(true);
|
|
|
|
|
}
|
2017-12-17 21:10:54 +01:00
|
|
|
|
}
|
2017-12-17 18:54:54 +01:00
|
|
|
|
|
2017-12-17 21:10:54 +01:00
|
|
|
|
function ProjectModel(data) {
|
|
|
|
|
var self = this;
|
|
|
|
|
self.name = ko.observable();
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
ko.mapping.fromJS(data, {}, this);
|
|
|
|
|
}
|
2017-12-17 18:54:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 23:25:30 +01:00
|
|
|
|
function ClientModel(data) {
|
|
|
|
|
var self = this;
|
|
|
|
|
self.name = ko.observable();
|
2017-12-24 11:02:49 +01:00
|
|
|
|
self.contacts = ko.observableArray();
|
2017-12-17 23:25:30 +01:00
|
|
|
|
|
|
|
|
|
self.displayName = ko.computed(function() {
|
2017-12-24 11:02:49 +01:00
|
|
|
|
if (self.name()) {
|
|
|
|
|
return self.name();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.contacts().length) {
|
|
|
|
|
return self.contacts()[0].displayName();
|
|
|
|
|
}
|
2017-12-17 23:25:30 +01:00
|
|
|
|
})
|
|
|
|
|
|
2017-12-24 11:02:49 +01:00
|
|
|
|
self.mapping = {
|
|
|
|
|
'contacts': {
|
|
|
|
|
create: function(options) {
|
|
|
|
|
return new ContactModel(options.data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
ko.mapping.fromJS(data, self.mapping, this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ContactModel(data) {
|
|
|
|
|
var self = this;
|
|
|
|
|
self.public_id = ko.observable('');
|
|
|
|
|
self.first_name = ko.observable('');
|
|
|
|
|
self.last_name = ko.observable('');
|
|
|
|
|
self.email = ko.observable('');
|
|
|
|
|
self.phone = ko.observable('');
|
|
|
|
|
|
2017-12-17 23:25:30 +01:00
|
|
|
|
if (data) {
|
|
|
|
|
ko.mapping.fromJS(data, {}, this);
|
|
|
|
|
}
|
2017-12-24 11:02:49 +01:00
|
|
|
|
|
|
|
|
|
self.displayName = ko.computed(function() {
|
|
|
|
|
if (self.first_name() || self.last_name()) {
|
|
|
|
|
return self.first_name() + ' ' + self.last_name();
|
|
|
|
|
} else {
|
|
|
|
|
return self.email();
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-12-17 23:25:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
$(function() {
|
2017-12-19 21:57:05 +01:00
|
|
|
|
$('#filter').typeahead({
|
|
|
|
|
hint: true,
|
|
|
|
|
highlight: true,
|
|
|
|
|
},{
|
|
|
|
|
name: 'data',
|
|
|
|
|
limit: 4,
|
|
|
|
|
display: 'value',
|
|
|
|
|
source: searchData(clientList, 'tokens'),
|
|
|
|
|
templates: {
|
|
|
|
|
header: ' <span style="font-weight:600;font-size:15px">{{ trans('texts.clients') }}</span>'
|
|
|
|
|
}
|
|
|
|
|
},{
|
|
|
|
|
name: 'data',
|
|
|
|
|
limit: 4,
|
|
|
|
|
display: 'value',
|
|
|
|
|
source: searchData(projectList, 'tokens'),
|
|
|
|
|
templates: {
|
|
|
|
|
header: ' <span style="font-weight:600;font-size:15px">{{ trans('texts.projects') }}</span>'
|
|
|
|
|
}
|
|
|
|
|
}).on('typeahead:selected', function(element, datum, name) {
|
2017-12-20 09:10:25 +01:00
|
|
|
|
model.filter(false);
|
|
|
|
|
if (datum.type == 'client') {
|
|
|
|
|
model.filter_client_id(datum.id);
|
|
|
|
|
model.filter_project_id(false);
|
|
|
|
|
} else {
|
|
|
|
|
model.filter_project_id(datum.id);
|
|
|
|
|
model.filter_client_id(false);
|
|
|
|
|
}
|
2017-12-19 21:57:05 +01:00
|
|
|
|
});
|
|
|
|
|
|
2017-12-20 09:10:25 +01:00
|
|
|
|
$('#filter').on('input', function() {
|
2017-12-19 21:57:05 +01:00
|
|
|
|
model.filter($('#filter').val());
|
2017-12-20 09:10:25 +01:00
|
|
|
|
model.filter_client_id(false);
|
|
|
|
|
model.filter_project_id(false);
|
2017-12-19 21:57:05 +01:00
|
|
|
|
});
|
2017-12-18 22:28:07 +01:00
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
window.model = new ViewModel();
|
|
|
|
|
ko.applyBindings(model);
|
2017-12-18 22:28:07 +01:00
|
|
|
|
|
2017-12-24 20:17:21 +01:00
|
|
|
|
if ({{ $projectPublicId ? 'true' : 'false' }}) {
|
|
|
|
|
var project = projectMap[{{ $projectPublicId ?: 0}}];
|
|
|
|
|
if (project) {
|
|
|
|
|
model.filter_project_id({{ $projectPublicId }});
|
|
|
|
|
model.filter(project.name());
|
|
|
|
|
}
|
|
|
|
|
} else if ({{ $clientPublicId ? 'true' : 'false' }}) {
|
2017-12-24 11:02:49 +01:00
|
|
|
|
var client = clientMap[{{ $clientPublicId ?: 0 }}];
|
|
|
|
|
if (client) {
|
|
|
|
|
model.filter_client_id({{ $clientPublicId }});
|
|
|
|
|
model.filter(client.displayName());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-17 23:28:11 +01:00
|
|
|
|
$('.kanban').show();
|
2017-12-17 18:54:54 +01:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
2017-12-17 23:28:11 +01:00
|
|
|
|
<div class="kanban" style="display: none">
|
2017-12-19 13:25:44 +01:00
|
|
|
|
<div data-bind="sortable: { data: statuses, as: 'status', afterMove: onStatusDragged, allowDrop: true, connectClass: 'connect-column' }" style="float:left">
|
2017-12-17 18:54:54 +01:00
|
|
|
|
<div class="well kanban-column">
|
|
|
|
|
|
2017-12-17 19:59:10 +01:00
|
|
|
|
<div class="kanban-column-header" data-bind="css: { editing: is_editing_status }, event: { mouseover: onHeaderMouseOver, mouseout: onHeaderMouseOut }">
|
2017-12-19 09:27:28 +01:00
|
|
|
|
<div class="pull-left" data-bind="event: { click: startEditStatus }">
|
2017-12-20 10:06:46 +01:00
|
|
|
|
<div class="view" data-bind="text: name().length > 24 ? name().substring(0, 24) + '...' : name()"></div>
|
2017-12-19 09:27:28 +01:00
|
|
|
|
<input class="edit" type="text" data-bind="value: name, valueUpdate: 'afterkeydown', hasfocus: is_editing_status, selected: is_editing_status,
|
2017-12-19 20:55:32 +01:00
|
|
|
|
event: { blur: saveEditStatus }, enterkey: saveEditStatus, escapekey: saveEditStatus"/>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
</div>
|
2017-12-17 23:25:30 +01:00
|
|
|
|
<div class="pull-right" data-bind="click: archiveStatus, visible: is_header_hovered">
|
2017-12-17 18:54:54 +01:00
|
|
|
|
<i class="fa fa-times" title="{{ trans('texts.archive') }}"></i>
|
|
|
|
|
</div><br/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2017-12-25 20:01:10 +01:00
|
|
|
|
<div data-bind="sortable: { data: tasks, as: 'task', afterMove: onTaskDragged, allowDrop: true, connectClass: 'connect-row' }" style="min-height:32px;">
|
2017-12-20 09:10:25 +01:00
|
|
|
|
<div class="kanban-column-row" data-bind="css: { editing: is_editing_task }, visible: task.matchesFilter($root.filter(), $root.filter_client_id(), $root.filter_project_id())">
|
2017-12-25 20:01:10 +01:00
|
|
|
|
<div class="view" data-bind="event: { click: startEditTask }">
|
|
|
|
|
<div class="panel" data-bind="css: { running: is_running, hovered: is_panel_hovered }, event: { mouseover: onPanelMouseOver, mouseout: onPanelMouseOut }">
|
2017-12-17 21:10:54 +01:00
|
|
|
|
<i class="fa fa-circle" data-bind="visible: project, css: projectColor"></i>
|
2017-12-20 10:06:46 +01:00
|
|
|
|
<div data-bind="text: description().length > 100 ? description().substring(0, 100) + '...' : description()"></div>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit">
|
2017-12-20 11:43:15 +01:00
|
|
|
|
<textarea data-bind="value: description, valueUpdate: 'afterkeydown', enterkey: saveEditTask, escapekey: cancelEditTask"></textarea>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
<div class="pull-right">
|
|
|
|
|
<button type='button' class='btn btn-default btn-sm' data-bind="click: cancelEditTask">
|
|
|
|
|
{{ trans('texts.cancel') }}
|
|
|
|
|
</button>
|
2017-12-17 21:41:36 +01:00
|
|
|
|
<button type='button' class='btn btn-primary btn-sm' data-bind="click: viewTask">
|
|
|
|
|
{{ trans('texts.view') }}
|
|
|
|
|
</button>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
<button type='button' class='btn btn-success btn-sm' data-bind="click: saveEditTask">
|
|
|
|
|
{{ trans('texts.save') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2017-12-18 12:34:25 +01:00
|
|
|
|
<div class="clearfix" style="padding-bottom:20px"></div>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2017-12-20 11:41:52 +01:00
|
|
|
|
<div class="kanban-column-row" data-bind="css: { editing: new_task.is_editing_task }, with: new_task" style="padding-bottom:6px">
|
2017-12-25 20:01:10 +01:00
|
|
|
|
<div class="view" data-bind="event: { click: startEditTask }">
|
|
|
|
|
<a href="#" class="text-muted" style="font-size:13px" data-bind="visible: is_blank">
|
2017-12-17 18:54:54 +01:00
|
|
|
|
{{ trans('texts.new_task') }}...
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit">
|
2017-12-20 11:43:15 +01:00
|
|
|
|
<textarea data-bind="value: description, valueUpdate: 'afterkeydown', enterkey: $parent.saveNewTask, escapekey: $parent.cancelNewTask"></textarea>
|
2017-12-17 18:54:54 +01:00
|
|
|
|
<div class="pull-right">
|
|
|
|
|
<button type='button' class='btn btn-default btn-sm' data-bind="click: $parent.cancelNewTask">
|
|
|
|
|
{{ trans('texts.cancel') }}
|
|
|
|
|
</button>
|
|
|
|
|
<button type='button' class='btn btn-success btn-sm' data-bind="click: $parent.saveNewTask">
|
|
|
|
|
{{ trans('texts.save') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2017-12-17 23:25:30 +01:00
|
|
|
|
|
2017-12-17 23:36:55 +01:00
|
|
|
|
<div class="kanban-column kanban-column-last well">
|
2017-12-17 23:25:30 +01:00
|
|
|
|
<div class="kanban-column-row" data-bind="css: { editing: is_adding_status }">
|
2017-12-19 09:27:28 +01:00
|
|
|
|
<div class="view" data-bind="event: { click: startNewStatus }" style="padding-bottom: 8px;">
|
2017-12-18 12:05:34 +01:00
|
|
|
|
<a href="#" class="text-muted" style="font-size:13px">
|
2017-12-17 23:25:30 +01:00
|
|
|
|
{{ trans('texts.new_status') }}...
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="edit">
|
2017-12-18 12:05:34 +01:00
|
|
|
|
<input data-bind="value: new_status, valueUpdate: 'afterkeydown',
|
2017-12-19 09:27:28 +01:00
|
|
|
|
hasfocus: is_adding_status, selected: is_adding_status, enterkey: saveNewStatus"></textarea>
|
2017-12-18 12:05:34 +01:00
|
|
|
|
<div class="pull-right" style="padding-top:6px">
|
2017-12-19 09:27:28 +01:00
|
|
|
|
<button type='button' class='btn btn-default btn-sm' data-bind="click: cancelNewStatus">
|
2017-12-17 23:25:30 +01:00
|
|
|
|
{{ trans('texts.cancel') }}
|
|
|
|
|
</button>
|
2017-12-19 09:27:28 +01:00
|
|
|
|
<button type='button' class='btn btn-success btn-sm' data-bind="click: saveNewStatus">
|
2017-12-17 23:25:30 +01:00
|
|
|
|
{{ trans('texts.save') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2017-12-17 18:54:54 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@stop
|