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) -> renderUser = (assignedObject) ->
if assignedObject?.assigned_to if assignedObject?.assigned_to
$scope.selected = assignedObject.assigned_to $scope.selected = assignedObject.assigned_to
assignedObject.assigned_to_extra_info = $scope.usersById[$scope.selected] assigned_to_extra_info = $scope.usersById[$scope.selected]
$scope.fullName = assignedObject.assigned_to_extra_info?.full_name_display $scope.fullName = assigned_to_extra_info?.full_name_display
$scope.isUnassigned = false $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.bg = $scope.avatar.bg
$scope.isIocaine = assignedObject?.is_iocaine $scope.isIocaine = assignedObject?.is_iocaine
else else

View File

@ -779,7 +779,13 @@ CreateEditDirective = (
$log, $repo, $model, $rs, $rootScope, lightboxService, $loading, $translate, $log, $repo, $model, $rs, $rootScope, lightboxService, $loading, $translate,
$confirm, $q, attachmentsService, $template, $compile) -> $confirm, $q, attachmentsService, $template, $compile) ->
link = ($scope, $el, attrs) -> link = ($scope, $el, attrs) ->
schema = null
objType = null
form = null form = null
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
schemas = { schemas = {
us: { us: {
objName: 'User Story', objName: 'User Story',
@ -851,67 +857,50 @@ $confirm, $q, attachmentsService, $template, $compile) ->
} }
} }
attachmentsToAdd = Immutable.List()
attachmentsToDelete = Immutable.List()
$scope.$on "genericform:new", (ctx, data) -> $scope.$on "genericform:new", (ctx, data) ->
if beforeMount('new', data, ['project']) getSchema(data)
mountCreateForm(data) $scope.mode = 'new'
afterMount() $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) -> $scope.$on "genericform:edit", (ctx, data) ->
if beforeMount('edit', data, ['project', 'obj', 'attachments']) getSchema(data)
mountUpdateForm(data) $scope.mode = 'edit'
afterMount() $scope.getOrCreate = false
mount(data)
beforeMount = (mode, data, requiredAttrs) -> getSchema = (data) ->
form.reset() if form
# Get form schema
if !data.objType || !schemas[data.objType]
return $log.error(
"Invalid objType `#{data.objType}` for `genericform:#{mode}` event")
$scope.objType = data.objType $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 mount = (data) ->
getAttrs(mode, data, requiredAttrs) $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()
return true _.map schema.data($scope.project), (value, key) ->
$scope[key] = value
mountCreateForm = (data) -> form.reset() if form
$scope.obj = $model.make_model($scope.schema.model, $scope.schema.initialData(data))
$scope.isNew = true
$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()
afterMount = () ->
resetAttachments() resetAttachments()
setStatus($scope.obj?.status) setStatus($scope.obj.status)
$scope.lightboxOpen = true
lightboxService.open($el)
$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 = () -> resetAttachments = () ->
attachmentsToAdd = Immutable.List() attachmentsToAdd = Immutable.List()
@ -929,7 +918,6 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.addTag = (tag, color) -> $scope.addTag = (tag, color) ->
value = trim(tag.toLowerCase()) value = trim(tag.toLowerCase())
tags = $scope.project.tags tags = $scope.project.tags
projectTags = $scope.project.tags_colors projectTags = $scope.project.tags_colors
@ -940,11 +928,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
tags.push(value) tags.push(value)
projectTags[tag] = color || null projectTags[tag] = color || null
$scope.project.tags = tags $scope.project.tags = tags
itemtags = _.clone($scope.obj.tags) itemtags = _.clone($scope.obj.tags)
inserted = _.find itemtags, (it) -> it[0] == value inserted = _.find itemtags, (it) -> it[0] == value
if !inserted if !inserted
@ -953,98 +939,117 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.deleteTag = (tag) -> $scope.deleteTag = (tag) ->
value = trim(tag[0].toLowerCase()) value = trim(tag[0].toLowerCase())
tags = $scope.project.tags tags = $scope.project.tags
itemtags = _.clone($scope.obj.tags) itemtags = _.clone($scope.obj.tags)
_.remove itemtags, (tag) -> tag[0] == value _.remove itemtags, (tag) -> tag[0] == value
$scope.obj.tags = itemtags $scope.obj.tags = itemtags
_.pull($scope.obj.tags, value) _.pull($scope.obj.tags, value)
createAttachments = (obj) -> createAttachments = (obj) ->
promises = _.map attachmentsToAdd.toJS(), (attachment) -> promises = _.map attachmentsToAdd.toJS(), (attachment) ->
attachmentsService.upload( attachmentsService.upload(attachment.file, obj.id, $scope.obj.project, $scope.objType)
attachment.file, obj.id, $scope.obj.project, $scope.objType)
return $q.all(promises) return $q.all(promises)
deleteAttachments = (obj) -> deleteAttachments = (obj) ->
promises = _.map attachmentsToDelete.toJS(), (attachment) -> promises = _.map attachmentsToDelete.toJS(), (attachment) ->
return attachmentsService.delete($scope.objType, attachment.id) return attachmentsService.delete($scope.objType, attachment.id)
return $q.all(promises) return $q.all(promises)
submit = debounce 2000, (event) -> addExisting = (ref) ->
event.preventDefault() 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() form = $el.find("form").checksley()
if not form.validate() if not form.validate()
return return
currentLoading = $loading().target(submitButton).start() currentLoading = $loading().target($el.find(".submit-button")).start()
if $scope.isNew if $scope.mode == 'new'
promise = $repo.create($scope.schema.model, $scope.obj) promise = $repo.create(schema.model, $scope.obj)
broadcastEvent = "#{$scope.objType}form:new:success" broadcastEvent = "#{$scope.objType}form:new:success"
else 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) promise = $repo.save($scope.obj, true)
broadcastEvent = "#{$scope.objType}form:edit:success" broadcastEvent = "#{$scope.objType}form:edit:success"
promise.then (data) -> promise.then (data) ->
deleteAttachments(data) deleteAttachments(data).then () ->
.then () -> createAttachments(data) createAttachments(data).then () ->
.then () ->
currentLoading.finish() currentLoading.finish()
lightboxService.close($el) close()
$rs[schema.model].getByRef(data.project, data.ref, schema.params).then (obj) ->
$rs[$scope.schema.model].getByRef( $rootScope.$broadcast(broadcastEvent, obj)
data.project, data.ref, $scope.schema.params).then (obj) ->
$rootScope.$broadcast(broadcastEvent, obj)
promise.then null, (data) -> promise.then null, (data) ->
currentLoading.finish() currentLoading.finish()
form.setErrors(data) form.setErrors(data)
if data._error_message if data._error_message
$confirm.notify("error", data._error_message) $confirm.notify("error", data._error_message)
submitButton = $el.find(".submit-button") checkClose = () ->
close = () ->
if !$scope.obj.isModified() if !$scope.obj.isModified()
lightboxService.close($el) close()
$scope.$apply -> $scope.$apply ->
$scope.obj.revert() $scope.obj.revert()
else else
$confirm.ask( $confirm.ask(
$translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")).then (result) -> $translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")).then (result) ->
lightboxService.close($el)
$scope.obj.revert()
result.finish() result.finish()
close()
close = () ->
delete $scope.objType
delete $scope.mode
$scope.lightboxOpen = false
lightboxService.close($el)
$el.on "submit", "form", submit $el.on "submit", "form", submit
$el.find('.close').on "click", (event) -> $el.find('.close').on "click", (event) ->
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
close() checkClose()
$el.keydown (event) -> $el.keydown (event) ->
event.stopPropagation() event.stopPropagation()
code = if event.keyCode then event.keyCode else event.which code = if event.keyCode then event.keyCode else event.which
if code == 27 if code == 27
close() checkClose()
$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) -> $el.on "click", ".status-dropdown", (event) ->
event.preventDefault() event.preventDefault()
@ -1080,28 +1085,30 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.obj.is_iocaine = not $scope.obj.is_iocaine $scope.obj.is_iocaine = not $scope.obj.is_iocaine
$scope.$broadcast("isiocaine:changed", $scope.obj) $scope.$broadcast("isiocaine:changed", $scope.obj)
setStatus = (id) ->
$scope.obj.status = id
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
$scope.isTeamRequirement = () -> $scope.isTeamRequirement = () ->
return $scope.obj?.team_requirement return $scope.obj?.team_requirement
$scope.isClientRequirement = () -> $scope.isClientRequirement = () ->
return $scope.obj?.client_requirement return $scope.obj?.client_requirement
render = () -> setStatus = (id) ->
templatePath = "common/lightbox/lightbox-create-edit/lb-create-edit-#{$scope.objType}.html" $scope.obj.status = id
template = $template.get(templatePath, true) $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 $scope[key] = value
html = $compile(template($scope))($scope) # html = $compile(template($scope))($scope)
$el.html(html) # $el.html(html)
return { return {
link: link link: link
templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"
} }
module.directive("tgLbCreateEdit", [ module.directive("tgLbCreateEdit", [

View File

@ -107,7 +107,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
else if @.zoomLevel > 1 && previousZoomLevel <= 1 else if @.zoomLevel > 1 && previousZoomLevel <= 1
@.zoomLoading = true @.zoomLoading = true
@.loadTasks().then () => @q.all([@.loadTasks(), @.loadIssues()]).then () =>
@.zoomLoading = false @.zoomLoading = false
@taskboardTasksService.resetFolds() @taskboardTasksService.resetFolds()
@ -320,6 +320,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@analytics.trackEvent("issue", "create", "create issue on taskboard", 1) @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) => @scope.$on "issueform:edit:success", (event, issue) =>
@.refreshTagsColors().then () => @.refreshTagsColors().then () =>
@taskboardIssuesService.replaceModel(issue) @taskboardIssuesService.replaceModel(issue)
@ -376,6 +380,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@scope.taskStatusList = _.sortBy(project.task_statuses, "order") @scope.taskStatusList = _.sortBy(project.task_statuses, "order")
@scope.usStatusList = _.sortBy(project.us_statuses, "order") @scope.usStatusList = _.sortBy(project.us_statuses, "order")
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id) @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) @scope.$emit('project:loaded', project)
@ -417,10 +423,14 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
loadIssues: -> loadIssues: ->
params = {} params = {}
if @.zoomLevel > 1
params.include_attachments = 1
params = _.merge params, @location.search() params = _.merge params, @location.search()
return @rs.issues.listInProject(@scope.projectId, @scope.sprintId, params).then (issues) => 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) @taskboardIssuesService.set(issues)
loadTasks: -> loadTasks: ->
@ -544,6 +554,26 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
askResponse.finish(false) askResponse.finish(false)
@confirm.notify("error") @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) -> taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
task = @taskboardTasksService.getTaskModel(task.get('id')) task = @taskboardTasksService.getTaskModel(task.get('id'))
@ -587,17 +617,26 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
addNewIssue: (type, us) -> addNewIssue: (type, us) ->
switch type switch type
when "standard" then @rootscope.$broadcast("genericform:new", when "standard" then @rootscope.$broadcast("genericform:new-or-existing",
{ {
'objType': 'issue', 'objType': 'issue',
'project': @scope.project, '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 "standard" then @rootscope.$broadcast("taskform:new", @scope.sprintId, us?.id)
when "bulk" then @rootscope.$broadcast("issueform:bulk", @scope.projectId, @scope.sprintId) when "bulk" then @rootscope.$broadcast("issueform:bulk", @scope.projectId, @scope.sprintId)
toggleFold: (id) -> toggleFold: (id, modelName) ->
@taskboardTasksService.toggleFold(id) if modelName == 'issues'
@taskboardIssuesService.toggleFold(id)
else if modelName == 'tasks'
@taskboardTasksService.toggleFold(id)
changeTaskAssignedTo: (id) -> changeTaskAssignedTo: (id) ->
task = @taskboardTasksService.getTaskModel(id) task = @taskboardTasksService.getTaskModel(id)

View File

@ -25,16 +25,33 @@ class TaskboardIssuesService extends taiga.Service
@.reset() @.reset()
reset: () -> reset: () ->
@.foldStatusChanged = {}
@.issuesRaw = [] @.issuesRaw = []
init: (project, usersById) -> init: (project, usersById, issueStatusById) ->
@.issueStatusById = issueStatusById
@.project = project @.project = project
@.usersById = usersById @.usersById = usersById
resetFolds: () ->
@.foldStatusChanged = {}
@.refresh()
toggleFold: (issueId) ->
@.foldStatusChanged[issueId] = !@.foldStatusChanged[issueId]
@.refresh()
add: (issue) -> add: (issue) ->
@.issuesRaw = @.issuesRaw.concat(issue) @.issuesRaw = @.issuesRaw.concat(issue)
@.refresh() @.refresh()
remove: (issue) ->
for key, item of @.issuesRaw
if issue.id == item.id
@.issuesRaw.splice(key, 1)
@.refresh()
return
set: (issues) -> set: (issues) ->
@.issuesRaw = issues @.issuesRaw = issues
@.refresh() @.refresh()
@ -58,14 +75,14 @@ class TaskboardIssuesService extends taiga.Service
issues = [] issues = []
for issueModel in @.issuesRaw for issueModel in @.issuesRaw
issue = {} issue = {}
issue.foldStatusChanged = @.foldStatusChanged[issueModel.id]
model = issueModel.getAttrs() issue.model = issueModel.getAttrs()
issue.modelName = issueModel.getName()
issue.model = model
issue.images = _.filter model.attachments, (it) -> return !!it.thumbnail_card_url
issue.id = issueModel.id 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.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]} return {name: tag[0], color: tag[1]}
issues.push(issue) 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} return {"task_id": task.id, "order": @.order[task.id], "set_orders": setOrders}
refresh: -> refresh: ->
if !@.project
return
@.tasksRaw = _.sortBy @.tasksRaw, (it) => @.order[it.id] @.tasksRaw = _.sortBy @.tasksRaw, (it) => @.order[it.id]
tasks = @.tasksRaw tasks = @.tasksRaw

View File

@ -48,7 +48,8 @@
"CARD": { "CARD": {
"ASSIGN_TO": "Assign To", "ASSIGN_TO": "Assign To",
"EDIT": "Edit card", "EDIT": "Edit card",
"DELETE": "Delete card" "DELETE": "Delete card",
"REMOVE_ISSUE_FROM_SPRINT": "Remove issue from sprint"
}, },
"FORM_ERRORS": { "FORM_ERRORS": {
"DEFAULT_MESSAGE": "This value seems to be invalid.", "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 }}", "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this {{ objName }}",
"NEW": "New {{ objName }}", "NEW": "New {{ objName }}",
"EDIT": "Edit {{ 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": { "DELETE_DUE_DATE": {
"TITLE": "Delete due date", "TITLE": "Delete due date",
@ -1351,6 +1357,8 @@
"TITLE_ACTION_ASSIGN": "Assign task", "TITLE_ACTION_ASSIGN": "Assign task",
"PLACEHOLDER_CARD_TITLE": "This could be a task", "PLACEHOLDER_CARD_TITLE": "This could be a task",
"PLACEHOLDER_CARD_TEXT": "Split Stories into tasks to track them separately", "PLACEHOLDER_CARD_TEXT": "Split Stories into tasks to track them separately",
"NEW_ISSUE": "New issue",
"EXISTING_ISSUE": "Existing issue",
"TABLE": { "TABLE": {
"COLUMN": "User story", "COLUMN": "User story",
"TITLE_ACTION_FOLD": "Fold column", "TITLE_ACTION_FOLD": "Fold column",
@ -1426,6 +1434,10 @@
"SEVERITY": "Severity", "SEVERITY": "Severity",
"TYPE": "Type" "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": { "CONFIRM_PROMOTE": {
"TITLE": "Promote this issue to a new user story", "TITLE": "Promote this issue to a new user story",
"MESSAGE": "Are you sure you want to create a new US from this Issue?" "MESSAGE": "Are you sure you want to create a new US from this Issue?"

View File

@ -2,13 +2,21 @@
ng-if="vm.visible('extra_info')" ng-if="vm.visible('extra_info')"
ng-class="{'empty-tasks': !vm.item.getIn(['model', 'tasks']).size}" ng-class="{'empty-tasks': !vm.item.getIn(['model', 'tasks']).size}"
) )
span.card-estimation.not-estimated( span(ng-switch="vm.item.get('modelName') == 'issues'")
ng-if="vm.item.getIn(['model', 'total_points']) === null && vm.visible('empty_extra_info')", span(ng-switch-when="true")
translate="US.NOT_ESTIMATED" span.card-status-tag(
) ng-if="vm.item.get('status')"
span.card-estimation( ng-style="{color: vm.item.getIn(['status', 'color'])}"
ng-if="vm.item.getIn(['model', 'total_points'])" ) {{ vm.item.getIn(['status', 'name']) }}
) {{"COMMON.FIELDS.POINTS" | translate}} {{vm.item.getIn(['model', 'total_points'])}} 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"
)
span.card-estimation(
ng-if="vm.item.getIn(['model', 'total_points'])"
) {{"COMMON.FIELDS.POINTS" | translate}} {{vm.item.getIn(['model', 'total_points'])}}
.card-statistics .card-statistics
tg-due-date.statistic.card-due-date( tg-due-date.statistic.card-due-date(
due-date="vm.item.getIn(['model', 'due_date'])" due-date="vm.item.getIn(['model', 'due_date'])"

View File

@ -46,7 +46,14 @@
title="{{ 'COMMON.CARD.EDIT' | translate }}" title="{{ 'COMMON.CARD.EDIT' | translate }}"
) )
tg-svg(svg-icon="icon-edit") 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( a.e2e-edit.card-delete(
href="" href=""
ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickDelete({id: vm.item.get('id')})" ng-click="!$event.ctrlKey && !$event.metaKey && vm.onClickDelete({id: vm.item.get('id')})"

View File

@ -87,6 +87,8 @@ class CardController
getNavKey: () -> getNavKey: () ->
if @.type == 'task' if @.type == 'task'
return 'project-tasks-detail' return 'project-tasks-detail'
else if @.type == 'issue'
return 'project-issues-detail'
else else
return 'project-userstories-detail' return 'project-userstories-detail'

View File

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

View File

@ -125,7 +125,7 @@
.card-actions { .card-actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 0 .5rem; padding: 0 0 0 .5rem;
} }
.card-delete:hover { .card-delete:hover {
color: $red-light; color: $red-light;
@ -160,6 +160,12 @@
font-size: 14px; font-size: 14px;
justify-content: space-between; justify-content: space-between;
padding: 0 1rem .5rem; padding: 0 1rem .5rem;
.card-status-tag {
font-size: .75rem;
height: .1rem;
line-height: .1rem;
padding: 0 .5em 0 0;
}
.card-estimation.not-estimated { .card-estimation.not-estimated {
font-size: .8125rem; font-size: .8125rem;
} }

View File

@ -22,24 +22,6 @@ module = angular.module("taigaComponents")
dueDatePopoverDirective = ($translate, datePickerConfigService) -> dueDatePopoverDirective = ($translate, datePickerConfigService) ->
return { return {
link: (scope, el, attrs, ctrl) -> 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() datePickerConfig = datePickerConfigService.get()
_.merge(datePickerConfig, { _.merge(datePickerConfig, {
field: el.find('input.due-date')[0] field: el.find('input.due-date')[0]
@ -50,9 +32,28 @@ dueDatePopoverDirective = ($translate, datePickerConfigService) ->
el.find(".date-picker-popover").popover().close() el.find(".date-picker-popover").popover().close()
scope.$apply() scope.$apply()
}) })
el.picker = new Pikaday(datePickerConfig) 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", controller: "DueDateCtrl",
controllerAs: "vm", controllerAs: "vm",
bindToController: true, bindToController: true,

View File

@ -1,40 +1,32 @@
extends lb-create-edit section.ticket-assigned-to(
tg-assigned-to-inline
block options ng-model="obj"
section.ticket-assigned-to( required-perm="modify_{{ objType }}"
tg-assigned-to-inline )
div.ticket-data-container
tg-issue-type-button.ticket-status(
not-auto-save="true"
ng-model="obj" ng-model="obj"
required-perm="modify_{{ objType }}"
) )
div.ticket-data-container tg-issue-severity-button.ticket-status(
tg-issue-type-button.ticket-status( not-auto-save="true"
not-auto-save="true" ng-model="obj"
ng-model="obj"
)
tg-issue-severity-button.ticket-status(
not-auto-save="true"
ng-model="obj"
)
tg-issue-priority-button.ticket-status(
not-auto-save="true"
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"
) )
tg-issue-priority-button.ticket-status(
not-auto-save="true"
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")

View File

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

View File

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

View File

@ -1,62 +1,139 @@
tg-lightbox-close tg-lightbox-close
form form(ng-if="lightboxOpen")
h2.title {{ text.title }} h2.title(ng-switch="mode")
div.form-wrapper span(ng-switch-when="new") {{ 'LIGHTBOX.CREATE_EDIT.NEW' | translate: { objName: objName } }}
main span(ng-switch-when="edit") {{ 'LIGHTBOX.CREATE_EDIT.EDIT' | translate: { objName: objName } }}
fieldset span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: existingOptions.title } }}
input(
type="text" .existing-or-new-selector(ng-show="getOrCreate == true")
name="subject" .existing-or-new-selector-single
ng-model-options="{ debounce: 200 }" input(
ng-model="obj.subject" type="radio"
placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" 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" data-required="true"
data-maxlength="500" )
- 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(
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"
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 }}
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
tg-blocking-message-input(
watch="obj.is_blocked"
ng-model="obj.blocked_note"
) )
fieldset button.button-green.submit-button(type="submit", ng-switch="mode")
tg-tag-line-common.tags-block( span(ng-switch-when="new") {{ 'COMMON.CREATE' | translate }}
ng-if="project && createEditOpen" span(ng-switch-when="edit") {{ 'COMMON.SAVE' | translate }}
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") {{ text.action }}
div.lightbox.lightbox-select-user(tg-lb-assignedto) 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)}" ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
tg-class-permission="{'readonly': '!modify_task'}" tg-class-permission="{'readonly': '!modify_task'}"
tg-bind-scope, tg-bind-scope,
on-toggle-fold="ctrl.toggleFold(id)" on-toggle-fold="ctrl.toggleFold(id, 'tasks')"
on-click-edit="ctrl.editTask(id)" on-click-edit="ctrl.editTask(id)"
on-click-delete="ctrl.deleteTask(id)" on-click-delete="ctrl.deleteTask(id)"
on-click-assigned-to="ctrl.changeTaskAssignedTo(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')" 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)}" ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}"
tg-class-permission="{'readonly': '!modify_task'}" 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-edit="ctrl.editTask(id)"
on-click-delete="ctrl.deleteTask(id)" on-click-delete="ctrl.deleteTask(id)"
on-click-assigned-to="ctrl.changeTaskAssignedTo(id)" on-click-assigned-to="ctrl.changeTaskAssignedTo(id)"
@ -141,22 +141,23 @@ div.taskboard-table(
) )
div.taskboard-row.issues-row(ng-class="{'row-fold':usFolded[0]}") div.taskboard-row.issues-row(ng-class="{'row-fold':usFolded[0]}")
div.taskboard-row-title-box.taskboard-column div.taskboard-row-title-box.taskboard-column
a.vfold( div.task-colum-name
href="" a.toggle-fold.vfold(
title="{{'TASKBOARD.TABLE.TITLE_ACTION_FOLD_ROW' | translate}}" href=""
ng-click='foldUs(0)' title="{{'TASKBOARD.TABLE.TITLE_ACTION_FOLD_ROW' | translate}}"
ng-class="{hidden:usFolded[0]}" ng-click='foldUs(0)'
) ng-class="{hidden:usFolded[0]}"
tg-svg.fold-action(svg-icon="icon-fold-row") )
a.vunfold( tg-svg.fold-action(svg-icon="icon-fold-row")
href="" a.toggle-fold.vunfold(
title="{{'TASKBOARD.TABLE.TITLE_ACTION_UNFOLD_ROW' | translate}}" href=""
ng-click='foldUs(0)' title="{{'TASKBOARD.TABLE.TITLE_ACTION_UNFOLD_ROW' | translate}}"
ng-class="{hidden:!usFolded[0]}" 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") tg-svg.fold-action(svg-icon="icon-unfold-row")
include ../components/addnewissue.jade span.row-title(translate="TASKBOARD.TABLE.ROW_ISSUES_TITLE")
include ../components/addnewissue.jade
div.taskboard-cards-box div.taskboard-cards-box
tg-card.card.ng-animate-disabled( tg-card.card.ng-animate-disabled(
@ -164,8 +165,9 @@ div.taskboard-table(
class="kanban-task-minimized" class="kanban-task-minimized"
tg-class-permission="{'readonly': '!modify_issue'}" tg-class-permission="{'readonly': '!modify_issue'}"
tg-bind-scope, tg-bind-scope,
on-toggle-fold="ctrl.toggleFold(id)" on-toggle-fold="ctrl.toggleFold(id, 'issues')"
on-click-edit="ctrl.editIssue(id)" on-click-edit="ctrl.editIssue(id)"
on-click-remove="ctrl.removeIssueFromSprint(id)"
on-click-delete="ctrl.deleteIssue(id)" on-click-delete="ctrl.deleteIssue(id)"
on-click-assigned-to="ctrl.changeIssueAssignedTo(id)" on-click-assigned-to="ctrl.changeIssueAssignedTo(id)"
project="project" project="project"

View File

@ -54,20 +54,7 @@ $column-padding: .5rem 1rem;
padding-right: 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 { .task-colum-name {
@include font-size(medium); @include font-size(medium);
align-items: center; align-items: center;
@ -104,6 +91,20 @@ $column-padding: .5rem 1rem;
@include ellipsis(65%); @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 { tg-svg {
display: block; display: block;
margin-right: .3rem; margin-right: .3rem;
@ -184,6 +185,22 @@ $column-padding: .5rem 1rem;
max-height: 400px; max-height: 400px;
width: 100%; 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 { .card {
cursor: default; cursor: default;
height: auto; height: auto;

View File

@ -21,23 +21,40 @@
min-height: 4.5rem; min-height: 4.5rem;
resize: vertical; resize: vertical;
} }
label {
@include font-size(xsmall); .existing-or-new-selector {
background: $mass-white; display: flex;
border: 1px solid $gray-light; margin-bottom: 2rem;
color: $grayer; input {
cursor: pointer; display: none;
display: block; &:checked+label {
padding: 7px 30px; background: $primary-light;
transition: all .2s ease-in;
&:hover {
span {
color: $white; color: $white;
transition: background .2s ease-in;
}
&:checked+label:hover {
background: $primary-light;
}
+label {
background: rgba($whitish, .7);
cursor: pointer;
display: block;
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 { .existing-or-new-selector-single {
color: $grayer; flex: 1;
vertical-align: middle; &:first-child {
margin-right: .5rem;
}
} }
} }
@ -622,14 +639,25 @@
margin-bottom: $spacing * 2; margin-bottom: $spacing * 2;
main { main {
flex-grow: 1; flex-grow: 1;
margin-right: $spacing; max-width: $width - $sidebar-width;
} }
.sidebar { .sidebar {
border-left: 2px solid $whitish; border-left: 2px solid $whitish;
margin-left: $spacing;
padding-left: $spacing; padding-left: $spacing;
$min-width: $sidebar-width;
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 { .status-button {
display: flex; display: flex;
position: relative; position: relative;