VRTKSSUP-229
Осьмаков Денис 5 years ago
parent 126add0d5b
commit 1d7ea3c156
  1. 123
      src/webparts/summaryWebPart/components/RejectTasks/RejectTasks.module.scss
  2. 203
      src/webparts/summaryWebPart/components/RejectTasks/RejectTasks.tsx
  3. 1
      src/webparts/summaryWebPart/components/Structure/Structure.module.scss
  4. 61
      src/webparts/summaryWebPart/components/Structure/Structure.tsx
  5. 49
      src/webparts/summaryWebPart/components/SummaryContainer/SummaryContainer.tsx
  6. 35
      src/webparts/summaryWebPart/components/SummaryGlobalComponent/SummaryGlobalComponent.module.scss
  7. 25
      src/webparts/summaryWebPart/components/SummaryGlobalComponent/SummaryGlobalComponent.tsx
  8. 76
      src/webparts/summaryWebPart/components/SummaryWebPart.module.scss
  9. 25
      src/webparts/summaryWebPart/components/SummaryWebPart.tsx
  10. 64
      src/webparts/summaryWebPart/components/TaskStatusesChart/TaskStatusesChart.tsx
  11. 56
      src/webparts/summaryWebPart/components/TrafficLight/TrafficLight.tsx
  12. 4
      src/webparts/summaryWebPart/interfaces/IDeviationTasks.ts
  13. 1
      src/webparts/summaryWebPart/interfaces/IPageData.ts

@ -0,0 +1,123 @@
.commonTable {
/* min-width: 361px;
max-width: 361px;
width: 361px; */
width: 100%;
border-bottom: 1px solid #c8c8c8;
.thead {
display: flex;
.th {
padding: 6px;
border-top: 1px solid #c8c8c8;
border-left: 1px solid #c8c8c8;
font-family: "Segoe UI Web (West European)",Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif;
width: 100%;
color: #767676;
font-size: 13px;
font-weight: bold;
transition: 0.5s;
cursor: pointer;
&:hover {
background: #f4f4f4;
color: #666666;
transition: 0.5s;
}
}
.Departments {
min-width: 123px !important;
}
.Managers {
min-width: 116px !important;
}
.Members {
min-width: 116px !important;
}
.count {
min-width: 46px !important;
max-width: 46px !important;
border-right: 1px solid #c8c8c8;
}
}
.tbody {
display: flex;
.td {
border-top: 1px solid #c8c8c8;
border-left: 1px solid #c8c8c8;
color: #767676;
background: #ffffff;
font-size: 13px;
width: 100%;
font-family: "Segoe UI Web (West European)",Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif;
transition: 0.5s;
cursor: pointer;
.cell {
padding: 3px 5px;
color: #767676;
border-bottom: 1px solid #c8c8c8;
display: flex;
align-items: center;
justify-content: center;
/* max-width: 100px; */
&:hover {
background: #f4f4f4;
color: #666666;
transition: 0.5s;
}
span {
/* white-space: nowrap; */
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
}
.Departments {
width: 124px !important;
margin-left: 1px;
}
.Managers {
min-width: 106px !important;
}
.Members {
min-width: 106px !important;
}
.isCrit {
background: #ff000042;
&:hover {
background: #ff0000ab;
color: #4b4b4b;
transition: 0.5s;
}
}
.count {
min-width: 47px !important;
}
.cell:last-child {
border-bottom: none;
}
div {
text-align: center;
vertical-align: center;
}
.dividingLine{
border-bottom: 1px solid #c8c8c8;
}
}
.MembersTaskCount {
width: 58px;
max-width: 58px !important;
min-width: 58px !important;
border-right: 1px solid #c8c8c8;
}
}
}
.dividingLine{
border-bottom: 1px solid #c8c8c8;
}

@ -0,0 +1,203 @@
import * as React from 'react';
/* import styles from '../SummaryGlobalComponent/SummaryGlobalComponent.module.scss' */
import { DetailsList, SelectionMode, DetailsListLayoutMode } from 'office-ui-fabric-react/lib/DetailsList';
import { DeviationTasks } from '../../interfaces/IDeviationTasks';
import styles from '../RejectTasks/RejectTasks.module.scss'
export interface IComponentProps {
structureData: DeviationTasks[]
}
export interface IComponentState {
}
export default class TrafficLight extends React.Component<IComponentProps, IComponentState> {
private _renderHeaderCell(): JSX.Element {
return (
<div className={styles.thead}>
<div className={styles.th + ' ' + styles.Departments}>
Подразделение
</div>
<div className={styles.th + ' ' + styles.Managers}>
Руководитель
</div>
<div className={styles.th + ' ' + styles.Members}>
Участник
</div>
<div className={styles.count + ' ' + styles.th}>
Кол-во задач
</div>
</div>
)
}
private _cell(value:string,length:any,className:string,crit?:boolean,): JSX.Element {
// 7 px это 1px border и padding 3px
var height = 'calc('+ 100/length + '% - 7px)'
var critClassName = ''
if(crit) {
critClassName = 'isCrit'
}
return (
<div className={styles.cell + ' ' + styles[className] + ' ' + styles[critClassName]} style={{height}}><span>{value}</span></div>
)
}
private _renderCell(structureData:any,i:number): JSX.Element {
var Departments = [] //Подразделения
var Managers = [] //Руководители
var Members = [] //Участники
var MembersTaskCount = [] //Кол-во задач
if(structureData){
if(structureData.Departments.length){
// пушу в массив Departments ячейки
Departments.push(this._cell(structureData.Departments[i].Name,1 , 'Departments'))
if(structureData.Departments[i].Managers){
for (let index = 0; index < structureData.Departments[i].Managers.length; index++) {
let menagers = structureData.Departments[i].Managers[index].Name
if(menagers){
//Сокращаю Имена и Отчества Пример Масловский Артем Андреевич Масловский А.А.
let ManagersName = structureData.Departments[i].Managers[index].Name.split(' ')
let name = ManagersName[1].split('')[0] + '.'
let surname = ManagersName[0]
if(ManagersName[2]){
let patronymic = ManagersName[1].split('')[0] + '.'
ManagersName = surname + ' ' + name + ' ' + patronymic
} else {
ManagersName = surname + ' ' + name
}
let maxLength = 0
structureData.Departments[i].Managers.forEach(function(e) {
maxLength = maxLength + e.Members.length
})
//Переменная для расчета высоты ячейки менеджеров.
let length = maxLength/structureData.Departments[i].Managers[index].Members.length
//Пушу ячейки
Managers.push(this._cell( ManagersName ,length,'Managers'))
} else {
Managers.push(this._cell( '' ,structureData.Departments[i].Managers[index].length,'Managers'))
}
if(structureData.Departments[i].Managers[index].Members.length){
for (let counter = 0; counter < structureData.Departments[i].Managers[index].Members.length; counter++) {
let MembersName = structureData.Departments[i].Managers[index].Members[counter].Name
if(MembersName) {
//Сокращаю Имена и Отчества Пример Масловский Артем Андреевич Масловский А.А.
MembersName = MembersName.split(' ');
let name = MembersName[1].split('')[0] + '.';
let surname = MembersName[0];
if(MembersName[2]){
MembersName[2].split('')[0];
let patronymic = MembersName[2].split('')[0] + '.';
MembersName = surname + ' ' + name + ' ' + patronymic;
} else {
MembersName = surname + ' ' + name
}
}
//Пушу ячейки
Members.push(
this._cell( MembersName ,structureData.Departments[i].Managers[index].length,'Members',
structureData.Departments[i].Managers[index].Members[counter].Crit)
)
//Пушу ячейки
MembersTaskCount.push(
this._cell(structureData.Departments[i].Managers[index].Members[counter].TaskCount ,structureData.Departments[i].Managers[index].length,'count',
structureData.Departments[i].Managers[index].Members[counter].Crit)
)
}
}
}
}
}
}
return (
<div className={styles.tbody}>
<div className={styles.td}>
{
Departments
}
</div>
<div className={styles.td}>
{
Managers
}
</div>
<div className={styles.td}>
{
Members
}
</div>
<div className={styles.MembersTaskCount + ' ' + styles.td}>
{
MembersTaskCount
}
</div>
</div>
)
}
private _renderBodyTable(structureData:any,): JSX.Element {
var body = []
for (let i = 0; i < structureData.Departments.length; i++) {
body.push(this._renderCell(structureData, i))
}
return (
<div>{body}</div>
)
}
componentDidMount(){
}
public onRenderFooter = () =>{
}
public render(): React.ReactElement<IComponentProps> {
return (
<div style={{display: 'flex', justifyContent: 'center',minWidth:' 360px'}}>
<div>
<div className={styles.commonTable}>
<div>
{this._renderHeaderCell()}
</div>
<div>
{
this._renderBodyTable(this.props.structureData)
}
</div>
</div>
</div>
</div>
);
}
}

@ -1,7 +1,11 @@
import * as React from 'react';
import styles from '../SummaryGlobalComponent/SummaryGlobalComponent.module.scss'
/* import styles from '../SummaryGlobalComponent/SummaryGlobalComponent.module.scss' */
import { DetailsList, SelectionMode, DetailsListLayoutMode } from 'office-ui-fabric-react/lib/DetailsList';
import { IMainExecItem } from '../../interfaces/IMainExecItem'
import { IMainExecItem } from '../../interfaces/IMainExecItem';
import styles from '../SummaryWebPart.module.scss'
import { FontSizes } from '@uifabric/styling/lib';
import { DetailsHeader } from 'office-ui-fabric-react/lib/components/DetailsList/DetailsHeader';
export interface IComponentProps {
structureData: IMainExecItem[]
@ -34,27 +38,68 @@ export default class TrafficLight extends React.Component<IComponentProps, IComp
this.setState({items: rows});
}
public onRenderFooter = () =>{
public renderHeader = (detailsHeaderProps) => {
return (
<DetailsHeader
{...detailsHeaderProps}
onRenderColumnHeaderTooltip={this.renderCustomHeaderTooltip}
/>
);
}
public renderCustomHeaderTooltip = (tooltipHostProps) => {
return (
<span
style={{
fontSize: "13px",
}}
>
{tooltipHostProps.children}
</span>
);
}
public render(): React.ReactElement<IComponentProps> {
const { items } = this.state;
if(items){
items.forEach(function(e) {
var name = ''
var surname = ''
var patronymic = ''
if(e.name) {
name = e.name.split(' ')[1].split('')[0] + '.'
surname = e.name.split(' ')[0]
if(e.name.split(' ')[2]){
patronymic = e.name.split(' ')[2].split('')[0] + '.'
e.name = surname + ' ' + name + ' ' + patronymic
} else {
e.name = surname + ' ' + name
}
}
})
}
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<div style={{display: 'flex', flexDirection: 'column',marginTop:'15px'}}>
<h1 style={{textAlign: 'center', fontSize: '13px', color: '#666',textTransform: 'uppercase'}}>Основные исполнители</h1>
<DetailsList
onRenderDetailsHeader={this.renderHeader}
items={items}
columns={[
{
key: 'column1',
name: 'Основные исполнители',
name: 'ФИО',
fieldName: 'name',
minWidth: 16,
maxWidth: 150
maxWidth: 150,
},
{
key: 'column2',
name: 'Структурное подразделение',
name: 'Подразделение',
fieldName: 'department',
minWidth: 16,
maxWidth: 80
@ -63,7 +108,9 @@ export default class TrafficLight extends React.Component<IComponentProps, IComp
compact={true}
isHeaderVisible={true}
selectionMode={SelectionMode.none}
className={styles.listElement}
/>
</div>
);
}

@ -1,5 +1,6 @@
import * as React from 'react';
import { ISummaryProjectData } from '../../interfaces/ISummaryProjectData';
import styles from '../SummaryWebPart.module.scss'
import {
DetailsList,
SelectionMode,
@ -9,7 +10,7 @@ import {
} from "office-ui-fabric-react/lib/DetailsList";
import { values } from '@uifabric/utilities/lib';
import { sp, Web, Field } from "@pnp/sp";
import { SPHttpClient, SPHttpClientResponse, SPHttpClientConfiguration } from '@microsoft/sp-http';
export interface IComponentProps {
order: string;
context: any;
@ -32,7 +33,7 @@ export default class SummaryContainer extends React.Component<IComponentProps,IC
componentDidMount() {
const { order, context } = this.props;
let relUrl = context.pageContext.web.serverRelativeUrl;
let prjCode = relUrl ? relUrl.split("/")[relUrl.split("/").length - 1] : "";
let prjCode = relUrl ? relUrl.split("/")[relUrl.split("/").length - 1] : ""; /* 'UNI_UNI_018' */
let web = new Web("http://portal.vertex.spb.ru/PMIS");
let webMDM = new Web("http://portal.vertex.spb.ru/sites/mdm");
@ -55,6 +56,7 @@ export default class SummaryContainer extends React.Component<IComponentProps,IC
});
}).then(res=>{
let selectedFields = res.map(e=>e.fieldName).join(', ');
selectedFields = selectedFields +', MainProjectPlanGuid, Title'
let lookupFields = res.filter(e=> e.LookupField !== "").map(e=>e.LookupField).join(', ');
let toState = [];
@ -67,11 +69,13 @@ export default class SummaryContainer extends React.Component<IComponentProps,IC
.expand(lookupFields)
.get()
.then(currentProject => {
res.forEach(e=>{
let fieldValue = !e.fieldName.match(/\/Title/gi)?currentProject[0][e.fieldName] : currentProject[0][e.fieldName.split('/')[0]]? currentProject[0][e.fieldName.split('/')[0]]['Title'] : '';
if (fieldValue && fieldValue.toString().match(/\;\#/gi)){
fieldValue = fieldValue.toString().replace(/[\;\#][^\s]/gi, '');
fieldValue = fieldValue.toString().replace(/[][^\s]/gi, '');
if (fieldValue.toString().match(/undefined/gi)) fieldValue = fieldValue.toString().replace(/undefined/gi, '');
}
@ -83,7 +87,39 @@ export default class SummaryContainer extends React.Component<IComponentProps,IC
});
}
});
var prjGuid = currentProject[0].MainProjectPlanGuid
let url = 'http://portal.vertex.spb.ru/pwa/_api/ProjectData/Проекты(guid\''+prjGuid+'\')/ИДРодительскогоПроекта'
const encoded = encodeURI(url);
fetch(encoded,{ headers: {
'accept': 'application/json',
}}).then(res => res.json())
.then((data) => {
let MainProjectPlanGuid = data.value
web.lists
.getById("eeba7db6-4c28-41a2-a54e-82a2aaf0ca15")
.items.filter(
"MainProjectPlanGuid eq '" + MainProjectPlanGuid + "'"
)
.select(selectedFields)
.expand(lookupFields)
.get().then(
(data) => {
if(data[0]) {
if(data[0]['Title']) {
toState.push({
key: 100000,
fieldName: 'Наименования родительского проекта',
value: data[0].Title
})
}
}
this.setState({ items: toState });
}
)
}).catch()
});
})
}
@ -110,10 +146,17 @@ export default class SummaryContainer extends React.Component<IComponentProps,IC
public render(): React.ReactElement<IComponentProps> {
const { items } = this.state;
items.forEach(function(e) {
if(e.fieldName == 'Внешний исполнитель') {
e.value = e.value.split(';#').join(' ')
}
})
return (
<div>
<DetailsList
setKey="items"
className={styles.listElement}
items={items}
columns={this.columns}
compact={true}

@ -1,9 +1,29 @@
.SummaryPageContainer{
display: grid;
grid-template-columns: 34% 34% 30%;
column-gap: 30px;
display: flex;
justify-content: space-between;
min-width: 1159px;
div{
.Title {
text-align: center;
font-size: 13px;
color:#666666;
}
}
.SummaryPageColumnOne{
width: 100%;
}
.SummaryPageColumnTwo{
width: 100%;
margin: 0 25px;
}
.SummaryPageColumnThree{
width: 100%;
}
.SummaryPageRowOne{
display: flex;
width: 100%;
flex-direction: column;
canvas{
height: 100% !important;
@ -12,9 +32,18 @@
.SummaryPageRowTwo{
width: 100%;
}
.SummaryPageRowThree{
width: 100%;
}
}
.statusesContainer{
display: flex;
flex-direction: column;
font-size: 13px !important;
}
.statusesDetailsContainer {
max-width: 200px;
font-size: 13px !important;
overflow-x: hidden !important;
}

@ -4,6 +4,7 @@ import SummaryContainer from '../SummaryContainer/SummaryContainer';
import TrafficLight from '../TrafficLight/TrafficLight';
import TaskStatusesChart from '../TaskStatusesChart/TaskStatusesChart';
import Structure from '../Structure/Structure';
import RejectTasks from '../RejectTasks/RejectTasks';
import { IPageData } from '../../interfaces/IPageData';
@ -21,21 +22,31 @@ export default class SummaryGlobalComponent extends React.Component<IComponentPr
return (
<div className={styles.SummaryPageContainer}>
<SummaryContainer
order={rowsOrder}
context={context}
/>
<div className={styles.SummaryPageRowOne}>
<div className={styles.SummaryPageColumnOne}>
<h1 className={styles.Title} style={{ fontSize: '13px' }}>СОСТОЯНИЕ ПРОЕКТА</h1>
<TrafficLight
statuses={data.Status}
taskStats={data.TaskStats}
dateInfo={data.HighLvlPrjInfo}
/>
<TaskStatusesChart taskStats={data.TaskStats} />
<Structure structureData={data.MainExec} />
</div>
<div className={styles.SummaryPageColumnTwo}>
<h1 className={styles.Title} style={{ fontSize: '13px' }}>ЗАДАЧИ С ОТКЛОНЕНИЕМ</h1>
<RejectTasks structureData={data.DeviationTasks} />
</div>
<Structure structureData={data.MainExec} />
<div className={styles.SummaryPageColumnThree}>
<h1 className={styles.Title} style={{fontSize: '13px'}}>ОБЩИЕ СВЕДЕНИЯ</h1>
<SummaryContainer
order={rowsOrder}
context={context}
/>
</div>
</div>
);
}

@ -1,74 +1,6 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.summaryWebPart {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
.listElement{
font-size: 13px !important;
.ms-DetailsHeader-cellName{
font-size: 13px !important;
}
}

@ -28,8 +28,28 @@ export default class SummaryWebPart extends React.Component<ISummaryWebPartProps
componentDidMount(){
const {requestUrl, context } = this.props;
const styles = document.createElement('link');
const scripts = document.createElement('script');
const stylesTemp = document.createElement('link');
styles.setAttribute('href', '/Style%20Library/extensionStyle.css');
stylesTemp.setAttribute('href', '/Style%20Library/additional.css');
scripts.setAttribute('src', '/Style%20Library/extensionScript.js');
styles.setAttribute('type', 'text/css');
stylesTemp.setAttribute('type', 'text/css');
scripts.setAttribute('type', 'text/javascript');
styles.setAttribute('rel', 'stylesheet');
stylesTemp.setAttribute('rel', 'stylesheet');
document.head.insertAdjacentElement("beforeend", styles);
/* document.head.insertAdjacentElement("beforeend", stylesTemp); */
document.body.appendChild(scripts);
let relUrl = context.pageContext.web.serverRelativeUrl;
let projectCode = relUrl ? relUrl.split('/')[relUrl.split('/').length - 1] : '';
let projectCode = relUrl ? relUrl.split('/')[relUrl.split('/').length - 1] : ''; /* 'UNI_UNI_018' */
let web = new Web('http://portal.vertex.spb.ru/PMIS');
let thisWeb = new Web('http://portal.vertex.spb.ru/PMIS/' + projectCode);
@ -66,11 +86,12 @@ export default class SummaryWebPart extends React.Component<ISummaryWebPartProps
const { pageData, isLoaded, isBlank, isPerm, noPermDiv } = this.state;
return isPerm ? isLoaded ? (
<div style={{ display: "flex", flexDirection: "column" }}>
<div style={{ display: "flex", flexDirection: "column", overflowX: 'auto' }}>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<PrimaryButton
text="Изменение карточки проекта"
onClick={this.relocate}
style={{fontSize: '13px'}}
/>
</div>
<SummaryGlobalComponent

@ -1,6 +1,7 @@
import * as React from 'react';
import { Bar } from 'react-chartjs-2';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { ClientSidePage } from '@pnp/sp';
export interface IComponentProps{
taskStats: {[key: string]: number;}
@ -12,21 +13,24 @@ export default class TaskStatusesChart extends React.Component<IComponentProps,
public getChartData = () =>{
const { taskStats } = this.props;
return {
labels: ['Выполняемые', 'Планируемые', 'Завершенные'],
labels: ['Планируемые','Выполняемые', 'Завершенные'],
datasets: [
{
label: '',
backgroundColor: '#407855',
borderColor: '#407855',
borderWidth: 1,
data: [taskStats.exec, taskStats.plan, taskStats.compl ]/* taskStats.plan + taskStats.compl + taskStats.exec */
data: [taskStats.plan, taskStats.exec, taskStats.compl ]/* taskStats.plan + taskStats.compl + taskStats.exec */
}
]
};
}
public render(): React.ReactElement<IComponentProps> {
const { taskStats } = this.props;
let chartTitle = 'Текущee состояние проекта (выполняемых задач: '+( Number(taskStats.exec) + Number(taskStats.plan) + Number(taskStats.compl))+' шт.)'
return (
<div >
<div><h1 style={{textAlign: 'center', fontSize: '13px', color: '#666',textTransform: 'uppercase'}}>{chartTitle}</h1></div>
<div>
<Bar
data={this.getChartData}
@ -35,39 +39,79 @@ export default class TaskStatusesChart extends React.Component<IComponentProps,
maintainAspectRatio: false,
legend: {
display: false
},
tooltips: {
callbacks: {
label: function(tooltipItem, data,context) {
var result = 0
data.datasets[tooltipItem.datasetIndex].data.forEach( function (e) {
result = result + Number(e)
})
var taskLabel = ""
if(tooltipItem.label == 'Планируемые') {
taskLabel = 'Планируемых задач: '
} else if(tooltipItem.label == 'Выполняемые') {
taskLabel = 'Выполняемых задач: '
} else if(tooltipItem.label == 'Завершенные') {
taskLabel = 'Завершенных задач: '
} else {
taskLabel = 'Задач'
}
var dataLabel = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]
var label = taskLabel+dataLabel
var labelLine2 = 'Всего задач: '+result
var labelLine3 = '% от общего количества задач: '+Math.round((dataLabel/result)*100) + '% '
var labelArray = [label,labelLine2,labelLine3]
return labelArray
}
}
},
plugins: {
datalabels: {
color: '#000',
font: {
size: '10',
size: '13',
},
anchor: 'end',
align: 'top',
formatter: function(value, context) {
var result = 0
context.chart.data.datasets[0].data.forEach( function (e) {
result = result + Number(e)
})
var dataLabel = context.chart.data.datasets[0].data[context.dataIndex]
if(Math.round((dataLabel/result)*100)){
var label = Math.round((dataLabel/result)*100) + '% ' + '('+dataLabel+'шт.)'
} else {
var label = ''
}
return label
}
},
},
title: {
display: true,
text: 'Текущee состояние проекта',
fontSize: 16
},
scales: {
yAxes: [{
showGrid: false,
ticks: {
fontSize: 8,
fontSize: 13,
suggestedMax: (taskStats.plan + taskStats.compl + taskStats.exec) + 20,
}
}],
xAxes: [{
maxBarThickness: 30,
ticks: {
fontSize: 8
fontSize: 13
}
}]
}
}}
/>
</div>
</div>
);

@ -1,8 +1,10 @@
import * as React from 'react';
import styles from '../SummaryGlobalComponent/SummaryGlobalComponent.module.scss'
import stylesEx from '../SummaryWebPart.module.scss'
import moment from "moment/min/moment-with-locales";
import { DetailsList, SelectionMode, DetailsListLayoutMode } from 'office-ui-fabric-react/lib/DetailsList';
import { DetailsHeader } from 'office-ui-fabric-react/lib/components/DetailsList/DetailsHeader';
export interface IComponentProps{
statuses: {[key: string]: string;};
@ -33,7 +35,7 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
result.push(
{
key: index,
fieldName: татус по сроку",
fieldName: "Срок",
status: statuses.term
});
++index;
@ -42,7 +44,7 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
result.push(
{
key: index,
fieldName: "Статус по бюджету",
fieldName: "Бюджет",
status: statuses.budget
});
++index;
@ -51,21 +53,45 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
result.push(
{
key: index,
fieldName: "Статус по % выполнения",
fieldName: "Статус по сроку",
status: statuses.percCompl
});
}
return result;
}
public renderHeader = (detailsHeaderProps) =>{
return (
<DetailsHeader
{...detailsHeaderProps}
onRenderColumnHeaderTooltip={this.renderCustomHeaderTooltip}
/>
);
}
public renderCustomHeaderTooltip = (tooltipHostProps) =>{
return (
<span
style={{
fontSize: "13px",
}}
>
{tooltipHostProps.children}
</span>
);
}
public render(): React.ReactElement<IComponentProps> {
const { statuses, taskStats, dateInfo} = this.props;
let procent = (taskStats.compl/(taskStats.plan + taskStats.compl + taskStats.exec))* 100;
let startDate = moment(dateInfo.dateStart).format("DD.MM.YYYY");
let finDate = moment(dateInfo.dateFinish).format("DD.MM.YYYY");
let totalDays = dateInfo.duration + 'дн.'
let totalDays = Math.round(dateInfo.duration) + 'дн.'
let allTasks = dateInfo.percentCompl ? dateInfo.percentCompl.toString().match(/\./gi) ? dateInfo.percentCompl.toFixed(2) : dateInfo.percentCompl : ''
if(allTasks) {
allTasks = allTasks +'%'
}
return (
<div className={styles.statusesContainer}>
<DetailsList
@ -90,15 +116,19 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
compact={true}
isHeaderVisible={false}
selectionMode={SelectionMode.none}
className={styles.statusesDetailsContainer}
/>
<DetailsList
className={stylesEx.listElement}
onRenderDetailsHeader={this.renderHeader}
items={[
{
key: 1,
start: startDate !== 'Invalid date' ? startDate : '',
end: finDate !== 'Invalid date' ? finDate : '',
time: totalDays ? totalDays : '',
allTasks: taskStats.plan + taskStats.compl + taskStats.exec
/* allTasks: taskStats.plan + taskStats.compl + taskStats.exec */
allTasks: allTasks
}
]}
columns={[
@ -118,17 +148,17 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
},
{
key: "column3",
name: "Срок выполнения",
name: "Срок",
fieldName: "time",
minWidth: 90,
maxWidth: 110
minWidth: 50,
maxWidth: 50
},
{
key: "column4",
name: "Всего задач",
name: "Актуальный статус",
fieldName: "allTasks",
minWidth: 60,
maxWidth: 80
minWidth: 110,
maxWidth: 110
}
]}
compact={true}
@ -143,7 +173,7 @@ export default class TrafficLight extends React.Component<IComponentProps, {}> {
marginTop: 15,
marginBottom: 15
}}
>{`Актуальный статус выполнения ${dateInfo.percentCompl ? dateInfo.percentCompl.toString().match(/\./gi) ? dateInfo.percentCompl.toFixed(2) : dateInfo.percentCompl : ' -'}${dateInfo.percentCompl ? '%' : ''}`}</div>
></div>
</div>
);
}

@ -0,0 +1,4 @@
export interface DeviationTasks {
Departments: any;
}

@ -7,4 +7,5 @@ export interface IPageData {
TaskStats: {[key: string]: number;};
MainExec: IMainExecItem[],
HighLvlPrjInfo: { dateFinish: string, dateStart:string};
DeviationTasks: any
}

Loading…
Cancel
Save