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')