Search List component
parent
7b3bdfbf97
commit
572ba7a8b9
|
@ -840,40 +840,43 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
}
|
||||
}
|
||||
|
||||
$scope.$on "genericform:new", (ctx, data) ->
|
||||
getSchema(data)
|
||||
$scope.$on "genericform:new", (ctx, params) ->
|
||||
getSchema(params)
|
||||
$scope.mode = 'new'
|
||||
$scope.getOrCreate = false
|
||||
mount(data)
|
||||
mount(params)
|
||||
|
||||
$scope.$on "genericform:new-or-existing", (ctx, data) ->
|
||||
getSchema(data)
|
||||
$scope.$on "genericform:new-or-existing", (ctx, params) ->
|
||||
getSchema(params)
|
||||
$scope.mode = 'add-existing'
|
||||
$scope.getOrCreate = true
|
||||
$scope.existingFilterText = ''
|
||||
$scope.existingItems = {}
|
||||
$scope.existingOptions = data.existingOptions
|
||||
mount(data)
|
||||
$scope.existingItems = []
|
||||
$rs[schema.model].listInAllProjects({ project: $scope.project.id }, true).then (data) ->
|
||||
$scope.existingItems = data
|
||||
mount(params)
|
||||
|
||||
$scope.$on "genericform:edit", (ctx, data) ->
|
||||
getSchema(data)
|
||||
$scope.$on "genericform:edit", (ctx, params) ->
|
||||
getSchema(params)
|
||||
$scope.mode = 'edit'
|
||||
$scope.getOrCreate = false
|
||||
mount(data)
|
||||
mount(params)
|
||||
|
||||
getSchema = (params) ->
|
||||
_.map params, (value, key) ->
|
||||
$scope[key] = value
|
||||
|
||||
getSchema = (data) ->
|
||||
$scope.objType = data.objType
|
||||
if !$scope.objType || !schemas[$scope.objType]
|
||||
return $log.error("Invalid objType `#{$scope.objType}` for `genericform` event")
|
||||
schema = schemas[$scope.objType]
|
||||
|
||||
mount = (data) ->
|
||||
mount = (params) ->
|
||||
$scope.objName = schema.objName
|
||||
if $scope.mode == 'edit'
|
||||
$scope.obj = data.obj
|
||||
$scope.attachments = Immutable.fromJS(data.attachments)
|
||||
$scope.obj = params.obj
|
||||
$scope.attachments = Immutable.fromJS(params.attachments)
|
||||
else
|
||||
$scope.obj = $model.make_model(schema.model, schema.initialData(data))
|
||||
$scope.obj = $model.make_model(schema.model, schema.initialData(params))
|
||||
$scope.attachments = Immutable.List()
|
||||
|
||||
_.map schema.data($scope.project), (value, key) ->
|
||||
|
@ -939,35 +942,13 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
return attachmentsService.delete($scope.objType, attachment.id)
|
||||
return $q.all(promises)
|
||||
|
||||
addExisting = (ref) ->
|
||||
addExisting = (item) ->
|
||||
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) ->
|
||||
item.setAttr($scope.relatedField, $scope.relatedObjectId)
|
||||
$repo.save(item, 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
|
||||
)
|
||||
$rootScope.$broadcast("#{$scope.objType}form:add:success", item)
|
||||
|
||||
$scope.addExisting = (selectedItem) ->
|
||||
event.preventDefault()
|
||||
|
@ -1079,16 +1060,6 @@ $confirm, $q, attachmentsService, $template, $compile) ->
|
|||
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
|
||||
$scope.obj.is_closed = $scope.selectedStatus.is_closed
|
||||
|
||||
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)
|
||||
|
||||
return {
|
||||
link: link
|
||||
templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"
|
||||
|
|
|
@ -110,7 +110,10 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@scope.$on "assign-sprint-to-issue:success", (ctx, milestoneId) =>
|
||||
@rootscope.$broadcast("object:updated")
|
||||
@scope.issue.milestone = milestoneId
|
||||
@.loadSprint()
|
||||
if milestoneId
|
||||
@.loadSprint()
|
||||
else
|
||||
@scope.sprint = null
|
||||
|
||||
initializeOnDeleteGoToUrl: ->
|
||||
ctx = {project: @scope.project.slug}
|
||||
|
@ -713,6 +716,7 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
|
|||
issue = $model.$modelValue
|
||||
$rs.sprints.list($scope.projectId, null).then (data) ->
|
||||
$scope.milestones = data.milestones
|
||||
$scope.selectedSprintId = issue.milestone
|
||||
avaliableMilestones = angular.copy($scope.milestones)
|
||||
lightboxService.open($el.find(".lightbox-assign-sprint-to-issue"))
|
||||
|
||||
|
@ -729,15 +733,13 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
|
|||
existsMilestone(filterText, milestone.name)
|
||||
)
|
||||
|
||||
$scope.saveIssueToSprint = (selectedSprintId) ->
|
||||
currentLoading = $loading().target($el.find(".e2e-select-related-sprint-button")).start()
|
||||
issue.setAttr('milestone', selectedSprintId)
|
||||
$scope.saveIssueToSprint = (selectedSprint, event) ->
|
||||
currentLoading = $loading().target($(event.currentTarget)).start()
|
||||
issue.setAttr('milestone', selectedSprint.id)
|
||||
$repo.save(issue, true).then (data) ->
|
||||
currentLoading.finish()
|
||||
lightboxService.close($el.find(".lightbox-assign-sprint-to-issue"))
|
||||
$scope.$broadcast("assign-sprint-to-issue:success", selectedSprintId)
|
||||
|
||||
|
||||
$scope.$broadcast("assign-sprint-to-issue:success", selectedSprint.id)
|
||||
|
||||
return {
|
||||
link: link
|
||||
|
|
|
@ -385,7 +385,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
@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)
|
||||
|
||||
|
@ -623,15 +622,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
|||
switch type
|
||||
when "standard" then @rootscope.$broadcast("genericform:new-or-existing",
|
||||
{
|
||||
'objType': 'issue',
|
||||
'project': @scope.project,
|
||||
'sprintId': @scope.sprintId,
|
||||
'existingOptions': {
|
||||
targetField: 'milestone',
|
||||
targetValue: @scope.sprintId,
|
||||
targetsById: @scope.milestonesById,
|
||||
title: "#{@translate.instant("COMMON.FIELDS.SPRINT")} #{@scope.sprint.name}",
|
||||
}
|
||||
objType: 'issue',
|
||||
project: @scope.project,
|
||||
sprintId: @scope.sprintId,
|
||||
relatedField: 'milestone',
|
||||
relatedObjectId: @scope.sprintId,
|
||||
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)
|
||||
|
|
|
@ -1434,7 +1434,7 @@
|
|||
"LINK_TASKBOARD": "Taskboard",
|
||||
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
|
||||
"FILTER_SPRINTS": "Filter Sprints",
|
||||
"CHOOSE_SPRINT": "What's the user Sprint?",
|
||||
"CHOOSE_SPRINT": "What's the Sprint?",
|
||||
"FIELDS": {
|
||||
"PRIORITY": "Priority",
|
||||
"SEVERITY": "Severity",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
- var hash = "#";
|
||||
div.title #{hash}{{ item.ref }} {{ item.subject }}
|
||||
div.info(ng-if="item.milestone") {{ milestonesById[item.milestone].name }}
|
|
@ -0,0 +1,2 @@
|
|||
div.title {{ item.name }}
|
||||
div.info {{ item.estimated_start }} - {{ item.estimated_finish }}
|
|
@ -0,0 +1,91 @@
|
|||
###
|
||||
# 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: search-list.directive.coffee
|
||||
###
|
||||
|
||||
module = angular.module("taigaComponents")
|
||||
|
||||
normalizeString = @.taiga.normalizeString
|
||||
groupBy = @.taiga.groupBy
|
||||
|
||||
searchListDirective = ($translate) ->
|
||||
link = (scope, el, attrs, model) ->
|
||||
scope.templateUrl = "components/search-list/search-list-#{scope.itemType}-choice.html"
|
||||
scope.currentSelected = null
|
||||
filtering = false
|
||||
avaliableItems = []
|
||||
itemsById = {}
|
||||
|
||||
if scope.itemType == 'issue'
|
||||
scope.milestonesById = groupBy(scope.project.milestones, (e) -> e.id)
|
||||
|
||||
el.on "click", ".choice", (event) ->
|
||||
choiceId = parseInt($(event.currentTarget).data("choice-id"))
|
||||
value = if attrs.ngModel?.id != choiceId then itemsById[choiceId] else null
|
||||
model.$setViewValue(value)
|
||||
scope.currentSelected = value
|
||||
scope.$apply()
|
||||
|
||||
isContainedIn = (needle, haystack) ->
|
||||
return _.includes(parseString(haystack), parseString(needle))
|
||||
|
||||
parseString = (value) ->
|
||||
if typeof value != 'string'
|
||||
value = value.toString()
|
||||
return normalizeString(value.toUpperCase())
|
||||
|
||||
el.on "blur", "#items-filter", (event) ->
|
||||
filtering = false
|
||||
|
||||
el.on "focus", "#items-filter", (event) ->
|
||||
filtering = true
|
||||
|
||||
scope.filterItems = (searchText) ->
|
||||
scope.items = avaliableItems.filter((item) ->
|
||||
itemAttrs = item.getAttrs()
|
||||
if Array.isArray(scope.filterBy)
|
||||
_.some(scope.filterBy, (attr) -> isContainedIn(searchText, itemAttrs[attr]))
|
||||
else
|
||||
isContainedIn(searchText, itemAttrs[scope.filterBy])
|
||||
)
|
||||
if scope.value
|
||||
scope.value = _.find(scope.items, scope.currentSelected)
|
||||
|
||||
scope.$watch 'items', (items) ->
|
||||
if !filtering && items?.length
|
||||
if scope.resetOnChange
|
||||
scope.currentSelected = null
|
||||
model.$setViewValue(null)
|
||||
avaliableItems = angular.copy(items)
|
||||
itemsById = groupBy(avaliableItems, (x) -> x.id)
|
||||
|
||||
return {
|
||||
link: link,
|
||||
templateUrl: "components/search-list/search-list.html",
|
||||
require: "ngModel",
|
||||
scope: {
|
||||
label: '@',
|
||||
placeholder: '@',
|
||||
project: '=',
|
||||
filterBy: '=',
|
||||
items: '=',
|
||||
itemType: '@',
|
||||
resetOnChange: "="
|
||||
}
|
||||
}
|
||||
|
||||
module.directive('tgSearchList', ['$translate', searchListDirective])
|
|
@ -0,0 +1,23 @@
|
|||
fieldset.search-list
|
||||
label(
|
||||
for="items-filter"
|
||||
) {{ label }}
|
||||
input.items-filter(
|
||||
id="items-filter"
|
||||
type="text"
|
||||
placeholder="{{ placeholder }}"
|
||||
ng-model="searchText"
|
||||
ng-change="filterItems(searchText)"
|
||||
)
|
||||
|
||||
ul
|
||||
li.choice(
|
||||
ng-repeat="item in items track by item.id"
|
||||
ng-class="{ 'selected': item.id == currentSelected.id }"
|
||||
data-choice-id="{{ item.id }}"
|
||||
)
|
||||
ng-include(src="templateUrl")
|
||||
|
||||
p.no-stories-found(
|
||||
ng-show="!items.length"
|
||||
) {{ noItemsText }}
|
|
@ -0,0 +1,36 @@
|
|||
.search-list {
|
||||
ul {
|
||||
background: $mass-white;
|
||||
border: 1px solid $gray-light;
|
||||
height: 200px;
|
||||
margin: .25em 0 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: .25em .5em;
|
||||
&.selected {
|
||||
background: $yellow-green;
|
||||
color: $white;
|
||||
.info {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
ng-include {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.info {
|
||||
color: $gray-lighter;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.title span {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,35 +31,17 @@ form(ng-if="lightboxOpen")
|
|||
|
||||
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)"
|
||||
tg-search-list(
|
||||
label="{{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}"
|
||||
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
|
||||
items="existingItems"
|
||||
ng-model="selectedItem"
|
||||
filter-by="['ref', 'subject']"
|
||||
project="project"
|
||||
item-type="{{ objType }}"
|
||||
reset-on-change="true"
|
||||
)
|
||||
|
||||
.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"
|
||||
|
|
|
@ -11,45 +11,19 @@ a.assign-issue-button.button-gray.is-editable(
|
|||
tg-lightbox-close
|
||||
|
||||
div.lightbox-assign-related-sprint
|
||||
h2 "{{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}"
|
||||
|
||||
fieldset.existing-sprint
|
||||
label(
|
||||
translate="ISSUES.CHOOSE_SPRINT"
|
||||
for="sprint-filter"
|
||||
)
|
||||
input.sprint-filter.e2e-filter-sprints-input(
|
||||
id="sprint-filter"
|
||||
type="text"
|
||||
placeholder="{{'ISSUES.FILTER_SPRINTS' | translate}}"
|
||||
ng-model="filterText"
|
||||
ng-change="filterMilestones(filterText)"
|
||||
)
|
||||
|
||||
form.existing-user-story-form
|
||||
select.userstory.e2e-userstories-select(
|
||||
size="5"
|
||||
data-required="true"
|
||||
ng-model="selectedSprint"
|
||||
)
|
||||
- var hash = "#";
|
||||
option.hidden(
|
||||
value=""
|
||||
)
|
||||
option(
|
||||
ng-repeat="milestone in milestones | toMutable track by milestone.id"
|
||||
value="{{ ::milestone.id }}"
|
||||
) #{hash}{{::milestone.name}}
|
||||
|
||||
p.no-stories-found(
|
||||
ng-show="!milestones.length"
|
||||
translate="EPIC.NO_USERSTORIES_FOUND"
|
||||
)
|
||||
|
||||
button.button-green.e2e-select-related-sprint-button(
|
||||
href=""
|
||||
ng-click="saveIssueToSprint(selectedSprint)"
|
||||
ng-disabled="!selectedSprint"
|
||||
tg-loading="vm.loading"
|
||||
translate="COMMON.SAVE"
|
||||
)
|
||||
h2.title {{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}
|
||||
tg-search-list(
|
||||
label="{{ 'ISSUES.CHOOSE_SPRINT' | translate }}"
|
||||
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
|
||||
items="milestones"
|
||||
ng-model="selectedSprint"
|
||||
filter-by="['name', 'estimated_start', 'estimated_finish']"
|
||||
project="project"
|
||||
item-type="sprint"
|
||||
)
|
||||
button.button-green.select-option(
|
||||
href=""
|
||||
ng-click="saveIssueToSprint(selectedSprint, $event)"
|
||||
tg-loading="vm.loading"
|
||||
translate="COMMON.SAVE"
|
||||
)
|
||||
|
|
|
@ -856,6 +856,10 @@
|
|||
|
||||
|
||||
.ticket-detail-settings .lightbox-assign-sprint-to-issue {
|
||||
.lightbox-assign-related-sprint {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: initial;
|
||||
max-height: initial;
|
||||
|
|
Loading…
Reference in New Issue