Generic lightbox form to create/edit us, tasks and issues

stable
Daniel García 2018-05-08 16:42:41 +02:00 committed by Alex Hermida
parent c2f69429df
commit 90fbaaee0e
38 changed files with 1527 additions and 879 deletions

View File

@ -541,7 +541,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
return @rs.userstories.getByRef(projectId, ref).then (us) =>
@rs2.attachments.list("us", us.id, projectId).then (attachments) =>
@rootscope.$broadcast("usform:edit", us, attachments.toJS())
@rootscope.$broadcast("genericform:edit", {
'objType': 'us',
'statusList': @scope.usStatusList,
'obj': us,
'attachments': attachments.toJS()
})
currentLoading.finish()
deleteUserStory: (us) ->
@ -566,8 +571,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
addNewUs: (type) ->
switch type
when "standard" then @rootscope.$broadcast("usform:new", @scope.projectId,
@scope.project.default_us_status, @scope.usStatusList)
when "standard" then @rootscope.$broadcast("genericform:new",
{
'objType': 'us',
'project': @scope.project,
'statusList': @scope.usStatusList
})
when "bulk" then @rootscope.$broadcast("usform:bulk", @scope.projectId,
@scope.project.default_us_status)
@ -575,13 +584,13 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@rootscope.$broadcast("sprintform:create", @scope.projectId)
findCurrentSprint: () ->
currentDate = new Date().getTime()
currentDate = new Date().getTime()
return _.find @scope.sprints, (sprint) ->
start = moment(sprint.estimated_start, 'YYYY-MM-DD').format('x')
end = moment(sprint.estimated_finish, 'YYYY-MM-DD').format('x')
return _.find @scope.sprints, (sprint) ->
start = moment(sprint.estimated_start, 'YYYY-MM-DD').format('x')
end = moment(sprint.estimated_finish, 'YYYY-MM-DD').format('x')
return currentDate >= start && currentDate <= end
return currentDate >= start && currentDate <= end
module.controller("BacklogController", BacklogController)

View File

@ -525,6 +525,266 @@ module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoa
AssignedToDirective])
#############################################################################
## Assigned to (inline) directive
#############################################################################
AssignedToInlineDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template
$translate, $compile, $currentUserService, avatarService) ->
link = ($scope, $el, $attrs, $model) ->
isEditable = ->
return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1
normalizeString = (string) ->
normalizedString = string
normalizedString = normalizedString.replace("Á", "A").replace("Ä", "A").replace("À", "A")
normalizedString = normalizedString.replace("É", "E").replace("Ë", "E").replace("È", "E")
normalizedString = normalizedString.replace("Í", "I").replace("Ï", "I").replace("Ì", "I")
normalizedString = normalizedString.replace("Ó", "O").replace("Ö", "O").replace("Ò", "O")
normalizedString = normalizedString.replace("Ú", "U").replace("Ü", "U").replace("Ù", "U")
return normalizedString
filterUsers = (text, user) ->
username = user.full_name_display.toUpperCase()
username = normalizeString(username)
text = text.toUpperCase()
text = normalizeString(text)
return _.includes(username, text)
renderUserlist = (text) ->
users = _.clone($scope.activeUsers, true)
users = _.reject(users, {"id": $scope.selected.id}) if $scope.selected?
users = _.sortBy(users, (o) -> if o.id is $scope.user.id then 0 else o.id)
users = _.filter(users, _.partial(filterUsers, text)) if text?
visibleUsers = _.slice(users, 0, 5)
visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user)
$scope.users = _.slice(users, 0, 5)
$scope.showMore = users.length > 5
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
$scope.isUnassigned = false
$scope.avatar = avatarService.getAvatar(assignedObject.assigned_to_extra_info)
$scope.bg = $scope.avatar.bg
$scope.isIocaine = assignedObject?.is_iocaine
else
$scope.fullName = $translate.instant("COMMON.ASSIGNED_TO.ASSIGN")
$scope.isUnassigned = true
$scope.avatar = avatarService.getAvatar(null)
$scope.bg = null
$scope.isIocaine = false
$scope.fullNameVisible = !($scope.isUnassigned && !$currentUserService.isAuthenticated())
$scope.isEditable = isEditable()
$el.on "click", ".users-dropdown", (event) ->
event.preventDefault()
event.stopPropagation()
renderUserlist()
$scope.$apply()
$el.find(".pop-users").popover().open()
$el.on "click", ".users-search", (event) ->
event.stopPropagation()
$el.on "click", ".assign-to-me", (event) ->
event.preventDefault()
return if not isEditable()
$model.$modelValue.assigned_to = $currentUserService.getUser().get('id')
renderUser($model.$modelValue)
$scope.$apply()
$el.on "click", ".remove-user", (event) ->
event.preventDefault()
return if not isEditable()
$model.$modelValue.assigned_to = null
renderUser()
$scope.$apply()
$scope.$watch "usersSearch", (searchingText) ->
if searchingText?
renderUserlist(searchingText)
$el.find('input').focus()
$el.on "click", ".user-list-single", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
$model.$modelValue.assigned_to = target.data("user-id")
renderUser($model.$modelValue)
$scope.$apply()
$scope.$watch $attrs.ngModel, (instance) ->
renderUser(instance)
$scope.$on "isiocaine:changed", (ctx, instance) ->
renderUser(instance)
$scope.$on "$destroy", ->
$el.off()
return {
link:link,
require:"ngModel",
templateUrl: "common/components/assigned-to-inline.html"
}
module.directive("tgAssignedToInline", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading"
"$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile","tgCurrentUserService"
"tgAvatarService", AssignedToInlineDirective])
#############################################################################
## Assigned users (inline) directive
#############################################################################
AssignedUsersInlineDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template
$translate, $compile, $currentUserService, avatarService) ->
link = ($scope, $el, $attrs, $model) ->
currentAssignedIds = []
currentAssignedTo = null
isAssigned = ->
return currentAssignedIds.length > 0
normalizeString = (string) ->
normalizedString = string
normalizedString = normalizedString.replace("Á", "A").replace("Ä", "A").replace("À", "A")
normalizedString = normalizedString.replace("É", "E").replace("Ë", "E").replace("È", "E")
normalizedString = normalizedString.replace("Í", "I").replace("Ï", "I").replace("Ì", "I")
normalizedString = normalizedString.replace("Ó", "O").replace("Ö", "O").replace("Ò", "O")
normalizedString = normalizedString.replace("Ú", "U").replace("Ü", "U").replace("Ù", "U")
return normalizedString
filterUsers = (text, user) ->
username = user.full_name_display.toUpperCase()
username = normalizeString(username)
text = text.toUpperCase()
text = normalizeString(text)
return _.includes(username, text)
renderUsersList = (text) ->
users = _.clone($scope.activeUsers, true)
users = _.sortBy(users, (o) -> if o.id is $scope.user.id then 0 else o.id)
users = _.filter(users, _.partial(filterUsers, text)) if text?
# Add selected users
selected = []
_.map users, (user) ->
if user.id in currentAssignedIds
user.avatar = avatarService.getAvatar(user)
selected.push(user)
# Filter users in searchs
visible = []
_.map users, (user) ->
if user.id not in currentAssignedIds
user.avatar = avatarService.getAvatar(user)
visible.push(user)
$scope.selected = _.slice(selected, 0, 5)
if $scope.selected.length < 5
$scope.users = _.slice(visible, 0, 5 - $scope.selected.length)
else
$scope.users = []
$scope.showMore = users.length > 5
renderUsers = () ->
assignedUsers = _.map(currentAssignedIds, (assignedUserId) -> $scope.usersById[assignedUserId])
assignedUsers = _.filter assignedUsers, (it) -> return !!it
$scope.hiddenUsers = if currentAssignedIds.length > 3 then currentAssignedIds.length - 3 else 0
$scope.assignedUsers = _.slice(assignedUsers, 0, 3)
$scope.isAssigned = isAssigned()
applyToModel = () ->
_.map currentAssignedIds, (userId) ->
if !$scope.usersById[userId]
currentAssignedIds.splice(currentAssignedIds.indexOf(userId), 1)
if currentAssignedIds.length == 0
currentAssignedTo = null
else if currentAssignedIds.indexOf(currentAssignedTo) == -1 || !currentAssignedTo
currentAssignedTo = currentAssignedIds[0]
$model.$modelValue.setAttr('assigned_users', currentAssignedIds)
$model.$modelValue.assigned_to = currentAssignedTo
$el.on "click", ".users-dropdown", (event) ->
event.preventDefault()
event.stopPropagation()
renderUsersList()
$scope.$apply()
$el.find(".pop-users").popover().open()
$el.on "click", ".users-search", (event) ->
event.stopPropagation()
$el.on "click", ".assign-to-me", (event) ->
event.preventDefault()
currentAssignedIds.push($currentUserService.getUser().get('id'))
renderUsers()
applyToModel()
$scope.usersSearch = null
$scope.$apply()
$scope.$watch "usersSearch", (searchingText) ->
if searchingText?
renderUsersList(searchingText)
$el.find('input').focus()
$el.on "click", ".user-list-single", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
index = currentAssignedIds.indexOf(target.data("user-id"))
if index == -1
currentAssignedIds.push(target.data("user-id"))
else
currentAssignedIds.splice(index, 1)
renderUsers()
applyToModel()
$el.find(".pop-users").popover().close()
$scope.usersSearch = null
$scope.$apply()
$el.on "click", ".remove-user", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
index = currentAssignedIds.indexOf(target.data("user-id"))
if index > -1
currentAssignedIds.splice(index, 1)
renderUsers()
applyToModel()
$scope.$apply()
$scope.$watch $attrs.ngModel, (item) ->
return if not item?
currentAssignedIds = []
assigned_to = null
if item.assigned_users?
currentAssignedIds = item.assigned_users
assigned_to = item.assigned_to
renderUsers()
$scope.$on "$destroy", ->
$el.off()
return {
link:link,
require: "ngModel",
templateUrl: "common/components/assigned-users-inline.html"
}
module.directive("tgAssignedUsersInline", ["$rootScope", "$tgConfirm", "$tgRepo",
"$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile",
"tgCurrentUserService", "tgAvatarService", AssignedUsersInlineDirective])
#############################################################################
## Block Button directive
#############################################################################

View File

@ -294,237 +294,6 @@ BlockingMessageInputDirective = ($log, $template, $compile) ->
module.directive("tgBlockingMessageInput", ["$log", "$tgTemplate", "$compile", BlockingMessageInputDirective])
#############################################################################
## Create/Edit Userstory Lightbox Directive
#############################################################################
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading, $translate, $confirm, $q, attachmentsService) ->
link = ($scope, $el, attrs) ->
form = null
$scope.createEditUs = {}
$scope.isNew = true
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
resetAttachments = () ->
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
$scope.addAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.push(attachment)
$scope.deleteAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.filter (it) ->
return it.get('name') != attachment.get('name')
if attachment.get("id")
attachmentsToDelete = attachmentsToDelete.push(attachment)
$scope.addTag = (tag, color) ->
value = trim(tag.toLowerCase())
tags = $scope.project.tags
projectTags = $scope.project.tags_colors
tags = [] if not tags?
projectTags = {} if not projectTags?
if value not in tags
tags.push(value)
projectTags[tag] = color || null
$scope.project.tags = tags
itemtags = _.clone($scope.us.tags)
inserted = _.find itemtags, (it) -> it[0] == value
if !inserted
itemtags.push([value , color])
$scope.us.tags = itemtags
$scope.deleteTag = (tag) ->
value = trim(tag[0].toLowerCase())
tags = $scope.project.tags
itemtags = _.clone($scope.us.tags)
_.remove itemtags, (tag) -> tag[0] == value
$scope.us.tags = itemtags
_.pull($scope.us.tags, value)
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
form.reset() if form
$scope.isNew = true
$scope.usStatusList = statusList
$scope.attachments = Immutable.List()
resetAttachments()
$scope.us = $model.make_model("userstories", {
project: projectId
points : {}
status: status
is_archived: false
tags: []
subject: ""
description: ""
})
# Update texts for creation
$el.find(".button-green").html($translate.instant("COMMON.CREATE"))
$el.find(".title").html($translate.instant("LIGHTBOX.CREATE_EDIT_US.NEW_US"))
$el.find(".tag-input").val("")
$el.find(".blocked-note").addClass("hidden")
$el.find("label.blocked").removeClass("selected")
$el.find("label.team-requirement").removeClass("selected")
$el.find("label.client-requirement").removeClass("selected")
$scope.createEditUsOpen = true
lightboxService.open $el, () ->
$scope.createEditUsOpen = false
$scope.$on "usform:edit", (ctx, us, attachments) ->
form.reset() if form
$scope.us = us
$scope.attachments = Immutable.fromJS(attachments)
$scope.isNew = false
resetAttachments()
# Update texts for edition
$el.find(".button-green").html($translate.instant("COMMON.SAVE"))
$el.find(".title").html($translate.instant("LIGHTBOX.CREATE_EDIT_US.EDIT_US"))
$el.find(".tag-input").val("")
# Update requirement info (team, client or blocked)
if us.is_blocked
$el.find(".blocked-note").removeClass("hidden")
$el.find("label.blocked").addClass("selected")
else
$el.find(".blocked-note").addClass("hidden")
$el.find("label.blocked").removeClass("selected")
if us.team_requirement
$el.find("label.team-requirement").addClass("selected")
else
$el.find("label.team-requirement").removeClass("selected")
if us.client_requirement
$el.find("label.client-requirement").addClass("selected")
else
$el.find("label.client-requirement").removeClass("selected")
$scope.createEditUsOpen = true
lightboxService.open $el, () ->
$scope.createEditUsOpen = false
createAttachments = (obj) ->
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
attachmentsService.upload(attachment.file, obj.id, $scope.us.project, 'us')
return $q.all(promises)
deleteAttachments = (obj) ->
promises = _.map attachmentsToDelete.toJS(), (attachment) ->
return attachmentsService.delete("us", attachment.id)
return $q.all(promises)
submit = debounce 2000, (event) =>
event.preventDefault()
form = $el.find("form").checksley()
if not form.validate()
return
currentLoading = $loading()
.target(submitButton)
.start()
params = {
include_attachments: true,
include_tasks: true
}
if $scope.isNew
promise = $repo.create("userstories", $scope.us)
broadcastEvent = "usform:new:success"
else
promise = $repo.save($scope.us, true)
broadcastEvent = "usform:edit:success"
promise.then (data) ->
deleteAttachments(data)
.then () => createAttachments(data)
.then () =>
currentLoading.finish()
lightboxService.close($el)
$rs.userstories.getByRef(data.project, data.ref, params).then (us) ->
$rootScope.$broadcast(broadcastEvent, us)
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 = () =>
if !$scope.us.isModified()
lightboxService.close($el)
$scope.$apply ->
$scope.us.revert()
else
$confirm.ask($translate.instant("LIGHTBOX.CREATE_EDIT_US.CONFIRM_CLOSE")).then (result) ->
lightboxService.close($el)
$scope.us.revert()
result.finish()
$el.on "submit", "form", submit
$el.find('.close').on "click", (event) ->
event.preventDefault()
event.stopPropagation()
close()
$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()
return {link: link}
module.directive("tgLbCreateEditUserstory", [
"$tgRepo",
"$tgModel",
"$tgResources",
"$rootScope",
"lightboxService",
"$tgLoading",
"$translate",
"$tgConfirm",
"$q",
"tgAttachmentsService"
CreateEditUserstoryDirective
])
#############################################################################
## Creare Bulk Userstories Lightbox Directive
#############################################################################
@ -938,6 +707,17 @@ SetDueDateDirective = (lightboxService, $loading, $translate, $confirm, $modelTr
.target($el.find(".submit-button"))
.start()
if $scope.notAutoSave
new_due_date = $('.due-date').val()
$scope.object.due_date = if (new_due_date) \
then moment(new_due_date, prettyDate).format("YYYY-MM-DD") \
else null
$scope.$apply()
currentLoading.finish()
lightboxService.close($el)
return
transform = $modelTransform.save (object) ->
new_due_date = $('.due-date').val()
object.due_date = if (new_due_date) \
@ -968,7 +748,11 @@ SetDueDateDirective = (lightboxService, $loading, $translate, $confirm, $modelTr
askResponse.finish()
$('.due-date').val(null)
$scope.object.due_date_reason = null
save()
if $scope.notAutoSave
$scope.object.due_date = null
lightboxService.close($el)
else
save()
$el.on "click", ".delete-due-date", (event) ->
event.preventDefault()
@ -982,3 +766,328 @@ SetDueDateDirective = (lightboxService, $loading, $translate, $confirm, $modelTr
module.directive("tgLbSetDueDate", ["lightboxService", "$tgLoading", "$translate", "$tgConfirm"
"$tgQueueModelTransformation", SetDueDateDirective])
#############################################################################
## Create/Edit Lightbox Directive
#############################################################################
CreateEditDirective = (
$log, $repo, $model, $rs, $rootScope, lightboxService, $loading, $translate,
$confirm, $q, attachmentsService) ->
link = ($scope, $el, attrs) ->
form = null
schemas = {
us: {
objName: 'User Story',
model: 'userstories',
params: { include_attachments: true, include_tasks: true },
requiredAttrs: ['project'],
initialData: (data) ->
return {
project: data.project.id
points : {}
status: data.project.default_us_status
is_archived: false
tags: []
subject: ""
description: ""
}
}
task: {
objName: 'Task',
model: 'tasks',
params: { include_attachments: true },
requiredAttrs: ['project', 'sprintId', 'usId'],
initialData: (data) ->
return {
project: data.project.id
milestone: data.sprintId
user_story: data.usId
is_archived: false
status: data.project.default_task_status
assigned_to: null
tags: [],
subject: "",
description: "",
}
},
issue: {
objName: 'Issue',
model: 'issues',
params: { include_attachments: true },
requiredAttrs: ['project', 'typeList', 'typeById', 'severityList', 'priorityList'],
initialData: (data) ->
return {
project: data.project.id
subject: ""
status: data.project.default_issue_status
type: data.project.default_issue_type
priority: data.project.default_priority
severity: data.project.default_severity
tags: []
}
}
}
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
$scope.$on "genericform:new", (ctx, data) ->
if beforeMount('new', data, ['objType', 'statusList', ])
mountCreateForm(data)
afterMount()
$scope.$on "genericform:edit", (ctx, data) ->
if beforeMount('edit', data, ['objType', 'statusList', 'obj', 'attachments'])
mountUpdateForm(data)
afterMount()
beforeMount = (mode, data, requiredAttrs) ->
form.reset() if form
$el.find(".tag-input").val("")
# Get form schema
if !data.objType || !schemas[data.objType]
return $log.error(
"Invalid objType `#{data.objType}` for `genericform:#{mode}` event")
$scope.schema = schemas[data.objType]
# Get required attrs for creation from objType schema
if mode == 'new'
requiredAttrs = $scope.schema.requiredAttrs.concat(requiredAttrs)
# Check if required attrs for creating are present
getAttrs(mode, data, requiredAttrs)
return true
mountCreateForm = (data) ->
$scope.obj = $model.make_model($scope.schema.model, $scope.schema.initialData(data))
$scope.isNew = true
$scope.attachments = Immutable.List()
# Update texts for creation
$el.find(".button-green").html($translate.instant("COMMON.CREATE"))
$el.find(".title").html($translate.instant(
"LIGHTBOX.CREATE_EDIT.NEW", { objName: $scope.schema.objName }))
$el.find(".blocked-note").addClass("hidden")
mountUpdateForm = (data) ->
$scope.isNew = false
$scope.attachments = Immutable.fromJS($scope.attachments)
# Update texts for edition
$el.find(".button-green").html($translate.instant("COMMON.SAVE"))
$el.find(".title").html($translate.instant(
"LIGHTBOX.CREATE_EDIT.EDIT", { objName: $scope.schema.objName }))
afterMount = () ->
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]
resetAttachments = () ->
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
$scope.addAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.push(attachment)
$scope.deleteAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.filter (it) ->
return it.get('name') != attachment.get('name')
if attachment.get("id")
attachmentsToDelete = attachmentsToDelete.push(attachment)
$scope.addTag = (tag, color) ->
value = trim(tag.toLowerCase())
tags = $scope.project.tags
projectTags = $scope.project.tags_colors
tags = [] if not tags?
projectTags = {} if not projectTags?
if value not in tags
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
itemtags.push([value , color])
$scope.obj.tags = itemtags
$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)
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()
form = $el.find("form").checksley()
if not form.validate()
return
currentLoading = $loading().target(submitButton).start()
if $scope.isNew
promise = $repo.create($scope.schema.model, $scope.obj)
broadcastEvent = "#{$scope.objType}form:new:success"
else
promise = $repo.save($scope.obj, true)
broadcastEvent = "#{$scope.objType}form:edit:success"
promise.then (data) ->
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) ->
$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 = () ->
if !$scope.obj.isModified()
lightboxService.close($el)
$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()
$el.on "submit", "form", submit
$el.find('.close').on "click", (event) ->
event.preventDefault()
event.stopPropagation()
close()
$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)
$el.on "click", ".status-dropdown", (event) ->
event.preventDefault()
event.stopPropagation()
$el.find(".pop-status").popover().open()
$el.on "click", ".status", (event) ->
event.preventDefault()
event.stopPropagation()
setStatus(angular.element(event.currentTarget).data("status-id"))
$scope.$apply()
$scope.$broadcast("status:changed", $scope.obj.status)
$el.find(".pop-status").popover().close()
$el.on "click", ".users-dropdown", (event) ->
event.preventDefault()
event.stopPropagation()
$el.find(".pop-users").popover().open()
$el.on "click", ".team-requirement", (event) ->
$scope.obj.team_requirement = not $scope.obj.team_requirement
$scope.$apply()
$el.on "click", ".client-requirement", (event) ->
$scope.obj.client_requirement = not $scope.obj.client_requirement
$scope.$apply()
$el.on "click", ".is-blocked", (event) ->
$scope.obj.is_blocked = not $scope.obj.is_blocked
$scope.$apply()
$el.on "click", ".iocaine", (event) ->
$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
return {
link: link
}
module.directive("tgLbCreateEdit", [
"$log",
"$tgRepo",
"$tgModel",
"$tgResources",
"$rootScope",
"lightboxService",
"$tgLoading",
"$translate",
"$tgConfirm",
"$q",
"tgAttachmentsService"
CreateEditDirective
])

View File

@ -355,10 +355,12 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransfo
template = $template.get("issue/issue-type-button.html", true)
link = ($scope, $el, $attrs, $model) ->
notAutoSave = $scope.$eval($attrs.notAutoSave)
isEditable = ->
return $scope.project.my_permissions.indexOf("modify_issue") != -1
render = (issue) =>
render = (issue) ->
type = $scope.typeById[issue.type]
html = template({
@ -374,6 +376,11 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransfo
save = (type) ->
$.fn.popover().closeAll()
if notAutoSave
$model.$modelValue.type = type
$scope.$apply()
return
currentLoading = $loading()
.target($el.find(".level-name"))
.start()
@ -445,10 +452,12 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTra
template = $template.get("issue/issue-severity-button.html", true)
link = ($scope, $el, $attrs, $model) ->
notAutoSave = $scope.$eval($attrs.notAutoSave)
isEditable = ->
return $scope.project.my_permissions.indexOf("modify_issue") != -1
render = (issue) =>
render = (issue) ->
severity = $scope.severityById[issue.severity]
html = template({
@ -464,6 +473,11 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTra
save = (severity) ->
$.fn.popover().closeAll()
if notAutoSave
$model.$modelValue.severity = severity
$scope.$apply()
return
currentLoading = $loading()
.target($el.find(".level-name"))
.start()
@ -536,10 +550,12 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTra
template = $template.get("issue/issue-priority-button.html", true)
link = ($scope, $el, $attrs, $model) ->
notAutoSave = $scope.$eval($attrs.notAutoSave)
isEditable = ->
return $scope.project.my_permissions.indexOf("modify_issue") != -1
render = (issue) =>
render = (issue) ->
priority = $scope.priorityById[issue.priority]
html = template({
@ -555,6 +571,11 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTra
save = (priority) ->
$.fn.popover().closeAll()
if notAutoSave
$model.$modelValue.priority = priority
$scope.$apply()
return
currentLoading = $loading()
.target($el.find(".level-name"))
.start()

View File

@ -29,131 +29,6 @@ trim = @.taiga.trim
module = angular.module("taigaIssues")
#############################################################################
## Issue Create Lightbox Directive
#############################################################################
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, $q, attachmentsService) ->
link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley()
$scope.issue = {}
$scope.attachments = Immutable.List()
$scope.$on "issueform:new", (ctx, project)->
form.reset()
resetAttachments()
$el.find(".tag-input").val("")
lightboxService.open $el, () ->
$scope.createIssueOpen = false
$scope.issue = {
project: project.id
subject: ""
status: project.default_issue_status
type: project.default_issue_type
priority: project.default_priority
severity: project.default_severity
tags: []
}
$scope.createIssueOpen = true
$scope.$on "$destroy", ->
$el.off()
createAttachments = (obj) ->
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
return attachmentsService.upload(attachment.file, obj.id, $scope.issue.project, 'issue')
return $q.all(promises)
attachmentsToAdd = Immutable.List()
resetAttachments = () ->
attachmentsToAdd = Immutable.List()
$scope.attachments = Immutable.List()
$scope.addAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.push(attachment)
$scope.deleteAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.filter (it) ->
return it.get('name') != attachment.get('name')
$scope.addTag = (tag, color) ->
value = trim(tag.toLowerCase())
tags = $scope.project.tags
projectTags = $scope.project.tags_colors
tags = [] if not tags?
projectTags = {} if not projectTags?
if value not in tags
tags.push(value)
projectTags[tag] = color || null
$scope.project.tags = tags
itemtags = _.clone($scope.issue.tags)
inserted = _.find itemtags, (it) -> it[0] == value
if !inserted
itemtags.push([tag , color])
$scope.issue.tags = itemtags
$scope.deleteTag = (tag) ->
value = trim(tag[0].toLowerCase())
tags = $scope.project.tags
itemtags = _.clone($scope.issue.tags)
_.remove itemtags, (tag) -> tag[0] == value
$scope.issue.tags = itemtags
_.pull($scope.issue.tags, value)
submit = debounce 2000, (event) =>
event.preventDefault()
if not form.validate()
return
currentLoading = $loading()
.target(submitButton)
.start()
promise = $repo.create("issues", $scope.issue)
promise.then (data) ->
return createAttachments(data)
promise.then (data) ->
currentLoading.finish()
$rootscope.$broadcast("issueform:new:success", data)
lightboxService.close($el)
$confirm.notify("success")
promise.then null, ->
currentLoading.finish()
$confirm.notify("error")
submitButton = $el.find(".submit-button")
$el.on "submit", "form", submit
return {link:link}
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading",
"$q", "tgAttachmentsService", CreateIssueDirective])
#############################################################################
## Issue Bulk Create Lightbox Directive
#############################################################################

View File

@ -367,7 +367,16 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
# Functions used from templates
addNewIssue: ->
@rootscope.$broadcast("issueform:new", @scope.project)
project = @projectService.project.toJS()
@rootscope.$broadcast("genericform:new", {
'objType': 'issue',
'statusList': @scope.issueStatusList,
'project': project,
'severityList': @scope.severityList,
'priorityList': @scope.priorityList,
'typeById': groupBy(project.issue_types, (x) -> x.id),
'typeList': _.sortBy(project.issue_types, "order")
})
addIssuesInBulk: ->
@rootscope.$broadcast("issueform:bulk", @scope.projectId)

View File

@ -164,8 +164,12 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
addNewUs: (type, statusId) ->
switch type
when "standard" then @rootscope.$broadcast("usform:new",
@scope.projectId, statusId, @scope.usStatusList)
when "standard" then @rootscope.$broadcast("genericform:new",
{
'objType': 'us',
'project': @scope.project,
'statusList': @scope.usStatusList
})
when "bulk" then @rootscope.$broadcast("usform:bulk",
@scope.projectId, statusId)
@ -175,9 +179,15 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@kanbanUserstoriesService.replace(us)
@rs.userstories.getByRef(us.getIn(['model', 'project']), us.getIn(['model', 'ref']))
.then (editingUserStory) =>
@rs2.attachments.list("us", us.get('id'), us.getIn(['model', 'project'])).then (attachments) =>
@rootscope.$broadcast("usform:edit", editingUserStory, attachments.toJS())
.then (editingUserStory) =>
@rs2.attachments.list(
"us", us.get('id'), us.getIn(['model', 'project'])).then (attachments) =>
@rootscope.$broadcast("genericform:edit", {
'objType': 'us',
'obj': editingUserStory,
'statusList': @scope.usStatusList,
'attachments': attachments.toJS()
})
us = us.set('loading-edit', false)
@kanbanUserstoriesService.replace(us)

View File

@ -27,196 +27,6 @@ bindOnce = @.taiga.bindOnce
debounce = @.taiga.debounce
trim = @.taiga.trim
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, $confirm, attachmentsService) ->
link = ($scope, $el, attrs) ->
$scope.isNew = true
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
resetAttachments = () ->
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
$scope.addAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.push(attachment)
$scope.deleteAttachment = (attachment) ->
attachmentsToAdd = attachmentsToAdd.filter (it) ->
return it.get('name') != attachment.get('name')
if attachment.get("id")
attachmentsToDelete = attachmentsToDelete.push(attachment)
createAttachments = (obj) ->
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
attachmentsService.upload(attachment.file, obj.id, $scope.task.project, 'task')
return $q.all(promises)
deleteAttachments = (obj) ->
promises = _.map attachmentsToDelete.toJS(), (attachment) ->
return attachmentsService.delete("task", attachment.id)
return $q.all(promises)
tagsToAdd = []
$scope.addTag = (tag, color) ->
value = trim(tag.toLowerCase())
tags = $scope.project.tags
projectTags = $scope.project.tags_colors
tags = [] if not tags?
projectTags = {} if not projectTags?
if value not in tags
tags.push(value)
projectTags[tag] = color || null
$scope.project.tags = tags
itemtags = _.clone($scope.task.tags)
inserted = _.find itemtags, (it) -> it[0] == value
if !inserted
itemtags.push([tag , color])
$scope.task.tags = itemtags
$scope.deleteTag = (tag) ->
value = trim(tag[0].toLowerCase())
tags = $scope.project.tags
itemtags = _.clone($scope.task.tags)
_.remove itemtags, (tag) -> tag[0] == value
$scope.task.tags = itemtags
_.pull($scope.task.tags, value)
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
$scope.task = $model.make_model('tasks', {
project: $scope.projectId
milestone: sprintId
user_story: usId
is_archived: false
status: $scope.project.default_task_status
assigned_to: null
tags: [],
subject: "",
description: "",
})
$scope.isNew = true
$scope.attachments = Immutable.List()
resetAttachments()
# Update texts for creation
create = $translate.instant("COMMON.CREATE")
$el.find(".button-green").html(create)
newTask = $translate.instant("LIGHTBOX.CREATE_EDIT_TASK.TITLE")
$el.find(".title").html(newTask + " ")
$el.find(".tag-input").val("")
lightboxService.open $el, () ->
$scope.createEditTaskOpen = false
$scope.createEditTaskOpen = true
$scope.$on "taskform:edit", (ctx, task, attachments) ->
$scope.task = task
$scope.isNew = false
$scope.attachments = Immutable.fromJS(attachments)
resetAttachments()
# Update texts for edition
save = $translate.instant("COMMON.SAVE")
edit = $translate.instant("LIGHTBOX.CREATE_EDIT_TASK.ACTION_EDIT")
$el.find(".button-green").html(save)
$el.find(".title").html(edit + " ")
$el.find(".tag-input").val("")
lightboxService.open $el, () ->
$scope.createEditTaskOpen = false
$scope.createEditTaskOpen = true
submitButton = $el.find(".submit-button")
submit = debounce 2000, (event) =>
event.preventDefault()
form = $el.find("form").checksley()
if not form.validate()
return
params = {
include_attachments: true,
include_tasks: true
}
if $scope.isNew
promise = $repo.create("tasks", $scope.task)
broadcastEvent = "taskform:new:success"
else
promise = $repo.save($scope.task)
broadcastEvent = "taskform:edit:success"
promise.then (data) ->
deleteAttachments(data)
.then () => createAttachments(data)
.then () =>
$rs.tasks.getByRef(data.project, data.ref, params).then (task) ->
$rootscope.$broadcast(broadcastEvent, task)
currentLoading = $loading()
.target(submitButton)
.start()
promise.then (data) ->
currentLoading.finish()
lightboxService.close($el)
$el.on "submit", "form", submit
close = () =>
if !$scope.task.isModified()
lightboxService.close($el)
$scope.$apply ->
$scope.task.revert()
else
$confirm.ask($translate.instant("LIGHTBOX.CREATE_EDIT_TASK.CONFIRM_CLOSE")).then (result) ->
lightboxService.close($el)
$scope.task.revert()
result.finish()
$el.find('.close').on "click", (event) ->
event.preventDefault()
event.stopPropagation()
close()
$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()
return {link: link}
CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService, $model) ->
link = ($scope, $el, attrs) ->
@ -248,7 +58,6 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService, $
# TODO: error handling
promise.then null, ->
currentLoading.finish()
console.log "FAIL"
$scope.$on "taskform:bulk", (ctx, sprintId, usId)->
lightboxService.open($el)
@ -266,20 +75,6 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService, $
module = angular.module("taigaTaskboard")
module.directive("tgLbCreateEditTask", [
"$tgRepo",
"$tgModel",
"$tgResources",
"$rootScope",
"$tgLoading",
"lightboxService",
"$translate",
"$q",
"$tgConfirm",
"tgAttachmentsService",
CreateEditTaskDirective
])
module.directive("tgLbCreateBulkTasks", [
"$tgRepo",
"$tgResources",

View File

@ -439,9 +439,17 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
task = task.set('loading-edit', true)
@taskboardTasksService.replace(task)
@rs.tasks.getByRef(task.getIn(['model', 'project']), task.getIn(['model', 'ref'])).then (editingTask) =>
@rs2.attachments.list("task", task.get('id'), task.getIn(['model', 'project'])).then (attachments) =>
@rootscope.$broadcast("taskform:edit", editingTask, attachments.toJS())
@rs.tasks.getByRef(task.getIn(['model', 'project']), task.getIn(['model', 'ref']))
.then (editingTask) =>
@rs2.attachments.list("task", task.get('id'), task.getIn(['model', 'project']))
.then (attachments) =>
@rootscope.$broadcast("genericform:edit", {
'objType': 'task',
'obj': editingTask,
'statusList': @scope.taskStatusList,
'attachments': attachments.toJS()
})
task = task.set('loading', false)
@taskboardTasksService.replace(task)
@ -496,7 +504,15 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
## Template actions
addNewTask: (type, us) ->
switch type
when "standard" then @rootscope.$broadcast("taskform:new", @scope.sprintId, us?.id)
when "standard" then @rootscope.$broadcast("genericform:new",
{
'objType': 'task',
'project': @scope.project,
'sprintId': @scope.sprintId,
'usId': us?.id,
'status': @scope.project.default_task_status,
'statusList': @scope.taskStatusList
})
when "bulk" then @rootscope.$broadcast("taskform:bulk", @scope.sprintId, us?.id)
toggleFold: (id) ->

View File

@ -150,7 +150,9 @@
"VOTES": "Votes",
"SPRINT": "Sprint",
"DUE_DATE": "Due date",
"DUE_DATE_REASON": "Due date reason"
"DUE_DATE_REASON": "Due date reason",
"CLIENT_REQUIREMENT": "Client requirement",
"TEAM_REQUIREMENT": "Team requirement"
},
"ROLES": {
"ALL": "All"
@ -1091,6 +1093,12 @@
"EDIT_US": "Edit user story",
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?"
},
"CREATE_EDIT": {
"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?"
},
"DELETE_DUE_DATE": {
"TITLE": "Delete due date",
"SUBTITLE": "Are you sure you want to delete this due date?"

View File

@ -12,7 +12,6 @@
.card-statistics
tg-due-date.statistic.card-due-date(
due-date="vm.item.getIn(['model', 'due_date'])"
due-date-status="vm.item.getIn(['model', 'due_date_status'])"
is-closed="vm.item.getIn(['model', 'is_closed'])"
)
.statistic.card-iocaine(

View File

@ -21,9 +21,8 @@
tg-svg.detail-edit.e2e-detail-edit(svg-icon="icon-edit")
tg-due-date(
due-date="vm.item.due_date"
due-date-status="vm.item.due_date_status"
ng-if="vm.item.due_date"
is-closed="vm.item.is_closed"
ng-if="vm.item.due_date"
)
.edit-title-wrapper(ng-if="vm.editMode")
input.edit-title-input.e2e-title-input(

View File

@ -21,9 +21,10 @@ class DueDateController
@.$inject = [
"$translate"
"tgLightboxFactory"
"tgProjectService"
]
constructor: (@translate, @tgLightboxFactory) ->
constructor: (@translate, @tgLightboxFactory, @projectService) ->
visible: () ->
return @.format == 'button' or @.dueDate?
@ -37,17 +38,35 @@ class DueDateController
'due_soon': 'due-soon',
'past_due': 'past-due',
'set': 'due-set',
'not_set': 'not-set',
}
return colors[@.dueDateStatus] or ''
return colors[@status()] or ''
title: () ->
if @.format == 'button'
return if @.dueDate then @._formatTitle() else 'Edit due date'
if @.dueDate
return @._formatTitle()
else if @.format == 'button'
return @translate.instant('COMMON.DUE_DATE.TITLE_ACTION_SET_DUE_DATE')
return ''
return @._formatTitle()
status: () ->
if !@.dueDate
return 'not_set'
project = @projectService.project.toJS()
project.due_soon_threshold = 14 # TODO get value from taiga-back
dueDate = moment(@.dueDate)
now = moment()
if @.isClosed
return 'no_longer_applicable'
else if now > dueDate
return 'past_due'
else if now.add(moment.duration(project.due_soon_threshold, "days")) >= dueDate
return 'due_soon'
return 'set'
_formatTitle: () ->
dueDateStatus = 'closed'
titles = {
'no_longer_applicable': 'COMMON.DUE_DATE.NO_LONGER_APPLICABLE',
'due_soon': 'COMMON.DUE_DATE.DUE_SOON',
@ -56,16 +75,17 @@ class DueDateController
prettyDate = @translate.instant("COMMON.PICKERDATE.FORMAT")
formatedDate = moment(@.dueDate).format(prettyDate)
if not titles[@.dueDateStatus]
status = @status()
if not titles[status]
return formatedDate
return "#{formatedDate} (#{@translate.instant(titles[@.dueDateStatus])})"
return "#{formatedDate} (#{@translate.instant(titles[status])})"
setDueDate: () ->
return if @.disabled()
@tgLightboxFactory.create(
"tg-lb-set-due-date",
{"class": "lightbox lightbox-set-due-date"},
{"object": @.item}
{"object": @.item, "notAutoSave": @.notAutoSave}
)
angular.module('taigaComponents').controller('DueDate', DueDateController)
angular.module('taigaComponents').controller('DueDateCtrl', DueDateController)

View File

@ -0,0 +1,69 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: due-date.directive.coffee
###
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]
container: el.find('.date-picker-container')[0]
bound: false
onSelect: () ->
ctrl.dueDate = this.getMoment().format('YYYY-MM-DD')
el.find(".date-picker-popover").popover().close()
scope.$apply()
})
el.picker = new Pikaday(datePickerConfig)
controller: "DueDateCtrl",
controllerAs: "vm",
bindToController: true,
templateUrl: "components/due-date/due-date-popover.html",
scope: {
dueDate: '=',
isClosed: '=',
item: '=',
format: '@',
notAutoSave: '='
}
}
module.directive('tgDueDatePopover', ['$translate', 'tgDatePickerConfigService', dueDatePopoverDirective])

View File

@ -0,0 +1,16 @@
.due-date-button-wrapper
label.due-date-button.button-gray.is-editable.date-picker-popover-trigger(
ng-disabled="vm.disabled()"
ng-class="vm.color()"
ng-attr-title="{{ vm.title() }}"
)
tg-svg(svg-icon="icon-clock")
input.due-date.no-focus(
type="hidden"
picker-value="{{ vm.dueDate }}"
)
div.date-picker-popover
div.date-picker-container
div.date-picker-popover-footer(ng-if="vm.dueDate")
a.date-picker-clean(href="", title="{{'LIGHTBOX.SET_DUE_DATE.TITLE_ACTION_DELETE_DUE_DATE' | translate}}")
tg-svg(svg-icon="icon-trash")

View File

@ -19,25 +19,60 @@
module = angular.module("taigaComponents")
dueDateDirective = () ->
dueDateDirective = ($translate, datePickerConfigService) ->
templateUrl = (el, attrs) ->
if attrs.format
return "components/due-date/due-date-" + attrs.format + ".html"
return "components/due-date/due-date-icon.html"
return {
link: (scope) ->
controller: "DueDate",
link: (scope, el, attrs, ctrl) ->
renderDatePicker = () ->
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]
container: el.find('.date-picker-container')[0]
bound: false
onSelect: () ->
ctrl.dueDate = this.getMoment().format('YYYY-MM-DD')
el.find(".date-picker-popover").popover().close()
scope.$apply()
})
el.picker = new Pikaday(datePickerConfig)
if attrs.format == 'button-popover'
renderDatePicker()
controller: "DueDateCtrl",
controllerAs: "vm",
bindToController: true,
templateUrl: templateUrl,
scope: {
dueDate: '=',
dueDateStatus: '=',
isClosed: '=',
item: '=',
format: '@'
format: '@',
notAutoSave: '='
}
}
module.directive('tgDueDate', dueDateDirective)
module.directive('tgDueDate', ['$translate', 'tgDatePickerConfigService', dueDateDirective])

View File

@ -6,17 +6,25 @@
padding: 1rem;
transition: background .2s linear;
transition-delay: .1s;
&.closed {
&.closed,
&.text-button.closed:hover {
background: $gray-lighter;
border-color: $gray-lighter;
}
&.due-set {
&.due-set,
&.text-button.due-set:hover {
background: $yellow-green;
border-color: $yellow-green;
}
&.due-soon {
&.due-soon,
&.text-button.due-soon:hover {
background: $my-sin;
border-color: $my-sin;
}
&.past-due {
&.past-due,
&.text-button.past-due:hover {
background: $red-light;
border-color: $red-light;
}
&:hover {
background: $gray;
@ -24,6 +32,17 @@
&.editable {
cursor: pointer;
}
&.text-button {
color: $white;
margin: 0;
padding: .5rem;
&.not-set:hover {
color: $white;
}
&.not-set {
color: $gray;
}
}
}
.due-date-icon {
@ -66,3 +85,39 @@
width: .9rem;
}
}
.due-date-button-wrapper {
display: flex;
position: relative;
}
.date-picker-container {
overflow: visible;
}
.date-picker-popover {
background: $white;
border: 1px solid $gray;
display: none;
left: 0;
overflow: visible;
position: absolute;
top: 56px;
width: auto;
.pika-single {
border: 0;
}
}
.date-picker-popover-footer {
padding: .2rem .5rem;
text-align: right;
svg {
fill: $gray;
height: 1rem;
width: 1rem;
}
a:hover svg {
fill: $red;
}
}

View File

@ -123,8 +123,8 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
sidebar.menu-secondary.sidebar
include ../includes/modules/sprints
div.lightbox.lightbox-generic-form.lb-create-edit-userstory(tg-lb-create-edit-userstory)
include ../includes/modules/lightbox-us-create-edit
div.lightbox.lightbox-generic-form.lightbox-create-edit(tg-lb-create-edit)
include ../includes/modules/lightbox-create-edit/lb-create-edit-us
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
include ../includes/modules/lightbox-us-bulk

View File

@ -0,0 +1,65 @@
.user-avatar(ng-class!="{ 'is-iocaine': isIocaine }")
img(
style="background-color: {{ bg }}"
src="{{ avatar.url }}"
alt="{{ fullName }}"
)
.iocaine-symbol(
ng-if="isIocaine"
title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}"
)
tg-svg(svg-icon="icon-iocaine")
.assigned-to
.assigned-to-options
span.assigned-name(
ng-if="!isEditable && fullNameVisible"
) {{ fullName }}
span.users-dropdown.user-assigned(
title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}"
ng-class="{ 'editable': isEditable }"
ng-if="isEditable"
)
span.assigned-name
span(ng-if="fullNameVisible") {{ fullName }}
div.pop-users.popover
input.users-search(
type="text"
data-maxlength="500"
placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}"
ng-model="usersSearch"
)
a.user-list-single(
href=""
data-user-id="{{ user.id }}"
title="{{ user.full_name_display }}"
ng-repeat="user in users"
)
img.user-list-avatar(
style="background: {{user.avatar.bg }}"
src="{{ user.avatar.url }}"
)
span.user-list-name(
href=""
title="{{ user.full_name_display }}"
) {{ user.full_name_display }}
.show-more(ng-if="showMore")
span(translate="COMMON.ASSIGNED_TO.TOO_MANY")
span(translate="COMMON.OR", ng-if="isUnassigned")
div(ng-if="isUnassigned")
a.assign-to-me(
href="#"
title="{{'COMMON.ASSIGNED_TO.SELF' | translate}}"
)
span {{ "COMMON.ASSIGNED_TO.SELF" | translate }}
tg-svg.remove-user(
ng-if="isEditable && !isUnassigned"
svg-icon="icon-close",
title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}"
)

View File

@ -0,0 +1,117 @@
div(ng-if="isAssigned")
.user-list(ng-if="assignedUsers.length > 1")
.user-list-item(ng-repeat="assignedUser in assignedUsers")
img(
tg-avatar="assignedUser"
title="{{assignedUser.full_name_display}}"
alt="{{assignedUser.full_name_display}}"
)
.user-list-item.counter(ng-if="hiddenUsers")
span +{{ hiddenUsers }}
.ticket-assigned-to.single-assign(ng-if="assignedUsers.length == 1")
.user-avatar
img(
tg-avatar="assignedUsers[0]"
title="{{assignedUsers[0].full_name_display}}"
alt="{{assignedUsers[0].full_name_display}}"
)
.assigned-to
.assigned-users-options
span.user-assigned {{ assignedUsers[0].full_name_display }}
tg-svg.remove-user(
svg-icon="icon-close",
title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}"
data-user-id="{{ assignedUsers[0].id }}"
)
div.add-assigned
a.users-dropdown.tg-add-assigned(
href=""
ng-click="openAssignedUsers()"
)
tg-svg.add-assigned(
data-assigned-user-id="{{assignedUser.id}}",
svg-icon="icon-add",
title="{{'COMMON.ASSIGNED_USERS.ADD_ASSIGNED' | translate}}"
)
span {{ "COMMON.ASSIGNED_USERS.ADD_ASSIGNED" | translate }}
.ticket-assigned-to(ng-if="!isAssigned")
.user-avatar
img(
tg-avatar=""
alt="{{ 'COMMON.ASSIGNED_TO.ASSIGN' | translate }}"
)
.assigned-to
.assigned-users-options
a.users-dropdown.user-assigned(
href=""
title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}"
class="user-assigned"
)
span.assigned-name {{ "COMMON.ASSIGNED_TO.ASSIGN" | translate }}
| &nbsp;
span(translate="COMMON.OR")
| &nbsp;
a.assign-to-me(
href="#"
title="{{'COMMON.ASSIGNED_TO.SELF' | translate}}"
)
span {{ "COMMON.ASSIGNED_TO.SELF" | translate }}
div.pop-users.popover(ng-class="{'multiple': assignedUsers.length > 0}")
input.users-search(
type="text"
data-maxlength="500"
placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}"
ng-model="usersSearch"
)
a.user-list-single.selected(
href=""
data-user-id="{{ user.id }}"
title="{{ user.full_name_display }}"
ng-repeat="user in selected"
ng-class="{'selected': selected.indexOf(user) == -1 }"
)
img.user-list-avatar(
style="background: {{user.avatar.bg }}"
src="{{ user.avatar.url }}"
)
span.user-list-name(
href=""
title="{{ user.full_name_display }}"
) {{ user.full_name_display }}
tg-svg.remove(
href=""
data-user-id="{{ user.id }}"
ng-if="selected.indexOf(user) > -1"
svg-icon="icon-close",
title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}"
)
a.user-list-single(
href=""
data-user-id="{{ user.id }}"
title="{{ user.full_name_display }}"
ng-repeat="user in users"
)
img.user-list-avatar(
style="background: {{user.avatar.bg }}"
src="{{ user.avatar.url }}"
)
span.user-list-name(
href=""
title="{{ user.full_name_display }}"
) {{ user.full_name_display }}
tg-svg.remove(
href=""
data-user-id="{{ user.id }}"
ng-if="selected.indexOf(user) > -1"
svg-icon="icon-close",
title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}"
)
.show-more(ng-if="showMore")
span(translate="COMMON.ASSIGNED_TO.TOO_MANY")

View File

@ -40,5 +40,6 @@ form
a.delete-due-date(
href=""
title="{{'LIGHTBOX.SET_DUE_DATE.TITLE_ACTION_DELETE_DUE_DATE' | translate}}"
v-if="new_due_date"
)
tg-svg(svg-icon="icon-trash")

View File

@ -29,7 +29,7 @@
span(ng-bind-html="us.subject | emojify")
tg-due-date(
due-date="us.due_date"
due-date-status="us.due_date_status"
is-closed="us.is_closed"
ng-if="us.due_date"
)
tg-belong-to-epics(

View File

@ -56,7 +56,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}")
span(ng-bind-html="issue.subject | emojify")
tg-due-date(
due-date="issue.due_date"
due-date-status="issue.due_date_status"
is-closed="us.is_closed"
ng-if="issue.due_date"
)

View File

@ -0,0 +1,40 @@
extends lb-create-edit
block options
section.ticket-assigned-to(
tg-assigned-to-inline
ng-model="obj"
required-perm="modify_{{ objType }}"
)
div.ticket-data-container
tg-issue-type-button.ticket-status(
autosave="false"
ng-model="obj"
)
tg-issue-severity-button.ticket-status(
autosave="false"
ng-model="obj"
)
tg-issue-priority-button.ticket-status(
autosave="false"
ng-model="obj"
)
div.ticket-detail-settings
tg-due-date-popover(
due-date="obj.due_date"
is-closed="obj.is_closed"
item="obj"
not-auto-save="true"
)
div
label.button-gray.is-blocked(
title="{{ 'COMMON.BLOCK_TITLE' | translate }}"
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"
)

View File

@ -0,0 +1,40 @@
extends lb-create-edit
block options
section.ticket-assigned-to(
tg-assigned-to-inline
ng-model="obj"
required-perm="modify_{{ objType }}"
)
div.ticket-detail-settings
tg-due-date-popover(
due-date="obj.due_date"
is-closed="obj.is_closed"
item="obj"
not-auto-save="true"
)
div
fieldset.iocaine-flag(title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}")
label.button-gray.iocaine(
for="is-iocaine"
ng-class="{'active': obj.is_iocaine}"
)
tg-svg(svg-icon="icon-iocaine")
input(
type="checkbox"
id="is-iocaine"
name="is-iocaine"
ng-model="obj.is_iocaine"
ng-value="true"
)
div
label.button-gray.is-blocked(
title="{{ 'COMMON.BLOCK_TITLE' | translate }}"
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"
)

View File

@ -0,0 +1,44 @@
extends lb-create-edit
block options
section.ticket-assigned-to.multiple-assign(
tg-assigned-users-inline
ng-model="obj"
required-perm="modify_{{ objType }}"
)
div.ticket-estimation
tg-lb-us-estimation(ng-model="obj")
div.ticket-detail-settings
tg-due-date-popover(
due-date="obj.due_date"
is-closed="obj.is_closed"
item="obj"
not-auto-save="true"
)
div
label.button-gray.team-requirement(
for="team-requirement"
title="{{ 'COMMON.TEAM_REQUIREMENT' | translate }}"
ng-class="{ 'active': isTeamRequirement() }"
)
tg-svg(svg-icon="icon-team-requirement")
div
label.button-gray.client-requirement(
for="client-requirement"
title="{{ 'COMMON.CLIENT_REQUIREMENT' | translate }}"
ng-class="{ 'active': isClientRequirement() }"
)
tg-svg(svg-icon="icon-client-requirement")
div
label.button-gray.is-blocked(
title="{{ 'COMMON.BLOCK_TITLE' | translate }}"
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"
)

View File

@ -0,0 +1,66 @@
tg-lightbox-close
form
h2.title(translate="LIGHTBOX.CREATE_EDIT.TITLE_NEW")
div.form-wrapper
main
fieldset
input(
type="text"
name="subject"
ng-model-options="{ debounce: 200 }"
ng-model="obj.subject"
placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}"
data-required="true"
data-maxlength="500"
)
fieldset
tg-tag-line-common.tags-block(
ng-if="project && createEditOpen"
project="project"
tags="obj.tags"
permissions="add_{{objType}}"
on-add-tag="addTag(name, color)"
on-delete-tag="deleteTag(tag)"
)
fieldset
textarea.description(
rows=7
name="description"
ng-model="obj.description"
ng-model-options="{ debounce: 200 }"
ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT.PLACEHOLDER_DESCRIPTION' | translate}}"
)
fieldset
section
tg-attachments-simple(
attachments="attachments",
on-add="addAttachment(attachment)"
on-delete="deleteAttachment(attachment)"
)
sidebar.sidebar.ticket-data
fieldset.status-button
div.status-dropdown.editable(style="background-color:{{ selectedStatus.color }}")
span.status-text {{ selectedStatus.name }}
tg-svg(svg-icon="icon-arrow-down")
ul.pop-status.popover
li(ng-repeat="s in statusList")
a.status(
href=""
title="{{ s.name }}"
data-status-id="{{ s.id }}"
) {{ s.name }}
block options
button.button-green.submit-button(
type="submit"
title="{{'COMMON.CREATE' | translate}}"
translate="COMMON.CREATE"
)
div.lightbox.lightbox-select-user(tg-lb-assignedto)

View File

@ -1,60 +0,0 @@
tg-lightbox-close
form
h2.title(translate="LIGHTBOX.CREATE_ISSUE.TITLE")
fieldset
input(
type="text"
ng-model="issue.subject"
ng-attr-placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}"
ng-model-options="{ debounce: 200 }"
data-required="true"
data-maxlength="500"
)
.fieldset-row
fieldset
select.type(
ng-model="issue.type"
ng-options="t.id as t.name for t in issueTypes"
)
fieldset
select.priority(
ng-model="issue.priority"
ng-options="p.id as p.name for p in priorityList"
)
fieldset
select.severity(
ng-model="issue.severity"
ng-options="s.id as s.name for s in severityList"
)
fieldset
tg-tag-line-common.tags-block(
ng-if="project && createIssueOpen"
project="project"
tags="issue.tags"
permissions="add_issue"
on-add-tag="addTag(name, color)"
on-delete-tag="deleteTag(tag)"
)
fieldset
section
tg-attachments-simple(
attachments="attachments",
on-add="addAttachment(attachment)"
on-delete="deleteAttachment(attachment)"
)
fieldset
textarea.description(
ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}"
ng-model="issue.description"
)
// include lightbox-attachments
button.button-green.submit-button(
type="submit"
title="{{'COMMON.CREATE' | translate}}"
translate="COMMON.CREATE"
)

View File

@ -1,92 +0,0 @@
tg-lightbox-close
form
h2.title(translate="LIGHTBOX.CREATE_EDIT_TASK.TITLE")
fieldset
input(
type="text"
ng-model="task.subject"
ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT_TASK.PLACEHOLDER_SUBJECT' | translate}}"
ng-model-options="{ debounce: 200 }"
data-required="true"
data-maxlength="500"
)
fieldset
select(
ng-model="task.status"
ng-options="s.id as s.name for s in taskStatusList"
placeholder="{{'LIGHTBOX.CREATE_EDIT_TASK.PLACEHOLDER_STATUS' | translate}}"
)
fieldset
select(
ng-model="task.assigned_to"
ng-options="s.id as s.full_name_display for s in users"
placeholder="{{'Assigned to'}}"
)
option(
value=""
translate="LIGHTBOX.CREATE_EDIT_TASK.OPTION_UNASSIGNED"
)
fieldset
tg-tag-line-common.tags-block(
ng-if="project && createEditTaskOpen"
project="project"
tags="task.tags"
permissions="add_task"
on-add-tag="addTag(name, color)"
on-delete-tag="deleteTag(tag)"
)
fieldset
section
tg-attachments-simple(
attachments="attachments",
on-add="addAttachment(attachment)"
on-delete="deleteAttachment(attachment)"
)
fieldset
textarea.description(
ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT_TASK.PLACEHOLDER_SHORT_DESCRIPTION' | translate}}"
ng-model="task.description"
ng-model-options="{ debounce: 200 }"
)
div.settings
fieldset.iocaine-flag(title="{{'COMMON.IOCAINE_TEXT' | translate}}")
input(
type="checkbox"
ng-model="task.is_iocaine"
name="iocaine-task"
id="iocaine-task"
ng-value="true"
)
label.iocaine.trans-button(for="iocaine-task")
tg-svg(svg-icon="icon-iocaine")
span Iocaine
fieldset.blocking-flag
input(
type="checkbox"
ng-model="task.is_blocked"
name="blocked-task"
id="blocked-task"
ng-value="true"
)
label.blocked.trans-button(
for="blocked-task"
translate="COMMON.BLOCKED"
)
tg-blocking-message-input(
watch="task.is_blocked"
ng-model="task.blocked_note"
)
button.button-green.submit-button(
type="submit"
title="{{'COMMON.CREATE' | translate}}"
translate="COMMON.CREATE"
)

View File

@ -1,100 +0,0 @@
tg-lightbox-close
form
h2.title(translate="LIGHTBOX.CREATE_EDIT_US.TITLE")
fieldset
input(
type="text"
name="subject"
ng-model-options="{ debounce: 200 }"
ng-model="us.subject"
placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}"
data-required="true"
data-maxlength="500"
)
fieldset.ticket-estimation
tg-lb-us-estimation(ng-model="us")
fieldset
select(
name="status"
ng-model="us.status"
ng-options="s.id as s.name for s in usStatusList"
)
fieldset
tg-tag-line-common.tags-block(
ng-if="project && createEditUsOpen"
project="project"
tags="us.tags"
permissions="add_us"
on-add-tag="addTag(name, color)"
on-delete-tag="deleteTag(tag)"
)
fieldset
textarea.description(
name="description"
ng-model="us.description"
ng-model-options="{ debounce: 200 }"
ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT_US.PLACEHOLDER_DESCRIPTION' | translate}}"
)
fieldset
section
tg-attachments-simple(
attachments="attachments",
on-add="addAttachment(attachment)"
on-delete="deleteAttachment(attachment)"
)
div.settings
fieldset.team-requirement
input(
type="checkbox"
name="team_requirement"
ng-model="us.team_requirement"
id="team-requirement"
ng-value="true"
)
label.requirement.trans-button(
for="team-requirement"
translate="US.FIELDS.TEAM_REQUIREMENT"
)
fieldset.client-requirement
input(
type="checkbox"
name="client_requirement"
ng-model="us.client_requirement",
id="client-requirement"
ng-value="true"
)
label.requirement.trans-button(
for="client-requirement"
translate="US.FIELDS.CLIENT_REQUIREMENT"
)
fieldset.blocking-flag
input(
type="checkbox"
name="is_blocked"
ng-model="us.is_blocked"
id="blocked-us"
ng-value="true"
)
label.blocked.trans-button(
for="blocked-us"
translate="COMMON.BLOCKED"
)
tg-blocking-message-input(
watch="us.is_blocked"
ng-model="us.blocked_note"
)
button.button-green.submit-button(
type="submit"
title="{{'COMMON.CREATE' | translate}}"
translate="COMMON.CREATE"
)

View File

@ -114,12 +114,11 @@ div.wrapper(
section.ticket-detail-settings
tg-due-date(
tg-check-permission="modify_issue"
due-date="issue.due_date"
due-date-status="issue.due_date_status"
format="button"
is-closed="issue.is_closed"
item="issue"
format="button"
tg-check-permission="modify_issue"
)
tg-promote-issue-to-us-button(
tg-check-permission="add_us",

View File

@ -33,8 +33,8 @@ div.wrapper.issues.lightbox-generic-form(
div.lightbox.lightbox-select-user(tg-lb-assignedto)
div.lightbox.lightbox-create-issue(tg-lb-create-issue)
include ../includes/modules/lightbox-create-issue
div.lightbox.lightbox-generic-form.lightbox-create-edit(tg-lb-create-edit)
include ../includes/modules/lightbox-create-edit/lb-create-edit-issue
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-issues)
include ../includes/modules/lightbox-issue-bulk

View File

@ -40,8 +40,8 @@ div.wrapper(
include ../includes/modules/kanban-table
div.lightbox.lightbox-generic-form.lb-create-edit-userstory(tg-lb-create-edit-userstory)
include ../includes/modules/lightbox-us-create-edit
div.lightbox.lightbox-generic-form.lightbox-create-edit(tg-lb-create-edit)
include ../includes/modules/lightbox-create-edit/lb-create-edit-us
div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories)
include ../includes/modules/lightbox-us-bulk

View File

@ -5,7 +5,6 @@
span(ng-non-bindable) <%= emojify(task.subject) %>
tg-due-date(
due-date="task.due_date"
due-date-status="task.due_date_status"
ng-if="task.due_date"
)
.task-settings

View File

@ -103,12 +103,11 @@ div.wrapper(
section.ticket-detail-settings
tg-due-date(
tg-check-permission="modify_task"
due-date="task.due_date"
due-date-status="task.due_date_status"
format="button"
is-closed="task.is_closed"
item="task"
format="button"
tg-check-permission="modify_task"
)
tg-task-is-iocaine-button(ng-model="task")
tg-block-button(tg-check-permission="modify_task", ng-model="task")

View File

@ -47,8 +47,8 @@ div.wrapper(
include ../includes/modules/taskboard-table
div.lightbox.lightbox-generic-form(tg-lb-create-edit-task)
include ../includes/modules/lightbox-task-create-edit
div.lightbox.lightbox-generic-form.lightbox-create-edit(tg-lb-create-edit)
include ../includes/modules/lightbox-create-edit/lb-create-edit-task
div.lightbox.lightbox-generic-bulk.lightbox-task-bulk(tg-lb-create-bulk-tasks)
include ../includes/modules/lightbox-task-bulk

View File

@ -128,12 +128,11 @@ div.wrapper(
section.ticket-detail-settings
tg-due-date(
tg-check-permission="modify_us"
due-date="us.due_date"
due-date-status="us.due_date_status"
format="button"
is-closed="us.is_closed"
item="us"
format="button"
tg-check-permission="modify_us"
)
tg-us-team-requirement-button(ng-model="us")
tg-us-client-requirement-button(ng-model="us")

View File

@ -600,3 +600,229 @@
}
}
}
.lightbox-create-edit {
z-index: 9998;
$control-height: 30px;
$spacing: 15px;
$width: 700px;
$pop-width: 203px;
$sidebar-width: 220px;
form {
flex-basis: $width;
max-width: $width;
width: $width;
}
.form-wrapper {
display: flex;
flex-basis: $width;
flex-direction: row;
flex-grow: 0;
margin-bottom: $spacing*2;
main {
flex-grow: 1;
margin-right: $spacing;
}
.sidebar {
border-left: 2px solid $whitish;
padding-left: $spacing;
width: $sidebar-width;
}
}
.status-button {
display: flex;
position: relative;
}
.status-dropdown {
align-content: center;
color: $white;
cursor: pointer;
display: flex;
flex-basis: 100%;
height: $control-height;
padding: .25rem .5rem;
.status-text {
flex-grow: 1;
text-transform: uppercase;
}
svg {
fill: $white;
height: .9rem;
width: .9rem;
}
}
.popover {
a {
color: $white;
display: block;
padding: .5rem 1rem;
text-align: left;
&:hover,
&.active {
color: $primary-light;
}
}
}
.ticket-assigned-to {
border: 0;
padding: 0;
&.single-assign {
margin: 0;
}
&.multiple-assign {
align-items: start;
flex-direction: column;
margin: 0;
}
.assigned-to-options {
display: block;
}
.remove-user {
top: 1.2rem;
}
}
.user-list {
display: flex;
width: 100%;
$item-size: 44.75px;
.user-list-item {
margin-right: .5rem;
width: $item-size;
img {
height: $item-size;
width: $item-size;
}
&:last-child {
margin-right: 0;
}
&.counter {
background: $gray-lighter;
color: $gray-light;
font-weight: 400;
height: $item-size;
line-height: $item-size;
text-align: center;
}
}
}
.tg-add-assigned {
align-items: center;
display: flex;
margin: .25rem 0 .75rem;
.add-assigned {
fill: $gray;
opacity: 1;
right: .5rem;
top: 2rem;
&:hover {
cursor: pointer;
fill: $red;
transition: fill .2s;
}
}
span {
@include font-size(small);
@include font-type(light);
color: $gray;
margin: .2rem .5rem;
}
}
.users-dropdown {
position: relative;
}
.pop-status {
@include popover($pop-width, $control-height - 2px);
}
.pop-users {
@include popover($pop-width, 60px, 0, '', '', 16px, -7px, ($pop-width / 3));
&.multiple {
top: 84px;
&:after {
left: 30px;
}
}
ul {
margin-bottom: .5rem;
}
li {
border-bottom: 1px solid $gray-light;
&:last-child {
border: 0;
}
}
.user-list-single {
align-content: center;
align-items: center;
display: flex;
flex-direction: row;
flex-grow: 0;
padding: .5rem 0;
&.selected {
color: $primary;
}
}
.user-list-avatar {
height: 32px;
margin-right: .5rem;
width: 32px;
}
.user-list-name {
@include font-type(text);
flex-grow: 1;
position: relative;
}
.remove svg {
fill: $whitish;
height: .8rem;
width: .8rem;
}
.show-more {
border-top: 1px solid $gray-light;
padding-top: .5rem;
text-align: center;
}
}
.ticket-data-container {
margin: 0;
padding: 0 0 .1rem;
.ticket-status .priority-data {
margin: 0;
}
}
.ticket-estimation .points-per-role {
margin: 0;
}
.ticket-detail-settings {
height: 52px;
justify-content: left;
margin: 1rem 0 0;
label {
border: 0;
padding: 12px 13px;
width: auto;
}
.item-block,
.item-unblock {
display: block;
margin: 0;
}
}
.blocked-note {
margin-top: .5rem;
}
}