@things-factory/kpi 9.0.28 → 9.0.30
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.
- package/client/google-map/common-google-map.ts +332 -0
- package/client/google-map/google-map-loader.ts +29 -0
- package/client/google-map/script-loader.ts +173 -0
- package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +248 -0
- package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +369 -0
- package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +443 -0
- package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +73 -0
- package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +222 -0
- package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +786 -0
- package/client/pages/kpi-dashboard/kpi-dashboard.ts +416 -0
- package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +761 -0
- package/client/pages/kpi-statistic/kpi-statistic-importer.ts +90 -0
- package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +505 -0
- package/client/route.ts +12 -0
- package/dist-client/google-map/common-google-map.d.ts +34 -0
- package/dist-client/google-map/common-google-map.js +300 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +22 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/google-map/script-loader.d.ts +3 -0
- package/dist-client/google-map/script-loader.js +144 -0
- package/dist-client/google-map/script-loader.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +279 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +19 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +385 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +23 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +465 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +79 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +23 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +223 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +38 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +813 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +21 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +398 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +53 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +718 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-importer.d.ts +23 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-importer.js +92 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-importer.js.map +1 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +66 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +487 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +9 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/index.d.ts +1 -0
- package/dist-server/migrations/index.js +12 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/service/index.d.ts +3 -2
- package/dist-server/service/index.js +4 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/kpi-mutation.js +9 -0
- package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
- package/dist-server/service/kpi/kpi-query.js +2 -0
- package/dist-server/service/kpi/kpi-query.js.map +1 -1
- package/dist-server/service/kpi-alert/kpi-alert-query.js +1 -0
- package/dist-server/service/kpi-alert/kpi-alert-query.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-mutation.js +4 -0
- package/dist-server/service/kpi-category/kpi-category-mutation.js.map +1 -1
- package/dist-server/service/kpi-category/kpi-category-query.js +2 -0
- package/dist-server/service/kpi-category/kpi-category-query.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js +6 -0
- package/dist-server/service/kpi-metric/kpi-metric-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric/kpi-metric-query.js +2 -0
- package/dist-server/service/kpi-metric/kpi-metric-query.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +7 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +2 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
- package/dist-server/service/kpi-statistic/index.d.ts +6 -0
- package/dist-server/service/kpi-statistic/index.js +10 -0
- package/dist-server/service/kpi-statistic/index.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +10 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +198 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +13 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-type.d.ts +59 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-type.js +200 -0
- package/dist-server/service/kpi-statistic/kpi-statistic-type.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +38 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.js +177 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.js +9 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.js +2 -0
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/helps/kpi/kpi-statistic.md +160 -0
- package/package.json +2 -2
- package/server/index.ts +1 -0
- package/server/migrations/index.ts +9 -0
- package/server/service/index.ts +8 -0
- package/server/service/kpi/kpi-mutation.ts +9 -0
- package/server/service/kpi/kpi-query.ts +2 -0
- package/server/service/kpi-alert/kpi-alert-query.ts +2 -1
- package/server/service/kpi-category/kpi-category-mutation.ts +4 -0
- package/server/service/kpi-category/kpi-category-query.ts +2 -0
- package/server/service/kpi-metric/kpi-metric-mutation.ts +6 -0
- package/server/service/kpi-metric/kpi-metric-query.ts +2 -0
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +7 -0
- package/server/service/kpi-metric-value/kpi-metric-value-query.ts +3 -1
- package/server/service/kpi-statistic/index.ts +7 -0
- package/server/service/kpi-statistic/kpi-statistic-mutation.ts +194 -0
- package/server/service/kpi-statistic/kpi-statistic-query.ts +59 -0
- package/server/service/kpi-statistic/kpi-statistic-type.ts +152 -0
- package/server/service/kpi-statistic/kpi-statistic.ts +167 -0
- package/server/service/kpi-value/kpi-value-mutation.ts +9 -0
- package/server/service/kpi-value/kpi-value-query.ts +2 -0
- package/things-factory.config.js +4 -1
- package/translations/en.json +24 -1
- package/translations/ja.json +43 -20
- package/translations/ko.json +30 -7
- package/translations/ms.json +35 -12
- package/translations/zh.json +39 -16
|
@@ -16,6 +16,11 @@ import './kpi-alert-panel'
|
|
|
16
16
|
import '../../charts/kpi-radar-chart'
|
|
17
17
|
import '../../charts/kpi-boxplot-chart'
|
|
18
18
|
|
|
19
|
+
// 3레벨 KPI 플로팅 컴포넌트들
|
|
20
|
+
import './cards/kpi-level1-card'
|
|
21
|
+
import './cards/kpi-level2-comparison'
|
|
22
|
+
import './cards/kpi-level3-comparison'
|
|
23
|
+
|
|
19
24
|
@customElement('kpi-dashboard')
|
|
20
25
|
export class KpiDashboardPage extends PageView {
|
|
21
26
|
static styles = [
|
|
@@ -32,6 +37,7 @@ export class KpiDashboardPage extends PageView {
|
|
|
32
37
|
}
|
|
33
38
|
.sample-charts-section {
|
|
34
39
|
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
35
41
|
gap: 40px;
|
|
36
42
|
margin-bottom: 48px;
|
|
37
43
|
align-items: flex-start;
|
|
@@ -109,6 +115,10 @@ export class KpiDashboardPage extends PageView {
|
|
|
109
115
|
@state() showHistoryModal: boolean = false
|
|
110
116
|
@state() modalHistories: any[] = []
|
|
111
117
|
@state() modalKpiName: string = ''
|
|
118
|
+
@state() kpiStatistics: any[] = [] // 실제 KPI 통계 데이터
|
|
119
|
+
@state() statisticsLoading = true // 통계 데이터 로딩 상태
|
|
120
|
+
@state() selectedPeriodType: string = 'MONTH' // 선택된 기간 타입 (MONTH로 고정)
|
|
121
|
+
@state() selectedValueDate: string = '' // 선택된 값 날짜
|
|
112
122
|
|
|
113
123
|
// 샘플 데이터
|
|
114
124
|
private get sampleCategories(): string[] {
|
|
@@ -231,14 +241,225 @@ export class KpiDashboardPage extends PageView {
|
|
|
231
241
|
return 'A'
|
|
232
242
|
}
|
|
233
243
|
|
|
244
|
+
// 현재 월을 YYYY-MM 형식으로 반환
|
|
245
|
+
private get currentMonth(): string {
|
|
246
|
+
const now = new Date()
|
|
247
|
+
const year = now.getFullYear()
|
|
248
|
+
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
249
|
+
return `${year}-${month}`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 필터링된 KPI 통계 데이터
|
|
253
|
+
private get filteredKpiStatistics(): any[] {
|
|
254
|
+
if (!this.kpiStatistics || this.kpiStatistics.length === 0) return []
|
|
255
|
+
|
|
256
|
+
return this.kpiStatistics.filter(stat => stat.periodType === 'MONTH' && stat.valueDate === this.selectedValueDate)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 사용 가능한 기간 타입들 (MONTH로 고정)
|
|
260
|
+
private get availablePeriodTypes(): string[] {
|
|
261
|
+
return ['MONTH']
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// MONTH 기간에 사용 가능한 날짜들
|
|
265
|
+
private get availableValueDates(): string[] {
|
|
266
|
+
if (!this.kpiStatistics || this.kpiStatistics.length === 0) return []
|
|
267
|
+
|
|
268
|
+
const valueDates = new Set<string>()
|
|
269
|
+
this.kpiStatistics.forEach(stat => {
|
|
270
|
+
if (stat.periodType === 'MONTH' && stat.valueDate) {
|
|
271
|
+
valueDates.add(stat.valueDate)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
return Array.from(valueDates).sort().reverse() // 최신 날짜부터 정렬
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 통계 요약 정보
|
|
279
|
+
private get statisticsSummary(): any {
|
|
280
|
+
const filteredStats = this.filteredKpiStatistics
|
|
281
|
+
if (filteredStats.length === 0) return null
|
|
282
|
+
|
|
283
|
+
const totalKpis = filteredStats.length
|
|
284
|
+
const categories = new Set(filteredStats.map(s => s.kpi?.category?.name).filter(Boolean))
|
|
285
|
+
const totalCategories = categories.size
|
|
286
|
+
|
|
287
|
+
const means = filteredStats.map(s => s.mean || 0).filter(v => v > 0)
|
|
288
|
+
const medians = filteredStats.map(s => s.median || 0).filter(v => v > 0)
|
|
289
|
+
const stdDevs = filteredStats.map(s => s.standardDeviation || 0).filter(v => v > 0)
|
|
290
|
+
|
|
291
|
+
const avgMean = means.length > 0 ? means.reduce((a, b) => a + b, 0) / means.length : 0
|
|
292
|
+
const avgMedian = medians.length > 0 ? medians.reduce((a, b) => a + b, 0) / medians.length : 0
|
|
293
|
+
const avgStdDev = stdDevs.length > 0 ? stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length : 0
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
totalKpis,
|
|
297
|
+
totalCategories,
|
|
298
|
+
avgMean: avgMean.toFixed(2),
|
|
299
|
+
avgMedian: avgMedian.toFixed(2),
|
|
300
|
+
avgStdDev: avgStdDev.toFixed(2),
|
|
301
|
+
periodType: this.selectedPeriodType,
|
|
302
|
+
valueDate: this.selectedValueDate
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 기간 타입 변경 핸들러 (사용하지 않음 - MONTH로 고정)
|
|
307
|
+
private _onPeriodTypeChange(event: Event) {
|
|
308
|
+
// MONTH로 고정되어 있으므로 변경하지 않음
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 날짜 변경 핸들러
|
|
312
|
+
private _onValueDateChange(event: Event) {
|
|
313
|
+
const target = event.target as HTMLSelectElement
|
|
314
|
+
this.selectedValueDate = target.value
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 실제 KPI 통계 데이터를 기반으로 한 레이더 차트 데이터
|
|
318
|
+
private get realKpiRadarData(): any[] {
|
|
319
|
+
const filteredStats = this.filteredKpiStatistics
|
|
320
|
+
if (filteredStats.length === 0) return []
|
|
321
|
+
|
|
322
|
+
// 카테고리별로 통계 데이터 그룹화
|
|
323
|
+
const categoryStats = new Map<string, any[]>()
|
|
324
|
+
|
|
325
|
+
filteredStats.forEach(stat => {
|
|
326
|
+
if (stat.kpi?.category?.name) {
|
|
327
|
+
const categoryName = stat.kpi.category.name
|
|
328
|
+
if (!categoryStats.has(categoryName)) {
|
|
329
|
+
categoryStats.set(categoryName, [])
|
|
330
|
+
}
|
|
331
|
+
categoryStats.get(categoryName)!.push(stat)
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
// 각 카테고리별로 평균, 중앙값, 표준편차 계산
|
|
336
|
+
const result: any[] = []
|
|
337
|
+
const categories = Array.from(categoryStats.keys())
|
|
338
|
+
|
|
339
|
+
categories.forEach(category => {
|
|
340
|
+
const stats = categoryStats.get(category)!
|
|
341
|
+
const means = stats.map(s => s.mean || 0).filter(v => v > 0)
|
|
342
|
+
const medians = stats.map(s => s.median || 0).filter(v => v > 0)
|
|
343
|
+
const stdDevs = stats.map(s => s.standardDeviation || 0).filter(v => v > 0)
|
|
344
|
+
|
|
345
|
+
if (means.length > 0) {
|
|
346
|
+
result.push({ group: '평균', category, value: means.reduce((a, b) => a + b, 0) / means.length })
|
|
347
|
+
}
|
|
348
|
+
if (medians.length > 0) {
|
|
349
|
+
result.push({ group: '중앙값', category, value: medians.reduce((a, b) => a + b, 0) / medians.length })
|
|
350
|
+
}
|
|
351
|
+
if (stdDevs.length > 0) {
|
|
352
|
+
result.push({ group: '표준편차', category, value: stdDevs.reduce((a, b) => a + b, 0) / stdDevs.length })
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
return result
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private get realKpiRadarCategories(): string[] {
|
|
360
|
+
const filteredStats = this.filteredKpiStatistics
|
|
361
|
+
if (filteredStats.length === 0) return []
|
|
362
|
+
|
|
363
|
+
const categories = new Set<string>()
|
|
364
|
+
filteredStats.forEach(stat => {
|
|
365
|
+
if (stat.kpi?.category?.name) {
|
|
366
|
+
categories.add(stat.kpi.category.name)
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
return Array.from(categories)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private get realKpiRadarGroups(): string[] {
|
|
374
|
+
return ['평균', '중앙값', '표준편차']
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 실제 KPI 통계 데이터를 기반으로 한 박스플롯 데이터
|
|
378
|
+
private get realKpiBoxplotData(): any[] {
|
|
379
|
+
const filteredStats = this.filteredKpiStatistics
|
|
380
|
+
if (filteredStats.length === 0) return []
|
|
381
|
+
|
|
382
|
+
// 카테고리별로 통계 데이터 그룹화
|
|
383
|
+
const categoryStats = new Map<string, any[]>()
|
|
384
|
+
|
|
385
|
+
filteredStats.forEach(stat => {
|
|
386
|
+
if (stat.kpi?.category?.name) {
|
|
387
|
+
const categoryName = stat.kpi.category.name
|
|
388
|
+
if (!categoryStats.has(categoryName)) {
|
|
389
|
+
categoryStats.set(categoryName, [])
|
|
390
|
+
}
|
|
391
|
+
categoryStats.get(categoryName)!.push(stat)
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const result: any[] = []
|
|
396
|
+
const categories = Array.from(categoryStats.keys())
|
|
397
|
+
|
|
398
|
+
categories.forEach(category => {
|
|
399
|
+
const stats = categoryStats.get(category)!
|
|
400
|
+
|
|
401
|
+
// 각 KPI의 통계값들을 수집
|
|
402
|
+
const allMeans = stats.map(s => s.mean || 0).filter(v => v > 0)
|
|
403
|
+
const allMedians = stats.map(s => s.median || 0).filter(v => v > 0)
|
|
404
|
+
const allMins = stats.map(s => s.minimum || 0).filter(v => v > 0)
|
|
405
|
+
const allMaxs = stats.map(s => s.maximum || 0).filter(v => v > 0)
|
|
406
|
+
const allQ1s = stats.map(s => s.percentile25 || 0).filter(v => v > 0)
|
|
407
|
+
const allQ3s = stats.map(s => s.percentile75 || 0).filter(v => v > 0)
|
|
408
|
+
|
|
409
|
+
if (allMeans.length > 0) {
|
|
410
|
+
const sortedMeans = [...allMeans].sort((a, b) => a - b)
|
|
411
|
+
const min = sortedMeans[0]
|
|
412
|
+
const max = sortedMeans[sortedMeans.length - 1]
|
|
413
|
+
const mean = allMeans.reduce((a, b) => a + b, 0) / allMeans.length
|
|
414
|
+
const median = allMedians.length > 0 ? allMedians.reduce((a, b) => a + b, 0) / allMedians.length : mean
|
|
415
|
+
|
|
416
|
+
const q1 = sortedMeans[Math.floor(sortedMeans.length / 4)]
|
|
417
|
+
const q3 = sortedMeans[Math.floor((sortedMeans.length * 3) / 4)]
|
|
418
|
+
|
|
419
|
+
result.push({
|
|
420
|
+
group: category,
|
|
421
|
+
min,
|
|
422
|
+
q1,
|
|
423
|
+
median,
|
|
424
|
+
q3,
|
|
425
|
+
max,
|
|
426
|
+
mean,
|
|
427
|
+
value: mean // 현재 값으로 평균 사용
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return result
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private get realKpiBoxplotGroups(): string[] {
|
|
436
|
+
const filteredStats = this.filteredKpiStatistics
|
|
437
|
+
if (filteredStats.length === 0) return []
|
|
438
|
+
|
|
439
|
+
const categories = new Set<string>()
|
|
440
|
+
filteredStats.forEach(stat => {
|
|
441
|
+
if (stat.kpi?.category?.name) {
|
|
442
|
+
categories.add(stat.kpi.category.name)
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
return Array.from(categories)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private get realKpiCurrentGroup() {
|
|
450
|
+
return '평균'
|
|
451
|
+
}
|
|
452
|
+
|
|
234
453
|
connectedCallback() {
|
|
235
454
|
super.connectedCallback()
|
|
236
455
|
this.fetchCategories()
|
|
456
|
+
this.fetchKpiStatistics()
|
|
237
457
|
}
|
|
238
458
|
|
|
239
459
|
pageUpdated(changes: any, lifecycle: any) {
|
|
240
460
|
if (this.active) {
|
|
241
461
|
this.fetchCategories()
|
|
462
|
+
this.fetchKpiStatistics()
|
|
242
463
|
}
|
|
243
464
|
}
|
|
244
465
|
|
|
@@ -296,6 +517,70 @@ export class KpiDashboardPage extends PageView {
|
|
|
296
517
|
}
|
|
297
518
|
}
|
|
298
519
|
|
|
520
|
+
async fetchKpiStatistics() {
|
|
521
|
+
this.statisticsLoading = true
|
|
522
|
+
try {
|
|
523
|
+
const response = await client.query({
|
|
524
|
+
query: gql`
|
|
525
|
+
query {
|
|
526
|
+
kpiStatistics {
|
|
527
|
+
items {
|
|
528
|
+
id
|
|
529
|
+
valueDate
|
|
530
|
+
periodType
|
|
531
|
+
count
|
|
532
|
+
sum
|
|
533
|
+
range
|
|
534
|
+
mean
|
|
535
|
+
median
|
|
536
|
+
minimum
|
|
537
|
+
maximum
|
|
538
|
+
standardDeviation
|
|
539
|
+
variance
|
|
540
|
+
percentile25
|
|
541
|
+
percentile75
|
|
542
|
+
iqr
|
|
543
|
+
lowerFence
|
|
544
|
+
upperFence
|
|
545
|
+
additionalStatistics
|
|
546
|
+
metadata
|
|
547
|
+
kpi {
|
|
548
|
+
id
|
|
549
|
+
name
|
|
550
|
+
unit
|
|
551
|
+
category {
|
|
552
|
+
id
|
|
553
|
+
name
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
`
|
|
560
|
+
})
|
|
561
|
+
this.kpiStatistics = response.data.kpiStatistics.items || []
|
|
562
|
+
|
|
563
|
+
// 현재 월을 기본값으로 설정
|
|
564
|
+
const currentMonth = this.currentMonth
|
|
565
|
+
const availableDates = this.availableValueDates
|
|
566
|
+
|
|
567
|
+
if (availableDates.includes(currentMonth)) {
|
|
568
|
+
// 현재 월 데이터가 있으면 현재 월로 설정
|
|
569
|
+
this.selectedValueDate = currentMonth
|
|
570
|
+
} else if (availableDates.length > 0) {
|
|
571
|
+
// 현재 월 데이터가 없으면 가장 최근 데이터로 설정
|
|
572
|
+
this.selectedValueDate = availableDates[0]
|
|
573
|
+
} else {
|
|
574
|
+
// 데이터가 없으면 현재 월로 설정
|
|
575
|
+
this.selectedValueDate = currentMonth
|
|
576
|
+
}
|
|
577
|
+
} catch (e) {
|
|
578
|
+
console.error('KPI 통계 데이터를 불러오지 못했습니다:', e)
|
|
579
|
+
} finally {
|
|
580
|
+
this.statisticsLoading = false
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
299
584
|
async openHistoryModal(kpi) {
|
|
300
585
|
// 전체 이력 fetch (limit 없이)
|
|
301
586
|
try {
|
|
@@ -501,6 +786,137 @@ export class KpiDashboardPage extends PageView {
|
|
|
501
786
|
</div>
|
|
502
787
|
</div>
|
|
503
788
|
</div>
|
|
789
|
+
|
|
790
|
+
<!-- 실제 KPI 통계 차트 섹션 -->
|
|
791
|
+
<div class="sample-charts-section">
|
|
792
|
+
<!-- 통계 필터 및 요약 -->
|
|
793
|
+
<div style="margin-bottom: 24px; background: #f8f9fa; padding: 16px; border-radius: 8px;">
|
|
794
|
+
<div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
|
|
795
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
796
|
+
<label style="font-weight: 500; color: #333;">기간:</label>
|
|
797
|
+
<span style="padding: 6px 12px; background: #e9ecef; border-radius: 4px; color: #495057;">
|
|
798
|
+
MONTH (월별)
|
|
799
|
+
</span>
|
|
800
|
+
</div>
|
|
801
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
802
|
+
<label style="font-weight: 500; color: #333;">날짜:</label>
|
|
803
|
+
<select
|
|
804
|
+
.value=${this.selectedValueDate}
|
|
805
|
+
@change=${this._onValueDateChange}
|
|
806
|
+
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: white;"
|
|
807
|
+
>
|
|
808
|
+
${this.availableValueDates.map(
|
|
809
|
+
date => html`<option value="${date}">${date}${date === this.currentMonth ? ' (현재)' : ''}</option>`
|
|
810
|
+
)}
|
|
811
|
+
</select>
|
|
812
|
+
</div>
|
|
813
|
+
${this.statisticsSummary
|
|
814
|
+
? html`
|
|
815
|
+
<div style="display: flex; gap: 16px; margin-left: auto;">
|
|
816
|
+
<span style="color: #666; font-size: 0.9em;">
|
|
817
|
+
<b>KPI:</b> ${this.statisticsSummary.totalKpis}개
|
|
818
|
+
</span>
|
|
819
|
+
<span style="color: #666; font-size: 0.9em;">
|
|
820
|
+
<b>카테고리:</b> ${this.statisticsSummary.totalCategories}개
|
|
821
|
+
</span>
|
|
822
|
+
<span style="color: #666; font-size: 0.9em;">
|
|
823
|
+
<b>평균:</b> ${this.statisticsSummary.avgMean}
|
|
824
|
+
</span>
|
|
825
|
+
<span style="color: #666; font-size: 0.9em;">
|
|
826
|
+
<b>중앙값:</b> ${this.statisticsSummary.avgMedian}
|
|
827
|
+
</span>
|
|
828
|
+
<span style="color: #666; font-size: 0.9em;">
|
|
829
|
+
<b>표준편차:</b> ${this.statisticsSummary.avgStdDev}
|
|
830
|
+
</span>
|
|
831
|
+
</div>
|
|
832
|
+
`
|
|
833
|
+
: nothing}
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
|
|
837
|
+
<div class="sample-chart-card">
|
|
838
|
+
<div class="sample-chart-title">실제 KPI 통계 비교 (Radar)</div>
|
|
839
|
+
<div class="sample-chart-container">
|
|
840
|
+
${this.statisticsLoading
|
|
841
|
+
? html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
|
|
842
|
+
통계 데이터 로딩 중...
|
|
843
|
+
</div>`
|
|
844
|
+
: this.realKpiRadarData.length > 0
|
|
845
|
+
? html`<kpi-radar-chart
|
|
846
|
+
.data=${this.realKpiRadarData}
|
|
847
|
+
.categories=${this.realKpiRadarCategories}
|
|
848
|
+
.currentGroup=${'평균'}
|
|
849
|
+
></kpi-radar-chart>`
|
|
850
|
+
: html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
|
|
851
|
+
선택된 기간에 통계 데이터가 없습니다.
|
|
852
|
+
</div>`}
|
|
853
|
+
</div>
|
|
854
|
+
<div style="margin-top:12px;color:#666;font-size:0.98em;">
|
|
855
|
+
※ <b>실제 KPI 통계 Radar 차트</b>는 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 평균, 중앙값,
|
|
856
|
+
표준편차를 비교합니다.<br />
|
|
857
|
+
<b>평균</b>은 산술평균, <b>중앙값</b>은 50분위수, <b>표준편차</b>는 데이터 분산 정도를 나타냅니다.
|
|
858
|
+
</div>
|
|
859
|
+
</div>
|
|
860
|
+
<div class="sample-chart-card">
|
|
861
|
+
<div class="sample-chart-title">실제 KPI 통계 분포 (Boxplot)</div>
|
|
862
|
+
<div class="sample-chart-container">
|
|
863
|
+
${this.statisticsLoading
|
|
864
|
+
? html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
|
|
865
|
+
통계 데이터 로딩 중...
|
|
866
|
+
</div>`
|
|
867
|
+
: this.realKpiBoxplotData.length > 0
|
|
868
|
+
? html`<kpi-boxplot-chart
|
|
869
|
+
.data=${this.realKpiBoxplotData}
|
|
870
|
+
.groups=${this.realKpiBoxplotGroups}
|
|
871
|
+
.currentGroup=${'평균'}
|
|
872
|
+
></kpi-boxplot-chart>`
|
|
873
|
+
: html`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#666;">
|
|
874
|
+
선택된 기간에 통계 데이터가 없습니다.
|
|
875
|
+
</div>`}
|
|
876
|
+
</div>
|
|
877
|
+
<div style="margin-top:12px;color:#666;font-size:0.98em;">
|
|
878
|
+
※ <b>실제 KPI 통계 Boxplot</b>은 실제 KPIStatistic 데이터를 기반으로 각 카테고리별 통계값의 분포를
|
|
879
|
+
보여줍니다.<br />
|
|
880
|
+
각 카테고리의 <b>평균값들</b>을 기준으로 분포를 계산하여, 카테고리 간 통계적 특성을 비교할 수 있습니다.
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
|
|
885
|
+
<!-- 3레벨 KPI 플로팅 섹션 -->
|
|
886
|
+
<div class="sample-charts-section">
|
|
887
|
+
<div style="margin-bottom: 24px;">
|
|
888
|
+
<h2 style="font-size: 1.5rem; font-weight: bold; color: #333; text-align: center; margin-bottom: 8px;">
|
|
889
|
+
3레벨 KPI 분석
|
|
890
|
+
</h2>
|
|
891
|
+
<p style="text-align: center; color: #666; font-size: 0.95rem;">
|
|
892
|
+
실제 KPIStatistic 데이터를 기반으로 한 다층적 분석
|
|
893
|
+
</p>
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
<!-- 1레벨: 그룹 총 스코어 -->
|
|
897
|
+
<div style="margin-bottom: 32px;">
|
|
898
|
+
<h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
|
|
899
|
+
📊 1레벨: 그룹 총 스코어
|
|
900
|
+
</h3>
|
|
901
|
+
<kpi-level1-card></kpi-level1-card>
|
|
902
|
+
</div>
|
|
903
|
+
|
|
904
|
+
<!-- 2레벨: 카테고리 비교 -->
|
|
905
|
+
<div style="margin-bottom: 32px;">
|
|
906
|
+
<h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
|
|
907
|
+
📈 2레벨: 카테고리 비교 분석
|
|
908
|
+
</h3>
|
|
909
|
+
<kpi-level2-comparison></kpi-level2-comparison>
|
|
910
|
+
</div>
|
|
911
|
+
|
|
912
|
+
<!-- 3레벨: 개별 KPI 비교 -->
|
|
913
|
+
<div style="margin-bottom: 32px;">
|
|
914
|
+
<h3 style="font-size: 1.2rem; font-weight: 600; color: #495057; margin-bottom: 16px;">
|
|
915
|
+
🔍 3레벨: 개별 KPI 상세 분석
|
|
916
|
+
</h3>
|
|
917
|
+
<kpi-level3-comparison></kpi-level3-comparison>
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
504
920
|
${this.showHistoryModal
|
|
505
921
|
? html`
|
|
506
922
|
<div
|