From 1e56104e581b7d64620b02433c88cb1a1d0bd131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Wed, 6 Jun 2018 13:50:59 +0200 Subject: [PATCH] Display user-defined due dates colors --- .../modules/admin/project-values.coffee | 88 +++++++++++- app/coffee/modules/common.coffee | 5 +- app/coffee/modules/resources.coffee | 11 +- app/coffee/modules/resources/issues.coffee | 5 + app/coffee/modules/resources/tasks.coffee | 5 + .../modules/resources/userstories.coffee | 6 +- app/locales/taiga/locale-en.json | 18 ++- .../card/card-templates/card-data.jade | 1 + .../header/detail-header.controller.coffee | 5 + .../detail/header/detail-header.jade | 1 + .../components/due-date/due-date-button.jade | 2 +- .../due-date/due-date-controller.coffee | 67 +++++---- .../due-date/due-date-controller.spec.coffee | 130 ++++++++++++++++++ .../components/due-date/due-date-icon.jade | 2 +- .../due-date-popover.directive.coffee | 1 + .../components/due-date/due-date-popover.jade | 2 +- .../due-date/due-date.directive.coffee | 1 + .../admin/admin-project-values-due-dates.jade | 18 +-- .../lb-create-edit-issue.jade | 1 + .../lb-create-edit-task.jade | 1 + .../lb-create-edit-us.jade | 1 + .../includes/components/backlog-row.jade | 1 + .../admin/project-due-date-status.jade | 57 ++++++-- .../includes/modules/issues-table.jade | 1 + app/partials/issue/issues-detail.jade | 1 + app/partials/task/related-task-row.jade | 1 + app/partials/task/task-detail.jade | 1 + app/partials/us/us-detail.jade | 1 + app/styles/modules/common/colors-table.scss | 43 ++++++ 29 files changed, 410 insertions(+), 67 deletions(-) create mode 100644 app/modules/components/due-date/due-date-controller.spec.coffee diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 4cbe826e..75698ac3 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -113,8 +113,8 @@ class ProjectValuesController extends taiga.Controller unwatch() loadValues: => return @rs[@scope.resource].listValues(@scope.projectId, @scope.type).then (values) => - @scope.values = values - @scope.maxValueOrder = _.maxBy(values, "order").order + if values.length + @scope.maxValueOrder = _.maxBy(values, "order").order return values moveValue: (ctx, itemValue, itemIndex) => @@ -130,6 +130,46 @@ class ProjectValuesController extends taiga.Controller module.controller("ProjectValuesController", ProjectValuesController) +############################################################################# +## Project due dates values Controller +############################################################################# + +class ProjectDueDatesValuesController extends ProjectValuesController + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "$tgConfirm", + "$tgResources", + ] + + loadValues: => + return @rs[@scope.resource].listValues(@scope.projectId, @scope.type).then (values) => + if values.length + @scope.maxValueOrder = _.maxBy(values, "order").order + @displayValues(values) + else + @createDefaultValues() + return values + + createDefaultValues: => + if !@rs[@scope.resource].createDefaultValues? + return + return @rs[@scope.resource].createDefaultValues(@scope.projectId, @scope.type).then (response) => + values = response.data + if values.length + @scope.maxValueOrder = _.maxBy(values, "order").order + @displayValues(values) + return values + + displayValues: (values) => + _.each values, (value, index) -> + value.days_to_due_abs = if value.days_to_due != null then Math.abs(value.days_to_due) else null + value.sign = if value.days_to_due >= 0 then 1 else -1 + @scope.values = values + +module.controller("ProjectDueDatesValuesController", ProjectDueDatesValuesController) + ############################################################################# ## Project values directive ############################################################################# @@ -288,6 +328,11 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra target = angular.element(event.currentTarget) saveValue(target) + $el.on "click", ".save", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + saveValue(target) + $el.on "click", ".cancel", (event) -> event.preventDefault() target = angular.element(event.currentTarget) @@ -332,6 +377,45 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", "tgProjectService", ProjectValuesDirective]) +############################################################################# +## Project due dates values directive +############################################################################# + +ProjectDueDatesValues = ($log, $repo, $confirm, $location, animationFrame, $translate, $rootscope, projectService) -> + parentDirective = ProjectValuesDirective($log, $repo, $confirm, $location, animationFrame, + $translate, $rootscope, projectService) + + linkDueDateStatusValue = ($scope, $el, $attrs) -> + _setDaysToDue = (value) -> + value.days_to_due = value.days_to_due_abs * value.sign + + _valueFromEventTarget = (event) -> + target = angular.element(event.currentTarget) + row = target.parents(".row.table-main") + formEl = target.parents("form") + return formEl.scope().value + + $el.on "input", ".days-to-due-abs", (event) -> + event.preventDefault() + value = _valueFromEventTarget(event) + $scope.$apply -> + _setDaysToDue(value) + + $el.on "click", ".days-to-due-sign", (event) -> + event.preventDefault() + value = _valueFromEventTarget(event) + $scope.$apply -> + value.sign = value.sign * -1 + _setDaysToDue(value) + + return { + link: ($scope, $el, $attrs) -> + parentDirective.link($scope, $el, $attrs) + linkDueDateStatusValue($scope, $el, $attrs) + } + +module.directive("tgProjectDueDatesValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", + "$translate", "$rootScope", "tgProjectService", ProjectDueDatesValues]) ############################################################################# ## Color selection directive diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index 4ca84b92..428331a6 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -376,7 +376,7 @@ module.directive("tgLightboxClose", [LightboxClose]) Svg = () -> template = """ - + {{svgTitle}} {{svgTitleTranslate | translate: svgTitleTranslateValues}} @@ -389,7 +389,8 @@ Svg = () -> svgIcon: "@", svgTitle: "@", svgTitleTranslate: "@", - svgTitleTranslateValues: "=" + svgTitleTranslateValues: "=", + svgFill: "=" }, template: template } diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 1696e79b..33ddb823 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -82,15 +82,18 @@ urls = { "project-transfer-request": "/projects/%s/transfer_request" "project-transfer-start": "/projects/%s/transfer_start" - # Project Values - Choises + # Project Values - Attributes "epic-statuses": "/epic-statuses" "userstory-statuses": "/userstory-statuses" - "userstory-due-date-statuses": "/userstory-duedates" + "userstory-due-dates": "/userstory-due-dates" + "userstory-due-dates-create-default": "/userstory-due-dates/create_default" "points": "/points" "task-statuses": "/task-statuses" - "task-due-date-statuses": "/task-due-date-statuses" + "task-due-dates": "/task-due-dates" + "task-due-dates-create-default": "/task-due-dates/create_default" "issue-statuses": "/issue-statuses" - "issue-due-date-statuses": "/issue-due-date-statuses" + "issue-due-dates": "/issue-due-dates" + "issue-due-dates-create-default": "/issue-due-dates/create_default" "issue-types": "/issue-types" "priorities": "/priorities" "severities": "/severities" diff --git a/app/coffee/modules/resources/issues.coffee b/app/coffee/modules/resources/issues.coffee index 1afbd5de..2e648c89 100644 --- a/app/coffee/modules/resources/issues.coffee +++ b/app/coffee/modules/resources/issues.coffee @@ -89,6 +89,11 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) -> service.storeQueryParams(projectId, params) return $repo.queryMany(type, params) + service.createDefaultValues = (projectId, type) -> + data = {"project_id": projectId} + url = $urls.resolve("#{type}-create-default") + return $http.post(url, data) + service.storeQueryParams = (projectId, params) -> ns = "#{projectId}:#{hashSuffix}" hash = generateHash([projectId, ns]) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index 62ce3e07..9070e0dd 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -94,6 +94,11 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params = {"project": projectId} return $repo.queryMany(type, params) + service.createDefaultValues = (projectId, type) -> + data = {"project_id": projectId} + url = $urls.resolve("#{type}-create-default") + return $http.post(url, data) + service.storeQueryParams = (projectId, params) -> ns = "#{projectId}:#{hashSuffix}" hash = generateHash([projectId, ns]) diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee index dd80a37a..4ea460f0 100644 --- a/app/coffee/modules/resources/userstories.coffee +++ b/app/coffee/modules/resources/userstories.coffee @@ -115,9 +115,13 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) -> service.listValues = (projectId, type) -> params = {"project": projectId} service.storeQueryParams(projectId, params) - console.log type return $repo.queryMany(type, params) + service.createDefaultValues = (projectId, type) -> + data = {"project_id": projectId} + url = $urls.resolve("#{type}-create-default") + return $http.post(url, data) + service.storeQueryParams = (projectId, params) -> ns = "#{projectId}:#{hashSuffix}" hash = generateHash([projectId, ns]) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 0e98986d..45790835 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -620,11 +620,16 @@ "SELECTED": "Selected" }, "PROJECT_DUE_DATE_STATUS": { - "TITLE": "Due dates statuses", - "SUBTITLE": "Specify the due date statuses your user stories, tasks and issues will go through", - "US_TITLE": "User Story Due Date Statuses", - "TASK_TITLE": "Task Due Date Statuses", - "ISSUE_TITLE": "Issue Due Date Statuses" + "TITLE": "Due Dates", + "SUBTITLE": "Specify the due dates your user stories, tasks and issues will go through if selected", + "US_TITLE": "User Story Due Date status", + "ACTION_ADD_STATUS": "Add new status", + "TASK_TITLE": "Task Due Date status", + "ISSUE_TITLE": "Issue Due Date status", + "DAYS_TO_DUE_DATE": "Days to due date", + "BEFORE_AFTER": "Before/after", + "BEFORE": "Before", + "AFTER": "After" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -726,7 +731,8 @@ "LABEL_SEVERITY": "Default value for severity selector" }, "STATUS": { - "PLACEHOLDER_WRITE_STATUS_NAME": "Write a name for the new status" + "PLACEHOLDER_WRITE_STATUS_NAME": "Write a name for the new status", + "PLACEHOLDER_DAYS_TO_DUE_DATE": "Write a number of days to due date" }, "TYPES": { "PLACEHOLDER_WRITE_NAME": "Write a name for the new element" diff --git a/app/modules/components/card/card-templates/card-data.jade b/app/modules/components/card/card-templates/card-data.jade index 1995f614..be9f028d 100644 --- a/app/modules/components/card/card-templates/card-data.jade +++ b/app/modules/components/card/card-templates/card-data.jade @@ -21,6 +21,7 @@ tg-due-date.statistic.card-due-date( due-date="vm.item.getIn(['model', 'due_date'])" is-closed="vm.item.getIn(['model', 'is_closed'])" + obj-type="task" ) .statistic.card-iocaine( ng-if="vm.item.getIn(['model', 'is_iocaine'])" diff --git a/app/modules/components/detail/header/detail-header.controller.coffee b/app/modules/components/detail/header/detail-header.controller.coffee index 2d2b5437..6702174e 100644 --- a/app/modules/components/detail/header/detail-header.controller.coffee +++ b/app/modules/components/detail/header/detail-header.controller.coffee @@ -32,6 +32,11 @@ class StoryHeaderController @.editMode = false @.loadingSubject = false @.originalSubject = @.item.subject + @.objType = { + 'tasks': 'task', + 'issues': 'issue', + 'userstories': 'us', + }[@.item._name] _checkNav: () -> if @.item.neighbors.previous?.ref? diff --git a/app/modules/components/detail/header/detail-header.jade b/app/modules/components/detail/header/detail-header.jade index 55e001d5..b254d1dc 100644 --- a/app/modules/components/detail/header/detail-header.jade +++ b/app/modules/components/detail/header/detail-header.jade @@ -23,6 +23,7 @@ due-date="vm.item.due_date" is-closed="vm.item.is_closed" ng-if="vm.item.due_date" + obj-type="{{ vm.objType }}" ) .edit-title-wrapper(ng-if="vm.editMode") input.edit-title-input.e2e-title-input( diff --git a/app/modules/components/due-date/due-date-button.jade b/app/modules/components/due-date/due-date-button.jade index b55c0efb..df250682 100644 --- a/app/modules/components/due-date/due-date-button.jade +++ b/app/modules/components/due-date/due-date-button.jade @@ -1,7 +1,7 @@ label.due-date-button.button-gray.is-editable( ng-if="vm.visible()" ng-disabled="vm.disabled()" - ng-class="vm.color()" + ng-style="{background: vm.color()}" ng-attr-title="{{ vm.title() }}" ng-click="vm.setDueDate()" ) diff --git a/app/modules/components/due-date/due-date-controller.coffee b/app/modules/components/due-date/due-date-controller.coffee index 8e344706..25cacb9b 100644 --- a/app/modules/components/due-date/due-date-controller.coffee +++ b/app/modules/components/due-date/due-date-controller.coffee @@ -22,9 +22,15 @@ class DueDateController "$translate" "tgLightboxFactory" "tgProjectService" + "$rootScope" ] - constructor: (@translate, @tgLightboxFactory, @projectService) -> + constructor: (@translate, @tgLightboxFactory, @projectService, @rootscope) -> + @.defaultConfig = [ + {"color": "#9dce0a", "name": "normal due", "days_to_due": null, "by_default": true}, + {"color": "#ff9900", "name": "due soon", "days_to_due": 14, "by_default": false}, + {"color": "#ff8a84", "name": "past due", "days_to_due": 0, "by_default": false} + ] visible: () -> return @.format == 'button' or @.dueDate? @@ -33,14 +39,7 @@ class DueDateController return @.isClosed color: () -> - colors = { - 'no_longer_applicable': 'closed', - 'due_soon': 'due-soon', - 'past_due': 'past-due', - 'set': 'due-set', - 'not_set': 'not-set', - } - return colors[@status()] or '' + return @.getStatus()?.color || null title: () -> if @.dueDate @@ -49,36 +48,48 @@ class DueDateController return @translate.instant('COMMON.DUE_DATE.TITLE_ACTION_SET_DUE_DATE') return '' - status: () -> + getStatus: (options) -> if !@.dueDate - return 'not_set' + return null project = @projectService.project.toJS() - project.due_soon_threshold = 14 # TODO get value from taiga-back + options = project["#{@.objType}_duedates"] + + if !options + options = @.defaultConfig + + return @._getAppearance(options) + + _getDefaultAppearance: (options) -> + defaultAppearance = null + _.map options, (option) -> + if option.by_default == true + defaultAppearance = option + return defaultAppearance + + _getAppearance: (options) -> + currentAppearance = @._getDefaultAppearance(options) + options = _.sortBy(options, (o) -> - o.days_to_due) # sort desc + dueDate = moment(@.dueDate) now = moment() + _.map options, (appearance) -> + if appearance.days_to_due == null + return + limitDate = moment(dueDate - moment.duration(appearance.days_to_due, "days")) + if now >= limitDate + currentAppearance = appearance - if @.isClosed - return 'no_longer_applicable' - else if now > dueDate - return 'past_due' - else if now.add(moment.duration(project.due_soon_threshold, "days")) >= dueDate - return 'due_soon' - return 'set' + return currentAppearance _formatTitle: () -> - titles = { - 'no_longer_applicable': 'COMMON.DUE_DATE.NO_LONGER_APPLICABLE', - 'due_soon': 'COMMON.DUE_DATE.DUE_SOON', - 'past_due': 'COMMON.DUE_DATE.PAST_DUE', - } prettyDate = @translate.instant("COMMON.PICKERDATE.FORMAT") formatedDate = moment(@.dueDate).format(prettyDate) - status = @status() - if not titles[status] - return formatedDate - return "#{formatedDate} (#{@translate.instant(titles[status])})" + status = @.getStatus() + if status?.name + return "#{formatedDate} (#{status.name})" + return formatedDate setDueDate: () -> return if @.disabled() diff --git a/app/modules/components/due-date/due-date-controller.spec.coffee b/app/modules/components/due-date/due-date-controller.spec.coffee new file mode 100644 index 00000000..daa2aac3 --- /dev/null +++ b/app/modules/components/due-date/due-date-controller.spec.coffee @@ -0,0 +1,130 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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 . +# +# File: color-selector.controller.spec.coffee +### + +describe "DueDate", -> + provide = null + controller = null + ctrl = null + mocks = {} + + dueDateConfig = { + 'us': [ + {"color": "#ff0000", "name": "past due", "days_to_due": 0, "by_default": false}, + {"color": "#00ff00", "name": "normal due", "days_to_due": null, "by_default": true} + {"color": "#333333", "name": "distant past due", "days_to_due": -7, "by_default": false} + {"color": "#ffff00", "name": "due soon", "days_to_due": 14, "by_default": false}, + ], + 'task': [ + {"color": "#ff0000", "name": "past due", "days_to_due": 0, "by_default": false}, + {"color": "#00ff00", "name": "normal due", "days_to_due": null, "by_default": true} + {"color": "#ffff00", "name": "due soon", "days_to_due": 3, "by_default": false} + ], + 'issue': [ + {"color": "#550000", "name": "past due", "days_to_due": 0, "by_default": false}, + {"color": "#00ff00", "name": "normal due", "days_to_due": null, "by_default": true}, + {"color": "#AAAA00", "name": "due soon", "days_to_due": 7, "by_default": false}, + {"color": "#333333", "name": "distant past due", "days_to_due": -10, "by_default": false} + ] + } + + _mockTranslate = () -> + mocks.$translate = { + instant: (index) -> + if index == "COMMON.PICKERDATE.FORMAT" + return "DD MMM YYYY" + return 'Set due date' + } + provide.value "$translate", mocks.$translate + + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: { + toJS: () -> {} + } + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgLightboxFactory = () -> + mocks.tgLightboxFactory = {} + provide.value "tgLightboxFactory", mocks.tgLightboxFactory + + _mockLog = () -> + mocks.log = { + error: (msg) -> return msg + } + provide.value "$log", mocks.log + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTranslate() + _mockTgLightboxFactory() + _mockTgProjectService() + _mockLog() + return null + + _inject = -> + inject ($controller) -> + controller = $controller + + beforeEach -> + module "taigaComponents" + _mocks() + _inject() + + describe "when is not set", -> + beforeEach -> + ctrl = controller "DueDateCtrl" + ctrl.dueDate = null + ctrl.format = 'button' + + it "get title", () -> + expect(ctrl.title()).to.be.eql('Set due date') + + it "get color", () -> + expect(ctrl.color()).to.be.eql(null) + + describe "when is set", -> + normalDue = ['normal due', '#9dce0a'] + dueSoon = ['due soon', '#ff8a84'] + pastDue = ['past due', '#ff9900'] + + runs = [ + { objType: 'us', days: -1, expect: pastDue }, + { objType: 'us', days: 15, expect: normalDue }, + { objType: 'us', days: 2, expect: dueSoon }, + { objType: 'task', days: -3, expect: pastDue }, + { objType: 'task', days: 18, expect: normalDue }, + { objType: 'task', days: 5, expect: dueSoon }, + { objType: 'issue', days: -5, expect: pastDue }, + { objType: 'issue', days: 20, expect: normalDue }, + { objType: 'issue', days: 8, expect: dueSoon }, + ] + + beforeEach -> + ctrl = controller "DueDateCtrl" + ctrl.format = 'button' + + runs.forEach (run) -> + it "get appearance in #{run.objType} view with #{run.days} days left", () -> + ctrl.objType = run.objType + ctrl.dueDate = moment().add(moment.duration(run.days, "days")) + formatedDate = ctrl.dueDate.format('DD MMM YYYY') + expect(ctrl.title()).to.be.eql("#{formatedDate} (#{run.expect[0]})") + expect(ctrl.color()).to.be.eql(run.expect[1]) diff --git a/app/modules/components/due-date/due-date-icon.jade b/app/modules/components/due-date/due-date-icon.jade index fe74740d..c843be10 100644 --- a/app/modules/components/due-date/due-date-icon.jade +++ b/app/modules/components/due-date/due-date-icon.jade @@ -1,6 +1,6 @@ tg-svg.due-date-icon( ng-if="vm.visible()" svg-icon="icon-clock" - ng-class="vm.color()" + svg-fill="vm.color()" ng-attr-title="{{ vm.title() }}" ) \ No newline at end of file diff --git a/app/modules/components/due-date/due-date-popover.directive.coffee b/app/modules/components/due-date/due-date-popover.directive.coffee index 3088cc9e..36a67c32 100644 --- a/app/modules/components/due-date/due-date-popover.directive.coffee +++ b/app/modules/components/due-date/due-date-popover.directive.coffee @@ -62,6 +62,7 @@ dueDatePopoverDirective = ($translate, datePickerConfigService) -> dueDate: '=', isClosed: '=', item: '=', + objType: '@', format: '@', notAutoSave: '=' } diff --git a/app/modules/components/due-date/due-date-popover.jade b/app/modules/components/due-date/due-date-popover.jade index e6cd616f..1484c380 100644 --- a/app/modules/components/due-date/due-date-popover.jade +++ b/app/modules/components/due-date/due-date-popover.jade @@ -1,7 +1,7 @@ .due-date-button-wrapper label.due-date-button.button-gray.is-editable.date-picker-popover-trigger( ng-disabled="vm.disabled()" - ng-class="vm.color()" + ng-style="{background: vm.color()}" ng-attr-title="{{ vm.title() }}" ) tg-svg(svg-icon="icon-clock") diff --git a/app/modules/components/due-date/due-date.directive.coffee b/app/modules/components/due-date/due-date.directive.coffee index 600f7664..e54ca83f 100644 --- a/app/modules/components/due-date/due-date.directive.coffee +++ b/app/modules/components/due-date/due-date.directive.coffee @@ -70,6 +70,7 @@ dueDateDirective = ($translate, datePickerConfigService) -> dueDate: '=', isClosed: '=', item: '=', + objType: '@', format: '@', notAutoSave: '=' } diff --git a/app/partials/admin/admin-project-values-due-dates.jade b/app/partials/admin/admin-project-values-due-dates.jade index 4d99fdf2..f944769b 100644 --- a/app/partials/admin/admin-project-values-due-dates.jade +++ b/app/partials/admin/admin-project-values-due-dates.jade @@ -15,20 +15,20 @@ div.wrapper(ng-controller="ProjectValuesSectionController", include ../includes/components/mainTitle p.admin-subtitle(translate="ADMIN.PROJECT_DUE_DATE_STATUS.SUBTITLE") - div.admin-attributes-section(tg-project-values, type="userstory-due-date-statuses", - ng-controller="ProjectValuesController as ctrl", - ng-init="section='admin'; resource='userstories'; type='userstory-due-date-statuses'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.US_TITLE'", + div.admin-attributes-section(tg-project-due-dates-values, type="userstory-due-dates", + ng-controller="ProjectDueDatesValuesController as ctrl", + ng-init="section='admin'; resource='userstories'; type='userstory-due-dates'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.US_TITLE'", objName="status") include ../includes/modules/admin/project-due-date-status - div.admin-attributes-section(tg-project-values, type="task-due-date-statuses", - ng-controller="ProjectValuesController as ctrl", - ng-init="section='admin'; resource='tasks'; type='task-due-date-statuses'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.TASK_TITLE'" + div.admin-attributes-section(tg-project-due-dates-values, type="task-due-dates", + ng-controller="ProjectDueDatesValuesController as ctrl", + ng-init="section='admin'; resource='tasks'; type='task-due-dates'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.TASK_TITLE'" objName="status") include ../includes/modules/admin/project-due-date-status - div.admin-attributes-section(tg-project-values, type="issue-due-date-statuses", - ng-controller="ProjectValuesController as ctrl", - ng-init="section='admin'; resource='issues'; type='issue-due-date-statuses'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.ISSUE_TITLE'", + div.admin-attributes-section(tg-project-due-dates-values, type="issue-due-dates", + ng-controller="ProjectDueDatesValuesController as ctrl", + ng-init="section='admin'; resource='issues'; type='issue-due-dates'; sectionName='ADMIN.PROJECT_DUE_DATE_STATUS.ISSUE_TITLE'", objName="status") include ../includes/modules/admin/project-due-date-status diff --git a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-issue.jade b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-issue.jade index 0edcc1f5..fd566c01 100644 --- a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-issue.jade +++ b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-issue.jade @@ -22,6 +22,7 @@ div.ticket-detail-settings due-date="obj.due_date" is-closed="obj.is_closed" item="obj" + obj-type="issue" not-auto-save="true" ) div diff --git a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-task.jade b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-task.jade index 713c825d..dc13ecef 100644 --- a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-task.jade +++ b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-task.jade @@ -8,6 +8,7 @@ div.ticket-detail-settings due-date="obj.due_date" is-closed="obj.is_closed" item="obj" + obj-type="task" not-auto-save="true" ) div diff --git a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-us.jade b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-us.jade index af33d1fa..d2b0bbcd 100644 --- a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-us.jade +++ b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit-us.jade @@ -11,6 +11,7 @@ div.ticket-detail-settings due-date="obj.due_date" is-closed="obj.is_closed" item="obj" + obj-type="task" not-auto-save="true" ) div diff --git a/app/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index 817d0ca7..1b9ac88d 100644 --- a/app/partials/includes/components/backlog-row.jade +++ b/app/partials/includes/components/backlog-row.jade @@ -31,6 +31,7 @@ due-date="us.due_date" is-closed="us.is_closed" ng-if="us.due_date" + obj-type="us" ) tg-belong-to-epics( format="pill" diff --git a/app/partials/includes/modules/admin/project-due-date-status.jade b/app/partials/includes/modules/admin/project-due-date-status.jade index a31b1235..23f68f5e 100644 --- a/app/partials/includes/modules/admin/project-due-date-status.jade +++ b/app/partials/includes/modules/admin/project-due-date-status.jade @@ -1,15 +1,15 @@ section.colors-table.admin-status-table div.project-values-title h2 {{ sectionName | translate }} - a.button.button-gray.show-add-new(href="", title="{{'ADMIN.US_STATUS.ACTION_ADD_STATUS' | translate}}") - span(translate="ADMIN.US_STATUS.ACTION_ADD_STATUS") + a.button.button-gray.show-add-new(href="", title="{{'ADMIN.PROJECT_DUE_DATE_STATUS.ACTION_ADD_STATUS' | translate}}") + span(translate="ADMIN.PROJECT_DUE_DATE_STATUS.ACTION_ADD_STATUS") div.table-header div.row div.color-column(translate="COMMON.FIELDS.COLOR") div.status-name(translate="COMMON.FIELDS.NAME") - div.status-slug(translate="COMMON.FIELDS.SLUG") - div.thresold-column(translate="COMMON.FIELDS.THRESHOLD") + div.thresold-column(translate="ADMIN.PROJECT_DUE_DATE_STATUS.DAYS_TO_DUE_DATE") + div.before-after-column(translate="ADMIN.PROJECT_DUE_DATE_STATUS.BEFORE_AFTER") div.options-column div.table-main @@ -24,11 +24,13 @@ section.colors-table.admin-status-table div.status-name span {{ value.name }} - div.status-slug - span {{ value.slug }} + div.thresold-column + span(ng-if="value.days_to_due_abs != null") {{ value.days_to_due_abs }} - div.is-closed-column - span {{ value.days_to_due }} + div.before-after-column + span(ng-if="value.days_to_due_abs", ng-switch="value.days_to_due >= 0") + span(ng-switch-when="true") {{ 'ADMIN.PROJECT_DUE_DATE_STATUS.BEFORE' | translate }} + span(ng-switch-when="false") {{ 'ADMIN.PROJECT_DUE_DATE_STATUS.AFTER' | translate }} div.options-column a.edit-value(href="") @@ -36,7 +38,7 @@ section.colors-table.admin-status-table title="{{'ADMIN.COMMON.TITLE_ACTION_EDIT_VALUE' | translate}}", svg-icon="icon-edit" ) - a.delete-value(href="") + a.delete-value(href="", ng-if="!value.by_default") tg-svg( title="{{'ADMIN.COMMON.TITLE_ACTION_DELETE_VALUE' | translate}}" svg-icon="icon-trash" @@ -60,8 +62,33 @@ section.colors-table.admin-status-table data-maxlength="255" ) - div.thresold-column - input(type="number", name="days_to_due", ng-model="value.days_to_due") + div.thresold-column(ng-if="!value.by_default") + input.days-to-due-abs(type="number", min="0", name="days_to_due_abs", ng-model="value.days_to_due_abs") + + div.before-after-column + div.before-after-selector(ng-if="!value.by_default && value.days_to_due_abs != 0") + .before-after-selector-single(ng-class="{'checked': value.sign > 0 }") + input( + type='radio' + id="due-date-status-before" + name="due-date-status-after" + ng-model="value.sign" + value="1" + ) + label.days-to-due-sign( + for="due-date-status-before", + ) {{ 'ADMIN.PROJECT_DUE_DATE_STATUS.BEFORE' | translate }} + .before-after-selector-single(ng-class="{'checked': value.sign < 0 }") + input( + type='radio' + id="due-date-status-after" + name="due-date-status-after" + ng-model="value.sign" + value="-1" + ) + label.days-to-due-sign( + for="due-date-status-after", + ) {{ 'ADMIN.PROJECT_DUE_DATE_STATUS.AFTER' | translate }} div.options-column a.save.e2e-save(href="", title="{{'COMMON.SAVE' | translate}}") @@ -90,7 +117,13 @@ section.colors-table.admin-status-table ) div.thresold-column - input(type="number") + input( + name="days_to_due" + type="number" + placeholder="{{'ADMIN.STATUS.PLACEHOLDER_DAYS_TO_DUE_DATE' | translate}}" + ng-model="newValue.days_to_due" + data-required="true" + ) div.options-column a.add-new.e2e-save(href="", title="{{'COMMON.ADD' | translate}}") diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 161b9c4c..f9aa3f5d 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -58,6 +58,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") due-date="issue.due_date" is-closed="us.is_closed" ng-if="issue.due_date" + obj-type="issue" ) diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 5e709831..632b24df 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -127,6 +127,7 @@ div.wrapper( format="button" is-closed="issue.is_closed" item="issue" + obj-type="issue" tg-check-permission="modify_issue" ) tg-promote-issue-to-us-button( diff --git a/app/partials/task/related-task-row.jade b/app/partials/task/related-task-row.jade index 11051648..e2fab2e2 100644 --- a/app/partials/task/related-task-row.jade +++ b/app/partials/task/related-task-row.jade @@ -5,6 +5,7 @@ span(ng-non-bindable) <%= emojify(task.subject) %> tg-due-date( due-date="task.due_date" + obj-type="task" ng-if="task.due_date" ) .task-settings diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index d96a3299..f23a204b 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -107,6 +107,7 @@ div.wrapper( format="button" is-closed="task.is_closed" item="task" + obj-type="task" tg-check-permission="modify_task" ) tg-task-is-iocaine-button(ng-model="task") diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 85563ba5..ce32b7e6 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -132,6 +132,7 @@ div.wrapper( format="button" is-closed="us.is_closed" item="us" + obj-type="us" tg-check-permission="modify_us" ) tg-us-team-requirement-button(ng-model="us") diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index 902fa371..f250c0cf 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -58,11 +58,17 @@ .is-archived-column, .is-closed-column, .options-column, + .thresold-column, + .before-after-column, .status-wip-limit { flex-basis: 100px; flex-grow: 1; flex-shrink: 0; } + .before-after-column { + padding: 0 10px; + } + .status-name, .color-name { flex-basis: 150px; @@ -115,6 +121,43 @@ } } + + .before-after-selector { + display: flex; + font-size: .9rem; + input { + display: none; + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: .5rem .25rem; + text-align: center; + text-transform: uppercase; + transition: background .2s ease-in; + } + +label:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } + } + .before-after-selector-single { + flex: 1; + margin-right: .25rem; + &:last-child { + margin-right: 0; + } + &.checked label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + } + &.checked label:hover { + background: $primary-light; + } + } + } + .options-column { a { cursor: pointer;