Working on task kanban

This commit is contained in:
Hillel Coren 2017-12-18 00:25:30 +02:00
parent 23b3f95265
commit 3edb013cb9
2 changed files with 126 additions and 32 deletions

View File

@ -2619,6 +2619,7 @@ $LANG = array(
'in_progress' => 'In progress', 'in_progress' => 'In progress',
'add_status' => 'Add status', 'add_status' => 'Add status',
'archive_status' => 'Archive Status', 'archive_status' => 'Archive Status',
'new_status' => 'New Status',
); );

View File

@ -11,7 +11,7 @@
} }
.kanban-column { .kanban-column {
background-color: #EAEAEA; background-color: #E9E9E9;
padding: 10px; padding: 10px;
height: 100%; height: 100%;
width: 230px; width: 230px;
@ -88,11 +88,17 @@
.project-group7 { color: #a87821; } .project-group7 { color: #a87821; }
.project-group8 { color: #676767; } .project-group8 { color: #676767; }
</style> </style>
@stop @stop
@section('top-right')
<div class="form-group">
<input type="text" placeholder="{{ trans('texts.filter') }}" data-bind="value: filter, valueUpdate: 'afterkeydown'"
class="form-control" style="background-color: #FFFFFF !important"/>
</div>
@stop
@section('content') @section('content')
<script type="text/javascript"> <script type="text/javascript">
@ -130,12 +136,15 @@
var self = this; var self = this;
self.statuses = ko.observableArray(); self.statuses = ko.observableArray();
self.is_adding_status = ko.observable(false);
self.new_status = ko.observable('');
self.filter = ko.observable('');
for (var i=0; i<statuses.length; i++) { for (var i=0; i<statuses.length; i++) {
var status = statuses[i]; var status = statuses[i];
var statusModel = new StatusModel(status); var statusModel = new StatusModel(status);
self.statuses.push(statusModel); self.statuses.push(statusModel);
} }
self.statuses.push(new StatusModel());
for (var i=0; i<projects.length; i++) { for (var i=0; i<projects.length; i++) {
var project = projects[i]; var project = projects[i];
@ -144,7 +153,7 @@
for (var i=0; i<clients.length; i++) { for (var i=0; i<clients.length; i++) {
var client = clients[i]; var client = clients[i];
//clientMap[client.public_id] = client; clientMap[client.public_id] = new ClientModel(client);
} }
for (var i=0; i<tasks.length; i++) { for (var i=0; i<tasks.length; i++) {
@ -154,6 +163,22 @@
statusModel.tasks.push(taskModel); statusModel.tasks.push(taskModel);
} }
self.startAddStatus = function() {
self.is_adding_status(true);
}
self.cancelAddStatus = function() {
self.is_adding_status(false);
}
self.completeAddStatus = function() {
self.is_adding_status(false);
var statusModel = new StatusModel({
name: self.new_status()
})
self.statuses.push(statusModel);
}
self.onDragged = function() { self.onDragged = function() {
} }
@ -162,7 +187,6 @@
function StatusModel(data) { function StatusModel(data) {
var self = this; var self = this;
self.name = ko.observable(); self.name = ko.observable();
self.is_blank = ko.observable(false);
self.is_editing_status = ko.observable(false); self.is_editing_status = ko.observable(false);
self.is_header_hovered = ko.observable(false); self.is_header_hovered = ko.observable(false);
self.tasks = ko.observableArray(); self.tasks = ko.observableArray();
@ -176,6 +200,7 @@
self.is_header_hovered(false); self.is_header_hovered(false);
} }
/*
self.inputValue = ko.computed({ self.inputValue = ko.computed({
read: function () { read: function () {
return self.is_blank() ? '' : self.name(); return self.is_blank() ? '' : self.name();
@ -188,16 +213,13 @@
} }
} }
}); });
*/
self.placeholder = ko.computed(function() { self.startStatusEdit = function() {
return self.is_blank() ? '{{ trans('texts.add_status') }}...' : '';
})
self.startEdit = function() {
self.is_editing_status(true); self.is_editing_status(true);
} }
self.endEdit = function() { self.endStatusEdit = function() {
self.is_editing_status(false); self.is_editing_status(false);
} }
@ -215,7 +237,7 @@
if (self.new_task.is_blank()) { if (self.new_task.is_blank()) {
self.new_task.description(''); self.new_task.description('');
} }
self.new_task.endEdit(); self.new_task.endTaskEdit();
} }
self.saveNewTask = function() { self.saveNewTask = function() {
@ -224,15 +246,11 @@
}) })
self.tasks.push(task); self.tasks.push(task);
self.new_task.reset(); self.new_task.reset();
self.is_blank(false); self.endStatusEdit();
self.endEdit();
} }
if (data) { if (data) {
ko.mapping.fromJS(data, {}, this); ko.mapping.fromJS(data, {}, this);
} else {
self.name('{{ trans('texts.add_status') }}...');
self.is_blank(true);
} }
} }
@ -244,6 +262,7 @@
self.is_blank = ko.observable(false); self.is_blank = ko.observable(false);
self.is_editing_task = ko.observable(false); self.is_editing_task = ko.observable(false);
self.project = ko.observable(); self.project = ko.observable();
self.client = ko.observable();
self.projectColor = ko.computed(function() { self.projectColor = ko.computed(function() {
if (! self.project()) { if (! self.project()) {
@ -254,13 +273,13 @@
return 'project-group' + (colorNum+1); return 'project-group' + (colorNum+1);
}) })
self.startEdit = function() { self.startTaskEdit = function() {
self.description.orig(self.description()); self.description.orig(self.description());
self.is_editing_task(true); self.is_editing_task(true);
$('.kanban-column-row.editing textarea').focus(); $('.kanban-column-row.editing textarea').focus();
} }
self.endEdit = function() { self.endTaskEdit = function() {
self.is_editing_task(false); self.is_editing_task(false);
} }
@ -268,6 +287,39 @@
} }
self.matchesFilter = function(filter) {
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;
}
}
}
return true;
}
self.cancelEditTask = function() { self.cancelEditTask = function() {
if (self.is_blank()) { if (self.is_blank()) {
self.description(''); self.description('');
@ -275,20 +327,20 @@
self.description(self.description.orig()); self.description(self.description.orig());
} }
self.endEdit(); self.endTaskEdit();
} }
self.saveEditTask = function() { self.saveEditTask = function() {
self.endEdit(); self.endTaskEdit();
} }
self.viewTask = function() { self.viewTask = function() {
//console.log(); //console.log();
window.open('{{ url('/tasks') }}/' + self.public_id() + '/edit', '_blank'); window.open('{{ url('/tasks') }}/' + self.public_id() + '/edit', 'task');
} }
self.reset = function() { self.reset = function() {
self.endEdit(); self.endTaskEdit();
self.description(''); self.description('');
self.is_blank(true); self.is_blank(true);
} }
@ -298,6 +350,11 @@
create: function(options) { create: function(options) {
return projectMap[options.data.public_id]; return projectMap[options.data.public_id];
} }
},
'client': {
create: function(options) {
return clientMap[options.data.public_id];
}
} }
} }
@ -318,6 +375,19 @@
} }
} }
function ClientModel(data) {
var self = this;
self.name = ko.observable();
self.displayName = ko.computed(function() {
return self.name();
})
if (data) {
ko.mapping.fromJS(data, {}, this);
}
}
$(function() { $(function() {
window.model = new ViewModel(); window.model = new ViewModel();
ko.applyBindings(model); ko.applyBindings(model);
@ -332,24 +402,24 @@
<div class="kanban"> <div class="kanban">
<div data-bind="sortable: { data: statuses, as: 'status', afterMove: onDragged, allowDrop: true, connectClass: 'connect-column' }"> <div data-bind="sortable: { data: statuses, as: 'status', afterMove: onDragged, allowDrop: true, connectClass: 'connect-column' }" style="float:left">
<div class="well kanban-column"> <div class="well kanban-column">
<div class="kanban-column-header" data-bind="css: { editing: is_editing_status }, event: { mouseover: onHeaderMouseOver, mouseout: onHeaderMouseOut }"> <div class="kanban-column-header" data-bind="css: { editing: is_editing_status }, event: { mouseover: onHeaderMouseOver, mouseout: onHeaderMouseOut }">
<div class="pull-left" data-bind="event: { click: startEdit }"> <div class="pull-left" data-bind="event: { click: startStatusEdit }">
<div class="view" data-bind="text: name"></div> <div class="view" data-bind="text: name"></div>
<input class="edit" type="text" data-bind="value: inputValue, hasfocus: is_editing_status, selected: is_editing_status, <input class="edit" type="text" data-bind="value: name, hasfocus: is_editing_status, selected: is_editing_status,
placeholder: placeholder, event: { blur: endEdit }, enterkey: endEdit"/> event: { blur: endStatusEdit }, enterkey: endStatusEdit"/>
</div> </div>
<div class="pull-right" data-bind="click: archiveStatus, visible: ! is_blank() &amp;&amp; is_header_hovered"> <div class="pull-right" data-bind="click: archiveStatus, visible: is_header_hovered">
<i class="fa fa-times" title="{{ trans('texts.archive') }}"></i> <i class="fa fa-times" title="{{ trans('texts.archive') }}"></i>
</div><br/> </div><br/>
</div> </div>
<div data-bind="sortable: { data: tasks, as: 'task', afterMove: onDragged, allowDrop: true, connectClass: 'connect-row' }"> <div data-bind="sortable: { data: tasks, as: 'task', afterMove: onDragged, allowDrop: true, connectClass: 'connect-row' }">
<div class="kanban-column-row" data-bind="css: { editing: is_editing_task }"> <div class="kanban-column-row" data-bind="css: { editing: is_editing_task }, visible: task.matchesFilter($root.filter())">
<div data-bind="event: { click: startEdit }"> <div data-bind="event: { click: startTaskEdit }">
<div class="view panel" data-bind="visible: ! is_blank()"> <div class="view panel">
<i class="fa fa-circle" data-bind="visible: project, css: projectColor"></i> <i class="fa fa-circle" data-bind="visible: project, css: projectColor"></i>
<div data-bind="text: description"></div> <div data-bind="text: description"></div>
</div> </div>
@ -372,7 +442,7 @@
</div> </div>
<div class="kanban-column-footer" data-bind="css: { editing: new_task.is_editing_task }, with: new_task"> <div class="kanban-column-footer" data-bind="css: { editing: new_task.is_editing_task }, with: new_task">
<div data-bind="event: { click: startEdit }"> <div data-bind="event: { click: startTaskEdit }">
<div class="view panel" data-bind="visible: ! is_blank()"> <div class="view panel" data-bind="visible: ! is_blank()">
<div data-bind="text: description"></div> <div data-bind="text: description"></div>
</div> </div>
@ -395,6 +465,29 @@
</div> </div>
</div> </div>
<div class="kanban-column well">
<div class="kanban-column-row" data-bind="css: { editing: is_adding_status }">
<div data-bind="event: { click: startAddStatus }" style="padding-bottom: 8px;">
<a href="#" class="view text-muted" style="font-size:13px">
{{ trans('texts.new_status') }}...
</a>
</div>
<div class="edit">
<textarea data-bind="value: new_status, valueUpdate: 'afterkeydown',
hasfocus: is_adding_status, selected: is_adding_status, enterkey: completeAddStatus"></textarea>
<div class="pull-right">
<button type='button' class='btn btn-default btn-sm' data-bind="click: cancelAddStatus">
{{ trans('texts.cancel') }}
</button>
<button type='button' class='btn btn-success btn-sm' data-bind="click: completeAddStatus">
{{ trans('texts.save') }}
</button>
</div>
</div>
</div>
</div>
</div> </div>
@stop @stop