@dssp/dkpi 1.0.0-alpha.9 → 1.0.0-alpha.90

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 (261) hide show
  1. package/KPI-STATISTICS-SERVICE.md +233 -0
  2. package/_index.html +4 -9
  3. package/assets/favicon.ico +0 -0
  4. package/assets/helps/index.md +1 -1
  5. package/assets/images/project-image.png +0 -0
  6. package/assets/manifest/apple-1024.png +0 -0
  7. package/assets/manifest/apple-120.png +0 -0
  8. package/assets/manifest/apple-152.png +0 -0
  9. package/assets/manifest/apple-167.png +0 -0
  10. package/assets/manifest/apple-180.png +0 -0
  11. package/assets/manifest/apple-touch-icon.png +0 -0
  12. package/assets/manifest/badge-128x128.png +0 -0
  13. package/assets/manifest/chrome-splashscreen-icon-384x384.png +0 -0
  14. package/assets/manifest/chrome-touch-icon-192x192.png +0 -0
  15. package/assets/manifest/icon-128x128.png +0 -0
  16. package/assets/manifest/icon-192x192.png +0 -0
  17. package/assets/manifest/icon-512x512.png +0 -0
  18. package/assets/manifest/icon-72x72.png +0 -0
  19. package/assets/manifest/icon-96x96.png +0 -0
  20. package/assets/manifest/image-metaog.png +0 -0
  21. package/assets/manifest/maskable_icon.png +0 -0
  22. package/assets/manifest/ms-icon-144x144.png +0 -0
  23. package/assets/manifest/ms-touch-icon-144x144-precomposed.png +0 -0
  24. package/assets/manifest.json +2 -2
  25. package/assets/videos/intro.mp4 +0 -0
  26. package/config/config.development.js +2 -1
  27. package/config/config.production.js +2 -1
  28. package/dist-client/bootstrap.d.ts +1 -0
  29. package/dist-client/bootstrap.js +75 -4
  30. package/dist-client/bootstrap.js.map +1 -1
  31. package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
  32. package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
  33. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  34. package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
  35. package/dist-client/components/kpi-boxplot-chart.js +306 -0
  36. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  37. package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
  38. package/dist-client/components/kpi-lookup-chart.js +459 -0
  39. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  40. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  41. package/dist-client/components/kpi-mini-trend-chart.js +180 -0
  42. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  43. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  44. package/dist-client/components/kpi-radar-chart.js +259 -0
  45. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  46. package/dist-client/components/kpi-single-boxplot-chart.d.ts +36 -0
  47. package/dist-client/components/kpi-single-boxplot-chart.js +417 -0
  48. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  49. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  50. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  51. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  52. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  53. package/dist-client/components/kpi-trend-chart.js +241 -0
  54. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  55. package/dist-client/components/sv-pagenation-control.d.ts +18 -0
  56. package/dist-client/components/sv-pagenation-control.js +142 -0
  57. package/dist-client/components/sv-pagenation-control.js.map +1 -0
  58. package/dist-client/entries/auth/checkin.d.ts +38 -0
  59. package/dist-client/entries/auth/checkin.js +551 -0
  60. package/dist-client/entries/auth/checkin.js.map +1 -0
  61. package/dist-client/google-map/common-google-map.d.ts +35 -0
  62. package/dist-client/google-map/common-google-map.js +349 -0
  63. package/dist-client/google-map/common-google-map.js.map +1 -0
  64. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  65. package/dist-client/google-map/google-map-loader.js +23 -0
  66. package/dist-client/google-map/google-map-loader.js.map +1 -0
  67. package/dist-client/icons/menu-icons.d.ts +6 -0
  68. package/dist-client/icons/menu-icons.js +42 -0
  69. package/dist-client/icons/menu-icons.js.map +1 -1
  70. package/dist-client/pages/component/project-update-header.d.ts +1 -0
  71. package/dist-client/pages/component/project-update-header.js +127 -0
  72. package/dist-client/pages/component/project-update-header.js.map +1 -0
  73. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.d.ts +22 -0
  74. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js +57 -0
  75. package/dist-client/pages/kpi-admin/dssp-kpi-list-page.js.map +1 -0
  76. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
  77. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
  78. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  79. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.d.ts +20 -0
  80. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js +445 -0
  81. package/dist-client/pages/kpi-admin/kpi-grade-2d-editor.js.map +1 -0
  82. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  83. package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
  84. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  85. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +18 -0
  86. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +259 -0
  87. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  88. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +22 -0
  89. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +346 -0
  90. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  91. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +26 -0
  92. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +433 -0
  93. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  94. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  95. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  96. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  97. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
  98. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +937 -0
  99. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  100. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +33 -0
  101. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +231 -0
  102. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  103. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +35 -0
  104. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +632 -0
  105. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  106. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  107. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  108. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  109. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +52 -0
  110. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +842 -0
  111. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  112. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +63 -0
  113. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1097 -0
  114. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  115. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  116. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  117. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  118. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  119. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  120. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  121. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  122. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  123. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  124. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  125. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  126. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  127. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  128. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  129. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  130. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +57 -0
  131. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +719 -0
  132. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -0
  133. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.d.ts +23 -0
  134. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +76 -0
  135. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -0
  136. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +68 -0
  137. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +394 -0
  138. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -0
  139. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +12 -0
  140. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +174 -0
  141. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -0
  142. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +40 -0
  143. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +190 -0
  144. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -0
  145. package/dist-client/pages/kpi-value/kpi-value-importer.d.ts +23 -0
  146. package/dist-client/pages/kpi-value/kpi-value-importer.js +93 -0
  147. package/dist-client/pages/kpi-value/kpi-value-importer.js.map +1 -0
  148. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +71 -0
  149. package/dist-client/pages/kpi-value/kpi-value-list-page.js +454 -0
  150. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -0
  151. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +66 -0
  152. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +775 -0
  153. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  154. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +16 -0
  155. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +337 -0
  156. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  157. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +21 -0
  158. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +335 -0
  159. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  160. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +42 -0
  161. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +635 -0
  162. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  163. package/dist-client/pages/sv-project-complete.d.ts +25 -0
  164. package/dist-client/pages/sv-project-complete.js +259 -0
  165. package/dist-client/pages/sv-project-complete.js.map +1 -0
  166. package/dist-client/pages/sv-project-completed-list.d.ts +27 -0
  167. package/dist-client/pages/sv-project-completed-list.js +416 -0
  168. package/dist-client/pages/sv-project-completed-list.js.map +1 -0
  169. package/dist-client/pages/sv-project-detail.d.ts +70 -0
  170. package/dist-client/pages/sv-project-detail.js +1486 -0
  171. package/dist-client/pages/sv-project-detail.js.map +1 -0
  172. package/dist-client/pages/sv-project-list.d.ts +175 -0
  173. package/dist-client/pages/sv-project-list.js +581 -0
  174. package/dist-client/pages/sv-project-list.js.map +1 -0
  175. package/dist-client/pages/sv-project-update.d.ts +91 -0
  176. package/dist-client/pages/sv-project-update.js +1151 -0
  177. package/dist-client/pages/sv-project-update.js.map +1 -0
  178. package/dist-client/pages/sv-user-management.d.ts +1 -0
  179. package/dist-client/pages/sv-user-management.js +5 -0
  180. package/dist-client/pages/sv-user-management.js.map +1 -1
  181. package/dist-client/route.d.ts +1 -1
  182. package/dist-client/route.js +50 -2
  183. package/dist-client/route.js.map +1 -1
  184. package/dist-client/shared/complete-api.d.ts +37 -0
  185. package/dist-client/shared/complete-api.js +227 -0
  186. package/dist-client/shared/complete-api.js.map +1 -0
  187. package/dist-client/shared/func.d.ts +2 -0
  188. package/dist-client/shared/func.js +22 -0
  189. package/dist-client/shared/func.js.map +1 -0
  190. package/dist-client/shared/geo-group-mapping.d.ts +25 -0
  191. package/dist-client/shared/geo-group-mapping.js +189 -0
  192. package/dist-client/shared/geo-group-mapping.js.map +1 -0
  193. package/dist-client/shared/integration-fetch.d.ts +35 -0
  194. package/dist-client/shared/integration-fetch.js +53 -0
  195. package/dist-client/shared/integration-fetch.js.map +1 -0
  196. package/dist-client/themes/dark.css +24 -24
  197. package/dist-client/themes/light.css +23 -23
  198. package/dist-client/tsconfig.tsbuildinfo +1 -1
  199. package/dist-client/viewparts/menu-tools.d.ts +40 -5
  200. package/dist-client/viewparts/menu-tools.js +317 -52
  201. package/dist-client/viewparts/menu-tools.js.map +1 -1
  202. package/dist-server/index.d.ts +2 -0
  203. package/dist-server/index.js +5 -0
  204. package/dist-server/index.js.map +1 -1
  205. package/dist-server/migrations/index.d.ts +1 -0
  206. package/dist-server/migrations/index.js +12 -0
  207. package/dist-server/migrations/index.js.map +1 -0
  208. package/dist-server/scripts/calculate-kpi-scores.d.ts +10 -0
  209. package/dist-server/scripts/calculate-kpi-scores.js +333 -0
  210. package/dist-server/scripts/calculate-kpi-scores.js.map +1 -0
  211. package/dist-server/scripts/load-grade-data-migration.d.ts +14 -0
  212. package/dist-server/scripts/load-grade-data-migration.js +279 -0
  213. package/dist-server/scripts/load-grade-data-migration.js.map +1 -0
  214. package/dist-server/scripts/propagate-parent-kpi-values.d.ts +14 -0
  215. package/dist-server/scripts/propagate-parent-kpi-values.js +786 -0
  216. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -0
  217. package/dist-server/scripts/recalculate-by-project-name.d.ts +2 -0
  218. package/dist-server/scripts/recalculate-by-project-name.js +72 -0
  219. package/dist-server/scripts/recalculate-by-project-name.js.map +1 -0
  220. package/dist-server/service/index.d.ts +4 -0
  221. package/dist-server/service/index.js +20 -0
  222. package/dist-server/service/index.js.map +1 -0
  223. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  224. package/dist-server/service/kpi-metric-value/index.js +8 -0
  225. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  226. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +171 -0
  227. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +983 -0
  228. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  229. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +12 -0
  230. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +56 -0
  231. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  232. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  233. package/dist-server/service/kpi-stat/index.js +8 -0
  234. package/dist-server/service/kpi-stat/index.js.map +1 -0
  235. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
  236. package/dist-server/service/kpi-stat/kpi-stat-query.js +727 -0
  237. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  238. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  239. package/dist-server/service/kpi-stat/kpi-stat-types.js +180 -0
  240. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  241. package/dist-server/service/kpi-value/index.d.ts +3 -0
  242. package/dist-server/service/kpi-value/index.js +7 -0
  243. package/dist-server/service/kpi-value/index.js.map +1 -0
  244. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  245. package/dist-server/service/kpi-value/kpi-value-query.js +90 -0
  246. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  247. package/dist-server/tsconfig.tsbuildinfo +1 -1
  248. package/kpi-module-service-tests.md +1286 -0
  249. package/kpi-module-test-report.md +676 -0
  250. package/kpi-module-unit-test-detailed-report.md +925 -0
  251. package/kpi-module-unit-tests-detailed.md +1452 -0
  252. package/openapi/unstable.yaml +2 -2
  253. package/package.json +69 -55
  254. package/recalculate-batch.sh +64 -0
  255. package/recalculate-projects-range.sh +98 -0
  256. package/schema.graphql +3464 -458
  257. package/things-factory.config.js +12 -1
  258. package/translations/en.json +5 -1
  259. package/translations/ko.json +5 -1
  260. package/views/auth-page.html +4 -5
  261. package/views/public/home.html +5 -6
@@ -0,0 +1,937 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import gql from 'graphql-tag';
3
+ import { LitElement, html, css } from 'lit';
4
+ import { customElement, property, state } from 'lit/decorators.js';
5
+ import { ScrollbarStyles } from '@operato/styles';
6
+ import { client } from '@operato/graphql';
7
+ import '../../../components/kpi-radar-chart.js';
8
+ import '../../../components/kpi-boxplot-chart.js';
9
+ import '../../../components/kpi-mini-trend-chart.js';
10
+ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.selectedCategory = '전체 KPI';
14
+ this.selectedChartType = 'boxplot';
15
+ this.mapData = [];
16
+ this.startYearMonth = ''; // YYYY-MM 형식
17
+ this.endYearMonth = ''; // YYYY-MM 형식
18
+ this.sectorType = '';
19
+ this.buildingUsage = '';
20
+ this.isAllPeriod = false;
21
+ this.chartData = [];
22
+ this.chartCategories = [];
23
+ this.kpiYComprehensiveStats = [];
24
+ this.regionalKpiStats = [];
25
+ this.monthlyTrendData = [];
26
+ // 행정안전부 행정구역코드 순서
27
+ this.regionOrder = [
28
+ '서울특별시',
29
+ '부산광역시',
30
+ '대구광역시',
31
+ '인천광역시',
32
+ '광주광역시',
33
+ '대전광역시',
34
+ '울산광역시',
35
+ '세종특별자치시',
36
+ '경기도',
37
+ '강원도',
38
+ '충청북도',
39
+ '충청남도',
40
+ '전라북도',
41
+ '전라남도',
42
+ '경상북도',
43
+ '경상남도',
44
+ '제주특별자치도'
45
+ ];
46
+ }
47
+ connectedCallback() {
48
+ super.connectedCallback();
49
+ // 기간 초기값이 비어있으면 현재 월 기준 12개월로 설정
50
+ if (!this.startYearMonth || !this.endYearMonth) {
51
+ const now = new Date();
52
+ const end = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
53
+ const start = new Date(now);
54
+ start.setMonth(start.getMonth() - 11);
55
+ const startStr = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}`;
56
+ this.startYearMonth = startStr;
57
+ this.endYearMonth = end;
58
+ }
59
+ this.fetchKpiYComprehensiveStats();
60
+ this.fetchRegionalKpiStats();
61
+ this.fetchMonthlyTrendData();
62
+ }
63
+ updated(changedProperties) {
64
+ // selectedCategory, startYearMonth, endYearMonth가 변경되면 데이터 다시 가져오기
65
+ if (changedProperties.has('selectedCategory') ||
66
+ changedProperties.has('startYearMonth') ||
67
+ changedProperties.has('endYearMonth') ||
68
+ changedProperties.has('sectorType') ||
69
+ changedProperties.has('buildingUsage')) {
70
+ // 기간 정보가 설정되어 있을 때만 조회 (빈 문자열이면 전체 기간)
71
+ if (this.startYearMonth !== undefined && this.endYearMonth !== undefined) {
72
+ this.fetchKpiYComprehensiveStats();
73
+ this.fetchRegionalKpiStats();
74
+ this.fetchMonthlyTrendData();
75
+ }
76
+ }
77
+ }
78
+ async fetchKpiYComprehensiveStats() {
79
+ try {
80
+ const response = await client.query({
81
+ query: gql `
82
+ query GetKpiYValueComprehensiveStats(
83
+ $startYearMonth: String
84
+ $endYearMonth: String
85
+ $sectorType: String
86
+ $buildingUsage: String
87
+ ) {
88
+ totalKpiYValueComprehensiveStats(
89
+ startYearMonth: $startYearMonth
90
+ endYearMonth: $endYearMonth
91
+ sectorType: $sectorType
92
+ buildingUsage: $buildingUsage
93
+ ) {
94
+ kpiName
95
+ minVal
96
+ q1Val
97
+ medVal
98
+ q3Val
99
+ maxVal
100
+ avgVal
101
+ }
102
+ }
103
+ `,
104
+ variables: {
105
+ startYearMonth: this.startYearMonth,
106
+ endYearMonth: this.endYearMonth,
107
+ sectorType: this.sectorType || null,
108
+ buildingUsage: this.buildingUsage || null
109
+ }
110
+ });
111
+ this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
112
+ // 부모에게 전체 Y-level KPI 통계 전달
113
+ this.dispatchEvent(new CustomEvent('total-kpi-y-stats-loaded', {
114
+ detail: { data: this.kpiYComprehensiveStats },
115
+ bubbles: true,
116
+ composed: true
117
+ }));
118
+ this.generateChartData();
119
+ }
120
+ catch (error) {
121
+ console.error('Failed to fetch KPI Y comprehensive stats:', error);
122
+ this.kpiYComprehensiveStats = [];
123
+ this.generateChartData();
124
+ }
125
+ }
126
+ async fetchRegionalKpiStats() {
127
+ try {
128
+ // selectedCategory에 따라 kpiName 결정
129
+ const categoryToKpiName = {
130
+ '전체 KPI': null,
131
+ '일정 성과': '일정성과(Y1)',
132
+ '비용 성과': '비용성과(Y2)',
133
+ '품질 성과': '품질성과(Y3)',
134
+ '안전 성과': 'Y4. 안전성과',
135
+ '환경 성과': '환경성과(Y5)',
136
+ '생산성 성과': '생산성성과(Y6)'
137
+ };
138
+ const kpiName = categoryToKpiName[this.selectedCategory];
139
+ const response = await client.query({
140
+ query: gql `
141
+ query GetRegionalKpiStats(
142
+ $kpiName: String
143
+ $startYearMonth: String
144
+ $endYearMonth: String
145
+ $sectorType: String
146
+ $buildingUsage: String
147
+ ) {
148
+ kpiZValueComprehensiveStatsByGeoGroup(
149
+ kpiName: $kpiName
150
+ startYearMonth: $startYearMonth
151
+ endYearMonth: $endYearMonth
152
+ sectorType: $sectorType
153
+ buildingUsage: $buildingUsage
154
+ ) {
155
+ geoGroup
156
+ avgVal
157
+ minVal
158
+ maxVal
159
+ projectCount
160
+ }
161
+ }
162
+ `,
163
+ variables: {
164
+ kpiName,
165
+ startYearMonth: this.startYearMonth,
166
+ endYearMonth: this.endYearMonth,
167
+ sectorType: this.sectorType || null,
168
+ buildingUsage: this.buildingUsage || null
169
+ }
170
+ });
171
+ this.regionalKpiStats = response.data.kpiZValueComprehensiveStatsByGeoGroup || [];
172
+ // 부모에게 지역별 KPI 통계 전달
173
+ this.dispatchEvent(new CustomEvent('regional-kpi-stats-loaded', {
174
+ detail: { data: this.regionalKpiStats },
175
+ bubbles: true,
176
+ composed: true
177
+ }));
178
+ }
179
+ catch (error) {
180
+ console.error('Failed to fetch regional KPI stats:', error);
181
+ this.regionalKpiStats = [];
182
+ }
183
+ }
184
+ async fetchMonthlyTrendData() {
185
+ try {
186
+ // selectedCategory에 따라 kpiName 결정
187
+ const categoryToKpiName = {
188
+ '전체 KPI': null,
189
+ '일정 성과': '일정성과(Y1)',
190
+ '비용 성과': '비용성과(Y2)',
191
+ '품질 성과': '품질성과(Y3)',
192
+ '안전 성과': 'Y4. 안전성과',
193
+ '환경 성과': '환경성과(Y5)',
194
+ '생산성 성과': '생산성성과(Y6)'
195
+ };
196
+ const kpiName = categoryToKpiName[this.selectedCategory];
197
+ const response = await client.query({
198
+ query: gql `
199
+ query GetMonthlyTrendData(
200
+ $kpiName: String
201
+ $startYearMonth: String
202
+ $endYearMonth: String
203
+ $sectorType: String
204
+ $buildingUsage: String
205
+ ) {
206
+ kpiZValueMonthlyTrendByGeoGroup(
207
+ kpiName: $kpiName
208
+ startYearMonth: $startYearMonth
209
+ endYearMonth: $endYearMonth
210
+ sectorType: $sectorType
211
+ buildingUsage: $buildingUsage
212
+ ) {
213
+ geoGroup
214
+ yearMonth
215
+ avgVal
216
+ projectCount
217
+ }
218
+ }
219
+ `,
220
+ variables: {
221
+ kpiName,
222
+ startYearMonth: this.startYearMonth,
223
+ endYearMonth: this.endYearMonth,
224
+ sectorType: this.sectorType || null,
225
+ buildingUsage: this.buildingUsage || null
226
+ }
227
+ });
228
+ this.monthlyTrendData = response.data.kpiZValueMonthlyTrendByGeoGroup || [];
229
+ // 부모에게 월별 추이 데이터 전달
230
+ this.dispatchEvent(new CustomEvent('monthly-trend-data-loaded', {
231
+ detail: { data: this.monthlyTrendData },
232
+ bubbles: true,
233
+ composed: true
234
+ }));
235
+ }
236
+ catch (error) {
237
+ console.error('Failed to fetch monthly trend data:', error);
238
+ this.monthlyTrendData = [];
239
+ }
240
+ }
241
+ getRegionTrendData(region) {
242
+ // 해당 지역 월별 값 맵 (20배 스케일)
243
+ const byMonth = new Map();
244
+ this.monthlyTrendData
245
+ .filter(d => d.geoGroup === region)
246
+ .forEach(d => byMonth.set(d.yearMonth, d.avgVal * 100));
247
+ // 선택 기간 전체 월을 커버 — 데이터 없는 월은 null(0 아님). 기간 정보 없으면 데이터 월만.
248
+ const months = this.startYearMonth && this.endYearMonth
249
+ ? this._buildMonthRange(this.startYearMonth, this.endYearMonth)
250
+ : [...byMonth.keys()].sort();
251
+ return months.map(ym => (byMonth.has(ym) ? byMonth.get(ym) : null));
252
+ }
253
+ /** start~end(YYYY-MM) 사이 모든 월을 순서대로 반환 */
254
+ _buildMonthRange(start, end) {
255
+ const [sy, sm] = start.split('-').map(Number);
256
+ const [ey, em] = end.split('-').map(Number);
257
+ if (!sy || !sm || !ey || !em)
258
+ return [];
259
+ const out = [];
260
+ let y = sy;
261
+ let m = sm;
262
+ for (let i = 0; i < 240; i++) {
263
+ out.push(`${y}-${String(m).padStart(2, '0')}`);
264
+ if (y === ey && m === em)
265
+ break;
266
+ if (y > ey || (y === ey && m > em))
267
+ break;
268
+ m++;
269
+ if (m > 12) {
270
+ m = 1;
271
+ y++;
272
+ }
273
+ }
274
+ return out;
275
+ }
276
+ calculateChangeRate(region) {
277
+ // 해당 지역의 월별 데이터를 시간순으로 정렬
278
+ const regionData = this.monthlyTrendData
279
+ .filter(d => d.geoGroup === region)
280
+ .sort((a, b) => a.yearMonth.localeCompare(b.yearMonth));
281
+ if (regionData.length < 2)
282
+ return 0;
283
+ // 가장 오래된 달과 가장 최근 달의 값 비교
284
+ const oldestValue = regionData[0].avgVal;
285
+ const latestValue = regionData[regionData.length - 1].avgVal;
286
+ if (oldestValue === 0)
287
+ return 0;
288
+ // 변동율 계산: (최근값 - 과거값) / 과거값 * 100
289
+ return ((latestValue - oldestValue) / oldestValue) * 100;
290
+ }
291
+ generateChartData() {
292
+ // kpiName 표기가 'Y1. 일정성과' / '일정성과(Y1)' 등으로 환경마다 달라 전체 문자열 매칭은 깨짐.
293
+ // Y1~Y6 코드만 추출해 짧은 라벨로 매핑.
294
+ const yLabelByCode = {
295
+ Y1: '일정',
296
+ Y2: '비용',
297
+ Y3: '품질',
298
+ Y4: '안전',
299
+ Y5: '환경',
300
+ Y6: '생산성'
301
+ };
302
+ const toCategory = (kpiName) => { var _a, _b; return yLabelByCode[(_b = (_a = (kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''] || kpiName; };
303
+ if (this.selectedChartType === 'radar') {
304
+ // 레이더 차트용 데이터 생성 (전체 평균만 표시)
305
+ const data = [];
306
+ const categories = [];
307
+ this.kpiYComprehensiveStats.forEach(stat => {
308
+ const category = toCategory(stat.kpiName);
309
+ if (!categories.includes(category)) {
310
+ categories.push(category);
311
+ }
312
+ // 전체 평균 데이터만 표시 (비교 시리즈 없음) — 박스플롯과 동일 정밀도(Math.round 제거)
313
+ data.push({
314
+ org: '전체평균',
315
+ category,
316
+ value: stat.avgVal * 100
317
+ });
318
+ });
319
+ this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
320
+ this.chartData = data;
321
+ }
322
+ else {
323
+ // 박스플롯용 데이터 생성 (sv-project-detail.ts의 getYKpiBoxplotData 참조)
324
+ const data = [];
325
+ const categories = [];
326
+ this.kpiYComprehensiveStats.forEach(stat => {
327
+ const category = toCategory(stat.kpiName);
328
+ if (!categories.includes(category)) {
329
+ categories.push(category);
330
+ }
331
+ data.push({
332
+ org: category,
333
+ min: stat.minVal * 100,
334
+ max: stat.maxVal * 100,
335
+ q1: stat.q1Val * 100,
336
+ q3: stat.q3Val * 100,
337
+ median: stat.medVal * 100,
338
+ mean: stat.avgVal * 100,
339
+ value: stat.avgVal * 100
340
+ });
341
+ });
342
+ this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
343
+ this.chartData = data;
344
+ }
345
+ }
346
+ _onAllPeriodChange(e) {
347
+ this.isAllPeriod = e.target.checked;
348
+ if (this.isAllPeriod) {
349
+ // 전체: 시작 = 선택 가능한 가장 이른 월(2022-01), 종료 = 현재월 → 모든 데이터 포함하며 표시도 명확
350
+ // (빈 문자열로 두면 드롭다운이 2022-01~2022-01 로 잘못 보이던 문제 방지)
351
+ const now = new Date();
352
+ this.startYearMonth = '2022-01';
353
+ this.endYearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
354
+ }
355
+ this._emitPeriodChange();
356
+ }
357
+ /** 시작월 설정 — 시작 > 종료(역전)면 종료를 시작에 맞춰 보정. (YYYY-MM 문자열 비교 = 시간순) */
358
+ _setStartYearMonth(ym) {
359
+ this.startYearMonth = ym;
360
+ if (this.endYearMonth && this.startYearMonth > this.endYearMonth) {
361
+ this.endYearMonth = this.startYearMonth;
362
+ }
363
+ this._emitPeriodChange();
364
+ }
365
+ /** 종료월 설정 — 종료 < 시작(역전)이면 시작을 종료에 맞춰 보정. */
366
+ _setEndYearMonth(ym) {
367
+ this.endYearMonth = ym;
368
+ if (this.startYearMonth && this.endYearMonth < this.startYearMonth) {
369
+ this.startYearMonth = this.endYearMonth;
370
+ }
371
+ this._emitPeriodChange();
372
+ }
373
+ /** 기간 변경을 부모로 전파 → 지역 팝업 등 다른 패널이 동일 기간을 쓰도록 동기화 */
374
+ _emitPeriodChange() {
375
+ this.dispatchEvent(new CustomEvent('period-change', {
376
+ detail: { startYearMonth: this.startYearMonth, endYearMonth: this.endYearMonth, isAllPeriod: this.isAllPeriod },
377
+ bubbles: true,
378
+ composed: true
379
+ }));
380
+ }
381
+ onCategoryChange(event) {
382
+ const target = event.target;
383
+ this.selectedCategory = target.value;
384
+ // 카테고리 변경 시 지역별 통계와 월별 추이 데이터만 다시 가져오기
385
+ // 종합 성과(박스플롯/레이더)는 항상 전체 Y-level KPI를 표시
386
+ this.fetchRegionalKpiStats();
387
+ this.fetchMonthlyTrendData();
388
+ this.dispatchEvent(new CustomEvent('category-change', {
389
+ detail: { category: target.value },
390
+ bubbles: true,
391
+ composed: true
392
+ }));
393
+ }
394
+ onChartTypeChange(type) {
395
+ this.selectedChartType = type;
396
+ this.generateChartData();
397
+ }
398
+ onRegionClick(region) {
399
+ // 지도를 해당 광역시도로 포커스
400
+ this.dispatchEvent(new CustomEvent('focus-region', {
401
+ detail: { region },
402
+ bubbles: true,
403
+ composed: true
404
+ }));
405
+ // 팝업 열기
406
+ this.dispatchEvent(new CustomEvent('region-click', {
407
+ detail: { region },
408
+ bubbles: true,
409
+ composed: true
410
+ }));
411
+ }
412
+ downloadExcel() {
413
+ import('xlsx').then(XLSX => {
414
+ const filterLabel = [
415
+ this.selectedCategory,
416
+ this.sectorType === 'PUBLIC' ? '공공' : this.sectorType === 'PRIVATE' ? '민간' : '',
417
+ this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : ''
418
+ ]
419
+ .filter(Boolean)
420
+ .join('_');
421
+ const period = this.isAllPeriod ? '전체기간' : `${this.startYearMonth}~${this.endYearMonth}`;
422
+ const wb = XLSX.utils.book_new();
423
+ // Sheet 1: 검색 조건
424
+ const condData = [
425
+ { 항목: 'KPI 카테고리', 값: this.selectedCategory },
426
+ { 항목: '사업 유형', 값: this.sectorType === 'PUBLIC' ? '공공' : this.sectorType === 'PRIVATE' ? '민간' : '전체' },
427
+ {
428
+ 항목: '건물 용도',
429
+ 값: this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : '전체'
430
+ },
431
+ { 항목: '기간', 값: this.isAllPeriod ? '전체 기간' : `${this.startYearMonth} ~ ${this.endYearMonth}` },
432
+ { 항목: '다운로드 일시', 값: new Date().toLocaleString('ko-KR') }
433
+ ];
434
+ const wsCond = XLSX.utils.json_to_sheet(condData);
435
+ wsCond['!cols'] = [{ wch: 16 }, { wch: 30 }];
436
+ XLSX.utils.book_append_sheet(wb, wsCond, '검색 조건');
437
+ // Sheet 2: 종합 성과
438
+ const yData = this.kpiYComprehensiveStats.map((s) => ({
439
+ KPI: s.kpiName,
440
+ 최소: Number((s.minVal * 100).toFixed(2)),
441
+ Q1: Number((s.q1Val * 100).toFixed(2)),
442
+ 중앙값: Number((s.medVal * 100).toFixed(2)),
443
+ Q3: Number((s.q3Val * 100).toFixed(2)),
444
+ 최대: Number((s.maxVal * 100).toFixed(2)),
445
+ 평균: Number((s.avgVal * 100).toFixed(2))
446
+ }));
447
+ const ws1 = XLSX.utils.json_to_sheet(yData);
448
+ ws1['!cols'] = [{ wch: 20 }, { wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 10 }];
449
+ XLSX.utils.book_append_sheet(wb, ws1, '종합 성과');
450
+ // Sheet 2: 시도별 성과
451
+ const rData = this.getSortedRegionData().map((item) => {
452
+ var _a;
453
+ const stat = this.regionalKpiStats.find((s) => s.geoGroup === item.region);
454
+ return {
455
+ 지역: item.region,
456
+ 'KPI 점수': Number(item.kpi) || 0,
457
+ '변동률(%)': Number(Number(item.change).toFixed(2)),
458
+ 프로젝트수: (_a = stat === null || stat === void 0 ? void 0 : stat.projectCount) !== null && _a !== void 0 ? _a : 0
459
+ };
460
+ });
461
+ const ws2 = XLSX.utils.json_to_sheet(rData);
462
+ ws2['!cols'] = [{ wch: 16 }, { wch: 12 }, { wch: 12 }, { wch: 12 }];
463
+ XLSX.utils.book_append_sheet(wb, ws2, '시도별 성과');
464
+ // Sheet 3: 월별 추이
465
+ if (this.monthlyTrendData.length > 0) {
466
+ const mData = this.monthlyTrendData.map((d) => ({
467
+ 지역: d.geoGroup,
468
+ 기간: d.yearMonth,
469
+ 평균: Number((d.avgVal * 100).toFixed(2)),
470
+ 프로젝트수: d.projectCount
471
+ }));
472
+ const ws3 = XLSX.utils.json_to_sheet(mData);
473
+ ws3['!cols'] = [{ wch: 16 }, { wch: 12 }, { wch: 10 }, { wch: 12 }];
474
+ XLSX.utils.book_append_sheet(wb, ws3, '월별 추이');
475
+ }
476
+ XLSX.writeFile(wb, `KPI_대시보드_${filterLabel}_${period}.xlsx`);
477
+ });
478
+ }
479
+ // regionOrder에 따라 지역 데이터를 정렬
480
+ getSortedRegionData() {
481
+ // 실제 데이터를 기반으로 각 시도에 대한 데이터 생성
482
+ return this.regionOrder.map(regionName => {
483
+ const stat = this.regionalKpiStats.find(s => s.geoGroup === regionName);
484
+ const changeRate = this.calculateChangeRate(regionName);
485
+ return {
486
+ region: regionName,
487
+ kpi: stat ? (stat.avgVal * 100).toFixed(1) : '-',
488
+ change: changeRate.toFixed(2),
489
+ trendData: this.getRegionTrendData(regionName),
490
+ lat: 36.5,
491
+ lng: 127.5
492
+ };
493
+ });
494
+ }
495
+ getChangeRateClass(change) {
496
+ if (change > 0)
497
+ return 'change-up';
498
+ if (change < 0)
499
+ return 'change-down';
500
+ return 'change-neutral';
501
+ }
502
+ getChangeIcon(change) {
503
+ if (change > 0)
504
+ return '▲';
505
+ if (change < 0)
506
+ return '▼';
507
+ return ''; // 변동 없음(0)이면 화살표 미표시
508
+ }
509
+ render() {
510
+ return html `
511
+ <div class="panel-header">
512
+ <div class="panel-title">전국 KPI</div>
513
+ <button class="panel-close" style="visibility: hidden;">×</button>
514
+ </div>
515
+ <div class="panel-content">
516
+ <!-- 필터: KPI / 사업유형 / 건물용도 -->
517
+ <div class="filter-row">
518
+ <span class="filter-label">KPI</span>
519
+ <select class="filter-select" .value=${this.selectedCategory} @change=${this.onCategoryChange}>
520
+ <option value="전체 KPI">전체 KPI</option>
521
+ <option value="일정 성과">일정</option>
522
+ <option value="비용 성과">비용</option>
523
+ <option value="품질 성과">품질</option>
524
+ <option value="안전 성과">안전</option>
525
+ <option value="환경 성과">환경</option>
526
+ <option value="생산성 성과">생산성</option>
527
+ </select>
528
+ <span class="filter-label">유형</span>
529
+ <select
530
+ class="filter-select"
531
+ .value=${this.sectorType}
532
+ @change=${(e) => {
533
+ this.sectorType = e.target.value;
534
+ }}
535
+ >
536
+ <option value="">전체</option>
537
+ <option value="PUBLIC">공공</option>
538
+ <option value="PRIVATE">민간</option>
539
+ </select>
540
+ <span class="filter-label">용도</span>
541
+ <select
542
+ class="filter-select"
543
+ .value=${this.buildingUsage}
544
+ @change=${(e) => {
545
+ this.buildingUsage = e.target.value;
546
+ }}
547
+ >
548
+ <option value="">전체</option>
549
+ <option value="RESIDENTIAL">주거</option>
550
+ <option value="NON_RESIDENTIAL">비주거</option>
551
+ </select>
552
+ </div>
553
+
554
+ <!-- 기간 필터 -->
555
+ <div class="filter-row">
556
+ <span class="filter-label">기간</span>
557
+ <select
558
+ class="filter-select"
559
+ ?disabled=${this.isAllPeriod}
560
+ @change=${(e) => {
561
+ const y = e.target.value;
562
+ const m = this.startYearMonth.split('-')[1] || '01';
563
+ this._setStartYearMonth(`${y}-${m}`);
564
+ }}
565
+ >
566
+ ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.startYearMonth.startsWith(String(y))}>${y}</option>`)}
567
+ </select>
568
+ <select
569
+ class="filter-select"
570
+ ?disabled=${this.isAllPeriod}
571
+ @change=${(e) => {
572
+ const y = this.startYearMonth.split('-')[0] || '2025';
573
+ const m = e.target.value.padStart(2, '0');
574
+ this._setStartYearMonth(`${y}-${m}`);
575
+ }}
576
+ >
577
+ ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.startYearMonth.split('-')[1]) === m}>${m}월</option>`)}
578
+ </select>
579
+ <span style="color:#6c757d;font-size:0.8rem;">~</span>
580
+ <select
581
+ class="filter-select"
582
+ ?disabled=${this.isAllPeriod}
583
+ @change=${(e) => {
584
+ const y = e.target.value;
585
+ const m = this.endYearMonth.split('-')[1] || '12';
586
+ this._setEndYearMonth(`${y}-${m}`);
587
+ }}
588
+ >
589
+ ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.endYearMonth.startsWith(String(y))}>${y}</option>`)}
590
+ </select>
591
+ <select
592
+ class="filter-select"
593
+ ?disabled=${this.isAllPeriod}
594
+ @change=${(e) => {
595
+ const y = this.endYearMonth.split('-')[0] || '2026';
596
+ const m = e.target.value.padStart(2, '0');
597
+ this._setEndYearMonth(`${y}-${m}`);
598
+ }}
599
+ >
600
+ ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.endYearMonth.split('-')[1]) === m}>${m}월</option>`)}
601
+ </select>
602
+ <label style="display:flex;align-items:center;gap:3px;cursor:pointer;">
603
+ <input type="checkbox" .checked=${this.isAllPeriod} @change=${this._onAllPeriodChange} style="cursor:pointer;" />
604
+ <span class="filter-label" style="min-width:auto;">전체</span>
605
+ </label>
606
+ </div>
607
+
608
+ <!-- 종합 성과 -->
609
+ <div class="chart-section">
610
+ <div class="sub-title">성과영역별 KPI</div>
611
+ <div class="chart-toggle">
612
+ <button
613
+ class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
614
+ @click=${() => this.onChartTypeChange('boxplot')}
615
+ >
616
+ 박스플롯
617
+ </button>
618
+ <button
619
+ class="toggle-button ${this.selectedChartType === 'radar' ? 'active' : ''}"
620
+ @click=${() => this.onChartTypeChange('radar')}
621
+ >
622
+ 레이더차트
623
+ </button>
624
+ </div>
625
+ <div class="chart-container">
626
+ ${this.chartData.length === 0
627
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:0.9rem;">
628
+ 선택한 기간에 데이터가 없습니다.
629
+ </div>`
630
+ : this.selectedChartType === 'boxplot'
631
+ ? html `
632
+ <sv-kpi-boxplot-chart
633
+ .data=${this.chartData}
634
+ .valueKey=${'value'}
635
+ .valueLabel=${'전국'}
636
+ .showStatus=${false}
637
+ ></sv-kpi-boxplot-chart>
638
+ `
639
+ : html `
640
+ <sv-kpi-radar-chart
641
+ .data=${this.chartData}
642
+ .categories=${this.chartCategories}
643
+ .valueKey=${'value'}
644
+ ></sv-kpi-radar-chart>
645
+ `}
646
+ </div>
647
+ </div>
648
+
649
+ <!-- 시도별 성과 -->
650
+ <div class="chart-section">
651
+ <div class="sub-title">시도별 종합 KPI</div>
652
+ <table class="performance-table">
653
+ <thead>
654
+ <tr>
655
+ <th>지역명</th>
656
+ <th>KPI</th>
657
+ <th>변동률(%)</th>
658
+ <th>성과 추이</th>
659
+ </tr>
660
+ </thead>
661
+ <tbody>
662
+ ${this.getSortedRegionData().map((item) => html `
663
+ <tr
664
+ style="cursor: pointer; transition: background-color 0.2s;"
665
+ @click=${() => this.onRegionClick(item.region)}
666
+ @mouseenter=${(e) => (e.target.style.backgroundColor = '#f8f9fa')}
667
+ @mouseleave=${(e) => (e.target.style.backgroundColor = '')}
668
+ >
669
+ <td>${item.region}</td>
670
+ <td>${item.kpi}</td>
671
+ <td>
672
+ <div class="change-rate ${this.getChangeRateClass(item.change)}">
673
+ ${this.getChangeIcon(item.change)}${Math.abs(item.change)}%
674
+ </div>
675
+ </td>
676
+ <td>
677
+ ${item.trendData && item.trendData.length > 0
678
+ ? html `
679
+ <sv-kpi-mini-trend-chart
680
+ .data=${item.trendData}
681
+ .width=${60}
682
+ .height=${30}
683
+ .lineColor=${'#2196f3'}
684
+ .strokeWidth=${1.5}
685
+ .showPoints=${true}
686
+ .pointRadius=${1.5}
687
+ ></sv-kpi-mini-trend-chart>
688
+ `
689
+ : html `<span style="color: #999;">-</span>`}
690
+ </td>
691
+ </tr>
692
+ `)}
693
+ </tbody>
694
+ </table>
695
+ <button class="download-button" @click=${this.downloadExcel}>📊 엑셀 다운로드</button>
696
+ </div>
697
+ </div>
698
+ `;
699
+ }
700
+ };
701
+ KpiLeftPanel.styles = [
702
+ ScrollbarStyles,
703
+ css `
704
+ :host {
705
+ display: block;
706
+ width: 400px;
707
+ background: #fff;
708
+ border-right: 1px solid #e0e0e0;
709
+ overflow: hidden;
710
+ display: flex;
711
+ flex-direction: column;
712
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
713
+ }
714
+ .panel-content {
715
+ padding: 20px;
716
+ overflow-y: auto;
717
+ flex: 1;
718
+ }
719
+ .panel-header {
720
+ display: flex;
721
+ justify-content: space-between;
722
+ align-items: center;
723
+ padding: 20px;
724
+ border-bottom: 1px solid #e0e0e0;
725
+ background: #fff;
726
+ height: 70px;
727
+ box-sizing: border-box;
728
+ }
729
+ .panel-title {
730
+ font-size: 1.3rem;
731
+ font-weight: bold;
732
+ color: #333;
733
+ margin: 0;
734
+ }
735
+ .panel-close {
736
+ width: 32px;
737
+ height: 32px;
738
+ border: none;
739
+ background: #fff;
740
+ border-radius: 50%;
741
+ cursor: pointer;
742
+ display: flex;
743
+ align-items: center;
744
+ justify-content: center;
745
+ font-size: 1.2rem;
746
+ color: #666;
747
+ transition: all 0.2s;
748
+ }
749
+ .panel-close:hover {
750
+ background: #e9ecef;
751
+ color: #333;
752
+ }
753
+ .sub-title {
754
+ font-size: 1rem;
755
+ font-weight: 600;
756
+ margin-bottom: 16px;
757
+ color: #495057;
758
+ }
759
+ .chart-section {
760
+ background: #f8f9fa;
761
+ border-radius: 8px;
762
+ padding: 16px;
763
+ margin-bottom: 20px;
764
+ }
765
+ .chart-toggle {
766
+ display: flex;
767
+ gap: 8px;
768
+ margin-bottom: 16px;
769
+ }
770
+ .toggle-button {
771
+ padding: 8px 16px;
772
+ border: 1px solid #ced4da;
773
+ background: #fff;
774
+ border-radius: 6px;
775
+ cursor: pointer;
776
+ font-size: 0.9rem;
777
+ transition: all 0.2s;
778
+ }
779
+ .toggle-button.active {
780
+ background: #667eea;
781
+ color: white;
782
+ border-color: #667eea;
783
+ }
784
+ .chart-container {
785
+ height: 300px;
786
+ display: flex;
787
+ align-items: center;
788
+ justify-content: center;
789
+ background: white;
790
+ border-radius: 6px;
791
+ border: 1px solid #e9ecef;
792
+ }
793
+ .performance-table {
794
+ width: 100%;
795
+ border-collapse: collapse;
796
+ margin-top: 16px;
797
+ }
798
+ .performance-table th,
799
+ .performance-table td {
800
+ padding: 12px 8px;
801
+ text-align: left;
802
+ border-bottom: 1px solid #e9ecef;
803
+ }
804
+ .performance-table th {
805
+ background: #f8f9fa;
806
+ font-weight: 600;
807
+ color: #495057;
808
+ }
809
+ .performance-table td {
810
+ color: #333;
811
+ }
812
+ .change-rate {
813
+ display: flex;
814
+ align-items: center;
815
+ gap: 4px;
816
+ }
817
+ .change-up {
818
+ color: #dc3545;
819
+ }
820
+ .change-down {
821
+ color: #2196f3;
822
+ }
823
+ .change-neutral {
824
+ color: #6c757d;
825
+ }
826
+ .trend-chart {
827
+ width: 60px;
828
+ height: 30px;
829
+ background: #f8f9fa;
830
+ border-radius: 4px;
831
+ display: flex;
832
+ align-items: center;
833
+ justify-content: center;
834
+ font-size: 0.8rem;
835
+ color: #666;
836
+ }
837
+ .download-button {
838
+ margin-top: 16px;
839
+ padding: 8px 16px;
840
+ background: #28a745;
841
+ color: white;
842
+ border: none;
843
+ border-radius: 6px;
844
+ cursor: pointer;
845
+ font-size: 0.9rem;
846
+ display: flex;
847
+ align-items: center;
848
+ gap: 8px;
849
+ }
850
+ .download-button:hover {
851
+ background: #218838;
852
+ }
853
+ .filter-row {
854
+ display: flex;
855
+ align-items: center;
856
+ justify-content: space-between;
857
+ gap: 6px;
858
+ margin-bottom: 10px;
859
+ }
860
+ .filter-label {
861
+ font-size: 0.75rem;
862
+ font-weight: 600;
863
+ color: #495057;
864
+ white-space: nowrap;
865
+ }
866
+ .filter-select {
867
+ flex: 1;
868
+ min-width: 0;
869
+ padding: 4px 6px;
870
+ border: 1px solid #ced4da;
871
+ border-radius: 4px;
872
+ background: white;
873
+ font-size: 0.78rem;
874
+ }
875
+ .filter-select:focus {
876
+ outline: none;
877
+ border-color: #667eea;
878
+ }
879
+ `
880
+ ];
881
+ __decorate([
882
+ property({ type: String }),
883
+ __metadata("design:type", Object)
884
+ ], KpiLeftPanel.prototype, "selectedCategory", void 0);
885
+ __decorate([
886
+ property({ type: String }),
887
+ __metadata("design:type", Object)
888
+ ], KpiLeftPanel.prototype, "selectedChartType", void 0);
889
+ __decorate([
890
+ property({ type: Array }),
891
+ __metadata("design:type", Array)
892
+ ], KpiLeftPanel.prototype, "mapData", void 0);
893
+ __decorate([
894
+ property({ type: String }),
895
+ __metadata("design:type", Object)
896
+ ], KpiLeftPanel.prototype, "startYearMonth", void 0);
897
+ __decorate([
898
+ property({ type: String }),
899
+ __metadata("design:type", Object)
900
+ ], KpiLeftPanel.prototype, "endYearMonth", void 0);
901
+ __decorate([
902
+ property({ type: String }),
903
+ __metadata("design:type", Object)
904
+ ], KpiLeftPanel.prototype, "sectorType", void 0);
905
+ __decorate([
906
+ property({ type: String }),
907
+ __metadata("design:type", Object)
908
+ ], KpiLeftPanel.prototype, "buildingUsage", void 0);
909
+ __decorate([
910
+ state(),
911
+ __metadata("design:type", Object)
912
+ ], KpiLeftPanel.prototype, "isAllPeriod", void 0);
913
+ __decorate([
914
+ state(),
915
+ __metadata("design:type", Array)
916
+ ], KpiLeftPanel.prototype, "chartData", void 0);
917
+ __decorate([
918
+ state(),
919
+ __metadata("design:type", Array)
920
+ ], KpiLeftPanel.prototype, "chartCategories", void 0);
921
+ __decorate([
922
+ state(),
923
+ __metadata("design:type", Array)
924
+ ], KpiLeftPanel.prototype, "kpiYComprehensiveStats", void 0);
925
+ __decorate([
926
+ state(),
927
+ __metadata("design:type", Array)
928
+ ], KpiLeftPanel.prototype, "regionalKpiStats", void 0);
929
+ __decorate([
930
+ state(),
931
+ __metadata("design:type", Array)
932
+ ], KpiLeftPanel.prototype, "monthlyTrendData", void 0);
933
+ KpiLeftPanel = __decorate([
934
+ customElement('kpi-left-panel')
935
+ ], KpiLeftPanel);
936
+ export { KpiLeftPanel };
937
+ //# sourceMappingURL=kpi-left-panel.js.map