import { html, css, nothing } from 'lit' import { customElement, state } from 'lit/decorators.js' import { LitElement } from 'lit' import { client } from '@operato/graphql' import gql from 'graphql-tag' @customElement('kpi-level3-comparison') export class KpiLevel3Comparison extends LitElement { static styles = css` :host { display: block; } .comparison-container { background: #fff; border-radius: 16px; padding: 24px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); border: 1px solid #e0e0e0; width: 100%; } .comparison-title { font-size: 1.3rem; font-weight: bold; margin-bottom: 20px; color: #333; text-align: center; } .category-selector { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; padding: 16px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef; } .selector-label { font-weight: 600; color: #495057; min-width: 80px; } .category-select { padding: 8px 12px; border: 1px solid #ced4da; border-radius: 6px; background: white; font-size: 1rem; min-width: 200px; } .category-select:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); } .charts-section { display: flex; gap: 24px; margin-bottom: 20px; } .chart-card { flex: 1; background: #f8f9fa; border-radius: 12px; padding: 20px; border: 1px solid #e9ecef; } .chart-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 16px; color: #495057; text-align: center; } .chart-container { height: 300px; display: flex; align-items: center; justify-content: center; } .loading { color: #666; font-size: 1rem; } .error { color: #d32f2f; font-size: 1rem; } .no-data { color: #666; font-size: 1rem; text-align: center; } .no-category { color: #666; font-size: 1rem; text-align: center; font-style: italic; } .summary-info { display: flex; justify-content: space-around; padding: 16px; background: #f8f9fa; border-radius: 8px; margin-top: 16px; } .summary-item { text-align: center; } .summary-value { font-size: 1.2rem; font-weight: bold; color: #333; margin-bottom: 4px; } .summary-label { font-size: 0.9rem; color: #666; } ` @state() loading = true @state() error = '' @state() radarData: any[] = [] @state() boxplotData: any[] = [] @state() kpis: string[] = [] @state() availableCategories: string[] = [] @state() selectedCategory = '' @state() currentMonth = '' @state() totalKpis = 0 @state() averageScore = 0 @state() categoryStats = new Map() connectedCallback() { super.connectedCallback() this.currentMonth = this.getCurrentMonth() this.fetchKpiComparison() } private getCurrentMonth(): string { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') return `${year}-${month}` } async fetchKpiComparison() { this.loading = true this.error = '' try { const response = await client.query({ query: gql` query { kpiStatistics { items { id valueDate periodType mean median standardDeviation minimum maximum percentile25 percentile75 kpi { id name targetValue unit category: parent { id name } } } } } ` }) const statistics = response.data.kpiStatistics.items || [] // MONTH 타입의 현재 월 데이터만 필터링 const currentMonthStats = statistics.filter( stat => stat.periodType === 'MONTH' && stat.valueDate === this.currentMonth ) if (currentMonthStats.length === 0) { this.error = '현재 월의 데이터가 없습니다.' return } // 카테고리별로 데이터 그룹화 const categoryStats = new Map() currentMonthStats.forEach(stat => { if (stat.kpi?.category?.name) { const categoryName = stat.kpi.category.name if (!categoryStats.has(categoryName)) { categoryStats.set(categoryName, []) } categoryStats.get(categoryName)!.push(stat) } }) this.categoryStats = categoryStats this.availableCategories = Array.from(categoryStats.keys()) // 첫 번째 카테고리를 기본 선택 if (this.availableCategories.length > 0 && !this.selectedCategory) { this.selectedCategory = this.availableCategories[0] } // 선택된 카테고리의 데이터로 차트 생성 if (this.selectedCategory) { this.generateKpiCharts() } } catch (e) { console.error('KPI 비교 데이터를 불러오지 못했습니다:', e) this.error = 'KPI 비교 데이터를 불러오지 못했습니다.' } finally { this.loading = false } } private onCategoryChange(event: Event) { const target = event.target as HTMLSelectElement this.selectedCategory = target.value this.generateKpiCharts() } private generateKpiCharts() { if (!this.selectedCategory || !this.categoryStats.has(this.selectedCategory)) { this.radarData = [] this.boxplotData = [] this.kpis = [] this.totalKpis = 0 this.averageScore = 0 return } const categoryData = this.categoryStats.get(this.selectedCategory)! this.kpis = categoryData.map(stat => stat.kpi?.name || '').filter(Boolean) this.totalKpis = this.kpis.length // 레이더 차트 데이터 생성 this.generateRadarData(categoryData) // 박스플롯 데이터 생성 this.generateBoxplotData(categoryData) // 평균 점수 계산 const scores = categoryData.map(stat => { const mean = stat.mean || 0 const targetValue = stat.kpi?.targetValue || 100 if (targetValue === 0) return 0 const achievement = Math.min((mean / targetValue) * 100, 100) return Math.max(achievement, 0) }) this.averageScore = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length) } private generateRadarData(categoryData: any[]) { const result: any[] = [] this.kpis.forEach(kpiName => { const stat = categoryData.find(s => s.kpi?.name === kpiName) if (stat) { const mean = stat.mean || 0 const median = stat.median || 0 const stdDev = stat.standardDeviation || 0 if (mean > 0) { result.push({ group: '평균', category: kpiName, value: mean }) } if (median > 0) { result.push({ group: '중앙값', category: kpiName, value: median }) } if (stdDev > 0) { result.push({ group: '표준편차', category: kpiName, value: stdDev }) } } }) this.radarData = result } private generateBoxplotData(categoryData: any[]) { const result: any[] = [] this.kpis.forEach(kpiName => { const stat = categoryData.find(s => s.kpi?.name === kpiName) if (stat) { const mean = stat.mean || 0 const median = stat.median || 0 const min = stat.minimum || 0 const max = stat.maximum || 0 const q1 = stat.percentile25 || 0 const q3 = stat.percentile75 || 0 if (mean > 0) { result.push({ group: kpiName, min, q1, median, q3, max, mean, value: mean }) } } }) this.boxplotData = result } render() { if (this.loading) { return html`
KPI 상세 비교 분석
카테고리:
레이더 차트
데이터 로딩 중...
박스플롯
데이터 로딩 중...
` } if (this.error) { return html`
KPI 상세 비교 분석
카테고리:
레이더 차트
${this.error}
박스플롯
${this.error}
` } return html`
KPI 상세 비교 분석 (${this.currentMonth})
카테고리:
레이더 차트
${this.selectedCategory ? this.radarData.length > 0 ? html`` : html`
선택된 카테고리에 데이터가 없습니다.
` : html`
카테고리를 선택해주세요.
`}
박스플롯
${this.selectedCategory ? this.boxplotData.length > 0 ? html`` : html`
선택된 카테고리에 데이터가 없습니다.
` : html`
카테고리를 선택해주세요.
`}
${this.selectedCategory ? html`
${this.selectedCategory}
선택된 카테고리
${this.totalKpis}
KPI
${this.averageScore}
평균 점수
` : nothing}
` } }