@dssp/dkpi 1.0.0-alpha.57 → 1.0.0-alpha.59

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 (94) hide show
  1. package/assets/images/project-image.png +0 -0
  2. package/dist-client/components/kpi-boxplot-chart.d.ts +1 -1
  3. package/dist-client/components/kpi-boxplot-chart.js +29 -65
  4. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  5. package/dist-client/components/kpi-lookup-chart.d.ts +29 -0
  6. package/dist-client/components/kpi-lookup-chart.js +434 -0
  7. package/dist-client/components/kpi-lookup-chart.js.map +1 -0
  8. package/dist-client/components/kpi-mini-trend-chart.d.ts +14 -0
  9. package/dist-client/components/kpi-mini-trend-chart.js +148 -0
  10. package/dist-client/components/kpi-mini-trend-chart.js.map +1 -0
  11. package/dist-client/components/kpi-radar-chart.d.ts +1 -1
  12. package/dist-client/components/kpi-radar-chart.js +73 -55
  13. package/dist-client/components/kpi-radar-chart.js.map +1 -1
  14. package/dist-client/components/kpi-trend-chart.d.ts +25 -0
  15. package/dist-client/components/kpi-trend-chart.js +220 -0
  16. package/dist-client/components/kpi-trend-chart.js.map +1 -0
  17. package/dist-client/google-map/common-google-map.d.ts +35 -0
  18. package/dist-client/google-map/common-google-map.js +343 -0
  19. package/dist-client/google-map/common-google-map.js.map +1 -0
  20. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  21. package/dist-client/google-map/google-map-loader.js +23 -0
  22. package/dist-client/google-map/google-map-loader.js.map +1 -0
  23. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  24. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +280 -0
  25. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  26. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +21 -0
  27. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +389 -0
  28. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  29. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +25 -0
  30. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +469 -0
  31. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  32. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  33. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  34. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  35. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +34 -0
  36. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +642 -0
  37. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +38 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +501 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
  41. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +26 -0
  42. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +439 -0
  43. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  44. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.d.ts +18 -0
  45. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js +131 -0
  46. package/dist-client/pages/kpi-dashboard/kpi-alert-panel.js.map +1 -0
  47. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +36 -0
  48. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +572 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +59 -0
  51. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +1027 -0
  52. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -0
  53. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.d.ts +12 -0
  54. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js +82 -0
  55. package/dist-client/pages/kpi-dashboard/kpi-grade-visualization.js.map +1 -0
  56. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.d.ts +11 -0
  57. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js +65 -0
  58. package/dist-client/pages/kpi-dashboard/kpi-history-viewer.js.map +1 -0
  59. package/dist-client/pages/kpi-dashboard/kpi-list-summary.d.ts +13 -0
  60. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js +115 -0
  61. package/dist-client/pages/kpi-dashboard/kpi-list-summary.js.map +1 -0
  62. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.d.ts +15 -0
  63. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +147 -0
  64. package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -0
  65. package/dist-client/pages/kpi-dashboard/kpi-value-entry.d.ts +7 -0
  66. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js +86 -0
  67. package/dist-client/pages/kpi-dashboard/kpi-value-entry.js.map +1 -0
  68. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +1 -1
  69. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  70. package/dist-client/pages/sv-project-detail.d.ts +10 -0
  71. package/dist-client/pages/sv-project-detail.js +381 -3
  72. package/dist-client/pages/sv-project-detail.js.map +1 -1
  73. package/dist-client/route.d.ts +1 -1
  74. package/dist-client/route.js +3 -0
  75. package/dist-client/route.js.map +1 -1
  76. package/dist-client/tsconfig.tsbuildinfo +1 -1
  77. package/dist-client/viewparts/menu-tools.d.ts +10 -1
  78. package/dist-client/viewparts/menu-tools.js +36 -7
  79. package/dist-client/viewparts/menu-tools.js.map +1 -1
  80. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +54 -0
  81. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +443 -18
  82. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  83. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +6 -4
  84. package/dist-server/service/kpi-stat/kpi-stat-query.js +232 -22
  85. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  86. package/dist-server/service/kpi-stat/kpi-stat-types.d.ts +6 -0
  87. package/dist-server/service/kpi-stat/kpi-stat-types.js +23 -1
  88. package/dist-server/service/kpi-stat/kpi-stat-types.js.map +1 -1
  89. package/dist-server/service/kpi-value/kpi-value-query.d.ts +1 -0
  90. package/dist-server/service/kpi-value/kpi-value-query.js +22 -0
  91. package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
  92. package/dist-server/tsconfig.tsbuildinfo +1 -1
  93. package/package.json +3 -3
  94. package/schema.graphql +3 -0
@@ -0,0 +1,642 @@
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.chartData = [];
19
+ this.chartCategories = [];
20
+ this.kpiYComprehensiveStats = [];
21
+ this.regionalKpiStats = [];
22
+ this.monthlyTrendData = [];
23
+ // 행정안전부 행정구역코드 순서
24
+ this.regionOrder = [
25
+ '서울특별시',
26
+ '부산광역시',
27
+ '대구광역시',
28
+ '인천광역시',
29
+ '광주광역시',
30
+ '대전광역시',
31
+ '울산광역시',
32
+ '세종특별자치시',
33
+ '경기도',
34
+ '강원도',
35
+ '충청북도',
36
+ '충청남도',
37
+ '전라북도',
38
+ '전라남도',
39
+ '경상북도',
40
+ '경상남도',
41
+ '제주특별자치도'
42
+ ];
43
+ }
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ this.fetchKpiYComprehensiveStats();
47
+ this.fetchRegionalKpiStats();
48
+ this.fetchMonthlyTrendData();
49
+ }
50
+ updated(changedProperties) {
51
+ // selectedCategory, startYearMonth, endYearMonth가 변경되면 데이터 다시 가져오기
52
+ if (changedProperties.has('selectedCategory') ||
53
+ changedProperties.has('startYearMonth') ||
54
+ changedProperties.has('endYearMonth')) {
55
+ // 기간 정보가 설정되어 있을 때만 조회 (빈 문자열이면 전체 기간)
56
+ if (this.startYearMonth !== undefined && this.endYearMonth !== undefined) {
57
+ this.fetchKpiYComprehensiveStats();
58
+ this.fetchRegionalKpiStats();
59
+ this.fetchMonthlyTrendData();
60
+ }
61
+ }
62
+ }
63
+ async fetchKpiYComprehensiveStats() {
64
+ try {
65
+ const response = await client.query({
66
+ query: gql `
67
+ query GetKpiYValueComprehensiveStats($startYearMonth: String, $endYearMonth: String) {
68
+ totalKpiYValueComprehensiveStats(startYearMonth: $startYearMonth, endYearMonth: $endYearMonth) {
69
+ kpiName
70
+ minVal
71
+ q1Val
72
+ medVal
73
+ q3Val
74
+ maxVal
75
+ avgVal
76
+ }
77
+ }
78
+ `,
79
+ variables: {
80
+ startYearMonth: this.startYearMonth,
81
+ endYearMonth: this.endYearMonth
82
+ }
83
+ });
84
+ this.kpiYComprehensiveStats = response.data.totalKpiYValueComprehensiveStats || [];
85
+ console.log('KPI Y-Value Comprehensive Stats:', this.kpiYComprehensiveStats);
86
+ // 부모에게 전체 Y-level KPI 통계 전달
87
+ this.dispatchEvent(new CustomEvent('total-kpi-y-stats-loaded', {
88
+ detail: { data: this.kpiYComprehensiveStats },
89
+ bubbles: true,
90
+ composed: true
91
+ }));
92
+ this.generateChartData();
93
+ }
94
+ catch (error) {
95
+ console.error('Failed to fetch KPI Y comprehensive stats:', error);
96
+ this.kpiYComprehensiveStats = [];
97
+ this.generateChartData();
98
+ }
99
+ }
100
+ async fetchRegionalKpiStats() {
101
+ try {
102
+ // selectedCategory에 따라 kpiName 결정
103
+ const categoryToKpiName = {
104
+ '전체 KPI': null,
105
+ '일정 성과': 'Y1. 일정성과',
106
+ '비용 성과': 'Y2. 비용성과',
107
+ '품질 성과': 'Y3. 품질성과',
108
+ '안전 성과': 'Y4. 안전성과',
109
+ '환경 성과': 'Y5. 환경성과',
110
+ '생산성 성과': 'Y6. 생산성성과'
111
+ };
112
+ const kpiName = categoryToKpiName[this.selectedCategory];
113
+ const response = await client.query({
114
+ query: gql `
115
+ query GetRegionalKpiStats($kpiName: String, $startYearMonth: String, $endYearMonth: String) {
116
+ kpiZValueComprehensiveStatsByGeoGroup(
117
+ kpiName: $kpiName
118
+ startYearMonth: $startYearMonth
119
+ endYearMonth: $endYearMonth
120
+ ) {
121
+ geoGroup
122
+ avgVal
123
+ minVal
124
+ maxVal
125
+ projectCount
126
+ }
127
+ }
128
+ `,
129
+ variables: {
130
+ kpiName,
131
+ startYearMonth: this.startYearMonth,
132
+ endYearMonth: this.endYearMonth
133
+ }
134
+ });
135
+ this.regionalKpiStats = response.data.kpiZValueComprehensiveStatsByGeoGroup || [];
136
+ console.log('Regional KPI Stats:', this.regionalKpiStats);
137
+ // 부모에게 지역별 KPI 통계 전달
138
+ this.dispatchEvent(new CustomEvent('regional-kpi-stats-loaded', {
139
+ detail: { data: this.regionalKpiStats },
140
+ bubbles: true,
141
+ composed: true
142
+ }));
143
+ }
144
+ catch (error) {
145
+ console.error('Failed to fetch regional KPI stats:', error);
146
+ this.regionalKpiStats = [];
147
+ }
148
+ }
149
+ async fetchMonthlyTrendData() {
150
+ try {
151
+ // selectedCategory에 따라 kpiName 결정
152
+ const categoryToKpiName = {
153
+ '전체 KPI': null,
154
+ '일정 성과': 'Y1. 일정성과',
155
+ '비용 성과': 'Y2. 비용성과',
156
+ '품질 성과': 'Y3. 품질성과',
157
+ '안전 성과': 'Y4. 안전성과',
158
+ '환경 성과': 'Y5. 환경성과',
159
+ '생산성 성과': 'Y6. 생산성성과'
160
+ };
161
+ const kpiName = categoryToKpiName[this.selectedCategory];
162
+ const response = await client.query({
163
+ query: gql `
164
+ query GetMonthlyTrendData($kpiName: String, $startYearMonth: String, $endYearMonth: String) {
165
+ kpiZValueMonthlyTrendByGeoGroup(kpiName: $kpiName, startYearMonth: $startYearMonth, endYearMonth: $endYearMonth) {
166
+ geoGroup
167
+ yearMonth
168
+ avgVal
169
+ projectCount
170
+ }
171
+ }
172
+ `,
173
+ variables: {
174
+ kpiName,
175
+ startYearMonth: this.startYearMonth,
176
+ endYearMonth: this.endYearMonth
177
+ }
178
+ });
179
+ this.monthlyTrendData = response.data.kpiZValueMonthlyTrendByGeoGroup || [];
180
+ console.log('Monthly Trend Data:', this.monthlyTrendData);
181
+ // 부모에게 월별 추이 데이터 전달
182
+ this.dispatchEvent(new CustomEvent('monthly-trend-data-loaded', {
183
+ detail: { data: this.monthlyTrendData },
184
+ bubbles: true,
185
+ composed: true
186
+ }));
187
+ }
188
+ catch (error) {
189
+ console.error('Failed to fetch monthly trend data:', error);
190
+ this.monthlyTrendData = [];
191
+ }
192
+ }
193
+ getRegionTrendData(region) {
194
+ // 해당 지역의 최근 12개월 트렌드 데이터 추출
195
+ const regionData = this.monthlyTrendData
196
+ .filter(d => d.geoGroup === region)
197
+ .sort((a, b) => a.yearMonth.localeCompare(b.yearMonth))
198
+ .map(d => d.avgVal * 20); // 20배 스케일
199
+ // 12개월 데이터가 없으면 빈 배열 반환
200
+ return regionData.length > 0 ? regionData : [];
201
+ }
202
+ calculateChangeRate(region) {
203
+ // 해당 지역의 월별 데이터를 시간순으로 정렬
204
+ const regionData = this.monthlyTrendData
205
+ .filter(d => d.geoGroup === region)
206
+ .sort((a, b) => a.yearMonth.localeCompare(b.yearMonth));
207
+ if (regionData.length < 2)
208
+ return 0;
209
+ // 가장 오래된 달과 가장 최근 달의 값 비교
210
+ const oldestValue = regionData[0].avgVal;
211
+ const latestValue = regionData[regionData.length - 1].avgVal;
212
+ if (oldestValue === 0)
213
+ return 0;
214
+ // 변동율 계산: (최근값 - 과거값) / 과거값 * 100
215
+ return ((latestValue - oldestValue) / oldestValue) * 100;
216
+ }
217
+ generateChartData() {
218
+ // KPI 이름을 카테고리로 매핑 및 축약
219
+ const categoryMapping = {
220
+ 'Y1. 일정성과': '일정',
221
+ 'Y2. 비용성과': '비용',
222
+ 'Y3. 품질성과': '품질',
223
+ 'Y4. 안전성과': '안전',
224
+ 'Y5. 환경성과': '환경',
225
+ 'Y6. 생산성성과': '생산성'
226
+ };
227
+ console.log('generateChartData - selectedChartType:', this.selectedChartType);
228
+ console.log('generateChartData - kpiYComprehensiveStats:', this.kpiYComprehensiveStats);
229
+ if (this.selectedChartType === 'radar') {
230
+ // 레이더 차트용 데이터 생성 (전체 평균만 표시)
231
+ const data = [];
232
+ const categories = [];
233
+ this.kpiYComprehensiveStats.forEach(stat => {
234
+ const category = categoryMapping[stat.kpiName] || stat.kpiName;
235
+ if (!categories.includes(category)) {
236
+ categories.push(category);
237
+ }
238
+ // 전체 평균 데이터만 표시 (비교 시리즈 없음)
239
+ data.push({
240
+ org: '전체평균',
241
+ category,
242
+ value: Math.round(stat.avgVal * 20)
243
+ });
244
+ });
245
+ this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
246
+ this.chartData = data;
247
+ console.log('Radar chart data:', data);
248
+ console.log('Radar chart categories:', this.chartCategories);
249
+ }
250
+ else {
251
+ // 박스플롯용 데이터 생성 (sv-project-detail.ts의 getYKpiBoxplotData 참조)
252
+ const data = [];
253
+ const categories = [];
254
+ this.kpiYComprehensiveStats.forEach(stat => {
255
+ const category = categoryMapping[stat.kpiName] || stat.kpiName;
256
+ if (!categories.includes(category)) {
257
+ categories.push(category);
258
+ }
259
+ data.push({
260
+ org: category,
261
+ min: stat.minVal * 20,
262
+ max: stat.maxVal * 20,
263
+ q1: stat.q1Val * 20,
264
+ q3: stat.q3Val * 20,
265
+ median: stat.medVal * 20,
266
+ mean: stat.avgVal * 20,
267
+ value: stat.avgVal * 20
268
+ });
269
+ });
270
+ this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
271
+ this.chartData = data;
272
+ console.log('Boxplot chart data:', data);
273
+ console.log('Boxplot chart categories:', this.chartCategories);
274
+ }
275
+ }
276
+ onCategoryChange(event) {
277
+ const target = event.target;
278
+ this.selectedCategory = target.value;
279
+ // 카테고리 변경 시 지역별 통계와 월별 추이 데이터만 다시 가져오기
280
+ // 종합 성과(박스플롯/레이더)는 항상 전체 Y-level KPI를 표시
281
+ this.fetchRegionalKpiStats();
282
+ this.fetchMonthlyTrendData();
283
+ this.dispatchEvent(new CustomEvent('category-change', {
284
+ detail: { category: target.value },
285
+ bubbles: true,
286
+ composed: true
287
+ }));
288
+ }
289
+ onChartTypeChange(type) {
290
+ this.selectedChartType = type;
291
+ this.generateChartData();
292
+ }
293
+ onRegionClick(region) {
294
+ this.dispatchEvent(new CustomEvent('region-click', {
295
+ detail: { region },
296
+ bubbles: true,
297
+ composed: true
298
+ }));
299
+ }
300
+ downloadExcel() {
301
+ this.dispatchEvent(new CustomEvent('download-excel', {
302
+ bubbles: true,
303
+ composed: true
304
+ }));
305
+ }
306
+ // regionOrder에 따라 지역 데이터를 정렬
307
+ getSortedRegionData() {
308
+ // 실제 데이터를 기반으로 각 시도에 대한 데이터 생성
309
+ return this.regionOrder.map(regionName => {
310
+ const stat = this.regionalKpiStats.find(s => s.geoGroup === regionName);
311
+ const changeRate = this.calculateChangeRate(regionName);
312
+ return {
313
+ region: regionName,
314
+ kpi: stat ? (stat.avgVal * 20).toFixed(1) : '-',
315
+ change: changeRate.toFixed(2),
316
+ trendData: this.getRegionTrendData(regionName),
317
+ lat: 36.5,
318
+ lng: 127.5
319
+ };
320
+ });
321
+ }
322
+ getChangeRateClass(change) {
323
+ if (change > 0)
324
+ return 'change-up';
325
+ if (change < 0)
326
+ return 'change-down';
327
+ return 'change-neutral';
328
+ }
329
+ getChangeIcon(change) {
330
+ if (change > 0)
331
+ return '▲';
332
+ if (change < 0)
333
+ return '▼';
334
+ return '─';
335
+ }
336
+ render() {
337
+ return html `
338
+ <div class="panel-header">
339
+ <div class="panel-title">전국 KPI</div>
340
+ <button class="panel-close" style="visibility: hidden;">×</button>
341
+ </div>
342
+ <div class="panel-content">
343
+ <!-- KPI 카테고리 선택 -->
344
+ <select class="category-select" .value=${this.selectedCategory} @change=${this.onCategoryChange}>
345
+ <option value="전체 KPI">전체 KPI</option>
346
+ <option value="일정 성과">일정 성과</option>
347
+ <option value="비용 성과">비용 성과</option>
348
+ <option value="품질 성과">품질 성과</option>
349
+ <option value="안전 성과">안전 성과</option>
350
+ <option value="환경 성과">환경 성과</option>
351
+ <option value="생산성 성과">생산성 성과</option>
352
+ </select>
353
+
354
+ <!-- 종합 성과 -->
355
+ <div class="chart-section">
356
+ <div class="sub-title">종합 성과</div>
357
+ <div class="chart-toggle">
358
+ <button
359
+ class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
360
+ @click=${() => this.onChartTypeChange('boxplot')}
361
+ >
362
+ 박스플롯
363
+ </button>
364
+ <button
365
+ class="toggle-button ${this.selectedChartType === 'radar' ? 'active' : ''}"
366
+ @click=${() => this.onChartTypeChange('radar')}
367
+ >
368
+ 레이더차트
369
+ </button>
370
+ </div>
371
+ <div class="chart-container">
372
+ ${this.selectedChartType === 'boxplot'
373
+ ? html ` <sv-kpi-boxplot-chart .data=${this.chartData} .valueKey=${'value'}></sv-kpi-boxplot-chart> `
374
+ : html `
375
+ <sv-kpi-radar-chart
376
+ .data=${this.chartData}
377
+ .categories=${this.chartCategories}
378
+ .valueKey=${'value'}
379
+ ></sv-kpi-radar-chart>
380
+ `}
381
+ </div>
382
+ </div>
383
+
384
+ <!-- 시도별 성과 -->
385
+ <div class="chart-section">
386
+ <div class="sub-title">시도별 성과</div>
387
+ <table class="performance-table">
388
+ <thead>
389
+ <tr>
390
+ <th>지역명</th>
391
+ <th>KPI</th>
392
+ <th>변동률(%)</th>
393
+ <th>성과 추이</th>
394
+ </tr>
395
+ </thead>
396
+ <tbody>
397
+ ${this.getSortedRegionData().map((item) => html `
398
+ <tr
399
+ style="cursor: pointer; transition: background-color 0.2s;"
400
+ @click=${() => this.onRegionClick(item.region)}
401
+ @mouseenter=${(e) => (e.target.style.backgroundColor = '#f8f9fa')}
402
+ @mouseleave=${(e) => (e.target.style.backgroundColor = '')}
403
+ >
404
+ <td>${item.region}</td>
405
+ <td>${item.kpi}</td>
406
+ <td>
407
+ <div class="change-rate ${this.getChangeRateClass(item.change)}">
408
+ ${this.getChangeIcon(item.change)}${Math.abs(item.change)}%
409
+ </div>
410
+ </td>
411
+ <td>
412
+ ${item.trendData && item.trendData.length > 0
413
+ ? html `
414
+ <sv-kpi-mini-trend-chart
415
+ .data=${item.trendData}
416
+ .width=${60}
417
+ .height=${30}
418
+ .lineColor=${'#2196f3'}
419
+ .strokeWidth=${1.5}
420
+ .showPoints=${true}
421
+ .pointRadius=${1.5}
422
+ ></sv-kpi-mini-trend-chart>
423
+ `
424
+ : html `<span style="color: #999;">-</span>`}
425
+ </td>
426
+ </tr>
427
+ `)}
428
+ </tbody>
429
+ </table>
430
+ <button class="download-button" @click=${this.downloadExcel}>📊 엑셀 다운로드</button>
431
+ </div>
432
+ </div>
433
+ `;
434
+ }
435
+ };
436
+ KpiLeftPanel.styles = [
437
+ ScrollbarStyles,
438
+ css `
439
+ :host {
440
+ display: block;
441
+ width: 400px;
442
+ background: #fff;
443
+ border-right: 1px solid #e0e0e0;
444
+ overflow: hidden;
445
+ display: flex;
446
+ flex-direction: column;
447
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
448
+ }
449
+ .panel-content {
450
+ padding: 20px;
451
+ overflow-y: auto;
452
+ flex: 1;
453
+ }
454
+ .panel-header {
455
+ display: flex;
456
+ justify-content: space-between;
457
+ align-items: center;
458
+ padding: 20px;
459
+ border-bottom: 1px solid #e0e0e0;
460
+ background: #fff;
461
+ height: 70px;
462
+ box-sizing: border-box;
463
+ }
464
+ .panel-title {
465
+ font-size: 1.3rem;
466
+ font-weight: bold;
467
+ color: #333;
468
+ margin: 0;
469
+ }
470
+ .panel-close {
471
+ width: 32px;
472
+ height: 32px;
473
+ border: none;
474
+ background: #fff;
475
+ border-radius: 50%;
476
+ cursor: pointer;
477
+ display: flex;
478
+ align-items: center;
479
+ justify-content: center;
480
+ font-size: 1.2rem;
481
+ color: #666;
482
+ transition: all 0.2s;
483
+ }
484
+ .panel-close:hover {
485
+ background: #e9ecef;
486
+ color: #333;
487
+ }
488
+ .sub-title {
489
+ font-size: 1rem;
490
+ font-weight: 600;
491
+ margin-bottom: 16px;
492
+ color: #495057;
493
+ }
494
+ .chart-section {
495
+ background: #f8f9fa;
496
+ border-radius: 8px;
497
+ padding: 16px;
498
+ margin-bottom: 20px;
499
+ }
500
+ .chart-toggle {
501
+ display: flex;
502
+ gap: 8px;
503
+ margin-bottom: 16px;
504
+ }
505
+ .toggle-button {
506
+ padding: 8px 16px;
507
+ border: 1px solid #ced4da;
508
+ background: #fff;
509
+ border-radius: 6px;
510
+ cursor: pointer;
511
+ font-size: 0.9rem;
512
+ transition: all 0.2s;
513
+ }
514
+ .toggle-button.active {
515
+ background: #667eea;
516
+ color: white;
517
+ border-color: #667eea;
518
+ }
519
+ .chart-container {
520
+ height: 300px;
521
+ display: flex;
522
+ align-items: center;
523
+ justify-content: center;
524
+ background: white;
525
+ border-radius: 6px;
526
+ border: 1px solid #e9ecef;
527
+ }
528
+ .performance-table {
529
+ width: 100%;
530
+ border-collapse: collapse;
531
+ margin-top: 16px;
532
+ }
533
+ .performance-table th,
534
+ .performance-table td {
535
+ padding: 12px 8px;
536
+ text-align: left;
537
+ border-bottom: 1px solid #e9ecef;
538
+ }
539
+ .performance-table th {
540
+ background: #f8f9fa;
541
+ font-weight: 600;
542
+ color: #495057;
543
+ }
544
+ .performance-table td {
545
+ color: #333;
546
+ }
547
+ .change-rate {
548
+ display: flex;
549
+ align-items: center;
550
+ gap: 4px;
551
+ }
552
+ .change-up {
553
+ color: #dc3545;
554
+ }
555
+ .change-down {
556
+ color: #198754;
557
+ }
558
+ .change-neutral {
559
+ color: #6c757d;
560
+ }
561
+ .trend-chart {
562
+ width: 60px;
563
+ height: 30px;
564
+ background: #f8f9fa;
565
+ border-radius: 4px;
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: center;
569
+ font-size: 0.8rem;
570
+ color: #666;
571
+ }
572
+ .download-button {
573
+ margin-top: 16px;
574
+ padding: 8px 16px;
575
+ background: #28a745;
576
+ color: white;
577
+ border: none;
578
+ border-radius: 6px;
579
+ cursor: pointer;
580
+ font-size: 0.9rem;
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 8px;
584
+ }
585
+ .download-button:hover {
586
+ background: #218838;
587
+ }
588
+ .category-select {
589
+ width: 100%;
590
+ padding: 8px 12px;
591
+ border: 1px solid #ced4da;
592
+ border-radius: 6px;
593
+ background: white;
594
+ margin-bottom: 20px;
595
+ }
596
+ `
597
+ ];
598
+ __decorate([
599
+ property({ type: String }),
600
+ __metadata("design:type", Object)
601
+ ], KpiLeftPanel.prototype, "selectedCategory", void 0);
602
+ __decorate([
603
+ property({ type: String }),
604
+ __metadata("design:type", Object)
605
+ ], KpiLeftPanel.prototype, "selectedChartType", void 0);
606
+ __decorate([
607
+ property({ type: Array }),
608
+ __metadata("design:type", Array)
609
+ ], KpiLeftPanel.prototype, "mapData", void 0);
610
+ __decorate([
611
+ property({ type: String }),
612
+ __metadata("design:type", Object)
613
+ ], KpiLeftPanel.prototype, "startYearMonth", void 0);
614
+ __decorate([
615
+ property({ type: String }),
616
+ __metadata("design:type", Object)
617
+ ], KpiLeftPanel.prototype, "endYearMonth", void 0);
618
+ __decorate([
619
+ state(),
620
+ __metadata("design:type", Array)
621
+ ], KpiLeftPanel.prototype, "chartData", void 0);
622
+ __decorate([
623
+ state(),
624
+ __metadata("design:type", Array)
625
+ ], KpiLeftPanel.prototype, "chartCategories", void 0);
626
+ __decorate([
627
+ state(),
628
+ __metadata("design:type", Array)
629
+ ], KpiLeftPanel.prototype, "kpiYComprehensiveStats", void 0);
630
+ __decorate([
631
+ state(),
632
+ __metadata("design:type", Array)
633
+ ], KpiLeftPanel.prototype, "regionalKpiStats", void 0);
634
+ __decorate([
635
+ state(),
636
+ __metadata("design:type", Array)
637
+ ], KpiLeftPanel.prototype, "monthlyTrendData", void 0);
638
+ KpiLeftPanel = __decorate([
639
+ customElement('kpi-left-panel')
640
+ ], KpiLeftPanel);
641
+ export { KpiLeftPanel };
642
+ //# sourceMappingURL=kpi-left-panel.js.map