Basic read only project status widget

pull/7722/head
Wieland Lindenthal 5 years ago
parent 57131fa32b
commit b533bbd70a
  1. 11
      frontend/src/app/modules/grids/openproject-grids.module.ts
  2. 29
      frontend/src/app/modules/grids/widgets/project-status/project-status.component.html
  3. 83
      frontend/src/app/modules/grids/widgets/project-status/project-status.component.sass
  4. 115
      frontend/src/app/modules/grids/widgets/project-status/project-status.component.ts
  5. 6
      modules/grids/config/locales/js-en.yml
  6. 1
      modules/grids/lib/grids/configuration/in_project_base_registration.rb
  7. 18
      modules/overviews/lib/overviews/grid_registration.rb

@ -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,

@ -0,0 +1,29 @@
<widget-header
[name]="widgetName"
[icon]="'info1'"
[editable]="isEditable">
<widget-menu
[resource]="resource">
</widget-menu>
</widget-header>
<div class="grid--widget-content">
<div class="content-grid">
<div class="left-column">
<div class="traffic-lights">
<div class="light" [ngClass]="{'-active':currentStatusCode === 'off_track'}">
<div class="bulb -red" [attr.title]="availableStatuses['off_track']"></div>
</div>
<div class="light" [ngClass]="{'-active':currentStatusCode === 'at_risk'}" >
<div class="bulb -yellow" [attr.title]="availableStatuses['at_risk']"></div>
</div>
<div class="light" [ngClass]="{'-active':currentStatusCode === 'on_track'}">
<div class="bulb -green -active" [attr.title]="availableStatuses['on_track']"></div>
</div>
</div>
<div class="current-status" [innerHtml]="availableStatuses[currentStatusCode]"></div>
</div>
<div class="right-column" [innerHtml]="explanation"></div>
</div>
</div>

@ -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

@ -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();
}
}

@ -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.'

@ -3,6 +3,7 @@ module Grids::Configuration
widgets 'work_packages_table',
'work_packages_graph',
'project_description',
'project_status',
'project_details',
'subprojects',
'work_packages_calendar',

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

Loading…
Cancel
Save