diff --git a/frontend/src/app/modules/grids/openproject-grids.module.ts b/frontend/src/app/modules/grids/openproject-grids.module.ts index f0dd7f06e7..54df0a45fd 100644 --- a/frontend/src/app/modules/grids/openproject-grids.module.ts +++ b/frontend/src/app/modules/grids/openproject-grids.module.ts @@ -63,6 +63,7 @@ import {WidgetTimeEntriesProjectComponent} from "core-app/modules/grids/widgets/ import {WidgetSubprojectsComponent} from "core-app/modules/grids/widgets/subprojects/subprojects.component"; import {OpenprojectAttachmentsModule} from "core-app/modules/attachments/openproject-attachments.module"; import {WidgetMembersComponent} from "core-app/modules/grids/widgets/members/members.component"; +import {WidgetProjectStatusComponent} from "core-app/modules/grids/widgets/project-status/project-status.component"; @NgModule({ imports: [ @@ -88,6 +89,7 @@ import {WidgetMembersComponent} from "core-app/modules/grids/widgets/members/mem WidgetWpOverviewComponent, WidgetProjectDescriptionComponent, WidgetProjectDetailsComponent, + WidgetProjectStatusComponent, WidgetSubprojectsComponent, WidgetTimeEntriesCurrentUserComponent, WidgetTimeEntriesProjectComponent]), @@ -121,6 +123,7 @@ import {WidgetMembersComponent} from "core-app/modules/grids/widgets/members/mem WidgetWpGraphComponent, WidgetProjectDescriptionComponent, WidgetProjectDetailsComponent, + WidgetProjectStatusComponent, WidgetSubprojectsComponent, WidgetTimeEntriesCurrentUserComponent, WidgetTimeEntriesProjectComponent, @@ -317,6 +320,14 @@ export function registerWidgets(injector:Injector) { name: i18n.t('js.grid.widgets.project_details.title') } }, + { + identifier: 'project_status', + component: WidgetProjectStatusComponent, + title: i18n.t(`js.grid.widgets.project_status.title`), + properties: { + name: i18n.t('js.grid.widgets.project_status.title') + } + }, { identifier: 'subprojects', component: WidgetSubprojectsComponent, diff --git a/frontend/src/app/modules/grids/widgets/project-status/project-status.component.html b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.html new file mode 100644 index 0000000000..e24b198436 --- /dev/null +++ b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.html @@ -0,0 +1,29 @@ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/modules/grids/widgets/project-status/project-status.component.sass b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.sass new file mode 100644 index 0000000000..fc00d2d233 --- /dev/null +++ b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.sass @@ -0,0 +1,83 @@ +.traffic-lights + width: 40px + height: 100px + padding: 5px + border: 1px solid gray + border-radius: 2px + +.light + width: 30px + height: 30px + padding: 5px + &:hover, + &.-active + padding: 0px + animation-name: shrink-padding + animation-duration: 200ms + &.-active + .-green + background-color: green + .-yellow + background-color: orange + .-red + background-color: red + +.bulb + width: 20px + height: 20px + border-radius: 50% + border-width: 1px + border-style: solid + &.-green + border-color: green + &.-yellow + border-color: orange + &.-red + border-color: red + +.light + &:hover, + &.-active + .bulb + width: 30px + height: 30px + border-width: 4px + animation-name: increase-size + animation-duration: 200ms + +.current-status + margin-top: 10px + +.content-grid + display: flex + flex-direction: row + width: 100% + +.left-column + flex: 1 + flex-direction: column + flex-basis: content + max-width: 100px + padding-right: 20px + +.right-column + flex: 2 + flex-direction: column + +@keyframes shrink-padding + from + padding: 5px + to + padding: 0px + +@keyframes increase-size + from + width: 20px + height: 20px + border-width: 1px + background-color: transparent + to + width: 30px + height: 30px + border-width: 4px + background-color: transparent diff --git a/frontend/src/app/modules/grids/widgets/project-status/project-status.component.ts b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.ts new file mode 100644 index 0000000000..80482880a9 --- /dev/null +++ b/frontend/src/app/modules/grids/widgets/project-status/project-status.component.ts @@ -0,0 +1,115 @@ +// -- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See doc/COPYRIGHT.rdoc for more details. +// ++ + +import {Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Injector, ViewChild, ElementRef} from '@angular/core'; +import {AbstractWidgetComponent} from "app/modules/grids/widgets/abstract-widget.component"; +import {I18nService} from "core-app/modules/common/i18n/i18n.service"; +import {ProjectDmService} from "core-app/modules/hal/dm-services/project-dm.service"; +import {CurrentProjectService} from "core-components/projects/current-project.service"; +import {SchemaResource} from "core-app/modules/hal/resources/schema-resource"; +import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; +import {PortalCleanupService} from 'core-app/modules/fields/display/display-portal/portal-cleanup.service'; +import {WorkPackageViewHighlightingService} from "core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-highlighting.service"; +import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; +import {ProjectCacheService} from "core-components/projects/project-cache.service"; + +export const emptyPlaceholder = '-'; + +@Component({ + templateUrl: './project-status.component.html', + styleUrls: ['./project-status.component.sass'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + // required by the displayField service to render the fields + PortalCleanupService, + WorkPackageViewHighlightingService, + IsolatedQuerySpace + ] +}) +export class WidgetProjectStatusComponent extends AbstractWidgetComponent implements OnInit { + @ViewChild('contentContainer', { static: true }) readonly contentContainer:ElementRef; + + public currentStatusCode:String = 'not_set'; + public explanation:String = ''; + public availableStatuses:any = { + on_track: this.i18n.t('js.grid.widgets.project_status.on_track'), + off_track: this.i18n.t('js.grid.widgets.project_status.off_track'), + at_risk: this.i18n.t('js.grid.widgets.project_status.at_risk'), + not_set: this.i18n.t('js.grid.widgets.project_status.not_set') + }; + + constructor(protected readonly i18n:I18nService, + protected readonly injector:Injector, + protected readonly projectDm:ProjectDmService, + protected readonly projectCache:ProjectCacheService, + protected readonly currentProject:CurrentProjectService, + protected readonly cdr:ChangeDetectorRef) { + super(i18n, injector); + } + + ngOnInit() { + this.loadAndRender(); + } + + public get isEditable() { + return false; + } + + private loadAndRender() { + Promise.all( + [this.loadCurrentProject(), + this.loadProjectSchema()] + ) + .then(([project, schema]) => { + if (project.status) { + this.currentStatusCode = project.status.code; + this.explanation = project.status.explanation; + } else { + this.currentStatusCode = 'not_set'; + this.explanation = ''; + } + this.redraw(); + }); + } + + private loadCurrentProject() { + return this.projectCache.require(this.currentProject.id as string); + } + + public get isLoaded() { + return this.projectCache.state(this.currentProject.id as string).value; + } + + private loadProjectSchema() { + return this.projectDm.schema(); + } + + private redraw() { + this.cdr.detectChanges(); + } +} diff --git a/modules/grids/config/locales/js-en.yml b/modules/grids/config/locales/js-en.yml index 1c317f6191..ded27f5e40 100644 --- a/modules/grids/config/locales/js-en.yml +++ b/modules/grids/config/locales/js-en.yml @@ -28,6 +28,12 @@ en: project_details: title: 'Project details' no_results: 'No custom fields have been defined for projects.' + project_status: + title: 'Project status' + on_track: 'On track' + off_track: 'Off track' + at_risk: 'At risk' + not_set: 'Not set' subprojects: title: 'Subprojects' no_results: 'No subprojects.' diff --git a/modules/grids/lib/grids/configuration/in_project_base_registration.rb b/modules/grids/lib/grids/configuration/in_project_base_registration.rb index 6a1ed85ee0..4ed219b0b2 100644 --- a/modules/grids/lib/grids/configuration/in_project_base_registration.rb +++ b/modules/grids/lib/grids/configuration/in_project_base_registration.rb @@ -3,6 +3,7 @@ module Grids::Configuration widgets 'work_packages_table', 'work_packages_graph', 'project_description', + 'project_status', 'project_details', 'subprojects', 'work_packages_calendar', diff --git a/modules/overviews/lib/overviews/grid_registration.rb b/modules/overviews/lib/overviews/grid_registration.rb index 163252a26c..9f7e85ed80 100644 --- a/modules/overviews/lib/overviews/grid_registration.rb +++ b/modules/overviews/lib/overviews/grid_registration.rb @@ -9,7 +9,7 @@ module Overviews defaults -> { { - row_count: 2, + row_count: 3, column_count: 2, widgets: [ { @@ -22,6 +22,16 @@ module Overviews name: I18n.t('js.grid.widgets.project_description.title') } }, + { + identifier: 'project_status', + start_row: 1, + end_row: 2, + start_column: 2, + end_column: 3, + options: { + name: I18n.t('js.grid.widgets.project_status.title') + } + }, { identifier: 'project_details', start_row: 2, @@ -34,9 +44,9 @@ module Overviews }, { identifier: 'work_packages_overview', - start_row: 1, - end_row: 2, - start_column: 2, + start_row: 3, + end_row: 4, + start_column: 1, end_column: 3, options: { name: I18n.t('js.grid.widgets.work_packages_overview.title')