Add existing issue to sprint from taskboard
parent
8e12fd3528
commit
92460e3733
|
@ -567,10 +567,10 @@ $translate, $compile, $currentUserService, avatarService) ->
|
|||
renderUser = (assignedObject) ->
|
||||
if assignedObject?.assigned_to
|
||||
$scope.selected = assignedObject.assigned_to
|
||||
assignedObject.assigned_to_extra_info = $scope.usersById[$scope.selected]
|
||||
$scope.fullName = assignedObject.assigned_to_extra_info?.full_name_display
|
||||
assigned_to_extra_info = $scope.usersById[$scope.selected]
|
||||
$scope.fullName = assigned_to_extra_info?.full_name_display
|
||||
$scope.isUnassigned = false
|
||||
$scope.avatar = avatarService.getAvatar(assignedObject.assigned_to_extra_info)
|
||||
$scope.avatar = avatarService.getAvatar(assigned_to_extra_info)
|
||||
$scope.bg = $scope.avatar.bg
|
||||
$scope.isIocaine = assignedObject?.is_iocaine
|
||||
else
|
||||
|
|
|
@ -779,7 +779,13 @@ CreateEditDirective = (
|
|||
$log, $repo, $model, $rs, $rootScope, lightboxService, $loading, $translate,
|
||||
$confirm, $q, attachmentsService, $template, $compile) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
schema = null
|
||||
objType = null
|
||||
form = null
|
||||
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
schemas = {
|
||||
us: {
|
||||
objName: 'User Story',
|
||||
|
@ -851,67 +857,50 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
}
|
||||
}
|
||||
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
$scope.$on "genericform:new", (ctx, data) ->
|
||||
if beforeMount('new', data, ['project'])
|
||||
mountCreateForm(data)
|
||||
afterMount()
|
||||
getSchema(data)
|
||||
$scope.mode = 'new'
|
||||
$scope.getOrCreate = false
|
||||
mount(data)
|
||||
|
||||
$scope.$on "genericform:new-or-existing", (ctx, data) ->
|
||||
getSchema(data)
|
||||
$scope.mode = 'add-existing'
|
||||
$scope.getOrCreate = true
|
||||
$scope.existingFilterText = ''
|
||||
$scope.existingItems = {}
|
||||
$scope.existingOptions = data.existingOptions
|
||||
mount(data)
|
||||
|
||||
$scope.$on "genericform:edit", (ctx, data) ->
|
||||
if beforeMount('edit', data, ['project', 'obj', 'attachments'])
|
||||
mountUpdateForm(data)
|
||||
afterMount()
|
||||
getSchema(data)
|
||||
$scope.mode = 'edit'
|
||||
$scope.getOrCreate = false
|
||||
mount(data)
|
||||
|
||||
beforeMount = (mode, data, requiredAttrs) ->
|
||||
form.reset() if form
|
||||
|
||||
# Get form schema
|
||||
if !data.objType || !schemas[data.objType]
|
||||
return $log.error(
|
||||
"Invalid objType `#{data.objType}` for `genericform:#{mode}` event")
|
||||
getSchema = (data) ->
|
||||
$scope.objType = data.objType
|
||||
$scope.schema = schemas[data.objType]
|
||||
if !$scope.objType || !schemas[$scope.objType]
|
||||
return $log.error("Invalid objType `#{$scope.objType}` for `genericform` event")
|
||||
schema = schemas[$scope.objType]
|
||||
|
||||
# Get required attrs of the directive
|
||||
getAttrs(mode, data, requiredAttrs)
|
||||
|
||||
return true
|
||||
|
||||
mountCreateForm = (data) ->
|
||||
$scope.obj = $model.make_model($scope.schema.model, $scope.schema.initialData(data))
|
||||
$scope.isNew = true
|
||||
mount = (data) ->
|
||||
$scope.objName = schema.objName
|
||||
if $scope.mode == 'edit'
|
||||
$scope.obj = data.obj
|
||||
$scope.attachments = Immutable.fromJS(data.attachments)
|
||||
else
|
||||
$scope.obj = $model.make_model(schema.model, schema.initialData(data))
|
||||
$scope.attachments = Immutable.List()
|
||||
$scope.text = {
|
||||
title: $translate.instant("LIGHTBOX.CREATE_EDIT.NEW", { objName: $scope.schema.objName })
|
||||
action: $translate.instant("COMMON.CREATE")
|
||||
}
|
||||
render()
|
||||
|
||||
mountUpdateForm = (data) ->
|
||||
$scope.isNew = false
|
||||
$scope.attachments = Immutable.fromJS($scope.attachments)
|
||||
$scope.text = {
|
||||
title: $translate.instant("LIGHTBOX.CREATE_EDIT.EDIT", { objName: $scope.schema.objName })
|
||||
action: $translate.instant("COMMON.SAVE")
|
||||
}
|
||||
render()
|
||||
_.map schema.data($scope.project), (value, key) ->
|
||||
$scope[key] = value
|
||||
|
||||
afterMount = () ->
|
||||
form.reset() if form
|
||||
resetAttachments()
|
||||
setStatus($scope.obj?.status)
|
||||
|
||||
|
||||
$scope.createEditOpen = true
|
||||
lightboxService.open $el, () ->
|
||||
$scope.createEditOpen = false
|
||||
|
||||
getAttrs = (mode, data, attrs) ->
|
||||
for attr in attrs
|
||||
if !data[attr]
|
||||
return $log.error "`#{attr}` attribute required in `genericform:#{mode}` event"
|
||||
$scope[attr] = data[attr]
|
||||
setStatus($scope.obj.status)
|
||||
$scope.lightboxOpen = true
|
||||
lightboxService.open($el)
|
||||
|
||||
resetAttachments = () ->
|
||||
attachmentsToAdd = Immutable.List()
|
||||
|
@ -929,7 +918,6 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
|
||||
$scope.addTag = (tag, color) ->
|
||||
value = trim(tag.toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
projectTags = $scope.project.tags_colors
|
||||
|
||||
|
@ -940,11 +928,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
tags.push(value)
|
||||
|
||||
projectTags[tag] = color || null
|
||||
|
||||
$scope.project.tags = tags
|
||||
|
||||
itemtags = _.clone($scope.obj.tags)
|
||||
|
||||
inserted = _.find itemtags, (it) -> it[0] == value
|
||||
|
||||
if !inserted
|
||||
|
@ -953,98 +939,117 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
|
||||
$scope.deleteTag = (tag) ->
|
||||
value = trim(tag[0].toLowerCase())
|
||||
|
||||
tags = $scope.project.tags
|
||||
itemtags = _.clone($scope.obj.tags)
|
||||
|
||||
_.remove itemtags, (tag) -> tag[0] == value
|
||||
|
||||
$scope.obj.tags = itemtags
|
||||
|
||||
_.pull($scope.obj.tags, value)
|
||||
|
||||
|
||||
createAttachments = (obj) ->
|
||||
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
|
||||
attachmentsService.upload(
|
||||
attachment.file, obj.id, $scope.obj.project, $scope.objType)
|
||||
|
||||
attachmentsService.upload(attachment.file, obj.id, $scope.obj.project, $scope.objType)
|
||||
return $q.all(promises)
|
||||
|
||||
deleteAttachments = (obj) ->
|
||||
promises = _.map attachmentsToDelete.toJS(), (attachment) ->
|
||||
return attachmentsService.delete($scope.objType, attachment.id)
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
submit = debounce 2000, (event) ->
|
||||
event.preventDefault()
|
||||
addExisting = (ref) ->
|
||||
currentLoading = $loading().target($el.find(".add-existing-button")).start()
|
||||
selectedItem = $scope.existingItems[parseInt(ref)]
|
||||
selectedItem.setAttr($scope.existingOptions.targetField, $scope.existingOptions.targetValue)
|
||||
$repo.save(selectedItem, true).then (data) ->
|
||||
currentLoading.finish()
|
||||
lightboxService.close($el)
|
||||
$rootScope.$broadcast("#{$scope.objType}form:add:success", selectedItem)
|
||||
|
||||
$scope.getTargetTitle = (item) ->
|
||||
index = item[$scope.existingOptions.targetField]
|
||||
return $scope.existingOptions.targetsById[index]?.name
|
||||
|
||||
$scope.existingFilterChanged = (value) ->
|
||||
if value?
|
||||
$rs[schema.model].listInAllProjects(
|
||||
{ project: $scope.project.id, q: value }, true
|
||||
).then (data) ->
|
||||
$scope.existingItems = {}
|
||||
_.map(data, (itemModel) ->
|
||||
itemModel.html = itemModel.subject
|
||||
|
||||
targetTitle = $scope.getTargetTitle(itemModel)
|
||||
if targetTitle
|
||||
itemModel.html = "#{itemModel.html} (#{targetTitle})"
|
||||
itemModel.class = 'strong'
|
||||
|
||||
$scope.existingItems[itemModel.ref] = itemModel
|
||||
)
|
||||
|
||||
$scope.addExisting = (selectedItem) ->
|
||||
event.preventDefault()
|
||||
addExisting(selectedItem)
|
||||
|
||||
submit = debounce 2000, (event) ->
|
||||
form = $el.find("form").checksley()
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
currentLoading = $loading().target(submitButton).start()
|
||||
currentLoading = $loading().target($el.find(".submit-button")).start()
|
||||
|
||||
if $scope.isNew
|
||||
promise = $repo.create($scope.schema.model, $scope.obj)
|
||||
if $scope.mode == 'new'
|
||||
promise = $repo.create(schema.model, $scope.obj)
|
||||
broadcastEvent = "#{$scope.objType}form:new:success"
|
||||
else
|
||||
if ($scope.obj.due_date instanceof moment)
|
||||
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
|
||||
$scope.obj.due_date = $scope.obj.due_date.format("YYYY-MM-DD")
|
||||
|
||||
promise = $repo.save($scope.obj, true)
|
||||
broadcastEvent = "#{$scope.objType}form:edit:success"
|
||||
|
||||
promise.then (data) ->
|
||||
deleteAttachments(data)
|
||||
.then () -> createAttachments(data)
|
||||
.then () ->
|
||||
deleteAttachments(data).then () ->
|
||||
createAttachments(data).then () ->
|
||||
currentLoading.finish()
|
||||
lightboxService.close($el)
|
||||
|
||||
$rs[$scope.schema.model].getByRef(
|
||||
data.project, data.ref, $scope.schema.params).then (obj) ->
|
||||
close()
|
||||
$rs[schema.model].getByRef(data.project, data.ref, schema.params).then (obj) ->
|
||||
$rootScope.$broadcast(broadcastEvent, obj)
|
||||
|
||||
promise.then null, (data) ->
|
||||
currentLoading.finish()
|
||||
form.setErrors(data)
|
||||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
close = () ->
|
||||
checkClose = () ->
|
||||
if !$scope.obj.isModified()
|
||||
lightboxService.close($el)
|
||||
close()
|
||||
$scope.$apply ->
|
||||
$scope.obj.revert()
|
||||
else
|
||||
$confirm.ask(
|
||||
$translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")).then (result) ->
|
||||
lightboxService.close($el)
|
||||
$scope.obj.revert()
|
||||
result.finish()
|
||||
close()
|
||||
|
||||
close = () ->
|
||||
delete $scope.objType
|
||||
delete $scope.mode
|
||||
$scope.lightboxOpen = false
|
||||
lightboxService.close($el)
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
|
||||
$el.find('.close').on "click", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
close()
|
||||
checkClose()
|
||||
|
||||
$el.keydown (event) ->
|
||||
event.stopPropagation()
|
||||
code = if event.keyCode then event.keyCode else event.which
|
||||
if code == 27
|
||||
close()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.find('.close').off()
|
||||
$el.off()
|
||||
|
||||
$scope.$watch "obj", ->
|
||||
if !$scope.obj
|
||||
return
|
||||
setStatus($scope.obj.status)
|
||||
checkClose()
|
||||
|
||||
$el.on "click", ".status-dropdown", (event) ->
|
||||
event.preventDefault()
|
||||
|
@ -1080,28 +1085,30 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
$scope.obj.is_iocaine = not $scope.obj.is_iocaine
|
||||
$scope.$broadcast("isiocaine:changed", $scope.obj)
|
||||
|
||||
setStatus = (id) ->
|
||||
$scope.obj.status = id
|
||||
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
|
||||
|
||||
$scope.isTeamRequirement = () ->
|
||||
return $scope.obj?.team_requirement
|
||||
|
||||
$scope.isClientRequirement = () ->
|
||||
return $scope.obj?.client_requirement
|
||||
|
||||
render = () ->
|
||||
templatePath = "common/lightbox/lightbox-create-edit/lb-create-edit-#{$scope.objType}.html"
|
||||
template = $template.get(templatePath, true)
|
||||
setStatus = (id) ->
|
||||
$scope.obj.status = id
|
||||
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
|
||||
$scope.obj.is_closed = $scope.selectedStatus.is_closed
|
||||
|
||||
_.map $scope.schema.data($scope.project), (value, key) ->
|
||||
render = () ->
|
||||
# templatePath = "common/lightbox/lightbox-create-edit/lb-create-edit-#{$scope.objType}.html"
|
||||
# template = $template.get(templatePath, true)
|
||||
|
||||
_.map schema.data($scope.project), (value, key) ->
|
||||
$scope[key] = value
|
||||
|
||||
html = $compile(template($scope))($scope)
|
||||
$el.html(html)
|
||||
# html = $compile(template($scope))($scope)
|
||||
# $el.html(html)
|
||||
|
||||
return {
|
||||
link: link
|
||||
templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"
|
||||
}
|
||||
|
||||
module.directive("tgLbCreateEdit", [
|
||||
|
|
|
@ -107,7 +107,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
|
||||
else if @.zoomLevel > 1 && previousZoomLevel <= 1
|
||||
@.zoomLoading = true
|
||||
@.loadTasks().then () =>
|
||||
@q.all([@.loadTasks(), @.loadIssues()]).then () =>
|
||||
@.zoomLoading = false
|
||||
@taskboardTasksService.resetFolds()
|
||||
|
||||
|
@ -320,6 +320,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
|
||||
@analytics.trackEvent("issue", "create", "create issue on taskboard", 1)
|
||||
|
||||
@scope.$on "issueform:add:success", (event, issue) =>
|
||||
@.refreshTagsColors().then () =>
|
||||
@taskboardIssuesService.add(issue)
|
||||
|
||||
@scope.$on "issueform:edit:success", (event, issue) =>
|
||||
@.refreshTagsColors().then () =>
|
||||
@taskboardIssuesService.replaceModel(issue)
|
||||
|
@ -376,6 +380,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
@scope.taskStatusList = _.sortBy(project.task_statuses, "order")
|
||||
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
|
||||
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id)
|
||||
@scope.issueStatusById = groupBy(project.issue_statuses, (e) -> e.id)
|
||||
@scope.milestonesById = groupBy(project.milestones, (e) -> e.id)
|
||||
|
||||
@scope.$emit('project:loaded', project)
|
||||
|
||||
|
@ -417,10 +423,14 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
|
||||
loadIssues: ->
|
||||
params = {}
|
||||
|
||||
if @.zoomLevel > 1
|
||||
params.include_attachments = 1
|
||||
|
||||
params = _.merge params, @location.search()
|
||||
|
||||
return @rs.issues.listInProject(@scope.projectId, @scope.sprintId, params).then (issues) =>
|
||||
@taskboardIssuesService.init(@scope.project, @scope.usersById)
|
||||
@taskboardIssuesService.init(@scope.project, @scope.usersById, @scope.issueStatusById)
|
||||
@taskboardIssuesService.set(issues)
|
||||
|
||||
loadTasks: ->
|
||||
|
@ -544,6 +554,26 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
askResponse.finish(false)
|
||||
@confirm.notify("error")
|
||||
|
||||
removeIssueFromSprint: (id) ->
|
||||
issue = @.taskboardIssuesService.getIssue(id)
|
||||
issue = issue.set('loading-delete', true)
|
||||
|
||||
@rs.issues.getByRef(issue.getIn(['model', 'project']), issue.getIn(['model', 'ref']))
|
||||
.then (removingIssue) =>
|
||||
issue = issue.set('loading-delete', false)
|
||||
title = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.TITLE")
|
||||
subtitle = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.MESSAGE")
|
||||
message = removingIssue.subject
|
||||
@confirm.askOnDelete(title, message, subtitle).then (askResponse) =>
|
||||
removingIssue.milestone = null
|
||||
promise = @repo.save(removingIssue)
|
||||
promise.then =>
|
||||
@.taskboardIssuesService.remove(removingIssue)
|
||||
askResponse.finish()
|
||||
promise.then null, ->
|
||||
askResponse.finish(false)
|
||||
@confirm.notify("error")
|
||||
|
||||
taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
|
||||
task = @taskboardTasksService.getTaskModel(task.get('id'))
|
||||
|
||||
|
@ -587,16 +617,25 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
|
||||
addNewIssue: (type, us) ->
|
||||
switch type
|
||||
when "standard" then @rootscope.$broadcast("genericform:new",
|
||||
when "standard" then @rootscope.$broadcast("genericform:new-or-existing",
|
||||
{
|
||||
'objType': 'issue',
|
||||
'project': @scope.project,
|
||||
'sprintId': @scope.sprintId
|
||||
'sprintId': @scope.sprintId,
|
||||
'existingOptions': {
|
||||
targetField: 'milestone',
|
||||
targetValue: @scope.sprintId,
|
||||
targetsById: @scope.milestonesById,
|
||||
title: "#{@translate.instant("COMMON.FIELDS.SPRINT")} #{@scope.sprint.name}",
|
||||
}
|
||||
})
|
||||
when "standard" then @rootscope.$broadcast("taskform:new", @scope.sprintId, us?.id)
|
||||
when "bulk" then @rootscope.$broadcast("issueform:bulk", @scope.projectId, @scope.sprintId)
|
||||
|
||||
toggleFold: (id) ->
|
||||
toggleFold: (id, modelName) ->
|
||||
if modelName == 'issues'
|
||||
@taskboardIssuesService.toggleFold(id)
|
||||
else if modelName == 'tasks'
|
||||
@taskboardTasksService.toggleFold(id)
|
||||
|
||||
changeTaskAssignedTo: (id) ->
|
||||
|
|
|
@ -25,16 +25,33 @@ class TaskboardIssuesService extends taiga.Service
|
|||
@.reset()
|
||||
|
||||
reset: () ->
|
||||
@.foldStatusChanged = {}
|
||||
@.issuesRaw = []
|
||||
|
||||
init: (project, usersById) ->
|
||||
init: (project, usersById, issueStatusById) ->
|
||||
@.issueStatusById = issueStatusById
|
||||
@.project = project
|
||||
@.usersById = usersById
|
||||
|
||||
resetFolds: () ->
|
||||
@.foldStatusChanged = {}
|
||||
@.refresh()
|
||||
|
||||
toggleFold: (issueId) ->
|
||||
@.foldStatusChanged[issueId] = !@.foldStatusChanged[issueId]
|
||||
@.refresh()
|
||||
|
||||
add: (issue) ->
|
||||
@.issuesRaw = @.issuesRaw.concat(issue)
|
||||
@.refresh()
|
||||
|
||||
remove: (issue) ->
|
||||
for key, item of @.issuesRaw
|
||||
if issue.id == item.id
|
||||
@.issuesRaw.splice(key, 1)
|
||||
@.refresh()
|
||||
return
|
||||
|
||||
set: (issues) ->
|
||||
@.issuesRaw = issues
|
||||
@.refresh()
|
||||
|
@ -58,14 +75,14 @@ class TaskboardIssuesService extends taiga.Service
|
|||
issues = []
|
||||
for issueModel in @.issuesRaw
|
||||
issue = {}
|
||||
|
||||
model = issueModel.getAttrs()
|
||||
|
||||
issue.model = model
|
||||
issue.images = _.filter model.attachments, (it) -> return !!it.thumbnail_card_url
|
||||
issue.foldStatusChanged = @.foldStatusChanged[issueModel.id]
|
||||
issue.model = issueModel.getAttrs()
|
||||
issue.modelName = issueModel.getName()
|
||||
issue.id = issueModel.id
|
||||
issue.status = @.issueStatusById[issueModel.status]
|
||||
issue.images = _.filter issue.model.attachments, (it) -> return !!it.thumbnail_card_url
|
||||
issue.assigned_to = @.usersById[issueModel.assigned_to]
|
||||
issue.colorized_tags = _.map issue.model.tags, (tag) =>
|
||||
issue.colorized_tags = _.map issue.model.tags, (tag) ->
|
||||
return {name: tag[0], color: tag[1]}
|
||||
|
||||
issues.push(issue)
|
||||
|
|
|
@ -140,6 +140,9 @@ class TaskboardTasksService extends taiga.Service
|
|||
return {"task_id": task.id, "order": @.order[task.id], "set_orders": setOrders}
|
||||
|
||||
refresh: ->
|
||||
if !@.project
|
||||
return
|
||||
|
||||
@.tasksRaw = _.sortBy @.tasksRaw, (it) => @.order[it.id]
|
||||
|
||||
tasks = @.tasksRaw
|
||||
|
|
|
@ -48,7 +48,8 @@
|
|||
"CARD": {
|
||||
"ASSIGN_TO": "Assign To",
|
||||
"EDIT": "Edit card",
|
||||
"DELETE": "Delete card"
|
||||
"DELETE": "Delete card",
|
||||
"REMOVE_ISSUE_FROM_SPRINT": "Remove issue from sprint"
|
||||
},
|
||||
"FORM_ERRORS": {
|
||||
"DEFAULT_MESSAGE": "This value seems to be invalid.",
|
||||
|
@ -1097,7 +1098,12 @@
|
|||
"PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this {{ objName }}",
|
||||
"NEW": "New {{ objName }}",
|
||||
"EDIT": "Edit {{ objName }}",
|
||||
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?"
|
||||
"ADD_EXISTING": "Add {{ objName }} to {{ targetName }}",
|
||||
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?",
|
||||
"EXISTING_OBJECT": "Existing {{ objName }}",
|
||||
"NEW_OBJECT": "New {{ objName }}",
|
||||
"CHOOSE_EXISTING": "What's the {{ objName }}?",
|
||||
"NO_ITEMS_FOUND": "It looks like nothing was found with your search criteria"
|
||||
},
|
||||
"DELETE_DUE_DATE": {
|
||||
"TITLE": "Delete due date",
|
||||
|
@ -1351,6 +1357,8 @@
|
|||
"TITLE_ACTION_ASSIGN": "Assign task",
|
||||
"PLACEHOLDER_CARD_TITLE": "This could be a task",
|
||||
"PLACEHOLDER_CARD_TEXT": "Split Stories into tasks to track them separately",
|
||||
"NEW_ISSUE": "New issue",
|
||||
"EXISTING_ISSUE": "Existing issue",
|
||||
"TABLE": {
|
||||
"COLUMN": "User story",
|
||||
"TITLE_ACTION_FOLD": "Fold column",
|
||||
|
@ -1426,6 +1434,10 @@
|
|||
"SEVERITY": "Severity",
|
||||
"TYPE": "Type"
|
||||
},
|
||||
"CONFIRM_REMOVE_FROM_SPRINT": {
|
||||
"TITLE": "Remove issue from sprint",
|
||||
"MESSAGE": "Are you sure you want to remove this issue from the sprint?"
|
||||
},
|
||||
"CONFIRM_PROMOTE": {
|
||||
"TITLE": "Promote this issue to a new user story",
|
||||
"MESSAGE": "Are you sure you want to create a new US from this Issue?"
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
ng-if="vm.visible('extra_info')"
|
||||
ng-class="{'empty-tasks': !vm.item.getIn(['model', 'tasks']).size}"
|
||||
)
|
||||
span(ng-switch="vm.item.get('modelName') == 'issues'")
|
||||
span(ng-switch-when="true")
|
||||
span.card-status-tag(
|
||||
ng-if="vm.item.get('status')"
|
||||
ng-style="{color: vm.item.getIn(['status', 'color'])}"
|
||||
) {{ vm.item.getIn(['status', 'name']) }}
|
||||
span(ng-switch-when="false")
|
||||
span.card-estimation.not-estimated(
|
||||
ng-if="vm.item.getIn(['model', 'total_points']) === null && vm.visible('empty_extra_info')",
|
||||
translate="US.NOT_ESTIMATED"
|
||||
|
@ -9,6 +16,7 @@
|
|||
span.card-estimation(
|
||||
ng-if="vm.item.getIn(['model', 'total_points'])"
|
||||
) {{"COMMON.FIELDS.POINTS" | translate}} {{vm.item.getIn(['model', 'total_points'])}}
|
||||
|
||||
.card-statistics
|
||||
tg-due-date.statistic.card-due-date(
|
||||
due-date="vm.item.getIn(['model', 'due_date'])"
|
||||
|
|
|
@ -46,7 +46,14 @@
|
|||
title="{{ 'COMMON.CARD.EDIT' | translate }}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-edit")
|
||||
|
||||
a.e2e-edit.card-edit(
|
||||
href=""
|
||||
ng-if="vm.item.get('modelName') == 'issues'"
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickRemove({id: vm.item.get('id')})"
|
||||
tg-loading="vm.item.get('loading-remove-from-sprint')"
|
||||
title="{{ 'COMMON.CARD.REMOVE_ISSUE_FROM_SPRINT' | translate }}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-close")
|
||||
a.e2e-edit.card-delete(
|
||||
href=""
|
||||
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickDelete({id: vm.item.get('id')})"
|
||||
|
|
|
@ -87,6 +87,8 @@ class CardController
|
|||
getNavKey: () ->
|
||||
if @.type == 'task'
|
||||
return 'project-tasks-detail'
|
||||
else if @.type == 'issue'
|
||||
return 'project-issues-detail'
|
||||
else
|
||||
return 'project-userstories-detail'
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ cardDirective = () ->
|
|||
onToggleFold: "&",
|
||||
onClickAssignedTo: "&",
|
||||
onClickEdit: "&",
|
||||
onClickRemove: "&",
|
||||
onClickDelete: "&",
|
||||
project: "=",
|
||||
item: "=",
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 0 0 .5rem;
|
||||
}
|
||||
.card-delete:hover {
|
||||
color: $red-light;
|
||||
|
@ -160,6 +160,12 @@
|
|||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
padding: 0 1rem .5rem;
|
||||
.card-status-tag {
|
||||
font-size: .75rem;
|
||||
height: .1rem;
|
||||
line-height: .1rem;
|
||||
padding: 0 .5em 0 0;
|
||||
}
|
||||
.card-estimation.not-estimated {
|
||||
font-size: .8125rem;
|
||||
}
|
||||
|
|
|
@ -22,24 +22,6 @@ module = angular.module("taigaComponents")
|
|||
dueDatePopoverDirective = ($translate, datePickerConfigService) ->
|
||||
return {
|
||||
link: (scope, el, attrs, ctrl) ->
|
||||
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
|
||||
if ctrl.dueDate
|
||||
ctrl.dueDate = moment(ctrl.dueDate, prettyDate)
|
||||
|
||||
el.on "click", ".date-picker-popover-trigger", (event) ->
|
||||
if ctrl.disabled()
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
el.find(".date-picker-popover").popover().open()
|
||||
|
||||
el.on "click", ".date-picker-clean", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
ctrl.dueDate = null
|
||||
scope.$apply()
|
||||
el.find(".date-picker-popover").popover().close()
|
||||
|
||||
datePickerConfig = datePickerConfigService.get()
|
||||
_.merge(datePickerConfig, {
|
||||
field: el.find('input.due-date')[0]
|
||||
|
@ -50,9 +32,28 @@ dueDatePopoverDirective = ($translate, datePickerConfigService) ->
|
|||
el.find(".date-picker-popover").popover().close()
|
||||
scope.$apply()
|
||||
})
|
||||
|
||||
el.picker = new Pikaday(datePickerConfig)
|
||||
|
||||
el.on "click", ".date-picker-popover-trigger", (event) ->
|
||||
if ctrl.disabled()
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if !el.picker.getDate()
|
||||
el.picker.setDate(moment(ctrl.dueDate).format('YYYY-MM-DD'))
|
||||
el.find(".date-picker-popover").popover().open()
|
||||
|
||||
el.on "click", ".date-picker-clean", (event) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
ctrl.dueDate = null
|
||||
el.picker.setDate(ctrl.dueDate)
|
||||
el.find(".date-picker-popover").popover().close()
|
||||
scope.$apply()
|
||||
|
||||
scope.$on "status:changed", (ctx, status) ->
|
||||
ctrl.isClosed = ctrl.item.is_closed
|
||||
|
||||
controller: "DueDateCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
extends lb-create-edit
|
||||
|
||||
block options
|
||||
section.ticket-assigned-to(
|
||||
tg-assigned-to-inline
|
||||
ng-model="obj"
|
||||
|
@ -33,8 +30,3 @@ block options
|
|||
ng-class="{ 'button-red item-unblock': obj.is_blocked, 'item-block': !obj.is_blocked }"
|
||||
)
|
||||
tg-svg(svg-icon="icon-lock")
|
||||
|
||||
tg-blocking-message-input(
|
||||
watch="obj.is_blocked"
|
||||
ng-model="obj.blocked_note"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
extends lb-create-edit
|
||||
|
||||
block options
|
||||
section.ticket-assigned-to(
|
||||
tg-assigned-to-inline
|
||||
ng-model="obj"
|
||||
|
@ -33,8 +30,3 @@ block options
|
|||
ng-class="{ 'button-red item-unblock': obj.is_blocked, 'item-block': !obj.is_blocked }"
|
||||
)
|
||||
tg-svg(svg-icon="icon-lock")
|
||||
|
||||
tg-blocking-message-input(
|
||||
watch="obj.is_blocked"
|
||||
ng-model="obj.blocked_note"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
extends lb-create-edit
|
||||
|
||||
block options
|
||||
section.ticket-assigned-to.multiple-assign(
|
||||
tg-assigned-users-inline
|
||||
ng-model="obj"
|
||||
|
@ -37,8 +34,3 @@ block options
|
|||
ng-class="{ 'button-red item-unblock': obj.is_blocked, 'item-block': !obj.is_blocked }"
|
||||
)
|
||||
tg-svg(svg-icon="icon-lock")
|
||||
|
||||
tg-blocking-message-input(
|
||||
watch="obj.is_blocked"
|
||||
ng-model="obj.blocked_note"
|
||||
)
|
||||
|
|
|
@ -1,8 +1,72 @@
|
|||
tg-lightbox-close
|
||||
|
||||
form
|
||||
h2.title {{ text.title }}
|
||||
div.form-wrapper
|
||||
form(ng-if="lightboxOpen")
|
||||
h2.title(ng-switch="mode")
|
||||
span(ng-switch-when="new") {{ 'LIGHTBOX.CREATE_EDIT.NEW' | translate: { objName: objName } }}
|
||||
span(ng-switch-when="edit") {{ 'LIGHTBOX.CREATE_EDIT.EDIT' | translate: { objName: objName } }}
|
||||
span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: existingOptions.title } }}
|
||||
|
||||
.existing-or-new-selector(ng-show="getOrCreate == true")
|
||||
.existing-or-new-selector-single
|
||||
input(
|
||||
type="radio"
|
||||
name="related-with-selector"
|
||||
id="add-existing"
|
||||
value="add-existing"
|
||||
ng-model="mode"
|
||||
)
|
||||
label.e2e-existing-user-story-label(for="add-existing")
|
||||
span.name {{ 'LIGHTBOX.CREATE_EDIT.EXISTING_OBJECT' | translate: { objName: objName } }}
|
||||
|
||||
.existing-or-new-selector-single
|
||||
input(
|
||||
type="radio"
|
||||
name="related-with-selector"
|
||||
id="new"
|
||||
value="new"
|
||||
ng-model="mode"
|
||||
)
|
||||
label.e2e-new-userstory-label(for="new")
|
||||
span.name {{ 'LIGHTBOX.CREATE_EDIT.NEW_OBJECT' | translate: { objName: objName } }}
|
||||
|
||||
div(ng-if="mode == 'add-existing'")
|
||||
.existing-item-wrapper
|
||||
label(for="existing-filter") {{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}
|
||||
input.filter(
|
||||
id="existing-filter"
|
||||
name="existing-filter"
|
||||
type="text"
|
||||
ng-model="existingFilterText"
|
||||
ng-model-options="{ debounce: 200 }"
|
||||
ng-change="existingFilterChanged(existingFilterText)"
|
||||
)
|
||||
|
||||
.existing-item(ng-show="existingItems")
|
||||
select.userstory.e2e-userstories-select(
|
||||
size="5"
|
||||
ng-model="selectedItem"
|
||||
data-required="true"
|
||||
)
|
||||
- var hash = "#";
|
||||
option.hidden(value="")
|
||||
option(
|
||||
ng-repeat="(ref, obj) in existingItems"
|
||||
ng-class="obj.class"
|
||||
value="{{ ::ref }}"
|
||||
) #{hash}{{ ref }} {{ obj.html }}
|
||||
|
||||
p.no-stories-found(
|
||||
ng-show="existingFilterText && !existingItems"
|
||||
translate="EPIC.NO_USERSTORIES_FOUND"
|
||||
) {{ 'LIGHTBOX.CREATE_EDIT.NO_ITEMS_FOUND' | translate }}
|
||||
|
||||
button.button-green.add-existing-button(
|
||||
ng-click="addExisting(selectedItem)"
|
||||
ng-disabled="!selectedItem"
|
||||
) {{ 'COMMON.ADD' | translate }} {{ objName }}
|
||||
|
||||
div(ng-if="mode != 'add-existing'")
|
||||
.form-wrapper
|
||||
main
|
||||
fieldset
|
||||
input(
|
||||
|
@ -17,7 +81,7 @@ form
|
|||
|
||||
fieldset
|
||||
tg-tag-line-common.tags-block(
|
||||
ng-if="project && createEditOpen"
|
||||
ng-if="project"
|
||||
project="project"
|
||||
tags="obj.tags"
|
||||
permissions="add_{{objType}}"
|
||||
|
@ -55,8 +119,21 @@ form
|
|||
data-status-id="{{ s.id }}"
|
||||
) {{ s.name }}
|
||||
|
||||
block options
|
||||
div(ng-switch="objType")
|
||||
div(ng-switch-when="issue")
|
||||
include lb-create-edit-issue
|
||||
div(ng-switch-when="task")
|
||||
include lb-create-edit-task
|
||||
div(ng-switch-when="us")
|
||||
include lb-create-edit-us
|
||||
|
||||
button.button-green.submit-button(type="submit") {{ text.action }}
|
||||
tg-blocking-message-input(
|
||||
watch="obj.is_blocked"
|
||||
ng-model="obj.blocked_note"
|
||||
)
|
||||
|
||||
button.button-green.submit-button(type="submit", ng-switch="mode")
|
||||
span(ng-switch-when="new") {{ 'COMMON.CREATE' | translate }}
|
||||
span(ng-switch-when="edit") {{ 'COMMON.SAVE' | translate }}
|
||||
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
|
@ -83,7 +83,7 @@ div.taskboard-table(
|
|||
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
|
||||
tg-class-permission="{'readonly': '!modify_task'}"
|
||||
tg-bind-scope,
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-toggle-fold="ctrl.toggleFold(id, 'tasks')"
|
||||
on-click-edit="ctrl.editTask(id)"
|
||||
on-click-delete="ctrl.deleteTask(id)"
|
||||
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
|
||||
|
@ -129,7 +129,7 @@ div.taskboard-table(
|
|||
tg-repeat="task in usTasks.getIn(['null', st.id.toString()]) track by task.get('id')"
|
||||
ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
|
||||
tg-class-permission="{'readonly': '!modify_task'}"
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-toggle-fold="ctrl.toggleFold(id, 'tasks')"
|
||||
on-click-edit="ctrl.editTask(id)"
|
||||
on-click-delete="ctrl.deleteTask(id)"
|
||||
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
|
||||
|
@ -141,21 +141,22 @@ div.taskboard-table(
|
|||
)
|
||||
div.taskboard-row.issues-row(ng-class="{'row-fold':usFolded[0]}")
|
||||
div.taskboard-row-title-box.taskboard-column
|
||||
a.vfold(
|
||||
div.task-colum-name
|
||||
a.toggle-fold.vfold(
|
||||
href=""
|
||||
title="{{'TASKBOARD.TABLE.TITLE_ACTION_FOLD_ROW' | translate}}"
|
||||
ng-click='foldUs(0)'
|
||||
ng-class="{hidden:usFolded[0]}"
|
||||
)
|
||||
tg-svg.fold-action(svg-icon="icon-fold-row")
|
||||
a.vunfold(
|
||||
a.toggle-fold.vunfold(
|
||||
href=""
|
||||
title="{{'TASKBOARD.TABLE.TITLE_ACTION_UNFOLD_ROW' | translate}}"
|
||||
ng-click='foldUs(0)'
|
||||
ng-class="{hidden:!usFolded[0]}"
|
||||
)
|
||||
tg-svg.fold-action(svg-icon="icon-unfold-row")
|
||||
h3.task-colum-name(translate="TASKBOARD.TABLE.ROW_ISSUES_TITLE")
|
||||
span.row-title(translate="TASKBOARD.TABLE.ROW_ISSUES_TITLE")
|
||||
include ../components/addnewissue.jade
|
||||
|
||||
div.taskboard-cards-box
|
||||
|
@ -164,8 +165,9 @@ div.taskboard-table(
|
|||
class="kanban-task-minimized"
|
||||
tg-class-permission="{'readonly': '!modify_issue'}"
|
||||
tg-bind-scope,
|
||||
on-toggle-fold="ctrl.toggleFold(id)"
|
||||
on-toggle-fold="ctrl.toggleFold(id, 'issues')"
|
||||
on-click-edit="ctrl.editIssue(id)"
|
||||
on-click-remove="ctrl.removeIssueFromSprint(id)"
|
||||
on-click-delete="ctrl.deleteIssue(id)"
|
||||
on-click-assigned-to="ctrl.changeIssueAssignedTo(id)"
|
||||
project="project"
|
||||
|
|
|
@ -54,20 +54,7 @@ $column-padding: .5rem 1rem;
|
|||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.taskboard-table-header {
|
||||
flex-basis: 2.4rem;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
min-height: 2.4rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.taskboard-table-inner {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
.task-colum-name {
|
||||
@include font-size(medium);
|
||||
align-items: center;
|
||||
|
@ -104,6 +91,20 @@ $column-padding: .5rem 1rem;
|
|||
@include ellipsis(65%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.taskboard-table-header {
|
||||
flex-basis: 2.4rem;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
min-height: 2.4rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.taskboard-table-inner {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
tg-svg {
|
||||
display: block;
|
||||
margin-right: .3rem;
|
||||
|
@ -184,6 +185,22 @@ $column-padding: .5rem 1rem;
|
|||
max-height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
.taskboard-row-title-box {
|
||||
padding: 0;
|
||||
}
|
||||
.task-colum-name {
|
||||
justify-content: flex-start;
|
||||
padding: .5rem .5rem .5rem 2em;
|
||||
}
|
||||
.row-title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.toggle-fold {
|
||||
display: block;
|
||||
left: .5rem;
|
||||
position: absolute;
|
||||
top: -.4rem;
|
||||
}
|
||||
.card {
|
||||
cursor: default;
|
||||
height: auto;
|
||||
|
|
|
@ -21,23 +21,40 @@
|
|||
min-height: 4.5rem;
|
||||
resize: vertical;
|
||||
}
|
||||
label {
|
||||
@include font-size(xsmall);
|
||||
background: $mass-white;
|
||||
border: 1px solid $gray-light;
|
||||
color: $grayer;
|
||||
|
||||
.existing-or-new-selector {
|
||||
display: flex;
|
||||
margin-bottom: 2rem;
|
||||
input {
|
||||
display: none;
|
||||
&:checked+label {
|
||||
background: $primary-light;
|
||||
color: $white;
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
&:checked+label:hover {
|
||||
background: $primary-light;
|
||||
}
|
||||
+label {
|
||||
background: rgba($whitish, .7);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 7px 30px;
|
||||
transition: all .2s ease-in;
|
||||
&:hover {
|
||||
span {
|
||||
color: $white;
|
||||
font-size: 1em;
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
+label:hover {
|
||||
background: rgba($primary-light, .3);
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
}
|
||||
span {
|
||||
color: $grayer;
|
||||
vertical-align: middle;
|
||||
.existing-or-new-selector-single {
|
||||
flex: 1;
|
||||
&:first-child {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,14 +639,25 @@
|
|||
margin-bottom: $spacing * 2;
|
||||
main {
|
||||
flex-grow: 1;
|
||||
margin-right: $spacing;
|
||||
max-width: $width - $sidebar-width;
|
||||
}
|
||||
.sidebar {
|
||||
border-left: 2px solid $whitish;
|
||||
margin-left: $spacing;
|
||||
padding-left: $spacing;
|
||||
$min-width: $sidebar-width;
|
||||
width: $sidebar-width;
|
||||
}
|
||||
}
|
||||
.existing-item-wrapper {
|
||||
margin-bottom: $spacing * 2;
|
||||
select .strong {
|
||||
@include font-type(bold);
|
||||
}
|
||||
}
|
||||
.add-existing-button {
|
||||
width: 100%;
|
||||
}
|
||||
.status-button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
|
Loading…
Reference in New Issue