@things-factory/kpi 9.0.29 → 9.0.31

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 (73) hide show
  1. package/client/charts/kpi-mini-trend-chart.ts +125 -0
  2. package/client/charts/kpi-trend-chart.ts +163 -0
  3. package/client/google-map/common-google-map.ts +370 -0
  4. package/client/google-map/google-map-loader.ts +29 -0
  5. package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +248 -0
  6. package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +369 -0
  7. package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +443 -0
  8. package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +72 -0
  9. package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +399 -0
  10. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +302 -0
  11. package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +355 -0
  12. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +243 -0
  13. package/client/pages/kpi-dashboard/kpi-dashboard.ts +416 -0
  14. package/client/route.ts +4 -0
  15. package/dist-client/charts/kpi-mini-trend-chart.d.ts +14 -0
  16. package/dist-client/charts/kpi-mini-trend-chart.js +148 -0
  17. package/dist-client/charts/kpi-mini-trend-chart.js.map +1 -0
  18. package/dist-client/charts/kpi-trend-chart.d.ts +25 -0
  19. package/dist-client/charts/kpi-trend-chart.js +186 -0
  20. package/dist-client/charts/kpi-trend-chart.js.map +1 -0
  21. package/dist-client/google-map/common-google-map.d.ts +34 -0
  22. package/dist-client/google-map/common-google-map.js +333 -0
  23. package/dist-client/google-map/common-google-map.js.map +1 -0
  24. package/dist-client/google-map/google-map-loader.d.ts +6 -0
  25. package/dist-client/google-map/google-map-loader.js +22 -0
  26. package/dist-client/google-map/google-map-loader.js.map +1 -0
  27. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
  28. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +279 -0
  29. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
  30. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +19 -0
  31. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +385 -0
  32. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
  33. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +23 -0
  34. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +465 -0
  35. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
  36. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
  37. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +78 -0
  38. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
  39. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +22 -0
  40. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +404 -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 +28 -0
  43. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +298 -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 +23 -0
  46. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +368 -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 +29 -0
  49. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +271 -0
  50. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
  51. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +21 -0
  52. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +398 -0
  53. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  54. package/dist-client/route.d.ts +1 -1
  55. package/dist-client/route.js +3 -0
  56. package/dist-client/route.js.map +1 -1
  57. package/dist-client/tsconfig.tsbuildinfo +1 -1
  58. package/dist-server/index.d.ts +1 -0
  59. package/dist-server/index.js +1 -0
  60. package/dist-server/index.js.map +1 -1
  61. package/dist-server/migrations/index.d.ts +1 -0
  62. package/dist-server/migrations/index.js +12 -0
  63. package/dist-server/migrations/index.js.map +1 -0
  64. package/dist-server/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +2 -2
  66. package/server/index.ts +1 -0
  67. package/server/migrations/index.ts +9 -0
  68. package/things-factory.config.js +2 -1
  69. package/translations/en.json +1 -0
  70. package/translations/ja.json +1 -0
  71. package/translations/ko.json +1 -0
  72. package/translations/ms.json +1 -0
  73. package/translations/zh.json +1 -0
@@ -0,0 +1,298 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { LitElement, html, css } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import '../../../google-map/common-google-map.js';
5
+ let KpiMapPanel = class KpiMapPanel extends LitElement {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.selectedCategory = '전체 KPI';
9
+ this.mapData = [];
10
+ this.map = null;
11
+ }
12
+ static { this.styles = css `
13
+ :host {
14
+ display: flex;
15
+ background: #f8f9fa;
16
+ overflow: hidden;
17
+ position: relative;
18
+ min-height: 500px;
19
+ }
20
+ .map-overlay {
21
+ position: absolute;
22
+ top: 16px;
23
+ left: 16px;
24
+ z-index: 10;
25
+ background: rgba(255, 255, 255, 0.95);
26
+ border-radius: 8px;
27
+ padding: 12px 16px;
28
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
29
+ backdrop-filter: blur(4px);
30
+ }
31
+ .category-buttons {
32
+ display: flex;
33
+ gap: 8px;
34
+ flex-wrap: wrap;
35
+ }
36
+ .category-button {
37
+ padding: 6px 12px;
38
+ border: 1px solid #ced4da;
39
+ background: #fff;
40
+ border-radius: 4px;
41
+ cursor: pointer;
42
+ font-size: 0.85rem;
43
+ transition: all 0.2s;
44
+ white-space: nowrap;
45
+ }
46
+ .category-button.active {
47
+ background: #667eea;
48
+ color: white;
49
+ border-color: #667eea;
50
+ }
51
+ .category-button:hover {
52
+ background: #f8f9fa;
53
+ }
54
+ .category-button.active:hover {
55
+ background: #5a6fd8;
56
+ }
57
+ .map-container {
58
+ flex: 1;
59
+ position: relative;
60
+ overflow: hidden;
61
+ }
62
+ .map-controls {
63
+ position: absolute;
64
+ top: 16px;
65
+ right: 16px;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 8px;
69
+ z-index: 10;
70
+ }
71
+ .map-control-button {
72
+ width: 40px;
73
+ height: 40px;
74
+ background: white;
75
+ border: 1px solid #ced4da;
76
+ border-radius: 6px;
77
+ cursor: pointer;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ font-size: 1.2rem;
82
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
83
+ }
84
+ .map-control-button:hover {
85
+ background: #f8f9fa;
86
+ }
87
+ .map-scale-direction {
88
+ position: absolute;
89
+ bottom: 16px;
90
+ right: 16px;
91
+ background: white;
92
+ padding: 8px 12px;
93
+ border-radius: 6px;
94
+ border: 1px solid #ced4da;
95
+ font-size: 0.8rem;
96
+ color: #666;
97
+ z-index: 10;
98
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
99
+ text-align: center;
100
+ }
101
+ .north-arrow {
102
+ font-size: 1rem;
103
+ margin-bottom: 4px;
104
+ }
105
+ .scale-info {
106
+ font-size: 0.7rem;
107
+ }
108
+ common-google-map {
109
+ width: 100%;
110
+ height: 100%;
111
+ }
112
+ `; }
113
+ // mapData를 지도 마커 형식으로 변환
114
+ get mapLocations() {
115
+ return (this.mapData?.map(item => ({
116
+ lat: item.lat,
117
+ lng: item.lng,
118
+ title: item.region,
119
+ region: item.region, // 지역명 추가
120
+ // 커스텀 마커 콘텐츠 생성
121
+ markerContent: `
122
+ <div style="
123
+ background: white;
124
+ border-radius: 8px;
125
+ padding: 8px 12px;
126
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
127
+ border: 1px solid #e9ecef;
128
+ min-width: 80px;
129
+ text-align: center;
130
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
131
+ cursor: pointer;
132
+ ">
133
+ <div style="
134
+ font-size: 11px;
135
+ font-weight: 600;
136
+ color: #495057;
137
+ margin-bottom: 2px;
138
+ white-space: nowrap;
139
+ ">${item.region}</div>
140
+ <div style="
141
+ font-size: 13px;
142
+ font-weight: 700;
143
+ color: #212529;
144
+ margin-bottom: 2px;
145
+ ">${item.kpi}</div>
146
+ <div style="
147
+ font-size: 10px;
148
+ color: ${item.change > 0 ? '#dc3545' : '#198754'};
149
+ font-weight: 500;
150
+ ">${item.change > 0 ? '▲' : '▼'} ${Math.abs(item.change)}%</div>
151
+ </div>
152
+ `,
153
+ content: `
154
+ <div style="padding: 12px; min-width: 200px;">
155
+ <h3 style="margin: 0 0 8px 0; font-size: 16px; color: #212529;">${item.region}</h3>
156
+ <div style="margin-bottom: 8px;">
157
+ <span style="font-size: 14px; color: #6c757d;">KPI: </span>
158
+ <span style="font-size: 16px; font-weight: 600; color: #212529;">${item.kpi}</span>
159
+ </div>
160
+ <div style="
161
+ font-size: 14px;
162
+ color: ${item.change > 0 ? '#dc3545' : '#198754'};
163
+ font-weight: 500;
164
+ ">
165
+ ${item.change > 0 ? '▲' : '▼'} ${Math.abs(item.change)}% 변화
166
+ </div>
167
+ </div>
168
+ `
169
+ })) || []);
170
+ }
171
+ onCategoryButtonClick(category) {
172
+ this.dispatchEvent(new CustomEvent('category-change', {
173
+ detail: { category },
174
+ bubbles: true,
175
+ composed: true
176
+ }));
177
+ }
178
+ onMapChange(event) {
179
+ this.map = event.detail;
180
+ }
181
+ onRegionClick(event) {
182
+ this.dispatchEvent(new CustomEvent('region-click', {
183
+ detail: event.detail,
184
+ bubbles: true,
185
+ composed: true
186
+ }));
187
+ }
188
+ zoomIn() {
189
+ if (this.map) {
190
+ this.map.setZoom(this.map.getZoom() + 1);
191
+ }
192
+ }
193
+ zoomOut() {
194
+ if (this.map) {
195
+ this.map.setZoom(this.map.getZoom() - 1);
196
+ }
197
+ }
198
+ resetView() {
199
+ if (this.map) {
200
+ this.map.setCenter({ lat: 36.5, lng: 127.5 });
201
+ this.map.setZoom(7);
202
+ }
203
+ }
204
+ render() {
205
+ return html `
206
+ <div class="map-overlay">
207
+ <div class="category-buttons">
208
+ <button
209
+ class="category-button ${this.selectedCategory === '전체 KPI' ? 'active' : ''}"
210
+ @click=${() => this.onCategoryButtonClick('전체 KPI')}
211
+ >
212
+ 전체 KPI
213
+ </button>
214
+ <button
215
+ class="category-button ${this.selectedCategory === '일정 성과' ? 'active' : ''}"
216
+ @click=${() => this.onCategoryButtonClick('일정 성과')}
217
+ >
218
+ 일정 성과
219
+ </button>
220
+ <button
221
+ class="category-button ${this.selectedCategory === '비용 성과' ? 'active' : ''}"
222
+ @click=${() => this.onCategoryButtonClick('비용 성과')}
223
+ >
224
+ 비용 성과
225
+ </button>
226
+ <button
227
+ class="category-button ${this.selectedCategory === '품질 성과' ? 'active' : ''}"
228
+ @click=${() => this.onCategoryButtonClick('품질 성과')}
229
+ >
230
+ 품질 성과
231
+ </button>
232
+ <button
233
+ class="category-button ${this.selectedCategory === '안전 성과' ? 'active' : ''}"
234
+ @click=${() => this.onCategoryButtonClick('안전 성과')}
235
+ >
236
+ 안전 성과
237
+ </button>
238
+ <button
239
+ class="category-button ${this.selectedCategory === '환경 성과' ? 'active' : ''}"
240
+ @click=${() => this.onCategoryButtonClick('환경 성과')}
241
+ >
242
+ 환경 성과
243
+ </button>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="map-container">
248
+ <!-- 지도 컨트롤 (오른쪽 상단) -->
249
+ <div class="map-controls">
250
+ <button class="map-control-button" title="확대" @click=${() => this.zoomIn()}>+</button>
251
+ <button class="map-control-button" title="축소" @click=${() => this.zoomOut()}>-</button>
252
+ <button class="map-control-button" title="뷰 초기화" @click=${() => this.resetView()}>⌖</button>
253
+ </div>
254
+
255
+ <!-- 스케일 및 방향 정보 (오른쪽 하단) -->
256
+ <div class="map-scale-direction">
257
+ <div class="north-arrow">↑ N</div>
258
+ <div class="scale-info">25km</div>
259
+ </div>
260
+
261
+ <!-- 공통 Google Maps 컴포넌트 사용 -->
262
+ <common-google-map
263
+ .center=${{ lat: 36.5, lng: 127.5 }}
264
+ .zoom=${7}
265
+ .locations=${this.mapLocations}
266
+ .clusterZoom=${10}
267
+ .controls=${{
268
+ zoomControl: false,
269
+ mapTypeControl: false,
270
+ scaleControl: false,
271
+ streetViewControl: false,
272
+ rotateControl: false,
273
+ fullscreenControl: false
274
+ }}
275
+ @map-change=${this.onMapChange}
276
+ @region-click=${this.onRegionClick}
277
+ ></common-google-map>
278
+ </div>
279
+ `;
280
+ }
281
+ };
282
+ __decorate([
283
+ property({ type: String }),
284
+ __metadata("design:type", Object)
285
+ ], KpiMapPanel.prototype, "selectedCategory", void 0);
286
+ __decorate([
287
+ property({ type: Array }),
288
+ __metadata("design:type", Array)
289
+ ], KpiMapPanel.prototype, "mapData", void 0);
290
+ __decorate([
291
+ state(),
292
+ __metadata("design:type", Object)
293
+ ], KpiMapPanel.prototype, "map", void 0);
294
+ KpiMapPanel = __decorate([
295
+ customElement('kpi-map-panel')
296
+ ], KpiMapPanel);
297
+ export { KpiMapPanel };
298
+ //# sourceMappingURL=kpi-map-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-map-panel.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/components/kpi-map-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport '../../../google-map/common-google-map.js'\n\ndeclare global {\n interface Window {\n google: any\n }\n}\n\n@customElement('kpi-map-panel')\nexport class KpiMapPanel extends LitElement {\n static styles = css`\n :host {\n display: flex;\n background: #f8f9fa;\n overflow: hidden;\n position: relative;\n min-height: 500px;\n }\n .map-overlay {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n background: rgba(255, 255, 255, 0.95);\n border-radius: 8px;\n padding: 12px 16px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n backdrop-filter: blur(4px);\n }\n .category-buttons {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n }\n .category-button {\n padding: 6px 12px;\n border: 1px solid #ced4da;\n background: #fff;\n border-radius: 4px;\n cursor: pointer;\n font-size: 0.85rem;\n transition: all 0.2s;\n white-space: nowrap;\n }\n .category-button.active {\n background: #667eea;\n color: white;\n border-color: #667eea;\n }\n .category-button:hover {\n background: #f8f9fa;\n }\n .category-button.active:hover {\n background: #5a6fd8;\n }\n .map-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n }\n .map-controls {\n position: absolute;\n top: 16px;\n right: 16px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 10;\n }\n .map-control-button {\n width: 40px;\n height: 40px;\n background: white;\n border: 1px solid #ced4da;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.2rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n .map-control-button:hover {\n background: #f8f9fa;\n }\n .map-scale-direction {\n position: absolute;\n bottom: 16px;\n right: 16px;\n background: white;\n padding: 8px 12px;\n border-radius: 6px;\n border: 1px solid #ced4da;\n font-size: 0.8rem;\n color: #666;\n z-index: 10;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n text-align: center;\n }\n .north-arrow {\n font-size: 1rem;\n margin-bottom: 4px;\n }\n .scale-info {\n font-size: 0.7rem;\n }\n common-google-map {\n width: 100%;\n height: 100%;\n }\n `\n\n @property({ type: String }) selectedCategory = '전체 KPI'\n @property({ type: Array }) mapData: any[] = []\n\n @state() private map: any = null\n\n // mapData를 지도 마커 형식으로 변환\n get mapLocations() {\n return (\n this.mapData?.map(item => ({\n lat: item.lat,\n lng: item.lng,\n title: item.region,\n region: item.region, // 지역명 추가\n // 커스텀 마커 콘텐츠 생성\n markerContent: `\n <div style=\"\n background: white;\n border-radius: 8px;\n padding: 8px 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n border: 1px solid #e9ecef;\n min-width: 80px;\n text-align: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n cursor: pointer;\n \">\n <div style=\"\n font-size: 11px;\n font-weight: 600;\n color: #495057;\n margin-bottom: 2px;\n white-space: nowrap;\n \">${item.region}</div>\n <div style=\"\n font-size: 13px;\n font-weight: 700;\n color: #212529;\n margin-bottom: 2px;\n \">${item.kpi}</div>\n <div style=\"\n font-size: 10px;\n color: ${item.change > 0 ? '#dc3545' : '#198754'};\n font-weight: 500;\n \">${item.change > 0 ? '▲' : '▼'} ${Math.abs(item.change)}%</div>\n </div>\n `,\n content: `\n <div style=\"padding: 12px; min-width: 200px;\">\n <h3 style=\"margin: 0 0 8px 0; font-size: 16px; color: #212529;\">${item.region}</h3>\n <div style=\"margin-bottom: 8px;\">\n <span style=\"font-size: 14px; color: #6c757d;\">KPI: </span>\n <span style=\"font-size: 16px; font-weight: 600; color: #212529;\">${item.kpi}</span>\n </div>\n <div style=\"\n font-size: 14px;\n color: ${item.change > 0 ? '#dc3545' : '#198754'};\n font-weight: 500;\n \">\n ${item.change > 0 ? '▲' : '▼'} ${Math.abs(item.change)}% 변화\n </div>\n </div>\n `\n })) || []\n )\n }\n\n private onCategoryButtonClick(category: string) {\n this.dispatchEvent(\n new CustomEvent('category-change', {\n detail: { category },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private onMapChange(event: CustomEvent) {\n this.map = event.detail\n }\n\n private onRegionClick(event: CustomEvent) {\n this.dispatchEvent(\n new CustomEvent('region-click', {\n detail: event.detail,\n bubbles: true,\n composed: true\n })\n )\n }\n\n private zoomIn() {\n if (this.map) {\n this.map.setZoom(this.map.getZoom() + 1)\n }\n }\n\n private zoomOut() {\n if (this.map) {\n this.map.setZoom(this.map.getZoom() - 1)\n }\n }\n\n private resetView() {\n if (this.map) {\n this.map.setCenter({ lat: 36.5, lng: 127.5 })\n this.map.setZoom(7)\n }\n }\n\n render() {\n return html`\n <div class=\"map-overlay\">\n <div class=\"category-buttons\">\n <button\n class=\"category-button ${this.selectedCategory === '전체 KPI' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('전체 KPI')}\n >\n 전체 KPI\n </button>\n <button\n class=\"category-button ${this.selectedCategory === '일정 성과' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('일정 성과')}\n >\n 일정 성과\n </button>\n <button\n class=\"category-button ${this.selectedCategory === '비용 성과' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('비용 성과')}\n >\n 비용 성과\n </button>\n <button\n class=\"category-button ${this.selectedCategory === '품질 성과' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('품질 성과')}\n >\n 품질 성과\n </button>\n <button\n class=\"category-button ${this.selectedCategory === '안전 성과' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('안전 성과')}\n >\n 안전 성과\n </button>\n <button\n class=\"category-button ${this.selectedCategory === '환경 성과' ? 'active' : ''}\"\n @click=${() => this.onCategoryButtonClick('환경 성과')}\n >\n 환경 성과\n </button>\n </div>\n </div>\n\n <div class=\"map-container\">\n <!-- 지도 컨트롤 (오른쪽 상단) -->\n <div class=\"map-controls\">\n <button class=\"map-control-button\" title=\"확대\" @click=${() => this.zoomIn()}>+</button>\n <button class=\"map-control-button\" title=\"축소\" @click=${() => this.zoomOut()}>-</button>\n <button class=\"map-control-button\" title=\"뷰 초기화\" @click=${() => this.resetView()}>⌖</button>\n </div>\n\n <!-- 스케일 및 방향 정보 (오른쪽 하단) -->\n <div class=\"map-scale-direction\">\n <div class=\"north-arrow\">↑ N</div>\n <div class=\"scale-info\">25km</div>\n </div>\n\n <!-- 공통 Google Maps 컴포넌트 사용 -->\n <common-google-map\n .center=${{ lat: 36.5, lng: 127.5 }}\n .zoom=${7}\n .locations=${this.mapLocations}\n .clusterZoom=${10}\n .controls=${{\n zoomControl: false,\n mapTypeControl: false,\n scaleControl: false,\n streetViewControl: false,\n rotateControl: false,\n fullscreenControl: false\n }}\n @map-change=${this.onMapChange}\n @region-click=${this.onRegionClick}\n ></common-google-map>\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { LitElement, nothing } from 'lit';
2
+ import '../../../charts/kpi-radar-chart.js';
3
+ import '../../../charts/kpi-boxplot-chart.js';
4
+ import '../../../charts/kpi-trend-chart.js';
5
+ export declare class KpiRegionPopup extends LitElement {
6
+ static styles: import("lit").CSSResult;
7
+ selectedRegion: string | null;
8
+ selectedChartType: string;
9
+ selectedPeriod: string;
10
+ startDate: string;
11
+ endDate: string;
12
+ private chartData;
13
+ private chartCategories;
14
+ private trendData;
15
+ connectedCallback(): void;
16
+ private generateChartData;
17
+ private generateTrendData;
18
+ private onClose;
19
+ private onChartTypeChange;
20
+ private onPeriodChange;
21
+ private onDateRangeChange;
22
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
23
+ }