@dssp/dkpi 1.0.0-alpha.6 → 1.0.0-alpha.60

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 (209) hide show
  1. package/KPI-STATISTICS-SERVICE.md +233 -0
  2. package/assets/favicon.ico +0 -0
  3. package/assets/images/project-image.png +0 -0
  4. package/assets/manifest/apple-1024.png +0 -0
  5. package/assets/manifest/apple-120.png +0 -0
  6. package/assets/manifest/apple-152.png +0 -0
  7. package/assets/manifest/apple-167.png +0 -0
  8. package/assets/manifest/apple-180.png +0 -0
  9. package/assets/manifest/apple-touch-icon.png +0 -0
  10. package/assets/manifest/badge-128x128.png +0 -0
  11. package/assets/manifest/chrome-splashscreen-icon-384x384.png +0 -0
  12. package/assets/manifest/chrome-touch-icon-192x192.png +0 -0
  13. package/assets/manifest/icon-128x128.png +0 -0
  14. package/assets/manifest/icon-192x192.png +0 -0
  15. package/assets/manifest/icon-512x512.png +0 -0
  16. package/assets/manifest/icon-72x72.png +0 -0
  17. package/assets/manifest/icon-96x96.png +0 -0
  18. package/assets/manifest/image-metaog.png +0 -0
  19. package/assets/manifest/maskable_icon.png +0 -0
  20. package/assets/manifest/ms-icon-144x144.png +0 -0
  21. package/assets/manifest/ms-touch-icon-144x144-precomposed.png +0 -0
  22. package/assets/videos/intro.mp4 +0 -0
  23. package/dist-client/bootstrap.js +64 -4
  24. package/dist-client/bootstrap.js.map +1 -1
  25. package/dist-client/components/kpi-boxplot-chart.d.ts +24 -0
  26. package/dist-client/components/kpi-boxplot-chart.js +264 -0
  27. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  28. package/dist-client/components/kpi-lookup-chart.d.ts +29 -0
  29. package/dist-client/components/kpi-lookup-chart.js +434 -0
  30. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  31. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  32. package/dist-client/components/kpi-mini-trend-chart.js +148 -0
  33. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  34. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  35. package/dist-client/components/kpi-radar-chart.js +235 -0
  36. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  37. package/dist-client/components/kpi-single-boxplot-chart.d.ts +24 -0
  38. package/dist-client/components/kpi-single-boxplot-chart.js +333 -0
  39. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  40. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  41. package/dist-client/components/kpi-trend-chart.js +220 -0
  42. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  43. package/dist-client/components/sv-pagenation-control.d.ts +18 -0
  44. package/dist-client/components/sv-pagenation-control.js +142 -0
  45. package/dist-client/components/sv-pagenation-control.js.map +1 -0
  46. package/dist-client/google-map/common-google-map.d.ts +35 -0
  47. package/dist-client/google-map/common-google-map.js +343 -0
  48. package/dist-client/google-map/common-google-map.js.map +1 -0
  49. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  50. package/dist-client/google-map/google-map-loader.js +23 -0
  51. package/dist-client/google-map/google-map-loader.js.map +1 -0
  52. package/dist-client/icons/menu-icons.d.ts +6 -0
  53. package/dist-client/icons/menu-icons.js +42 -0
  54. package/dist-client/icons/menu-icons.js.map +1 -1
  55. package/dist-client/menu.d.ts +23 -1
  56. package/dist-client/menu.js +57 -2
  57. package/dist-client/menu.js.map +1 -1
  58. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  59. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +280 -0
  60. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  61. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +21 -0
  62. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +389 -0
  63. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  64. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +25 -0
  65. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +469 -0
  66. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  67. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  68. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  69. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  70. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +34 -0
  71. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +642 -0
  72. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  73. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +38 -0
  74. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +501 -0
  75. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  76. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +26 -0
  77. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +439 -0
  78. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  79. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  80. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  81. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  82. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +36 -0
  83. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +572 -0
  84. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  85. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +59 -0
  86. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1027 -0
  87. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  88. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  89. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  90. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  91. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  92. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  93. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  94. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  95. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  96. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  97. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  98. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  99. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  100. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  101. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  102. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  103. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +58 -0
  104. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +731 -0
  105. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  106. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
  107. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +76 -0
  108. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
  109. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +69 -0
  110. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +385 -0
  111. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
  112. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +12 -0
  113. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +174 -0
  114. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
  115. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +41 -0
  116. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +191 -0
  117. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
  118. package/dist-client/pages/kpi-value/kpi-value-importer.d.ts +23 -0
  119. package/dist-client/pages/kpi-value/kpi-value-importer.js +93 -0
  120. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -0
  121. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +72 -0
  122. package/dist-client/pages/kpi-value/kpi-value-list-page.js +465 -0
  123. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -0
  124. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +14 -0
  125. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +315 -0
  126. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  127. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +14 -0
  128. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +275 -0
  129. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  130. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +15 -0
  131. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +222 -0
  132. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  133. package/dist-client/pages/sv-project-complete.d.ts +20 -0
  134. package/dist-client/pages/sv-project-complete.js +209 -0
  135. package/dist-client/pages/sv-project-complete.js.map +1 -0
  136. package/dist-client/pages/sv-project-completed-list.d.ts +27 -0
  137. package/dist-client/pages/sv-project-completed-list.js +416 -0
  138. package/dist-client/pages/sv-project-completed-list.js.map +1 -0
  139. package/dist-client/pages/sv-project-detail.d.ts +34 -0
  140. package/dist-client/pages/sv-project-detail.js +1081 -0
  141. package/dist-client/pages/sv-project-detail.js.map +1 -0
  142. package/dist-client/pages/sv-project-list.d.ts +165 -0
  143. package/dist-client/pages/sv-project-list.js +489 -0
  144. package/dist-client/pages/sv-project-list.js.map +1 -0
  145. package/dist-client/route.d.ts +1 -1
  146. package/dist-client/route.js +25 -1
  147. package/dist-client/route.js.map +1 -1
  148. package/dist-client/shared/complete-api.d.ts +8 -0
  149. package/dist-client/shared/complete-api.js +177 -0
  150. package/dist-client/shared/complete-api.js.map +1 -0
  151. package/dist-client/shared/func.d.ts +2 -0
  152. package/dist-client/shared/func.js +22 -0
  153. package/dist-client/shared/func.js.map +1 -0
  154. package/dist-client/themes/dark.css +24 -24
  155. package/dist-client/themes/light.css +23 -23
  156. package/dist-client/tsconfig.tsbuildinfo +1 -1
  157. package/dist-client/viewparts/menu-tools.d.ts +46 -1
  158. package/dist-client/viewparts/menu-tools.js +377 -15
  159. package/dist-client/viewparts/menu-tools.js.map +1 -1
  160. package/dist-server/index.d.ts +2 -0
  161. package/dist-server/index.js +5 -0
  162. package/dist-server/index.js.map +1 -1
  163. package/dist-server/migrations/index.d.ts +1 -0
  164. package/dist-server/migrations/index.js +12 -0
  165. package/dist-server/migrations/index.js.map +1 -0
  166. package/dist-server/scripts/calculate-kpi-scores.d.ts +10 -0
  167. package/dist-server/scripts/calculate-kpi-scores.js +271 -0
  168. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -0
  169. package/dist-server/scripts/load-grade-data-migration.d.ts +10 -0
  170. package/dist-server/scripts/load-grade-data-migration.js +194 -0
  171. package/dist-server/scripts/load-grade-data-migration.js.map +1 -0
  172. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +10 -0
  173. package/dist-server/scripts/propagate-parent-kpi-values.js +440 -0
  174. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -0
  175. package/dist-server/service/index.d.ts +4 -0
  176. package/dist-server/service/index.js +20 -0
  177. package/dist-server/service/index.js.map +1 -0
  178. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  179. package/dist-server/service/kpi-metric-value/index.js +8 -0
  180. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  181. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +62 -0
  182. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +572 -0
  183. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  184. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +7 -0
  185. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +47 -0
  186. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  187. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  188. package/dist-server/service/kpi-stat/index.js +8 -0
  189. package/dist-server/service/kpi-stat/index.js.map +1 -0
  190. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +9 -0
  191. package/dist-server/service/kpi-stat/kpi-stat-query.js +443 -0
  192. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  193. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +19 -0
  194. package/dist-server/service/kpi-stat/kpi-stat-types.js +130 -0
  195. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  196. package/dist-server/service/kpi-value/index.d.ts +3 -0
  197. package/dist-server/service/kpi-value/index.js +7 -0
  198. package/dist-server/service/kpi-value/index.js.map +1 -0
  199. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  200. package/dist-server/service/kpi-value/kpi-value-query.js +69 -0
  201. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  202. package/dist-server/tsconfig.tsbuildinfo +1 -1
  203. package/kpi-module-service-tests.md +1286 -0
  204. package/kpi-module-test-report.md +676 -0
  205. package/kpi-module-unit-test-detailed-report.md +925 -0
  206. package/kpi-module-unit-tests-detailed.md +1452 -0
  207. package/package.json +62 -51
  208. package/schema.graphql +11559 -3215
  209. package/things-factory.config.js +7 -1
@@ -0,0 +1,1027 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css, nothing } from 'lit';
3
+ import { customElement } from 'lit/decorators.js';
4
+ import { PageView } from '@operato/shell';
5
+ import { ScrollbarStyles } from '@operato/styles';
6
+ import { client } from '@operato/graphql';
7
+ import gql from 'graphql-tag';
8
+ import { state } from 'lit/decorators.js';
9
+ import '@material/web/icon/icon.js';
10
+ import './kpi-performance-summary';
11
+ import './kpi-grade-visualization';
12
+ import './kpi-history-viewer';
13
+ import './kpi-list-summary';
14
+ import './kpi-value-entry';
15
+ import './kpi-alert-panel';
16
+ import '../../components/kpi-radar-chart';
17
+ import '../../components/kpi-boxplot-chart';
18
+ // 3레벨 KPI 플로팅 컴포넌트들
19
+ import './cards/kpi-level1-card';
20
+ import './cards/kpi-level2-comparison';
21
+ import './cards/kpi-level3-comparison';
22
+ let KpiDashboardPage = class KpiDashboardPage extends PageView {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.categories = [];
26
+ this.loading = true;
27
+ this.error = '';
28
+ this.alerts = [];
29
+ this.showHistoryModal = false;
30
+ this.modalHistories = [];
31
+ this.modalKpiName = '';
32
+ this.kpiStatistics = []; // 실제 KPI 통계 데이터
33
+ this.statisticsLoading = true; // 통계 데이터 로딩 상태
34
+ this.selectedPeriodType = 'MONTH'; // 선택된 기간 타입 (MONTH로 고정)
35
+ this.selectedValueDate = ''; // 선택된 값 날짜
36
+ }
37
+ // 샘플 데이터
38
+ get sampleCategories() {
39
+ return ['생산성', '품질', '안전', '환경', '비용', '일정'];
40
+ }
41
+ get sampleGroups() {
42
+ return ['A', 'B', 'C'];
43
+ }
44
+ get sampleSeriesData() {
45
+ // 각 카테고리별로 10개 이상의 다양한 값(평균, 분산, 이상치 포함)
46
+ // 생산성: 평균 높고 분산 큼, 이상치 포함
47
+ const 생산성 = [95, 92, 90, 88, 85, 80, 78, 75, 70, 60, 100]; // 100은 이상치
48
+ // 품질: 평균 높고 분산 작음
49
+ const 품질 = [90, 89, 88, 87, 86, 85, 84, 83, 82, 80, 70];
50
+ // 안전: 평균 중간, 이상치 포함
51
+ const 안전 = [92, 91, 90, 89, 88, 87, 86, 85, 84, 65, 60]; // 60은 이상치
52
+ // 환경: 낮은 값에 몰림, 분산 큼
53
+ const 환경 = [95, 90, 85, 80, 75, 70, 65, 60, 60, 60, 55];
54
+ // 비용: 전체적으로 낮음, 이상치 포함
55
+ const 비용 = [80, 78, 76, 74, 72, 70, 68, 66, 64, 62, 50]; // 50은 이상치
56
+ // 일정: 분산 큼, 이상치 포함
57
+ const 일정 = [90, 88, 86, 84, 82, 80, 78, 76, 74, 60, 100]; // 100은 이상치
58
+ const categories = this.sampleCategories;
59
+ const groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'];
60
+ const all = [];
61
+ categories.forEach((cat, i) => {
62
+ let arr = [];
63
+ switch (cat) {
64
+ case '생산성':
65
+ arr = 생산성;
66
+ break;
67
+ case '품질':
68
+ arr = 품질;
69
+ break;
70
+ case '안전':
71
+ arr = 안전;
72
+ break;
73
+ case '환경':
74
+ arr = 환경;
75
+ break;
76
+ case '비용':
77
+ arr = 비용;
78
+ break;
79
+ case '일정':
80
+ arr = 일정;
81
+ break;
82
+ }
83
+ arr.forEach((v, idx) => {
84
+ var _a;
85
+ all.push({ group: (_a = groups[idx]) !== null && _a !== void 0 ? _a : `G${idx + 1}`, category: cat, value: v });
86
+ });
87
+ });
88
+ return all;
89
+ }
90
+ get sampleRadarData() {
91
+ // 카테고리별로 min, max, avg, A(자신의 그룹)만 반환
92
+ const categories = this.sampleCategories;
93
+ const data = this.sampleSeriesData;
94
+ const minData = { group: 'Min' };
95
+ const maxData = { group: 'Max' };
96
+ const avgData = { group: 'Avg' };
97
+ const aData = { group: 'A' };
98
+ categories.forEach(category => {
99
+ var _a, _b;
100
+ const values = data.filter(d => d.category === category).map(d => d.value);
101
+ minData[category] = Math.min(...values);
102
+ maxData[category] = Math.max(...values);
103
+ avgData[category] = values.reduce((a, b) => a + b, 0) / values.length;
104
+ aData[category] = (_b = (_a = data.find(d => d.category === category && d.org === 'A')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : avgData[category];
105
+ });
106
+ // kpi-radar-chart가 기대하는 구조로 변환: [{org, category, value} ...]
107
+ const result = [];
108
+ categories.forEach(category => {
109
+ result.push({ org: 'Min', category, value: minData[category] });
110
+ result.push({ org: 'Max', category, value: maxData[category] });
111
+ result.push({ org: 'Avg', category, value: avgData[category] });
112
+ result.push({ org: 'A', category, value: aData[category] });
113
+ });
114
+ return result;
115
+ }
116
+ get sampleRadarCategories() {
117
+ return this.sampleCategories;
118
+ }
119
+ get sampleRadarGroups() {
120
+ return this.sampleGroups;
121
+ }
122
+ get sampleBoxplotData() {
123
+ // 카테고리별로 그룹별 value의 분포 계산
124
+ const categories = this.sampleCategories;
125
+ const data = this.sampleSeriesData;
126
+ return categories.map(category => {
127
+ var _a, _b;
128
+ const values = data.filter(d => d.category === category).map(d => d.value);
129
+ const sorted = [...values].sort((a, b) => a - b);
130
+ const min = sorted[0];
131
+ const max = sorted[sorted.length - 1];
132
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
133
+ const median = sorted.length % 2 === 0
134
+ ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
135
+ : sorted[Math.floor(sorted.length / 2)];
136
+ const q1 = sorted[Math.floor(sorted.length / 4)];
137
+ const q3 = sorted[Math.floor((sorted.length * 3) / 4)];
138
+ // value: A그룹의 실제값(강조)
139
+ const value = (_b = (_a = data.find(d => d.category === category && d.org === 'A')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : mean;
140
+ return {
141
+ org: category, // x축: 카테고리
142
+ min,
143
+ q1,
144
+ median,
145
+ q3,
146
+ max,
147
+ mean,
148
+ value
149
+ };
150
+ });
151
+ }
152
+ get sampleBoxplotGroups() {
153
+ return this.sampleCategories;
154
+ }
155
+ get sampleCurrentGroup() {
156
+ return 'A';
157
+ }
158
+ // 현재 월을 YYYY-MM 형식으로 반환
159
+ get currentMonth() {
160
+ const now = new Date();
161
+ const year = now.getFullYear();
162
+ const month = String(now.getMonth() + 1).padStart(2, '0');
163
+ return `${year}-${month}`;
164
+ }
165
+ // 필터링된 KPI 통계 데이터
166
+ get filteredKpiStatistics() {
167
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0)
168
+ return [];
169
+ return this.kpiStatistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.selectedValueDate);
170
+ }
171
+ // 사용 가능한 기간 타입들 (MONTH로 고정)
172
+ get availablePeriodTypes() {
173
+ return ['MONTH'];
174
+ }
175
+ // MONTH 기간에 사용 가능한 날짜들
176
+ get availableValueDates() {
177
+ if (!this.kpiStatistics || this.kpiStatistics.length === 0)
178
+ return [];
179
+ const valueDates = new Set();
180
+ this.kpiStatistics.forEach(stat => {
181
+ if (stat.periodType === 'MONTH' && stat.valueDate) {
182
+ valueDates.add(stat.valueDate);
183
+ }
184
+ });
185
+ return Array.from(valueDates).sort().reverse(); // 최신 날짜부터 정렬
186
+ }
187
+ // 통계 요약 정보
188
+ get statisticsSummary() {
189
+ const filteredStats = this.filteredKpiStatistics;
190
+ if (filteredStats.length === 0)
191
+ return null;
192
+ const totalKpis = filteredStats.length;
193
+ const categories = new Set(filteredStats.map(s => { var _a, _b; return (_b = (_a = s.kpi) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.name; }).filter(Boolean));
194
+ const totalCategories = categories.size;
195
+ const means = filteredStats.map(s => s.mean || 0).filter(v => v > 0);
196
+ const medians = filteredStats.map(s => s.median || 0).filter(v => v > 0);
197
+ const stdDevs = filteredStats.map(s => s.standardDeviation || 0).filter(v => v > 0);
198
+ const avgMean = means.length > 0 ? means.reduce((a, b) => a + b, 0) / means.length : 0;
199
+ const avgMedian = medians.length > 0 ? medians.reduce((a, b) => a + b, 0) / medians.length : 0;
200
+ const avgStdDev = stdDevs.length > 0 ? stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length : 0;
201
+ return {
202
+ totalKpis,
203
+ totalCategories,
204
+ avgMean: avgMean.toFixed(2),
205
+ avgMedian: avgMedian.toFixed(2),
206
+ avgStdDev: avgStdDev.toFixed(2),
207
+ periodType: this.selectedPeriodType,
208
+ valueDate: this.selectedValueDate
209
+ };
210
+ }
211
+ // 기간 타입 변경 핸들러 (사용하지 않음 - MONTH로 고정)
212
+ _onPeriodTypeChange(event) {
213
+ // MONTH로 고정되어 있으므로 변경하지 않음
214
+ }
215
+ // 날짜 변경 핸들러
216
+ _onValueDateChange(event) {
217
+ const target = event.target;
218
+ this.selectedValueDate = target.value;
219
+ }
220
+ // 실제 KPI 통계 데이터를 기반으로 한 레이더 차트 데이터
221
+ get realKpiRadarData() {
222
+ const filteredStats = this.filteredKpiStatistics;
223
+ if (filteredStats.length === 0)
224
+ return [];
225
+ // 카테고리별로 통계 데이터 그룹화
226
+ const categoryStats = new Map();
227
+ filteredStats.forEach(stat => {
228
+ var _a, _b;
229
+ if ((_b = (_a = stat.kpi) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.name) {
230
+ const categoryName = stat.kpi.category.name;
231
+ if (!categoryStats.has(categoryName)) {
232
+ categoryStats.set(categoryName, []);
233
+ }
234
+ categoryStats.get(categoryName).push(stat);
235
+ }
236
+ });
237
+ // 각 카테고리별로 평균, 중앙값, 표준편차 계산
238
+ const result = [];
239
+ const categories = Array.from(categoryStats.keys());
240
+ categories.forEach(category => {
241
+ const stats = categoryStats.get(category);
242
+ const means = stats.map(s => s.mean || 0).filter(v => v > 0);
243
+ const medians = stats.map(s => s.median || 0).filter(v => v > 0);
244
+ const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0);
245
+ if (means.length > 0) {
246
+ result.push({ group: '평균', category, value: means.reduce((a, b) => a + b, 0) / means.length });
247
+ }
248
+ if (medians.length > 0) {
249
+ result.push({ group: '중앙값', category, value: medians.reduce((a, b) => a + b, 0) / medians.length });
250
+ }
251
+ if (stdDevs.length > 0) {
252
+ result.push({ group: '표준편차', category, value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length });
253
+ }
254
+ });
255
+ return result;
256
+ }
257
+ get realKpiRadarCategories() {
258
+ const filteredStats = this.filteredKpiStatistics;
259
+ if (filteredStats.length === 0)
260
+ return [];
261
+ const categories = new Set();
262
+ filteredStats.forEach(stat => {
263
+ var _a, _b;
264
+ if ((_b = (_a = stat.kpi) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.name) {
265
+ categories.add(stat.kpi.category.name);
266
+ }
267
+ });
268
+ return Array.from(categories);
269
+ }
270
+ get realKpiRadarGroups() {
271
+ return ['평균', '중앙값', '표준편차'];
272
+ }
273
+ // 실제 KPI 통계 데이터를 기반으로 한 박스플롯 데이터
274
+ get realKpiBoxplotData() {
275
+ const filteredStats = this.filteredKpiStatistics;
276
+ if (filteredStats.length === 0)
277
+ return [];
278
+ // 카테고리별로 통계 데이터 그룹화
279
+ const categoryStats = new Map();
280
+ filteredStats.forEach(stat => {
281
+ var _a, _b;
282
+ if ((_b = (_a = stat.kpi) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.name) {
283
+ const categoryName = stat.kpi.category.name;
284
+ if (!categoryStats.has(categoryName)) {
285
+ categoryStats.set(categoryName, []);
286
+ }
287
+ categoryStats.get(categoryName).push(stat);
288
+ }
289
+ });
290
+ const result = [];
291
+ const categories = Array.from(categoryStats.keys());
292
+ categories.forEach(category => {
293
+ const stats = categoryStats.get(category);
294
+ // 각 KPI의 통계값들을 수집
295
+ const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0);
296
+ const allMedians = stats.map(s => s.median || 0).filter(v => v > 0);
297
+ const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0);
298
+ const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0);
299
+ const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0);
300
+ const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0);
301
+ if (allMeans.length > 0) {
302
+ const sortedMeans = [...allMeans].sort((a, b) => a - b);
303
+ const min = sortedMeans[0];
304
+ const max = sortedMeans[sortedMeans.length - 1];
305
+ const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length;
306
+ const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean;
307
+ const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)];
308
+ const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)];
309
+ result.push({
310
+ group: category,
311
+ min,
312
+ q1,
313
+ median,
314
+ q3,
315
+ max,
316
+ mean,
317
+ value: mean // 현재 값으로 평균 사용
318
+ });
319
+ }
320
+ });
321
+ return result;
322
+ }
323
+ get realKpiBoxplotGroups() {
324
+ const filteredStats = this.filteredKpiStatistics;
325
+ if (filteredStats.length === 0)
326
+ return [];
327
+ const categories = new Set();
328
+ filteredStats.forEach(stat => {
329
+ var _a, _b;
330
+ if ((_b = (_a = stat.kpi) === null || _a === void 0 ? void 0 : _a.category) === null || _b === void 0 ? void 0 : _b.name) {
331
+ categories.add(stat.kpi.category.name);
332
+ }
333
+ });
334
+ return Array.from(categories);
335
+ }
336
+ get realKpiCurrentGroup() {
337
+ return '평균';
338
+ }
339
+ connectedCallback() {
340
+ super.connectedCallback();
341
+ this.fetchCategories();
342
+ this.fetchKpiStatistics();
343
+ }
344
+ pageUpdated(changes, lifecycle) {
345
+ if (this.active) {
346
+ this.fetchCategories();
347
+ this.fetchKpiStatistics();
348
+ }
349
+ }
350
+ async fetchCategories() {
351
+ this.loading = true;
352
+ this.error = '';
353
+ try {
354
+ const response = await client.query({
355
+ query: gql `
356
+ query {
357
+ kpiCategories: kpisLevel1 {
358
+ id
359
+ name
360
+ kpis: children {
361
+ id
362
+ name
363
+ value {
364
+ value
365
+ valueDate
366
+ }
367
+ targetValue
368
+ unit
369
+ grades
370
+ vizType
371
+ vizMeta
372
+ histories(limit: 1) {
373
+ version
374
+ updatedAt
375
+ updater {
376
+ name
377
+ }
378
+ }
379
+ }
380
+ }
381
+ kpiAlerts {
382
+ id
383
+ kpi {
384
+ id
385
+ }
386
+ message
387
+ level
388
+ createdAt
389
+ }
390
+ }
391
+ `
392
+ });
393
+ this.categories = response.data.kpiCategories || [];
394
+ this.alerts = response.data.kpiAlerts || [];
395
+ }
396
+ catch (e) {
397
+ this.error = 'KPI 카테고리 데이터를 불러오지 못했습니다.';
398
+ }
399
+ finally {
400
+ this.loading = false;
401
+ }
402
+ }
403
+ async fetchKpiStatistics() {
404
+ this.statisticsLoading = true;
405
+ try {
406
+ const response = await client.query({
407
+ query: gql `
408
+ query {
409
+ kpiStatistics {
410
+ items {
411
+ id
412
+ valueDate
413
+ periodType
414
+ count
415
+ sum
416
+ range
417
+ mean
418
+ median
419
+ minimum
420
+ maximum
421
+ standardDeviation
422
+ variance
423
+ percentile25
424
+ percentile75
425
+ iqr
426
+ lowerFence
427
+ upperFence
428
+ additionalStatistics
429
+ metadata
430
+ kpi {
431
+ id
432
+ name
433
+ unit
434
+ category: parent {
435
+ id
436
+ name
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
442
+ `
443
+ });
444
+ this.kpiStatistics = response.data.kpiStatistics.items || [];
445
+ // 현재 월을 기본값으로 설정
446
+ const currentMonth = this.currentMonth;
447
+ const availableDates = this.availableValueDates;
448
+ if (availableDates.includes(currentMonth)) {
449
+ // 현재 월 데이터가 있으면 현재 월로 설정
450
+ this.selectedValueDate = currentMonth;
451
+ }
452
+ else if (availableDates.length > 0) {
453
+ // 현재 월 데이터가 없으면 가장 최근 데이터로 설정
454
+ this.selectedValueDate = availableDates[0];
455
+ }
456
+ else {
457
+ // 데이터가 없으면 현재 월로 설정
458
+ this.selectedValueDate = currentMonth;
459
+ }
460
+ }
461
+ catch (e) {
462
+ console.error('KPI 통계 데이터를 불러오지 못했습니다:', e);
463
+ }
464
+ finally {
465
+ this.statisticsLoading = false;
466
+ }
467
+ }
468
+ async openHistoryModal(kpi) {
469
+ // 전체 이력 fetch (limit 없이)
470
+ try {
471
+ const response = await client.query({
472
+ query: gql `
473
+ query ($kpiId: String!) {
474
+ kpi(id: $kpiId) {
475
+ name
476
+ histories {
477
+ version
478
+ updatedAt
479
+ updater {
480
+ name
481
+ }
482
+ }
483
+ }
484
+ }
485
+ `,
486
+ variables: { kpiId: kpi.id }
487
+ });
488
+ this.modalHistories = response.data.kpi.histories || [];
489
+ this.modalKpiName = response.data.kpi.name || '';
490
+ this.showHistoryModal = true;
491
+ }
492
+ catch (e) {
493
+ alert('이력 데이터를 불러오지 못했습니다.');
494
+ }
495
+ }
496
+ closeHistoryModal() {
497
+ this.showHistoryModal = false;
498
+ this.modalHistories = [];
499
+ this.modalKpiName = '';
500
+ }
501
+ _renderKpiValue(kpi) {
502
+ var _a, _b, _c, _d;
503
+ const kpiValue = (_b = (_a = kpi.value) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 0;
504
+ const targetValue = (_c = kpi.targetValue) !== null && _c !== void 0 ? _c : 100;
505
+ const unit = (_d = kpi.unit) !== null && _d !== void 0 ? _d : '';
506
+ const vizType = kpi.vizType || 'CARD';
507
+ const vizMeta = kpi.vizMeta || {};
508
+ const color = vizMeta.color || '#3a3ad6';
509
+ const icon = vizMeta.icon || 'trending_up';
510
+ const minValue = vizMeta.minValue || 0;
511
+ const maxValue = vizMeta.maxValue || 100;
512
+ const decimalPlaces = vizMeta.decimalPlaces || 0;
513
+ const formattedValue = typeof kpiValue === 'number' ? kpiValue.toFixed(decimalPlaces) : kpiValue;
514
+ switch (vizType) {
515
+ case 'GAUGE':
516
+ const gaugePercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100);
517
+ return html `
518
+ <div style="text-align:center;margin:16px 0;">
519
+ <div
520
+ style="width:120px;height:60px;border-radius:60px 60px 0 0;background:conic-gradient(${color} 0deg ${gaugePercentage *
521
+ 3.6}deg, #e0e0e0 ${gaugePercentage * 3.6}deg 360deg);margin:0 auto;position:relative;"
522
+ >
523
+ <div
524
+ style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);font-size:1.2rem;font-weight:bold;color:${color};"
525
+ >
526
+ ${formattedValue}${unit}
527
+ </div>
528
+ </div>
529
+ </div>
530
+ `;
531
+ case 'PROGRESS':
532
+ const progressPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100);
533
+ return html `
534
+ <div style="margin:16px 0;">
535
+ <div style="background:#e0e0e0;height:20px;border-radius:10px;overflow:hidden;">
536
+ <div style="background:${color};height:100%;width:${progressPercentage}%;transition:width 0.3s;"></div>
537
+ </div>
538
+ <div style="text-align:center;margin-top:8px;font-weight:bold;color:${color};font-size:1.2rem;">
539
+ ${formattedValue}${unit}
540
+ </div>
541
+ </div>
542
+ `;
543
+ case 'ICON':
544
+ return html `
545
+ <div style="text-align:center;margin:16px 0;">
546
+ <md-icon style="color:${color};font-size:48px;">${icon}</md-icon>
547
+ <div style="font-size:1.5rem;font-weight:bold;color:${color};margin-top:8px;">${formattedValue}${unit}</div>
548
+ </div>
549
+ `;
550
+ case 'THERMOMETER':
551
+ const thermoPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100);
552
+ return html `
553
+ <div style="text-align:center;margin:16px 0;">
554
+ <div
555
+ style="width:40px;height:120px;background:#e0e0e0;border-radius:20px;margin:0 auto;position:relative;overflow:hidden;"
556
+ >
557
+ <div
558
+ style="position:absolute;bottom:0;width:100%;height:${thermoPercentage}%;background:${color};border-radius:20px;"
559
+ ></div>
560
+ </div>
561
+ <div style="font-size:1.2rem;font-weight:bold;color:${color};margin-top:8px;">${formattedValue}${unit}</div>
562
+ </div>
563
+ `;
564
+ case 'SPEEDOMETER':
565
+ const speedPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100);
566
+ const angle = (speedPercentage / 100) * 180 - 90; // -90도에서 90도까지
567
+ return html `
568
+ <div style="text-align:center;margin:16px 0;">
569
+ <div
570
+ style="width:120px;height:60px;border-radius:60px 60px 0 0;background:conic-gradient(${color} -90deg ${angle}deg, #e0e0e0 ${angle}deg 90deg);margin:0 auto;position:relative;"
571
+ >
572
+ <div
573
+ style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);font-size:1.2rem;font-weight:bold;color:${color};"
574
+ >
575
+ ${formattedValue}${unit}
576
+ </div>
577
+ </div>
578
+ </div>
579
+ `;
580
+ case 'BULLET':
581
+ const bulletPercentage = Math.min(((kpiValue - minValue) / (maxValue - minValue)) * 100, 100);
582
+ const targetPercentage = Math.min(((targetValue - minValue) / (maxValue - minValue)) * 100, 100);
583
+ return html `
584
+ <div style="margin:16px 0;">
585
+ <div style="background:#e0e0e0;height:30px;border-radius:15px;overflow:hidden;position:relative;">
586
+ <div style="background:${color};height:100%;width:${bulletPercentage}%;transition:width 0.3s;"></div>
587
+ <div style="position:absolute;top:0;left:${targetPercentage}%;width:2px;height:100%;background:#333;"></div>
588
+ </div>
589
+ <div style="text-align:center;margin-top:8px;font-weight:bold;color:${color};font-size:1.2rem;">
590
+ ${formattedValue}${unit}
591
+ </div>
592
+ </div>
593
+ `;
594
+ case 'TEXT':
595
+ return html `
596
+ <div style="text-align:center;margin:16px 0;">
597
+ <div style="font-size:1.5rem;font-weight:bold;color:${color};">${formattedValue}${unit}</div>
598
+ </div>
599
+ `;
600
+ case 'BADGE':
601
+ return html `
602
+ <div style="text-align:center;margin:16px 0;">
603
+ <span
604
+ style="display:inline-block;padding:8px 16px;background:${color};color:white;border-radius:20px;font-size:1.2rem;font-weight:bold;"
605
+ >${formattedValue}${unit}</span
606
+ >
607
+ </div>
608
+ `;
609
+ default: // CARD, BAR, LINE, PIE, DONUT, RADAR, TABLE
610
+ return html ` <div class="kpi-value">${formattedValue}${unit}</div> `;
611
+ }
612
+ }
613
+ get context() {
614
+ return {
615
+ title: 'KPI 대시보드',
616
+ description: '조직 KPI 실적, 등급, 이력, 시각화 등 KPI 전용 대시보드'
617
+ };
618
+ }
619
+ render() {
620
+ var _a, _b, _c, _d;
621
+ if (this.loading)
622
+ return nothing;
623
+ if (this.error)
624
+ return html `<div>${this.error}</div>`;
625
+ return html `
626
+ <div class="dashboard-root">
627
+ <!-- 샘플 차트 섹션 -->
628
+ <div class="sample-charts-section">
629
+ <div class="sample-chart-card">
630
+ <div class="sample-chart-title">그룹별 KPI 비교 (Radar)</div>
631
+ <div class="sample-chart-container">
632
+ <sv-kpi-radar-chart
633
+ .data=${(_a = this.sampleRadarData) !== null && _a !== void 0 ? _a : []}
634
+ .categories=${(_b = this.sampleRadarCategories) !== null && _b !== void 0 ? _b : []}
635
+ .currentGroup=${this.sampleCurrentGroup}
636
+ ></sv-kpi-radar-chart>
637
+ </div>
638
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
639
+ ※ <b>Radar 차트</b>는 각 카테고리별로 최소(Min), 최대(Max), 평균(Avg), 그리고 이 프로젝트의 값을 한눈에 비교할 수
640
+ 있도록 시각화합니다.<br />
641
+ <b>진한 파란색</b> 다각형이 이 프로젝트의 성과이며, 회색 다각형은 기준값(최소/최대/평균)입니다.
642
+ </div>
643
+ </div>
644
+ <div class="sample-chart-card">
645
+ <div class="sample-chart-title">그룹별 분포 (Boxplot)</div>
646
+ <div class="sample-chart-container">
647
+ <sv-kpi-boxplot-chart
648
+ .data=${(_c = this.sampleBoxplotData) !== null && _c !== void 0 ? _c : []}
649
+ .groups=${(_d = this.sampleBoxplotGroups) !== null && _d !== void 0 ? _d : []}
650
+ .currentGroup=${this.sampleCurrentGroup}
651
+ ></sv-kpi-boxplot-chart>
652
+ </div>
653
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
654
+ ※ <b>Boxplot(박스플롯)</b>은 각 카테고리별로 값의 분포(최소, 1사분위, 중앙값, 3사분위, 최대, 평균, 이상치 등)를
655
+ 보여줍니다.<br />
656
+ <b>박스</b>는 중앙 50% 구간, <b>수염</b>은 전체 범위, <b>굵은 검정색 가로선</b>은 중앙값(메디안), <b>주황색 원</b>은
657
+ 평균값(Mean)을 의미합니다.<br />
658
+ <b>이 프로젝트의 값</b>은 <b>진한 오렌지색 원</b>으로 별도 강조되어 표시되며, 중앙값/평균과 다를 수 있습니다.<br />
659
+ </div>
660
+ </div>
661
+ </div>
662
+
663
+ <!-- 실제 KPI 통계 차트 섹션 -->
664
+ <div class="sample-charts-section">
665
+ <!-- 통계 필터 및 요약 -->
666
+ <div style="margin-bottom: 24px; background: #f8f9fa; padding: 16px; border-radius: 8px;">
667
+ <div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
668
+ <div style="display: flex; align-items: center; gap: 8px;">
669
+ <label style="font-weight: 500; color: #333;">기간:</label>
670
+ <span style="padding: 6px 12px; background: #e9ecef; border-radius: 4px; color: #495057;"> MONTH (월별) </span>
671
+ </div>
672
+ <div style="display: flex; align-items: center; gap: 8px;">
673
+ <label style="font-weight: 500; color: #333;">날짜:</label>
674
+ <select
675
+ .value=${this.selectedValueDate}
676
+ @change=${this._onValueDateChange}
677
+ style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: white;"
678
+ >
679
+ ${this.availableValueDates.map(date => html `<option value="${date}">${date}${date === this.currentMonth ? ' (현재)' : ''}</option>`)}
680
+ </select>
681
+ </div>
682
+ ${this.statisticsSummary
683
+ ? html `
684
+ <div style="display: flex; gap: 16px; margin-left: auto;">
685
+ <span style="color: #666; font-size: 0.9em;"> <b>KPI:</b> ${this.statisticsSummary.totalKpis}개 </span>
686
+ <span style="color: #666; font-size: 0.9em;">
687
+ <b>카테고리:</b> ${this.statisticsSummary.totalCategories}개
688
+ </span>
689
+ <span style="color: #666; font-size: 0.9em;"> <b>평균:</b> ${this.statisticsSummary.avgMean} </span>
690
+ <span style="color: #666; font-size: 0.9em;"> <b>중앙값:</b> ${this.statisticsSummary.avgMedian} </span>
691
+ <span style="color: #666; font-size: 0.9em;"> <b>표준편차:</b> ${this.statisticsSummary.avgStdDev} </span>
692
+ </div>
693
+ `
694
+ : nothing}
695
+ </div>
696
+ </div>
697
+
698
+ <div class="sample-chart-card">
699
+ <div class="sample-chart-title">실제 KPI 통계 비교 (Radar)</div>
700
+ <div class="sample-chart-container">
701
+ ${this.statisticsLoading
702
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
703
+ 통계 데이터 로딩 중...
704
+ </div>`
705
+ : this.realKpiRadarData.length > 0
706
+ ? html `<sv-kpi-radar-chart
707
+ .data=${this.realKpiRadarData}
708
+ .categories=${this.realKpiRadarCategories}
709
+ .currentGroup=${'평균'}
710
+ ></sv-kpi-radar-chart>`
711
+ : html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
712
+ 선택된 기간에 통계 데이터가 없습니다.
713
+ </div>`}
714
+ </div>
715
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
716
+ ※ <b>실제 KPI 통계 Radar 차트</b>는 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 평균, 중앙값, 표준편차를
717
+ 비교합니다.<br />
718
+ <b>평균</b>은 산술평균, <b>중앙값</b>은 50분위수, <b>표준편차</b>는 데이터 분산 정도를 나타냅니다.
719
+ </div>
720
+ </div>
721
+ <div class="sample-chart-card">
722
+ <div class="sample-chart-title">실제 KPI 통계 분포 (Boxplot)</div>
723
+ <div class="sample-chart-container">
724
+ ${this.statisticsLoading
725
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
726
+ 통계 데이터 로딩 중...
727
+ </div>`
728
+ : this.realKpiBoxplotData.length > 0
729
+ ? html `<sv-kpi-boxplot-chart
730
+ .data=${this.realKpiBoxplotData}
731
+ .groups=${this.realKpiBoxplotGroups}
732
+ .currentGroup=${'평균'}
733
+ ></sv-kpi-boxplot-chart>`
734
+ : html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
735
+ 선택된 기간에 통계 데이터가 없습니다.
736
+ </div>`}
737
+ </div>
738
+ <div style="margin-top:12px;color:#666;font-size:0.98em;">
739
+ ※ <b>실제 KPI 통계 Boxplot</b>은 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 통계값의 분포를 보여줍니다.<br />
740
+ 각 카테고리의 <b>평균값들</b>을 기준으로 분포를 계산하여, 카테고리 간 통계적 특성을 비교할 수 있습니다.
741
+ </div>
742
+ </div>
743
+ </div>
744
+
745
+ <!-- 3레벨 KPI 플로팅 섹션 -->
746
+ <div class="sample-charts-section">
747
+ <div style="margin-bottom: 24px;">
748
+ <h2 style="font-size: 1.5rem; font-weight: bold; color: #333; text-align: center; margin-bottom: 8px;">
749
+ 3레벨 KPI 분석
750
+ </h2>
751
+ <p style="text-align: center; color: #666; font-size: 0.95rem;">실제 KPIStatistic 데이터를 기반으로 한 다층적 분석</p>
752
+ </div>
753
+
754
+ <!-- 1레벨: 그룹 총 스코어 -->
755
+ <div style="margin-bottom: 32px;">
756
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">📊 1레벨: 그룹 총 스코어</h3>
757
+ <kpi-level1-card></kpi-level1-card>
758
+ </div>
759
+
760
+ <!-- 2레벨: 카테고리 비교 -->
761
+ <div style="margin-bottom: 32px;">
762
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
763
+ 📈 2레벨: 카테고리 비교 분석
764
+ </h3>
765
+ <kpi-level2-comparison></kpi-level2-comparison>
766
+ </div>
767
+
768
+ <!-- 3레벨: 개별 KPI 비교 -->
769
+ <div style="margin-bottom: 32px;">
770
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
771
+ 🔍 3레벨: 개별 KPI 상세 분석
772
+ </h3>
773
+ <kpi-level3-comparison></kpi-level3-comparison>
774
+ </div>
775
+ </div>
776
+ ${this.showHistoryModal
777
+ ? html `
778
+ <div
779
+ style="position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.25);z-index:1000;display:flex;align-items:center;justify-content:center;"
780
+ >
781
+ <div
782
+ style="background:#fff;padding:32px 40px;border-radius:16px;min-width:340px;max-width:90vw;max-height:80vh;overflow:auto;box-shadow:0 4px 24px rgba(0,0,0,0.15);"
783
+ >
784
+ <div style="font-size:1.2rem;font-weight:bold;margin-bottom:16px;">${this.modalKpiName} 변경 이력</div>
785
+ <ul style="padding:0;list-style:none;">
786
+ ${this.modalHistories.length === 0
787
+ ? html `<li style="color:#bbb;">이력이 없습니다.</li>`
788
+ : this.modalHistories.map(h => {
789
+ var _a, _b, _c, _d;
790
+ return html `<li style="margin-bottom:8px;">
791
+ v${h.version} (${(_b = (_a = h.updatedAt) === null || _a === void 0 ? void 0 : _a.slice(0, 10)) !== null && _b !== void 0 ? _b : ''} ${(_d = (_c = h.updater) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : ''})
792
+ </li>`;
793
+ })}
794
+ </ul>
795
+ <button style="margin-top:16px;padding:6px 18px;" @click=${() => this.closeHistoryModal()}>닫기</button>
796
+ </div>
797
+ </div>
798
+ `
799
+ : nothing}
800
+ ${this.categories.map(cat => html `
801
+ <div class="category-section">
802
+ <div class="category-title">${cat.name}</div>
803
+ <div class="kpi-cards">
804
+ ${(cat.kpis || []).map(kpi => {
805
+ var _a, _b, _c, _d, _e, _f;
806
+ return html `
807
+ <div class="kpi-card">
808
+ <div class="kpi-name">${kpi.name}</div>
809
+ ${this._renderKpiValue(kpi)}
810
+ <div class="kpi-target">목표: ${(_a = kpi.targetValue) !== null && _a !== void 0 ? _a : '-'}${(_b = kpi.unit) !== null && _b !== void 0 ? _b : ''}</div>
811
+ <!-- 등급 시각화 -->
812
+ <div style="margin-top:8px;">
813
+ <b>등급:</b>
814
+ ${(() => {
815
+ var _a, _b, _c, _d, _e;
816
+ const grades = kpi.grades || [];
817
+ const kpiValue = (_a = kpi.value) === null || _a === void 0 ? void 0 : _a.value;
818
+ if (grades.length > 5) {
819
+ // 5개를 넘으면 현재 값에 해당하는 grade만 표시
820
+ const currentGrade = grades.find(g => kpiValue >= g.minValue && kpiValue <= g.maxValue);
821
+ if (currentGrade) {
822
+ return html `<span
823
+ style="display:inline-block;margin-right:8px;padding:4px 12px;border-radius:12px;background:${(_b = currentGrade.color) !== null && _b !== void 0 ? _b : '#3a3ad6'};color:white;font-size:1em;font-weight:bold;box-shadow:0 2px 8px rgba(58,58,214,0.3);border:2px solid ${(_c = currentGrade.color) !== null && _c !== void 0 ? _c : '#3a3ad6'};"
824
+ >${currentGrade.name}(${currentGrade.minValue}~${currentGrade.maxValue}${(_d = kpi.unit) !== null && _d !== void 0 ? _d : ''},
825
+ ${(_e = currentGrade.score) !== null && _e !== void 0 ? _e : ''}점)</span
826
+ >`;
827
+ }
828
+ else {
829
+ return html `<span style="color:#bbb;font-size:0.95em;">등급 없음</span>`;
830
+ }
831
+ }
832
+ else {
833
+ // 5개 이하면 전체 리스트 표시하되 현재 등급은 강조
834
+ return grades.map(g => {
835
+ var _a, _b, _c, _d, _e;
836
+ const isCurrentGrade = kpiValue >= g.minValue && kpiValue <= g.maxValue;
837
+ return html `<span
838
+ style="display:inline-block;margin-right:8px;padding:${isCurrentGrade
839
+ ? '4px 12px'
840
+ : '2px 8px'};border-radius:${isCurrentGrade ? '12px' : '8px'};background:${isCurrentGrade
841
+ ? ((_a = g.color) !== null && _a !== void 0 ? _a : '#3a3ad6')
842
+ : ((_b = g.color) !== null && _b !== void 0 ? _b : '#eee')};color:${isCurrentGrade ? 'white' : '#222'};font-size:${isCurrentGrade
843
+ ? '1em'
844
+ : '0.95em'};font-weight:${isCurrentGrade ? 'bold' : 'normal'};box-shadow:${isCurrentGrade
845
+ ? '0 2px 8px rgba(58,58,214,0.3)'
846
+ : 'none'};border:${isCurrentGrade ? `2px solid ${(_c = g.color) !== null && _c !== void 0 ? _c : '#3a3ad6'}` : 'none'};"
847
+ >${g.name}(${g.minValue}~${g.maxValue}${(_d = kpi.unit) !== null && _d !== void 0 ? _d : ''}, ${(_e = g.score) !== null && _e !== void 0 ? _e : ''}점)</span
848
+ >`;
849
+ });
850
+ }
851
+ })()}
852
+ </div>
853
+ <!-- 최근 변경 이력(1건만) -->
854
+ <div style="margin-top:8px;font-size:0.95em;color:#888;">
855
+ <b>최근 변경:</b>
856
+ ${kpi.histories && kpi.histories.length > 0
857
+ ? html `<span style="margin-right:8px;"
858
+ >v${kpi.histories[0].version} (${(_d = (_c = kpi.histories[0].updatedAt) === null || _c === void 0 ? void 0 : _c.slice(0, 10)) !== null && _d !== void 0 ? _d : ''}
859
+ ${(_f = (_e = kpi.histories[0].updater) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : ''})</span
860
+ >`
861
+ : html `<span style="color:#bbb">없음</span>`}
862
+ <button
863
+ style="margin-left:8px;font-size:0.95em;padding:2px 8px;"
864
+ @click=${() => this.openHistoryModal(kpi)}
865
+ >
866
+ 더보기
867
+ </button>
868
+ </div>
869
+ <!-- 경고/알림 -->
870
+ <div style="margin-top:8px;">
871
+ ${this.alerts.filter(a => { var _a; return ((_a = a.kpi) === null || _a === void 0 ? void 0 : _a.id) === kpi.id; }).length > 0
872
+ ? this.alerts
873
+ .filter(a => { var _a; return ((_a = a.kpi) === null || _a === void 0 ? void 0 : _a.id) === kpi.id; })
874
+ .map(a => html `<span
875
+ style="display:inline-block;margin-right:8px;padding:2px 8px;border-radius:8px;background:${a.level ===
876
+ 'critical'
877
+ ? '#ffb3b3'
878
+ : a.level === 'warning'
879
+ ? '#ffe082'
880
+ : '#e0e0e0'};color:#b71c1c;font-size:0.95em;"
881
+ >${a.message}</span
882
+ >`)
883
+ : html `<span style="color:#4caf50;font-size:0.95em;">이상/경고 없음</span>`}
884
+ </div>
885
+ </div>
886
+ `;
887
+ })}
888
+ </div>
889
+ </div>
890
+ `)}
891
+ </div>
892
+ `;
893
+ }
894
+ };
895
+ KpiDashboardPage.styles = [
896
+ ScrollbarStyles,
897
+ css `
898
+ :host {
899
+ display: flex;
900
+ flex-direction: column;
901
+ overflow-y: auto;
902
+ }
903
+ .dashboard-root {
904
+ flex: 1;
905
+ padding: 24px;
906
+ }
907
+ .sample-charts-section {
908
+ display: flex;
909
+ flex-direction: column;
910
+ gap: 40px;
911
+ margin-bottom: 48px;
912
+ align-items: flex-start;
913
+ }
914
+ .sample-chart-card {
915
+ background: #fff;
916
+ border-radius: 12px;
917
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
918
+ border: 1px solid #ececec;
919
+ padding: 24px 32px;
920
+ min-width: 340px;
921
+ flex: 1;
922
+ display: flex;
923
+ flex-direction: column;
924
+ align-items: stretch;
925
+ min-height: 500px;
926
+ }
927
+ .sample-chart-title {
928
+ font-size: 1.1rem;
929
+ font-weight: bold;
930
+ margin-bottom: 12px;
931
+ }
932
+ .sample-chart-container {
933
+ width: 100%;
934
+ height: 340px;
935
+ min-height: 340px;
936
+ }
937
+ .category-section {
938
+ margin-bottom: 40px;
939
+ }
940
+ .category-title {
941
+ font-size: 1.3rem;
942
+ font-weight: bold;
943
+ margin-bottom: 16px;
944
+ }
945
+ .kpi-cards {
946
+ display: flex;
947
+ gap: 24px;
948
+ flex-wrap: wrap;
949
+ }
950
+ .kpi-card {
951
+ background: #fff;
952
+ border-radius: 12px;
953
+ padding: 24px 32px;
954
+ min-width: 220px;
955
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
956
+ display: flex;
957
+ flex-direction: column;
958
+ align-items: flex-start;
959
+ margin-bottom: 8px;
960
+ border: 1px solid #ececec;
961
+ }
962
+ .kpi-name {
963
+ font-size: 1.1rem;
964
+ font-weight: 500;
965
+ margin-bottom: 8px;
966
+ }
967
+ .kpi-value {
968
+ font-size: 2.2rem;
969
+ font-weight: bold;
970
+ color: #3a3ad6;
971
+ margin-bottom: 4px;
972
+ }
973
+ .kpi-target {
974
+ font-size: 1rem;
975
+ color: #888;
976
+ }
977
+ `
978
+ ];
979
+ __decorate([
980
+ state(),
981
+ __metadata("design:type", Array)
982
+ ], KpiDashboardPage.prototype, "categories", void 0);
983
+ __decorate([
984
+ state(),
985
+ __metadata("design:type", Object)
986
+ ], KpiDashboardPage.prototype, "loading", void 0);
987
+ __decorate([
988
+ state(),
989
+ __metadata("design:type", Object)
990
+ ], KpiDashboardPage.prototype, "error", void 0);
991
+ __decorate([
992
+ state(),
993
+ __metadata("design:type", Array)
994
+ ], KpiDashboardPage.prototype, "alerts", void 0);
995
+ __decorate([
996
+ state(),
997
+ __metadata("design:type", Boolean)
998
+ ], KpiDashboardPage.prototype, "showHistoryModal", void 0);
999
+ __decorate([
1000
+ state(),
1001
+ __metadata("design:type", Array)
1002
+ ], KpiDashboardPage.prototype, "modalHistories", void 0);
1003
+ __decorate([
1004
+ state(),
1005
+ __metadata("design:type", String)
1006
+ ], KpiDashboardPage.prototype, "modalKpiName", void 0);
1007
+ __decorate([
1008
+ state(),
1009
+ __metadata("design:type", Array)
1010
+ ], KpiDashboardPage.prototype, "kpiStatistics", void 0);
1011
+ __decorate([
1012
+ state(),
1013
+ __metadata("design:type", Object)
1014
+ ], KpiDashboardPage.prototype, "statisticsLoading", void 0);
1015
+ __decorate([
1016
+ state(),
1017
+ __metadata("design:type", String)
1018
+ ], KpiDashboardPage.prototype, "selectedPeriodType", void 0);
1019
+ __decorate([
1020
+ state(),
1021
+ __metadata("design:type", String)
1022
+ ], KpiDashboardPage.prototype, "selectedValueDate", void 0);
1023
+ KpiDashboardPage = __decorate([
1024
+ customElement('kpi-dashboard')
1025
+ ], KpiDashboardPage);
1026
+ export { KpiDashboardPage };
1027
+ //# sourceMappingURL=kpi-dashboard.js.map