@things-factory/kpi 9.0.30 → 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 (39) 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 +45 -7
  4. package/client/google-map/google-map-loader.ts +1 -1
  5. package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +1 -2
  6. package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +399 -0
  7. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +110 -30
  8. package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +355 -0
  9. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +46 -589
  10. package/dist-client/charts/kpi-mini-trend-chart.d.ts +14 -0
  11. package/dist-client/charts/kpi-mini-trend-chart.js +148 -0
  12. package/dist-client/charts/kpi-mini-trend-chart.js.map +1 -0
  13. package/dist-client/charts/kpi-trend-chart.d.ts +25 -0
  14. package/dist-client/charts/kpi-trend-chart.js +186 -0
  15. package/dist-client/charts/kpi-trend-chart.js.map +1 -0
  16. package/dist-client/google-map/common-google-map.js +40 -7
  17. package/dist-client/google-map/common-google-map.js.map +1 -1
  18. package/dist-client/google-map/google-map-loader.js +1 -1
  19. package/dist-client/google-map/google-map-loader.js.map +1 -1
  20. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +1 -2
  21. package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -1
  22. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +22 -0
  23. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +404 -0
  24. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -0
  25. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +6 -1
  26. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +100 -25
  27. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  28. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +23 -0
  29. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +368 -0
  30. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -0
  31. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +6 -15
  32. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +43 -585
  33. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +2 -2
  36. package/client/google-map/script-loader.ts +0 -173
  37. package/dist-client/google-map/script-loader.d.ts +0 -3
  38. package/dist-client/google-map/script-loader.js +0 -144
  39. package/dist-client/google-map/script-loader.js.map +0 -1
@@ -0,0 +1,355 @@
1
+ import { LitElement, html, css, nothing } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+
4
+ import '../../../charts/kpi-radar-chart.js'
5
+ import '../../../charts/kpi-boxplot-chart.js'
6
+ import '../../../charts/kpi-trend-chart.js'
7
+
8
+ @customElement('kpi-region-popup')
9
+ export class KpiRegionPopup extends LitElement {
10
+ static styles = css`
11
+ :host {
12
+ display: block;
13
+ position: absolute;
14
+ top: 0;
15
+ left: 402px;
16
+ width: 400px;
17
+ height: 100%;
18
+ background: #fff;
19
+ border-right: 1px solid #e0e0e0;
20
+ z-index: 1000;
21
+ overflow: hidden;
22
+ display: flex;
23
+ flex-direction: column;
24
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
25
+ }
26
+ .popup-content {
27
+ padding: 24px;
28
+ overflow-y: auto;
29
+ flex: 1;
30
+ }
31
+ .popup-header {
32
+ display: flex;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ padding: 20px 24px;
36
+ border-bottom: 1px solid #e0e0e0;
37
+ background: #fff;
38
+ height: 70px;
39
+ box-sizing: border-box;
40
+ }
41
+ .popup-title {
42
+ font-size: 1.2rem;
43
+ font-weight: bold;
44
+ color: #333;
45
+ }
46
+ .popup-close {
47
+ width: 32px;
48
+ height: 32px;
49
+ border: none;
50
+ background: #fff;
51
+ border-radius: 50%;
52
+ cursor: pointer;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ font-size: 1.2rem;
57
+ color: #666;
58
+ transition: all 0.2s;
59
+ }
60
+ .popup-close:hover {
61
+ background: #e9ecef;
62
+ color: #333;
63
+ }
64
+ .sub-title {
65
+ font-size: 1rem;
66
+ font-weight: 600;
67
+ margin-bottom: 16px;
68
+ color: #495057;
69
+ }
70
+ .chart-section {
71
+ background: #f8f9fa;
72
+ border-radius: 8px;
73
+ padding: 16px;
74
+ margin-bottom: 20px;
75
+ }
76
+ .chart-toggle {
77
+ display: flex;
78
+ gap: 8px;
79
+ margin-bottom: 16px;
80
+ }
81
+ .toggle-button {
82
+ padding: 8px 16px;
83
+ border: 1px solid #ced4da;
84
+ background: #fff;
85
+ border-radius: 6px;
86
+ cursor: pointer;
87
+ font-size: 0.9rem;
88
+ transition: all 0.2s;
89
+ }
90
+ .toggle-button.active {
91
+ background: #667eea;
92
+ color: white;
93
+ border-color: #667eea;
94
+ }
95
+ .chart-container {
96
+ height: 300px;
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ background: white;
101
+ border-radius: 6px;
102
+ border: 1px solid #e9ecef;
103
+ }
104
+ .period-selector {
105
+ display: flex;
106
+ gap: 8px;
107
+ margin-bottom: 16px;
108
+ }
109
+ .period-button {
110
+ padding: 6px 12px;
111
+ border: 1px solid #ced4da;
112
+ background: #fff;
113
+ border-radius: 4px;
114
+ cursor: pointer;
115
+ font-size: 0.85rem;
116
+ transition: all 0.2s;
117
+ }
118
+ .period-button.active {
119
+ background: #667eea;
120
+ color: white;
121
+ border-color: #667eea;
122
+ }
123
+ .date-range-selector {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 8px;
127
+ margin-bottom: 16px;
128
+ }
129
+ .date-select {
130
+ padding: 6px 12px;
131
+ border: 1px solid #ced4da;
132
+ border-radius: 4px;
133
+ background: white;
134
+ font-size: 0.9rem;
135
+ }
136
+ `
137
+
138
+ @property({ type: String }) selectedRegion: string | null = null
139
+ @property({ type: String }) selectedChartType = 'boxplot'
140
+ @property({ type: String }) selectedPeriod = '월'
141
+ @property({ type: String }) startDate = '전체'
142
+ @property({ type: String }) endDate = '전체'
143
+
144
+ @state() private chartData: any[] = []
145
+ @state() private chartCategories: string[] = []
146
+ @state() private trendData: { date: string; value: number; color?: string }[] = []
147
+
148
+ connectedCallback() {
149
+ super.connectedCallback()
150
+ this.generateChartData()
151
+ this.generateTrendData()
152
+ }
153
+
154
+ private generateChartData() {
155
+ // 선택된 지역의 KPI 데이터 생성
156
+ const categories = ['일정 성과', '비용 성과', '품질 성과', '안전 성과', '환경 성과']
157
+ this.chartCategories = categories
158
+
159
+ if (this.selectedChartType === 'radar') {
160
+ // 레이더 차트용 데이터 생성
161
+ this.chartData = categories.map(category => ({
162
+ category,
163
+ value: Math.random() * 50 + 25, // 25-75 범위
164
+ group: this.selectedRegion || '전체'
165
+ }))
166
+ } else {
167
+ // 박스플롯용 데이터 생성
168
+ this.chartData = categories.map(category => {
169
+ const baseValue = Math.random() * 50 + 25 // 25-75 범위
170
+ const variation = Math.random() * 20 // 변동폭
171
+
172
+ // 각 카테고리별로 20개의 데이터 포인트 생성
173
+ const dataPoints: { value: number; group: string }[] = []
174
+ for (let i = 0; i < 20; i++) {
175
+ dataPoints.push({
176
+ value: baseValue + (Math.random() - 0.5) * variation,
177
+ group: category
178
+ })
179
+ }
180
+
181
+ // 통계값 계산
182
+ const values = dataPoints.map(d => d.value).sort((a, b) => a - b)
183
+ const min = Math.min(...values)
184
+ const max = Math.max(...values)
185
+ const q1 = values[Math.floor(values.length * 0.25)]
186
+ const q3 = values[Math.floor(values.length * 0.75)]
187
+ const median = values[Math.floor(values.length * 0.5)]
188
+ const mean = values.reduce((a, b) => a + b, 0) / values.length
189
+
190
+ return {
191
+ group: category,
192
+ min: min,
193
+ max: max,
194
+ q1: q1,
195
+ q3: q3,
196
+ median: median,
197
+ mean: mean,
198
+ value: mean
199
+ }
200
+ })
201
+ }
202
+ }
203
+
204
+ private generateTrendData() {
205
+ // 최근 30일간의 트렌드 데이터 생성
206
+ const today = new Date()
207
+ this.trendData = []
208
+
209
+ for (let i = 29; i >= 0; i--) {
210
+ const date = new Date(today)
211
+ date.setDate(date.getDate() - i)
212
+
213
+ // 기본값에서 약간의 변동을 주어 트렌드 생성
214
+ const baseValue = 50
215
+ const trend = Math.sin(i * 0.2) * 10 // 사인파로 변동
216
+ const noise = (Math.random() - 0.5) * 5 // 랜덤 노이즈
217
+ const value = Math.max(0, Math.min(100, baseValue + trend + noise))
218
+
219
+ this.trendData.push({
220
+ date: date.toISOString().split('T')[0], // YYYY-MM-DD 형식
221
+ value: Math.round(value),
222
+ color: value > 60 ? '#4caf50' : value > 40 ? '#ff9800' : '#f44336'
223
+ })
224
+ }
225
+ }
226
+
227
+ private onClose() {
228
+ this.dispatchEvent(
229
+ new CustomEvent('popup-close', {
230
+ bubbles: true,
231
+ composed: true
232
+ })
233
+ )
234
+ }
235
+
236
+ private onChartTypeChange(type: string) {
237
+ this.selectedChartType = type
238
+ this.generateChartData()
239
+ }
240
+
241
+ private onPeriodChange(period: string) {
242
+ this.dispatchEvent(
243
+ new CustomEvent('period-change', {
244
+ detail: { period },
245
+ bubbles: true,
246
+ composed: true
247
+ })
248
+ )
249
+ this.generateTrendData()
250
+ }
251
+
252
+ private onDateRangeChange() {
253
+ this.dispatchEvent(
254
+ new CustomEvent('date-range-change', {
255
+ detail: { startDate: this.startDate, endDate: this.endDate },
256
+ bubbles: true,
257
+ composed: true
258
+ })
259
+ )
260
+ }
261
+
262
+ render() {
263
+ if (!this.selectedRegion) {
264
+ return nothing
265
+ }
266
+
267
+ return html`
268
+ <div class="popup-header">
269
+ <div class="popup-title">${this.selectedRegion} KPI</div>
270
+ <button class="popup-close" @click=${this.onClose}>×</button>
271
+ </div>
272
+ <div class="popup-content">
273
+ <!-- 종합 성과 -->
274
+ <div class="chart-section">
275
+ <div class="sub-title">종합 성과</div>
276
+ <div class="chart-toggle">
277
+ <button
278
+ class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
279
+ @click=${() => this.onChartTypeChange('boxplot')}
280
+ >
281
+ 박스플롯
282
+ </button>
283
+ <button
284
+ class="toggle-button ${this.selectedChartType === 'radar' ? 'active' : ''}"
285
+ @click=${() => this.onChartTypeChange('radar')}
286
+ >
287
+ 레이더차트
288
+ </button>
289
+ </div>
290
+ <div class="chart-container">
291
+ ${this.selectedChartType === 'boxplot'
292
+ ? html`
293
+ <kpi-boxplot-chart
294
+ .data=${this.chartData}
295
+ .groups=${this.chartCategories}
296
+ .valueKey=${'value'}
297
+ .currentGroup=${this.selectedRegion || '전체'}
298
+ ></kpi-boxplot-chart>
299
+ `
300
+ : html`
301
+ <kpi-radar-chart
302
+ .data=${this.chartData}
303
+ .categories=${this.chartCategories}
304
+ .valueKey=${'value'}
305
+ .currentGroup=${this.selectedRegion || '전체'}
306
+ ></kpi-radar-chart>
307
+ `}
308
+ </div>
309
+ </div>
310
+
311
+ <!-- 기간별 성과 추이 -->
312
+ <div class="trend-section">
313
+ <div class="sub-title">기간별 성과 추이</div>
314
+ <div class="trend-chart-container" style="height: 200px; margin-bottom: 16px;">
315
+ <kpi-trend-chart
316
+ .data=${this.trendData}
317
+ .lineColor=${'#2196f3'}
318
+ .strokeWidth=${2}
319
+ .showPoints=${true}
320
+ .pointRadius=${4}
321
+ ></kpi-trend-chart>
322
+ </div>
323
+ <div class="trend-table">
324
+ <table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">
325
+ <thead>
326
+ <tr style="border-bottom: 2px solid #f1f3f4; background-color: #f8f9fa;">
327
+ <th style="padding: 8px; text-align: left; font-weight: 600;">날짜</th>
328
+ <th style="padding: 8px; text-align: right; font-weight: 600;">성과</th>
329
+ <th style="padding: 8px; text-align: center; font-weight: 600;">추이</th>
330
+ </tr>
331
+ </thead>
332
+ <tbody>
333
+ ${this.trendData.slice(-10).map(
334
+ (item: any) => html`
335
+ <tr style="border-bottom: 1px solid #f1f3f4;">
336
+ <td style="padding: 8px; font-size: 0.85rem;">${item.date}</td>
337
+ <td style="padding: 8px; text-align: right; font-size: 0.85rem; font-weight: 600;">
338
+ ${item.value}
339
+ </td>
340
+ <td style="padding: 8px; text-align: center; font-size: 0.85rem;">
341
+ <span style="color: ${item.color};">
342
+ ${item.value > 60 ? '▲' : item.value > 40 ? '▲' : '▼'}
343
+ </span>
344
+ </td>
345
+ </tr>
346
+ `
347
+ )}
348
+ </tbody>
349
+ </table>
350
+ </div>
351
+ </div>
352
+ </div>
353
+ `
354
+ }
355
+ }