@dssp/dkpi 1.0.0-alpha.84 → 1.0.0-alpha.85
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/dist-client/components/kpi-2d-lookup-chart.d.ts +7 -0
- package/dist-client/components/kpi-2d-lookup-chart.js +26 -16
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +2 -2
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +11 -0
- package/dist-client/pages/sv-project-detail.js +94 -49
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +5 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +30 -9
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/schema.graphql +27 -2
|
@@ -25,6 +25,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
25
25
|
this.selectedKpi = null;
|
|
26
26
|
/** 이 프로젝트에 아직 한 번도 입력되지 않은 metric (active 한 manual/import 한정) */
|
|
27
27
|
this.pendingMetrics = [];
|
|
28
|
+
/** 현재 프로젝트의 모든 KpiMetricValue (metric.name 포함) — 2D lookup chart 의 progressRate / value 도출용. */
|
|
29
|
+
this.allKpiMetricValues = [];
|
|
28
30
|
}
|
|
29
31
|
get context() {
|
|
30
32
|
var _a;
|
|
@@ -35,7 +37,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
render() {
|
|
38
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l
|
|
40
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
39
41
|
return html `
|
|
40
42
|
<div content>
|
|
41
43
|
<div left>
|
|
@@ -271,6 +273,14 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
271
273
|
<div class="popup-content" @click=${(e) => e.stopPropagation()}>
|
|
272
274
|
<div class="popup-header">
|
|
273
275
|
<div class="popup-title">${this.selectedKpi.kpiName} — 성과 분포</div>
|
|
276
|
+
${this.selectedKpi.score !== undefined && this.selectedKpi.score !== null
|
|
277
|
+
? html `
|
|
278
|
+
<div class="popup-score">
|
|
279
|
+
<span class="score-value">${Math.round(Number(this.selectedKpi.score))}</span>
|
|
280
|
+
<span class="score-label">점</span>
|
|
281
|
+
</div>
|
|
282
|
+
`
|
|
283
|
+
: ''}
|
|
274
284
|
<button class="popup-close" @click=${this._closePopup}>×</button>
|
|
275
285
|
</div>
|
|
276
286
|
<div class="popup-chart-container">
|
|
@@ -284,32 +294,24 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
284
294
|
* LOOKUP: 성과 분포 커브 (kpi-lookup-chart)
|
|
285
295
|
* ASSESSMENT: 5단계 게이지 (kpi-lookup-chart 자동 감지)
|
|
286
296
|
*/''}
|
|
287
|
-
${
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
?
|
|
294
|
-
<kpi-2d-lookup-chart
|
|
295
|
-
.grades=${this.selectedKpi.grades}
|
|
296
|
-
.value=${this.selectedKpi.value}
|
|
297
|
-
.progressRate=${(_o = (_m = this.project) === null || _m === void 0 ? void 0 : _m.totalProgress) !== null && _o !== void 0 ? _o : 50}
|
|
298
|
-
unit="%"
|
|
299
|
-
kpiName=${this.selectedKpi.kpiName || ''}
|
|
300
|
-
></kpi-2d-lookup-chart>
|
|
301
|
-
`
|
|
297
|
+
${
|
|
298
|
+
// 2D lookup KPI (X13 등) 는 항상 등급 기준 + 히트맵 함께 표시 (value 가
|
|
299
|
+
// 정수든 소수든 일관). _renderAssessmentGrades 안의 is2D 분기가 히트맵
|
|
300
|
+
// 같이 그림. 그 외 ASSESSMENT 도 등급 기준, LOOKUP 은 분포 커브.
|
|
301
|
+
(this.selectedKpi.scoreType === 'CUSTOM' ||
|
|
302
|
+
((_l = this.selectedKpi.grades) === null || _l === void 0 ? void 0 : _l.type) === 'PROGRESS_DEVIATION_LOOKUP')
|
|
303
|
+
? this._renderAssessmentGrades()
|
|
302
304
|
: this._isAssessmentOrIntegerScore()
|
|
303
305
|
? this._renderAssessmentGrades()
|
|
304
306
|
: html `
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
307
|
+
<kpi-lookup-chart
|
|
308
|
+
.grades=${this.selectedKpi.grades || []}
|
|
309
|
+
.value=${this.selectedKpi.value}
|
|
310
|
+
.scoreType=${this.selectedKpi.scoreType || ''}
|
|
311
|
+
.valueType=${this.selectedKpi.valueType || ''}
|
|
312
|
+
unit=${this.selectedKpi.unit || ''}
|
|
313
|
+
></kpi-lookup-chart>
|
|
314
|
+
`}
|
|
313
315
|
</div>
|
|
314
316
|
</div>
|
|
315
317
|
</div>
|
|
@@ -489,7 +491,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
489
491
|
this.pendingMetrics = [];
|
|
490
492
|
return;
|
|
491
493
|
}
|
|
492
|
-
// 2) 이 프로젝트의 모든 KpiMetricValue (
|
|
494
|
+
// 2) 이 프로젝트의 모든 KpiMetricValue (metric.name 포함 — 2D 차트의 latest lookup 에도 사용)
|
|
493
495
|
const valuesResp = await client.query({
|
|
494
496
|
query: gql `
|
|
495
497
|
query KpiMetricValues($filters: [Filter!]) {
|
|
@@ -499,6 +501,10 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
499
501
|
periodType
|
|
500
502
|
valueDate
|
|
501
503
|
value
|
|
504
|
+
metric {
|
|
505
|
+
id
|
|
506
|
+
name
|
|
507
|
+
}
|
|
502
508
|
}
|
|
503
509
|
}
|
|
504
510
|
}
|
|
@@ -507,6 +513,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
507
513
|
context: { headers: tenantHeaders((_e = this.project) === null || _e === void 0 ? void 0 : _e.code) }
|
|
508
514
|
});
|
|
509
515
|
const values = ((_g = (_f = valuesResp.data) === null || _f === void 0 ? void 0 : _f.kpiMetricValues) === null || _g === void 0 ? void 0 : _g.items) || [];
|
|
516
|
+
this.allKpiMetricValues = values;
|
|
510
517
|
const lastMonthYm = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM');
|
|
511
518
|
// 3) Step 1/2 와 동일 기준으로 미입력 판정 — row 존재 AND value 존재 (null/undefined 아님).
|
|
512
519
|
const hasValid = (v) => v.value !== null && v.value !== undefined;
|
|
@@ -585,6 +592,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
585
592
|
projectXKpiValues(projectId: $projectId) {
|
|
586
593
|
id
|
|
587
594
|
value
|
|
595
|
+
score
|
|
588
596
|
valueDate
|
|
589
597
|
kpi {
|
|
590
598
|
id
|
|
@@ -616,7 +624,7 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
616
624
|
this._fetchKpiWithGradesByPattern(kpiNamePattern, metric.label);
|
|
617
625
|
}
|
|
618
626
|
async _fetchKpiWithGradesByPattern(kpiNamePattern, displayName) {
|
|
619
|
-
var _a, _b, _c, _d;
|
|
627
|
+
var _a, _b, _c, _d, _e;
|
|
620
628
|
try {
|
|
621
629
|
// X-KPI 이름 패턴으로 전체 이름 찾기 (projectXKpiValues에서)
|
|
622
630
|
const kpiValue = this.projectXKpiValues.find(kv => { var _a; return ((_a = kv.kpi) === null || _a === void 0 ? void 0 : _a.name) && kv.kpi.name.includes(kpiNamePattern); });
|
|
@@ -657,7 +665,8 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
657
665
|
scoreType: kpi.scoreType || '',
|
|
658
666
|
valueType: kpi.valueType || '',
|
|
659
667
|
value: (_c = kpiValue.value) !== null && _c !== void 0 ? _c : null,
|
|
660
|
-
|
|
668
|
+
score: (_d = kpiValue.score) !== null && _d !== void 0 ? _d : null,
|
|
669
|
+
unit: ((_e = kpi.vizMeta) === null || _e === void 0 ? void 0 : _e.unit) || ''
|
|
661
670
|
};
|
|
662
671
|
this.showScoreDistribution = true;
|
|
663
672
|
}
|
|
@@ -693,9 +702,10 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
693
702
|
}
|
|
694
703
|
/** 등급 기준 표시 (ASSESSED / 1~5 정수 score) — 현재 값 강조 + CUSTOM은 2D 히트맵도 표시 */
|
|
695
704
|
_renderAssessmentGrades() {
|
|
696
|
-
var _a
|
|
705
|
+
var _a;
|
|
697
706
|
const kpi = this.selectedKpi;
|
|
698
|
-
|
|
707
|
+
// score 가 진짜 등급. kpi.value 는 raw 측정값 (편차율 등) 이라 score 로 쓰면 안 됨.
|
|
708
|
+
const currentScore = (kpi === null || kpi === void 0 ? void 0 : kpi.score) != null ? Math.round(kpi.score) : 0;
|
|
699
709
|
const grades = Array.isArray(kpi === null || kpi === void 0 ? void 0 : kpi.grades) ? kpi.grades : [];
|
|
700
710
|
const colors = ['#e53935', '#ff9800', '#ffca28', '#66bb6a', '#2e7d32'];
|
|
701
711
|
const is2D = (kpi === null || kpi === void 0 ? void 0 : kpi.scoreType) === 'CUSTOM' || (kpi === null || kpi === void 0 ? void 0 : kpi.valueType) === 'COMPOSITE' ||
|
|
@@ -714,11 +724,10 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
714
724
|
// ASSESSED(감리자 평가)이고 grades에 description이 있는 경우만 한 줄씩 상세 표시
|
|
715
725
|
// X13 등 COMPOSITE/CUSTOM은 인라인 칩으로 간결하게
|
|
716
726
|
const hasDescriptions = !is2D && grades.length > 0 && grades.some((g) => g.description);
|
|
727
|
+
// ASSESSMENT (감리자 평가) 의 등급 description 만 노출. 2D lookup 은 score 가 이미
|
|
728
|
+
// popup-header 큰 점수로 표시되므로 등급 기준 행/동그라미 중복 제거.
|
|
717
729
|
return html `
|
|
718
|
-
<div style="padding: 20px;">
|
|
719
|
-
<div style="font-size: 1rem; font-weight: 700; margin-bottom: 16px; color: #333;">
|
|
720
|
-
등급 기준 ${currentScore >= 1 ? html `<span style="color: ${colors[currentScore - 1]}; margin-left: 8px;">현재: ${currentScore}점</span>` : ''}
|
|
721
|
-
</div>
|
|
730
|
+
<div style="padding: 20px; display: flex; flex-direction: column; height: 100%;">
|
|
722
731
|
${hasDescriptions
|
|
723
732
|
? items.map(item => html `
|
|
724
733
|
<div
|
|
@@ -744,30 +753,19 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
744
753
|
${item.desc}
|
|
745
754
|
</span>
|
|
746
755
|
</div>`)
|
|
747
|
-
:
|
|
748
|
-
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
|
749
|
-
${items.map(item => html `
|
|
750
|
-
<span style="
|
|
751
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
752
|
-
width: 36px; height: 36px; border-radius: 50%;
|
|
753
|
-
background: ${item.active ? item.color : '#e0e0e0'};
|
|
754
|
-
color: ${item.active ? '#fff' : '#999'};
|
|
755
|
-
font-weight: 700; font-size: 14px;
|
|
756
|
-
border: 2px solid ${item.active ? item.color : 'transparent'};
|
|
757
|
-
">${item.score}</span>`)}
|
|
758
|
-
</div>
|
|
759
|
-
`}
|
|
756
|
+
: ''}
|
|
760
757
|
|
|
761
758
|
${is2D && ((_a = kpi === null || kpi === void 0 ? void 0 : kpi.grades) === null || _a === void 0 ? void 0 : _a.rows)
|
|
762
759
|
? html `
|
|
763
|
-
<div style="
|
|
760
|
+
<div style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
|
764
761
|
<div style="font-size: 0.9rem; font-weight: 700; color: #333; margin-bottom: 8px;">
|
|
765
762
|
공정률 × 공기편차 성과 분포
|
|
766
763
|
</div>
|
|
767
|
-
<div style="height:
|
|
764
|
+
<div style="flex: 1; min-height: 0;">
|
|
768
765
|
<kpi-2d-lookup-chart
|
|
769
766
|
.grades=${kpi.grades}
|
|
770
|
-
.
|
|
767
|
+
.value=${this._getScheduleDeviation()}
|
|
768
|
+
.progressRate=${this._getProgressRate()}
|
|
771
769
|
></kpi-2d-lookup-chart>
|
|
772
770
|
</div>
|
|
773
771
|
</div>
|
|
@@ -882,6 +880,32 @@ let SvProjectDetailPage = class SvProjectDetailPage extends ScopedElementsMixin(
|
|
|
882
880
|
});
|
|
883
881
|
return data.sort((a, b) => a.org.localeCompare(b.org));
|
|
884
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* KpiMetric.name 이 주어진 이름인 KpiMetricValue 중 가장 최근 valueDate 의 value.
|
|
885
|
+
* 2D lookup chart 의 progressRate / value 도출용.
|
|
886
|
+
*/
|
|
887
|
+
_getLatestMetricValue(metricName) {
|
|
888
|
+
const candidates = this.allKpiMetricValues
|
|
889
|
+
.filter(v => { var _a; return ((_a = v.metric) === null || _a === void 0 ? void 0 : _a.name) === metricName && v.value !== null && v.value !== undefined; })
|
|
890
|
+
.sort((a, b) => (b.valueDate || '').localeCompare(a.valueDate || ''));
|
|
891
|
+
return candidates.length > 0 ? candidates[0].value : null;
|
|
892
|
+
}
|
|
893
|
+
/** 2D lookup chart 의 progressRate (X축, 실적공정율 %) — 실적공정율 metric latest 우선, 없으면 fallback. */
|
|
894
|
+
_getProgressRate() {
|
|
895
|
+
var _a, _b;
|
|
896
|
+
const latest = this._getLatestMetricValue('실적공정율');
|
|
897
|
+
if (latest !== null)
|
|
898
|
+
return latest;
|
|
899
|
+
return (_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.totalProgress) !== null && _b !== void 0 ? _b : 50;
|
|
900
|
+
}
|
|
901
|
+
/** 2D lookup chart 의 value (Y축, 공기편차 비율) — '일정 이탈 수준' metric latest 우선, 없으면 selectedKpi.value. */
|
|
902
|
+
_getScheduleDeviation() {
|
|
903
|
+
var _a, _b;
|
|
904
|
+
const latest = this._getLatestMetricValue('일정 이탈 수준');
|
|
905
|
+
if (latest !== null)
|
|
906
|
+
return latest;
|
|
907
|
+
return (_b = (_a = this.selectedKpi) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : null;
|
|
908
|
+
}
|
|
885
909
|
getProjectYKpiValue(kpiName) {
|
|
886
910
|
var _a, _b;
|
|
887
911
|
// 현재 프로젝트의 kpiValues 배열에서 해당 KPI 값을 찾습니다
|
|
@@ -1365,6 +1389,23 @@ SvProjectDetailPage.styles = [
|
|
|
1365
1389
|
font-weight: 700;
|
|
1366
1390
|
color: #35618e;
|
|
1367
1391
|
}
|
|
1392
|
+
.popup-score {
|
|
1393
|
+
display: flex;
|
|
1394
|
+
align-items: baseline;
|
|
1395
|
+
gap: 6px;
|
|
1396
|
+
margin-left: auto;
|
|
1397
|
+
margin-right: 16px;
|
|
1398
|
+
}
|
|
1399
|
+
.popup-score .score-value {
|
|
1400
|
+
font-size: 44px;
|
|
1401
|
+
font-weight: 800;
|
|
1402
|
+
color: #e53935;
|
|
1403
|
+
line-height: 1;
|
|
1404
|
+
}
|
|
1405
|
+
.popup-score .score-label {
|
|
1406
|
+
font-size: 14px;
|
|
1407
|
+
color: #888;
|
|
1408
|
+
}
|
|
1368
1409
|
.popup-close {
|
|
1369
1410
|
cursor: pointer;
|
|
1370
1411
|
font-size: 24px;
|
|
@@ -1417,6 +1458,10 @@ __decorate([
|
|
|
1417
1458
|
state(),
|
|
1418
1459
|
__metadata("design:type", Array)
|
|
1419
1460
|
], SvProjectDetailPage.prototype, "pendingMetrics", void 0);
|
|
1461
|
+
__decorate([
|
|
1462
|
+
state(),
|
|
1463
|
+
__metadata("design:type", Array)
|
|
1464
|
+
], SvProjectDetailPage.prototype, "allKpiMetricValues", void 0);
|
|
1420
1465
|
SvProjectDetailPage = __decorate([
|
|
1421
1466
|
customElement('sv-project-detail')
|
|
1422
1467
|
], SvProjectDetailPage);
|