diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index 5641fd80..d9e42386 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -292,7 +292,6 @@ class QueueModelTransformation extends taiga.Service save: (transformation) -> defered = @q.defer() - @qqueue.add () => obj = @.getObj() comment = obj.comment diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index c7111d0f..ae65482a 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -230,7 +230,6 @@ WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $c watchers = _.map(watchers, (watcherId) -> $scope.usersById[watcherId]) renderWatchers(watchers) $rootscope.$broadcast("object:updated") - transform.then null, -> $confirm.notify("error") @@ -304,7 +303,7 @@ module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueue ## Assigned Users directive ############################################################################# -AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, $translate) -> +AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, $translate, $currentUserService) -> # You have to include a div with the tg-lb-assignedusers directive in the page # where use this directive @@ -314,27 +313,42 @@ AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $templat isAssigned = -> return $scope.assignedUsers.length > 0 - save = (assignedUsers) -> + save = (assignedUsers, assignedToUser) -> transform = $modelTransform.save (item) -> - item.assignedUsers = assignedUsers + item.assigned_users = assignedUsers + if not item.assigned_to + item.assigned_to = assignedToUser return item transform.then -> - console.log(assignedUserId) assignedUsers = _.map(assignedUsers, (assignedUserId) -> $scope.usersById[assignedUserId]) renderAssignedUsers(assignedUsers) - $rootscope.$broadcast("object:updated") + result = $rootscope.$broadcast("object:updated") transform.then null, -> $confirm.notify("error") openAssignedUsers = -> item = _.clone($model.$modelValue, false) - $rootscope.$broadcast("assignedUser:add", item) + $rootscope.$broadcast("assigned-user:add", item) + + assignToMe = -> + return if not isEditable() + currentUserId = $currentUserService.getUser().get('id') + assignedUsers = _.clone($model.$modelValue.assigned_users, false) + assignedUsers.push(currentUserId) + assignedUsers = _.uniq(assignedUsers) + save(assignedUsers, currentUserId) deleteAssignedUser = (assignedUserIds) -> transform = $modelTransform.save (item) -> - item.assignedUsers = assignedUserIds + item.assigned_users = assignedUserIds + + # Update as + if item.assigned_to not in assignedUserIds and assignedUserIds.length > 0 + item.assigned_to = assignedUserIds[0] + if assignedUserIds.length == 0 + item.assigned_to = null return item @@ -353,32 +367,32 @@ AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $templat $scope.isEditable = isEditable() $scope.isAssigned = isAssigned() $scope.openAssignedUsers = openAssignedUsers - console.log('rendering...') + $scope.assignToMe = assignToMe - $el.on "click", ".js-delete-watcher", (event) -> + $el.on "click", ".remove-user", (event) -> event.preventDefault() return if not isEditable() target = angular.element(event.currentTarget) - watcherId = target.data("watcher-id") + assignedUserId = target.data("assigned-user-id") - title = $translate.instant("COMMON.WATCHERS.TITLE_LIGHTBOX_DELETE_WARTCHER") - message = $scope.usersById[watcherId].full_name_display + title = $translate.instant("COMMON.ASSIGNED_USERS.TITLE_LIGHTBOX_DELETE_ASSIGNED") + message = $scope.usersById[assignedUserId].full_name_display $confirm.askOnDelete(title, message).then (askResponse) => askResponse.finish() - watcherIds = _.clone($model.$modelValue.watchers, false) - watcherIds = _.pull(watcherIds, watcherId) + assignedUserIds = _.clone($model.$modelValue.assigned_users, false) + assignedUserIds = _.pull(assignedUserIds, assignedUserId) - deleteWatcher(watcherIds) - - $scope.$on "assignedUser:added", (ctx, assignedUserId) -> + deleteAssignedUser(assignedUserIds) + $scope.$on "assigned-user:added", (ctx, assignedUserId) -> assignedUsers = _.clone($model.$modelValue.assigned_users, false) assignedUsers.push(assignedUserId) assignedUsers = _.uniq(assignedUsers) - - save(assignedUsers) + + # Save assigned_users and assignedUserId for assign_to legacy attribute + save(assignedUsers, assignedUserId) $scope.$watch $attrs.ngModel, (item) -> return if not item? @@ -398,7 +412,7 @@ AssignedUsersDirective = ($rootscope, $confirm, $repo, $modelTransform, $templat } module.directive("tgAssignedUsers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueueModelTransformation", "$tgTemplate", "$compile", - "$translate", AssignedUsersDirective]) + "$translate", "tgCurrentUserService", AssignedUsersDirective]) ############################################################################# @@ -488,7 +502,6 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $ $scope.$on "assigned-to:added", (ctx, userId, item) -> return if item.id != $model.$modelValue.id - save(userId) $scope.$watch $attrs.ngModel, (instance) -> diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index fa1f5ad6..cd370894 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -646,7 +646,6 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic selectedItem = item assignedToId = item.assigned_to selectedUser = $scope.usersById[assignedToId] - render(selectedUser) lightboxService.open($el).then -> $el.find('input').focus() @@ -706,10 +705,10 @@ AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNaviga usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true) # Get prefiltered users by text - # and without now watched users. + # and without now assigned users. getFilteredUsers = (text="") -> _filterUsers = (text, user) -> - if selectedItem && _.find(selectedItem.assignedUsers, (x) -> x == user.id) + if selectedItem && _.find(selectedItem.assigned_users, (x) -> x == user.id) return false username = user.full_name_display.toUpperCase() @@ -737,16 +736,14 @@ AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNaviga html = usersTemplate(ctx) html = $compile(html)($scope) - $el.find(".ticket-watchers").html(html) + $el.find(".assigned-to-list").html(html) closeLightbox = () -> lightboxKeyboardNavigationService.stop() lightboxService.close($el) - $scope.$on "assignedUser:add", (ctx, item) -> - console.log(item) + $scope.$on "assigned-user:add", (ctx, item) -> selectedItem = item - users = getFilteredUsers() render(users) @@ -770,7 +767,7 @@ AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNaviga $scope.$apply -> $scope.usersSearch = null - $scope.$broadcast("assignedUser:added", target.data("user-id")) + $scope.$broadcast("assigned-user:added", target.data("user-id"), selectedItem) $el.on "click", ".close", (event) -> event.preventDefault() @@ -784,7 +781,7 @@ AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNaviga $el.off() return { - templateUrl: "common/lightbox/lightbox-users.html" + templateUrl: "common/lightbox/lightbox-assigned-users.html" link:link } diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 697f7ad8..1f8cb760 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -152,6 +152,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @kanbanUserstoriesService.replaceModel(us) @scope.$on("assigned-to:added", @.onAssignedToChanged) + @scope.$on("assigned-user:added", @.onAssignedUsersChanged) @scope.$on("kanban:us:move", @.moveUs) @scope.$on("kanban:show-userstories-for-status", @.loadUserStoriesForStatus) @scope.$on("kanban:hide-userstories-for-status", @.hideUserStoriesForStatus) @@ -194,6 +195,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @rootscope.$broadcast("assigned-to:add", us) + changeUsAssignedUsers: (id) -> + us = @kanbanUserstoriesService.getUsModel(id) + @rootscope.$broadcast("assigned-user:add", us) + onAssignedToChanged: (ctx, userid, usModel) -> usModel.assigned_to = userid @@ -204,6 +209,20 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi if @.isFilterDataTypeSelected('assigned_to') || @.isFilterDataTypeSelected('role') @.filtersReloadContent() + onAssignedUsersChanged: (ctx, userid, usModel) -> + assignedUsers = _.clone(usModel.assigned_users, false) + assignedUsers.push(userid) + assignedUsers = _.uniq(assignedUsers) + usModel.assigned_users = assignedUsers + if not usModel.assigned_to + usModel.assigned_to = userid + + @kanbanUserstoriesService.replaceModel(usModel) + + promise = @repo.save(usModel) + promise.then null, -> + console.log "FAIL" # TODO + refreshTagsColors: -> return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) => @scope.project.tags_colors = tags_colors._attrs diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 66c4c7ff..d58986f6 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -170,6 +170,11 @@ "PAST_DUE": "past due", "NO_LONGER_APPLICABLE": "no longer applicable" }, + "ASSIGNED_USERS": { + "ADD": "Select assigned user", + "ADD_ASSIGNED": "Add assigned", + "TITLE_LIGHTBOX_DELETE_ASSIGNED": "Delete assigned..." + }, "STATUS": { "CLOSED": "Closed", "OPEN": "Open" diff --git a/app/partials/common/components/assigned-users.jade b/app/partials/common/components/assigned-users.jade new file mode 100644 index 00000000..eb37fdc8 --- /dev/null +++ b/app/partials/common/components/assigned-users.jade @@ -0,0 +1,62 @@ +.assigned-title(ng-if="!isAssigned") {{ "COMMON.ASSIGNED_TO.NOT_ASSIGNED" | translate }} +.assigned-title(ng-if="isAssigned") {{ "COMMON.FIELDS.ASSIGNED_TO" | translate }} + +.tg-assigned-users + .not-assigned-users(ng-if="!isAssigned") + .user-avatar(ng-class="{'is-iocaine': isIocaine}") + img( + tg-avatar="" + alt="{{ 'COMMON.ASSIGNED_TO.ASSIGN' | translate }}" + ) + + //- .iocaine-symbol(ng-if="isIocaine" title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}") + //- tg-svg(svg-icon="icon-iocaine") + .assigned-to + .assigned-users-options + a( + href="" + title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}" + class="user-assigned" + ng-class="{editable: isEditable}" + ng-click="openAssignedUsers()" + ) + span.assigned-name {{ "COMMON.ASSIGNED_TO.ASSIGN" | translate }} + + span(translate="COMMON.OR") + |   + a.assign-to-me( + href="#" + title="{{'COMMON.ASSIGNED_TO.SELF' | translate}}" + ng-click="assignToMe()" + ) + span {{ "COMMON.ASSIGNED_TO.SELF" | translate }} + + .user-list-single(ng-repeat="assignedUser in assignedUsers") + .user-list-avatar + img( + tg-avatar="assignedUser" + alt="{{assignedUser.full_name_display}}" + ) + .user-list-name.assigned-users-options + a( + href="" + title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}" + class!="user-assigned <% if (isEditable) { %>editable<% }; %>" + ) + span.assigned-name {{assignedUser.full_name_display}} + tg-svg.remove-user( + ng-if="isEditable", + data-assigned-user-id="{{assignedUser.id}}" + svg-icon="icon-close", + title="{{'COMMON.ASSIGNED_TO.DELETE_ASSIGNMENT' | translate}}" + ) + +.tg-add-assigned(ng-if="isAssigned") + tg-svg.add-assigned( + ng-if="isEditable", + data-assigned-user-id="{{assignedUser.id}}", + ng-click="openAssignedUsers()", + svg-icon="icon-add", + title="{{'COMMON.ASSIGNED_USERS.ADD_ASSIGNED' | translate}}" + ) + span {{ "COMMON.ASSIGNED_USERS.ADD_ASSIGNED" | translate }} diff --git a/app/partials/common/lightbox/lightbox-assigned-users.jade b/app/partials/common/lightbox/lightbox-assigned-users.jade new file mode 100644 index 00000000..85abe7b6 --- /dev/null +++ b/app/partials/common/lightbox/lightbox-assigned-users.jade @@ -0,0 +1,8 @@ +tg-lightbox-close + +div.form + h2.title(translate="COMMON.ASSIGNED_USERS.ADD") + fieldset + input(type="text", data-maxlength="500", placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}", ng-model="usersSearch") + div.assigned-to-list + //- The content of this is rendered by directive diff --git a/app/partials/includes/modules/kanban-table.jade b/app/partials/includes/modules/kanban-table.jade index f5e39d9b..b9bf46ef 100644 --- a/app/partials/includes/modules/kanban-table.jade +++ b/app/partials/includes/modules/kanban-table.jade @@ -80,7 +80,7 @@ div.kanban-table( tg-bind-scope, on-toggle-fold="ctrl.toggleFold(id)" on-click-edit="ctrl.editUs(id)" - on-click-assigned-to="ctrl.changeUsAssignedTo(id)" + on-click-assigned-to="ctrl.changeUsAssignedUsers(id)" project="project" item="us" zoom="ctrl.zoom" diff --git a/app/partials/kanban/kanban.jade b/app/partials/kanban/kanban.jade index 3a63c543..28eb7134 100644 --- a/app/partials/kanban/kanban.jade +++ b/app/partials/kanban/kanban.jade @@ -46,4 +46,4 @@ div.wrapper( div.lightbox.lightbox-generic-bulk(tg-lb-create-bulk-userstories) include ../includes/modules/lightbox-us-bulk - div.lightbox.lightbox-select-user(tg-lb-assignedto) + div.lightbox.lightbox-select-user(tg-lb-assigned-users) diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index df8a3ee5..c2a0d7ac 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -95,6 +95,7 @@ div.wrapper( tg-us-estimation.ticket-estimation(ng-model="us") + #TODELETE section.ticket-assigned-to( tg-assigned-to ng-model="us" diff --git a/app/styles/components/user-list.scss b/app/styles/components/user-list.scss index b52796a2..feeaf44a 100644 --- a/app/styles/components/user-list.scss +++ b/app/styles/components/user-list.scss @@ -92,3 +92,35 @@ } } } + +.ticket-assigned-users { + @include user-list; + margin-top: 1rem; + .user-list-single { + flex-grow: 1; + &:hover { + .remove-user { + opacity: 1; + transition: opacity .2s ease-in; + } + } + } + .user-list-name { + @include font-type(text); + flex: 1; + position: relative; + } + .remove-user { + cursor: pointer; + fill: currentColor; + opacity: 0; + position: absolute; + right: .5rem; + top: 0; + transition: all .2s ease-in; + &:hover { + fill: $red; + transition: color .3s ease-in; + } + } +} diff --git a/app/styles/modules/common/assigned-users.scss b/app/styles/modules/common/assigned-users.scss new file mode 100644 index 00000000..900cb4c2 --- /dev/null +++ b/app/styles/modules/common/assigned-users.scss @@ -0,0 +1,139 @@ +.ticket-assigned-users { + align-items: center; + border-bottom: 1px solid $gray-light; + border-top: 1px solid $gray-light; + margin-bottom: 1rem; + padding: .5rem 0; + position: relative; + + .loading-spinner { + @include loading-spinner; + margin: 1rem auto; + max-height: 2rem; + max-width: 2rem; + } + + .assigned-title { + @include font-size(small); + @include font-type(light); + color: $gray; + display: block; + margin: .2rem 0 .25rem; + } + + .tg-assigned-users { + align-items: center; + position: relative; + } + + .tg-add-assigned { + margin-top: .25rem; + + .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; + } + } + + .assigned-users-options { + align-content: center; + display: flex; + a { + margin-right: .2rem; + } + } + + .user-assigned, + .assign-to-me { + color: $primary; + &.editable { + color: $primary; + &:hover { + cursor: pointer; + } + } + .icon { + vertical-align: middle; + } + } + + .not-assigned-users { + align-items: center; + display: flex; + + .assigned-title { + @include font-size(small); + @include font-type(light); + color: $gray; + display: block; + margin: .2rem 0 .25rem; + } + .assigned-to { + flex-grow: 1; + margin-left: .5rem; + } + + .assigned-to-options { + align-content: center; + display: flex; + a { + margin-right: .2rem; + } + } + .user-assigned, + .assign-to-me { + color: $primary; + cursor: default; + &:hover { + cursor: pointer; + } + .icon { + fill: currentColor; + height: .75rem; + width: .75rem; + } + } + } + + .user-avatar { + flex-basis: 3rem; + flex-shrink: 0; + position: relative; + img { + width: 100%; + } + &.is-iocaine { + img { + filter: hue-rotate(150deg) saturate(200%); + } + } + .iocaine-symbol { + left: -.5rem; + position: absolute; + top: -.75rem; + z-index: 9; + svg { + background: $grayer; + border-radius: .25rem; + fill: $white; + min-height: 1.75rem; + min-width: 1.75rem; + padding: .25rem; + } + } + } +}