@dssp/dcsp 1.0.2 → 1.0.6

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 (161) hide show
  1. package/dist-client/bootstrap.js +13 -10
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/components/kpi-2d-lookup-chart.d.ts +50 -0
  4. package/dist-client/components/kpi-2d-lookup-chart.js +263 -0
  5. package/dist-client/components/kpi-2d-lookup-chart.js.map +1 -0
  6. package/dist-client/components/kpi-boxplot-chart.d.ts +31 -0
  7. package/dist-client/components/kpi-boxplot-chart.js +306 -0
  8. package/dist-client/components/kpi-boxplot-chart.js.map +1 -0
  9. package/dist-client/components/kpi-lookup-chart.d.ts +58 -0
  10. package/dist-client/components/kpi-lookup-chart.js +464 -0
  11. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  12. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  13. package/dist-client/components/kpi-mini-trend-chart.js +180 -0
  14. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  15. package/dist-client/components/kpi-radar-chart.d.ts +17 -0
  16. package/dist-client/components/kpi-radar-chart.js +259 -0
  17. package/dist-client/components/kpi-radar-chart.js.map +1 -0
  18. package/dist-client/components/kpi-single-boxplot-chart.d.ts +36 -0
  19. package/dist-client/components/kpi-single-boxplot-chart.js +417 -0
  20. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -0
  21. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  22. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  23. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  24. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  25. package/dist-client/components/kpi-trend-chart.js +241 -0
  26. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  27. package/dist-client/google-map/common-google-map.d.ts +35 -0
  28. package/dist-client/google-map/common-google-map.js +349 -0
  29. package/dist-client/google-map/common-google-map.js.map +1 -0
  30. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  31. package/dist-client/google-map/google-map-loader.js +23 -0
  32. package/dist-client/google-map/google-map-loader.js.map +1 -0
  33. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +47 -0
  34. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +393 -0
  35. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -0
  36. package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +18 -0
  37. package/dist-client/pages/kpi-admin/kpi-system-guide.js +696 -0
  38. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +46 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +945 -0
  41. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  42. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +34 -0
  43. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +238 -0
  44. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  45. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +37 -0
  46. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +689 -0
  47. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +57 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +866 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  51. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +69 -0
  52. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +790 -0
  53. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -0
  54. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +22 -0
  55. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +344 -0
  56. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -0
  57. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +21 -0
  58. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +335 -0
  59. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -0
  60. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +51 -0
  61. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +693 -0
  62. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -0
  63. package/dist-client/pages/sv-project-complete.d.ts +31 -0
  64. package/dist-client/pages/sv-project-complete.js +364 -0
  65. package/dist-client/pages/sv-project-complete.js.map +1 -0
  66. package/dist-client/pages/sv-project-detail.d.ts +73 -0
  67. package/dist-client/pages/sv-project-detail.js +1387 -0
  68. package/dist-client/pages/sv-project-detail.js.map +1 -0
  69. package/dist-client/route.d.ts +1 -1
  70. package/dist-client/route.js +15 -0
  71. package/dist-client/route.js.map +1 -1
  72. package/dist-client/shared/complete-api.d.ts +45 -0
  73. package/dist-client/shared/complete-api.js +274 -0
  74. package/dist-client/shared/complete-api.js.map +1 -0
  75. package/dist-client/shared/func.d.ts +2 -0
  76. package/dist-client/shared/func.js +22 -0
  77. package/dist-client/shared/func.js.map +1 -0
  78. package/dist-client/shared/integration-fetch.d.ts +35 -0
  79. package/dist-client/shared/integration-fetch.js +53 -0
  80. package/dist-client/shared/integration-fetch.js.map +1 -0
  81. package/dist-client/shared/kpi-project-sync.d.ts +16 -0
  82. package/dist-client/shared/kpi-project-sync.js +129 -0
  83. package/dist-client/shared/kpi-project-sync.js.map +1 -0
  84. package/dist-client/tsconfig.tsbuildinfo +1 -1
  85. package/dist-client/viewparts/menu-tools.d.ts +7 -0
  86. package/dist-client/viewparts/menu-tools.js +168 -21
  87. package/dist-client/viewparts/menu-tools.js.map +1 -1
  88. package/dist-server/index.d.ts +2 -0
  89. package/dist-server/index.js +5 -0
  90. package/dist-server/index.js.map +1 -1
  91. package/dist-server/middlewares/index.d.ts +7 -0
  92. package/dist-server/middlewares/index.js +100 -0
  93. package/dist-server/middlewares/index.js.map +1 -0
  94. package/dist-server/service/index.d.ts +4 -0
  95. package/dist-server/service/index.js +19 -0
  96. package/dist-server/service/index.js.map +1 -0
  97. package/dist-server/service/kpi-metric-value/index.d.ts +4 -0
  98. package/dist-server/service/kpi-metric-value/index.js +8 -0
  99. package/dist-server/service/kpi-metric-value/index.js.map +1 -0
  100. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +198 -0
  101. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +1067 -0
  102. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -0
  103. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.d.ts +15 -0
  104. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +72 -0
  105. package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -0
  106. package/dist-server/service/kpi-stat/index.d.ts +4 -0
  107. package/dist-server/service/kpi-stat/index.js +8 -0
  108. package/dist-server/service/kpi-stat/index.js.map +1 -0
  109. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +13 -0
  110. package/dist-server/service/kpi-stat/kpi-stat-query.js +751 -0
  111. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -0
  112. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +32 -0
  113. package/dist-server/service/kpi-stat/kpi-stat-types.js +126 -0
  114. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -0
  115. package/dist-server/service/kpi-value/index.d.ts +3 -0
  116. package/dist-server/service/kpi-value/index.js +7 -0
  117. package/dist-server/service/kpi-value/index.js.map +1 -0
  118. package/dist-server/service/kpi-value/kpi-value-query.d.ts +8 -0
  119. package/dist-server/service/kpi-value/kpi-value-query.js +90 -0
  120. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -0
  121. package/dist-server/tsconfig.tsbuildinfo +1 -1
  122. package/package.json +8 -7
  123. package/python/RUNNER_CONVERSION_GUIDE.md +81 -0
  124. package/python/doc_summary_engine_handoff/.env.example +21 -0
  125. package/python/doc_summary_engine_handoff/HANDOFF.md +266 -0
  126. package/python/doc_summary_engine_handoff/README.md +71 -0
  127. package/python/doc_summary_engine_handoff/__main__.py +159 -0
  128. package/python/doc_summary_engine_handoff/api.py +165 -0
  129. package/python/doc_summary_engine_handoff/extract.py +290 -0
  130. package/python/doc_summary_engine_handoff/requirements.txt +15 -0
  131. package/python/doc_summary_engine_handoff/summarize.py +247 -0
  132. package/python/inspection_ai_engine_handoff/.env.example +22 -0
  133. package/python/inspection_ai_engine_handoff/HANDOFF.md +541 -0
  134. package/python/inspection_ai_engine_handoff/README.md +83 -0
  135. package/python/inspection_ai_engine_handoff/__main__.py +169 -0
  136. package/python/inspection_ai_engine_handoff/activities.py +147 -0
  137. package/python/inspection_ai_engine_handoff/api.py +273 -0
  138. package/python/inspection_ai_engine_handoff/checklist.py +53 -0
  139. package/python/inspection_ai_engine_handoff/cv_handlers.py +46 -0
  140. package/python/inspection_ai_engine_handoff/cv_model_registry.py +252 -0
  141. package/python/inspection_ai_engine_handoff/cv_models/README.md +58 -0
  142. package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/handler.py +39 -0
  143. package/python/inspection_ai_engine_handoff/cv_models/_example_frame_alignment/model.json +22 -0
  144. package/python/inspection_ai_engine_handoff/engine.py +333 -0
  145. package/python/inspection_ai_engine_handoff/requirements.txt +12 -0
  146. package/python/inspection_ai_engine_handoff/router.py +200 -0
  147. package/python/inspection_ai_engine_handoff/shot_catalog.py +79 -0
  148. package/python/inspection_ai_engine_handoff/shot_planner.py +278 -0
  149. package/python/inspection_ai_engine_handoff/vlm.py +335 -0
  150. package/python/spec-matching-0.5.5/README.md +113 -0
  151. package/python/spec-matching-0.5.5/__main__.py +331 -0
  152. package/python/spec-matching-0.5.5/kcs.csv +6028 -0
  153. package/python/spec-matching-0.5.5/kcs_embeddings.meta.json +1 -0
  154. package/python/spec-matching-0.5.5/kcs_embeddings.npy +0 -0
  155. package/python/spec-matching-0.5.5/requirements.txt +3 -0
  156. package/python/spec-matching-0.5.5//342/230/2057 SF-TD4 /341/204/221/341/205/263/341/204/205/341/205/251/341/204/200/341/205/263/341/204/205/341/205/242/341/206/267/341/204/211/341/205/245/341/206/257/341/204/200/341/205/250/341/204/211/341/205/245 /341/204/213/341/205/243/341/206/274/341/204/211/341/205/265/341/206/250_V1.0.hwp +0 -0
  157. package/schema.graphql +1274 -22
  158. package/things-factory.config.js +7 -1
  159. package/translations/en.json +2 -0
  160. package/translations/ja.json +2 -0
  161. package/translations/ko.json +2 -0
@@ -0,0 +1,945 @@
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
+ // 부모로 전파 → selectedSectorType 동기화 → 지역 팝업도 같은 유형 필터 사용
535
+ this.dispatchEvent(new CustomEvent('sector-change', { detail: { sectorType: this.sectorType }, bubbles: true, composed: true }));
536
+ }}
537
+ >
538
+ <option value="">전체</option>
539
+ <option value="PUBLIC">공공</option>
540
+ <option value="PRIVATE">민간</option>
541
+ </select>
542
+ <span class="filter-label">용도</span>
543
+ <select
544
+ class="filter-select"
545
+ .value=${this.buildingUsage}
546
+ @change=${(e) => {
547
+ this.buildingUsage = e.target.value;
548
+ // 부모로 전파 → selectedBuildingUsage 동기화 → 지역 팝업도 같은 용도 필터 사용
549
+ this.dispatchEvent(new CustomEvent('usage-change', {
550
+ detail: { buildingUsage: this.buildingUsage },
551
+ bubbles: true,
552
+ composed: true
553
+ }));
554
+ }}
555
+ >
556
+ <option value="">전체</option>
557
+ <option value="RESIDENTIAL">주거</option>
558
+ <option value="NON_RESIDENTIAL">비주거</option>
559
+ </select>
560
+ </div>
561
+
562
+ <!-- 기간 필터 -->
563
+ <div class="filter-row">
564
+ <span class="filter-label">기간</span>
565
+ <select
566
+ class="filter-select"
567
+ ?disabled=${this.isAllPeriod}
568
+ @change=${(e) => {
569
+ const y = e.target.value;
570
+ const m = this.startYearMonth.split('-')[1] || '01';
571
+ this._setStartYearMonth(`${y}-${m}`);
572
+ }}
573
+ >
574
+ ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.startYearMonth.startsWith(String(y))}>${y}</option>`)}
575
+ </select>
576
+ <select
577
+ class="filter-select"
578
+ ?disabled=${this.isAllPeriod}
579
+ @change=${(e) => {
580
+ const y = this.startYearMonth.split('-')[0] || '2025';
581
+ const m = e.target.value.padStart(2, '0');
582
+ this._setStartYearMonth(`${y}-${m}`);
583
+ }}
584
+ >
585
+ ${[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>`)}
586
+ </select>
587
+ <span style="color:#6c757d;font-size:0.8rem;">~</span>
588
+ <select
589
+ class="filter-select"
590
+ ?disabled=${this.isAllPeriod}
591
+ @change=${(e) => {
592
+ const y = e.target.value;
593
+ const m = this.endYearMonth.split('-')[1] || '12';
594
+ this._setEndYearMonth(`${y}-${m}`);
595
+ }}
596
+ >
597
+ ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.endYearMonth.startsWith(String(y))}>${y}</option>`)}
598
+ </select>
599
+ <select
600
+ class="filter-select"
601
+ ?disabled=${this.isAllPeriod}
602
+ @change=${(e) => {
603
+ const y = this.endYearMonth.split('-')[0] || '2026';
604
+ const m = e.target.value.padStart(2, '0');
605
+ this._setEndYearMonth(`${y}-${m}`);
606
+ }}
607
+ >
608
+ ${[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>`)}
609
+ </select>
610
+ <label style="display:flex;align-items:center;gap:3px;cursor:pointer;">
611
+ <input type="checkbox" .checked=${this.isAllPeriod} @change=${this._onAllPeriodChange} style="cursor:pointer;" />
612
+ <span class="filter-label" style="min-width:auto;">전체</span>
613
+ </label>
614
+ </div>
615
+
616
+ <!-- 종합 성과 -->
617
+ <div class="chart-section">
618
+ <div class="sub-title">성과영역별 KPI</div>
619
+ <div class="chart-toggle">
620
+ <button
621
+ class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
622
+ @click=${() => this.onChartTypeChange('boxplot')}
623
+ >
624
+ 박스플롯
625
+ </button>
626
+ <button
627
+ class="toggle-button ${this.selectedChartType === 'radar' ? 'active' : ''}"
628
+ @click=${() => this.onChartTypeChange('radar')}
629
+ >
630
+ 레이더차트
631
+ </button>
632
+ </div>
633
+ <div class="chart-container">
634
+ ${this.chartData.length === 0
635
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:0.9rem;">
636
+ 선택한 기간에 데이터가 없습니다.
637
+ </div>`
638
+ : this.selectedChartType === 'boxplot'
639
+ ? html `
640
+ <sv-kpi-boxplot-chart
641
+ .data=${this.chartData}
642
+ .valueKey=${'value'}
643
+ .valueLabel=${'전국'}
644
+ .showStatus=${false}
645
+ ></sv-kpi-boxplot-chart>
646
+ `
647
+ : html `
648
+ <sv-kpi-radar-chart
649
+ .data=${this.chartData}
650
+ .categories=${this.chartCategories}
651
+ .valueKey=${'value'}
652
+ ></sv-kpi-radar-chart>
653
+ `}
654
+ </div>
655
+ </div>
656
+
657
+ <!-- 시도별 성과 -->
658
+ <div class="chart-section">
659
+ <div class="sub-title">시도별 종합 KPI</div>
660
+ <table class="performance-table">
661
+ <thead>
662
+ <tr>
663
+ <th>지역명</th>
664
+ <th>KPI</th>
665
+ <th>변동률(%)</th>
666
+ <th>성과 추이</th>
667
+ </tr>
668
+ </thead>
669
+ <tbody>
670
+ ${this.getSortedRegionData().map((item) => html `
671
+ <tr
672
+ style="cursor: pointer; transition: background-color 0.2s;"
673
+ @click=${() => this.onRegionClick(item.region)}
674
+ @mouseenter=${(e) => (e.target.style.backgroundColor = '#f8f9fa')}
675
+ @mouseleave=${(e) => (e.target.style.backgroundColor = '')}
676
+ >
677
+ <td>${item.region}</td>
678
+ <td>${item.kpi}</td>
679
+ <td>
680
+ <div class="change-rate ${this.getChangeRateClass(item.change)}">
681
+ ${this.getChangeIcon(item.change)}${Math.abs(item.change)}%
682
+ </div>
683
+ </td>
684
+ <td>
685
+ ${item.trendData && item.trendData.length > 0
686
+ ? html `
687
+ <sv-kpi-mini-trend-chart
688
+ .data=${item.trendData}
689
+ .width=${60}
690
+ .height=${30}
691
+ .lineColor=${'#2196f3'}
692
+ .strokeWidth=${1.5}
693
+ .showPoints=${true}
694
+ .pointRadius=${1.5}
695
+ ></sv-kpi-mini-trend-chart>
696
+ `
697
+ : html `<span style="color: #999;">-</span>`}
698
+ </td>
699
+ </tr>
700
+ `)}
701
+ </tbody>
702
+ </table>
703
+ <button class="download-button" @click=${this.downloadExcel}>📊 엑셀 다운로드</button>
704
+ </div>
705
+ </div>
706
+ `;
707
+ }
708
+ };
709
+ KpiLeftPanel.styles = [
710
+ ScrollbarStyles,
711
+ css `
712
+ :host {
713
+ display: block;
714
+ width: 400px;
715
+ background: #fff;
716
+ border-right: 1px solid #e0e0e0;
717
+ overflow: hidden;
718
+ display: flex;
719
+ flex-direction: column;
720
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
721
+ }
722
+ .panel-content {
723
+ padding: 20px;
724
+ overflow-y: auto;
725
+ flex: 1;
726
+ }
727
+ .panel-header {
728
+ display: flex;
729
+ justify-content: space-between;
730
+ align-items: center;
731
+ padding: 20px;
732
+ border-bottom: 1px solid #e0e0e0;
733
+ background: #fff;
734
+ height: 70px;
735
+ box-sizing: border-box;
736
+ }
737
+ .panel-title {
738
+ font-size: 1.3rem;
739
+ font-weight: bold;
740
+ color: #333;
741
+ margin: 0;
742
+ }
743
+ .panel-close {
744
+ width: 32px;
745
+ height: 32px;
746
+ border: none;
747
+ background: #fff;
748
+ border-radius: 50%;
749
+ cursor: pointer;
750
+ display: flex;
751
+ align-items: center;
752
+ justify-content: center;
753
+ font-size: 1.2rem;
754
+ color: #666;
755
+ transition: all 0.2s;
756
+ }
757
+ .panel-close:hover {
758
+ background: #e9ecef;
759
+ color: #333;
760
+ }
761
+ .sub-title {
762
+ font-size: 1rem;
763
+ font-weight: 600;
764
+ margin-bottom: 16px;
765
+ color: #495057;
766
+ }
767
+ .chart-section {
768
+ background: #f8f9fa;
769
+ border-radius: 8px;
770
+ padding: 16px;
771
+ margin-bottom: 20px;
772
+ }
773
+ .chart-toggle {
774
+ display: flex;
775
+ gap: 8px;
776
+ margin-bottom: 16px;
777
+ }
778
+ .toggle-button {
779
+ padding: 8px 16px;
780
+ border: 1px solid #ced4da;
781
+ background: #fff;
782
+ border-radius: 6px;
783
+ cursor: pointer;
784
+ font-size: 0.9rem;
785
+ transition: all 0.2s;
786
+ }
787
+ .toggle-button.active {
788
+ background: #667eea;
789
+ color: white;
790
+ border-color: #667eea;
791
+ }
792
+ .chart-container {
793
+ height: 300px;
794
+ display: flex;
795
+ align-items: center;
796
+ justify-content: center;
797
+ background: white;
798
+ border-radius: 6px;
799
+ border: 1px solid #e9ecef;
800
+ }
801
+ .performance-table {
802
+ width: 100%;
803
+ border-collapse: collapse;
804
+ margin-top: 16px;
805
+ }
806
+ .performance-table th,
807
+ .performance-table td {
808
+ padding: 12px 8px;
809
+ text-align: left;
810
+ border-bottom: 1px solid #e9ecef;
811
+ }
812
+ .performance-table th {
813
+ background: #f8f9fa;
814
+ font-weight: 600;
815
+ color: #495057;
816
+ }
817
+ .performance-table td {
818
+ color: #333;
819
+ }
820
+ .change-rate {
821
+ display: flex;
822
+ align-items: center;
823
+ gap: 4px;
824
+ }
825
+ .change-up {
826
+ color: #dc3545;
827
+ }
828
+ .change-down {
829
+ color: #2196f3;
830
+ }
831
+ .change-neutral {
832
+ color: #6c757d;
833
+ }
834
+ .trend-chart {
835
+ width: 60px;
836
+ height: 30px;
837
+ background: #f8f9fa;
838
+ border-radius: 4px;
839
+ display: flex;
840
+ align-items: center;
841
+ justify-content: center;
842
+ font-size: 0.8rem;
843
+ color: #666;
844
+ }
845
+ .download-button {
846
+ margin-top: 16px;
847
+ padding: 8px 16px;
848
+ background: #28a745;
849
+ color: white;
850
+ border: none;
851
+ border-radius: 6px;
852
+ cursor: pointer;
853
+ font-size: 0.9rem;
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 8px;
857
+ }
858
+ .download-button:hover {
859
+ background: #218838;
860
+ }
861
+ .filter-row {
862
+ display: flex;
863
+ align-items: center;
864
+ justify-content: space-between;
865
+ gap: 6px;
866
+ margin-bottom: 10px;
867
+ }
868
+ .filter-label {
869
+ font-size: 0.75rem;
870
+ font-weight: 600;
871
+ color: #495057;
872
+ white-space: nowrap;
873
+ }
874
+ .filter-select {
875
+ flex: 1;
876
+ min-width: 0;
877
+ padding: 4px 6px;
878
+ border: 1px solid #ced4da;
879
+ border-radius: 4px;
880
+ background: white;
881
+ font-size: 0.78rem;
882
+ }
883
+ .filter-select:focus {
884
+ outline: none;
885
+ border-color: #667eea;
886
+ }
887
+ `
888
+ ];
889
+ __decorate([
890
+ property({ type: String }),
891
+ __metadata("design:type", Object)
892
+ ], KpiLeftPanel.prototype, "selectedCategory", void 0);
893
+ __decorate([
894
+ property({ type: String }),
895
+ __metadata("design:type", Object)
896
+ ], KpiLeftPanel.prototype, "selectedChartType", void 0);
897
+ __decorate([
898
+ property({ type: Array }),
899
+ __metadata("design:type", Array)
900
+ ], KpiLeftPanel.prototype, "mapData", void 0);
901
+ __decorate([
902
+ property({ type: String }),
903
+ __metadata("design:type", Object)
904
+ ], KpiLeftPanel.prototype, "startYearMonth", void 0);
905
+ __decorate([
906
+ property({ type: String }),
907
+ __metadata("design:type", Object)
908
+ ], KpiLeftPanel.prototype, "endYearMonth", void 0);
909
+ __decorate([
910
+ property({ type: String }),
911
+ __metadata("design:type", Object)
912
+ ], KpiLeftPanel.prototype, "sectorType", void 0);
913
+ __decorate([
914
+ property({ type: String }),
915
+ __metadata("design:type", Object)
916
+ ], KpiLeftPanel.prototype, "buildingUsage", void 0);
917
+ __decorate([
918
+ state(),
919
+ __metadata("design:type", Object)
920
+ ], KpiLeftPanel.prototype, "isAllPeriod", void 0);
921
+ __decorate([
922
+ state(),
923
+ __metadata("design:type", Array)
924
+ ], KpiLeftPanel.prototype, "chartData", void 0);
925
+ __decorate([
926
+ state(),
927
+ __metadata("design:type", Array)
928
+ ], KpiLeftPanel.prototype, "chartCategories", void 0);
929
+ __decorate([
930
+ state(),
931
+ __metadata("design:type", Array)
932
+ ], KpiLeftPanel.prototype, "kpiYComprehensiveStats", void 0);
933
+ __decorate([
934
+ state(),
935
+ __metadata("design:type", Array)
936
+ ], KpiLeftPanel.prototype, "regionalKpiStats", void 0);
937
+ __decorate([
938
+ state(),
939
+ __metadata("design:type", Array)
940
+ ], KpiLeftPanel.prototype, "monthlyTrendData", void 0);
941
+ KpiLeftPanel = __decorate([
942
+ customElement('kpi-left-panel')
943
+ ], KpiLeftPanel);
944
+ export { KpiLeftPanel };
945
+ //# sourceMappingURL=kpi-left-panel.js.map