import gql from 'graphql-tag' import { html, css, nothing } from 'lit' import { customElement, state } from 'lit/decorators.js' import { PageView } from '@operato/shell' import { ScrollbarStyles } from '@operato/styles' import { client } from '@operato/graphql' import { notify } from '@operato/layout' import './components/kpi-map-panel' import './components/kpi-left-panel' import './components/kpi-region-popup' @customElement('kpi-dashboard-map') export class KpiDashboardMapPage extends PageView { static styles = [ ScrollbarStyles, css` :host { display: flex; flex-direction: column; overflow: hidden; position: relative; } .dashboard-container { display: flex; flex: 1; overflow: hidden; } .right-panel { flex: 1; background: #f8f9fa; overflow: hidden; position: relative; } ` ] @state() selectedCategory = '전체 KPI' @state() selectedPeriod = '월' @state() startDate = '전체' @state() endDate = '전체' @state() nationalData: any = null @state() seoulData: any = null @state() mapData: any = null // 팝업 관련 상태 추가 @state() selectedRegion: string | null = null @state() showRegionPopup = false connectedCallback() { super.connectedCallback() this.fetchDashboardData() this.setupKeyboardEvents() } private setupKeyboardEvents() { document.addEventListener('keydown', e => { if (e.key === 'Escape' && this.showRegionPopup) { this.closeRegionPopup() } }) } async fetchDashboardData() { try { // 새로운 KpiStatistic 구조 사용 - 지역별 집계된 통계 데이터 조회 const currentMonth = new Date().toISOString().substring(0, 7) // YYYY-MM const statisticsResponse = await client.query({ query: gql` query GetDashboardStatistics($valueDate: String!) { regionalKpiStatistics(periodType: MONTH, valueDate: $valueDate) { id mean median count minimum maximum standardDeviation kpi { id name description } kpiOrgScope { id scope02 entityName org } } } `, variables: { valueDate: currentMonth } }) this.nationalData = statisticsResponse.data.regionalKpiStatistics || [] // 통계 데이터로부터 지도 데이터 생성 this.generateMapDataFromStatistics() } catch (e) { console.log('KpiStatistic 데이터가 없어서 KpiValue로 폴백') // KpiStatistic이 없는 경우 기존 방식으로 폴백 await this.fetchDashboardDataFallback() } } // 폴백: KpiValue를 직접 집계하는 기존 방식 async fetchDashboardDataFallback() { try { const kpiResponse = await client.query({ query: gql` query { kpisLevel1 { id name description isLeaf children { id name isLeaf value { id value score valueDate kpiOrgScope { id entityName org scope01 scope02 } } } value { id value score valueDate kpiOrgScope { id entityName org scope01 scope02 } } } } ` }) this.nationalData = kpiResponse.data.kpisLevel1 || [] this.generateMapDataFromKpiValues() } catch (e) { notify({ level: 'error', message: '대시보드 데이터를 불러오지 못했습니다.' }) } } private generateMapDataFromKpiValues() { // 실제 KPI 데이터로부터 지도 데이터 생성 const regionCoordinates = { '서울': { lat: 37.5665, lng: 126.978 }, '부산': { lat: 35.1796, lng: 129.0756 }, '대구': { lat: 35.8714, lng: 128.6014 }, '인천': { lat: 37.4563, lng: 126.7052 }, '광주': { lat: 35.1595, lng: 126.8526 }, '대전': { lat: 36.3504, lng: 127.3845 }, '울산': { lat: 35.5384, lng: 129.3114 }, '세종': { lat: 36.4875, lng: 127.2816 }, '경기': { lat: 37.4138, lng: 127.5183 }, '강원': { lat: 37.8228, lng: 128.1555 }, '충북': { lat: 36.8, lng: 127.7 }, '충남': { lat: 36.5184, lng: 126.8 }, '전북': { lat: 35.7175, lng: 127.153 }, '전남': { lat: 34.8679, lng: 126.991 }, '경북': { lat: 36.4919, lng: 128.8889 }, '경남': { lat: 35.4606, lng: 128.2132 }, '제주': { lat: 33.4996, lng: 126.5312 } } const mapDataMap = new Map() // nationalData에서 KPI 값들을 지역별로 집계 this.nationalData.forEach((kpi: any) => { // KPI 자체에 값이 있는 경우 (leaf KPI) if (kpi.value && kpi.value.kpiOrgScope) { const region = kpi.value.kpiOrgScope.scope02 // 시도는 scope02에 저장 if (region && regionCoordinates[region]) { if (!mapDataMap.has(region)) { mapDataMap.set(region, { region, values: [], scores: [], ...regionCoordinates[region] }) } mapDataMap.get(region).values.push(kpi.value.value) mapDataMap.get(region).scores.push(kpi.value.score) } } // 하위 KPI들의 값들도 처리 if (kpi.children) { kpi.children.forEach((child: any) => { if (child.value && child.value.kpiOrgScope) { const region = child.value.kpiOrgScope.scope02 // 시도는 scope02에 저장 if (region && regionCoordinates[region]) { if (!mapDataMap.has(region)) { mapDataMap.set(region, { region, values: [], scores: [], ...regionCoordinates[region] }) } mapDataMap.get(region).values.push(child.value.value) mapDataMap.get(region).scores.push(child.value.score) } } }) } }) // 지역별 평균 계산 및 최종 지도 데이터 생성 this.mapData = Array.from(mapDataMap.values()).map(regionData => { const avgValue = regionData.values.length > 0 ? regionData.values.reduce((sum: number, val: number) => sum + val, 0) / regionData.values.length : 0 const avgScore = regionData.scores.length > 0 ? regionData.scores.reduce((sum: number, val: number) => sum + val, 0) / regionData.scores.length : 0 // 간단한 변화율 계산 (실제로는 이전 기간과 비교해야 함) const change = (Math.random() - 0.5) * 0.4 // 임시로 -0.2 ~ 0.2 범위 const trend = change >= 0 ? 'up' : 'down' return { region: regionData.region, kpi: parseFloat(avgValue.toFixed(1)), score: parseFloat(avgScore.toFixed(3)), change: parseFloat(change.toFixed(2)), trend, lat: regionData.lat, lng: regionData.lng, dataCount: regionData.values.length } }) // 데이터가 없는 지역들도 기본값으로 추가 Object.keys(regionCoordinates).forEach(region => { if (!mapDataMap.has(region)) { this.mapData.push({ region, kpi: 0, score: 0, change: 0, trend: 'neutral', lat: regionCoordinates[region].lat, lng: regionCoordinates[region].lng, dataCount: 0 }) } }) } private generateMapDataFromStatistics() { // KpiStatistic 데이터로부터 지도 데이터 생성 (최적화된 버전) const regionCoordinates = { '서울': { lat: 37.5665, lng: 126.978 }, '부산': { lat: 35.1796, lng: 129.0756 }, '대구': { lat: 35.8714, lng: 128.6014 }, '인천': { lat: 37.4563, lng: 126.7052 }, '광주': { lat: 35.1595, lng: 126.8526 }, '대전': { lat: 36.3504, lng: 127.3845 }, '울산': { lat: 35.5384, lng: 129.3114 }, '세종': { lat: 36.4875, lng: 127.2816 }, '경기': { lat: 37.4138, lng: 127.5183 }, '강원': { lat: 37.8228, lng: 128.1555 }, '충북': { lat: 36.8, lng: 127.7 }, '충남': { lat: 36.5184, lng: 126.8 }, '전북': { lat: 35.7175, lng: 127.153 }, '전남': { lat: 34.8679, lng: 126.991 }, '경북': { lat: 36.4919, lng: 128.8889 }, '경남': { lat: 35.4606, lng: 128.2132 }, '제주': { lat: 33.4996, lng: 126.5312 } } const mapDataMap = new Map() // 통계 데이터를 지역별로 집계 (이미 서버에서 계산된 데이터 사용) this.nationalData.forEach((statistic: any) => { const region = statistic.kpiOrgScope?.scope02 if (region && regionCoordinates[region]) { if (!mapDataMap.has(region)) { mapDataMap.set(region, { region, means: [], counts: [], ...regionCoordinates[region] }) } // 통계값 집계 (이미 계산된 평균값들 사용) mapDataMap.get(region).means.push(statistic.mean || 0) mapDataMap.get(region).counts.push(statistic.count || 0) } }) // 지역별 최종 평균 계산 및 지도 데이터 생성 this.mapData = Array.from(mapDataMap.values()).map(regionData => { const avgValue = regionData.means.length > 0 ? regionData.means.reduce((sum: number, val: number) => sum + val, 0) / regionData.means.length : 0 const totalCount = regionData.counts.reduce((sum: number, val: number) => sum + val, 0) // 간단한 변화율 계산 (실제로는 이전 기간과 비교해야 함) const change = (Math.random() - 0.5) * 0.4 const trend = change >= 0 ? 'up' : 'down' return { region: regionData.region, kpi: parseFloat(avgValue.toFixed(1)), change: parseFloat(change.toFixed(2)), trend, lat: regionData.lat, lng: regionData.lng, dataCount: totalCount, statisticsCount: regionData.means.length // 통계 항목 수 } }) // 데이터가 없는 지역들도 기본값으로 추가 Object.keys(regionCoordinates).forEach(region => { if (!mapDataMap.has(region)) { this.mapData.push({ region, kpi: 0, change: 0, trend: 'neutral', lat: regionCoordinates[region].lat, lng: regionCoordinates[region].lng, dataCount: 0, statisticsCount: 0 }) } }) console.log('Generated map data from KpiStatistics:', this.mapData.length, 'regions') } private downloadExcel() { // 엑셀 다운로드 기능 console.log('엑셀 다운로드') } private onRegionClick(region: string) { this.selectedRegion = region this.showRegionPopup = true this.fetchRegionData(region) } private closeRegionPopup() { this.showRegionPopup = false this.selectedRegion = null } private async fetchRegionData(region: string) { try { const response = await client.query({ query: gql` query GetRegionKpiData($filters: [Filter!]) { kpiValues(filters: $filters) { items { id value score valueDate kpiOrgScope { id org scope01 scope02 entityName } kpi { id name parent { id name } } } total } } `, variables: { filters: [ { name: 'kpiOrgScope.scope02', operator: 'eq', value: region } ] } }) this.seoulData = response.data.kpiValues.items || [] } catch (e) { notify({ level: 'error', message: '지역 데이터를 불러오지 못했습니다.' }) } } get context() { return { title: '전국 KPI 대시보드', description: '전국 및 지역별 KPI 성과 분석 대시보드' } } render() { return html`
{ this.selectedCategory = e.detail.category this.fetchDashboardData() }} @region-click=${(e: CustomEvent) => { this.onRegionClick(e.detail.region) }} @download-excel=${this.downloadExcel} > { this.selectedCategory = e.detail.category this.fetchDashboardData() }} @region-click=${(e: CustomEvent) => { this.onRegionClick(e.detail.region) }} > ${this.showRegionPopup ? html` { this.selectedPeriod = e.detail.period }} @date-range-change=${(e: CustomEvent) => { this.startDate = e.detail.startDate this.endDate = e.detail.endDate }} > ` : nothing}
` } }