@dssp/dkpi 1.0.0-alpha.7 → 1.0.0-alpha.71

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