@dssp/dkpi 1.0.0-alpha.67 → 1.0.0-alpha.69
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 +10 -30
- package/dist-client/components/kpi-2d-lookup-chart.js +145 -362
- package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -1
- package/dist-client/components/kpi-boxplot-chart.js +51 -24
- package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
- package/dist-client/components/kpi-lookup-chart.d.ts +28 -4
- package/dist-client/components/kpi-lookup-chart.js +314 -293
- package/dist-client/components/kpi-lookup-chart.js.map +1 -1
- package/dist-client/components/kpi-radar-chart.js +29 -5
- package/dist-client/components/kpi-radar-chart.js.map +1 -1
- package/dist-client/components/kpi-single-boxplot-chart.js +72 -14
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +46 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +378 -0
- package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +0 -9
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +0 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +0 -4
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +0 -11
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/sv-project-completed-list.js +0 -1
- package/dist-client/pages/sv-project-completed-list.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +11 -1
- package/dist-client/pages/sv-project-detail.js +150 -20
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-list.js +0 -1
- package/dist-client/pages/sv-project-list.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/schema.graphql +26 -5
- package/things-factory.config.js +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { KpiOverview } from '@things-factory/kpi/dist-client/pages/kpi/kpi-overview.js';
|
|
2
|
+
import '../../components/kpi-lookup-chart';
|
|
3
|
+
import '../../components/kpi-2d-lookup-chart';
|
|
4
|
+
/**
|
|
5
|
+
* KPI scoreType 정의 — value → score 변환 방식
|
|
6
|
+
*
|
|
7
|
+
* NONE — 미설정 (부모 KPI 등)
|
|
8
|
+
* DIRECT — value = score, 별도 변환 없음 (Y/Z: formula 결과가 곧 score)
|
|
9
|
+
* FORMULA — scoreFormula로 value → score 변환
|
|
10
|
+
* LOOKUP — grade table(1D)로 raw value → 0~1 score 변환 (연속값 KPI)
|
|
11
|
+
* 예: X11(연면적 대비 공사기간), X33(검측 불합격률), X34(SL-PA)
|
|
12
|
+
* → 성과 분포 커브 차트로 표시
|
|
13
|
+
* CUSTOM — 2D 룩업 등 특수 구조의 grade (다차원 입력 → 점수)
|
|
14
|
+
* 예: X13(일정 이탈 수준) = 공정률 × 편차율 → 1~5점
|
|
15
|
+
* → 히트맵 차트로 표시
|
|
16
|
+
* ASSESSMENT — 감리자 직접 평가, value(1~5) = score (grade table 불필요)
|
|
17
|
+
* 예: X14(일정성과 수준 평가), X23(비용성과), X35(품질성과) 등
|
|
18
|
+
* → 등급 기준 설명만 표시 (차트 없음)
|
|
19
|
+
*
|
|
20
|
+
* Note: DIRECT와 ASSESSMENT는 모두 value=score이지만,
|
|
21
|
+
* ASSESSMENT는 감리자가 1~5점을 직접 입력하는 정성 평가를 의미하고,
|
|
22
|
+
* DIRECT는 산식 등으로 자동 계산된 value가 변환 없이 score가 되는 것을 의미한다.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* KPI 개요 페이지 — 등급 구간을 성과 분포 차트로 표현
|
|
26
|
+
*
|
|
27
|
+
* - 0~1 스케일 KPI: 성과 분포 커브 (kpi-lookup-chart)
|
|
28
|
+
* - 1~5 평가형 KPI: 5단계 세그먼트 게이지 (kpi-lookup-chart 자동 감지)
|
|
29
|
+
* - X13 2D 룩업: 공정률×편차율 히트맵 (kpi-2d-lookup-chart)
|
|
30
|
+
*/
|
|
31
|
+
export declare class DsspKpiOverview extends KpiOverview {
|
|
32
|
+
static styles: import("lit").CSSResult[];
|
|
33
|
+
firstUpdated(): Promise<void>;
|
|
34
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
35
|
+
/**
|
|
36
|
+
* 성과분포/등급 콘텐츠 렌더링 — scoreType + valueType 기반 분기
|
|
37
|
+
*
|
|
38
|
+
* valueType=ASSESSED → 등급 기준 설명만 (감리자 직접 평가, 차트 없음)
|
|
39
|
+
* scoreType=CUSTOM → 2D 히트맵 (공정률×편차율)
|
|
40
|
+
* scoreType=LOOKUP → 성과 분포 커브 (value→score 매핑)
|
|
41
|
+
* scoreType=DIRECT → 등급 데이터 없음 (Y/Z 등)
|
|
42
|
+
*/
|
|
43
|
+
private _renderGradesContent;
|
|
44
|
+
private _renderTypeInfo;
|
|
45
|
+
private _renderAssessmentDescriptions;
|
|
46
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { html, css, nothing } from 'lit';
|
|
3
|
+
import { customElement } from 'lit/decorators.js';
|
|
4
|
+
import { client } from '@operato/graphql';
|
|
5
|
+
import gql from 'graphql-tag';
|
|
6
|
+
import { KpiOverview } from '@things-factory/kpi/dist-client/pages/kpi/kpi-overview.js';
|
|
7
|
+
import '../../components/kpi-lookup-chart';
|
|
8
|
+
import '../../components/kpi-2d-lookup-chart';
|
|
9
|
+
const GET_KPI_OVERVIEW_EXT = gql `
|
|
10
|
+
query {
|
|
11
|
+
kpiCategories: kpisLevel1 {
|
|
12
|
+
id
|
|
13
|
+
name
|
|
14
|
+
description
|
|
15
|
+
formula
|
|
16
|
+
active
|
|
17
|
+
kpis: children {
|
|
18
|
+
id
|
|
19
|
+
name
|
|
20
|
+
description
|
|
21
|
+
formula
|
|
22
|
+
active
|
|
23
|
+
grades
|
|
24
|
+
scoreType
|
|
25
|
+
valueType
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
/**
|
|
31
|
+
* KPI scoreType 정의 — value → score 변환 방식
|
|
32
|
+
*
|
|
33
|
+
* NONE — 미설정 (부모 KPI 등)
|
|
34
|
+
* DIRECT — value = score, 별도 변환 없음 (Y/Z: formula 결과가 곧 score)
|
|
35
|
+
* FORMULA — scoreFormula로 value → score 변환
|
|
36
|
+
* LOOKUP — grade table(1D)로 raw value → 0~1 score 변환 (연속값 KPI)
|
|
37
|
+
* 예: X11(연면적 대비 공사기간), X33(검측 불합격률), X34(SL-PA)
|
|
38
|
+
* → 성과 분포 커브 차트로 표시
|
|
39
|
+
* CUSTOM — 2D 룩업 등 특수 구조의 grade (다차원 입력 → 점수)
|
|
40
|
+
* 예: X13(일정 이탈 수준) = 공정률 × 편차율 → 1~5점
|
|
41
|
+
* → 히트맵 차트로 표시
|
|
42
|
+
* ASSESSMENT — 감리자 직접 평가, value(1~5) = score (grade table 불필요)
|
|
43
|
+
* 예: X14(일정성과 수준 평가), X23(비용성과), X35(품질성과) 등
|
|
44
|
+
* → 등급 기준 설명만 표시 (차트 없음)
|
|
45
|
+
*
|
|
46
|
+
* Note: DIRECT와 ASSESSMENT는 모두 value=score이지만,
|
|
47
|
+
* ASSESSMENT는 감리자가 1~5점을 직접 입력하는 정성 평가를 의미하고,
|
|
48
|
+
* DIRECT는 산식 등으로 자동 계산된 value가 변환 없이 score가 되는 것을 의미한다.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* KPI 개요 페이지 — 등급 구간을 성과 분포 차트로 표현
|
|
52
|
+
*
|
|
53
|
+
* - 0~1 스케일 KPI: 성과 분포 커브 (kpi-lookup-chart)
|
|
54
|
+
* - 1~5 평가형 KPI: 5단계 세그먼트 게이지 (kpi-lookup-chart 자동 감지)
|
|
55
|
+
* - X13 2D 룩업: 공정률×편차율 히트맵 (kpi-2d-lookup-chart)
|
|
56
|
+
*/
|
|
57
|
+
let DsspKpiOverview = class DsspKpiOverview extends KpiOverview {
|
|
58
|
+
async firstUpdated() {
|
|
59
|
+
try {
|
|
60
|
+
const { data } = await client.query({ query: GET_KPI_OVERVIEW_EXT });
|
|
61
|
+
this.kpiCategories = data.kpiCategories;
|
|
62
|
+
this.loading = false;
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
this.error = e instanceof Error ? e : new Error(String(e));
|
|
66
|
+
this.loading = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
render() {
|
|
70
|
+
var _a, _b, _c, _d, _e;
|
|
71
|
+
if (this.error)
|
|
72
|
+
return html `<div>에러: ${(_a = this.error) === null || _a === void 0 ? void 0 : _a.message}</div>`;
|
|
73
|
+
if (!this.kpiCategories.length)
|
|
74
|
+
return html `<div>KPI 카테고리가 없습니다.</div>`;
|
|
75
|
+
const selctedY = ((_b = this.kpiCategories) === null || _b === void 0 ? void 0 : _b[this.selectedGroup]) || '';
|
|
76
|
+
const groupRaw = this.kpiCategories[this.selectedGroup];
|
|
77
|
+
// active가 false인 KPI 제외 (X31, X32 등 삭제된 지표)
|
|
78
|
+
const activeKpis = ((_c = groupRaw === null || groupRaw === void 0 ? void 0 : groupRaw.kpis) === null || _c === void 0 ? void 0 : _c.filter((k) => k.active !== false)) || [];
|
|
79
|
+
const group = Object.assign(Object.assign({}, groupRaw), { kpis: activeKpis });
|
|
80
|
+
const kpi = (_d = group === null || group === void 0 ? void 0 : group.kpis) === null || _d === void 0 ? void 0 : _d[this.selectedKpi];
|
|
81
|
+
return html `
|
|
82
|
+
<div class="overview-container">
|
|
83
|
+
<div class="overview-left">
|
|
84
|
+
<div class="overview-title">KPI개요</div>
|
|
85
|
+
<div class="overview-desc">
|
|
86
|
+
건설현장 KPI관리 시스템은 프로젝트 성과를 측정하고, 모니터링하여 효율적인 공정 관리를 지원합니다. 주요 지표를
|
|
87
|
+
통해 현장 생산성, 품질, 안전 등을 실시간으로 파악할 수 있습니다.
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="group-tabs">
|
|
92
|
+
${this.kpiCategories.map((g, i) => html `
|
|
93
|
+
<button
|
|
94
|
+
class="group-tab"
|
|
95
|
+
?selected=${i === this.selectedGroup}
|
|
96
|
+
@click=${() => {
|
|
97
|
+
this.selectedGroup = i;
|
|
98
|
+
this.selectedKpi = 0;
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
${g.name}
|
|
102
|
+
</button>
|
|
103
|
+
`)}
|
|
104
|
+
</div>
|
|
105
|
+
<!-- Y 섹션: 좌=Y 설명+산식 | 우=X-KPI 목록 -->
|
|
106
|
+
<div class="main-content">
|
|
107
|
+
<div class="markdown">
|
|
108
|
+
<div class="kpi-title">${group.name}</div>
|
|
109
|
+
${group.description ? html `<div style="line-height:1.7;margin-bottom:12px;">${group.description}</div>` : ''}
|
|
110
|
+
${group.formula
|
|
111
|
+
? html `
|
|
112
|
+
<div class="subtitle"><span class="subtitle-icon"></span> 산식</div>
|
|
113
|
+
<div class="kpi-formula">${group.formula}</div>
|
|
114
|
+
`
|
|
115
|
+
: ''}
|
|
116
|
+
</div>
|
|
117
|
+
<div class="toc">
|
|
118
|
+
<div class="submenu-header">
|
|
119
|
+
<span class="submenu-icon"></span>
|
|
120
|
+
<span class="toc-title">${selctedY.name} 지표</span>
|
|
121
|
+
</div>
|
|
122
|
+
<ul class="toc-list">
|
|
123
|
+
${(_e = group === null || group === void 0 ? void 0 : group.kpis) === null || _e === void 0 ? void 0 : _e.map((k, i) => html `
|
|
124
|
+
<li>
|
|
125
|
+
<button class="toc-btn" ?selected=${i === this.selectedKpi} @click=${() => (this.selectedKpi = i)}>
|
|
126
|
+
${k.name}
|
|
127
|
+
</button>
|
|
128
|
+
</li>
|
|
129
|
+
`)}
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<!-- X 섹션: 좌=X 설명+산식 | 우=성과분포/등급 -->
|
|
135
|
+
<div class="x-section">
|
|
136
|
+
<div class="x-detail">
|
|
137
|
+
<div class="kpi-title">${(kpi === null || kpi === void 0 ? void 0 : kpi.name) || ''}</div>
|
|
138
|
+
<div style="line-height:1.7;margin-bottom:12px;">${(kpi === null || kpi === void 0 ? void 0 : kpi.description) || ''}</div>
|
|
139
|
+
${(kpi === null || kpi === void 0 ? void 0 : kpi.formula)
|
|
140
|
+
? html `
|
|
141
|
+
<div class="subtitle"><span class="subtitle-icon"></span> 주요산식</div>
|
|
142
|
+
<div class="kpi-formula">${kpi === null || kpi === void 0 ? void 0 : kpi.formula}</div>
|
|
143
|
+
`
|
|
144
|
+
: ''}
|
|
145
|
+
${this._renderTypeInfo(kpi)}
|
|
146
|
+
</div>
|
|
147
|
+
<div class="x-chart">
|
|
148
|
+
${this._renderGradesContent(kpi)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 성과분포/등급 콘텐츠 렌더링 — scoreType + valueType 기반 분기
|
|
155
|
+
*
|
|
156
|
+
* valueType=ASSESSED → 등급 기준 설명만 (감리자 직접 평가, 차트 없음)
|
|
157
|
+
* scoreType=CUSTOM → 2D 히트맵 (공정률×편차율)
|
|
158
|
+
* scoreType=LOOKUP → 성과 분포 커브 (value→score 매핑)
|
|
159
|
+
* scoreType=DIRECT → 등급 데이터 없음 (Y/Z 등)
|
|
160
|
+
*/
|
|
161
|
+
_renderGradesContent(kpi) {
|
|
162
|
+
if (!(kpi === null || kpi === void 0 ? void 0 : kpi.grades))
|
|
163
|
+
return html `<div style="color:#999;padding:20px;">등급 데이터 없음</div>`;
|
|
164
|
+
const scoreType = kpi.scoreType;
|
|
165
|
+
const valueType = kpi.valueType;
|
|
166
|
+
const is2D = scoreType === 'CUSTOM' ||
|
|
167
|
+
(kpi.grades && typeof kpi.grades === 'object' && !Array.isArray(kpi.grades) && kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP');
|
|
168
|
+
const isArray = Array.isArray(kpi.grades) && kpi.grades.length > 0;
|
|
169
|
+
const isAssessment = valueType === 'ASSESSED' ||
|
|
170
|
+
(!scoreType && !valueType && isArray && kpi.grades.length <= 5 &&
|
|
171
|
+
kpi.grades.every((g) => g.score >= 1 && g.score <= 5 && g.score === Math.floor(g.score) && g.minValue === g.score));
|
|
172
|
+
if (isAssessment) {
|
|
173
|
+
return html `
|
|
174
|
+
<div class="grades-title">등급 기준</div>
|
|
175
|
+
${isArray ? this._renderAssessmentDescriptions(kpi.grades) : nothing}
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
if (is2D) {
|
|
179
|
+
return html `
|
|
180
|
+
<div class="grades-title">성과 분포</div>
|
|
181
|
+
<div class="grades-chart">
|
|
182
|
+
<kpi-2d-lookup-chart .grades=${kpi.grades} .progressRate=${50}></kpi-2d-lookup-chart>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
if (isArray) {
|
|
187
|
+
return html `
|
|
188
|
+
<div class="grades-title">성과 분포</div>
|
|
189
|
+
<div class="grades-chart">
|
|
190
|
+
<kpi-lookup-chart .grades=${kpi.grades} .showDots=${true} .scoreType=${kpi.scoreType || ''}></kpi-lookup-chart>
|
|
191
|
+
</div>
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
return html `<div style="color:#999;padding:20px;">등급 데이터 없음</div>`;
|
|
195
|
+
}
|
|
196
|
+
_renderTypeInfo(kpi) {
|
|
197
|
+
const valueLabels = {
|
|
198
|
+
MEASURED: '외부 시스템 수집',
|
|
199
|
+
ASSESSED: '감리자 직접 평가 (1~5)',
|
|
200
|
+
CALCULATED: '산식 자동 계산',
|
|
201
|
+
COMPOSITE: '다차원 복합 입력'
|
|
202
|
+
};
|
|
203
|
+
const scoreLabels = {
|
|
204
|
+
DIRECT: 'value = score (변환 없음)',
|
|
205
|
+
FORMULA: 'scoreFormula 수식 변환',
|
|
206
|
+
LOOKUP: 'Grade Table 룩업',
|
|
207
|
+
CUSTOM: '2D 룩업 (특수 변환)'
|
|
208
|
+
};
|
|
209
|
+
const vLabel = (kpi === null || kpi === void 0 ? void 0 : kpi.valueType) ? valueLabels[kpi.valueType] || kpi.valueType : null;
|
|
210
|
+
const sLabel = (kpi === null || kpi === void 0 ? void 0 : kpi.scoreType) ? scoreLabels[kpi.scoreType] || kpi.scoreType : null;
|
|
211
|
+
if (!vLabel && !sLabel)
|
|
212
|
+
return nothing;
|
|
213
|
+
return html `
|
|
214
|
+
<div class="type-info">
|
|
215
|
+
${vLabel
|
|
216
|
+
? html `<span class="type-badge"><span class="label">Value 산정</span><span class="value">${vLabel}</span></span>`
|
|
217
|
+
: ''}
|
|
218
|
+
${sLabel
|
|
219
|
+
? html `<span class="type-badge"><span class="label">Score 산정</span><span class="value">${sLabel}</span></span>`
|
|
220
|
+
: ''}
|
|
221
|
+
</div>
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
_renderAssessmentDescriptions(grades) {
|
|
225
|
+
const colors = ['#e53935', '#ff9800', '#ffca28', '#66bb6a', '#2e7d32'];
|
|
226
|
+
const sorted = [...grades].sort((a, b) => a.score - b.score);
|
|
227
|
+
return html `
|
|
228
|
+
<div class="grade-descriptions">
|
|
229
|
+
${sorted.map((g) => html `
|
|
230
|
+
<div class="grade-chip">
|
|
231
|
+
<span class="dot" style="background: ${colors[(g.score || 1) - 1]}"></span>
|
|
232
|
+
<span class="score">${g.score}점</span>
|
|
233
|
+
<span class="desc">${g.description || g.name || ''}</span>
|
|
234
|
+
</div>
|
|
235
|
+
`)}
|
|
236
|
+
</div>
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
DsspKpiOverview.styles = [
|
|
241
|
+
KpiOverview.styles,
|
|
242
|
+
css `
|
|
243
|
+
:host {
|
|
244
|
+
overflow-y: auto;
|
|
245
|
+
}
|
|
246
|
+
.grades-section {
|
|
247
|
+
margin-top: 16px;
|
|
248
|
+
background: #fff;
|
|
249
|
+
border-radius: 8px;
|
|
250
|
+
padding: 20px;
|
|
251
|
+
border: 2px solid #0c4da2;
|
|
252
|
+
}
|
|
253
|
+
.grades-title {
|
|
254
|
+
font-size: 1rem;
|
|
255
|
+
font-weight: 700;
|
|
256
|
+
color: #333;
|
|
257
|
+
margin-bottom: 12px;
|
|
258
|
+
}
|
|
259
|
+
.grades-chart {
|
|
260
|
+
width: 100%;
|
|
261
|
+
height: 320px;
|
|
262
|
+
}
|
|
263
|
+
.grade-descriptions {
|
|
264
|
+
display: flex;
|
|
265
|
+
flex-direction: column;
|
|
266
|
+
gap: 6px;
|
|
267
|
+
margin-top: 12px;
|
|
268
|
+
}
|
|
269
|
+
.grade-chip {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 6px;
|
|
273
|
+
padding: 6px 12px;
|
|
274
|
+
border-radius: 20px;
|
|
275
|
+
font-size: 0.8rem;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
background: #f5f5f5;
|
|
278
|
+
border: 1px solid #e0e0e0;
|
|
279
|
+
}
|
|
280
|
+
.grade-chip .dot {
|
|
281
|
+
width: 10px;
|
|
282
|
+
height: 10px;
|
|
283
|
+
border-radius: 50%;
|
|
284
|
+
flex-shrink: 0;
|
|
285
|
+
}
|
|
286
|
+
.grade-chip .score {
|
|
287
|
+
font-weight: 700;
|
|
288
|
+
min-width: 24px;
|
|
289
|
+
}
|
|
290
|
+
.grade-chip .desc {
|
|
291
|
+
color: #555;
|
|
292
|
+
}
|
|
293
|
+
.type-info {
|
|
294
|
+
margin-top: 16px;
|
|
295
|
+
display: flex;
|
|
296
|
+
gap: 8px;
|
|
297
|
+
flex-wrap: wrap;
|
|
298
|
+
}
|
|
299
|
+
.type-badge {
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 5px;
|
|
303
|
+
padding: 5px 10px;
|
|
304
|
+
border-radius: 6px;
|
|
305
|
+
font-size: 0.75rem;
|
|
306
|
+
border: 1px solid #e0e0e0;
|
|
307
|
+
background: #fafafa;
|
|
308
|
+
}
|
|
309
|
+
.type-badge .label {
|
|
310
|
+
color: #888;
|
|
311
|
+
font-weight: 400;
|
|
312
|
+
}
|
|
313
|
+
.type-badge .value {
|
|
314
|
+
font-weight: 600;
|
|
315
|
+
color: #333;
|
|
316
|
+
}
|
|
317
|
+
.x-section {
|
|
318
|
+
display: flex;
|
|
319
|
+
gap: 0;
|
|
320
|
+
margin-top: 12px;
|
|
321
|
+
background: #fff;
|
|
322
|
+
border: 2px solid #0c4da2;
|
|
323
|
+
border-top: none;
|
|
324
|
+
overflow: hidden;
|
|
325
|
+
}
|
|
326
|
+
.x-detail {
|
|
327
|
+
flex: 0 0 320px;
|
|
328
|
+
padding: 20px 25px;
|
|
329
|
+
border-right: 1px solid rgba(46, 164, 223, 0.1);
|
|
330
|
+
font-size: 0.875rem;
|
|
331
|
+
color: #212529;
|
|
332
|
+
}
|
|
333
|
+
.x-chart {
|
|
334
|
+
flex: 1;
|
|
335
|
+
padding: 16px;
|
|
336
|
+
min-height: 280px;
|
|
337
|
+
}
|
|
338
|
+
.y-info {
|
|
339
|
+
background: linear-gradient(135deg, #f0f7ff 0%, #e8f4f8 100%);
|
|
340
|
+
border-radius: 8px;
|
|
341
|
+
padding: 16px 20px;
|
|
342
|
+
margin-bottom: 16px;
|
|
343
|
+
border-left: 4px solid #0c4da2;
|
|
344
|
+
}
|
|
345
|
+
.y-name {
|
|
346
|
+
font-size: 1rem;
|
|
347
|
+
font-weight: 700;
|
|
348
|
+
color: #0c4da2;
|
|
349
|
+
margin-bottom: 6px;
|
|
350
|
+
}
|
|
351
|
+
.y-desc {
|
|
352
|
+
font-size: 0.875rem;
|
|
353
|
+
color: #444;
|
|
354
|
+
line-height: 1.6;
|
|
355
|
+
margin-bottom: 8px;
|
|
356
|
+
}
|
|
357
|
+
.y-formula {
|
|
358
|
+
font-size: 0.85rem;
|
|
359
|
+
font-weight: 600;
|
|
360
|
+
color: #333;
|
|
361
|
+
background: rgba(255,255,255,0.7);
|
|
362
|
+
padding: 8px 12px;
|
|
363
|
+
border-radius: 4px;
|
|
364
|
+
font-family: monospace;
|
|
365
|
+
}
|
|
366
|
+
.y-formula-label {
|
|
367
|
+
font-size: 0.75rem;
|
|
368
|
+
color: #888;
|
|
369
|
+
margin-bottom: 4px;
|
|
370
|
+
}
|
|
371
|
+
`
|
|
372
|
+
];
|
|
373
|
+
DsspKpiOverview = __decorate([
|
|
374
|
+
customElement('dssp-kpi-overview')
|
|
375
|
+
// @ts-ignore styles type override
|
|
376
|
+
], DsspKpiOverview);
|
|
377
|
+
export { DsspKpiOverview };
|
|
378
|
+
//# sourceMappingURL=dssp-kpi-overview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dssp-kpi-overview.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/dssp-kpi-overview.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing, CSSResultGroup } from 'lit'\nimport { customElement } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\nimport { KpiOverview } from '@things-factory/kpi/dist-client/pages/kpi/kpi-overview.js'\n\nimport '../../components/kpi-lookup-chart'\nimport '../../components/kpi-2d-lookup-chart'\n\nconst GET_KPI_OVERVIEW_EXT = gql`\n query {\n kpiCategories: kpisLevel1 {\n id\n name\n description\n formula\n active\n kpis: children {\n id\n name\n description\n formula\n active\n grades\n scoreType\n valueType\n }\n }\n }\n`\n\n/**\n * KPI scoreType 정의 — value → score 변환 방식\n *\n * NONE — 미설정 (부모 KPI 등)\n * DIRECT — value = score, 별도 변환 없음 (Y/Z: formula 결과가 곧 score)\n * FORMULA — scoreFormula로 value → score 변환\n * LOOKUP — grade table(1D)로 raw value → 0~1 score 변환 (연속값 KPI)\n * 예: X11(연면적 대비 공사기간), X33(검측 불합격률), X34(SL-PA)\n * → 성과 분포 커브 차트로 표시\n * CUSTOM — 2D 룩업 등 특수 구조의 grade (다차원 입력 → 점수)\n * 예: X13(일정 이탈 수준) = 공정률 × 편차율 → 1~5점\n * → 히트맵 차트로 표시\n * ASSESSMENT — 감리자 직접 평가, value(1~5) = score (grade table 불필요)\n * 예: X14(일정성과 수준 평가), X23(비용성과), X35(품질성과) 등\n * → 등급 기준 설명만 표시 (차트 없음)\n *\n * Note: DIRECT와 ASSESSMENT는 모두 value=score이지만,\n * ASSESSMENT는 감리자가 1~5점을 직접 입력하는 정성 평가를 의미하고,\n * DIRECT는 산식 등으로 자동 계산된 value가 변환 없이 score가 되는 것을 의미한다.\n */\n\n/**\n * KPI 개요 페이지 — 등급 구간을 성과 분포 차트로 표현\n *\n * - 0~1 스케일 KPI: 성과 분포 커브 (kpi-lookup-chart)\n * - 1~5 평가형 KPI: 5단계 세그먼트 게이지 (kpi-lookup-chart 자동 감지)\n * - X13 2D 룩업: 공정률×편차율 히트맵 (kpi-2d-lookup-chart)\n */\n@customElement('dssp-kpi-overview')\n// @ts-ignore styles type override\nexport class DsspKpiOverview extends KpiOverview {\n static styles = [\n KpiOverview.styles,\n css`\n :host {\n overflow-y: auto;\n }\n .grades-section {\n margin-top: 16px;\n background: #fff;\n border-radius: 8px;\n padding: 20px;\n border: 2px solid #0c4da2;\n }\n .grades-title {\n font-size: 1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 12px;\n }\n .grades-chart {\n width: 100%;\n height: 320px;\n }\n .grade-descriptions {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 12px;\n }\n .grade-chip {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border-radius: 20px;\n font-size: 0.8rem;\n font-weight: 500;\n background: #f5f5f5;\n border: 1px solid #e0e0e0;\n }\n .grade-chip .dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n .grade-chip .score {\n font-weight: 700;\n min-width: 24px;\n }\n .grade-chip .desc {\n color: #555;\n }\n .type-info {\n margin-top: 16px;\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n .type-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 5px 10px;\n border-radius: 6px;\n font-size: 0.75rem;\n border: 1px solid #e0e0e0;\n background: #fafafa;\n }\n .type-badge .label {\n color: #888;\n font-weight: 400;\n }\n .type-badge .value {\n font-weight: 600;\n color: #333;\n }\n .x-section {\n display: flex;\n gap: 0;\n margin-top: 12px;\n background: #fff;\n border: 2px solid #0c4da2;\n border-top: none;\n overflow: hidden;\n }\n .x-detail {\n flex: 0 0 320px;\n padding: 20px 25px;\n border-right: 1px solid rgba(46, 164, 223, 0.1);\n font-size: 0.875rem;\n color: #212529;\n }\n .x-chart {\n flex: 1;\n padding: 16px;\n min-height: 280px;\n }\n .y-info {\n background: linear-gradient(135deg, #f0f7ff 0%, #e8f4f8 100%);\n border-radius: 8px;\n padding: 16px 20px;\n margin-bottom: 16px;\n border-left: 4px solid #0c4da2;\n }\n .y-name {\n font-size: 1rem;\n font-weight: 700;\n color: #0c4da2;\n margin-bottom: 6px;\n }\n .y-desc {\n font-size: 0.875rem;\n color: #444;\n line-height: 1.6;\n margin-bottom: 8px;\n }\n .y-formula {\n font-size: 0.85rem;\n font-weight: 600;\n color: #333;\n background: rgba(255,255,255,0.7);\n padding: 8px 12px;\n border-radius: 4px;\n font-family: monospace;\n }\n .y-formula-label {\n font-size: 0.75rem;\n color: #888;\n margin-bottom: 4px;\n }\n `\n ]\n\n override async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_OVERVIEW_EXT })\n this.kpiCategories = data.kpiCategories\n this.loading = false\n } catch (e) {\n this.error = e instanceof Error ? e : new Error(String(e))\n this.loading = false\n }\n }\n\n override render() {\n if (this.error) return html`<div>에러: ${this.error?.message}</div>`\n if (!this.kpiCategories.length) return html`<div>KPI 카테고리가 없습니다.</div>`\n\n const selctedY = this.kpiCategories?.[this.selectedGroup] || ''\n const groupRaw = this.kpiCategories[this.selectedGroup] as any\n // active가 false인 KPI 제외 (X31, X32 등 삭제된 지표)\n const activeKpis = groupRaw?.kpis?.filter((k: any) => k.active !== false) || []\n const group = { ...groupRaw, kpis: activeKpis }\n const kpi = group?.kpis?.[this.selectedKpi] as any\n\n return html`\n <div class=\"overview-container\">\n <div class=\"overview-left\">\n <div class=\"overview-title\">KPI개요</div>\n <div class=\"overview-desc\">\n 건설현장 KPI관리 시스템은 프로젝트 성과를 측정하고, 모니터링하여 효율적인 공정 관리를 지원합니다. 주요 지표를\n 통해 현장 생산성, 품질, 안전 등을 실시간으로 파악할 수 있습니다.\n </div>\n </div>\n </div>\n <div class=\"group-tabs\">\n ${this.kpiCategories.map(\n (g: any, i: number) => html`\n <button\n class=\"group-tab\"\n ?selected=${i === this.selectedGroup}\n @click=${() => {\n this.selectedGroup = i\n this.selectedKpi = 0\n }}\n >\n ${g.name}\n </button>\n `\n )}\n </div>\n <!-- Y 섹션: 좌=Y 설명+산식 | 우=X-KPI 목록 -->\n <div class=\"main-content\">\n <div class=\"markdown\">\n <div class=\"kpi-title\">${group.name}</div>\n ${group.description ? html`<div style=\"line-height:1.7;margin-bottom:12px;\">${group.description}</div>` : ''}\n ${group.formula\n ? html`\n <div class=\"subtitle\"><span class=\"subtitle-icon\"></span> 산식</div>\n <div class=\"kpi-formula\">${group.formula}</div>\n `\n : ''}\n </div>\n <div class=\"toc\">\n <div class=\"submenu-header\">\n <span class=\"submenu-icon\"></span>\n <span class=\"toc-title\">${selctedY.name} 지표</span>\n </div>\n <ul class=\"toc-list\">\n ${group?.kpis?.map(\n (k: any, i: number) => html`\n <li>\n <button class=\"toc-btn\" ?selected=${i === this.selectedKpi} @click=${() => (this.selectedKpi = i)}>\n ${k.name}\n </button>\n </li>\n `\n )}\n </ul>\n </div>\n </div>\n\n <!-- X 섹션: 좌=X 설명+산식 | 우=성과분포/등급 -->\n <div class=\"x-section\">\n <div class=\"x-detail\">\n <div class=\"kpi-title\">${kpi?.name || ''}</div>\n <div style=\"line-height:1.7;margin-bottom:12px;\">${kpi?.description || ''}</div>\n ${kpi?.formula\n ? html`\n <div class=\"subtitle\"><span class=\"subtitle-icon\"></span> 주요산식</div>\n <div class=\"kpi-formula\">${kpi?.formula}</div>\n `\n : ''}\n ${this._renderTypeInfo(kpi)}\n </div>\n <div class=\"x-chart\">\n ${this._renderGradesContent(kpi)}\n </div>\n </div>\n `\n }\n\n /**\n * 성과분포/등급 콘텐츠 렌더링 — scoreType + valueType 기반 분기\n *\n * valueType=ASSESSED → 등급 기준 설명만 (감리자 직접 평가, 차트 없음)\n * scoreType=CUSTOM → 2D 히트맵 (공정률×편차율)\n * scoreType=LOOKUP → 성과 분포 커브 (value→score 매핑)\n * scoreType=DIRECT → 등급 데이터 없음 (Y/Z 등)\n */\n private _renderGradesContent(kpi: any) {\n if (!kpi?.grades) return html`<div style=\"color:#999;padding:20px;\">등급 데이터 없음</div>`\n\n const scoreType = kpi.scoreType\n const valueType = kpi.valueType\n const is2D = scoreType === 'CUSTOM' ||\n (kpi.grades && typeof kpi.grades === 'object' && !Array.isArray(kpi.grades) && kpi.grades.type === 'PROGRESS_DEVIATION_LOOKUP')\n const isArray = Array.isArray(kpi.grades) && kpi.grades.length > 0\n const isAssessment = valueType === 'ASSESSED' ||\n (!scoreType && !valueType && isArray && kpi.grades.length <= 5 &&\n kpi.grades.every((g: any) => g.score >= 1 && g.score <= 5 && g.score === Math.floor(g.score) && g.minValue === g.score))\n\n if (isAssessment) {\n return html`\n <div class=\"grades-title\">등급 기준</div>\n ${isArray ? this._renderAssessmentDescriptions(kpi.grades) : nothing}\n `\n }\n\n if (is2D) {\n return html`\n <div class=\"grades-title\">성과 분포</div>\n <div class=\"grades-chart\">\n <kpi-2d-lookup-chart .grades=${kpi.grades} .progressRate=${50}></kpi-2d-lookup-chart>\n </div>\n `\n }\n\n if (isArray) {\n return html`\n <div class=\"grades-title\">성과 분포</div>\n <div class=\"grades-chart\">\n <kpi-lookup-chart .grades=${kpi.grades} .showDots=${true} .scoreType=${kpi.scoreType || ''}></kpi-lookup-chart>\n </div>\n `\n }\n\n return html`<div style=\"color:#999;padding:20px;\">등급 데이터 없음</div>`\n }\n\n private _renderTypeInfo(kpi: any) {\n const valueLabels: Record<string, string> = {\n MEASURED: '외부 시스템 수집',\n ASSESSED: '감리자 직접 평가 (1~5)',\n CALCULATED: '산식 자동 계산',\n COMPOSITE: '다차원 복합 입력'\n }\n const scoreLabels: Record<string, string> = {\n DIRECT: 'value = score (변환 없음)',\n FORMULA: 'scoreFormula 수식 변환',\n LOOKUP: 'Grade Table 룩업',\n CUSTOM: '2D 룩업 (특수 변환)'\n }\n\n const vLabel = kpi?.valueType ? valueLabels[kpi.valueType] || kpi.valueType : null\n const sLabel = kpi?.scoreType ? scoreLabels[kpi.scoreType] || kpi.scoreType : null\n\n if (!vLabel && !sLabel) return nothing\n\n return html`\n <div class=\"type-info\">\n ${vLabel\n ? html`<span class=\"type-badge\"><span class=\"label\">Value 산정</span><span class=\"value\">${vLabel}</span></span>`\n : ''}\n ${sLabel\n ? html`<span class=\"type-badge\"><span class=\"label\">Score 산정</span><span class=\"value\">${sLabel}</span></span>`\n : ''}\n </div>\n `\n }\n\n private _renderAssessmentDescriptions(grades: any[]) {\n const colors = ['#e53935', '#ff9800', '#ffca28', '#66bb6a', '#2e7d32']\n const sorted = [...grades].sort((a, b) => a.score - b.score)\n\n return html`\n <div class=\"grade-descriptions\">\n ${sorted.map(\n (g: any) => html`\n <div class=\"grade-chip\">\n <span class=\"dot\" style=\"background: ${colors[(g.score || 1) - 1]}\"></span>\n <span class=\"score\">${g.score}점</span>\n <span class=\"desc\">${g.description || g.name || ''}</span>\n </div>\n `\n )}\n </div>\n `\n }\n}\n"]}
|
|
@@ -109,7 +109,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
111
|
this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
|
|
112
|
-
console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats);
|
|
113
112
|
// 부모에게 전체 Y-level KPI 통계 전달
|
|
114
113
|
this.dispatchEvent(new CustomEvent('total-kpi-y-stats-loaded', {
|
|
115
114
|
detail: { data: this.kpiYComprehensiveStats },
|
|
@@ -170,7 +169,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
170
169
|
}
|
|
171
170
|
});
|
|
172
171
|
this.regionalKpiStats = response.data.kpiZValueComprehensiveStatsByGeoGroup || [];
|
|
173
|
-
console.log('Regional KPI Stats:', this.regionalKpiStats);
|
|
174
172
|
// 부모에게 지역별 KPI 통계 전달
|
|
175
173
|
this.dispatchEvent(new CustomEvent('regional-kpi-stats-loaded', {
|
|
176
174
|
detail: { data: this.regionalKpiStats },
|
|
@@ -228,7 +226,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
228
226
|
}
|
|
229
227
|
});
|
|
230
228
|
this.monthlyTrendData = response.data.kpiZValueMonthlyTrendByGeoGroup || [];
|
|
231
|
-
console.log('Monthly Trend Data:', this.monthlyTrendData);
|
|
232
229
|
// 부모에게 월별 추이 데이터 전달
|
|
233
230
|
this.dispatchEvent(new CustomEvent('monthly-trend-data-loaded', {
|
|
234
231
|
detail: { data: this.monthlyTrendData },
|
|
@@ -275,8 +272,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
275
272
|
'Y5. 환경성과': '환경',
|
|
276
273
|
'Y6. 생산성성과': '생산성'
|
|
277
274
|
};
|
|
278
|
-
console.log('generateChartData - selectedChartType:', this.selectedChartType);
|
|
279
|
-
console.log('generateChartData - kpiYComprehensiveStats:', this.kpiYComprehensiveStats);
|
|
280
275
|
if (this.selectedChartType === 'radar') {
|
|
281
276
|
// 레이더 차트용 데이터 생성 (전체 평균만 표시)
|
|
282
277
|
const data = [];
|
|
@@ -295,8 +290,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
295
290
|
});
|
|
296
291
|
this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
|
|
297
292
|
this.chartData = data;
|
|
298
|
-
console.log('Radar chart data:', data);
|
|
299
|
-
console.log('Radar chart categories:', this.chartCategories);
|
|
300
293
|
}
|
|
301
294
|
else {
|
|
302
295
|
// 박스플롯용 데이터 생성 (sv-project-detail.ts의 getYKpiBoxplotData 참조)
|
|
@@ -320,8 +313,6 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
|
|
|
320
313
|
});
|
|
321
314
|
this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
|
|
322
315
|
this.chartData = data;
|
|
323
|
-
console.log('Boxplot chart data:', data);
|
|
324
|
-
console.log('Boxplot chart categories:', this.chartCategories);
|
|
325
316
|
}
|
|
326
317
|
}
|
|
327
318
|
_onAllPeriodChange(e) {
|