@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
@@ -15,6 +15,10 @@ import './kpi-value-entry';
15
15
  import './kpi-alert-panel';
16
16
  import '../../charts/kpi-radar-chart';
17
17
  import '../../charts/kpi-boxplot-chart';
18
+ // 3레벨 KPI 플로팅 컴포넌트들
19
+ import './cards/kpi-level1-card';
20
+ import './cards/kpi-level2-comparison';
21
+ import './cards/kpi-level3-comparison';
18
22
  let KpiDashboardPage = class KpiDashboardPage extends PageView {
19
23
  constructor() {
20
24
  super(...arguments);
@@ -25,6 +29,10 @@ let KpiDashboardPage = class KpiDashboardPage extends PageView {
25
29
  this.showHistoryModal = false;
26
30
  this.modalHistories = [];
27
31
  this.modalKpiName = '';
32
+ this.kpiStatistics = []; // 실제 KPI 통계 데이터
33
+ this.statisticsLoading = true; // 통계 데이터 로딩 상태
34
+ this.selectedPeriodType = 'MONTH'; // 선택된 기간 타입 (MONTH로 고정)
35
+ this.selectedValueDate = ''; // 선택된 값 날짜
28
36
  }
29
37
  static { this.styles = [
30
38
  ScrollbarStyles,
@@ -40,6 +48,7 @@ let KpiDashboardPage = class KpiDashboardPage extends PageView {
40
48
  }
41
49
  .sample-charts-section {
42
50
  display: flex;
51
+ flex-direction: column;
43
52
  gap: 40px;
44
53
  margin-bottom: 48px;
45
54
  align-items: flex-start;
@@ -227,13 +236,192 @@ let KpiDashboardPage = class KpiDashboardPage extends PageView {
227
236
  get sampleCurrentGroup() {
228
237
  return 'A';
229
238
  }
239
+ // 현재 월을 YYYY-MM 형식으로 반환
240
+ get currentMonth() {
241
+ const now = new Date();
242
+ const year = now.getFullYear();
243
+ const month = String(now.getMonth() + 1).padStart(2, '0');
244
+ return `${year}-${month}`;
245
+ }
246
+ // 필터링된 KPI 통계 데이터
247
+ get filteredKpiStatistics() {
248
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0)
249
+ return [];
250
+ return this.kpiStatistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.selectedValueDate);
251
+ }
252
+ // 사용 가능한 기간 타입들 (MONTH로 고정)
253
+ get availablePeriodTypes() {
254
+ return ['MONTH'];
255
+ }
256
+ // MONTH 기간에 사용 가능한 날짜들
257
+ get availableValueDates() {
258
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0)
259
+ return [];
260
+ const valueDates = new Set();
261
+ this.kpiStatistics.forEach(stat => {
262
+ if (stat.periodType === 'MONTH' && stat.valueDate) {
263
+ valueDates.add(stat.valueDate);
264
+ }
265
+ });
266
+ return Array.from(valueDates).sort().reverse(); // 최신 날짜부터 정렬
267
+ }
268
+ // 통계 요약 정보
269
+ get statisticsSummary() {
270
+ const filteredStats = this.filteredKpiStatistics;
271
+ if (filteredStats.length === 0)
272
+ return null;
273
+ const totalKpis = filteredStats.length;
274
+ const categories = new Set(filteredStats.map(s => s.kpi?.category?.name).filter(Boolean));
275
+ const totalCategories = categories.size;
276
+ const means = filteredStats.map(s => s.mean || 0).filter(v => v > 0);
277
+ const medians = filteredStats.map(s => s.median || 0).filter(v => v > 0);
278
+ const stdDevs = filteredStats.map(s => s.standardDeviation || 0).filter(v => v > 0);
279
+ const avgMean = means.length > 0 ? means.reduce((a, b) => a + b, 0) / means.length : 0;
280
+ const avgMedian = medians.length > 0 ? medians.reduce((a, b) => a + b, 0) / medians.length : 0;
281
+ const avgStdDev = stdDevs.length > 0 ? stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length : 0;
282
+ return {
283
+ totalKpis,
284
+ totalCategories,
285
+ avgMean: avgMean.toFixed(2),
286
+ avgMedian: avgMedian.toFixed(2),
287
+ avgStdDev: avgStdDev.toFixed(2),
288
+ periodType: this.selectedPeriodType,
289
+ valueDate: this.selectedValueDate
290
+ };
291
+ }
292
+ // 기간 타입 변경 핸들러 (사용하지 않음 - MONTH로 고정)
293
+ _onPeriodTypeChange(event) {
294
+ // MONTH로 고정되어 있으므로 변경하지 않음
295
+ }
296
+ // 날짜 변경 핸들러
297
+ _onValueDateChange(event) {
298
+ const target = event.target;
299
+ this.selectedValueDate = target.value;
300
+ }
301
+ // 실제 KPI 통계 데이터를 기반으로 한 레이더 차트 데이터
302
+ get realKpiRadarData() {
303
+ const filteredStats = this.filteredKpiStatistics;
304
+ if (filteredStats.length === 0)
305
+ return [];
306
+ // 카테고리별로 통계 데이터 그룹화
307
+ const categoryStats = new Map();
308
+ filteredStats.forEach(stat => {
309
+ if (stat.kpi?.category?.name) {
310
+ const categoryName = stat.kpi.category.name;
311
+ if (!categoryStats.has(categoryName)) {
312
+ categoryStats.set(categoryName, []);
313
+ }
314
+ categoryStats.get(categoryName).push(stat);
315
+ }
316
+ });
317
+ // 각 카테고리별로 평균, 중앙값, 표준편차 계산
318
+ const result = [];
319
+ const categories = Array.from(categoryStats.keys());
320
+ categories.forEach(category => {
321
+ const stats = categoryStats.get(category);
322
+ const means = stats.map(s => s.mean || 0).filter(v => v > 0);
323
+ const medians = stats.map(s => s.median || 0).filter(v => v > 0);
324
+ const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0);
325
+ if (means.length > 0) {
326
+ result.push({ group: '평균', category, value: means.reduce((a, b) => a + b, 0) / means.length });
327
+ }
328
+ if (medians.length > 0) {
329
+ result.push({ group: '중앙값', category, value: medians.reduce((a, b) => a + b, 0) / medians.length });
330
+ }
331
+ if (stdDevs.length > 0) {
332
+ result.push({ group: '표준편차', category, value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length });
333
+ }
334
+ });
335
+ return result;
336
+ }
337
+ get realKpiRadarCategories() {
338
+ const filteredStats = this.filteredKpiStatistics;
339
+ if (filteredStats.length === 0)
340
+ return [];
341
+ const categories = new Set();
342
+ filteredStats.forEach(stat => {
343
+ if (stat.kpi?.category?.name) {
344
+ categories.add(stat.kpi.category.name);
345
+ }
346
+ });
347
+ return Array.from(categories);
348
+ }
349
+ get realKpiRadarGroups() {
350
+ return ['평균', '중앙값', '표준편차'];
351
+ }
352
+ // 실제 KPI 통계 데이터를 기반으로 한 박스플롯 데이터
353
+ get realKpiBoxplotData() {
354
+ const filteredStats = this.filteredKpiStatistics;
355
+ if (filteredStats.length === 0)
356
+ return [];
357
+ // 카테고리별로 통계 데이터 그룹화
358
+ const categoryStats = new Map();
359
+ filteredStats.forEach(stat => {
360
+ if (stat.kpi?.category?.name) {
361
+ const categoryName = stat.kpi.category.name;
362
+ if (!categoryStats.has(categoryName)) {
363
+ categoryStats.set(categoryName, []);
364
+ }
365
+ categoryStats.get(categoryName).push(stat);
366
+ }
367
+ });
368
+ const result = [];
369
+ const categories = Array.from(categoryStats.keys());
370
+ categories.forEach(category => {
371
+ const stats = categoryStats.get(category);
372
+ // 각 KPI의 통계값들을 수집
373
+ const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0);
374
+ const allMedians = stats.map(s => s.median || 0).filter(v => v > 0);
375
+ const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0);
376
+ const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0);
377
+ const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0);
378
+ const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0);
379
+ if (allMeans.length > 0) {
380
+ const sortedMeans = [...allMeans].sort((a, b) => a - b);
381
+ const min = sortedMeans[0];
382
+ const max = sortedMeans[sortedMeans.length - 1];
383
+ const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length;
384
+ const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean;
385
+ const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)];
386
+ const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)];
387
+ result.push({
388
+ group: category,
389
+ min,
390
+ q1,
391
+ median,
392
+ q3,
393
+ max,
394
+ mean,
395
+ value: mean // 현재 값으로 평균 사용
396
+ });
397
+ }
398
+ });
399
+ return result;
400
+ }
401
+ get realKpiBoxplotGroups() {
402
+ const filteredStats = this.filteredKpiStatistics;
403
+ if (filteredStats.length === 0)
404
+ return [];
405
+ const categories = new Set();
406
+ filteredStats.forEach(stat => {
407
+ if (stat.kpi?.category?.name) {
408
+ categories.add(stat.kpi.category.name);
409
+ }
410
+ });
411
+ return Array.from(categories);
412
+ }
413
+ get realKpiCurrentGroup() {
414
+ return '평균';
415
+ }
230
416
  connectedCallback() {
231
417
  super.connectedCallback();
232
418
  this.fetchCategories();
419
+ this.fetchKpiStatistics();
233
420
  }
234
421
  pageUpdated(changes, lifecycle) {
235
422
  if (this.active) {
236
423
  this.fetchCategories();
424
+ this.fetchKpiStatistics();
237
425
  }
238
426
  }
239
427
  async fetchCategories() {
@@ -291,6 +479,71 @@ let KpiDashboardPage = class KpiDashboardPage extends PageView {
291
479
  this.loading = false;
292
480
  }
293
481
  }
482
+ async fetchKpiStatistics() {
483
+ this.statisticsLoading = true;
484
+ try {
485
+ const response = await client.query({
486
+ query: gql `
487
+ query {
488
+ kpiStatistics {
489
+ items {
490
+ id
491
+ valueDate
492
+ periodType
493
+ count
494
+ sum
495
+ range
496
+ mean
497
+ median
498
+ minimum
499
+ maximum
500
+ standardDeviation
501
+ variance
502
+ percentile25
503
+ percentile75
504
+ iqr
505
+ lowerFence
506
+ upperFence
507
+ additionalStatistics
508
+ metadata
509
+ kpi {
510
+ id
511
+ name
512
+ unit
513
+ category {
514
+ id
515
+ name
516
+ }
517
+ }
518
+ }
519
+ }
520
+ }
521
+ `
522
+ });
523
+ this.kpiStatistics = response.data.kpiStatistics.items || [];
524
+ // 현재 월을 기본값으로 설정
525
+ const currentMonth = this.currentMonth;
526
+ const availableDates = this.availableValueDates;
527
+ if (availableDates.includes(currentMonth)) {
528
+ // 현재 월 데이터가 있으면 현재 월로 설정
529
+ this.selectedValueDate = currentMonth;
530
+ }
531
+ else if (availableDates.length > 0) {
532
+ // 현재 월 데이터가 없으면 가장 최근 데이터로 설정
533
+ this.selectedValueDate = availableDates[0];
534
+ }
535
+ else {
536
+ // 데이터가 없으면 현재 월로 설정
537
+ this.selectedValueDate = currentMonth;
538
+ }
539
+ }
540
+ catch (e) {
541
+ console.error('KPI 통계 데이터를 불러오지 못했습니다:', e);
542
+ }
543
+ finally {
544
+ this.statisticsLoading = false;
545
+ }
546
+ }
294
547
  async openHistoryModal(kpi) {
295
548
  // 전체 이력 fetch (limit 없이)
296
549
  try {
@@ -486,6 +739,135 @@ let KpiDashboardPage = class KpiDashboardPage extends PageView {
486
739
  </div>
487
740
  </div>
488
741
  </div>
742
+
743
+ <!-- 실제 KPI 통계 차트 섹션 -->
744
+ <div class="sample-charts-section">
745
+ <!-- 통계 필터 및 요약 -->
746
+ <div style="margin-bottom: 24px; background: #f8f9fa; padding: 16px; border-radius: 8px;">
747
+ <div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
748
+ <div style="display: flex; align-items: center; gap: 8px;">
749
+ <label style="font-weight: 500; color: #333;">기간:</label>
750
+ <span style="padding: 6px 12px; background: #e9ecef; border-radius: 4px; color: #495057;">
751
+ MONTH (월별)
752
+ </span>
753
+ </div>
754
+ <div style="display: flex; align-items: center; gap: 8px;">
755
+ <label style="font-weight: 500; color: #333;">날짜:</label>
756
+ <select
757
+ .value=${this.selectedValueDate}
758
+ @change=${this._onValueDateChange}
759
+ style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: white;"
760
+ >
761
+ ${this.availableValueDates.map(date => html `<option value="${date}">${date}${date === this.currentMonth ? ' (현재)' : ''}</option>`)}
762
+ </select>
763
+ </div>
764
+ ${this.statisticsSummary
765
+ ? html `
766
+ <div style="display: flex; gap: 16px; margin-left: auto;">
767
+ <span style="color: #666; font-size: 0.9em;">
768
+ <b>KPI:</b> ${this.statisticsSummary.totalKpis}개
769
+ </span>
770
+ <span style="color: #666; font-size: 0.9em;">
771
+ <b>카테고리:</b> ${this.statisticsSummary.totalCategories}개
772
+ </span>
773
+ <span style="color: #666; font-size: 0.9em;">
774
+ <b>평균:</b> ${this.statisticsSummary.avgMean}
775
+ </span>
776
+ <span style="color: #666; font-size: 0.9em;">
777
+ <b>중앙값:</b> ${this.statisticsSummary.avgMedian}
778
+ </span>
779
+ <span style="color: #666; font-size: 0.9em;">
780
+ <b>표준편차:</b> ${this.statisticsSummary.avgStdDev}
781
+ </span>
782
+ </div>
783
+ `
784
+ : nothing}
785
+ </div>
786
+ </div>
787
+
788
+ <div class="sample-chart-card">
789
+ <div class="sample-chart-title">실제 KPI 통계 비교 (Radar)</div>
790
+ <div class="sample-chart-container">
791
+ ${this.statisticsLoading
792
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
793
+ 통계 데이터 로딩 중...
794
+ </div>`
795
+ : this.realKpiRadarData.length > 0
796
+ ? html `<kpi-radar-chart
797
+ .data=${this.realKpiRadarData}
798
+ .categories=${this.realKpiRadarCategories}
799
+ .currentGroup=${'평균'}
800
+ ></kpi-radar-chart>`
801
+ : html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
802
+ 선택된 기간에 통계 데이터가 없습니다.
803
+ </div>`}
804
+ </div>
805
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
806
+ ※ <b>실제 KPI 통계 Radar 차트</b>는 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 평균, 중앙값,
807
+ 표준편차를 비교합니다.<br />
808
+ <b>평균</b>은 산술평균, <b>중앙값</b>은 50분위수, <b>표준편차</b>는 데이터 분산 정도를 나타냅니다.
809
+ </div>
810
+ </div>
811
+ <div class="sample-chart-card">
812
+ <div class="sample-chart-title">실제 KPI 통계 분포 (Boxplot)</div>
813
+ <div class="sample-chart-container">
814
+ ${this.statisticsLoading
815
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
816
+ 통계 데이터 로딩 중...
817
+ </div>`
818
+ : this.realKpiBoxplotData.length > 0
819
+ ? html `<kpi-boxplot-chart
820
+ .data=${this.realKpiBoxplotData}
821
+ .groups=${this.realKpiBoxplotGroups}
822
+ .currentGroup=${'평균'}
823
+ ></kpi-boxplot-chart>`
824
+ : html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
825
+ 선택된 기간에 통계 데이터가 없습니다.
826
+ </div>`}
827
+ </div>
828
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
829
+ ※ <b>실제 KPI 통계 Boxplot</b>은 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 통계값의 분포를
830
+ 보여줍니다.<br />
831
+ 각 카테고리의 <b>평균값들</b>을 기준으로 분포를 계산하여, 카테고리 간 통계적 특성을 비교할 수 있습니다.
832
+ </div>
833
+ </div>
834
+ </div>
835
+
836
+ <!-- 3레벨 KPI 플로팅 섹션 -->
837
+ <div class="sample-charts-section">
838
+ <div style="margin-bottom: 24px;">
839
+ <h2 style="font-size: 1.5rem; font-weight: bold; color: #333; text-align: center; margin-bottom: 8px;">
840
+ 3레벨 KPI 분석
841
+ </h2>
842
+ <p style="text-align: center; color: #666; font-size: 0.95rem;">
843
+ 실제 KPIStatistic 데이터를 기반으로 한 다층적 분석
844
+ </p>
845
+ </div>
846
+
847
+ <!-- 1레벨: 그룹 총 스코어 -->
848
+ <div style="margin-bottom: 32px;">
849
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
850
+ 📊 1레벨: 그룹 총 스코어
851
+ </h3>
852
+ <kpi-level1-card></kpi-level1-card>
853
+ </div>
854
+
855
+ <!-- 2레벨: 카테고리 비교 -->
856
+ <div style="margin-bottom: 32px;">
857
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
858
+ 📈 2레벨: 카테고리 비교 분석
859
+ </h3>
860
+ <kpi-level2-comparison></kpi-level2-comparison>
861
+ </div>
862
+
863
+ <!-- 3레벨: 개별 KPI 비교 -->
864
+ <div style="margin-bottom: 32px;">
865
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
866
+ 🔍 3레벨: 개별 KPI 상세 분석
867
+ </h3>
868
+ <kpi-level3-comparison></kpi-level3-comparison>
869
+ </div>
870
+ </div>
489
871
  ${this.showHistoryModal
490
872
  ? html `
491
873
  <div
@@ -636,6 +1018,22 @@ __decorate([
636
1018
  state(),
637
1019
  __metadata("design:type", String)
638
1020
  ], KpiDashboardPage.prototype, "modalKpiName", void 0);
1021
+ __decorate([
1022
+ state(),
1023
+ __metadata("design:type", Array)
1024
+ ], KpiDashboardPage.prototype, "kpiStatistics", void 0);
1025
+ __decorate([
1026
+ state(),
1027
+ __metadata("design:type", Object)
1028
+ ], KpiDashboardPage.prototype, "statisticsLoading", void 0);
1029
+ __decorate([
1030
+ state(),
1031
+ __metadata("design:type", String)
1032
+ ], KpiDashboardPage.prototype, "selectedPeriodType", void 0);
1033
+ __decorate([
1034
+ state(),
1035
+ __metadata("design:type", String)
1036
+ ], KpiDashboardPage.prototype, "selectedValueDate", void 0);
639
1037
  KpiDashboardPage = __decorate([
640
1038
  customElement('kpi-dashboard')
641
1039
  ], KpiDashboardPage);