@dssp/dkpi 1.0.0-y.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/KPI-STATISTICS-SERVICE.md +233 -0
- package/_index.html +4 -9
- package/assets/helps/index.md +1 -1
- package/assets/images/project-image.png +0 -0
- package/assets/manifest.json +2 -2
- package/config/config.development.js +2 -1
- package/config/config.production.js +2 -1
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +15 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
- package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
- package/dist-client/components/kpi-boxplot-chart.js +306 -0
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
- package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
- package/dist-client/components/kpi-lookup-chart.js +464 -0
- package/dist-client/components/kpi-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
- package/dist-client/components/kpi-mini-trend-chart.js +180 -0
- package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
- package/dist-client/components/kpi-radar-chart.d.ts +17 -0
- package/dist-client/components/kpi-radar-chart.js +259 -0
- package/dist-client/components/kpi-radar-chart.js.map +1 -0
- package/dist-client/components/kpi-single-boxplot-chart.d.ts +14 -2
- package/dist-client/components/kpi-single-boxplot-chart.js +237 -137
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
- package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
- package/dist-client/components/kpi-step-lookup-chart.js +181 -0
- package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
- package/dist-client/components/kpi-trend-chart.d.ts +25 -0
- package/dist-client/components/kpi-trend-chart.js +241 -0
- package/dist-client/components/kpi-trend-chart.js.map +1 -0
- package/dist-client/entries/auth/checkin.d.ts +38 -0
- package/dist-client/entries/auth/checkin.js +551 -0
- package/dist-client/entries/auth/checkin.js.map +1 -0
- package/dist-client/google-map/common-google-map.d.ts +35 -0
- package/dist-client/google-map/common-google-map.js +349 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +23 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/pages/component/project-update-header.d.ts +1 -0
- package/dist-client/pages/component/project-update-header.js +127 -0
- package/dist-client/pages/component/project-update-header.js.map +1 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
- package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
- package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1097 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
- package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
- package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
- package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
- package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +1 -13
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +20 -11
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +1 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +1 -2
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +22 -33
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +56 -0
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +609 -67
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +15 -3
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +180 -65
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +13 -4
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +213 -91
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
- package/dist-client/pages/sv-project-complete.d.ts +12 -3
- package/dist-client/pages/sv-project-complete.js +175 -29
- package/dist-client/pages/sv-project-complete.js.map +1 -1
- package/dist-client/pages/sv-project-completed-list.d.ts +3 -0
- package/dist-client/pages/sv-project-completed-list.js +81 -22
- package/dist-client/pages/sv-project-completed-list.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +60 -0
- package/dist-client/pages/sv-project-detail.js +1092 -85
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-list.d.ts +20 -2
- package/dist-client/pages/sv-project-list.js +180 -30
- package/dist-client/pages/sv-project-list.js.map +1 -1
- package/dist-client/pages/sv-project-update.d.ts +91 -0
- package/dist-client/pages/sv-project-update.js +1150 -0
- package/dist-client/pages/sv-project-update.js.map +1 -0
- package/dist-client/pages/sv-user-management.d.ts +1 -0
- package/dist-client/pages/sv-user-management.js +5 -0
- package/dist-client/pages/sv-user-management.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +29 -2
- package/dist-client/route.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +45 -5
- package/dist-client/shared/complete-api.js +177 -28
- package/dist-client/shared/complete-api.js.map +1 -1
- package/dist-client/shared/func.d.ts +1 -1
- package/dist-client/shared/func.js.map +1 -1
- package/dist-client/shared/geo-group-mapping.d.ts +25 -0
- package/dist-client/shared/geo-group-mapping.js +189 -0
- package/dist-client/shared/geo-group-mapping.js.map +1 -0
- package/dist-client/shared/integration-fetch.d.ts +35 -0
- package/dist-client/shared/integration-fetch.js +53 -0
- package/dist-client/shared/integration-fetch.js.map +1 -0
- package/dist-client/shared/kpi-project-sync.d.ts +16 -0
- package/dist-client/shared/kpi-project-sync.js +129 -0
- package/dist-client/shared/kpi-project-sync.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/menu-tools.d.ts +11 -3
- package/dist-client/viewparts/menu-tools.js +126 -75
- package/dist-client/viewparts/menu-tools.js.map +1 -1
- package/dist-server/scripts/calculate-kpi-scores.js +65 -3
- package/dist-server/scripts/calculate-kpi-scores.js.map +1 -1
- package/dist-server/scripts/load-grade-data-migration.d.ts +4 -0
- package/dist-server/scripts/load-grade-data-migration.js +95 -10
- package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
- package/dist-server/scripts/propagate-parent-kpi-values.d.ts +4 -0
- package/dist-server/scripts/propagate-parent-kpi-values.js +423 -77
- package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
- package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
- package/dist-server/scripts/recalculate-by-project-name.js +72 -0
- package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
- package/dist-server/service/index.d.ts +1 -3
- package/dist-server/service/index.js +3 -4
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +192 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +993 -38
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +9 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +33 -8
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
- package/dist-server/service/kpi-stat/index.d.ts +4 -0
- package/dist-server/service/kpi-stat/index.js +8 -0
- package/dist-server/service/kpi-stat/index.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
- package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -1
- package/dist-server/service/kpi-value/kpi-value-query.js +49 -6
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/kpi-module-service-tests.md +1286 -0
- package/kpi-module-test-report.md +676 -0
- package/kpi-module-unit-test-detailed-report.md +925 -0
- package/kpi-module-unit-tests-detailed.md +1452 -0
- package/openapi/unstable.yaml +2 -2
- package/package.json +58 -54
- package/recalculate-batch.sh +64 -0
- package/recalculate-projects-range.sh +98 -0
- package/schema.graphql +1159 -55
- package/things-factory.config.js +6 -1
- package/translations/en.json +5 -1
- package/translations/ko.json +5 -1
- package/views/auth-page.html +4 -5
- package/views/public/home.html +5 -6
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import { navigate, PageView } from '@operato/shell';
|
|
3
|
+
import { ScrollbarStyles } from '@operato/styles';
|
|
3
4
|
import { css, html } from 'lit';
|
|
4
5
|
import { customElement, state } from 'lit/decorators.js';
|
|
5
6
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
|
6
|
-
import { PROJECT_STATE
|
|
7
|
+
import { PROJECT_STATE } from './sv-project-list';
|
|
7
8
|
import { client } from '@operato/graphql';
|
|
8
9
|
import gql from 'graphql-tag';
|
|
10
|
+
import moment from 'moment-timezone';
|
|
11
|
+
import { isProjectTypeDomain, tenantHeaders } from '@dssp/project/dist-client/shared/domain-context';
|
|
12
|
+
import '../components/kpi-boxplot-chart';
|
|
13
|
+
import '../components/kpi-single-boxplot-chart';
|
|
14
|
+
import '../components/kpi-radar-chart';
|
|
15
|
+
import '../components/kpi-lookup-chart';
|
|
16
|
+
import '../components/kpi-step-lookup-chart';
|
|
17
|
+
import '../components/kpi-2d-lookup-chart';
|
|
9
18
|
let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(PageView) {
|
|
10
19
|
constructor() {
|
|
11
20
|
super(...arguments);
|
|
12
21
|
this.project = {};
|
|
22
|
+
this.kpiComprehensiveStats = [];
|
|
23
|
+
this.kpiYComprehensiveStats = [];
|
|
24
|
+
this.projectXKpiValues = [];
|
|
25
|
+
this.showScoreDistribution = false;
|
|
26
|
+
this.selectedKpi = null;
|
|
27
|
+
/** 이 프로젝트에 아직 한 번도 입력되지 않은 metric (active 한 manual/import 한정) */
|
|
28
|
+
this.pendingMetrics = [];
|
|
29
|
+
/** 현재 프로젝트의 모든 KpiMetricValue (metric.name 포함) — 2D lookup chart 의 progressRate / value 도출용. */
|
|
30
|
+
this.allKpiMetricValues = [];
|
|
13
31
|
}
|
|
14
32
|
get context() {
|
|
33
|
+
var _a;
|
|
15
34
|
return {
|
|
16
|
-
title: '프로젝트 상세 정보'
|
|
35
|
+
title: !isProjectTypeDomain() && this.project ? `프로젝트 상세 정보 - ${(_a = this.project) === null || _a === void 0 ? void 0 : _a.name}` : '프로젝트 상세 정보'
|
|
17
36
|
};
|
|
18
37
|
}
|
|
19
38
|
render() {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f;
|
|
39
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
21
40
|
return html `
|
|
22
41
|
<div content>
|
|
23
42
|
<div left>
|
|
@@ -25,35 +44,65 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
25
44
|
<div class="card-title">
|
|
26
45
|
<div>${this.project.name}</div>
|
|
27
46
|
<div class="triangle"></div>
|
|
47
|
+
<div
|
|
48
|
+
class="ghost-btn header-btn"
|
|
49
|
+
@click=${async () => {
|
|
50
|
+
if (isProjectTypeDomain()) {
|
|
51
|
+
navigate('project-update');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const id = this.project.id || (await this._resolveProjectIdFromDomain());
|
|
55
|
+
if (id)
|
|
56
|
+
navigate(`project-update/${id}`);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<md-icon slot="">assignment</md-icon>
|
|
60
|
+
<div>프로젝트 수정</div>
|
|
61
|
+
</div>
|
|
28
62
|
</div>
|
|
29
63
|
|
|
30
64
|
<div class="top-info" style="margin-top: 10px;">
|
|
31
|
-
<img pic src=${((_a = this.project.mainPhoto) === null || _a === void 0 ? void 0 : _a.fullpath) || '/assets/images/
|
|
65
|
+
<img pic src=${((_a = this.project.mainPhoto) === null || _a === void 0 ? void 0 : _a.fullpath) || '/assets/images/project-image.png'} alt="project" />
|
|
32
66
|
<div class="info">
|
|
33
|
-
<div class="address">• ${(_b = this.project.buildingComplex) === null || _b === void 0 ? void 0 : _b.address}</div>
|
|
34
67
|
<div class="row">
|
|
35
|
-
<div class="label">•
|
|
68
|
+
<div class="label">• 주소</div>
|
|
69
|
+
<div class="value">${(_b = this.project.buildingComplex) === null || _b === void 0 ? void 0 : _b.address}</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="row">
|
|
72
|
+
<div class="label">• 계획공사기간</div>
|
|
36
73
|
<div class="value">${this.project.startDate} ~ ${this.project.endDate}</div>
|
|
37
74
|
</div>
|
|
38
75
|
<div class="row">
|
|
39
|
-
<div class="label">•
|
|
76
|
+
<div class="label">• 프로젝트 상태</div>
|
|
40
77
|
<div class="value status-value">${PROJECT_STATE[this.project.state]}</div>
|
|
41
78
|
</div>
|
|
42
79
|
<div class="row">
|
|
43
|
-
<div class="label">•
|
|
44
|
-
<div class="value
|
|
80
|
+
<div class="label">• 유형</div>
|
|
81
|
+
<div class="value">
|
|
82
|
+
${this.project.sectorType === 'PUBLIC'
|
|
83
|
+
? '공공'
|
|
84
|
+
: this.project.sectorType === 'PRIVATE'
|
|
85
|
+
? '민간'
|
|
86
|
+
: '-'}
|
|
87
|
+
</div>
|
|
45
88
|
</div>
|
|
46
89
|
<div class="row">
|
|
47
|
-
<div class="label">•
|
|
48
|
-
<div class="value"
|
|
90
|
+
<div class="label">• 용도</div>
|
|
91
|
+
<div class="value">
|
|
92
|
+
${this.project.buildingUsage === 'RESIDENTIAL'
|
|
93
|
+
? '주거'
|
|
94
|
+
: this.project.buildingUsage === 'NON_RESIDENTIAL'
|
|
95
|
+
? '비주거'
|
|
96
|
+
: '-'}
|
|
97
|
+
</div>
|
|
49
98
|
</div>
|
|
50
99
|
</div>
|
|
51
100
|
</div>
|
|
52
101
|
|
|
53
102
|
<div class="detail-grid">
|
|
54
103
|
<div class="grid-item row">
|
|
55
|
-
<div class="label">•
|
|
56
|
-
<div class="value">${((_d = (_c = this.project.buildingComplex) === null || _c === void 0 ? void 0 : _c.constructionCost) === null || _d === void 0 ? void 0 : _d.toLocaleString()) || '-'}
|
|
104
|
+
<div class="label">• 계획공사비</div>
|
|
105
|
+
<div class="value">${((_d = (_c = this.project.buildingComplex) === null || _c === void 0 ? void 0 : _c.constructionCost) === null || _d === void 0 ? void 0 : _d.toLocaleString()) || '-'} 억원</div>
|
|
57
106
|
</div>
|
|
58
107
|
<div class="grid-item row">
|
|
59
108
|
<div class="label">• 연면적</div>
|
|
@@ -61,11 +110,11 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
61
110
|
</div>
|
|
62
111
|
<div class="grid-item row">
|
|
63
112
|
<div class="label">• 용적률</div>
|
|
64
|
-
<div class="value"
|
|
113
|
+
<div class="value">${((_h = (_g = this.project.buildingComplex) === null || _g === void 0 ? void 0 : _g.floorAreaRatio) === null || _h === void 0 ? void 0 : _h.toLocaleString()) || '-'} %</div>
|
|
65
114
|
</div>
|
|
66
115
|
<div class="grid-item row">
|
|
67
|
-
<div class="label">•
|
|
68
|
-
<div class="value"
|
|
116
|
+
<div class="label">• 총 투입인력(M/M)</div>
|
|
117
|
+
<div class="value">${((_k = (_j = this.project.buildingComplex) === null || _j === void 0 ? void 0 : _j.workerCount) === null || _k === void 0 ? void 0 : _k.toLocaleString()) || '-'} 명</div>
|
|
69
118
|
</div>
|
|
70
119
|
</div>
|
|
71
120
|
</div>
|
|
@@ -74,117 +123,286 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
74
123
|
<div class="card-title">
|
|
75
124
|
<div>입력 대기 중</div>
|
|
76
125
|
<div class="triangle"></div>
|
|
126
|
+
<div
|
|
127
|
+
class="ghost-btn header-btn"
|
|
128
|
+
@click=${async () => {
|
|
129
|
+
if (isProjectTypeDomain()) {
|
|
130
|
+
navigate('project-complete');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const id = this.project.id || (await this._resolveProjectIdFromDomain());
|
|
134
|
+
if (id)
|
|
135
|
+
navigate(`project-complete/${id}`);
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<md-icon slot="">assignment_turned_in</md-icon>
|
|
139
|
+
<div>KPI 데이타입력</div>
|
|
140
|
+
</div>
|
|
77
141
|
</div>
|
|
78
142
|
<div class="sub-desc">
|
|
79
|
-
|
|
143
|
+
이 프로젝트에 아직 입력되지 않은 KPI 항목입니다. 해당 항목에 대한 데이터를 입력 해 주시기 바랍니다.
|
|
80
144
|
</div>
|
|
81
145
|
|
|
82
146
|
<div class="list-block">
|
|
83
|
-
<div class="list-title">•
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<div class="list-text"># 2012345-Y4.32 : 책임감리원의 일정성과 수준 평가</div>
|
|
104
|
-
<div class="list-text"># 2035641-Y5.22 : 설계 변경에 따른 공기 증감률</div>
|
|
147
|
+
<div class="list-title">• 미입력 ${this.pendingMetrics.length}건</div>
|
|
148
|
+
${this.pendingMetrics.length === 0
|
|
149
|
+
? html `<div class="list-text">모든 항목이 입력되었습니다.</div>`
|
|
150
|
+
: this.pendingMetrics.map(m => html `
|
|
151
|
+
<div
|
|
152
|
+
class="list-text"
|
|
153
|
+
style="cursor: pointer;"
|
|
154
|
+
@click=${async () => {
|
|
155
|
+
if (isProjectTypeDomain()) {
|
|
156
|
+
navigate('project-complete');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const id = this.project.id || (await this._resolveProjectIdFromDomain());
|
|
160
|
+
if (id)
|
|
161
|
+
navigate(`project-complete/${id}`);
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
# ${m.name}
|
|
165
|
+
</div>
|
|
166
|
+
`)}
|
|
105
167
|
</div>
|
|
106
168
|
</div>
|
|
107
169
|
</div>
|
|
108
170
|
|
|
109
171
|
<div right>
|
|
110
|
-
${this.project.state === ProjectState.ONGOING
|
|
111
|
-
? html `
|
|
112
|
-
<div class="button-line">
|
|
113
|
-
<div class="ghost-btn" @click=${() => navigate(`project-update/${this.project.id}`)}>
|
|
114
|
-
<md-icon slot="">assignment</md-icon>
|
|
115
|
-
<div>프로젝트 수정</div>
|
|
116
|
-
</div>
|
|
117
|
-
<div class="ghost-btn" @click=${() => navigate(`project-complete/${this.project.id}`)}>
|
|
118
|
-
<md-icon slot="">assignment_turned_in</md-icon>
|
|
119
|
-
<div>프로젝트 완공 처리</div>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
`
|
|
123
|
-
: ''}
|
|
124
|
-
|
|
125
172
|
<div class="card kpi-box">
|
|
126
173
|
<div class="kpi-header">
|
|
127
|
-
<div
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
분포차트입니다.
|
|
174
|
+
<div>
|
|
175
|
+
<div>종합 KPI</div>
|
|
176
|
+
<div class="triangle"></div>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="kpi-desc">프로젝트의 6개 성과를 종합한 종합 KPI포인트와 각 지표별 분포</div>
|
|
133
179
|
</div>
|
|
134
180
|
|
|
135
181
|
<div class="kpi-summary">
|
|
136
182
|
<div class="mini-boxplot" title="boxplot placeholder">
|
|
137
|
-
<div class="boxplot-area">
|
|
183
|
+
<div class="boxplot-area">
|
|
184
|
+
<kpi-single-boxplot-chart .data=${this.getBoxPlotDataForProject(this.project)}></kpi-single-boxplot-chart>
|
|
185
|
+
</div>
|
|
138
186
|
|
|
139
187
|
<div class="boxplot-point">
|
|
140
|
-
<div class="value"
|
|
188
|
+
<div class="value">${this.project.kpi ? this.project.kpi.toFixed(1) : '-'}</div>
|
|
141
189
|
<div class="label">Point</div>
|
|
142
190
|
</div>
|
|
143
191
|
</div>
|
|
144
192
|
|
|
145
|
-
<div class="spider-area">
|
|
193
|
+
<div class="spider-area">
|
|
194
|
+
<sv-kpi-radar-chart
|
|
195
|
+
.data=${this.getRadarChartData().data}
|
|
196
|
+
.categories=${this.getRadarChartData().categories}
|
|
197
|
+
.currentGroup=${'프로젝트성과'}
|
|
198
|
+
></sv-kpi-radar-chart>
|
|
199
|
+
</div>
|
|
146
200
|
</div>
|
|
147
201
|
</div>
|
|
148
202
|
|
|
149
203
|
<div class="card group-box">
|
|
150
204
|
<div class="kpi-header">
|
|
151
|
-
<div
|
|
152
|
-
|
|
205
|
+
<div>
|
|
206
|
+
<div>성과영역별 KPI</div>
|
|
207
|
+
<div class="triangle"></div>
|
|
208
|
+
</div>
|
|
153
209
|
</div>
|
|
154
210
|
<div class="desc">
|
|
155
|
-
Boxplot(박스플롯)은
|
|
156
|
-
|
|
157
|
-
|
|
211
|
+
Boxplot(박스플롯)은 박스는 중앙 50% 구간, 수염은 전체 범위, 굵은 파란색 가로선은 중앙값(median), 초록색 원은
|
|
212
|
+
평균값(Mean)을 의미합니다. 당해 프로젝트의 값은 붉은색 원으로 표시됩니다.
|
|
213
|
+
</div>
|
|
214
|
+
<div class="group-chart">
|
|
215
|
+
<sv-kpi-boxplot-chart .data=${this.getYKpiBoxplotData()}></sv-kpi-boxplot-chart>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div class="metrics-card">
|
|
220
|
+
<div class="metrics-header">
|
|
221
|
+
<div class="title">
|
|
222
|
+
<span>성과영역별 KPI 측정값</span>
|
|
223
|
+
<div class="triangle"></div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="desc">
|
|
227
|
+
프로젝트의 6개 성과별 상세 주요 매트릭 데이터 (각 세부 항목을 클릭하시면 평가 기준표를 확인할 수 있습니다)
|
|
228
|
+
</div>
|
|
229
|
+
<div class="metrics-content">
|
|
230
|
+
${Object.values(this.getMetricGroups()).map(group => html `
|
|
231
|
+
<div class="metric-group">
|
|
232
|
+
<div class="metric-group-header">
|
|
233
|
+
<div class="metric-group-icon"></div>
|
|
234
|
+
<span>${group.name}</span>
|
|
235
|
+
<span style="margin-left: 10px; color: #4caf50; font-weight: 700;">[${group.score}]</span>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="metric-items">
|
|
238
|
+
${group.metrics.map(metric => html `
|
|
239
|
+
<div class="metric-item">
|
|
240
|
+
<span
|
|
241
|
+
class="metric-label"
|
|
242
|
+
@click=${() => this._onMetricLabelClick(metric)}
|
|
243
|
+
title="클릭하시면 평가 기준표를 확인할 수 있습니다"
|
|
244
|
+
>
|
|
245
|
+
${metric.label}
|
|
246
|
+
</span>
|
|
247
|
+
<span class="metric-value">${metric.value}</span>
|
|
248
|
+
</div>
|
|
249
|
+
`)}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
`)}
|
|
158
253
|
</div>
|
|
159
|
-
<div class="group-chart"></div>
|
|
160
254
|
</div>
|
|
161
255
|
</div>
|
|
162
256
|
</div>
|
|
257
|
+
|
|
258
|
+
${this.showScoreDistribution && this.selectedKpi
|
|
259
|
+
? html `
|
|
260
|
+
<div class="popup-overlay" @click=${this._closePopup}>
|
|
261
|
+
<div class="popup-content" @click=${(e) => e.stopPropagation()}>
|
|
262
|
+
<div class="popup-header">
|
|
263
|
+
<div class="popup-title">${this.selectedKpi.kpiName} — 성과 분포</div>
|
|
264
|
+
${this.selectedKpi.score !== undefined && this.selectedKpi.score !== null
|
|
265
|
+
? html `
|
|
266
|
+
<div class="popup-score">
|
|
267
|
+
<span class="score-value">${Number(this.selectedKpi.score).toFixed(1)}</span>
|
|
268
|
+
</div>
|
|
269
|
+
`
|
|
270
|
+
: ''}
|
|
271
|
+
<button class="popup-close" @click=${this._closePopup}>×</button>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="popup-chart-container">
|
|
274
|
+
${
|
|
275
|
+
/*
|
|
276
|
+
* scoreType 기반 차트 분기:
|
|
277
|
+
*
|
|
278
|
+
* CUSTOM (X13 등): 2D 룩업 히트맵 — 단, 증강데이터에서는 value가
|
|
279
|
+
* 이미 1~5 정수(룩업 결과)이므로 히트맵 대신 게이지로 폴백.
|
|
280
|
+
* 실제 프로젝트에서는 value가 편차율(소수점)이므로 히트맵 표시.
|
|
281
|
+
*
|
|
282
|
+
* LOOKUP: 성과 분포 커브 (kpi-lookup-chart)
|
|
283
|
+
* ASSESSMENT: 5단계 게이지 (kpi-lookup-chart 자동 감지)
|
|
284
|
+
*/ ''}
|
|
285
|
+
${
|
|
286
|
+
// SL-PA 결과(X34 품질, X43 안전) 만 계단형 등급 분포 차트로 표시.
|
|
287
|
+
// (다른 KPI 차트 경로에는 영향 없음)
|
|
288
|
+
this.selectedKpi.pattern === 'X34' || this.selectedKpi.pattern === 'X43'
|
|
289
|
+
? html `
|
|
290
|
+
<kpi-step-lookup-chart
|
|
291
|
+
.grades=${this.selectedKpi.grades || []}
|
|
292
|
+
.value=${this.selectedKpi.value}
|
|
293
|
+
.score=${(_l = this.selectedKpi.score) !== null && _l !== void 0 ? _l : null}
|
|
294
|
+
unit=${this.selectedKpi.unit || ''}
|
|
295
|
+
></kpi-step-lookup-chart>
|
|
296
|
+
`
|
|
297
|
+
: // 2D lookup KPI (X13 등) 는 항상 등급 기준 + 히트맵 함께 표시 (value 가
|
|
298
|
+
// 정수든 소수든 일관). _renderAssessmentGrades 안의 is2D 분기가 히트맵
|
|
299
|
+
// 같이 그림. 그 외 ASSESSMENT 도 등급 기준, LOOKUP 은 분포 커브.
|
|
300
|
+
this.selectedKpi.scoreType === 'CUSTOM' || ((_m = this.selectedKpi.grades) === null || _m === void 0 ? void 0 : _m.type) === 'PROGRESS_DEVIATION_LOOKUP'
|
|
301
|
+
? this._renderAssessmentGrades()
|
|
302
|
+
: this._isAssessmentOrIntegerScore()
|
|
303
|
+
? this._renderAssessmentGrades()
|
|
304
|
+
: html `
|
|
305
|
+
<kpi-lookup-chart
|
|
306
|
+
.grades=${this.selectedKpi.grades || []}
|
|
307
|
+
.value=${this.selectedKpi.value}
|
|
308
|
+
.score=${(_o = this.selectedKpi.score) !== null && _o !== void 0 ? _o : null}
|
|
309
|
+
.scoreType=${this.selectedKpi.scoreType || ''}
|
|
310
|
+
.valueType=${this.selectedKpi.valueType || ''}
|
|
311
|
+
unit=${this.selectedKpi.unit || ''}
|
|
312
|
+
></kpi-lookup-chart>
|
|
313
|
+
`}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
`
|
|
318
|
+
: ''}
|
|
163
319
|
`;
|
|
164
320
|
}
|
|
165
321
|
async pageUpdated(changes, lifecycle) {
|
|
166
|
-
if (this.active)
|
|
167
|
-
|
|
322
|
+
if (!this.active)
|
|
323
|
+
return;
|
|
324
|
+
let projectId = lifecycle.resourceId;
|
|
325
|
+
// 프로젝트 테넌트 컨텍스트(/project/<code>/) 에서 진입한 경우 resourceId 가 비어있다.
|
|
326
|
+
// 현재 도메인의 subdomain(= Project.code) 으로 프로젝트를 역조회.
|
|
327
|
+
if (!projectId) {
|
|
328
|
+
projectId = await this._resolveProjectIdFromDomain();
|
|
168
329
|
}
|
|
330
|
+
if (!projectId)
|
|
331
|
+
return;
|
|
332
|
+
// initProject 의 GraphQL 응답을 기다리지 않고 project.id 를 즉시 채워서,
|
|
333
|
+
// 링크의 `project-update/${project.id}` 가 절대 undefined 가 되지 않도록 보장.
|
|
334
|
+
this.project = Object.assign(Object.assign({}, this.project), { id: projectId });
|
|
335
|
+
// 후속 query 들이 tenant 컨텍스트로 동작하도록 project.code 를 먼저 가볍게 resolve.
|
|
336
|
+
// (tenant 컨텍스트에서 진입한 경우는 헤더 불필요 — tenantHeaders 가 빈 객체 반환.)
|
|
337
|
+
if (!isProjectTypeDomain()) {
|
|
338
|
+
await this._resolveProjectCode(projectId);
|
|
339
|
+
}
|
|
340
|
+
this.initProject(projectId);
|
|
341
|
+
this.getKpiComprehensiveStats();
|
|
342
|
+
this.getKpiYComprehensiveStats();
|
|
343
|
+
this.getProjectXKpiValues(projectId);
|
|
344
|
+
}
|
|
345
|
+
async _resolveProjectCode(projectId) {
|
|
346
|
+
var _a, _b;
|
|
347
|
+
try {
|
|
348
|
+
const resp = await client.query({
|
|
349
|
+
query: gql `
|
|
350
|
+
query ProjectCode($id: String!) {
|
|
351
|
+
project(id: $id) {
|
|
352
|
+
id
|
|
353
|
+
code
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`,
|
|
357
|
+
variables: { id: projectId }
|
|
358
|
+
});
|
|
359
|
+
const code = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.project) === null || _b === void 0 ? void 0 : _b.code;
|
|
360
|
+
if (code) {
|
|
361
|
+
this.project = Object.assign(Object.assign({}, this.project), { code });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (e) {
|
|
365
|
+
console.warn('[project-detail] project.code resolve 실패', e);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async _resolveProjectIdFromDomain() {
|
|
369
|
+
var _a, _b;
|
|
370
|
+
const response = await client.query({
|
|
371
|
+
query: gql `
|
|
372
|
+
query CurrentProject {
|
|
373
|
+
currentProject {
|
|
374
|
+
id
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`
|
|
378
|
+
});
|
|
379
|
+
return ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.currentProject) === null || _b === void 0 ? void 0 : _b.id) || '';
|
|
169
380
|
}
|
|
170
381
|
async initProject(projectId = '') {
|
|
171
|
-
var _a;
|
|
382
|
+
var _a, _b;
|
|
172
383
|
const response = await client.query({
|
|
173
384
|
query: gql `
|
|
174
385
|
query Project($id: String!) {
|
|
175
386
|
project(id: $id) {
|
|
176
387
|
id
|
|
388
|
+
code
|
|
177
389
|
name
|
|
178
390
|
state
|
|
179
391
|
startDate
|
|
180
392
|
endDate
|
|
181
393
|
projectType
|
|
394
|
+
sectorType
|
|
395
|
+
buildingUsage
|
|
182
396
|
mainPhoto {
|
|
183
397
|
fullpath
|
|
184
398
|
}
|
|
185
399
|
totalProgress
|
|
186
400
|
weeklyProgress
|
|
187
401
|
kpi
|
|
402
|
+
kpiValues {
|
|
403
|
+
kpiName
|
|
404
|
+
value
|
|
405
|
+
}
|
|
188
406
|
inspPassRate
|
|
189
407
|
robotProgressRate
|
|
190
408
|
structuralSafetyRate
|
|
@@ -195,6 +413,10 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
195
413
|
latitude
|
|
196
414
|
longitude
|
|
197
415
|
area
|
|
416
|
+
coverageRatio
|
|
417
|
+
floorAreaRatio
|
|
418
|
+
siteType
|
|
419
|
+
workerCount
|
|
198
420
|
clientCompany
|
|
199
421
|
constructionCompany
|
|
200
422
|
supervisoryCompany
|
|
@@ -220,14 +442,576 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
220
442
|
}
|
|
221
443
|
}
|
|
222
444
|
`,
|
|
223
|
-
variables: { id: projectId }
|
|
445
|
+
variables: { id: projectId },
|
|
446
|
+
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
224
447
|
});
|
|
225
448
|
if (response.errors)
|
|
226
449
|
return;
|
|
227
|
-
this.project = ((
|
|
450
|
+
this.project = ((_b = response.data) === null || _b === void 0 ? void 0 : _b.project) || {};
|
|
451
|
+
this.updateContext();
|
|
452
|
+
this._loadPendingMetrics();
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* 이 프로젝트의 미입력 KPI metric 들을 조회 — Step 1/2 와 동일 기준.
|
|
456
|
+
* - 대상: active=true 인 manual/import metric (자동수집/외부API 제외)
|
|
457
|
+
* - 판정:
|
|
458
|
+
* · MONTH metric → 전월 row (periodType='MONTH', valueDate startsWith 전월) 없으면 pending
|
|
459
|
+
* · 그 외 → (metric, periodType) 매칭 row 없으면 pending
|
|
460
|
+
*/
|
|
461
|
+
async _loadPendingMetrics() {
|
|
462
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
463
|
+
if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
464
|
+
this.pendingMetrics = [];
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
// 1) Active manual/import metric 정의 목록 (periodType 포함)
|
|
469
|
+
const metricsResp = await client.query({
|
|
470
|
+
query: gql `
|
|
471
|
+
query KpiMetrics {
|
|
472
|
+
kpiMetrics {
|
|
473
|
+
items {
|
|
474
|
+
id
|
|
475
|
+
name
|
|
476
|
+
description
|
|
477
|
+
periodType
|
|
478
|
+
collectType
|
|
479
|
+
active
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
`,
|
|
484
|
+
context: { headers: tenantHeaders((_b = this.project) === null || _b === void 0 ? void 0 : _b.code) }
|
|
485
|
+
});
|
|
486
|
+
// collectType 필터 제거 — Step 1/2 가 모든 metric 을 보여주므로 동일하게 전체 대상.
|
|
487
|
+
// active=false 인 metric 만 제외.
|
|
488
|
+
const targets = (((_d = (_c = metricsResp.data) === null || _c === void 0 ? void 0 : _c.kpiMetrics) === null || _d === void 0 ? void 0 : _d.items) || []).filter((m) => m.active);
|
|
489
|
+
if (targets.length === 0) {
|
|
490
|
+
this.pendingMetrics = [];
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// 2) 이 프로젝트의 모든 KpiMetricValue (metric.name 포함 — 2D 차트의 latest lookup 에도 사용)
|
|
494
|
+
const valuesResp = await client.query({
|
|
495
|
+
query: gql `
|
|
496
|
+
query KpiMetricValues($filters: [Filter!]) {
|
|
497
|
+
kpiMetricValues(filters: $filters) {
|
|
498
|
+
items {
|
|
499
|
+
metricId
|
|
500
|
+
periodType
|
|
501
|
+
valueDate
|
|
502
|
+
value
|
|
503
|
+
metric {
|
|
504
|
+
id
|
|
505
|
+
name
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
`,
|
|
511
|
+
variables: { filters: [{ name: 'org', operator: 'eq', value: this.project.id }] },
|
|
512
|
+
context: { headers: tenantHeaders((_e = this.project) === null || _e === void 0 ? void 0 : _e.code) }
|
|
513
|
+
});
|
|
514
|
+
const values = ((_g = (_f = valuesResp.data) === null || _f === void 0 ? void 0 : _f.kpiMetricValues) === null || _g === void 0 ? void 0 : _g.items) || [];
|
|
515
|
+
this.allKpiMetricValues = values;
|
|
516
|
+
const lastMonthYm = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM');
|
|
517
|
+
// 3) Step 1/2 와 동일 기준으로 미입력 판정 — row 존재 AND value 존재 (null/undefined 아님).
|
|
518
|
+
const hasValid = (v) => v.value !== null && v.value !== undefined;
|
|
519
|
+
this.pendingMetrics = targets.filter((m) => {
|
|
520
|
+
if (m.periodType === 'MONTH') {
|
|
521
|
+
return !values.some(v => v.metricId === m.id && v.periodType === 'MONTH' && (v.valueDate || '').startsWith(lastMonthYm) && hasValid(v));
|
|
522
|
+
}
|
|
523
|
+
return !values.some(v => v.metricId === m.id && v.periodType === m.periodType && hasValid(v));
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
console.error('[입력 대기 중] 조회 실패', e);
|
|
528
|
+
this.pendingMetrics = [];
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async getKpiComprehensiveStats() {
|
|
532
|
+
var _a;
|
|
533
|
+
try {
|
|
534
|
+
const response = await client.query({
|
|
535
|
+
query: gql `
|
|
536
|
+
query GetKpiZValueComprehensiveStats {
|
|
537
|
+
totalKpiZValueComprehensiveStats {
|
|
538
|
+
minVal
|
|
539
|
+
q1Val
|
|
540
|
+
medVal
|
|
541
|
+
q3Val
|
|
542
|
+
maxVal
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
`,
|
|
546
|
+
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
547
|
+
});
|
|
548
|
+
this.kpiComprehensiveStats = response.data.totalKpiZValueComprehensiveStats || [];
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
console.error('Failed to fetch KPI comprehensive stats:', error);
|
|
552
|
+
this.kpiComprehensiveStats = [];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async getKpiYComprehensiveStats() {
|
|
556
|
+
var _a;
|
|
557
|
+
try {
|
|
558
|
+
const response = await client.query({
|
|
559
|
+
query: gql `
|
|
560
|
+
query GetKpiYValueComprehensiveStats {
|
|
561
|
+
totalKpiYValueComprehensiveStats {
|
|
562
|
+
kpiName
|
|
563
|
+
minVal
|
|
564
|
+
q1Val
|
|
565
|
+
medVal
|
|
566
|
+
q3Val
|
|
567
|
+
maxVal
|
|
568
|
+
avgVal
|
|
569
|
+
projectCount
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
`,
|
|
573
|
+
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
574
|
+
});
|
|
575
|
+
this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
console.error('Failed to fetch KPI Y comprehensive stats:', error);
|
|
579
|
+
this.kpiYComprehensiveStats = [];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async getProjectXKpiValues(projectId) {
|
|
583
|
+
var _a;
|
|
584
|
+
try {
|
|
585
|
+
const response = await client.query({
|
|
586
|
+
query: gql `
|
|
587
|
+
query GetProjectXKpiValues($projectId: String!) {
|
|
588
|
+
projectXKpiValues(projectId: $projectId) {
|
|
589
|
+
id
|
|
590
|
+
value
|
|
591
|
+
score
|
|
592
|
+
valueDate
|
|
593
|
+
kpi {
|
|
594
|
+
id
|
|
595
|
+
name
|
|
596
|
+
description
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
`,
|
|
601
|
+
variables: { projectId },
|
|
602
|
+
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
603
|
+
});
|
|
604
|
+
this.projectXKpiValues = response.data.projectXKpiValues || [];
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
console.error('Failed to fetch project X KPI values:', error);
|
|
608
|
+
this.projectXKpiValues = [];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
_onMetricLabelClick(metric) {
|
|
612
|
+
// metric.pattern을 사용하여 X-KPI 조회
|
|
613
|
+
if (!metric.pattern) {
|
|
614
|
+
console.warn('No pattern found for metric:', metric);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
// X-KPI 이름 패턴 (예: X11, X12, X21, ...)
|
|
618
|
+
const kpiNamePattern = metric.pattern;
|
|
619
|
+
// 해당 KPI 정보 조회
|
|
620
|
+
this._fetchKpiWithGradesByPattern(kpiNamePattern, metric.label);
|
|
621
|
+
}
|
|
622
|
+
async _fetchKpiWithGradesByPattern(kpiNamePattern, displayName) {
|
|
623
|
+
var _a, _b, _c, _d, _e;
|
|
624
|
+
try {
|
|
625
|
+
// X-KPI 이름 패턴으로 전체 이름 찾기 (projectXKpiValues에서) — 선두 코드 토큰 정확 매칭
|
|
626
|
+
const codeOf = (name) => (name || '').trim().split(/[.\s]/)[0];
|
|
627
|
+
const kpiValue = this.projectXKpiValues.find(kv => { var _a; return ((_a = kv.kpi) === null || _a === void 0 ? void 0 : _a.name) && codeOf(kv.kpi.name) === kpiNamePattern; });
|
|
628
|
+
if (!kpiValue || !kpiValue.kpi) {
|
|
629
|
+
console.warn(`No KPI found for pattern: ${kpiNamePattern}`);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
// GraphQL로 KPI 상세 정보 (grades 포함) 조회
|
|
633
|
+
const response = await client.query({
|
|
634
|
+
query: gql `
|
|
635
|
+
query Kpi($id: String!) {
|
|
636
|
+
kpi(id: $id) {
|
|
637
|
+
id
|
|
638
|
+
name
|
|
639
|
+
grades
|
|
640
|
+
scoreType
|
|
641
|
+
valueType
|
|
642
|
+
vizMeta
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
`,
|
|
646
|
+
variables: { id: kpiValue.kpi.id },
|
|
647
|
+
context: { headers: tenantHeaders((_a = this.project) === null || _a === void 0 ? void 0 : _a.code) }
|
|
648
|
+
});
|
|
649
|
+
if (response.errors || !((_b = response.data) === null || _b === void 0 ? void 0 : _b.kpi)) {
|
|
650
|
+
console.error('KPI not found or error:', response.errors);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const kpi = response.data.kpi;
|
|
654
|
+
// grades가 없으면 경고
|
|
655
|
+
const is2D = kpi.grades && !Array.isArray(kpi.grades) && kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP';
|
|
656
|
+
if (!kpi.grades || (!is2D && (!Array.isArray(kpi.grades) || kpi.grades.length === 0))) {
|
|
657
|
+
console.warn(`No grades found for KPI: ${kpiValue.kpi.name}`);
|
|
658
|
+
}
|
|
659
|
+
this.selectedKpi = {
|
|
660
|
+
kpiName: displayName,
|
|
661
|
+
pattern: kpiNamePattern, // X34/X43 등 — 계단형 차트 라우팅용
|
|
662
|
+
grades: kpi.grades || [],
|
|
663
|
+
scoreType: kpi.scoreType || '',
|
|
664
|
+
valueType: kpi.valueType || '',
|
|
665
|
+
value: (_c = kpiValue.value) !== null && _c !== void 0 ? _c : null,
|
|
666
|
+
score: (_d = kpiValue.score) !== null && _d !== void 0 ? _d : null,
|
|
667
|
+
unit: ((_e = kpi.vizMeta) === null || _e === void 0 ? void 0 : _e.unit) || ''
|
|
668
|
+
};
|
|
669
|
+
this.showScoreDistribution = true;
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
console.error('Error fetching KPI:', error);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* 등급 기준으로 표시할지 판별 (차트 대신 등급 설명 목록)
|
|
677
|
+
*
|
|
678
|
+
* - valueType=ASSESSED: 감리자 직접 평가 → 등급 설명
|
|
679
|
+
* - CUSTOM/COMPOSITE이지만 value가 1~5 정수 (증강데이터): 등급 설명 + 히트맵
|
|
680
|
+
* - grades가 배열이 아닌 경우 (2D lookup 객체): 등급 설명 + 히트맵
|
|
681
|
+
*/
|
|
682
|
+
_isAssessmentOrIntegerScore() {
|
|
683
|
+
const kpi = this.selectedKpi;
|
|
684
|
+
if (!kpi)
|
|
685
|
+
return false;
|
|
686
|
+
// valueType 기반 판별 (우선)
|
|
687
|
+
if (kpi.valueType === 'ASSESSED')
|
|
688
|
+
return true;
|
|
689
|
+
// CUSTOM/COMPOSITE이지만 value가 1~5 정수 (증강데이터)
|
|
690
|
+
if ((kpi.scoreType === 'CUSTOM' || kpi.valueType === 'COMPOSITE') &&
|
|
691
|
+
kpi.value !== null &&
|
|
692
|
+
kpi.value === Math.floor(kpi.value) &&
|
|
693
|
+
kpi.value >= 1 &&
|
|
694
|
+
kpi.value <= 5)
|
|
695
|
+
return true;
|
|
696
|
+
// grades가 배열이 아닌 경우 (2D lookup 객체)
|
|
697
|
+
if (kpi.grades && !Array.isArray(kpi.grades))
|
|
698
|
+
return true;
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
/** 등급 기준 표시 (ASSESSED / 1~5 정수 score) — 현재 값 강조 + CUSTOM은 2D 히트맵도 표시 */
|
|
702
|
+
_renderAssessmentGrades() {
|
|
703
|
+
var _a;
|
|
704
|
+
const kpi = this.selectedKpi;
|
|
705
|
+
// score 가 진짜 등급. kpi.value 는 raw 측정값 (편차율 등) 이라 score 로 쓰면 안 됨.
|
|
706
|
+
const currentScore = (kpi === null || kpi === void 0 ? void 0 : kpi.score) != null ? Math.round(kpi.score) : 0;
|
|
707
|
+
const grades = Array.isArray(kpi === null || kpi === void 0 ? void 0 : kpi.grades) ? kpi.grades : [];
|
|
708
|
+
const colors = ['#e53935', '#ff9800', '#ffca28', '#66bb6a', '#2e7d32'];
|
|
709
|
+
const is2D = (kpi === null || kpi === void 0 ? void 0 : kpi.scoreType) === 'CUSTOM' ||
|
|
710
|
+
(kpi === null || kpi === void 0 ? void 0 : kpi.valueType) === 'COMPOSITE' ||
|
|
711
|
+
((kpi === null || kpi === void 0 ? void 0 : kpi.grades) &&
|
|
712
|
+
typeof kpi.grades === 'object' &&
|
|
713
|
+
!Array.isArray(kpi.grades) &&
|
|
714
|
+
kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP');
|
|
715
|
+
// grades가 있으면 description 사용, 없으면 기본 라벨
|
|
716
|
+
const defaultLabels = ['매우 미흡', '미흡', '보통', '양호', '우수'];
|
|
717
|
+
const items = [1, 2, 3, 4, 5].map(score => {
|
|
718
|
+
const grade = grades.find((g) => g.score === score);
|
|
719
|
+
return {
|
|
720
|
+
score,
|
|
721
|
+
desc: (grade === null || grade === void 0 ? void 0 : grade.description) || (grade === null || grade === void 0 ? void 0 : grade.name) || defaultLabels[score - 1],
|
|
722
|
+
color: colors[score - 1],
|
|
723
|
+
active: score === currentScore
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
// ASSESSED(감리자 평가)이고 grades에 description이 있는 경우만 한 줄씩 상세 표시
|
|
727
|
+
// X13 등 COMPOSITE/CUSTOM은 인라인 칩으로 간결하게
|
|
728
|
+
const hasDescriptions = !is2D && grades.length > 0 && grades.some((g) => g.description);
|
|
729
|
+
// ASSESSMENT (감리자 평가) 의 등급 description 만 노출. 2D lookup 은 score 가 이미
|
|
730
|
+
// popup-header 큰 점수로 표시되므로 등급 기준 행/동그라미 중복 제거.
|
|
731
|
+
return html `
|
|
732
|
+
<div style="padding: 20px; display: flex; flex-direction: column; height: 100%;">
|
|
733
|
+
${hasDescriptions
|
|
734
|
+
? items.map(item => html ` <div
|
|
735
|
+
style="
|
|
736
|
+
display: flex; align-items: center; gap: 10px;
|
|
737
|
+
padding: 10px 14px; margin-bottom: 6px;
|
|
738
|
+
border-radius: 8px;
|
|
739
|
+
background: ${item.active ? item.color + '18' : '#f9f9f9'};
|
|
740
|
+
border: 2px solid ${item.active ? item.color : 'transparent'};
|
|
741
|
+
transition: all 0.2s;
|
|
742
|
+
"
|
|
743
|
+
>
|
|
744
|
+
<span
|
|
745
|
+
style="
|
|
746
|
+
width: 32px; height: 32px; border-radius: 50%;
|
|
747
|
+
background: ${item.active ? item.color : '#e0e0e0'};
|
|
748
|
+
color: ${item.active ? '#fff' : '#999'};
|
|
749
|
+
display: flex; align-items: center; justify-content: center;
|
|
750
|
+
font-weight: 700; font-size: 14px; flex-shrink: 0;
|
|
751
|
+
"
|
|
752
|
+
>${item.score}</span
|
|
753
|
+
>
|
|
754
|
+
<span
|
|
755
|
+
style="font-size: 0.9rem; color: ${item.active ? '#333' : '#666'}; font-weight: ${item.active ? '600' : '400'};"
|
|
756
|
+
>
|
|
757
|
+
${item.desc}
|
|
758
|
+
</span>
|
|
759
|
+
</div>`)
|
|
760
|
+
: ''}
|
|
761
|
+
${is2D && ((_a = kpi === null || kpi === void 0 ? void 0 : kpi.grades) === null || _a === void 0 ? void 0 : _a.rows)
|
|
762
|
+
? html `
|
|
763
|
+
<div style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
|
764
|
+
<div style="font-size: 0.9rem; font-weight: 700; color: #333; margin-bottom: 8px;">
|
|
765
|
+
공정률 × 공기편차 성과 분포
|
|
766
|
+
</div>
|
|
767
|
+
<div style="flex: 1; min-height: 0;">
|
|
768
|
+
<kpi-2d-lookup-chart
|
|
769
|
+
.grades=${kpi.grades}
|
|
770
|
+
.value=${this._getScheduleDeviation()}
|
|
771
|
+
.progressRate=${this._getProgressRate()}
|
|
772
|
+
></kpi-2d-lookup-chart>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
`
|
|
776
|
+
: ''}
|
|
777
|
+
</div>
|
|
778
|
+
`;
|
|
779
|
+
}
|
|
780
|
+
_closePopup() {
|
|
781
|
+
this.showScoreDistribution = false;
|
|
782
|
+
this.selectedKpi = null;
|
|
783
|
+
}
|
|
784
|
+
getMetricValue(kpiNamePattern) {
|
|
785
|
+
// KPI 이름 "X52. 환경성과 수준 평가" 의 선두 코드 토큰("X52")을 정확히 매칭.
|
|
786
|
+
// includes 부분매칭은 유사코드 오매칭 위험이 있어 코드 토큰 === 로 비교한다.
|
|
787
|
+
const codeOf = (name) => (name || '').trim().split(/[.\s]/)[0];
|
|
788
|
+
const matches = this.projectXKpiValues.filter(kv => { var _a; return ((_a = kv.kpi) === null || _a === void 0 ? void 0 : _a.name) && codeOf(kv.kpi.name) === kpiNamePattern; });
|
|
789
|
+
// 같은 코드가 여러 scope/기간으로 중복될 수 있어 최신 valueDate 를 대표값으로 선택.
|
|
790
|
+
const kpiValue = matches.sort((a, b) => (b.valueDate || '').localeCompare(a.valueDate || ''))[0];
|
|
791
|
+
const value = (kpiValue === null || kpiValue === void 0 ? void 0 : kpiValue.value) || 0;
|
|
792
|
+
// 성과영역별 KPI 측정값: 정수는 그대로, 소수는 소숫점 2자리로 표시
|
|
793
|
+
if (Number.isInteger(value)) {
|
|
794
|
+
return value.toString();
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
return value.toFixed(2);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
getYKpiScoreFormatted(kpiName) {
|
|
801
|
+
const value = this.getProjectYKpiValue(kpiName);
|
|
802
|
+
if (value === 0)
|
|
803
|
+
return '-';
|
|
804
|
+
return (value * 100).toFixed(1);
|
|
805
|
+
}
|
|
806
|
+
getMetricGroups() {
|
|
807
|
+
return {
|
|
808
|
+
Y1: {
|
|
809
|
+
name: '일정성과 (Y1)',
|
|
810
|
+
score: this.getYKpiScoreFormatted('일정성과(Y1)'),
|
|
811
|
+
metrics: [
|
|
812
|
+
{ label: '연면적 대비 공사기간', pattern: 'X11', value: this.getMetricValue('X11') },
|
|
813
|
+
{ label: '설계변경 공기 증감률', pattern: 'X12', value: this.getMetricValue('X12') },
|
|
814
|
+
{ label: '일정 이탈 수준', pattern: 'X13', value: this.getMetricValue('X13') },
|
|
815
|
+
{ label: '일정성과 정성 평가', pattern: 'X14', value: this.getMetricValue('X14') }
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
Y2: {
|
|
819
|
+
name: '비용성과 (Y2)',
|
|
820
|
+
score: this.getYKpiScoreFormatted('비용성과(Y2)'),
|
|
821
|
+
metrics: [
|
|
822
|
+
{ label: '연면적 대비 공사비', pattern: 'X21', value: this.getMetricValue('X21') },
|
|
823
|
+
{ label: '설계변경 공사비 증감률', pattern: 'X22', value: this.getMetricValue('X22') },
|
|
824
|
+
{ label: '비용성과 정성 평가', pattern: 'X23', value: this.getMetricValue('X23') }
|
|
825
|
+
]
|
|
826
|
+
},
|
|
827
|
+
Y3: {
|
|
828
|
+
name: '품질성과 (Y3)',
|
|
829
|
+
score: this.getYKpiScoreFormatted('품질성과(Y3)'),
|
|
830
|
+
metrics: [
|
|
831
|
+
{ label: '검측 불합격률', pattern: 'X33', value: this.getMetricValue('X33') },
|
|
832
|
+
{ label: '품질 SL-PA 결과', pattern: 'X34', value: this.getMetricValue('X34') },
|
|
833
|
+
{ label: '품질성과 정성 평가', pattern: 'X35', value: this.getMetricValue('X35') }
|
|
834
|
+
]
|
|
835
|
+
},
|
|
836
|
+
Y4: {
|
|
837
|
+
name: '안전성과 (Y4)',
|
|
838
|
+
score: this.getYKpiScoreFormatted('Y4. 안전성과'),
|
|
839
|
+
metrics: [
|
|
840
|
+
{ label: '재해율', pattern: 'X41', value: this.getMetricValue('X41') },
|
|
841
|
+
{ label: '재해강도율', pattern: 'X42', value: this.getMetricValue('X42') },
|
|
842
|
+
{ label: '안전 SL-PA 결과', pattern: 'X43', value: this.getMetricValue('X43') },
|
|
843
|
+
{ label: '안전성과 정성 평가', pattern: 'X44', value: this.getMetricValue('X44') }
|
|
844
|
+
]
|
|
845
|
+
},
|
|
846
|
+
Y5: {
|
|
847
|
+
name: '환경성과 (Y5)',
|
|
848
|
+
score: this.getYKpiScoreFormatted('환경성과(Y5)'),
|
|
849
|
+
metrics: [
|
|
850
|
+
{ label: '건설폐기물 발생량', pattern: 'X51', value: this.getMetricValue('X51') },
|
|
851
|
+
{ label: '환경성과 정성 평가', pattern: 'X52', value: this.getMetricValue('X52') }
|
|
852
|
+
]
|
|
853
|
+
},
|
|
854
|
+
Y6: {
|
|
855
|
+
name: '생산성 성과 (Y6)',
|
|
856
|
+
score: this.getYKpiScoreFormatted('생산성성과(Y6)'),
|
|
857
|
+
metrics: [
|
|
858
|
+
{ label: '총 투입인력(M/M) 생산성', pattern: 'X61', value: this.getMetricValue('X61') },
|
|
859
|
+
{ label: '생산성성과 정성 평가', pattern: 'X62', value: this.getMetricValue('X62') }
|
|
860
|
+
]
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
getYKpiBoxplotData() {
|
|
865
|
+
// kpiName 표기가 환경마다 다양('Y1. 일정성과' / 'Y1. 일정' / '일정성과(Y1)' 등)하므로
|
|
866
|
+
// Y1~Y6 코드만 추출해 짧은 X축 라벨로 매핑 — 전체 문자열에 의존하지 않아 견고함.
|
|
867
|
+
const yLabelByCode = {
|
|
868
|
+
Y1: '일정',
|
|
869
|
+
Y2: '비용',
|
|
870
|
+
Y3: '품질',
|
|
871
|
+
Y4: '안전',
|
|
872
|
+
Y5: '환경',
|
|
873
|
+
Y6: '생산성'
|
|
874
|
+
};
|
|
875
|
+
const data = [];
|
|
876
|
+
// Y-KPI 카테고리별로 통합된 통계 생성
|
|
877
|
+
this.kpiYComprehensiveStats.forEach(stat => {
|
|
878
|
+
var _a, _b;
|
|
879
|
+
const category = yLabelByCode[(_b = (_a = (stat.kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''] || stat.kpiName;
|
|
880
|
+
data.push({
|
|
881
|
+
org: category, // org 필드 사용 (boxplot 차트가 기대하는 필드명)
|
|
882
|
+
min: stat.minVal * 100,
|
|
883
|
+
max: stat.maxVal * 100,
|
|
884
|
+
q1: stat.q1Val * 100,
|
|
885
|
+
q3: stat.q3Val * 100,
|
|
886
|
+
median: stat.medVal * 100,
|
|
887
|
+
mean: stat.avgVal * 100,
|
|
888
|
+
value: this.getProjectYKpiValue(stat.kpiName) * 100 // 현재 프로젝트의 실제 Y-KPI 값 (0~1 → 0~20)
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
// 가나다순(localeCompare)이 아니라 Y1~Y6 고정 순서로 정렬 (일정·비용·품질·안전·환경·생산성)
|
|
892
|
+
const ORDER = ['일정', '비용', '품질', '안전', '환경', '생산성'];
|
|
893
|
+
return data.sort((a, b) => ORDER.indexOf(a.org) - ORDER.indexOf(b.org));
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* KpiMetric.name 이 주어진 이름인 KpiMetricValue 중 가장 최근 valueDate 의 value.
|
|
897
|
+
* 2D lookup chart 의 progressRate / value 도출용.
|
|
898
|
+
*/
|
|
899
|
+
_getLatestMetricValue(metricName) {
|
|
900
|
+
const candidates = this.allKpiMetricValues
|
|
901
|
+
.filter(v => { var _a; return ((_a = v.metric) === null || _a === void 0 ? void 0 : _a.name) === metricName && v.value !== null && v.value !== undefined; })
|
|
902
|
+
.sort((a, b) => (b.valueDate || '').localeCompare(a.valueDate || ''));
|
|
903
|
+
return candidates.length > 0 ? candidates[0].value : null;
|
|
904
|
+
}
|
|
905
|
+
/** 2D lookup chart 의 progressRate (X축, 실적공정율 %) — 이름이 '실적공정' 을 포함하는
|
|
906
|
+
* metric 의 latest 값 우선(율/률·공백·코드 접두 차이 허용, '계획공정' 과는 구분). 없으면 fallback. */
|
|
907
|
+
_getProgressRate() {
|
|
908
|
+
var _a, _b;
|
|
909
|
+
const candidates = this.allKpiMetricValues
|
|
910
|
+
// 월별(MONTH) 행만 — 옛 일별(DAY) 잔재가 valueDate 로 더 최신이라 잘못 집히던 것 방지.
|
|
911
|
+
.filter(v => { var _a; return (((_a = v.metric) === null || _a === void 0 ? void 0 : _a.name) || '').includes('실적공정') && v.periodType === 'MONTH' && v.value !== null && v.value !== undefined; })
|
|
912
|
+
.sort((a, b) => (b.valueDate || '').localeCompare(a.valueDate || ''));
|
|
913
|
+
if (candidates.length > 0)
|
|
914
|
+
return candidates[0].value;
|
|
915
|
+
return (_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.totalProgress) !== null && _b !== void 0 ? _b : 50;
|
|
916
|
+
}
|
|
917
|
+
/** 2D lookup chart 의 value (Y축, 공기편차) — X13(일정 이탈 수준)이 formula
|
|
918
|
+
* (`[계획공정률] − [실적공정률]`)로 계산한 값을 그대로 사용한다. 산식은 X13 한 곳에만
|
|
919
|
+
* 두고, 클라는 재계산하지 않는다(단일 소스). 값이 없으면 '일정 이탈 수준' metric 폴백. */
|
|
920
|
+
_getScheduleDeviation() {
|
|
921
|
+
var _a, _b;
|
|
922
|
+
if (((_a = this.selectedKpi) === null || _a === void 0 ? void 0 : _a.value) !== undefined && ((_b = this.selectedKpi) === null || _b === void 0 ? void 0 : _b.value) !== null)
|
|
923
|
+
return this.selectedKpi.value;
|
|
924
|
+
return this._getLatestMetricValue('일정 이탈 수준');
|
|
925
|
+
}
|
|
926
|
+
getProjectYKpiValue(kpiName) {
|
|
927
|
+
var _a, _b, _c;
|
|
928
|
+
// kpiName 표기가 'Y1. 일정성과' / '일정성과(Y1)' 등으로 달라 정확 매칭은 자주 실패함.
|
|
929
|
+
// Y1~Y6 코드만 추출해 매칭 → 형식 차이와 무관하게 값 조회.
|
|
930
|
+
const code = (_a = (kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0];
|
|
931
|
+
const kpiValue = (_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.kpiValues) === null || _c === void 0 ? void 0 : _c.find(kv => { var _a; return code ? ((_a = (kv.kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) === code : kv.kpiName === kpiName; });
|
|
932
|
+
return (kpiValue === null || kpiValue === void 0 ? void 0 : kpiValue.value) || 0;
|
|
933
|
+
}
|
|
934
|
+
getRadarChartData() {
|
|
935
|
+
// kpiName 표기가 다양하므로 Y1~Y6 코드만 추출해 짧은 라벨로 매핑 (박스플롯과 동일 방식).
|
|
936
|
+
const yLabelByCode = {
|
|
937
|
+
Y1: '일정',
|
|
938
|
+
Y2: '비용',
|
|
939
|
+
Y3: '품질',
|
|
940
|
+
Y4: '안전',
|
|
941
|
+
Y5: '환경',
|
|
942
|
+
Y6: '생산성'
|
|
943
|
+
};
|
|
944
|
+
const data = [];
|
|
945
|
+
const categories = [];
|
|
946
|
+
// Create baseline data from comprehensive stats (using avgVal as baseline)
|
|
947
|
+
this.kpiYComprehensiveStats.forEach(stat => {
|
|
948
|
+
var _a, _b;
|
|
949
|
+
const category = yLabelByCode[(_b = (_a = (stat.kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''] || stat.kpiName;
|
|
950
|
+
if (!categories.includes(category)) {
|
|
951
|
+
categories.push(category);
|
|
952
|
+
}
|
|
953
|
+
// Add baseline average
|
|
954
|
+
// 성과영역별 측정값(getYKpiScoreFormatted, toFixed(1))과 소수 1자리로 일치시킨다.
|
|
955
|
+
// 과거엔 Math.round(정수)라 radar 76 vs 측정값 76.2 처럼 반올림 불일치가 있었음.
|
|
956
|
+
data.push({
|
|
957
|
+
org: '기준평균',
|
|
958
|
+
category,
|
|
959
|
+
value: Number((stat.avgVal * 100).toFixed(1)) // Scale to match expected range (0~1 → 0~100)
|
|
960
|
+
});
|
|
961
|
+
// Add current project performance (using project's individual values)
|
|
962
|
+
data.push({
|
|
963
|
+
org: '프로젝트성과',
|
|
964
|
+
category,
|
|
965
|
+
value: Number((this.getProjectYKpiValue(stat.kpiName) * 100).toFixed(1)) // Y-KPI 값 (0~1 → 0~100)
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
return {
|
|
969
|
+
data,
|
|
970
|
+
categories: categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성']
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
getBoxPlotDataForProject(project) {
|
|
974
|
+
var _a;
|
|
975
|
+
// 프로젝트의 지역(geo_group)에 해당하는 통계 찾기
|
|
976
|
+
const stats = (_a = this.kpiComprehensiveStats) === null || _a === void 0 ? void 0 : _a[0];
|
|
977
|
+
if (!stats) {
|
|
978
|
+
// 전체 통계의 평균값 사용 또는 기본값 반환
|
|
979
|
+
const defaultStats = this.kpiComprehensiveStats.length > 0 ? this.kpiComprehensiveStats[0] : null;
|
|
980
|
+
if (defaultStats) {
|
|
981
|
+
return {
|
|
982
|
+
min: defaultStats.minVal,
|
|
983
|
+
max: defaultStats.maxVal,
|
|
984
|
+
mean: (defaultStats.q1Val + defaultStats.q3Val) / 2,
|
|
985
|
+
median: defaultStats.medVal,
|
|
986
|
+
q1: defaultStats.q1Val,
|
|
987
|
+
q3: defaultStats.q3Val,
|
|
988
|
+
value: project.kpi || 0
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
// 완전한 기본값
|
|
992
|
+
return {
|
|
993
|
+
min: 0,
|
|
994
|
+
max: 100,
|
|
995
|
+
mean: 50,
|
|
996
|
+
median: 50,
|
|
997
|
+
q1: 25,
|
|
998
|
+
q3: 75,
|
|
999
|
+
value: project.kpi || 0
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
min: stats.minVal * 100,
|
|
1004
|
+
max: stats.maxVal * 100,
|
|
1005
|
+
mean: ((stats.q1Val + stats.q3Val) / 2) * 100, // 대략적 평균값
|
|
1006
|
+
median: stats.medVal * 100,
|
|
1007
|
+
q1: stats.q1Val * 100,
|
|
1008
|
+
q3: stats.q3Val * 100,
|
|
1009
|
+
value: project.kpi || 0
|
|
1010
|
+
};
|
|
228
1011
|
}
|
|
229
1012
|
};
|
|
230
1013
|
SvProjectDetailPage.styles = [
|
|
1014
|
+
ScrollbarStyles,
|
|
231
1015
|
css `
|
|
232
1016
|
:host {
|
|
233
1017
|
display: flex;
|
|
@@ -242,6 +1026,8 @@ SvProjectDetailPage.styles = [
|
|
|
242
1026
|
/* content layout */
|
|
243
1027
|
div[content] {
|
|
244
1028
|
display: flex;
|
|
1029
|
+
/* 미러링: KPI(right)를 좌측·넓게, 정보(left)를 우측·좁게 */
|
|
1030
|
+
flex-direction: row-reverse;
|
|
245
1031
|
gap: 35px;
|
|
246
1032
|
padding: 25px;
|
|
247
1033
|
}
|
|
@@ -273,6 +1059,16 @@ SvProjectDetailPage.styles = [
|
|
|
273
1059
|
color: #35618e;
|
|
274
1060
|
font-weight: 700;
|
|
275
1061
|
}
|
|
1062
|
+
/* 카드 헤더 우상단 버튼 (프로젝트 수정 / KPI 데이타입력) */
|
|
1063
|
+
.card-title .header-btn {
|
|
1064
|
+
margin-left: auto;
|
|
1065
|
+
padding: 4px 10px;
|
|
1066
|
+
font-size: 13px;
|
|
1067
|
+
font-weight: 500;
|
|
1068
|
+
}
|
|
1069
|
+
.card-title .header-btn md-icon {
|
|
1070
|
+
--md-icon-size: 18px;
|
|
1071
|
+
}
|
|
276
1072
|
.triangle {
|
|
277
1073
|
width: 0;
|
|
278
1074
|
height: 0;
|
|
@@ -314,7 +1110,7 @@ SvProjectDetailPage.styles = [
|
|
|
314
1110
|
letter-spacing: -0.05em;
|
|
315
1111
|
}
|
|
316
1112
|
.value {
|
|
317
|
-
color: #
|
|
1113
|
+
color: #333;
|
|
318
1114
|
font-size: 16px;
|
|
319
1115
|
}
|
|
320
1116
|
.status-value {
|
|
@@ -407,16 +1203,23 @@ SvProjectDetailPage.styles = [
|
|
|
407
1203
|
flex-direction: column;
|
|
408
1204
|
gap: 10px;
|
|
409
1205
|
padding: 15px;
|
|
1206
|
+
max-height: 420px;
|
|
1207
|
+
min-width: 560px;
|
|
410
1208
|
}
|
|
411
1209
|
.kpi-header {
|
|
412
1210
|
display: flex;
|
|
413
1211
|
align-items: center;
|
|
414
|
-
|
|
1212
|
+
justify-content: space-between;
|
|
415
1213
|
color: #35618e;
|
|
416
1214
|
font-weight: 700;
|
|
417
1215
|
font-size: 20px;
|
|
418
1216
|
letter-spacing: -0.05em;
|
|
419
1217
|
}
|
|
1218
|
+
.kpi-header div {
|
|
1219
|
+
display: flex;
|
|
1220
|
+
align-items: center;
|
|
1221
|
+
gap: 5px;
|
|
1222
|
+
}
|
|
420
1223
|
.kpi-desc {
|
|
421
1224
|
color: #35618e;
|
|
422
1225
|
font-size: 13px;
|
|
@@ -425,18 +1228,23 @@ SvProjectDetailPage.styles = [
|
|
|
425
1228
|
.kpi-summary {
|
|
426
1229
|
display: flex;
|
|
427
1230
|
align-items: center;
|
|
428
|
-
gap: 35px;
|
|
429
1231
|
padding: 0 30px;
|
|
1232
|
+
max-height: 360px;
|
|
1233
|
+
gap: 20px;
|
|
430
1234
|
}
|
|
431
1235
|
.mini-boxplot {
|
|
432
|
-
|
|
1236
|
+
flex: 1;
|
|
1237
|
+
display: flex;
|
|
1238
|
+
flex-direction: column;
|
|
433
1239
|
height: 100%;
|
|
434
1240
|
position: relative;
|
|
1241
|
+
min-width: 120px;
|
|
435
1242
|
}
|
|
436
1243
|
.boxplot-area {
|
|
1244
|
+
flex: 1;
|
|
437
1245
|
width: 100%;
|
|
438
|
-
height: 190px;
|
|
439
1246
|
display: flex;
|
|
1247
|
+
flex-direction: column;
|
|
440
1248
|
align-items: center;
|
|
441
1249
|
background: #ffffff;
|
|
442
1250
|
}
|
|
@@ -445,15 +1253,13 @@ SvProjectDetailPage.styles = [
|
|
|
445
1253
|
display: flex;
|
|
446
1254
|
flex-direction: column;
|
|
447
1255
|
align-items: center;
|
|
448
|
-
gap: 6px;
|
|
449
1256
|
color: #35618e;
|
|
450
1257
|
}
|
|
451
1258
|
.boxplot-point .value {
|
|
452
|
-
font-size:
|
|
453
|
-
font-weight:
|
|
1259
|
+
font-size: 42px;
|
|
1260
|
+
font-weight: 800;
|
|
454
1261
|
color: #35618e;
|
|
455
1262
|
border-radius: 6px;
|
|
456
|
-
padding: 4px 8px;
|
|
457
1263
|
}
|
|
458
1264
|
.boxplot-point .label {
|
|
459
1265
|
font-size: 14px;
|
|
@@ -463,11 +1269,13 @@ SvProjectDetailPage.styles = [
|
|
|
463
1269
|
|
|
464
1270
|
/* spider chart placeholder */
|
|
465
1271
|
.spider-area {
|
|
1272
|
+
flex: 5;
|
|
466
1273
|
display: flex;
|
|
1274
|
+
flex-direction: column;
|
|
467
1275
|
align-items: center;
|
|
468
1276
|
justify-content: center;
|
|
469
1277
|
gap: 10px;
|
|
470
|
-
|
|
1278
|
+
height: 100%;
|
|
471
1279
|
}
|
|
472
1280
|
|
|
473
1281
|
/* group distribution */
|
|
@@ -478,15 +1286,214 @@ SvProjectDetailPage.styles = [
|
|
|
478
1286
|
margin-top: 4px;
|
|
479
1287
|
}
|
|
480
1288
|
.group-chart {
|
|
481
|
-
height:
|
|
1289
|
+
height: 300px;
|
|
482
1290
|
margin-top: 25px;
|
|
483
1291
|
}
|
|
1292
|
+
|
|
1293
|
+
kpi-single-boxplot-chart {
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/* KPI 메트릭 데이터 카드 */
|
|
1298
|
+
.metrics-card {
|
|
1299
|
+
background: #ffffff;
|
|
1300
|
+
border-radius: 10px;
|
|
1301
|
+
padding: 20px;
|
|
1302
|
+
box-shadow: 3px 3px 3px 0 rgba(0, 0, 0, 0.1);
|
|
1303
|
+
max-width: 100%;
|
|
1304
|
+
overflow: hidden;
|
|
1305
|
+
}
|
|
1306
|
+
.metrics-header {
|
|
1307
|
+
display: flex;
|
|
1308
|
+
align-items: center;
|
|
1309
|
+
justify-content: space-between;
|
|
1310
|
+
}
|
|
1311
|
+
.metrics-header .title {
|
|
1312
|
+
display: flex;
|
|
1313
|
+
align-items: center;
|
|
1314
|
+
gap: 8px;
|
|
1315
|
+
color: #35618e;
|
|
1316
|
+
font-weight: 700;
|
|
1317
|
+
font-size: 18px;
|
|
1318
|
+
}
|
|
1319
|
+
.metrics-card .desc {
|
|
1320
|
+
color: #35618e;
|
|
1321
|
+
font-size: 13px;
|
|
1322
|
+
letter-spacing: -0.05em;
|
|
1323
|
+
margin-top: 4px;
|
|
1324
|
+
margin-bottom: 15px;
|
|
1325
|
+
}
|
|
1326
|
+
.metrics-content {
|
|
1327
|
+
display: flex;
|
|
1328
|
+
flex-direction: column;
|
|
1329
|
+
gap: 12px;
|
|
1330
|
+
}
|
|
1331
|
+
.metric-group {
|
|
1332
|
+
border-bottom: 1px dotted #ddd;
|
|
1333
|
+
padding-bottom: 12px;
|
|
1334
|
+
}
|
|
1335
|
+
.metric-group:last-child {
|
|
1336
|
+
border-bottom: none;
|
|
1337
|
+
padding-bottom: 0;
|
|
1338
|
+
}
|
|
1339
|
+
.metric-group-header {
|
|
1340
|
+
display: flex;
|
|
1341
|
+
align-items: center;
|
|
1342
|
+
gap: 8px;
|
|
1343
|
+
margin-bottom: 8px;
|
|
1344
|
+
color: #35618e;
|
|
1345
|
+
font-weight: 600;
|
|
1346
|
+
font-size: 16px;
|
|
1347
|
+
}
|
|
1348
|
+
.metric-group-icon {
|
|
1349
|
+
width: 20px;
|
|
1350
|
+
height: 20px;
|
|
1351
|
+
background: #35618e;
|
|
1352
|
+
border-radius: 3px;
|
|
1353
|
+
position: relative;
|
|
1354
|
+
}
|
|
1355
|
+
.metric-group-icon::before {
|
|
1356
|
+
content: '✓';
|
|
1357
|
+
position: absolute;
|
|
1358
|
+
top: 50%;
|
|
1359
|
+
left: 50%;
|
|
1360
|
+
transform: translate(-50%, -50%);
|
|
1361
|
+
color: white;
|
|
1362
|
+
font-size: 12px;
|
|
1363
|
+
font-weight: bold;
|
|
1364
|
+
}
|
|
1365
|
+
.metric-items {
|
|
1366
|
+
display: grid;
|
|
1367
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
1368
|
+
gap: 8px 16px;
|
|
1369
|
+
padding-left: 28px;
|
|
1370
|
+
}
|
|
1371
|
+
.metric-item {
|
|
1372
|
+
display: flex;
|
|
1373
|
+
justify-content: space-between;
|
|
1374
|
+
align-items: center;
|
|
1375
|
+
font-size: 14px;
|
|
1376
|
+
}
|
|
1377
|
+
.metric-label {
|
|
1378
|
+
color: #333;
|
|
1379
|
+
flex: 1;
|
|
1380
|
+
}
|
|
1381
|
+
.metric-value {
|
|
1382
|
+
color: #35618e;
|
|
1383
|
+
font-weight: 600;
|
|
1384
|
+
min-width: 40px;
|
|
1385
|
+
text-align: right;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/* Popup overlay */
|
|
1389
|
+
.popup-overlay {
|
|
1390
|
+
position: fixed;
|
|
1391
|
+
top: 0;
|
|
1392
|
+
left: 0;
|
|
1393
|
+
right: 0;
|
|
1394
|
+
bottom: 0;
|
|
1395
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1396
|
+
display: flex;
|
|
1397
|
+
align-items: center;
|
|
1398
|
+
justify-content: center;
|
|
1399
|
+
z-index: 1000;
|
|
1400
|
+
}
|
|
1401
|
+
.popup-content {
|
|
1402
|
+
background: white;
|
|
1403
|
+
border-radius: 12px;
|
|
1404
|
+
padding: 24px;
|
|
1405
|
+
max-width: 800px;
|
|
1406
|
+
width: 90%;
|
|
1407
|
+
max-height: 80vh;
|
|
1408
|
+
overflow-y: auto;
|
|
1409
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
1410
|
+
}
|
|
1411
|
+
.popup-header {
|
|
1412
|
+
display: flex;
|
|
1413
|
+
justify-content: space-between;
|
|
1414
|
+
align-items: center;
|
|
1415
|
+
margin-bottom: 20px;
|
|
1416
|
+
padding-bottom: 16px;
|
|
1417
|
+
border-bottom: 2px solid #e0e0e0;
|
|
1418
|
+
}
|
|
1419
|
+
.popup-title {
|
|
1420
|
+
font-size: 20px;
|
|
1421
|
+
font-weight: 700;
|
|
1422
|
+
color: #35618e;
|
|
1423
|
+
}
|
|
1424
|
+
.popup-score {
|
|
1425
|
+
display: flex;
|
|
1426
|
+
align-items: baseline;
|
|
1427
|
+
gap: 6px;
|
|
1428
|
+
margin-left: auto;
|
|
1429
|
+
margin-right: 16px;
|
|
1430
|
+
}
|
|
1431
|
+
.popup-score .score-value {
|
|
1432
|
+
font-size: 44px;
|
|
1433
|
+
font-weight: 800;
|
|
1434
|
+
color: #e53935;
|
|
1435
|
+
line-height: 1;
|
|
1436
|
+
}
|
|
1437
|
+
.popup-score .score-label {
|
|
1438
|
+
font-size: 14px;
|
|
1439
|
+
color: #888;
|
|
1440
|
+
}
|
|
1441
|
+
.popup-close {
|
|
1442
|
+
cursor: pointer;
|
|
1443
|
+
font-size: 24px;
|
|
1444
|
+
color: #999;
|
|
1445
|
+
background: none;
|
|
1446
|
+
border: none;
|
|
1447
|
+
padding: 4px 8px;
|
|
1448
|
+
}
|
|
1449
|
+
.popup-close:hover {
|
|
1450
|
+
color: #333;
|
|
1451
|
+
}
|
|
1452
|
+
.popup-chart-container {
|
|
1453
|
+
min-height: 400px;
|
|
1454
|
+
height: 500px;
|
|
1455
|
+
}
|
|
1456
|
+
.metric-label {
|
|
1457
|
+
cursor: pointer;
|
|
1458
|
+
}
|
|
1459
|
+
.metric-label:hover {
|
|
1460
|
+
opacity: 0.7;
|
|
1461
|
+
text-decoration: underline;
|
|
1462
|
+
}
|
|
484
1463
|
`
|
|
485
1464
|
];
|
|
486
1465
|
__decorate([
|
|
487
1466
|
state(),
|
|
488
1467
|
__metadata("design:type", Object)
|
|
489
1468
|
], SvProjectDetailPage.prototype, "project", void 0);
|
|
1469
|
+
__decorate([
|
|
1470
|
+
state(),
|
|
1471
|
+
__metadata("design:type", Array)
|
|
1472
|
+
], SvProjectDetailPage.prototype, "kpiComprehensiveStats", void 0);
|
|
1473
|
+
__decorate([
|
|
1474
|
+
state(),
|
|
1475
|
+
__metadata("design:type", Array)
|
|
1476
|
+
], SvProjectDetailPage.prototype, "kpiYComprehensiveStats", void 0);
|
|
1477
|
+
__decorate([
|
|
1478
|
+
state(),
|
|
1479
|
+
__metadata("design:type", Array)
|
|
1480
|
+
], SvProjectDetailPage.prototype, "projectXKpiValues", void 0);
|
|
1481
|
+
__decorate([
|
|
1482
|
+
state(),
|
|
1483
|
+
__metadata("design:type", Boolean)
|
|
1484
|
+
], SvProjectDetailPage.prototype, "showScoreDistribution", void 0);
|
|
1485
|
+
__decorate([
|
|
1486
|
+
state(),
|
|
1487
|
+
__metadata("design:type", Object)
|
|
1488
|
+
], SvProjectDetailPage.prototype, "selectedKpi", void 0);
|
|
1489
|
+
__decorate([
|
|
1490
|
+
state(),
|
|
1491
|
+
__metadata("design:type", Array)
|
|
1492
|
+
], SvProjectDetailPage.prototype, "pendingMetrics", void 0);
|
|
1493
|
+
__decorate([
|
|
1494
|
+
state(),
|
|
1495
|
+
__metadata("design:type", Array)
|
|
1496
|
+
], SvProjectDetailPage.prototype, "allKpiMetricValues", void 0);
|
|
490
1497
|
SvProjectDetailPage = __decorate([
|
|
491
1498
|
customElement('sv-project-detail')
|
|
492
1499
|
], SvProjectDetailPage);
|