@dssp/dkpi 1.0.0-alpha.69 → 1.0.0-alpha.70

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.
@@ -0,0 +1,18 @@
1
+ import { PageView } from '@operato/shell';
2
+ export declare class KpiSystemGuide extends PageView {
3
+ static styles: import("lit").CSSResult[];
4
+ kpiRoot: any[];
5
+ metrics: any[];
6
+ loading: boolean;
7
+ selectedDetail: any;
8
+ get context(): {
9
+ title: string;
10
+ };
11
+ firstUpdated(): Promise<void>;
12
+ /** 메트릭이 사용되는 KPI 목록 */
13
+ private getMetricUsage;
14
+ private showDetail;
15
+ private closeDetail;
16
+ render(): import("lit-html").TemplateResult<1>;
17
+ private _renderDetailPopup;
18
+ }
@@ -0,0 +1,535 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css, nothing } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ import { PageView } from '@operato/shell';
5
+ import { ScrollbarStyles } from '@operato/styles';
6
+ import { client } from '@operato/graphql';
7
+ import { navigate } from '@operato/shell';
8
+ import gql from 'graphql-tag';
9
+ /**
10
+ * KPI 시스템 종합 안내 페이지
11
+ *
12
+ * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여
13
+ * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.
14
+ */
15
+ const GET_KPI_SYSTEM = gql `
16
+ query {
17
+ kpiRoot: kpisLevel1 {
18
+ id
19
+ name
20
+ description
21
+ formula
22
+ weight
23
+ scoreType
24
+ valueType
25
+ active
26
+ kpis: children {
27
+ id
28
+ name
29
+ description
30
+ formula
31
+ weight
32
+ scoreType
33
+ valueType
34
+ active
35
+ grades
36
+ }
37
+ }
38
+ kpiMetrics {
39
+ items {
40
+ id
41
+ name
42
+ unit
43
+ source
44
+ collectType
45
+ active
46
+ }
47
+ }
48
+ }
49
+ `;
50
+ const SCORE_TYPE_LABELS = {
51
+ DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },
52
+ FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },
53
+ LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },
54
+ CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }
55
+ };
56
+ /** 외부 시스템 URL 매핑 */
57
+ const EXTERNAL_SYSTEM_URLS = {
58
+ '세움터(EAIS)': 'https://cloud.eais.go.kr',
59
+ '키스콘(KISCON)': 'https://www.kiscon.net',
60
+ '올바로(Allbaro)': 'https://www.allbaro.or.kr',
61
+ '전자카드제': 'https://www.cw.or.kr'
62
+ };
63
+ const VALUE_TYPE_LABELS = {
64
+ MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },
65
+ ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },
66
+ CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },
67
+ COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }
68
+ };
69
+ let KpiSystemGuide = class KpiSystemGuide extends PageView {
70
+ constructor() {
71
+ super(...arguments);
72
+ this.kpiRoot = [];
73
+ this.metrics = [];
74
+ this.loading = true;
75
+ this.selectedDetail = null;
76
+ }
77
+ get context() {
78
+ return { title: 'KPI 시스템 가이드' };
79
+ }
80
+ async firstUpdated() {
81
+ var _a;
82
+ try {
83
+ const { data } = await client.query({ query: GET_KPI_SYSTEM });
84
+ this.kpiRoot = data.kpiRoot || [];
85
+ this.metrics = ((_a = data.kpiMetrics) === null || _a === void 0 ? void 0 : _a.items) || [];
86
+ }
87
+ catch (e) {
88
+ console.error('Failed to load KPI system data:', e);
89
+ }
90
+ finally {
91
+ this.loading = false;
92
+ }
93
+ }
94
+ /** 메트릭이 사용되는 KPI 목록 */
95
+ getMetricUsage(metricName) {
96
+ var _a;
97
+ const used = [];
98
+ for (const y of this.kpiRoot) {
99
+ for (const x of (y.kpis || [])) {
100
+ if ((_a = x.formula) === null || _a === void 0 ? void 0 : _a.includes(`[${metricName}]`)) {
101
+ used.push(x.name);
102
+ }
103
+ }
104
+ }
105
+ return used;
106
+ }
107
+ showDetail(item, type) {
108
+ this.selectedDetail = Object.assign(Object.assign({}, item), { _type: type });
109
+ }
110
+ closeDetail() {
111
+ this.selectedDetail = null;
112
+ }
113
+ render() {
114
+ if (this.loading)
115
+ return html `<div style="padding:40px;text-align:center;color:#888;">로딩 중...</div>`;
116
+ // Z 노드 (루트의 부모)
117
+ const allX = this.kpiRoot.flatMap((y) => (y.kpis || []).filter((x) => x.active !== false));
118
+ return html `
119
+ <div class="page-title">KPI 시스템 가이드</div>
120
+ <div class="page-subtitle">
121
+ 건축현장 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다.
122
+ 모든 정보는 DB에서 실시간 조회됩니다.
123
+ </div>
124
+
125
+ <!-- 범례 -->
126
+ <div class="legend">
127
+ <span class="legend-title">Score 산정:</span>
128
+ ${Object.entries(SCORE_TYPE_LABELS).map(([, v]) => html `<span class="legend-group"><span class="badge badge-score">${v.label}</span></span>`)}
129
+ <span style="width:16px"></span>
130
+ <span class="legend-title">Value 획득:</span>
131
+ ${Object.entries(VALUE_TYPE_LABELS).map(([, v]) => html `<span class="legend-group">${v.icon} <span class="badge badge-value">${v.label}</span></span>`)}
132
+ </div>
133
+
134
+ <!-- 1. KPI 계층 구조 -->
135
+ <div class="tree-section">
136
+ <div class="section-title">📊 KPI 계층 구조 (Z → Y → X)</div>
137
+
138
+ <!-- Z 루트 -->
139
+ <div class="z-node" @click=${() => this.showDetail({ name: 'Z. 전체스코어', description: 'Y1~Y6 가중합', formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6', scoreType: 'DIRECT', valueType: 'CALCULATED' }, 'kpi')}>
140
+ <div class="z-name">Z. 전체스코어</div>
141
+ <div class="z-formula">= ${this.kpiRoot.map((y) => `${y.name.split('.')[0]}×${y.weight}`).join(' + ')}</div>
142
+ </div>
143
+
144
+ <!-- Y 그룹들 -->
145
+ ${this.kpiRoot.map((y) => {
146
+ const activeKpis = (y.kpis || []).filter((x) => x.active !== false);
147
+ return html `
148
+ <div class="y-group">
149
+ <div class="y-header" @click=${() => this.showDetail(y, 'kpi')}>
150
+ <span class="y-name">${y.name}</span>
151
+ <span class="y-weight">가중치: ${y.weight}</span>
152
+ </div>
153
+ <div class="y-body">
154
+ ${y.formula ? html `<div class="y-formula">${y.formula}</div>` : ''}
155
+ <div class="x-cards">
156
+ ${activeKpis.map((x) => {
157
+ const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' };
158
+ const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' };
159
+ return html `
160
+ <div class="x-card" @click=${() => this.showDetail(x, 'kpi')}>
161
+ <div class="x-name">${x.name}</div>
162
+ <div class="x-formula">${x.formula || '—'}</div>
163
+ <div class="x-badges">
164
+ <span class="badge badge-score">${st.label}</span>
165
+ <span class="badge badge-value">${vt.icon} ${vt.label}</span>
166
+ </div>
167
+ </div>
168
+ `;
169
+ })}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ `;
174
+ })}
175
+ </div>
176
+
177
+ <!-- 2. 원천 메트릭 -->
178
+ <div class="tree-section">
179
+ <div class="section-title">📋 원천 메트릭 (${this.metrics.filter((m) => m.active).length}개 정의)</div>
180
+ <div class="metrics-grid">
181
+ ${this.metrics
182
+ .filter((m) => m.active)
183
+ .map((m) => {
184
+ const usage = this.getMetricUsage(m.name);
185
+ return html `
186
+ <div class="metric-card" @click=${() => this.showDetail(m, 'metric')}>
187
+ <div class="metric-name">${m.name}</div>
188
+ <div class="metric-unit">
189
+ ${m.unit || '—'} ·
190
+ ${m.collectType === 'EXTERNAL' && m.source
191
+ ? EXTERNAL_SYSTEM_URLS[m.source]
192
+ ? html `<a href="${EXTERNAL_SYSTEM_URLS[m.source]}" target="_blank" rel="noopener"
193
+ style="color:#1976d2;text-decoration:none;" @click=${(e) => e.stopPropagation()}
194
+ >${m.source} ↗</a>`
195
+ : m.source
196
+ : m.collectType || 'MANUAL'}
197
+ </div>
198
+ ${usage.length > 0
199
+ ? html `<div class="metric-used">→ ${usage.join(', ')}</div>`
200
+ : html `<div class="metric-used" style="color:#ccc;">미사용</div>`}
201
+ </div>
202
+ `;
203
+ })}
204
+ </div>
205
+ </div>
206
+
207
+ <!-- 3. 시스템 요약 -->
208
+ <div class="tree-section">
209
+ <div class="section-title">📈 시스템 요약</div>
210
+ <div style="display:flex;gap:16px;flex-wrap:wrap;">
211
+ <div style="flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;">
212
+ <div style="font-size:2rem;font-weight:800;color:#1565c0;">${allX.length}</div>
213
+ <div style="font-size:0.85rem;color:#555;">X-level 지표</div>
214
+ </div>
215
+ <div style="flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;">
216
+ <div style="font-size:2rem;font-weight:800;color:#2e7d32;">${this.kpiRoot.length}</div>
217
+ <div style="font-size:0.85rem;color:#555;">Y-level 영역</div>
218
+ </div>
219
+ <div style="flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;">
220
+ <div style="font-size:2rem;font-weight:800;color:#e65100;">${this.metrics.filter((m) => m.active).length}</div>
221
+ <div style="font-size:0.85rem;color:#555;">원천 메트릭</div>
222
+ </div>
223
+ <div style="flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;">
224
+ <div style="font-size:2rem;font-weight:800;color:#c62828;">${allX.filter((x) => x.valueType === 'ASSESSED').length}</div>
225
+ <div style="font-size:0.85rem;color:#555;">감리자 평가 항목</div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+
230
+ <!-- 팝업 -->
231
+ ${this.selectedDetail ? this._renderDetailPopup() : nothing}
232
+ `;
233
+ }
234
+ _renderDetailPopup() {
235
+ const d = this.selectedDetail;
236
+ const isKpi = d._type === 'kpi';
237
+ return html `
238
+ <div class="popup-overlay" @click=${() => this.closeDetail()}>
239
+ <div class="popup-box" @click=${(e) => e.stopPropagation()}>
240
+ <div class="popup-title">
241
+ <span>${d.name}</span>
242
+ <button class="popup-close" @click=${() => this.closeDetail()}>×</button>
243
+ </div>
244
+
245
+ ${d.description ? html `
246
+ <div class="popup-row">
247
+ <span class="popup-label">설명</span>
248
+ <span class="popup-value">${d.description}</span>
249
+ </div>
250
+ ` : ''}
251
+
252
+ ${isKpi ? html `
253
+ ${d.formula ? html `
254
+ <div class="popup-row"><span class="popup-label">산식</span></div>
255
+ <div class="popup-formula">${d.formula}</div>
256
+ ` : ''}
257
+
258
+ ${d.scoreType ? html `
259
+ <div class="popup-row">
260
+ <span class="popup-label">Score 산정</span>
261
+ <span class="popup-value" style="color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}">
262
+ ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}
263
+ </span>
264
+ </div>
265
+ ` : ''}
266
+
267
+ ${d.valueType ? html `
268
+ <div class="popup-row">
269
+ <span class="popup-label">Value 획득</span>
270
+ <span class="popup-value" style="color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}">
271
+ ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''} ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}
272
+ </span>
273
+ </div>
274
+ ` : ''}
275
+
276
+ ${d.weight ? html `
277
+ <div class="popup-row">
278
+ <span class="popup-label">가중치</span>
279
+ <span class="popup-value">${d.weight}</span>
280
+ </div>
281
+ ` : ''}
282
+
283
+ <div style="margin-top:16px;padding-top:12px;border-top:1px solid #eee;">
284
+ <span class="popup-link" @click=${() => { this.closeDetail(); navigate('kpi-overview'); }}>
285
+ → KPI 개요에서 보기
286
+ </span>
287
+ &nbsp;&nbsp;
288
+ <span class="popup-link" @click=${() => { this.closeDetail(); navigate('kpi-admin'); }}>
289
+ → KPI 관리에서 편집
290
+ </span>
291
+ </div>
292
+ ` : html `
293
+ <div class="popup-row">
294
+ <span class="popup-label">단위</span>
295
+ <span class="popup-value">${d.unit || '—'}</span>
296
+ </div>
297
+ <div class="popup-row">
298
+ <span class="popup-label">수집 방식</span>
299
+ <span class="popup-value">${d.collectType || 'MANUAL'}</span>
300
+ </div>
301
+ <div class="popup-row">
302
+ <span class="popup-label">데이터 소스</span>
303
+ <span class="popup-value">
304
+ ${d.source && EXTERNAL_SYSTEM_URLS[d.source]
305
+ ? html `<a href="${EXTERNAL_SYSTEM_URLS[d.source]}" target="_blank" rel="noopener" style="color:#1976d2;">${d.source} ↗</a>`
306
+ : d.source || '미설정'}
307
+ </span>
308
+ </div>
309
+
310
+ <div style="margin-top:12px;">
311
+ <div class="popup-label" style="margin-bottom:6px;">사용처:</div>
312
+ ${this.getMetricUsage(d.name).map(kpi => html `<div style="font-size:0.85rem;color:#1976d2;margin-bottom:2px;">· ${kpi}</div>`)}
313
+ ${this.getMetricUsage(d.name).length === 0 ? html `<div style="color:#ccc;font-size:0.85rem;">미사용</div>` : ''}
314
+ </div>
315
+
316
+ <div style="margin-top:16px;padding-top:12px;border-top:1px solid #eee;">
317
+ <span class="popup-link" @click=${() => { this.closeDetail(); navigate('kpi-metric-value-list'); }}>
318
+ → 메트릭 값 관리
319
+ </span>
320
+ </div>
321
+ `}
322
+ </div>
323
+ </div>
324
+ `;
325
+ }
326
+ };
327
+ KpiSystemGuide.styles = [
328
+ ScrollbarStyles,
329
+ css `
330
+ :host {
331
+ display: block;
332
+ overflow-y: auto;
333
+ background: #f5f6fa;
334
+ padding: 24px;
335
+ }
336
+
337
+ .page-title {
338
+ font-size: 1.5rem;
339
+ font-weight: 800;
340
+ color: #1a237e;
341
+ margin-bottom: 4px;
342
+ }
343
+ .page-subtitle {
344
+ font-size: 0.9rem;
345
+ color: #666;
346
+ margin-bottom: 24px;
347
+ }
348
+
349
+ /* 계층 트리 */
350
+ .tree-section {
351
+ background: #fff;
352
+ border-radius: 12px;
353
+ padding: 24px;
354
+ margin-bottom: 24px;
355
+ box-shadow: 0 2px 8px rgba(0,0,0,0.06);
356
+ }
357
+ .section-title {
358
+ font-size: 1.1rem;
359
+ font-weight: 700;
360
+ color: #333;
361
+ margin-bottom: 16px;
362
+ display: flex;
363
+ align-items: center;
364
+ gap: 8px;
365
+ }
366
+
367
+ /* Z 루트 */
368
+ .z-node {
369
+ background: linear-gradient(135deg, #1a237e, #283593);
370
+ color: #fff;
371
+ border-radius: 10px;
372
+ padding: 16px 20px;
373
+ margin-bottom: 16px;
374
+ cursor: pointer;
375
+ }
376
+ .z-node:hover { opacity: 0.95; }
377
+ .z-name { font-size: 1.1rem; font-weight: 700; }
378
+ .z-formula { font-size: 0.8rem; opacity: 0.8; margin-top: 4px; font-family: monospace; }
379
+
380
+ /* Y 그룹 */
381
+ .y-group {
382
+ margin-bottom: 16px;
383
+ border: 1px solid #e0e0e0;
384
+ border-radius: 10px;
385
+ overflow: hidden;
386
+ }
387
+ .y-header {
388
+ background: linear-gradient(135deg, #0c4da2, #1565c0);
389
+ color: #fff;
390
+ padding: 12px 16px;
391
+ display: flex;
392
+ justify-content: space-between;
393
+ align-items: center;
394
+ cursor: pointer;
395
+ }
396
+ .y-header:hover { opacity: 0.95; }
397
+ .y-name { font-weight: 700; font-size: 1rem; }
398
+ .y-weight {
399
+ background: rgba(255,255,255,0.2);
400
+ padding: 2px 10px;
401
+ border-radius: 12px;
402
+ font-size: 0.8rem;
403
+ }
404
+ .y-body { padding: 12px; }
405
+ .y-formula {
406
+ font-size: 0.8rem;
407
+ color: #555;
408
+ background: #f8f9fa;
409
+ padding: 8px 12px;
410
+ border-radius: 6px;
411
+ font-family: monospace;
412
+ margin-bottom: 10px;
413
+ word-break: break-all;
414
+ }
415
+
416
+ /* X 카드 */
417
+ .x-cards { display: flex; flex-wrap: wrap; gap: 8px; }
418
+ .x-card {
419
+ flex: 1 1 220px;
420
+ background: #fff;
421
+ border: 1px solid #e8e8e8;
422
+ border-radius: 8px;
423
+ padding: 10px 14px;
424
+ cursor: pointer;
425
+ transition: all 0.2s;
426
+ min-width: 200px;
427
+ }
428
+ .x-card:hover {
429
+ border-color: #1976d2;
430
+ box-shadow: 0 2px 8px rgba(25,118,210,0.15);
431
+ }
432
+ .x-name { font-weight: 600; font-size: 0.85rem; color: #333; margin-bottom: 4px; }
433
+ .x-formula {
434
+ font-size: 0.75rem; color: #888; font-family: monospace;
435
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
436
+ margin-bottom: 6px;
437
+ }
438
+ .x-badges { display: flex; gap: 4px; flex-wrap: wrap; }
439
+ .badge {
440
+ font-size: 0.65rem;
441
+ padding: 2px 6px;
442
+ border-radius: 4px;
443
+ font-weight: 600;
444
+ }
445
+ .badge-score { background: #e3f2fd; color: #1565c0; }
446
+ .badge-value { background: #e8f5e9; color: #2e7d32; }
447
+
448
+ /* 메트릭 섹션 */
449
+ .metrics-grid {
450
+ display: grid;
451
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
452
+ gap: 10px;
453
+ }
454
+ .metric-card {
455
+ background: #fff;
456
+ border: 1px solid #e8e8e8;
457
+ border-radius: 8px;
458
+ padding: 10px 14px;
459
+ transition: all 0.2s;
460
+ }
461
+ .metric-card:hover { border-color: #4caf50; }
462
+ .metric-name { font-weight: 600; font-size: 0.85rem; color: #333; }
463
+ .metric-unit { font-size: 0.75rem; color: #888; }
464
+ .metric-used {
465
+ font-size: 0.7rem; color: #1976d2; margin-top: 4px;
466
+ }
467
+
468
+ /* 팝업 */
469
+ .popup-overlay {
470
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
471
+ background: rgba(0,0,0,0.3); z-index: 1000;
472
+ display: flex; align-items: center; justify-content: center;
473
+ }
474
+ .popup-box {
475
+ background: #fff; border-radius: 12px; padding: 24px;
476
+ min-width: 400px; max-width: 600px; max-height: 80vh;
477
+ overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.2);
478
+ }
479
+ .popup-title {
480
+ font-size: 1.1rem; font-weight: 700; color: #1a237e;
481
+ margin-bottom: 16px; display: flex; justify-content: space-between;
482
+ }
483
+ .popup-close {
484
+ background: none; border: none; font-size: 1.2rem;
485
+ cursor: pointer; color: #999;
486
+ }
487
+ .popup-row {
488
+ display: flex; gap: 8px; margin-bottom: 8px;
489
+ font-size: 0.85rem;
490
+ }
491
+ .popup-label { color: #888; min-width: 100px; flex-shrink: 0; }
492
+ .popup-value { color: #333; font-weight: 500; }
493
+ .popup-formula {
494
+ background: #f5f5f5; padding: 10px; border-radius: 6px;
495
+ font-family: monospace; font-size: 0.85rem; margin: 8px 0;
496
+ word-break: break-all;
497
+ }
498
+ .popup-link {
499
+ color: #1976d2; cursor: pointer; text-decoration: underline;
500
+ font-size: 0.85rem;
501
+ }
502
+ .popup-link:hover { color: #0d47a1; }
503
+
504
+ /* 범례 */
505
+ .legend {
506
+ display: flex; gap: 16px; flex-wrap: wrap;
507
+ margin-bottom: 20px; padding: 12px 16px;
508
+ background: #fff; border-radius: 8px;
509
+ box-shadow: 0 1px 4px rgba(0,0,0,0.04);
510
+ }
511
+ .legend-group { display: flex; align-items: center; gap: 4px; font-size: 0.8rem; }
512
+ .legend-title { font-weight: 700; color: #555; margin-right: 4px; }
513
+ `
514
+ ];
515
+ __decorate([
516
+ state(),
517
+ __metadata("design:type", Array)
518
+ ], KpiSystemGuide.prototype, "kpiRoot", void 0);
519
+ __decorate([
520
+ state(),
521
+ __metadata("design:type", Array)
522
+ ], KpiSystemGuide.prototype, "metrics", void 0);
523
+ __decorate([
524
+ state(),
525
+ __metadata("design:type", Object)
526
+ ], KpiSystemGuide.prototype, "loading", void 0);
527
+ __decorate([
528
+ state(),
529
+ __metadata("design:type", Object)
530
+ ], KpiSystemGuide.prototype, "selectedDetail", void 0);
531
+ KpiSystemGuide = __decorate([
532
+ customElement('kpi-system-guide')
533
+ ], KpiSystemGuide);
534
+ export { KpiSystemGuide };
535
+ //# sourceMappingURL=kpi-system-guide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-system-guide.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-system-guide.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { client } from '@operato/graphql'\nimport { navigate } from '@operato/shell'\nimport gql from 'graphql-tag'\n\n/**\n * KPI 시스템 종합 안내 페이지\n *\n * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여\n * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.\n */\n\nconst GET_KPI_SYSTEM = gql`\n query {\n kpiRoot: kpisLevel1 {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n kpis: children {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n grades\n }\n }\n kpiMetrics {\n items {\n id\n name\n unit\n source\n collectType\n active\n }\n }\n }\n`\n\nconst SCORE_TYPE_LABELS: Record<string, { label: string; color: string }> = {\n DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },\n FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },\n LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },\n CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }\n}\n\n/** 외부 시스템 URL 매핑 */\nconst EXTERNAL_SYSTEM_URLS: Record<string, string> = {\n '세움터(EAIS)': 'https://cloud.eais.go.kr',\n '키스콘(KISCON)': 'https://www.kiscon.net',\n '올바로(Allbaro)': 'https://www.allbaro.or.kr',\n '전자카드제': 'https://www.cw.or.kr'\n}\n\nconst VALUE_TYPE_LABELS: Record<string, { label: string; color: string; icon: string }> = {\n MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },\n ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },\n CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },\n COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }\n}\n\n@customElement('kpi-system-guide')\nexport class KpiSystemGuide extends PageView {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: block;\n overflow-y: auto;\n background: #f5f6fa;\n padding: 24px;\n }\n\n .page-title {\n font-size: 1.5rem;\n font-weight: 800;\n color: #1a237e;\n margin-bottom: 4px;\n }\n .page-subtitle {\n font-size: 0.9rem;\n color: #666;\n margin-bottom: 24px;\n }\n\n /* 계층 트리 */\n .tree-section {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n margin-bottom: 24px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.06);\n }\n .section-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Z 루트 */\n .z-node {\n background: linear-gradient(135deg, #1a237e, #283593);\n color: #fff;\n border-radius: 10px;\n padding: 16px 20px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n .z-node:hover { opacity: 0.95; }\n .z-name { font-size: 1.1rem; font-weight: 700; }\n .z-formula { font-size: 0.8rem; opacity: 0.8; margin-top: 4px; font-family: monospace; }\n\n /* Y 그룹 */\n .y-group {\n margin-bottom: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 10px;\n overflow: hidden;\n }\n .y-header {\n background: linear-gradient(135deg, #0c4da2, #1565c0);\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n }\n .y-header:hover { opacity: 0.95; }\n .y-name { font-weight: 700; font-size: 1rem; }\n .y-weight {\n background: rgba(255,255,255,0.2);\n padding: 2px 10px;\n border-radius: 12px;\n font-size: 0.8rem;\n }\n .y-body { padding: 12px; }\n .y-formula {\n font-size: 0.8rem;\n color: #555;\n background: #f8f9fa;\n padding: 8px 12px;\n border-radius: 6px;\n font-family: monospace;\n margin-bottom: 10px;\n word-break: break-all;\n }\n\n /* X 카드 */\n .x-cards { display: flex; flex-wrap: wrap; gap: 8px; }\n .x-card {\n flex: 1 1 220px;\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n cursor: pointer;\n transition: all 0.2s;\n min-width: 200px;\n }\n .x-card:hover {\n border-color: #1976d2;\n box-shadow: 0 2px 8px rgba(25,118,210,0.15);\n }\n .x-name { font-weight: 600; font-size: 0.85rem; color: #333; margin-bottom: 4px; }\n .x-formula {\n font-size: 0.75rem; color: #888; font-family: monospace;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n margin-bottom: 6px;\n }\n .x-badges { display: flex; gap: 4px; flex-wrap: wrap; }\n .badge {\n font-size: 0.65rem;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n }\n .badge-score { background: #e3f2fd; color: #1565c0; }\n .badge-value { background: #e8f5e9; color: #2e7d32; }\n\n /* 메트릭 섹션 */\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 10px;\n }\n .metric-card {\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n transition: all 0.2s;\n }\n .metric-card:hover { border-color: #4caf50; }\n .metric-name { font-weight: 600; font-size: 0.85rem; color: #333; }\n .metric-unit { font-size: 0.75rem; color: #888; }\n .metric-used {\n font-size: 0.7rem; color: #1976d2; margin-top: 4px;\n }\n\n /* 팝업 */\n .popup-overlay {\n position: fixed; top: 0; left: 0; right: 0; bottom: 0;\n background: rgba(0,0,0,0.3); z-index: 1000;\n display: flex; align-items: center; justify-content: center;\n }\n .popup-box {\n background: #fff; border-radius: 12px; padding: 24px;\n min-width: 400px; max-width: 600px; max-height: 80vh;\n overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.2);\n }\n .popup-title {\n font-size: 1.1rem; font-weight: 700; color: #1a237e;\n margin-bottom: 16px; display: flex; justify-content: space-between;\n }\n .popup-close {\n background: none; border: none; font-size: 1.2rem;\n cursor: pointer; color: #999;\n }\n .popup-row {\n display: flex; gap: 8px; margin-bottom: 8px;\n font-size: 0.85rem;\n }\n .popup-label { color: #888; min-width: 100px; flex-shrink: 0; }\n .popup-value { color: #333; font-weight: 500; }\n .popup-formula {\n background: #f5f5f5; padding: 10px; border-radius: 6px;\n font-family: monospace; font-size: 0.85rem; margin: 8px 0;\n word-break: break-all;\n }\n .popup-link {\n color: #1976d2; cursor: pointer; text-decoration: underline;\n font-size: 0.85rem;\n }\n .popup-link:hover { color: #0d47a1; }\n\n /* 범례 */\n .legend {\n display: flex; gap: 16px; flex-wrap: wrap;\n margin-bottom: 20px; padding: 12px 16px;\n background: #fff; border-radius: 8px;\n box-shadow: 0 1px 4px rgba(0,0,0,0.04);\n }\n .legend-group { display: flex; align-items: center; gap: 4px; font-size: 0.8rem; }\n .legend-title { font-weight: 700; color: #555; margin-right: 4px; }\n `\n ]\n\n @state() kpiRoot: any[] = []\n @state() metrics: any[] = []\n @state() loading = true\n @state() selectedDetail: any = null\n\n get context() {\n return { title: 'KPI 시스템 가이드' }\n }\n\n async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_SYSTEM })\n this.kpiRoot = data.kpiRoot || []\n this.metrics = data.kpiMetrics?.items || []\n } catch (e) {\n console.error('Failed to load KPI system data:', e)\n } finally {\n this.loading = false\n }\n }\n\n /** 메트릭이 사용되는 KPI 목록 */\n private getMetricUsage(metricName: string): string[] {\n const used: string[] = []\n for (const y of this.kpiRoot) {\n for (const x of (y.kpis || [])) {\n if (x.formula?.includes(`[${metricName}]`)) {\n used.push(x.name)\n }\n }\n }\n return used\n }\n\n private showDetail(item: any, type: 'kpi' | 'metric') {\n this.selectedDetail = { ...item, _type: type }\n }\n\n private closeDetail() {\n this.selectedDetail = null\n }\n\n render() {\n if (this.loading) return html`<div style=\"padding:40px;text-align:center;color:#888;\">로딩 중...</div>`\n\n // Z 노드 (루트의 부모)\n const allX = this.kpiRoot.flatMap((y: any) => (y.kpis || []).filter((x: any) => x.active !== false))\n\n return html`\n <div class=\"page-title\">KPI 시스템 가이드</div>\n <div class=\"page-subtitle\">\n 건축현장 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다.\n 모든 정보는 DB에서 실시간 조회됩니다.\n </div>\n\n <!-- 범례 -->\n <div class=\"legend\">\n <span class=\"legend-title\">Score 산정:</span>\n ${Object.entries(SCORE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\"><span class=\"badge badge-score\">${v.label}</span></span>`\n )}\n <span style=\"width:16px\"></span>\n <span class=\"legend-title\">Value 획득:</span>\n ${Object.entries(VALUE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\">${v.icon} <span class=\"badge badge-value\">${v.label}</span></span>`\n )}\n </div>\n\n <!-- 1. KPI 계층 구조 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📊 KPI 계층 구조 (Z → Y → X)</div>\n\n <!-- Z 루트 -->\n <div class=\"z-node\" @click=${() => this.showDetail({ name: 'Z. 전체스코어', description: 'Y1~Y6 가중합', formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6', scoreType: 'DIRECT', valueType: 'CALCULATED' }, 'kpi')}>\n <div class=\"z-name\">Z. 전체스코어</div>\n <div class=\"z-formula\">= ${this.kpiRoot.map((y: any) => `${y.name.split('.')[0]}×${y.weight}`).join(' + ')}</div>\n </div>\n\n <!-- Y 그룹들 -->\n ${this.kpiRoot.map((y: any) => {\n const activeKpis = (y.kpis || []).filter((x: any) => x.active !== false)\n return html`\n <div class=\"y-group\">\n <div class=\"y-header\" @click=${() => this.showDetail(y, 'kpi')}>\n <span class=\"y-name\">${y.name}</span>\n <span class=\"y-weight\">가중치: ${y.weight}</span>\n </div>\n <div class=\"y-body\">\n ${y.formula ? html`<div class=\"y-formula\">${y.formula}</div>` : ''}\n <div class=\"x-cards\">\n ${activeKpis.map((x: any) => {\n const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' }\n const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' }\n return html`\n <div class=\"x-card\" @click=${() => this.showDetail(x, 'kpi')}>\n <div class=\"x-name\">${x.name}</div>\n <div class=\"x-formula\">${x.formula || '—'}</div>\n <div class=\"x-badges\">\n <span class=\"badge badge-score\">${st.label}</span>\n <span class=\"badge badge-value\">${vt.icon} ${vt.label}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n </div>\n `\n })}\n </div>\n\n <!-- 2. 원천 메트릭 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📋 원천 메트릭 (${this.metrics.filter((m: any) => m.active).length}개 정의)</div>\n <div class=\"metrics-grid\">\n ${this.metrics\n .filter((m: any) => m.active)\n .map((m: any) => {\n const usage = this.getMetricUsage(m.name)\n return html`\n <div class=\"metric-card\" @click=${() => this.showDetail(m, 'metric')}>\n <div class=\"metric-name\">${m.name}</div>\n <div class=\"metric-unit\">\n ${m.unit || '—'} ·\n ${m.collectType === 'EXTERNAL' && m.source\n ? EXTERNAL_SYSTEM_URLS[m.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[m.source]}\" target=\"_blank\" rel=\"noopener\"\n style=\"color:#1976d2;text-decoration:none;\" @click=${(e: Event) => e.stopPropagation()}\n >${m.source} ↗</a>`\n : m.source\n : m.collectType || 'MANUAL'}\n </div>\n ${usage.length > 0\n ? html`<div class=\"metric-used\">→ ${usage.join(', ')}</div>`\n : html`<div class=\"metric-used\" style=\"color:#ccc;\">미사용</div>`}\n </div>\n `\n })}\n </div>\n </div>\n\n <!-- 3. 시스템 요약 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📈 시스템 요약</div>\n <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n <div style=\"flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#1565c0;\">${allX.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">X-level 지표</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#2e7d32;\">${this.kpiRoot.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">Y-level 영역</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#e65100;\">${this.metrics.filter((m: any) => m.active).length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">원천 메트릭</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#c62828;\">${allX.filter((x: any) => x.valueType === 'ASSESSED').length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">감리자 평가 항목</div>\n </div>\n </div>\n </div>\n\n <!-- 팝업 -->\n ${this.selectedDetail ? this._renderDetailPopup() : nothing}\n `\n }\n\n private _renderDetailPopup() {\n const d = this.selectedDetail\n const isKpi = d._type === 'kpi'\n\n return html`\n <div class=\"popup-overlay\" @click=${() => this.closeDetail()}>\n <div class=\"popup-box\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"popup-title\">\n <span>${d.name}</span>\n <button class=\"popup-close\" @click=${() => this.closeDetail()}>×</button>\n </div>\n\n ${d.description ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">설명</span>\n <span class=\"popup-value\">${d.description}</span>\n </div>\n ` : ''}\n\n ${isKpi ? html`\n ${d.formula ? html`\n <div class=\"popup-row\"><span class=\"popup-label\">산식</span></div>\n <div class=\"popup-formula\">${d.formula}</div>\n ` : ''}\n\n ${d.scoreType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Score 산정</span>\n <span class=\"popup-value\" style=\"color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}\">\n ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}\n </span>\n </div>\n ` : ''}\n\n ${d.valueType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Value 획득</span>\n <span class=\"popup-value\" style=\"color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}\">\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''} ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}\n </span>\n </div>\n ` : ''}\n\n ${d.weight ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">가중치</span>\n <span class=\"popup-value\">${d.weight}</span>\n </div>\n ` : ''}\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-overview') }}>\n → KPI 개요에서 보기\n </span>\n &nbsp;&nbsp;\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-admin') }}>\n → KPI 관리에서 편집\n </span>\n </div>\n ` : html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">단위</span>\n <span class=\"popup-value\">${d.unit || '—'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">수집 방식</span>\n <span class=\"popup-value\">${d.collectType || 'MANUAL'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">데이터 소스</span>\n <span class=\"popup-value\">\n ${d.source && EXTERNAL_SYSTEM_URLS[d.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[d.source]}\" target=\"_blank\" rel=\"noopener\" style=\"color:#1976d2;\">${d.source} ↗</a>`\n : d.source || '미설정'}\n </span>\n </div>\n\n <div style=\"margin-top:12px;\">\n <div class=\"popup-label\" style=\"margin-bottom:6px;\">사용처:</div>\n ${this.getMetricUsage(d.name).map(kpi =>\n html`<div style=\"font-size:0.85rem;color:#1976d2;margin-bottom:2px;\">· ${kpi}</div>`\n )}\n ${this.getMetricUsage(d.name).length === 0 ? html`<div style=\"color:#ccc;font-size:0.85rem;\">미사용</div>` : ''}\n </div>\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-metric-value-list') }}>\n → 메트릭 값 관리\n </span>\n </div>\n `}\n </div>\n </div>\n `\n }\n}\n"]}
@@ -124,7 +124,7 @@ let KpiMetricValueListPage = class KpiMetricValueListPage extends connect(store)
124
124
  },
125
125
  renderer: (value, column, record, rowIndex, field) => {
126
126
  var _a;
127
- return ((_a = record.project) === null || _a === void 0 ? void 0 : _a.name) || '';
127
+ return ((_a = record.project) === null || _a === void 0 ? void 0 : _a.name) || record.org || '';
128
128
  }
129
129
  },
130
130
  filter: true,
@@ -263,10 +263,6 @@ let KpiMetricValueListPage = class KpiMetricValueListPage extends connect(store)
263
263
  name
264
264
  }
265
265
  org
266
- project {
267
- id
268
- name
269
- }
270
266
  valueDate
271
267
  value
272
268
  }