Add existing issue to sprint from taskboard

stable
Daniel García 2018-07-12 16:02:31 +02:00 committed by Alex Hermida
parent 8e12fd3528
commit 92460e3733
19 changed files with 570 additions and 367 deletions

View File

@ -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

View File

@ -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", [

View File

@ -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) ->

View File

@ -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)

View File

@ -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

View File

@ -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?"

View File

@ -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'])"

View File

@ -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')})"

View File

@ -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'

View File

@ -31,6 +31,7 @@ cardDirective = () ->
onToggleFold: "&",
onClickAssignedTo: "&",
onClickEdit: "&",
onClickRemove: "&",
onClickDelete: "&",
project: "=",
item: "=",

View File

@ -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;
}

View File

@ -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,

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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)

View File

@ -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"

View File

@ -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;

View File

@ -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;