Add Due Dates to boards
This patch adds a table of due dates in a given board on the board detail view. This allows due dates to be created, edited, and removed from the board (they will not be deleted in this way). At the moment there is no way to assign a due date to a card, but this patch adds CSS such that cards with impending due dates or late due dates will show some indication of it. Change-Id: Ib7905fc7d5ea2313219976f5c62ec38c012fde8e
This commit is contained in:
parent
0a546b32e7
commit
e8b8c9fe3c
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
angular.module('sb.board').controller('BoardDetailController',
|
||||
function ($scope, Worklist, $modal, Board, Project, $stateParams,
|
||||
BoardHelper, $document, User, $q) {
|
||||
BoardHelper, DueDate, $document, User, $q, moment) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
|
@ -285,6 +285,99 @@ angular.module('sb.board').controller('BoardDetailController',
|
|||
modelArray.splice(modelIdx, 1);
|
||||
};
|
||||
|
||||
$scope.newDueDate = function() {
|
||||
var modalInstance = $modal.open({
|
||||
templateUrl: 'app/due_dates/template/new.html',
|
||||
controller: 'DueDateNewController',
|
||||
resolve: {
|
||||
board: function() {
|
||||
return $scope.board;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.finally(function() {
|
||||
loadBoard();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editDueDate = function(dueDate) {
|
||||
var modalInstance = $modal.open({
|
||||
templateUrl: 'app/due_dates/template/new.html',
|
||||
controller: 'DueDateEditController',
|
||||
resolve: {
|
||||
board: function() {
|
||||
return $scope.board;
|
||||
},
|
||||
dueDate: function() {
|
||||
return dueDate;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.finally(function() {
|
||||
loadBoard();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeDueDate = function(dueDate) {
|
||||
var modalInstance = $modal.open({
|
||||
templateUrl: 'app/due_dates/template/remove_from_board.html',
|
||||
controller: 'DueDateRemoveController',
|
||||
resolve: {
|
||||
board: function() {
|
||||
return $scope.board;
|
||||
},
|
||||
dueDate: function() {
|
||||
return dueDate;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.finally(function() {
|
||||
loadBoard();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isDue = function(card) {
|
||||
if (card.item_type === 'task') {
|
||||
if (card.task.status === 'merged') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var now = moment();
|
||||
var tomorrow = now.clone();
|
||||
tomorrow.add(1, 'day');
|
||||
if (!card.resolved_due_date) {
|
||||
return false;
|
||||
}
|
||||
if ((now.isSame(card.resolved_due_date.date) ||
|
||||
now.isBefore(card.resolved_due_date.date)) &&
|
||||
(tomorrow.isSame(card.resolved_due_date.date) ||
|
||||
tomorrow.isAfter(card.resolved_due_date.date))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isLate = function(card) {
|
||||
if (card.item_type === 'task') {
|
||||
if (card.task.status === 'merged') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var now = moment();
|
||||
if (!card.resolved_due_date) {
|
||||
return false;
|
||||
}
|
||||
if (now.isAfter(card.resolved_due_date.date)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Config for the lanes sortable.
|
||||
*/
|
||||
|
|
|
@ -17,30 +17,73 @@
|
|||
ng-model="lane.worklist.items"
|
||||
class="kanban-lane-contents">
|
||||
<div class="kanban-card"
|
||||
ng-class="{'kanban-card-due': isDue(item), 'kanban-card-late': isLate(item)}"
|
||||
as-sortable-item
|
||||
ng-repeat="item in lane.worklist.items"
|
||||
ng-switch="item.item_type">
|
||||
<div as-sortable-item-handle ng-switch-when="story">
|
||||
<button type="button" class="close" title="Remove"
|
||||
ng-click="removeCard(lane.worklist, item)">
|
||||
×
|
||||
</button>
|
||||
<i class="fa fa-sb-story text-muted"></i>
|
||||
<a href="#!/story/{{item.story.id}}">
|
||||
{{item.story.title}}
|
||||
</a>
|
||||
<div class="row">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-sb-story text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<button type="button" class="close" title="Remove"
|
||||
ng-click="removeCard(lane.worklist, item); $event.stopPropagation();">
|
||||
×
|
||||
</button>
|
||||
<a href="#!/story/{{item.story.id}}">
|
||||
{{item.story.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row target-date" ng-show="item.resolved_due_date">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-clock-o text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<span time-moment
|
||||
eventdate="item.resolved_due_date.date"
|
||||
format-string="'MMM Do, YYYY'">
|
||||
</span>
|
||||
<br/>
|
||||
<span class="text-muted">{{item.resolved_due_date.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div as-sortable-item-handle ng-switch-when="task">
|
||||
<button type="button" class="close" title="Remove"
|
||||
ng-click="removeCard(lane.worklist, item)">
|
||||
×
|
||||
</button>
|
||||
<i class="fa fa-tasks text-muted"></i>
|
||||
<a href="#!/story/{{item.task.story_id}}">
|
||||
{{item.task.title}}
|
||||
</a>
|
||||
<user-typeahead ng-show="item.task.assignee_id"
|
||||
ng-model="item.task.assignee_id"/>
|
||||
<div class="row">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-tasks text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<button type="button" class="close" title="Remove"
|
||||
ng-click="removeCard(lane.worklist, item); $event.stopPropagation()">
|
||||
×
|
||||
</button>
|
||||
<a href="#!/story/{{item.task.story_id}}">
|
||||
{{item.task.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row target-date" ng-show="item.resolved_due_date">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-clock-o text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<span time-moment
|
||||
eventdate="item.resolved_due_date.date"
|
||||
format-string="'MMM Do, YYYY'">
|
||||
</span>
|
||||
<br/>
|
||||
<span class="text-muted">{{item.resolved_due_date.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<user-typeahead ng-show="item.task.assignee_id"
|
||||
ng-model="item.task.assignee_id"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,21 +15,64 @@
|
|||
-->
|
||||
<div class="kanban-lane-contents-readonly">
|
||||
<div class="kanban-card-readonly"
|
||||
ng-class="{'kanban-card-due': isDue(item), 'kanban-card-late': isLate(item)}"
|
||||
ng-repeat="item in lane.worklist.items"
|
||||
ng-switch="item.item_type">
|
||||
<div ng-switch-when="story">
|
||||
<i class="fa fa-sb-story text-muted"></i>
|
||||
<a href="#!/story/{{item.story.id}}">
|
||||
{{item.story.title}}
|
||||
</a>
|
||||
<div class="row">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-sb-story text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<a href="#!/story/{{item.story.id}}">
|
||||
{{item.story.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row target-date" ng-show="item.resolved_due_date">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-clock-o text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<span time-moment
|
||||
eventdate="item.resolved_due_date.date"
|
||||
format-string="'MMM Do, YYYY'">
|
||||
</span>
|
||||
<br/>
|
||||
<span class="text-muted">{{item.resolved_due_date.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="task">
|
||||
<i class="fa fa-tasks text-muted"></i>
|
||||
<a href="#!/story/{{item.task.story_id}}">
|
||||
{{item.task.title}}
|
||||
</a>
|
||||
<user-typeahead ng-show="item.task.assignee_id"
|
||||
ng-model="item.task.assignee_id"/>
|
||||
<div class="row">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-tasks text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<a href="#!/story/{{item.task.story_id}}">
|
||||
{{item.task.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row target-date" ng-show="item.resolved_due_date">
|
||||
<div class="col-xs-1">
|
||||
<i class="fa fa-clock-o text-muted"></i>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
<span time-moment
|
||||
eventdate="item.resolved_due_date.date"
|
||||
format-string="'MMM Do, YYYY'">
|
||||
</span>
|
||||
<br/>
|
||||
<span class="text-muted">{{item.resolved_due_date.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<user-typeahead ng-show="item.task.assignee_id"
|
||||
ng-model="item.task.assignee_id"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,21 +34,77 @@
|
|||
<!-- Template for the header and description -->
|
||||
<script type="text/ng-template" id="/inline/board_detail.html">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1 class="no-margin-bottom" view-title>
|
||||
{{board.title}}
|
||||
<small>
|
||||
<a ng-click="toggleEditMode()"
|
||||
ng-show="permissions.editBoard">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1 class="no-margin-bottom" view-title>
|
||||
{{board.title}}
|
||||
<small>
|
||||
<a ng-click="toggleEditMode()"
|
||||
ng-show="permissions.editBoard">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<insert-markdown ng-if="board.description"
|
||||
content="board.description">
|
||||
</insert-markdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<insert-markdown ng-if="board.description"
|
||||
content="board.description">
|
||||
</insert-markdown>
|
||||
<div class="col-sm-4">
|
||||
<small>
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr><th colspan="5">Due Dates</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="dueDate in board.due_dates">
|
||||
<td ng-if="dueDate.name"
|
||||
class="col-xs-4">
|
||||
{{dueDate.name}}
|
||||
</td>
|
||||
<td ng-if="dueDate.name"
|
||||
class="col-xs-3">
|
||||
<span time-moment eventdate="dueDate.date"></span>
|
||||
</td>
|
||||
<td colspan="2"
|
||||
ng-if="!dueDate.name"
|
||||
class="col-xs-8">
|
||||
<span time-moment eventdate="dueDate.date"></span>
|
||||
</td>
|
||||
<td class="col-xs-3">
|
||||
{{dueDate.count}} cards
|
||||
</td>
|
||||
<td class="col-xs-1 text-right">
|
||||
<a href ng-click="editDueDate(dueDate)"
|
||||
ng-if="dueDate.editable">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="col-xs-1 text-right">
|
||||
<a href ng-click="removeDueDate(dueDate)"
|
||||
ng-if="dueDate.editable">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-row" ng-click="newDueDate()"
|
||||
ng-if="permissions.moveCards || permissions.editBoard">
|
||||
<td colspan="5" class="text-center">
|
||||
<a href>
|
||||
<i class="fa fa-plus-circle"></i>
|
||||
New due date
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Codethink Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller for the "new due date" modal popup.
|
||||
*/
|
||||
angular.module('sb.due_date').controller('DueDateEditController',
|
||||
function ($scope, $modalInstance, $state, $q, DueDate, User,
|
||||
board, dueDate) {
|
||||
'use strict';
|
||||
$scope.owners = [];
|
||||
$scope.users = [];
|
||||
angular.forEach(dueDate.owners, function(id) {
|
||||
$scope.owners.push(User.get({id: id}));
|
||||
});
|
||||
angular.forEach(dueDate.users, function(id) {
|
||||
$scope.users.push(User.get({id: id}));
|
||||
});
|
||||
|
||||
/**
|
||||
* User typeahead search method.
|
||||
*/
|
||||
$scope.searchUsers = function (value, array) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
User.browse({full_name: value, limit: 10},
|
||||
function(searchResults) {
|
||||
var results = [];
|
||||
angular.forEach(searchResults, function(result) {
|
||||
if (array.indexOf(result.id) === -1) {
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
deferred.resolve(results);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the user name.
|
||||
*/
|
||||
$scope.formatUserName = function (model) {
|
||||
if (!!model) {
|
||||
return model.name;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new user to one of the permission levels.
|
||||
*/
|
||||
$scope.addUser = function (model, modelArray, idArray) {
|
||||
idArray.push(model.id);
|
||||
modelArray.push(model);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a user from one of the permission levels.
|
||||
*/
|
||||
$scope.removeUser = function (model, modelArray, idArray) {
|
||||
var idIdx = idArray.indexOf(model.id);
|
||||
idArray.splice(idIdx, 1);
|
||||
|
||||
var modelIdx = modelArray.indexOf(model);
|
||||
modelArray.splice(modelIdx, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save changes to the due date.
|
||||
*/
|
||||
function saveDueDate() {
|
||||
DueDate.update($scope.dueDate, function (result) {
|
||||
var params = {id: $scope.dueDate.id};
|
||||
var owners = {
|
||||
codename: 'edit_date',
|
||||
users: $scope.dueDate.owners
|
||||
};
|
||||
var users = {
|
||||
codename: 'assign_date',
|
||||
users: $scope.dueDate.users
|
||||
};
|
||||
DueDate.Permissions.update(params, users)
|
||||
.$promise.then(function() {
|
||||
DueDate.Permissions.update(params, owners)
|
||||
.$promise.then(function() {
|
||||
$scope.isSaving = false;
|
||||
$modalInstance.close(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the due date.
|
||||
*/
|
||||
$scope.save = function() {
|
||||
$scope.isSaving = true;
|
||||
$scope.dueDate.date.second(0);
|
||||
saveDueDate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Close this modal without saving.
|
||||
*/
|
||||
$scope.close = function() {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
// Create a blank DueDate to begin with.
|
||||
$scope.isSaving = false;
|
||||
$scope.dueDate = dueDate;
|
||||
$scope.modalTitle = 'Edit Due Date';
|
||||
});
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Codethink Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller for the "new due date" modal popup.
|
||||
*/
|
||||
angular.module('sb.due_date').controller('DueDateNewController',
|
||||
function ($scope, $modalInstance, $state, $q, DueDate, board, CurrentUser,
|
||||
User) {
|
||||
'use strict';
|
||||
|
||||
var currentUser = CurrentUser.resolve();
|
||||
$scope.owners = [];
|
||||
$scope.users = [];
|
||||
angular.forEach(board.owners, function(id) {
|
||||
$scope.owners.push(User.get({id: id}));
|
||||
});
|
||||
angular.forEach(board.users, function(id) {
|
||||
$scope.users.push(User.get({id: id}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Create the new due date.
|
||||
*/
|
||||
function saveDueDate() {
|
||||
if ($scope.mode === 'edit') {
|
||||
$scope.dueDate.$create(
|
||||
function (result) {
|
||||
$scope.isSaving = false;
|
||||
$modalInstance.close(result);
|
||||
}
|
||||
);
|
||||
} else if ($scope.mode === 'find') {
|
||||
DueDate.update({
|
||||
id: $scope.selected.id,
|
||||
board_id: board.id
|
||||
}, function (result) {
|
||||
$scope.isSaving = false;
|
||||
$modalInstance.close(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User typeahead search method.
|
||||
*/
|
||||
$scope.searchUsers = function (value, array) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
User.browse({full_name: value, limit: 10},
|
||||
function(searchResults) {
|
||||
var results = [];
|
||||
angular.forEach(searchResults, function(result) {
|
||||
if (array.indexOf(result.id) === -1) {
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
deferred.resolve(results);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the user name.
|
||||
*/
|
||||
$scope.formatUserName = function (model) {
|
||||
if (!!model) {
|
||||
return model.name;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new user to one of the permission levels.
|
||||
*/
|
||||
$scope.addUser = function (model, modelArray, idArray) {
|
||||
idArray.push(model.id);
|
||||
modelArray.push(model);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a user from one of the permission levels.
|
||||
*/
|
||||
$scope.removeUser = function (model, modelArray, idArray) {
|
||||
var idIdx = idArray.indexOf(model.id);
|
||||
idArray.splice(idIdx, 1);
|
||||
|
||||
var modelIdx = modelArray.indexOf(model);
|
||||
modelArray.splice(modelIdx, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the due date.
|
||||
*/
|
||||
$scope.save = function() {
|
||||
$scope.isSaving = true;
|
||||
$scope.dueDate.date.second(0);
|
||||
saveDueDate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Close this modal without saving.
|
||||
*/
|
||||
$scope.close = function() {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
$scope.setMode = function(mode) {
|
||||
$scope.mode = mode;
|
||||
};
|
||||
|
||||
$scope.searchDueDates = function() {
|
||||
DueDate.browse($scope.criteria, function(results) {
|
||||
var existing = [];
|
||||
angular.forEach(board.due_dates, function(date) {
|
||||
existing.push(date.id);
|
||||
});
|
||||
$scope.results = results;
|
||||
angular.forEach(results, function(result) {
|
||||
if (existing.indexOf(result.id) !== -1) {
|
||||
var idx = $scope.results.indexOf(result);
|
||||
$scope.results.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.select = function(dueDate) {
|
||||
$scope.selected = dueDate;
|
||||
};
|
||||
|
||||
$scope.selected = {};
|
||||
$scope.criteria = {};
|
||||
currentUser.then(function(result) {
|
||||
currentUser = result;
|
||||
$scope.criteria = {
|
||||
user: currentUser.id
|
||||
};
|
||||
});
|
||||
|
||||
// Create a blank DueDate to begin with.
|
||||
$scope.isSaving = false;
|
||||
$scope.newDueDate = true;
|
||||
$scope.modalTitle = 'New Due Date';
|
||||
$scope.mode = 'edit';
|
||||
$scope.dueDate = new DueDate({
|
||||
name: ''
|
||||
});
|
||||
if (!!board) {
|
||||
$scope.dueDate.board_id = board.id;
|
||||
$scope.dueDate.owners = board.owners;
|
||||
$scope.dueDate.users = board.users;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Codethink Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller for "archive board" modal
|
||||
*/
|
||||
angular.module('sb.board').controller('DueDateRemoveController',
|
||||
function ($log, $scope, $state, board, dueDate, $modalInstance, DueDate) {
|
||||
'use strict';
|
||||
|
||||
$scope.board = board;
|
||||
$scope.dueDate = dueDate;
|
||||
|
||||
// Set our progress flags and clear previous error conditions.
|
||||
$scope.isUpdating = true;
|
||||
$scope.error = {};
|
||||
|
||||
$scope.remove = function () {
|
||||
DueDate.delete({
|
||||
id: dueDate.id,
|
||||
board_id: board.id
|
||||
},
|
||||
function () {
|
||||
$modalInstance.close('success');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.close = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Codethink Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The StoryBoard due dates submodule handles the creation of task and story
|
||||
* due dates.
|
||||
*/
|
||||
angular.module('sb.due_date', ['ui.router', 'sb.services', 'sb.util',
|
||||
'ui.bootstrap'])
|
||||
.config(function ($stateProvider, $urlRouterProvider) {
|
||||
'use strict';
|
||||
|
||||
// URL Defaults.
|
||||
$urlRouterProvider.when('/due_date', '/due_date/list');
|
||||
|
||||
// Set our page routes.
|
||||
$stateProvider
|
||||
.state('sb.due_date', {
|
||||
abstract: true,
|
||||
url: '/due_date',
|
||||
template: '<div ui-view></div>'
|
||||
})
|
||||
.state('sb.due_date.detail', {
|
||||
url: '/{dueDateID:[0-9]+}',
|
||||
controller: 'DueDateDetailController',
|
||||
templateUrl: 'app/due_dates/template/detail.html'
|
||||
});
|
||||
});
|
|
@ -0,0 +1,232 @@
|
|||
<!--
|
||||
~ Copyright (c) 2015-2016 Codethink Limited
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<div class="panel panel-default due-date-modal">
|
||||
<div class="panel-heading">
|
||||
<button type="button" class="close" aria-hidden="true"
|
||||
ng-click="close()">×</button>
|
||||
<h3 class="panel-title">{{modalTitle}}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="nav nav-tabs"
|
||||
ng-if="newDueDate">
|
||||
<li ng-class="{'active': mode === 'edit'}">
|
||||
<a href ng-click="setMode('edit')">Create New</a>
|
||||
</li>
|
||||
<li ng-class="{'active': mode === 'find'}">
|
||||
<a href ng-click="setMode('find')">Use Existing</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="row" ng-show="(mode === 'edit') || !newDueDate">
|
||||
<div class="col-xs-12">
|
||||
<form class="form-horizontal" role="form" name="dueDateForm">
|
||||
<div class="form-group">
|
||||
<label for="title" class="col-sm-2 control-label">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="title"
|
||||
name="title"
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="dueDate.name"
|
||||
focus
|
||||
maxlength="100"
|
||||
placeholder="Due date name"
|
||||
ng-disabled="isSaving">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="private" class="col-sm-2 control-label">
|
||||
Private
|
||||
</label>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<input id="private"
|
||||
type="checkbox"
|
||||
class="modal-checkbox"
|
||||
ng-model="dueDate.private"
|
||||
ng-disabled="isSaving"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<calendar selected-date="dueDate.date"></calendar>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row" ng-include src="'/inline/permissions_tables.html'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="(mode === 'find') && newDueDate">
|
||||
<div class="col-xs-12">
|
||||
<form class="form-horizontal" role="form" name="dueDateForm">
|
||||
<div class="form-group">
|
||||
<label for="title" class="col-sm-2 control-label">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="title"
|
||||
name="title"
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="criteria.name"
|
||||
ng-change="searchDueDates()"
|
||||
placeholder="Due date name"
|
||||
ng-disabled="isSaving">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="table-container">
|
||||
<table class="table table-fixed table-striped table-hover">
|
||||
<tbody>
|
||||
<tr ng-repeat="result in results"
|
||||
ng-click="select(result)"
|
||||
ng-class="{'selected': selected === result}">
|
||||
<td>{{result.name}}</td>
|
||||
<td><span time-moment eventdate="result.date"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-right">
|
||||
<div class="btn" ng-show="isSaving">
|
||||
<i class="fa fa-spinner fa-lg fa-spin"></i>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
ng-click="save()"
|
||||
ng-disabled="!dueDateForm.$valid || isSaving">
|
||||
<span ng-show="mode != 'find'">
|
||||
Save Due Date
|
||||
</span>
|
||||
<span ng-show="mode === 'find'">
|
||||
Add Due Date
|
||||
</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
ng-click="close()"
|
||||
class="btn btn-default"
|
||||
ng-disabled="isSaving">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="/inline/permissions_tables.html">
|
||||
<div class="col-sm-6">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr title="Owners can modify the name and date of the Due
|
||||
Date, in addition to everything Users can do.">
|
||||
<th>Owners</th>
|
||||
<th class="text-right">
|
||||
<small>
|
||||
<a href
|
||||
ng-click="showAddOwner = !showAddOwner"
|
||||
ng-disabled="isUpdating">
|
||||
<i class="fa fa-plus" ng-if="!showAddOwner"></i>
|
||||
<i class="fa fa-minus" ng-if="showAddOwner"></i>
|
||||
Add Owner
|
||||
</a>
|
||||
</small>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="owner in owners">
|
||||
<td colspan="2">
|
||||
{{owner.full_name}}
|
||||
<a class="close"
|
||||
ng-show="owners.length > 1"
|
||||
ng-click="removeUser(owner, owners, dueDate.owners)">
|
||||
×
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="showAddOwner && !isUpdating">
|
||||
<td colspan="2">
|
||||
<input id="owner"
|
||||
type="text"
|
||||
placeholder="Click to add an owner"
|
||||
ng-model="asyncOwner"
|
||||
typeahead-wait-ms="200"
|
||||
typeahead-editable="false"
|
||||
typeahead="user as user.full_name for user in
|
||||
searchUsers($viewValue, dueDate.owners)"
|
||||
typeahead-loading="loadingUsers"
|
||||
typeahead-input-formatter="formatUserName($model)"
|
||||
typeahead-on-select="addUser($model, owners, dueDate.owners)"
|
||||
class="form-control input-sm"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr title="Users can assign the Due Date to Tasks and Stories, and
|
||||
add it to Boards or Worklists that they have access to.">
|
||||
<th>Users</th>
|
||||
<th class="text-right">
|
||||
<small>
|
||||
<a href
|
||||
ng-click="showAddUser = !showAddUser"
|
||||
ng-disabled="isUpdating">
|
||||
<i class="fa fa-plus" ng-if="!showAddUser"></i>
|
||||
<i class="fa fa-minus" ng-if="showAddUser"></i>
|
||||
Add User
|
||||
</a>
|
||||
</small>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="user in users">
|
||||
<td colspan="2">
|
||||
{{user.full_name}}
|
||||
<a class="close"
|
||||
ng-click="removeUser(user, users, dueDate.users)">
|
||||
×
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="showAddUser && !isUpdating">
|
||||
<td colspan="2">
|
||||
<input id="user"
|
||||
type="text"
|
||||
placeholder="Click to add a user"
|
||||
ng-model="asyncUser"
|
||||
typeahead-wait-ms="200"
|
||||
typeahead-editable="false"
|
||||
typeahead="user as user.full_name for user in
|
||||
searchUsers($viewValue, dueDate.users)"
|
||||
typeahead-loading="loadingUsers"
|
||||
typeahead-input-formatter="formatUserName($model)"
|
||||
typeahead-on-select="addUser($model, users, dueDate.users)"
|
||||
class="form-control input-sm"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</script>
|
|
@ -0,0 +1,57 @@
|
|||
<!--
|
||||
~ Copyright (c) 2015 Codethink Limited
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<button type="button" class="close" aria-hidden="true"
|
||||
ng-click="close()">×</button>
|
||||
<h3 class="panel-title">
|
||||
Removing Due Date:
|
||||
<span time-moment eventdate="dueDate.date"
|
||||
format-string="'MMMM Do, YYYY'"></span>
|
||||
<span ng-show="dueDate.name">({{dueDate.name}})</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2 text-center">
|
||||
<h2 class="text-danger">
|
||||
Confirm removal of due date
|
||||
</h2>
|
||||
<p class="text-danger">
|
||||
Are you certain that you want to remove the due date
|
||||
</p>
|
||||
<div>
|
||||
<span time-moment eventdate="dueDate.date"
|
||||
format-string="'MMMM Do, YYYY [at] HH:mm'"></span>
|
||||
<br>
|
||||
<span ng-show="dueDate.name" class="text-muted">{{dueDate.name}}</span>
|
||||
</div>
|
||||
<p class="text-danger">
|
||||
from the board "{{board.title}}"?
|
||||
</p>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="" class="btn btn-danger" ng-click="remove()">
|
||||
Remove
|
||||
</a>
|
||||
<a href="" class="btn btn-default" ng-click="close()">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -26,9 +26,9 @@ angular.module('storyboard',
|
|||
[ 'sb.services', 'sb.templates', 'sb.dashboard', 'sb.pages', 'sb.projects',
|
||||
'sb.auth', 'sb.story', 'sb.profile', 'sb.notification', 'sb.search',
|
||||
'sb.admin', 'sb.subscription', 'sb.project_group', 'sb.worklist',
|
||||
'sb.board', 'ui.router', 'ui.bootstrap', 'monospaced.elastic',
|
||||
'angularMoment', 'angular-data.DSCacheFactory', 'viewhead',
|
||||
'ngSanitize', 'as.sortable'])
|
||||
'sb.board', 'sb.due_date', 'ui.router', 'ui.bootstrap',
|
||||
'monospaced.elastic', 'angularMoment', 'angular-data.DSCacheFactory',
|
||||
'viewhead', 'ngSanitize', 'as.sortable'])
|
||||
.constant('angularMomentConfig', {
|
||||
preprocess: 'utc',
|
||||
timezone: 'UTC'
|
||||
|
|
|
@ -121,13 +121,39 @@
|
|||
border: 1px solid #bbb;
|
||||
& user-typeahead > .form-group {
|
||||
margin-bottom: 0px;
|
||||
margin-top: 10px;
|
||||
margin-top: 0px;
|
||||
> .form-control-static {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-card-due {
|
||||
border-right: @border-radius-base solid #555;
|
||||
& .target-date {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-card-late {
|
||||
border-right: @border-radius-base solid #555;
|
||||
& .target-date {
|
||||
font-weight: bold;
|
||||
color: @navbar-default-color;
|
||||
background-color: @brand-primary;
|
||||
border-radius: 10px;
|
||||
margin: 0px;
|
||||
|
||||
& .col-xs-1, & .col-xs-11 {
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
& .text-muted {
|
||||
color: @navbar-default-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-card-readonly {
|
||||
.kanban-card;
|
||||
|
||||
|
@ -141,6 +167,10 @@
|
|||
width: 100vw;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
& a {
|
||||
color: darken(@brand-primary, 10%);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,3 +192,20 @@
|
|||
box-sizing: border-box;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.due-date-modal {
|
||||
& .nav-tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
& .table-hover {
|
||||
& tr:hover {
|
||||
background-color: #efefef;
|
||||
cursor: pointer;
|
||||
}
|
||||
& tr.selected {
|
||||
background-color: @brand-primary;
|
||||
color: @navbar-default-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue