Files
genome2025/frontend/src/app/cow/[cowNo]/genome/_components/genome-integrated-comparison.tsx
2025-12-24 22:50:13 +09:00

711 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from "react"
import apiClient from "@/lib/api-client"
import { genomeApi, TraitRankDto } from "@/lib/api/genome.api"
import { useFilterStore } from "@/store/filter-store"
import { useAnalysisYear } from "@/contexts/AnalysisYearContext"
import { CowNumberDisplay } from "@/components/common/cow-number-display"
import { ALL_TRAITS } from "@/constants/traits"
// 분포 데이터 타입
interface DistributionBin {
range: string
count: number // 보은군 전체 두수
farmCount: number // 우리 농가 두수
min: number
max: number
}
// 형질 데이터 타입
interface GenomicTrait {
id: number
name: string
category: string
breedVal: number
percentile: number
description: string
actualValue: number
unit: string
}
interface GenomeIntegratedComparisonProps {
farmNo: number | null
cowNo?: string
// 선발지수 데이터 추가
selectionIndex?: {
score: number | null
percentile: number | null
farmRank: number | null
farmTotal: number
regionRank: number | null
regionTotal: number
regionName: string | null
farmerName: string | null
} | null
overallScore?: number
// 분포 데이터 콜백
onDistributionDataChange?: (data: {
distributionData: DistributionBin[]
totalCowCount: number
farmCowCount: number
farmAvgScore: number // 우리농장 평균 선발지수
regionAvgScore: number // 보은군 평균 선발지수
traitComparisons: TraitComparison[] // 형질별 농가/보은군 평균 비교
}) => void
// 하이라이트 모드 (농가/보은군 비교 클릭 시)
highlightMode?: 'farm' | 'region' | null
onComparisonClick?: (mode: 'farm' | 'region') => void
// 차트 형질 필터 연동
chartFilterTrait?: string
selectedTraitData?: GenomicTrait[]
traitComparisons?: TraitComparison[]
}
export interface TraitComparison {
trait: string
shortName: string
myFarm: number
region: number
diff: number
}
interface IntegratedStats {
farmBreedVal: number
farmPercentile: number
regionBreedVal: number
regionPercentile: number
difference: number
selectedTraitCount: number
totalCowCount: number
traitComparisons: TraitComparison[]
// 농장 순위 관련
farmRank: number
totalFarmCount: number
topPercent: number
regionTopPercent: number
farmAvgTopPercent: number // 우리 농가 평균 퍼센트
}
// 유전체 종합보고서 보은군 내 농장 순위 가로바 차트
export function GenomeIntegratedComparison({
farmNo,
cowNo,
selectionIndex,
overallScore = 0,
onDistributionDataChange,
highlightMode,
onComparisonClick,
chartFilterTrait = 'overall',
selectedTraitData = [],
traitComparisons: externalTraitComparisons = []
}: GenomeIntegratedComparisonProps) {
// =======================개체번호 포맷팅: KOR 제외 + 002 1696 8353 8 형식======================
// 개체번호 포맷팅 함수 formatCowNo / 유전체 보은 군 내 농장 순위 가로바 차트에서 사용
const formatCowNo = (no?: string) => {
if (!no) return ''
// KOR 제거
const numOnly = no.replace(/^KOR/i, '')
// 002 1696 8353 8 형식으로 포맷팅
if (numOnly.length === 12) {
return `${numOnly.slice(0, 3)} ${numOnly.slice(3, 7)} ${numOnly.slice(7, 11)} ${numOnly.slice(11)}`
}
return numOnly
}
//===========================================================================================
const { filters } = useFilterStore()
const { selectedYear } = useAnalysisYear()
const [stats, setStats] = useState<IntegratedStats | null>(null)
const [loading, setLoading] = useState(true)
// 형질별 순위 데이터
const [traitRank, setTraitRank] = useState<TraitRankDto | null>(null)
const [traitRankLoading, setTraitRankLoading] = useState(false)
// 연도별 추이 데이터
const [yearlyTrendData, setYearlyTrendData] = useState<{
year: number
analyzedCount: number // 분석 두수
avgEbv: number // 평균 표준화 육종가
}[]>([])
const [trendLoading, setTrendLoading] = useState(true)
// 형질 조건 생성 (형질명 + 가중치)
const getTraitConditions = () => {
const selected = Object.entries(filters.traitWeights)
.filter(([_, weight]) => weight > 0)
.map(([traitNm, weight]) => ({ traitNm, weight }))
// 선택된 형질이 없으면 전체 35개 형질에 가중치 1 적용
if (selected.length === 0) {
return ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 }))
}
return selected
}
const traitShortNames: Record<string, string> = {
'도체중': '도체중',
'근내지방도': '근내지방도',
'등심단면적': '등심단면적',
'등지방두께': '등지방두께',
'12개월령체중': '12개월령체중',
// 체형형질
'체고': '체고',
'십자': '십자',
'체장': '체장',
'흉심': '흉심',
'흉폭': '흉폭',
'고장': '고장',
'요각폭': '요각폭',
'좌골폭': '좌골폭',
'곤폭': '곤폭',
'흉위': '흉위',
// 부위별무게
'안심weight': '안심무게',
'등심weight': '등심무게',
'채끝weight': '채끝무게',
'목심weight': '목심무게',
'앞다리weight': '앞다리무게',
'우둔weight': '우둔무게',
'설도weight': '설도무게',
'사태weight': '사태무게',
'양지weight': '양지무게',
'갈비weight': '갈비무게',
// 부위별비율
'안심rate': '안심비율',
'등심rate': '등심비율',
'채끝rate': '채끝비율',
'목심rate': '목심비율',
'앞다리rate': '앞다리비율',
'우둔rate': '우둔비율',
'설도rate': '설도비율',
'사태rate': '사태비율',
'양지rate': '양지비율',
'갈비rate': '갈비비율',
}
useEffect(() => {
const fetchIntegratedStats = async () => {
if (!farmNo) {
setLoading(false)
return
}
setLoading(true)
try {
const traitConditions = getTraitConditions()
// API 2번만 호출 (병렬 처리)
const [farmResponse, globalResponse] = await Promise.all([
// 1. 내 농장 데이터
apiClient.post('/cow/ranking', {
filterOptions: { farmNo },
rankingOptions: {
criteriaType: 'GENOME',
traitConditions
}
}),
// 2. 전체 유저(보은군) 데이터
apiClient.post('/cow/ranking/global', {
rankingOptions: {
criteriaType: 'GENOME',
traitConditions
}
})
])
const farmResult = farmResponse.data || farmResponse
const globalResult = globalResponse.data || globalResponse
// 분석완료 개체만 필터링 (sortValue !== null)
const farmItems = (farmResult.items || []).filter((item: any) => item.sortValue !== null)
const globalItems = (globalResult.items || []).filter((item: any) => item.sortValue !== null)
if (farmItems.length === 0) {
setStats(null)
return
}
// 내 농장 평균 (필터 가중치 적용된 선발지수의 평균)
const farmScores = farmItems.map((item: any) => item.sortValue || 0)
const farmBreedVal = farmScores.reduce((sum: number, s: number) => sum + s, 0) / farmScores.length
// 전체 유저(보은군) 평균 (필터 가중치 적용된 선발지수의 평균)
const globalScores = globalItems.map((item: any) => item.sortValue || 0)
const regionBreedVal = globalScores.length > 0
? globalScores.reduce((sum: number, s: number) => sum + s, 0) / globalScores.length
: 0
// 형질별 비교 데이터 생성 - 개체별 traits에서 형질별 평균 계산
const selectedTraitNames = traitConditions.map(t => t.traitNm)
const traitComparisons: TraitComparison[] = selectedTraitNames.map(traitNm => {
// 내 농장 형질별 평균
// ranking.traits 배열에서 traitName으로 찾아서 traitEbv 값 사용
const farmTraitValues = farmItems
.map((item: any) => {
const traitsArray = item.ranking?.traits || []
const trait = traitsArray.find((t: any) => t.traitName === traitNm)
return trait?.traitEbv ?? null
})
.filter((v: any) => v !== null)
const farmTraitAvg = farmTraitValues.length > 0
? farmTraitValues.reduce((sum: number, v: number) => sum + v, 0) / farmTraitValues.length
: 0
// 전체 유저 형질별 평균
const globalTraitValues = globalItems
.map((item: any) => {
const traitsArray = item.ranking?.traits || []
const trait = traitsArray.find((t: any) => t.traitName === traitNm)
return trait?.traitEbv ?? null
})
.filter((v: any) => v !== null)
const globalTraitAvg = globalTraitValues.length > 0
? globalTraitValues.reduce((sum: number, v: number) => sum + v, 0) / globalTraitValues.length
: 0
return {
trait: traitNm,
shortName: traitShortNames[traitNm] || traitNm.slice(0, 4),
myFarm: parseFloat(farmTraitAvg.toFixed(2)),
region: parseFloat(globalTraitAvg.toFixed(2)),
diff: parseFloat((farmTraitAvg - globalTraitAvg).toFixed(2))
}
})
// 개체 단위 순위 계산
let farmRank = 1
let totalFarmCount = 1
let topPercent = 50
let regionTopPercent = 50
let farmAvgTopPercent = 50
if (globalItems.length > 0) {
// 전체 개체 점수 배열 (내림차순 정렬)
const allCowScores = globalItems
.map((item: any) => item.sortValue || 0)
.sort((a: number, b: number) => b - a)
const totalCowCount = allCowScores.length
// 농가 평균(farmBreedVal)이 전체 개체 중 상위 몇 %인지 계산
const farmAvgRank = allCowScores.filter((score: number) => score > farmBreedVal).length + 1
farmAvgTopPercent = Math.round((farmAvgRank / totalCowCount) * 100)
// 보은군 평균(regionBreedVal)이 전체 개체 중 상위 몇 %인지 계산
const regionRank = allCowScores.filter((score: number) => score > regionBreedVal).length + 1
regionTopPercent = Math.round((regionRank / totalCowCount) * 100)
// 농장별 그룹핑 (농장 순위용)
const farmScoresMap: Record<number, number[]> = {}
globalItems.forEach((item: any) => {
const itemFarmNo =
item.entity?.farmNo ||
item.entity?.farm?.pkFarmNo ||
item.entity?.pkFarmNo ||
item.farmNo ||
item.entity?.fkFarmNo
if (itemFarmNo) {
if (!farmScoresMap[itemFarmNo]) {
farmScoresMap[itemFarmNo] = []
}
farmScoresMap[itemFarmNo].push(item.sortValue || 0)
}
})
// 각 농장의 평균 계산 및 정렬
const farmAverages = Object.entries(farmScoresMap)
.map(([fNo, scores]) => ({
farmNo: parseInt(fNo),
avg: scores.reduce((sum, s) => sum + s, 0) / scores.length
}))
.sort((a, b) => b.avg - a.avg)
totalFarmCount = farmAverages.length || 1
const myFarmIndex = farmAverages.findIndex(f => f.farmNo === farmNo)
farmRank = myFarmIndex >= 0 ? myFarmIndex + 1 : 1
topPercent = Math.round((farmRank / totalFarmCount) * 100)
}
// 분포 데이터 계산 (히스토그램용)
if (onDistributionDataChange && globalItems.length > 0) {
const bins: DistributionBin[] = [
{ range: '-3σ ~ -2.5σ', min: -3, max: -2.5, count: 0, farmCount: 0 },
{ range: '-2.5σ ~ -2σ', min: -2.5, max: -2, count: 0, farmCount: 0 },
{ range: '-2σ ~ -1.5σ', min: -2, max: -1.5, count: 0, farmCount: 0 },
{ range: '-1.5σ ~ -1σ', min: -1.5, max: -1, count: 0, farmCount: 0 },
{ range: '-1σ ~ -0.5σ', min: -1, max: -0.5, count: 0, farmCount: 0 },
{ range: '-0.5σ ~ 0σ', min: -0.5, max: 0, count: 0, farmCount: 0 },
{ range: '0σ ~ 0.5σ', min: 0, max: 0.5, count: 0, farmCount: 0 },
{ range: '0.5σ ~ 1σ', min: 0.5, max: 1, count: 0, farmCount: 0 },
{ range: '1σ ~ 1.5σ', min: 1, max: 1.5, count: 0, farmCount: 0 },
{ range: '1.5σ ~ 2σ', min: 1.5, max: 2, count: 0, farmCount: 0 },
{ range: '2σ ~ 2.5σ', min: 2, max: 2.5, count: 0, farmCount: 0 },
{ range: '2.5σ ~ 3σ', min: 2.5, max: 3, count: 0, farmCount: 0 },
]
// 전체 개체(보은군)의 선발지수를 구간별로 카운트
globalItems.forEach((item: any) => {
const score = item.sortValue ?? 0
// -3 미만은 첫 번째 구간에
if (score < -3) {
bins[0].count++
return
}
// 3 이상은 마지막 구간에
if (score >= 3) {
bins[bins.length - 1].count++
return
}
// 일반 구간 매칭 (마지막 구간은 >= 포함)
for (let i = 0; i < bins.length; i++) {
const bin = bins[i]
const isLastBin = i === bins.length - 1
if (isLastBin) {
if (score >= bin.min && score <= bin.max) {
bin.count++
break
}
} else {
if (score >= bin.min && score < bin.max) {
bin.count++
break
}
}
}
})
// 우리 농가 개체의 선발지수를 구간별로 카운트
farmItems.forEach((item: any) => {
const score = item.sortValue ?? 0
// -3 미만은 첫 번째 구간에
if (score < -3) {
bins[0].farmCount++
return
}
// 3 이상은 마지막 구간에
if (score >= 3) {
bins[bins.length - 1].farmCount++
return
}
// 일반 구간 매칭 (마지막 구간은 >= 포함)
for (let i = 0; i < bins.length; i++) {
const bin = bins[i]
const isLastBin = i === bins.length - 1
if (isLastBin) {
if (score >= bin.min && score <= bin.max) {
bin.farmCount++
break
}
} else {
if (score >= bin.min && score < bin.max) {
bin.farmCount++
break
}
}
}
})
onDistributionDataChange({
distributionData: bins,
totalCowCount: selectionIndex?.regionTotal || globalItems.length,
farmCowCount: selectionIndex?.farmTotal || farmItems.length,
farmAvgScore: farmBreedVal,
regionAvgScore: regionBreedVal,
traitComparisons
})
}
setStats({
farmBreedVal: parseFloat(farmBreedVal.toFixed(2)),
farmPercentile: normalCdfToPercentile(farmBreedVal),
regionBreedVal: parseFloat(regionBreedVal.toFixed(2)),
regionPercentile: normalCdfToPercentile(regionBreedVal),
difference: parseFloat((farmBreedVal - regionBreedVal).toFixed(2)),
selectedTraitCount: traitConditions.length,
totalCowCount: farmItems.length,
traitComparisons,
farmRank,
totalFarmCount,
topPercent,
regionTopPercent,
farmAvgTopPercent
})
} catch (error) {
console.error('데이터 로드 실패:', error)
setStats(null)
} finally {
setLoading(false)
}
}
fetchIntegratedStats()
}, [farmNo, filters.traitWeights, selectionIndex?.regionTotal])
// 연도별 추이 데이터 가져오기
useEffect(() => {
const fetchYearlyTrend = async () => {
if (!farmNo) {
setTrendLoading(false)
return
}
setTrendLoading(true)
try {
const ebvStats = await genomeApi.getYearlyEbvStats(farmNo)
// yearlyStats와 yearlyAvgEbv 합치기
const yearlyStats = ebvStats.yearlyStats || []
const yearlyAvgEbv = ebvStats.yearlyAvgEbv || []
// 연도별 데이터 맵 생성
const yearMap = new Map<number, { analyzedCount: number; avgEbv: number }>()
// yearlyStats에서 분석 두수 가져오기
yearlyStats.forEach(stat => {
yearMap.set(stat.year, {
analyzedCount: stat.analyzedCount || 0,
avgEbv: 0
})
})
// yearlyAvgEbv에서 평균 육종가 가져오기
yearlyAvgEbv.forEach(avg => {
if (yearMap.has(avg.year)) {
yearMap.get(avg.year)!.avgEbv = avg.farmAvgEbv
} else {
yearMap.set(avg.year, {
analyzedCount: 0,
avgEbv: avg.farmAvgEbv
})
}
})
// 배열로 변환하고 연도 오름차순 정렬
const trendData = Array.from(yearMap.entries())
.map(([year, data]) => ({
year,
analyzedCount: data.analyzedCount,
avgEbv: data.avgEbv
}))
.sort((a, b) => a.year - b.year)
setYearlyTrendData(trendData)
} catch (error) {
console.error('[연도별추이] 데이터 로드 실패:', error)
setYearlyTrendData([])
} finally {
setTrendLoading(false)
}
}
fetchYearlyTrend()
}, [farmNo])
// 형질별 순위 조회 (형질 필터 변경 시)
useEffect(() => {
const fetchTraitRank = async () => {
// 전체 선발지수 모드면 순위 조회 안 함
if (chartFilterTrait === 'overall' || !cowNo) {
setTraitRank(null)
return
}
setTraitRankLoading(true)
try {
const rankData = await genomeApi.getTraitRank(cowNo, chartFilterTrait)
setTraitRank(rankData)
} catch (error) {
console.error('[형질순위] 데이터 로드 실패:', error)
setTraitRank(null)
} finally {
setTraitRankLoading(false)
}
}
fetchTraitRank()
}, [chartFilterTrait, cowNo])
const normalCdfToPercentile = (z: number): number => {
const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741
const a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911
const sign = z < 0 ? -1 : 1
const absZ = Math.abs(z) / Math.sqrt(2)
const t = 1.0 / (1.0 + p * absZ)
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-absZ * absZ)
const cdf = 0.5 * (1.0 + sign * y)
return Math.round((1 - cdf) * 100)
}
if (loading) {
return (
<div className="bg-white rounded-2xl border border-border p-6">
<div className="flex items-center justify-center h-[260px]">
<div className="w-8 h-8 border-2 border-border border-t-primary rounded-full animate-spin"></div>
</div>
</div>
)
}
if (!stats) {
return (
<div className="bg-white rounded-2xl border border-border p-6">
<div className="flex items-center justify-center h-[260px] text-muted-foreground text-base">
.
</div>
</div>
)
}
// 형질 필터에 따른 데이터 계산
const isTraitMode = chartFilterTrait !== 'overall'
// 개별 형질 모드일 때 해당 형질의 데이터 찾기
const selectedTrait = isTraitMode
? selectedTraitData.find(t => t.name === chartFilterTrait)
: null
const traitComparison = isTraitMode
? externalTraitComparisons.find(tc => tc.trait === chartFilterTrait)
: null
// 표시할 값 결정
const displayScore = isTraitMode && selectedTrait ? selectedTrait.breedVal : overallScore
const displayPercentile = isTraitMode && selectedTrait ? selectedTrait.percentile : (selectionIndex?.percentile || 50)
// 형질 모드일 때는 API에서 가져온 평균값 사용, 없으면 traitComparison 사용
const displayFarmAvg = isTraitMode
? (traitRank?.farmAvgEbv ?? traitComparison?.myFarm ?? 0)
: stats.farmBreedVal
const displayRegionAvg = isTraitMode
? (traitRank?.regionAvgEbv ?? traitComparison?.region ?? 0)
: stats.regionBreedVal
const displayLabel = isTraitMode ? chartFilterTrait : '선발지수'
return (
<div className="bg-white rounded-2xl border border-border overflow-hidden">
{/* 콘텐츠 */}
<div className="p-4 sm:p-5 lg:p-6">
<div className="flex flex-col lg:flex-row lg:items-stretch gap-4 lg:gap-6">
{/* 선발지수/형질 - 타이포 중심 */}
<div className="flex-shrink-0 flex flex-col items-center justify-center py-4 lg:py-6 lg:px-8 lg:border-r lg:border-border">
<span className="text-sm text-muted-foreground mb-2">{displayLabel}</span>
<span className={`text-4xl sm:text-5xl font-black tracking-tight ${displayScore >= 0 ? 'text-primary' : 'text-red-500'}`}>
{displayScore > 0 ? '+' : ''}{displayScore.toFixed(2)}
</span>
<span className="text-sm text-muted-foreground mt-2">
<span className="font-semibold text-foreground">{displayPercentile.toFixed(0)}%</span>
</span>
</div>
{/* 순위 + 평균 대비 */}
<div className="flex-1 grid grid-cols-2 gap-3">
{/* 농가 내 순위 */}
<div className="bg-muted/40 rounded-xl p-3 sm:p-4 flex flex-col justify-center">
<span className="text-xs sm:text-sm text-muted-foreground"> </span>
<div className="mt-1">
{traitRankLoading && isTraitMode ? (
<span className="text-xl sm:text-2xl font-bold text-muted-foreground">...</span>
) : (
<>
<span className="text-xl sm:text-2xl font-bold text-foreground">
{isTraitMode ? (traitRank?.farmRank || '-') : (selectionIndex?.farmRank || '-')}
</span>
<span className="text-sm text-muted-foreground ml-1">
/ {isTraitMode ? (traitRank?.farmTotal || 0) : (selectionIndex?.farmTotal || 0)}
</span>
</>
)}
</div>
</div>
{/* 보은군 내 순위 */}
<div className="bg-muted/40 rounded-xl p-3 sm:p-4 flex flex-col justify-center">
<span className="text-xs sm:text-sm text-muted-foreground"> </span>
<div className="mt-1">
{traitRankLoading && isTraitMode ? (
<span className="text-xl sm:text-2xl font-bold text-muted-foreground">...</span>
) : (
<>
<span className="text-xl sm:text-2xl font-bold text-foreground">
{isTraitMode ? (traitRank?.regionRank || '-') : (selectionIndex?.regionRank || '-')}
</span>
<span className="text-sm text-muted-foreground ml-1">
/ {isTraitMode ? (traitRank?.regionTotal || 0) : (selectionIndex?.regionTotal || 0)}
</span>
</>
)}
</div>
</div>
{/* 농가 평균 대비 - 클릭 가능 */}
<button
onClick={() => onComparisonClick?.('farm')}
className={`rounded-xl p-3 sm:p-4 flex flex-col justify-center text-left transition-all duration-200 ${(displayScore - displayFarmAvg) >= 0 ? 'bg-amber-50' : 'bg-red-50'
} ${highlightMode === 'farm'
? 'ring-2 ring-amber-500 ring-offset-2 shadow-lg scale-[1.02]'
: 'hover:ring-2 hover:ring-amber-300 hover:shadow-md cursor-pointer'
} ${isTraitMode ? 'col-span-1' : ''}`}
>
<div className="flex items-center gap-2">
<span className="text-xs sm:text-sm text-muted-foreground"></span>
<span className="text-sm sm:text-base font-semibold text-amber-700">{displayFarmAvg > 0 ? '+' : ''}{displayFarmAvg.toFixed(2)}</span>
{highlightMode === 'farm' && (
<span className="ml-auto text-[10px] bg-amber-500 text-white px-1.5 py-0.5 rounded-full font-medium"></span>
)}
</div>
<div className="mt-1 flex items-baseline gap-1">
<span className={`text-xl sm:text-2xl font-black ${(displayScore - displayFarmAvg) >= 0 ? 'text-amber-600' : 'text-red-500'}`}>
{(displayScore - displayFarmAvg) >= 0 ? '+' : ''}{(displayScore - displayFarmAvg).toFixed(2)}
</span>
<span className={`text-sm font-medium ${(displayScore - displayFarmAvg) >= 0 ? 'text-amber-600' : 'text-red-500'}`}>
{(displayScore - displayFarmAvg) >= 0 ? '높음' : '낮음'}
</span>
</div>
<span className="text-[10px] text-muted-foreground mt-1"> </span>
</button>
{/* 보은군 평균 대비 - 클릭 가능 */}
<button
onClick={() => onComparisonClick?.('region')}
className={`rounded-xl p-3 sm:p-4 flex flex-col justify-center text-left transition-all duration-200 ${(displayScore - displayRegionAvg) >= 0 ? 'bg-blue-50' : 'bg-red-50'
} ${highlightMode === 'region'
? 'ring-2 ring-blue-500 ring-offset-2 shadow-lg scale-[1.02]'
: 'hover:ring-2 hover:ring-blue-300 hover:shadow-md cursor-pointer'
} ${isTraitMode ? 'col-span-1' : ''}`}
>
<div className="flex items-center gap-2">
<span className="text-xs sm:text-sm text-muted-foreground"></span>
<span className="text-sm sm:text-base font-semibold text-blue-700">{displayRegionAvg > 0 ? '+' : ''}{displayRegionAvg.toFixed(2)}</span>
{highlightMode === 'region' && (
<span className="ml-auto text-[10px] bg-blue-500 text-white px-1.5 py-0.5 rounded-full font-medium"></span>
)}
</div>
<div className="mt-1 flex items-baseline gap-1">
<span className={`text-xl sm:text-2xl font-black ${(displayScore - displayRegionAvg) >= 0 ? 'text-blue-600' : 'text-red-500'}`}>
{(displayScore - displayRegionAvg) >= 0 ? '+' : ''}{(displayScore - displayRegionAvg).toFixed(2)}
</span>
<span className={`text-sm font-medium ${(displayScore - displayRegionAvg) >= 0 ? 'text-blue-600' : 'text-red-500'}`}>
{(displayScore - displayRegionAvg) >= 0 ? '높음' : '낮음'}
</span>
</div>
<span className="text-[10px] text-muted-foreground mt-1"> </span>
</button>
</div>
</div>
</div>
</div>
)
}