@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,385 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { html, css } from 'lit';
3
+ import { customElement, state } from 'lit/decorators.js';
4
+ import { LitElement } from 'lit';
5
+ import { client } from '@operato/graphql';
6
+ import gql from 'graphql-tag';
7
+ let KpiLevel2Comparison = class KpiLevel2Comparison extends LitElement {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.loading = true;
11
+ this.error = '';
12
+ this.radarData = [];
13
+ this.boxplotData = [];
14
+ this.categories = [];
15
+ this.currentMonth = '';
16
+ this.totalCategories = 0;
17
+ this.totalKpis = 0;
18
+ this.averageScore = 0;
19
+ }
20
+ static { this.styles = css `
21
+ :host {
22
+ display: block;
23
+ }
24
+ .comparison-container {
25
+ background: #fff;
26
+ border-radius: 16px;
27
+ padding: 24px;
28
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
29
+ border: 1px solid #e0e0e0;
30
+ width: 100%;
31
+ }
32
+ .comparison-title {
33
+ font-size: 1.3rem;
34
+ font-weight: bold;
35
+ margin-bottom: 20px;
36
+ color: #333;
37
+ text-align: center;
38
+ }
39
+ .charts-section {
40
+ display: flex;
41
+ gap: 24px;
42
+ margin-bottom: 20px;
43
+ }
44
+ .chart-card {
45
+ flex: 1;
46
+ background: #f8f9fa;
47
+ border-radius: 12px;
48
+ padding: 20px;
49
+ border: 1px solid #e9ecef;
50
+ }
51
+ .chart-title {
52
+ font-size: 1.1rem;
53
+ font-weight: 600;
54
+ margin-bottom: 16px;
55
+ color: #495057;
56
+ text-align: center;
57
+ }
58
+ .chart-container {
59
+ height: 300px;
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ }
64
+ .loading {
65
+ color: #666;
66
+ font-size: 1rem;
67
+ }
68
+ .error {
69
+ color: #d32f2f;
70
+ font-size: 1rem;
71
+ }
72
+ .no-data {
73
+ color: #666;
74
+ font-size: 1rem;
75
+ text-align: center;
76
+ }
77
+ .summary-info {
78
+ display: flex;
79
+ justify-content: space-around;
80
+ padding: 16px;
81
+ background: #f8f9fa;
82
+ border-radius: 8px;
83
+ margin-top: 16px;
84
+ }
85
+ .summary-item {
86
+ text-align: center;
87
+ }
88
+ .summary-value {
89
+ font-size: 1.2rem;
90
+ font-weight: bold;
91
+ color: #333;
92
+ margin-bottom: 4px;
93
+ }
94
+ .summary-label {
95
+ font-size: 0.9rem;
96
+ color: #666;
97
+ }
98
+ `; }
99
+ connectedCallback() {
100
+ super.connectedCallback();
101
+ this.currentMonth = this.getCurrentMonth();
102
+ this.fetchCategoryComparison();
103
+ }
104
+ getCurrentMonth() {
105
+ const now = new Date();
106
+ const year = now.getFullYear();
107
+ const month = String(now.getMonth() + 1).padStart(2, '0');
108
+ return `${year}-${month}`;
109
+ }
110
+ async fetchCategoryComparison() {
111
+ this.loading = true;
112
+ this.error = '';
113
+ try {
114
+ const response = await client.query({
115
+ query: gql `
116
+ query {
117
+ kpiStatistics {
118
+ items {
119
+ id
120
+ valueDate
121
+ periodType
122
+ mean
123
+ median
124
+ standardDeviation
125
+ minimum
126
+ maximum
127
+ percentile25
128
+ percentile75
129
+ kpi {
130
+ id
131
+ name
132
+ targetValue
133
+ unit
134
+ category {
135
+ id
136
+ name
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ `
143
+ });
144
+ const statistics = response.data.kpiStatistics.items || [];
145
+ // MONTH 타입의 현재 월 데이터만 필터링
146
+ const currentMonthStats = statistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.currentMonth);
147
+ if (currentMonthStats.length === 0) {
148
+ this.error = '현재 월의 데이터가 없습니다.';
149
+ return;
150
+ }
151
+ // 카테고리별로 데이터 그룹화
152
+ const categoryStats = new Map();
153
+ currentMonthStats.forEach(stat => {
154
+ if (stat.kpi?.category?.name) {
155
+ const categoryName = stat.kpi.category.name;
156
+ if (!categoryStats.has(categoryName)) {
157
+ categoryStats.set(categoryName, []);
158
+ }
159
+ categoryStats.get(categoryName).push(stat);
160
+ }
161
+ });
162
+ this.categories = Array.from(categoryStats.keys());
163
+ this.totalCategories = this.categories.length;
164
+ this.totalKpis = currentMonthStats.length;
165
+ // 레이더 차트 데이터 생성
166
+ this.generateRadarData(categoryStats);
167
+ // 박스플롯 데이터 생성
168
+ this.generateBoxplotData(categoryStats);
169
+ // 평균 점수 계산
170
+ const scores = currentMonthStats.map(stat => {
171
+ const mean = stat.mean || 0;
172
+ const targetValue = stat.kpi?.targetValue || 100;
173
+ if (targetValue === 0)
174
+ return 0;
175
+ const achievement = Math.min((mean / targetValue) * 100, 100);
176
+ return Math.max(achievement, 0);
177
+ });
178
+ this.averageScore = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length);
179
+ }
180
+ catch (e) {
181
+ console.error('카테고리 비교 데이터를 불러오지 못했습니다:', e);
182
+ this.error = '카테고리 비교 데이터를 불러오지 못했습니다.';
183
+ }
184
+ finally {
185
+ this.loading = false;
186
+ }
187
+ }
188
+ generateRadarData(categoryStats) {
189
+ const result = [];
190
+ this.categories.forEach(category => {
191
+ const stats = categoryStats.get(category);
192
+ const means = stats.map(s => s.mean || 0).filter(v => v > 0);
193
+ const medians = stats.map(s => s.median || 0).filter(v => v > 0);
194
+ const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0);
195
+ if (means.length > 0) {
196
+ result.push({
197
+ group: '평균',
198
+ category,
199
+ value: means.reduce((a, b) => a + b, 0) / means.length
200
+ });
201
+ }
202
+ if (medians.length > 0) {
203
+ result.push({
204
+ group: '중앙값',
205
+ category,
206
+ value: medians.reduce((a, b) => a + b, 0) / medians.length
207
+ });
208
+ }
209
+ if (stdDevs.length > 0) {
210
+ result.push({
211
+ group: '표준편차',
212
+ category,
213
+ value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length
214
+ });
215
+ }
216
+ });
217
+ this.radarData = result;
218
+ }
219
+ generateBoxplotData(categoryStats) {
220
+ const result = [];
221
+ this.categories.forEach(category => {
222
+ const stats = categoryStats.get(category);
223
+ // 각 KPI의 평균값들을 수집
224
+ const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0);
225
+ const allMedians = stats.map(s => s.median || 0).filter(v => v > 0);
226
+ const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0);
227
+ const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0);
228
+ const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0);
229
+ const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0);
230
+ if (allMeans.length > 0) {
231
+ const sortedMeans = [...allMeans].sort((a, b) => a - b);
232
+ const min = sortedMeans[0];
233
+ const max = sortedMeans[sortedMeans.length - 1];
234
+ const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length;
235
+ const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean;
236
+ const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)];
237
+ const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)];
238
+ result.push({
239
+ group: category,
240
+ min,
241
+ q1,
242
+ median,
243
+ q3,
244
+ max,
245
+ mean,
246
+ value: mean
247
+ });
248
+ }
249
+ });
250
+ this.boxplotData = result;
251
+ }
252
+ render() {
253
+ if (this.loading) {
254
+ return html `
255
+ <div class="comparison-container">
256
+ <div class="comparison-title">카테고리 비교 분석</div>
257
+ <div class="charts-section">
258
+ <div class="chart-card">
259
+ <div class="chart-title">레이더 차트</div>
260
+ <div class="chart-container">
261
+ <div class="loading">데이터 로딩 중...</div>
262
+ </div>
263
+ </div>
264
+ <div class="chart-card">
265
+ <div class="chart-title">박스플롯</div>
266
+ <div class="chart-container">
267
+ <div class="loading">데이터 로딩 중...</div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ `;
273
+ }
274
+ if (this.error) {
275
+ return html `
276
+ <div class="comparison-container">
277
+ <div class="comparison-title">카테고리 비교 분석</div>
278
+ <div class="charts-section">
279
+ <div class="chart-card">
280
+ <div class="chart-title">레이더 차트</div>
281
+ <div class="chart-container">
282
+ <div class="error">${this.error}</div>
283
+ </div>
284
+ </div>
285
+ <div class="chart-card">
286
+ <div class="chart-title">박스플롯</div>
287
+ <div class="chart-container">
288
+ <div class="error">${this.error}</div>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ `;
294
+ }
295
+ return html `
296
+ <div class="comparison-container">
297
+ <div class="comparison-title">카테고리 비교 분석 (${this.currentMonth})</div>
298
+
299
+ <div class="charts-section">
300
+ <div class="chart-card">
301
+ <div class="chart-title">레이더 차트</div>
302
+ <div class="chart-container">
303
+ ${this.radarData.length > 0
304
+ ? html `<kpi-radar-chart
305
+ .data=${this.radarData}
306
+ .categories=${this.categories}
307
+ .currentGroup=${'평균'}
308
+ ></kpi-radar-chart>`
309
+ : html `<div class="no-data">데이터가 없습니다.</div>`}
310
+ </div>
311
+ </div>
312
+
313
+ <div class="chart-card">
314
+ <div class="chart-title">박스플롯</div>
315
+ <div class="chart-container">
316
+ ${this.boxplotData.length > 0
317
+ ? html `<kpi-boxplot-chart
318
+ .data=${this.boxplotData}
319
+ .groups=${this.categories}
320
+ .currentGroup=${'평균'}
321
+ ></kpi-boxplot-chart>`
322
+ : html `<div class="no-data">데이터가 없습니다.</div>`}
323
+ </div>
324
+ </div>
325
+ </div>
326
+
327
+ <div class="summary-info">
328
+ <div class="summary-item">
329
+ <div class="summary-value">${this.totalCategories}</div>
330
+ <div class="summary-label">카테고리</div>
331
+ </div>
332
+ <div class="summary-item">
333
+ <div class="summary-value">${this.totalKpis}</div>
334
+ <div class="summary-label">KPI</div>
335
+ </div>
336
+ <div class="summary-item">
337
+ <div class="summary-value">${this.averageScore}</div>
338
+ <div class="summary-label">평균 점수</div>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ `;
343
+ }
344
+ };
345
+ __decorate([
346
+ state(),
347
+ __metadata("design:type", Object)
348
+ ], KpiLevel2Comparison.prototype, "loading", void 0);
349
+ __decorate([
350
+ state(),
351
+ __metadata("design:type", Object)
352
+ ], KpiLevel2Comparison.prototype, "error", void 0);
353
+ __decorate([
354
+ state(),
355
+ __metadata("design:type", Array)
356
+ ], KpiLevel2Comparison.prototype, "radarData", void 0);
357
+ __decorate([
358
+ state(),
359
+ __metadata("design:type", Array)
360
+ ], KpiLevel2Comparison.prototype, "boxplotData", void 0);
361
+ __decorate([
362
+ state(),
363
+ __metadata("design:type", Array)
364
+ ], KpiLevel2Comparison.prototype, "categories", void 0);
365
+ __decorate([
366
+ state(),
367
+ __metadata("design:type", Object)
368
+ ], KpiLevel2Comparison.prototype, "currentMonth", void 0);
369
+ __decorate([
370
+ state(),
371
+ __metadata("design:type", Object)
372
+ ], KpiLevel2Comparison.prototype, "totalCategories", void 0);
373
+ __decorate([
374
+ state(),
375
+ __metadata("design:type", Object)
376
+ ], KpiLevel2Comparison.prototype, "totalKpis", void 0);
377
+ __decorate([
378
+ state(),
379
+ __metadata("design:type", Object)
380
+ ], KpiLevel2Comparison.prototype, "averageScore", void 0);
381
+ KpiLevel2Comparison = __decorate([
382
+ customElement('kpi-level2-comparison')
383
+ ], KpiLevel2Comparison);
384
+ export { KpiLevel2Comparison };
385
+ //# sourceMappingURL=kpi-level2-comparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kpi-level2-comparison.js","sourceRoot":"","sources":["../../../../client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { LitElement } from 'lit'\nimport { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\n\n@customElement('kpi-level2-comparison')\nexport class KpiLevel2Comparison extends LitElement {\n static styles = css`\n :host {\n display: block;\n }\n .comparison-container {\n background: #fff;\n border-radius: 16px;\n padding: 24px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n border: 1px solid #e0e0e0;\n width: 100%;\n }\n .comparison-title {\n font-size: 1.3rem;\n font-weight: bold;\n margin-bottom: 20px;\n color: #333;\n text-align: center;\n }\n .charts-section {\n display: flex;\n gap: 24px;\n margin-bottom: 20px;\n }\n .chart-card {\n flex: 1;\n background: #f8f9fa;\n border-radius: 12px;\n padding: 20px;\n border: 1px solid #e9ecef;\n }\n .chart-title {\n font-size: 1.1rem;\n font-weight: 600;\n margin-bottom: 16px;\n color: #495057;\n text-align: center;\n }\n .chart-container {\n height: 300px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .loading {\n color: #666;\n font-size: 1rem;\n }\n .error {\n color: #d32f2f;\n font-size: 1rem;\n }\n .no-data {\n color: #666;\n font-size: 1rem;\n text-align: center;\n }\n .summary-info {\n display: flex;\n justify-content: space-around;\n padding: 16px;\n background: #f8f9fa;\n border-radius: 8px;\n margin-top: 16px;\n }\n .summary-item {\n text-align: center;\n }\n .summary-value {\n font-size: 1.2rem;\n font-weight: bold;\n color: #333;\n margin-bottom: 4px;\n }\n .summary-label {\n font-size: 0.9rem;\n color: #666;\n }\n `\n\n @state() loading = true\n @state() error = ''\n @state() radarData: any[] = []\n @state() boxplotData: any[] = []\n @state() categories: string[] = []\n @state() currentMonth = ''\n @state() totalCategories = 0\n @state() totalKpis = 0\n @state() averageScore = 0\n\n connectedCallback() {\n super.connectedCallback()\n this.currentMonth = this.getCurrentMonth()\n this.fetchCategoryComparison()\n }\n\n private getCurrentMonth(): string {\n const now = new Date()\n const year = now.getFullYear()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n return `${year}-${month}`\n }\n\n async fetchCategoryComparison() {\n this.loading = true\n this.error = ''\n\n try {\n const response = await client.query({\n query: gql`\n query {\n kpiStatistics {\n items {\n id\n valueDate\n periodType\n mean\n median\n standardDeviation\n minimum\n maximum\n percentile25\n percentile75\n kpi {\n id\n name\n targetValue\n unit\n category {\n id\n name\n }\n }\n }\n }\n }\n `\n })\n\n const statistics = response.data.kpiStatistics.items || []\n\n // MONTH 타입의 현재 월 데이터만 필터링\n const currentMonthStats = statistics.filter(\n stat => stat.periodType === 'MONTH' && stat.valueDate === this.currentMonth\n )\n\n if (currentMonthStats.length === 0) {\n this.error = '현재 월의 데이터가 없습니다.'\n return\n }\n\n // 카테고리별로 데이터 그룹화\n const categoryStats = new Map<string, any[]>()\n\n currentMonthStats.forEach(stat => {\n if (stat.kpi?.category?.name) {\n const categoryName = stat.kpi.category.name\n if (!categoryStats.has(categoryName)) {\n categoryStats.set(categoryName, [])\n }\n categoryStats.get(categoryName)!.push(stat)\n }\n })\n\n this.categories = Array.from(categoryStats.keys())\n this.totalCategories = this.categories.length\n this.totalKpis = currentMonthStats.length\n\n // 레이더 차트 데이터 생성\n this.generateRadarData(categoryStats)\n\n // 박스플롯 데이터 생성\n this.generateBoxplotData(categoryStats)\n\n // 평균 점수 계산\n const scores = currentMonthStats.map(stat => {\n const mean = stat.mean || 0\n const targetValue = stat.kpi?.targetValue || 100\n if (targetValue === 0) return 0\n const achievement = Math.min((mean / targetValue) * 100, 100)\n return Math.max(achievement, 0)\n })\n this.averageScore = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length)\n } catch (e) {\n console.error('카테고리 비교 데이터를 불러오지 못했습니다:', e)\n this.error = '카테고리 비교 데이터를 불러오지 못했습니다.'\n } finally {\n this.loading = false\n }\n }\n\n private generateRadarData(categoryStats: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryStats.get(category)!\n const means = stats.map(s => s.mean || 0).filter(v => v > 0)\n const medians = stats.map(s => s.median || 0).filter(v => v > 0)\n const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0)\n\n if (means.length > 0) {\n result.push({\n group: '평균',\n category,\n value: means.reduce((a, b) => a + b, 0) / means.length\n })\n }\n if (medians.length > 0) {\n result.push({\n group: '중앙값',\n category,\n value: medians.reduce((a, b) => a + b, 0) / medians.length\n })\n }\n if (stdDevs.length > 0) {\n result.push({\n group: '표준편차',\n category,\n value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length\n })\n }\n })\n\n this.radarData = result\n }\n\n private generateBoxplotData(categoryStats: Map<string, any[]>) {\n const result: any[] = []\n\n this.categories.forEach(category => {\n const stats = categoryStats.get(category)!\n\n // 각 KPI의 평균값들을 수집\n const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0)\n const allMedians = stats.map(s => s.median || 0).filter(v => v > 0)\n const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0)\n const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0)\n const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0)\n const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0)\n\n if (allMeans.length > 0) {\n const sortedMeans = [...allMeans].sort((a, b) => a - b)\n const min = sortedMeans[0]\n const max = sortedMeans[sortedMeans.length - 1]\n const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length\n const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean\n\n const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)]\n const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)]\n\n result.push({\n group: category,\n min,\n q1,\n median,\n q3,\n max,\n mean,\n value: mean\n })\n }\n })\n\n this.boxplotData = result\n }\n\n render() {\n if (this.loading) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"loading\">데이터 로딩 중...</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n if (this.error) {\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석</div>\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n <div class=\"error\">${this.error}</div>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n return html`\n <div class=\"comparison-container\">\n <div class=\"comparison-title\">카테고리 비교 분석 (${this.currentMonth})</div>\n\n <div class=\"charts-section\">\n <div class=\"chart-card\">\n <div class=\"chart-title\">레이더 차트</div>\n <div class=\"chart-container\">\n ${this.radarData.length > 0\n ? html`<kpi-radar-chart\n .data=${this.radarData}\n .categories=${this.categories}\n .currentGroup=${'평균'}\n ></kpi-radar-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n\n <div class=\"chart-card\">\n <div class=\"chart-title\">박스플롯</div>\n <div class=\"chart-container\">\n ${this.boxplotData.length > 0\n ? html`<kpi-boxplot-chart\n .data=${this.boxplotData}\n .groups=${this.categories}\n .currentGroup=${'평균'}\n ></kpi-boxplot-chart>`\n : html`<div class=\"no-data\">데이터가 없습니다.</div>`}\n </div>\n </div>\n </div>\n\n <div class=\"summary-info\">\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalCategories}</div>\n <div class=\"summary-label\">카테고리</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.totalKpis}</div>\n <div class=\"summary-label\">KPI</div>\n </div>\n <div class=\"summary-item\">\n <div class=\"summary-value\">${this.averageScore}</div>\n <div class=\"summary-label\">평균 점수</div>\n </div>\n </div>\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { LitElement } from 'lit';
2
+ export declare class KpiLevel3Comparison extends LitElement {
3
+ static styles: import("lit").CSSResult;
4
+ loading: boolean;
5
+ error: string;
6
+ radarData: any[];
7
+ boxplotData: any[];
8
+ kpis: string[];
9
+ availableCategories: string[];
10
+ selectedCategory: string;
11
+ currentMonth: string;
12
+ totalKpis: number;
13
+ averageScore: number;
14
+ categoryStats: Map<string, any[]>;
15
+ connectedCallback(): void;
16
+ private getCurrentMonth;
17
+ fetchKpiComparison(): Promise<void>;
18
+ private onCategoryChange;
19
+ private generateKpiCharts;
20
+ private generateRadarData;
21
+ private generateBoxplotData;
22
+ render(): import("lit-html").TemplateResult<1>;
23
+ }