@things-factory/kpi 9.0.29 → 9.0.31

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.
Files changed (73) hide show
  1. package/client/charts/kpi-mini-trend-chart.ts +125 -0
  2. package/client/charts/kpi-trend-chart.ts +163 -0
  3. package/client/google-map/common-google-map.ts +370 -0
  4. package/client/google-map/google-map-loader.ts +29 -0
  5. package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +248 -0
  6. package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +369 -0
  7. package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +443 -0
  8. package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +72 -0
  9. package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +399 -0
  10. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +302 -0
  11. package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +355 -0
  12. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +243 -0
  13. package/client/pages/kpi-dashboard/kpi-dashboard.ts +416 -0
  14. package/client/route.ts +4 -0
  15. package/dist-client/charts/kpi-mini-trend-chart.d.ts +14 -0
  16. package/dist-client/charts/kpi-mini-trend-chart.js +148 -0
  17. package/dist-client/charts/kpi-mini-trend-chart.js.map +1 -0
  18. package/dist-client/charts/kpi-trend-chart.d.ts +25 -0
  19. package/dist-client/charts/kpi-trend-chart.js +186 -0
  20. package/dist-client/charts/kpi-trend-chart.js.map +1 -0
  21. package/dist-client/google-map/common-google-map.d.ts +34 -0
  22. package/dist-client/google-map/common-google-map.js +333 -0
  23. package/dist-client/google-map/common-google-map.js.map +1 -0
  24. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  25. package/dist-client/google-map/google-map-loader.js +22 -0
  26. package/dist-client/google-map/google-map-loader.js.map +1 -0
  27. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  28. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +279 -0
  29. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  30. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +19 -0
  31. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +385 -0
  32. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  33. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +23 -0
  34. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +465 -0
  35. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  36. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  37. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +22 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +404 -0
  41. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  42. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +28 -0
  43. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +298 -0
  44. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  45. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +23 -0
  46. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +368 -0
  47. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +29 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +271 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  51. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +21 -0
  52. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +398 -0
  53. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  54. package/dist-client/route.d.ts +1 -1
  55. package/dist-client/route.js +3 -0
  56. package/dist-client/route.js.map +1 -1
  57. package/dist-client/tsconfig.tsbuildinfo +1 -1
  58. package/dist-server/index.d.ts +1 -0
  59. package/dist-server/index.js +1 -0
  60. package/dist-server/index.js.map +1 -1
  61. package/dist-server/migrations/index.d.ts +1 -0
  62. package/dist-server/migrations/index.js +12 -0
  63. package/dist-server/migrations/index.js.map +1 -0
  64. package/dist-server/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +2 -2
  66. package/server/index.ts +1 -0
  67. package/server/migrations/index.ts +9 -0
  68. package/things-factory.config.js +2 -1
  69. package/translations/en.json +1 -0
  70. package/translations/ja.json +1 -0
  71. package/translations/ko.json +1 -0
  72. package/translations/ms.json +1 -0
  73. package/translations/zh.json +1 -0
@@ -0,0 +1,465 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css, nothing } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ import { LitElement } from 'lit';
5
+ import { client } from '@operato/graphql';
6
+ import gql from 'graphql-tag';
7
+ let KpiLevel3Comparison = class KpiLevel3Comparison extends LitElement {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.loading = true;
11
+ this.error = '';
12
+ this.radarData = [];
13
+ this.boxplotData = [];
14
+ this.kpis = [];
15
+ this.availableCategories = [];
16
+ this.selectedCategory = '';
17
+ this.currentMonth = '';
18
+ this.totalKpis = 0;
19
+ this.averageScore = 0;
20
+ this.categoryStats = new Map();
21
+ }
22
+ static { this.styles = css `
23
+ :host {
24
+ display: block;
25
+ }
26
+ .comparison-container {
27
+ background: #fff;
28
+ border-radius: 16px;
29
+ padding: 24px;
30
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
31
+ border: 1px solid #e0e0e0;
32
+ width: 100%;
33
+ }
34
+ .comparison-title {
35
+ font-size: 1.3rem;
36
+ font-weight: bold;
37
+ margin-bottom: 20px;
38
+ color: #333;
39
+ text-align: center;
40
+ }
41
+ .category-selector {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 12px;
45
+ margin-bottom: 24px;
46
+ padding: 16px;
47
+ background: #f8f9fa;
48
+ border-radius: 8px;
49
+ border: 1px solid #e9ecef;
50
+ }
51
+ .selector-label {
52
+ font-weight: 600;
53
+ color: #495057;
54
+ min-width: 80px;
55
+ }
56
+ .category-select {
57
+ padding: 8px 12px;
58
+ border: 1px solid #ced4da;
59
+ border-radius: 6px;
60
+ background: white;
61
+ font-size: 1rem;
62
+ min-width: 200px;
63
+ }
64
+ .category-select:focus {
65
+ outline: none;
66
+ border-color: #667eea;
67
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
68
+ }
69
+ .charts-section {
70
+ display: flex;
71
+ gap: 24px;
72
+ margin-bottom: 20px;
73
+ }
74
+ .chart-card {
75
+ flex: 1;
76
+ background: #f8f9fa;
77
+ border-radius: 12px;
78
+ padding: 20px;
79
+ border: 1px solid #e9ecef;
80
+ }
81
+ .chart-title {
82
+ font-size: 1.1rem;
83
+ font-weight: 600;
84
+ margin-bottom: 16px;
85
+ color: #495057;
86
+ text-align: center;
87
+ }
88
+ .chart-container {
89
+ height: 300px;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ }
94
+ .loading {
95
+ color: #666;
96
+ font-size: 1rem;
97
+ }
98
+ .error {
99
+ color: #d32f2f;
100
+ font-size: 1rem;
101
+ }
102
+ .no-data {
103
+ color: #666;
104
+ font-size: 1rem;
105
+ text-align: center;
106
+ }
107
+ .no-category {
108
+ color: #666;
109
+ font-size: 1rem;
110
+ text-align: center;
111
+ font-style: italic;
112
+ }
113
+ .summary-info {
114
+ display: flex;
115
+ justify-content: space-around;
116
+ padding: 16px;
117
+ background: #f8f9fa;
118
+ border-radius: 8px;
119
+ margin-top: 16px;
120
+ }
121
+ .summary-item {
122
+ text-align: center;
123
+ }
124
+ .summary-value {
125
+ font-size: 1.2rem;
126
+ font-weight: bold;
127
+ color: #333;
128
+ margin-bottom: 4px;
129
+ }
130
+ .summary-label {
131
+ font-size: 0.9rem;
132
+ color: #666;
133
+ }
134
+ `; }
135
+ connectedCallback() {
136
+ super.connectedCallback();
137
+ this.currentMonth = this.getCurrentMonth();
138
+ this.fetchKpiComparison();
139
+ }
140
+ getCurrentMonth() {
141
+ const now = new Date();
142
+ const year = now.getFullYear();
143
+ const month = String(now.getMonth() + 1).padStart(2, '0');
144
+ return `${year}-${month}`;
145
+ }
146
+ async fetchKpiComparison() {
147
+ this.loading = true;
148
+ this.error = '';
149
+ try {
150
+ const response = await client.query({
151
+ query: gql `
152
+ query {
153
+ kpiStatistics {
154
+ items {
155
+ id
156
+ valueDate
157
+ periodType
158
+ mean
159
+ median
160
+ standardDeviation
161
+ minimum
162
+ maximum
163
+ percentile25
164
+ percentile75
165
+ kpi {
166
+ id
167
+ name
168
+ targetValue
169
+ unit
170
+ category {
171
+ id
172
+ name
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ `
179
+ });
180
+ const statistics = response.data.kpiStatistics.items || [];
181
+ // MONTH 타입의 현재 월 데이터만 필터링
182
+ const currentMonthStats = statistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.currentMonth);
183
+ if (currentMonthStats.length === 0) {
184
+ this.error = '현재 월의 데이터가 없습니다.';
185
+ return;
186
+ }
187
+ // 카테고리별로 데이터 그룹화
188
+ const categoryStats = new Map();
189
+ currentMonthStats.forEach(stat => {
190
+ if (stat.kpi?.category?.name) {
191
+ const categoryName = stat.kpi.category.name;
192
+ if (!categoryStats.has(categoryName)) {
193
+ categoryStats.set(categoryName, []);
194
+ }
195
+ categoryStats.get(categoryName).push(stat);
196
+ }
197
+ });
198
+ this.categoryStats = categoryStats;
199
+ this.availableCategories = Array.from(categoryStats.keys());
200
+ // 첫 번째 카테고리를 기본 선택
201
+ if (this.availableCategories.length > 0 && !this.selectedCategory) {
202
+ this.selectedCategory = this.availableCategories[0];
203
+ }
204
+ // 선택된 카테고리의 데이터로 차트 생성
205
+ if (this.selectedCategory) {
206
+ this.generateKpiCharts();
207
+ }
208
+ }
209
+ catch (e) {
210
+ console.error('KPI 비교 데이터를 불러오지 못했습니다:', e);
211
+ this.error = 'KPI 비교 데이터를 불러오지 못했습니다.';
212
+ }
213
+ finally {
214
+ this.loading = false;
215
+ }
216
+ }
217
+ onCategoryChange(event) {
218
+ const target = event.target;
219
+ this.selectedCategory = target.value;
220
+ this.generateKpiCharts();
221
+ }
222
+ generateKpiCharts() {
223
+ if (!this.selectedCategory || !this.categoryStats.has(this.selectedCategory)) {
224
+ this.radarData = [];
225
+ this.boxplotData = [];
226
+ this.kpis = [];
227
+ this.totalKpis = 0;
228
+ this.averageScore = 0;
229
+ return;
230
+ }
231
+ const categoryData = this.categoryStats.get(this.selectedCategory);
232
+ this.kpis = categoryData.map(stat => stat.kpi?.name || '').filter(Boolean);
233
+ this.totalKpis = this.kpis.length;
234
+ // 레이더 차트 데이터 생성
235
+ this.generateRadarData(categoryData);
236
+ // 박스플롯 데이터 생성
237
+ this.generateBoxplotData(categoryData);
238
+ // 평균 점수 계산
239
+ const scores = categoryData.map(stat => {
240
+ const mean = stat.mean || 0;
241
+ const targetValue = stat.kpi?.targetValue || 100;
242
+ if (targetValue === 0)
243
+ return 0;
244
+ const achievement = Math.min((mean / targetValue) * 100, 100);
245
+ return Math.max(achievement, 0);
246
+ });
247
+ this.averageScore = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length);
248
+ }
249
+ generateRadarData(categoryData) {
250
+ const result = [];
251
+ this.kpis.forEach(kpiName => {
252
+ const stat = categoryData.find(s => s.kpi?.name === kpiName);
253
+ if (stat) {
254
+ const mean = stat.mean || 0;
255
+ const median = stat.median || 0;
256
+ const stdDev = stat.standardDeviation || 0;
257
+ if (mean > 0) {
258
+ result.push({ group: '평균', category: kpiName, value: mean });
259
+ }
260
+ if (median > 0) {
261
+ result.push({ group: '중앙값', category: kpiName, value: median });
262
+ }
263
+ if (stdDev > 0) {
264
+ result.push({ group: '표준편차', category: kpiName, value: stdDev });
265
+ }
266
+ }
267
+ });
268
+ this.radarData = result;
269
+ }
270
+ generateBoxplotData(categoryData) {
271
+ const result = [];
272
+ this.kpis.forEach(kpiName => {
273
+ const stat = categoryData.find(s => s.kpi?.name === kpiName);
274
+ if (stat) {
275
+ const mean = stat.mean || 0;
276
+ const median = stat.median || 0;
277
+ const min = stat.minimum || 0;
278
+ const max = stat.maximum || 0;
279
+ const q1 = stat.percentile25 || 0;
280
+ const q3 = stat.percentile75 || 0;
281
+ if (mean > 0) {
282
+ result.push({
283
+ group: kpiName,
284
+ min,
285
+ q1,
286
+ median,
287
+ q3,
288
+ max,
289
+ mean,
290
+ value: mean
291
+ });
292
+ }
293
+ }
294
+ });
295
+ this.boxplotData = result;
296
+ }
297
+ render() {
298
+ if (this.loading) {
299
+ return html `
300
+ <div class="comparison-container">
301
+ <div class="comparison-title">KPI 상세 비교 분석</div>
302
+ <div class="category-selector">
303
+ <div class="selector-label">카테고리:</div>
304
+ <select class="category-select" disabled>
305
+ <option>로딩 중...</option>
306
+ </select>
307
+ </div>
308
+ <div class="charts-section">
309
+ <div class="chart-card">
310
+ <div class="chart-title">레이더 차트</div>
311
+ <div class="chart-container">
312
+ <div class="loading">데이터 로딩 중...</div>
313
+ </div>
314
+ </div>
315
+ <div class="chart-card">
316
+ <div class="chart-title">박스플롯</div>
317
+ <div class="chart-container">
318
+ <div class="loading">데이터 로딩 중...</div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ </div>
323
+ `;
324
+ }
325
+ if (this.error) {
326
+ return html `
327
+ <div class="comparison-container">
328
+ <div class="comparison-title">KPI 상세 비교 분석</div>
329
+ <div class="category-selector">
330
+ <div class="selector-label">카테고리:</div>
331
+ <select class="category-select" disabled>
332
+ <option>오류 발생</option>
333
+ </select>
334
+ </div>
335
+ <div class="charts-section">
336
+ <div class="chart-card">
337
+ <div class="chart-title">레이더 차트</div>
338
+ <div class="chart-container">
339
+ <div class="error">${this.error}</div>
340
+ </div>
341
+ </div>
342
+ <div class="chart-card">
343
+ <div class="chart-title">박스플롯</div>
344
+ <div class="chart-container">
345
+ <div class="error">${this.error}</div>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ `;
351
+ }
352
+ return html `
353
+ <div class="comparison-container">
354
+ <div class="comparison-title">KPI 상세 비교 분석 (${this.currentMonth})</div>
355
+
356
+ <div class="category-selector">
357
+ <div class="selector-label">카테고리:</div>
358
+ <select class="category-select" .value=${this.selectedCategory} @change=${this.onCategoryChange}>
359
+ ${this.availableCategories.map(category => html `<option value="${category}">${category}</option>`)}
360
+ </select>
361
+ </div>
362
+
363
+ <div class="charts-section">
364
+ <div class="chart-card">
365
+ <div class="chart-title">레이더 차트</div>
366
+ <div class="chart-container">
367
+ ${this.selectedCategory
368
+ ? this.radarData.length > 0
369
+ ? html `<kpi-radar-chart
370
+ .data=${this.radarData}
371
+ .categories=${this.kpis}
372
+ .currentGroup=${'평균'}
373
+ ></kpi-radar-chart>`
374
+ : html `<div class="no-data">선택된 카테고리에 데이터가 없습니다.</div>`
375
+ : html `<div class="no-category">카테고리를 선택해주세요.</div>`}
376
+ </div>
377
+ </div>
378
+
379
+ <div class="chart-card">
380
+ <div class="chart-title">박스플롯</div>
381
+ <div class="chart-container">
382
+ ${this.selectedCategory
383
+ ? this.boxplotData.length > 0
384
+ ? html `<kpi-boxplot-chart
385
+ .data=${this.boxplotData}
386
+ .groups=${this.kpis}
387
+ .currentGroup=${'평균'}
388
+ ></kpi-boxplot-chart>`
389
+ : html `<div class="no-data">선택된 카테고리에 데이터가 없습니다.</div>`
390
+ : html `<div class="no-category">카테고리를 선택해주세요.</div>`}
391
+ </div>
392
+ </div>
393
+ </div>
394
+
395
+ ${this.selectedCategory
396
+ ? html `
397
+ <div class="summary-info">
398
+ <div class="summary-item">
399
+ <div class="summary-value">${this.selectedCategory}</div>
400
+ <div class="summary-label">선택된 카테고리</div>
401
+ </div>
402
+ <div class="summary-item">
403
+ <div class="summary-value">${this.totalKpis}</div>
404
+ <div class="summary-label">KPI</div>
405
+ </div>
406
+ <div class="summary-item">
407
+ <div class="summary-value">${this.averageScore}</div>
408
+ <div class="summary-label">평균 점수</div>
409
+ </div>
410
+ </div>
411
+ `
412
+ : nothing}
413
+ </div>
414
+ `;
415
+ }
416
+ };
417
+ __decorate([
418
+ state(),
419
+ __metadata("design:type", Object)
420
+ ], KpiLevel3Comparison.prototype, "loading", void 0);
421
+ __decorate([
422
+ state(),
423
+ __metadata("design:type", Object)
424
+ ], KpiLevel3Comparison.prototype, "error", void 0);
425
+ __decorate([
426
+ state(),
427
+ __metadata("design:type", Array)
428
+ ], KpiLevel3Comparison.prototype, "radarData", void 0);
429
+ __decorate([
430
+ state(),
431
+ __metadata("design:type", Array)
432
+ ], KpiLevel3Comparison.prototype, "boxplotData", void 0);
433
+ __decorate([
434
+ state(),
435
+ __metadata("design:type", Array)
436
+ ], KpiLevel3Comparison.prototype, "kpis", void 0);
437
+ __decorate([
438
+ state(),
439
+ __metadata("design:type", Array)
440
+ ], KpiLevel3Comparison.prototype, "availableCategories", void 0);
441
+ __decorate([
442
+ state(),
443
+ __metadata("design:type", Object)
444
+ ], KpiLevel3Comparison.prototype, "selectedCategory", void 0);
445
+ __decorate([
446
+ state(),
447
+ __metadata("design:type", Object)
448
+ ], KpiLevel3Comparison.prototype, "currentMonth", void 0);
449
+ __decorate([
450
+ state(),
451
+ __metadata("design:type", Object)
452
+ ], KpiLevel3Comparison.prototype, "totalKpis", void 0);
453
+ __decorate([
454
+ state(),
455
+ __metadata("design:type", Object)
456
+ ], KpiLevel3Comparison.prototype, "averageScore", void 0);
457
+ __decorate([
458
+ state(),
459
+ __metadata("design:type", Object)
460
+ ], KpiLevel3Comparison.prototype, "categoryStats", void 0);
461
+ KpiLevel3Comparison = __decorate([
462
+ customElement('kpi-level3-comparison')
463
+ ], KpiLevel3Comparison);
464
+ export { KpiLevel3Comparison };
465
+ //# sourceMappingURL=kpi-level3-comparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-level3-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAGtB,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,UAAU;IAA5C;;QAmHI,YAAO,GAAG,IAAI,CAAA;QACd,UAAK,GAAG,EAAE,CAAA;QACV,cAAS,GAAU,EAAE,CAAA;QACrB,gBAAW,GAAU,EAAE,CAAA;QACvB,SAAI,GAAa,EAAE,CAAA;QACnB,wBAAmB,GAAa,EAAE,CAAA;QAClC,qBAAgB,GAAG,EAAE,CAAA;QACrB,iBAAY,GAAG,EAAE,CAAA;QACjB,cAAS,GAAG,CAAC,CAAA;QACb,iBAAY,GAAG,CAAC,CAAA;QAChB,kBAAa,GAAG,IAAI,GAAG,EAAiB,CAAA;IAsTnD,CAAC;aAlbQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgHlB,AAhHY,CAgHZ;IAcD,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,CAAA;QAC1C,IAAI,CAAC,kBAAkB,EAAE,CAAA;IAC3B,CAAC;IAEO,eAAe;QACrB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QACzD,OAAO,GAAG,IAAI,IAAI,KAAK,EAAE,CAAA;IAC3B,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QAEf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;gBAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2BT;aACF,CAAC,CAAA;YAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAA;YAE1D,0BAA0B;YAC1B,MAAM,iBAAiB,GAAG,UAAU,CAAC,MAAM,CACzC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,KAAK,OAAO,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,YAAY,CAC5E,CAAA;YAED,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAA;gBAC/B,OAAM;YACR,CAAC;YAED,iBAAiB;YACjB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAiB,CAAA;YAE9C,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBAC/B,IAAI,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAA;oBAC3C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;wBACrC,aAAa,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;oBACrC,CAAC;oBACD,aAAa,CAAC,GAAG,CAAC,YAAY,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC7C,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA;YAClC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAA;YAE3D,mBAAmB;YACnB,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAClE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAA;YACrD,CAAC;YAED,uBAAuB;YACvB,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC1B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAA;YAC3C,IAAI,CAAC,KAAK,GAAG,yBAAyB,CAAA;QACxC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACtB,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAY;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,MAA2B,CAAA;QAChD,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAA;QACpC,IAAI,CAAC,iBAAiB,EAAE,CAAA;IAC1B,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC7E,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;YACnB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAA;YACrB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAA;YACd,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;YAClB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;YACrB,OAAM;QACR,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAE,CAAA;QACnE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1E,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;QAEjC,gBAAgB;QAChB,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAA;QAEpC,cAAc;QACd,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAA;QAEtC,WAAW;QACX,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;YAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,IAAI,GAAG,CAAA;YAChD,IAAI,WAAW,KAAK,CAAC;gBAAE,OAAO,CAAC,CAAA;YAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,WAAW,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAA;YAC7D,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAC/F,CAAC;IAEO,iBAAiB,CAAC,YAAmB;QAC3C,MAAM,MAAM,GAAU,EAAE,CAAA;QAExB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,CAAA;YAC5D,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;gBAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;gBAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,IAAI,CAAC,CAAA;gBAE1C,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC9D,CAAC;gBACD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;gBACjE,CAAC;gBACD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;gBAClE,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,SAAS,GAAG,MAAM,CAAA;IACzB,CAAC;IAEO,mBAAmB,CAAC,YAAmB;QAC7C,MAAM,MAAM,GAAU,EAAE,CAAA;QAExB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,CAAA;YAC5D,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;gBAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;gBAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA;gBAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA;gBAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAA;gBACjC,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAA;gBAEjC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,CAAC;wBACV,KAAK,EAAE,OAAO;wBACd,GAAG;wBACH,EAAE;wBACF,MAAM;wBACN,EAAE;wBACF,GAAG;wBACH,IAAI;wBACJ,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,WAAW,GAAG,MAAM,CAAA;IAC3B,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;OAwBV,CAAA;QACH,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAA;;;;;;;;;;;;;qCAaoB,IAAI,CAAC,KAAK;;;;;;qCAMV,IAAI,CAAC,KAAK;;;;;OAKxC,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;;sDAEuC,IAAI,CAAC,YAAY;;;;mDAIpB,IAAI,CAAC,gBAAgB,YAAY,IAAI,CAAC,gBAAgB;cAC3F,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAA,kBAAkB,QAAQ,KAAK,QAAQ,WAAW,CAAC;;;;;;;;gBAQ9F,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACzB,CAAC,CAAC,IAAI,CAAA;8BACM,IAAI,CAAC,SAAS;oCACR,IAAI,CAAC,IAAI;sCACP,IAAI;wCACF;gBACtB,CAAC,CAAC,IAAI,CAAA,iDAAiD;YACzD,CAAC,CAAC,IAAI,CAAA,8CAA8C;;;;;;;gBAOpD,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;gBAC3B,CAAC,CAAC,IAAI,CAAA;8BACM,IAAI,CAAC,WAAW;gCACd,IAAI,CAAC,IAAI;sCACH,IAAI;0CACA;gBACxB,CAAC,CAAC,IAAI,CAAA,iDAAiD;YACzD,CAAC,CAAC,IAAI,CAAA,8CAA8C;;;;;UAK1D,IAAI,CAAC,gBAAgB;YACrB,CAAC,CAAC,IAAI,CAAA;;;+CAG+B,IAAI,CAAC,gBAAgB;;;;+CAIrB,IAAI,CAAC,SAAS;;;;+CAId,IAAI,CAAC,YAAY;;;;aAInD;YACH,CAAC,CAAC,OAAO;;KAEd,CAAA;IACH,CAAC;;AA/TQ;IAAR,KAAK,EAAE;;oDAAe;AACd;IAAR,KAAK,EAAE;;kDAAW;AACV;IAAR,KAAK,EAAE;;sDAAsB;AACrB;IAAR,KAAK,EAAE;;wDAAwB;AACvB;IAAR,KAAK,EAAE;;iDAAoB;AACnB;IAAR,KAAK,EAAE;;gEAAmC;AAClC;IAAR,KAAK,EAAE;;6DAAsB;AACrB;IAAR,KAAK,EAAE;;yDAAkB;AACjB;IAAR,KAAK,EAAE;;sDAAc;AACb;IAAR,KAAK,EAAE;;yDAAiB;AAChB;IAAR,KAAK,EAAE;;0DAAyC;AA7HtC,mBAAmB;IAD/B,aAAa,CAAC,uBAAuB,CAAC;GAC1B,mBAAmB,CAmb/B","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\n\n@customElement('kpi-level3-comparison')\nexport class KpiLevel3Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .category-selector {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 24px;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n border: 1px solid #e9ecef;\n }\n .selector-label {\n font-weight: 600;\n color: #495057;\n min-width: 80px;\n }\n .category-select {\n padding: 8px 12px;\n border: 1px solid #ced4da;\n border-radius: 6px;\n background: white;\n font-size: 1rem;\n min-width: 200px;\n }\n .category-select:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .no-category {\n color: #666;\n font-size: 1rem;\n text-align: center;\n font-style: italic;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() kpis: string[] = []\n @state() availableCategories: string[] = []\n @state() selectedCategory = ''\n @state() currentMonth = ''\n @state() totalKpis = 0\n @state() averageScore = 0\n @state() categoryStats = new Map<string, any[]>()\n\n connectedCallback() {\n super.connectedCallback()\n this.currentMonth = this.getCurrentMonth()\n this.fetchKpiComparison()\n }\n\n private getCurrentMonth(): string {\n const now = new Date()\n const year = now.getFullYear()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n return `${year}-${month}`\n }\n\n async fetchKpiComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query {\n kpiStatistics {\n items {\n id\n valueDate\n periodType\n mean\n median\n standardDeviation\n minimum\n maximum\n percentile25\n percentile75\n kpi {\n id\n name\n targetValue\n unit\n category {\n id\n name\n }\n }\n }\n }\n }\n `\n })\n\n const statistics = response.data.kpiStatistics.items || []\n\n // MONTH 타입의 현재 월 데이터만 필터링\n const currentMonthStats = statistics.filter(\n stat => stat.periodType === 'MONTH' && stat.valueDate === this.currentMonth\n )\n\n if (currentMonthStats.length === 0) {\n this.error = '현재 월의 데이터가 없습니다.'\n return\n }\n\n // 카테고리별로 데이터 그룹화\n const categoryStats = new Map<string, any[]>()\n\n currentMonthStats.forEach(stat => {\n if (stat.kpi?.category?.name) {\n const categoryName = stat.kpi.category.name\n if (!categoryStats.has(categoryName)) {\n categoryStats.set(categoryName, [])\n }\n categoryStats.get(categoryName)!.push(stat)\n }\n })\n\n this.categoryStats = categoryStats\n this.availableCategories = Array.from(categoryStats.keys())\n\n // 첫 번째 카테고리를 기본 선택\n if (this.availableCategories.length > 0 && !this.selectedCategory) {\n this.selectedCategory = this.availableCategories[0]\n }\n\n // 선택된 카테고리의 데이터로 차트 생성\n if (this.selectedCategory) {\n this.generateKpiCharts()\n }\n } catch (e) {\n console.error('KPI 비교 데이터를 불러오지 못했습니다:', e)\n this.error = 'KPI 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private onCategoryChange(event: Event) {\n const target = event.target as HTMLSelectElement\n this.selectedCategory = target.value\n this.generateKpiCharts()\n }\n\n private generateKpiCharts() {\n if (!this.selectedCategory || !this.categoryStats.has(this.selectedCategory)) {\n this.radarData = []\n this.boxplotData = []\n this.kpis = []\n this.totalKpis = 0\n this.averageScore = 0\n return\n }\n\n const categoryData = this.categoryStats.get(this.selectedCategory)!\n this.kpis = categoryData.map(stat => stat.kpi?.name || '').filter(Boolean)\n this.totalKpis = this.kpis.length\n\n // 레이더 차트 데이터 생성\n this.generateRadarData(categoryData)\n\n // 박스플롯 데이터 생성\n this.generateBoxplotData(categoryData)\n\n // 평균 점수 계산\n const scores = categoryData.map(stat => {\n const mean = stat.mean || 0\n const targetValue = stat.kpi?.targetValue || 100\n if (targetValue === 0) return 0\n const achievement = Math.min((mean / targetValue) * 100, 100)\n return Math.max(achievement, 0)\n })\n this.averageScore = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length)\n }\n\n private generateRadarData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpi?.name === kpiName)\n if (stat) {\n const mean = stat.mean || 0\n const median = stat.median || 0\n const stdDev = stat.standardDeviation || 0\n\n if (mean > 0) {\n result.push({ group: '평균', category: kpiName, value: mean })\n }\n if (median > 0) {\n result.push({ group: '중앙값', category: kpiName, value: median })\n }\n if (stdDev > 0) {\n result.push({ group: '표준편차', category: kpiName, value: stdDev })\n }\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryData: any[]) {\n const result: any[] = []\n\n this.kpis.forEach(kpiName => {\n const stat = categoryData.find(s => s.kpi?.name === kpiName)\n if (stat) {\n const mean = stat.mean || 0\n const median = stat.median || 0\n const min = stat.minimum || 0\n const max = stat.maximum || 0\n const q1 = stat.percentile25 || 0\n const q3 = stat.percentile75 || 0\n\n if (mean > 0) {\n result.push({\n group: kpiName,\n min,\n q1,\n median,\n q3,\n max,\n mean,\n value: mean\n })\n }\n }\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>로딩 중...</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석</div>\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" disabled>\n <option>오류 발생</option>\n </select>\n </div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">KPI 상세 비교 분석 (${this.currentMonth})</div>\n\n <div class=\"category-selector\">\n <div class=\"selector-label\">카테고리:</div>\n <select class=\"category-select\" .value=${this.selectedCategory} @change=${this.onCategoryChange}>\n ${this.availableCategories.map(category => html`<option value=\"${category}\">${category}</option>`)}\n </select>\n </div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.radarData.length > 0\n ? html`<kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.kpis}\n .currentGroup=${'평균'}\n ></kpi-radar-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.selectedCategory\n ? this.boxplotData.length > 0\n ? html`<kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.kpis}\n .currentGroup=${'평균'}\n ></kpi-boxplot-chart>`\n : html`<div class=\"no-data\">선택된 카테고리에 데이터가 없습니다.</div>`\n : html`<div class=\"no-category\">카테고리를 선택해주세요.</div>`}\n </div>\n </div>\n </div>\n\n ${this.selectedCategory\n ? html`\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.selectedCategory}</div>\n <div class=\"summary-label\">선택된 카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n `\n : nothing}\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import { LitElement } from 'lit';
2
+ export declare class KpiChartToggle extends LitElement {
3
+ static styles: import("lit").CSSResult;
4
+ selectedType: string;
5
+ onTypeChange?: (type: string) => void;
6
+ private handleTypeChange;
7
+ render(): import("lit-html").TemplateResult<1>;
8
+ }
@@ -0,0 +1,78 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { LitElement, html, css } from 'lit';
3
+ import { customElement, property } from 'lit/decorators.js';
4
+ let KpiChartToggle = class KpiChartToggle extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.selectedType = 'boxplot';
8
+ }
9
+ static { this.styles = css `
10
+ :host {
11
+ display: block;
12
+ }
13
+ .chart-toggle {
14
+ display: flex;
15
+ gap: 8px;
16
+ margin-bottom: 16px;
17
+ }
18
+ .toggle-button {
19
+ padding: 8px 16px;
20
+ border: 1px solid #ced4da;
21
+ background: #fff;
22
+ border-radius: 6px;
23
+ cursor: pointer;
24
+ font-size: 0.9rem;
25
+ transition: all 0.2s;
26
+ }
27
+ .toggle-button:hover {
28
+ background: #f8f9fa;
29
+ }
30
+ .toggle-button.active {
31
+ background: #667eea;
32
+ color: white;
33
+ border-color: #667eea;
34
+ }
35
+ `; }
36
+ handleTypeChange(type) {
37
+ this.selectedType = type;
38
+ if (this.onTypeChange) {
39
+ this.onTypeChange(type);
40
+ }
41
+ this.dispatchEvent(new CustomEvent('type-change', {
42
+ detail: { type },
43
+ bubbles: true,
44
+ composed: true
45
+ }));
46
+ }
47
+ render() {
48
+ return html `
49
+ <div class="chart-toggle">
50
+ <button
51
+ class="toggle-button ${this.selectedType === 'boxplot' ? 'active' : ''}"
52
+ @click=${() => this.handleTypeChange('boxplot')}
53
+ >
54
+ 박스플롯
55
+ </button>
56
+ <button
57
+ class="toggle-button ${this.selectedType === 'radar' ? 'active' : ''}"
58
+ @click=${() => this.handleTypeChange('radar')}
59
+ >
60
+ 레이더차트
61
+ </button>
62
+ </div>
63
+ `;
64
+ }
65
+ };
66
+ __decorate([
67
+ property({ type: String }),
68
+ __metadata("design:type", Object)
69
+ ], KpiChartToggle.prototype, "selectedType", void 0);
70
+ __decorate([
71
+ property({ type: Function }),
72
+ __metadata("design:type", Function)
73
+ ], KpiChartToggle.prototype, "onTypeChange", void 0);
74
+ KpiChartToggle = __decorate([
75
+ customElement('kpi-chart-toggle')
76
+ ], KpiChartToggle);
77
+ export { KpiChartToggle };
78
+ //# sourceMappingURL=kpi-chart-toggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-chart-toggle.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/components/kpi-chart-toggle.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAGpD,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QA8BL,iBAAY,GAAG,SAAS,CAAA;IAqC1B,CAAC;aAlEQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BlB,AA1BY,CA0BZ;IAQO,gBAAgB,CAAC,IAAY;QACnC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QACxB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE;YAC7B,MAAM,EAAE,EAAE,IAAI,EAAE;YAChB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;iCAGkB,IAAI,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;mBAC7D,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC;;;;;iCAKxB,IAAI,CAAC,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;mBAC3D,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;;;;;KAKlD,CAAA;IACH,CAAC;;AApCD;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oDACH;AAGxB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;;oDACQ;AAjC1B,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAmE1B","sourcesContent":["import { LitElement, html, css } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\n@customElement('kpi-chart-toggle')\nexport class KpiChartToggle extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .chart-toggle {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n }\n .toggle-button {\n padding: 8px 16px;\n border: 1px solid #ced4da;\n background: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.9rem;\n transition: all 0.2s;\n }\n .toggle-button:hover {\n background: #f8f9fa;\n }\n .toggle-button.active {\n background: #667eea;\n color: white;\n border-color: #667eea;\n }\n `\n\n @property({ type: String })\n selectedType = 'boxplot'\n\n @property({ type: Function })\n onTypeChange?: (type: string) => void\n\n private handleTypeChange(type: string) {\n this.selectedType = type\n if (this.onTypeChange) {\n this.onTypeChange(type)\n }\n this.dispatchEvent(\n new CustomEvent('type-change', {\n detail: { type },\n bubbles: true,\n composed: true\n })\n )\n }\n\n render() {\n return html`\n <div class=\"chart-toggle\">\n <button\n class=\"toggle-button ${this.selectedType === 'boxplot' ? 'active' : ''}\"\n @click=${() => this.handleTypeChange('boxplot')}\n >\n 박스플롯\n </button>\n <button\n class=\"toggle-button ${this.selectedType === 'radar' ? 'active' : ''}\"\n @click=${() => this.handleTypeChange('radar')}\n >\n 레이더차트\n </button>\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { LitElement } from 'lit';
2
+ import '../../../charts/kpi-radar-chart.js';
3
+ import '../../../charts/kpi-boxplot-chart.js';
4
+ import '../../../charts/kpi-mini-trend-chart.js';
5
+ export declare class KpiLeftPanel extends LitElement {
6
+ static styles: import("lit").CSSResult;
7
+ selectedCategory: string;
8
+ selectedChartType: string;
9
+ mapData: any[];
10
+ private chartData;
11
+ private chartCategories;
12
+ connectedCallback(): void;
13
+ private generateTrendData;
14
+ private generateChartData;
15
+ private onCategoryChange;
16
+ private onChartTypeChange;
17
+ private onRegionClick;
18
+ private downloadExcel;
19
+ private getChangeRateClass;
20
+ private getChangeIcon;
21
+ render(): import("lit-html").TemplateResult<1>;
22
+ }