entity 연결 수정 및 코드정리
This commit is contained in:
@@ -19,6 +19,7 @@ import { geneApi, GeneDetail } from "@/lib/api/gene.api"
|
||||
import { genomeApi, ComparisonAveragesDto, GenomeRequestDto, TraitComparisonAveragesDto } from "@/lib/api/genome.api"
|
||||
import { mptApi } from "@/lib/api/mpt.api"
|
||||
import { getInvalidMessage, getInvalidReason, isExcludedCow, isValidGenomeAnalysis } from "@/lib/utils/genome-analysis-config"
|
||||
import { ALL_TRAITS } from "@/constants/traits"
|
||||
import { CowDetail } from "@/types/cow.types"
|
||||
import { GenomeTrait } from "@/types/genome.types"
|
||||
import {
|
||||
@@ -40,82 +41,7 @@ import { NormalDistributionChart } from "./genome/_components/normal-distributio
|
||||
import { TraitDistributionCharts } from "./genome/_components/trait-distribution-charts"
|
||||
import { MptTable } from "./reproduction/_components/mpt-table"
|
||||
|
||||
// 형질명 → 카테고리 매핑 (한우 35개 형질)
|
||||
const TRAIT_CATEGORY_MAP: Record<string, string> = {
|
||||
// 성장형질 (1개)
|
||||
'12개월령체중': '성장',
|
||||
// 생산형질 (4개)
|
||||
'도체중': '생산',
|
||||
'등심단면적': '생산',
|
||||
'등지방두께': '생산',
|
||||
'근내지방도': '생산',
|
||||
// 체형형질 (10개)
|
||||
'체고': '체형',
|
||||
'십자': '체형',
|
||||
'체장': '체형',
|
||||
'흉심': '체형',
|
||||
'흉폭': '체형',
|
||||
'고장': '체형',
|
||||
'요각폭': '체형',
|
||||
'곤폭': '체형',
|
||||
'좌골폭': '체형',
|
||||
'흉위': '체형',
|
||||
// 부위별 weight (10개)
|
||||
'안심weight': '무게',
|
||||
'등심weight': '무게',
|
||||
'채끝weight': '무게',
|
||||
'목심weight': '무게',
|
||||
'앞다리weight': '무게',
|
||||
'우둔weight': '무게',
|
||||
'설도weight': '무게',
|
||||
'사태weight': '무게',
|
||||
'양지weight': '무게',
|
||||
'갈비weight': '무게',
|
||||
// 부위별 rate (10개)
|
||||
'안심rate': '비율',
|
||||
'등심rate': '비율',
|
||||
'채끝rate': '비율',
|
||||
'목심rate': '비율',
|
||||
'앞다리rate': '비율',
|
||||
'우둔rate': '비율',
|
||||
'설도rate': '비율',
|
||||
'사태rate': '비율',
|
||||
'양지rate': '비율',
|
||||
'갈비rate': '비율',
|
||||
}
|
||||
|
||||
// 형질명으로 카테고리 찾기
|
||||
function getTraitCategory(traitName: string): string {
|
||||
if (TRAIT_CATEGORY_MAP[traitName]) {
|
||||
return TRAIT_CATEGORY_MAP[traitName]
|
||||
}
|
||||
if (traitName.includes('체중') || traitName.includes('개월령')) return '성장'
|
||||
if (traitName.includes('도체') || traitName.includes('등심단면적') || traitName.includes('지방') || traitName.includes('근내')) return '생산'
|
||||
if (traitName.includes('weight')) return '무게'
|
||||
if (traitName.includes('rate')) return '비율'
|
||||
return '체형'
|
||||
}
|
||||
|
||||
// API 데이터를 화면 표시용으로 변환
|
||||
function transformGenomeData(genomeData: GenomeTrait[]) {
|
||||
if (genomeData.length === 0) return []
|
||||
return genomeData[0].genomeCows?.map((trait, index) => {
|
||||
const traitName = trait.traitInfo?.traitNm || ''
|
||||
return {
|
||||
id: index + 1,
|
||||
name: traitName,
|
||||
category: getTraitCategory(traitName),
|
||||
breedVal: trait.breedVal || 0,
|
||||
percentile: trait.percentile || 0,
|
||||
actualValue: trait.traitVal || 0,
|
||||
unit: '',
|
||||
description: trait.traitInfo?.traitDesc || '',
|
||||
importance: 'medium' as 'low' | 'medium' | 'high' | 'critical',
|
||||
}
|
||||
}) || []
|
||||
}
|
||||
|
||||
// 3개의 정규분포 곡선 생성
|
||||
// 유전체 차트 3개의 정규분포 곡선 생성
|
||||
function generateMultipleDistributions(
|
||||
nationwideMean: number, nationwideStd: number,
|
||||
regionMean: number, regionStd: number,
|
||||
@@ -142,6 +68,9 @@ function generateMultipleDistributions(
|
||||
}
|
||||
|
||||
export default function CowOverviewPage() {
|
||||
// ========================================
|
||||
// 기본 훅
|
||||
// ========================================
|
||||
const params = useParams()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
@@ -151,24 +80,25 @@ export default function CowOverviewPage() {
|
||||
const { filters } = useFilterStore()
|
||||
const isMobile = useMediaQuery("(max-width: 640px)")
|
||||
|
||||
// ========================================
|
||||
// 상태 정의
|
||||
// ========================================
|
||||
// 1. 개체/유전체 데이터
|
||||
const [cow, setCow] = useState<CowDetail | null>(null)
|
||||
const [genomeData, setGenomeData] = useState<GenomeTrait[]>([])
|
||||
const [geneData, setGeneData] = useState<GeneDetail[]>([])
|
||||
const [geneDataLoaded, setGeneDataLoaded] = useState(false) // 유전자 데이터 로드 여부
|
||||
const [geneDataLoading, setGeneDataLoading] = useState(false) // 유전자 데이터 로딩 중
|
||||
const [geneDataLoaded, setGeneDataLoaded] = useState(false)
|
||||
const [geneDataLoading, setGeneDataLoading] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeTab, setActiveTab] = useState<string>('genome')
|
||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||
|
||||
// 검사 상태
|
||||
// 2. 검사 상태
|
||||
const [hasGenomeData, setHasGenomeData] = useState(false)
|
||||
const [hasGeneData, setHasGeneData] = useState(false)
|
||||
const [hasReproductionData, setHasReproductionData] = useState(false)
|
||||
|
||||
// 분석 의뢰 정보 (친자감별 결과 포함)
|
||||
const [genomeRequest, setGenomeRequest] = useState<GenomeRequestDto | null>(null)
|
||||
|
||||
// 선발지수 상태
|
||||
// 3. 선발지수
|
||||
const [selectionIndex, setSelectionIndex] = useState<{
|
||||
score: number | null;
|
||||
percentile: number | null;
|
||||
@@ -182,7 +112,7 @@ export default function CowOverviewPage() {
|
||||
regionAvgScore: number | null;
|
||||
} | null>(null)
|
||||
|
||||
// 분포 데이터
|
||||
// 4. 분포/비교 데이터
|
||||
const [comparisonAverages, setComparisonAverages] = useState<ComparisonAveragesDto | null>(null)
|
||||
const [traitComparisonAverages, setTraitComparisonAverages] = useState<TraitComparisonAveragesDto | null>(null)
|
||||
const [distributionData, setDistributionData] = useState<{ range: string; count: number; farmCount: number; min: number; max: number }[]>([])
|
||||
@@ -191,39 +121,46 @@ export default function CowOverviewPage() {
|
||||
const [farmAvgScore, setFarmAvgScore] = useState(0)
|
||||
const [regionAvgScore, setRegionAvgScore] = useState(0)
|
||||
const [traitComparisons, setTraitComparisons] = useState<TraitComparison[]>([])
|
||||
|
||||
// 5. UI 상태
|
||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||
const [showAllAppliedTraits, setShowAllAppliedTraits] = useState(false)
|
||||
const [isChartModalOpen, setIsChartModalOpen] = useState(false)
|
||||
|
||||
// 분포 곡선 표시 토글
|
||||
const [showNationwide] = useState(true)
|
||||
const [showRegion] = useState(true)
|
||||
const [showFarm] = useState(true)
|
||||
const [showAllTraits] = useState(false)
|
||||
const [selectedTraits, setSelectedTraits] = useState<number[]>([])
|
||||
const [geneCurrentPage, setGeneCurrentPage] = useState(1)
|
||||
const GENES_PER_PAGE = 50
|
||||
|
||||
// 농가/보은군 비교 하이라이트 모드
|
||||
const [highlightMode, setHighlightMode] = useState<'farm' | 'region' | null>(null)
|
||||
const distributionChartRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// 필터에서 고정된 첫 번째 형질 (없으면 '도체중')
|
||||
// 6. 차트 필터
|
||||
const firstPinnedTrait = filters.pinnedTraits?.[0] || '도체중'
|
||||
|
||||
// 차트 형질 필터 (전체 선발지수 또는 개별 형질)
|
||||
// 필터 비활성 시 기본값은 첫 번째 고정 형질
|
||||
const [chartFilterTrait, setChartFilterTrait] = useState<string>(() => {
|
||||
return filters.isActive ? 'overall' : firstPinnedTrait
|
||||
})
|
||||
|
||||
// 필터 활성 상태 변경 시 기본값 업데이트
|
||||
// 7. 유전자 탭 필터/정렬
|
||||
const [geneSearchInput, setGeneSearchInput] = useState('')
|
||||
const [geneSearchKeyword, setGeneSearchKeyword] = useState('')
|
||||
const [geneTypeFilter, setGeneTypeFilter] = useState<'all' | 'QTY' | 'QLT'>('all')
|
||||
const [genotypeFilter, setGenotypeFilter] = useState<'all' | 'homozygous' | 'heterozygous'>('all')
|
||||
const [geneSortBy, setGeneSortBy] = useState<'snpName' | 'chromosome' | 'position' | 'snpType' | 'allele1' | 'allele2' | 'remarks'>('snpName')
|
||||
const [geneSortOrder, setGeneSortOrder] = useState<'asc' | 'desc'>('asc')
|
||||
const [geneCurrentPage, setGeneCurrentPage] = useState(1)
|
||||
const GENES_PER_PAGE = 50
|
||||
|
||||
// ========================================
|
||||
// useEffect - UI 이벤트
|
||||
// ========================================
|
||||
// 필터 활성 상태 변경 시 차트 기본값 업데이트
|
||||
useEffect(() => {
|
||||
if (!filters.isActive && chartFilterTrait === 'overall') {
|
||||
setChartFilterTrait(firstPinnedTrait)
|
||||
}
|
||||
}, [filters.isActive, firstPinnedTrait, chartFilterTrait])
|
||||
|
||||
// 스크롤 투 탑 버튼 표시 여부
|
||||
// 스크롤 투 탑 버튼 표시
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setShowScrollTop(window.scrollY > 400)
|
||||
@@ -232,18 +169,9 @@ export default function CowOverviewPage() {
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
// 맨 위로 스크롤
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
// 유전자 탭 필터 상태
|
||||
const [geneSearchInput, setGeneSearchInput] = useState('') // 실시간 입력값
|
||||
const [geneSearchKeyword, setGeneSearchKeyword] = useState('') // 디바운스된 검색값
|
||||
const [geneTypeFilter, setGeneTypeFilter] = useState<'all' | 'QTY' | 'QLT'>('all')
|
||||
|
||||
// 검색어 디바운스 (300ms) 실시간 필터링 너무 느림
|
||||
// 타이핑이 멈추고 0.3초 후에 검색이 실행
|
||||
// 검색어 디바운스 (300ms)
|
||||
// 유전자 데이터가 너무 많아서 검색창에 입력할 때마다 모든 데이터를 필터링하지 않고
|
||||
// 검색어가 변경된 후 300ms 후에 필터링을 적용
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setGeneSearchKeyword(geneSearchInput)
|
||||
@@ -251,11 +179,16 @@ export default function CowOverviewPage() {
|
||||
}, 300)
|
||||
return () => clearTimeout(timer)
|
||||
}, [geneSearchInput])
|
||||
const [genotypeFilter, setGenotypeFilter] = useState<'all' | 'homozygous' | 'heterozygous'>('all')
|
||||
const [geneSortBy, setGeneSortBy] = useState<'snpName' | 'chromosome' | 'position' | 'snpType' | 'allele1' | 'allele2' | 'remarks'>('snpName')
|
||||
const [geneSortOrder, setGeneSortOrder] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
// ========================================
|
||||
// 헬퍼 함수
|
||||
// ========================================
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
// 부 KPN 배지 렌더링 (분석불가/일치/불일치)
|
||||
// 항상 분석 불가 상태를 체크 후에 데이터를 보여줘야함
|
||||
const renderSireBadge = (chipSireName: string | null | undefined, size: 'sm' | 'lg' = 'lg') => {
|
||||
const sizeClasses = size === 'lg'
|
||||
? 'gap-1.5 text-sm px-3 py-1.5'
|
||||
@@ -289,6 +222,7 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
|
||||
// 모 개체 배지 렌더링 (일치/불일치/이력제부재)
|
||||
// 모 불일치일 경우도 유전체 분석결과가 안나옴 체크 후 데이터를 보여줘야함
|
||||
const renderDamBadge = (chipDamName: string | null | undefined, size: 'sm' | 'lg' = 'lg') => {
|
||||
// 분석불가 개체는 어미 배지 표시 안 함
|
||||
if (isExcludedCow(cow?.cowId)) return null
|
||||
@@ -345,6 +279,7 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
|
||||
// 탭 변경 핸들러
|
||||
// 유전자 탭이 활성화되면 유전자 데이터 로드
|
||||
const handleTabChange = (value: string) => {
|
||||
setActiveTab(value)
|
||||
if (value === 'gene' && !geneDataLoaded) {
|
||||
@@ -352,20 +287,6 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// 농가/보은군 배지 클릭 시 차트로 스크롤 + 하이라이트
|
||||
const handleComparisonClick = (mode: 'farm' | 'region') => {
|
||||
// 토글: 같은 모드 클릭 시 해제
|
||||
setHighlightMode(prev => prev === mode ? null : mode)
|
||||
|
||||
// 차트로 스크롤
|
||||
if (distributionChartRef.current) {
|
||||
distributionChartRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (from === 'ranking') {
|
||||
router.push('/ranking')
|
||||
@@ -376,10 +297,15 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 개체 상세 데이터 조회
|
||||
// ========================================
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// 1. 개체 정보 조회
|
||||
const cowData = await cowApi.findOne(cowNo)
|
||||
const cowDetail: CowDetail = {
|
||||
...cowData,
|
||||
@@ -388,19 +314,17 @@ export default function CowOverviewPage() {
|
||||
: undefined,
|
||||
}
|
||||
setCow(cowDetail)
|
||||
|
||||
// dataStatus에서 데이터 존재 여부 설정 (백엔드에서 가벼운 COUNT 쿼리로 확인)
|
||||
if (cowData.dataStatus) {
|
||||
setHasGeneData(cowData.dataStatus.hasGeneData)
|
||||
}
|
||||
|
||||
// 유전체 데이터 가져오기
|
||||
// 2. 유전체 데이터 조회
|
||||
const genomeDataResult = await genomeApi.findByCowNo(cowNo)
|
||||
setGenomeData(genomeDataResult)
|
||||
const genomeExists = genomeDataResult.length > 0 && !!genomeDataResult[0].genomeCows && genomeDataResult[0].genomeCows.length > 0
|
||||
setHasGenomeData(genomeExists)
|
||||
|
||||
// 분석 의뢰 정보 가져오기 (친자감별 결과 포함)
|
||||
// 3. 분석 의뢰 정보 조회 (친자감별 결과)
|
||||
try {
|
||||
const requestData = await genomeApi.getRequest(cowNo)
|
||||
setGenomeRequest(requestData)
|
||||
@@ -409,7 +333,7 @@ export default function CowOverviewPage() {
|
||||
setGenomeRequest(null)
|
||||
}
|
||||
|
||||
// 번식능력 데이터 조회
|
||||
// 4. 번식능력(MPT) 데이터 조회
|
||||
try {
|
||||
const mptData = await mptApi.findByCowId(cowNo)
|
||||
setHasReproductionData(mptData && mptData.length > 0)
|
||||
@@ -417,35 +341,23 @@ export default function CowOverviewPage() {
|
||||
setHasReproductionData(false)
|
||||
}
|
||||
|
||||
// 첫 번째 사용 가능한 탭 자동 선택
|
||||
// 5. 탭 자동 선택
|
||||
if (genomeExists) {
|
||||
setActiveTab('genome')
|
||||
} else if (geneData && geneData.length > 0) {
|
||||
setActiveTab('gene')
|
||||
}
|
||||
|
||||
// 비교 데이터 가져오기
|
||||
// 6. 비교 데이터 + 선발지수 조회
|
||||
if (genomeDataResult.length > 0) {
|
||||
try {
|
||||
const comparisonData = await genomeApi.getComparisonAverages(cowNo)
|
||||
setComparisonAverages(comparisonData)
|
||||
|
||||
// 형질별 비교 평균 가져오기 (폴리곤 차트용)
|
||||
const traitComparisonData = await genomeApi.getTraitComparisonAverages(cowNo)
|
||||
setTraitComparisonAverages(traitComparisonData)
|
||||
|
||||
// 선발지수 계산
|
||||
const ALL_TRAITS = [
|
||||
'12개월령체중',
|
||||
'도체중', '등심단면적', '등지방두께', '근내지방도',
|
||||
'체고', '십자', '체장', '흉심', '흉폭', '고장', '요각폭', '좌골폭', '곤폭', '흉위',
|
||||
'안심weight', '등심weight', '채끝weight', '목심weight', '앞다리weight',
|
||||
'우둔weight', '설도weight', '사태weight', '양지weight', '갈비weight',
|
||||
'안심rate', '등심rate', '채끝rate', '목심rate', '앞다리rate',
|
||||
'우둔rate', '설도rate', '사태rate', '양지rate', '갈비rate',
|
||||
]
|
||||
|
||||
// 필터가 활성화되어 있으면 가중치 > 0인 형질만 사용 (리스트와 동일 로직)
|
||||
const traitConditions = Object.entries(filters.traitWeights as Record<string, number>)
|
||||
.filter(([, weight]) => weight > 0)
|
||||
.map(([traitNm, weight]) => ({ traitNm, weight }))
|
||||
@@ -454,7 +366,6 @@ export default function CowOverviewPage() {
|
||||
? traitConditions
|
||||
: ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 }))
|
||||
|
||||
|
||||
const indexResult = await genomeApi.getSelectionIndex(cowNo, finalConditions)
|
||||
setSelectionIndex(indexResult)
|
||||
} catch (compErr) {
|
||||
@@ -476,25 +387,27 @@ export default function CowOverviewPage() {
|
||||
fetchData()
|
||||
}, [cowNo, toast, filters.isActive, filters.selectedTraits, filters.traitWeights])
|
||||
|
||||
// API 데이터를 화면용으로 변환
|
||||
const GENOMIC_TRAITS = useMemo(() => {
|
||||
return transformGenomeData(genomeData)
|
||||
}, [genomeData])
|
||||
// ========================================
|
||||
// 계산된 데이터 (useMemo)
|
||||
// - 의존성 변경 시에만 재계산하여 성능 최적화
|
||||
// ========================================
|
||||
|
||||
// 고유 카테고리 목록
|
||||
const CATEGORIES = useMemo(() => {
|
||||
return [...new Set(GENOMIC_TRAITS.map(t => t.category).filter(Boolean))]
|
||||
}, [GENOMIC_TRAITS])
|
||||
// API 응답 형질 데이터 (변환 없이 직접 사용)
|
||||
const GENOMIC_TRAITS = useMemo(() => genomeData[0]?.genomeCows || [], [genomeData])
|
||||
|
||||
// 종합 지표
|
||||
// 형질 카테고리 목록 (성장/생산/체형/무게/비율)
|
||||
const CATEGORIES = useMemo(() => [...new Set(GENOMIC_TRAITS.map(t => t.traitCategory).filter((cat): cat is string => !!cat))], [GENOMIC_TRAITS])
|
||||
|
||||
// 종합 선발지수 점수 (API 값 우선, 없으면 형질 평균)
|
||||
const overallScore = useMemo(() => {
|
||||
if (selectionIndex?.score !== null && selectionIndex?.score !== undefined) {
|
||||
return selectionIndex.score // 내개체
|
||||
return selectionIndex.score
|
||||
}
|
||||
if (GENOMIC_TRAITS.length === 0) return 0
|
||||
return GENOMIC_TRAITS.reduce((sum, t) => sum + t.breedVal, 0) / GENOMIC_TRAITS.length
|
||||
return GENOMIC_TRAITS.reduce((sum, t) => sum + (t.breedVal || 0), 0) / GENOMIC_TRAITS.length
|
||||
}, [GENOMIC_TRAITS, selectionIndex])
|
||||
|
||||
// 종합 백분위 (API 값 우선, 없으면 CDF 계산)
|
||||
const overallPercentile = useMemo(() => {
|
||||
if (selectionIndex?.percentile !== null && selectionIndex?.percentile !== undefined) {
|
||||
return selectionIndex.percentile
|
||||
@@ -504,79 +417,68 @@ export default function CowOverviewPage() {
|
||||
return (1 - cdf) * 100
|
||||
}
|
||||
if (GENOMIC_TRAITS.length === 0) return 50
|
||||
return GENOMIC_TRAITS.reduce((sum, t) => sum + t.percentile, 0) / GENOMIC_TRAITS.length
|
||||
return GENOMIC_TRAITS.reduce((sum, t) => sum + (t.percentile || 0), 0) / GENOMIC_TRAITS.length
|
||||
}, [GENOMIC_TRAITS, selectionIndex, overallScore])
|
||||
|
||||
// 카테고리별 평균
|
||||
// 카테고리별 평균 육종가/백분위 (카드 표시용)
|
||||
const categoryStats = useMemo(() => {
|
||||
return CATEGORIES.map(cat => {
|
||||
const traits = GENOMIC_TRAITS.filter(t => t.category === cat)
|
||||
const avgBreedVal = traits.reduce((sum, t) => sum + t.breedVal, 0) / traits.length
|
||||
const avgPercentile = traits.reduce((sum, t) => sum + t.percentile, 0) / traits.length
|
||||
const traits = GENOMIC_TRAITS.filter(t => t.traitCategory === cat)
|
||||
const avgBreedVal = traits.reduce((sum, t) => sum + (t.breedVal || 0), 0) / traits.length
|
||||
const avgPercentile = traits.reduce((sum, t) => sum + (t.percentile || 0), 0) / traits.length
|
||||
return { category: cat, avgBreedVal, avgPercentile, count: traits.length }
|
||||
})
|
||||
}, [CATEGORIES, GENOMIC_TRAITS])
|
||||
|
||||
// 평균 Z-Score
|
||||
// 농가 평균 Z-Score (정규분포 차트용)
|
||||
const farmAvgZ = useMemo(() => {
|
||||
if (!comparisonAverages?.farm || comparisonAverages.farm.length === 0) {
|
||||
return overallScore > 0.5 ? overallScore * 0.5 : 0.3
|
||||
}
|
||||
const totalEbv = comparisonAverages.farm.reduce((sum, cat) => sum + cat.avgEbv, 0)
|
||||
return totalEbv / comparisonAverages.farm.length
|
||||
return comparisonAverages.farm.reduce((sum, cat) => sum + cat.avgEbv, 0) / comparisonAverages.farm.length
|
||||
}, [comparisonAverages, overallScore])
|
||||
|
||||
// 지역 평균 Z-Score (정규분포 차트용)
|
||||
const regionAvgZ = useMemo(() => {
|
||||
if (!comparisonAverages?.region || comparisonAverages.region.length === 0) {
|
||||
return -0.2
|
||||
}
|
||||
const totalEbv = comparisonAverages.region.reduce((sum, cat) => sum + cat.avgEbv, 0)
|
||||
return totalEbv / comparisonAverages.region.length
|
||||
if (!comparisonAverages?.region || comparisonAverages.region.length === 0) return -0.2
|
||||
return comparisonAverages.region.reduce((sum, cat) => sum + cat.avgEbv, 0) / comparisonAverages.region.length
|
||||
}, [comparisonAverages])
|
||||
|
||||
// 개체 EPD 평균 (선택된 형질 기준)
|
||||
// 개체 EPD 평균 (필터 선택 형질 기준)
|
||||
const cowAvgEpd = useMemo(() => {
|
||||
const selectedTraitNames = Object.entries(filters.traitWeights)
|
||||
.filter(([, weight]) => weight > 0)
|
||||
.map(([traitNm]) => traitNm)
|
||||
|
||||
const targetTraits = selectedTraitNames.length > 0
|
||||
? GENOMIC_TRAITS.filter(t => selectedTraitNames.includes(t.name))
|
||||
? GENOMIC_TRAITS.filter(t => selectedTraitNames.includes(t.traitName || ''))
|
||||
: GENOMIC_TRAITS
|
||||
|
||||
if (targetTraits.length === 0) return null
|
||||
|
||||
const totalEpd = targetTraits.reduce((sum, t) => sum + t.actualValue, 0)
|
||||
return totalEpd / targetTraits.length
|
||||
return targetTraits.reduce((sum, t) => sum + (t.traitVal || 0), 0) / targetTraits.length
|
||||
}, [GENOMIC_TRAITS, filters.traitWeights])
|
||||
|
||||
// 농가 EPD 평균
|
||||
const farmAvgEpdValue = useMemo(() => {
|
||||
if (!comparisonAverages?.farm || comparisonAverages.farm.length === 0) return null
|
||||
const totalEpd = comparisonAverages.farm.reduce((sum, cat) => sum + (cat.avgEpd || 0), 0)
|
||||
return totalEpd / comparisonAverages.farm.length
|
||||
return comparisonAverages.farm.reduce((sum, cat) => sum + (cat.avgEpd || 0), 0) / comparisonAverages.farm.length
|
||||
}, [comparisonAverages])
|
||||
|
||||
// 보은군 EPD 평균
|
||||
// 지역 EPD 평균
|
||||
const regionAvgEpdValue = useMemo(() => {
|
||||
if (!comparisonAverages?.region || comparisonAverages.region.length === 0) return null
|
||||
const totalEpd = comparisonAverages.region.reduce((sum, cat) => sum + (cat.avgEpd || 0), 0)
|
||||
return totalEpd / comparisonAverages.region.length
|
||||
return comparisonAverages.region.reduce((sum, cat) => sum + (cat.avgEpd || 0), 0) / comparisonAverages.region.length
|
||||
}, [comparisonAverages])
|
||||
|
||||
// 필터에서 선택한 형질 데이터
|
||||
// 전역 필터에서 선택한 형질 데이터
|
||||
const filterSelectedTraitData = useMemo(() => {
|
||||
const selectedTraitNames = Object.entries(filters.traitWeights)
|
||||
.filter(([, weight]) => weight > 0)
|
||||
.map(([traitNm]) => traitNm)
|
||||
if (selectedTraitNames.length === 0) return []
|
||||
return GENOMIC_TRAITS.filter(t => selectedTraitNames.includes(t.name))
|
||||
return GENOMIC_TRAITS.filter(t => selectedTraitNames.includes(t.traitName || ''))
|
||||
}, [filters.traitWeights, GENOMIC_TRAITS])
|
||||
|
||||
// 정규분포 데이터
|
||||
const multiDistribution = useMemo(() => {
|
||||
return generateMultipleDistributions(0, 1, regionAvgZ, 1, farmAvgZ, 1)
|
||||
}, [regionAvgZ, farmAvgZ])
|
||||
// 정규분포 곡선 데이터 (전국/지역/농가 비교 차트)
|
||||
const multiDistribution = useMemo(() => generateMultipleDistributions(0, 1, regionAvgZ, 1, farmAvgZ, 1), [regionAvgZ, farmAvgZ])
|
||||
|
||||
const toggleTraitSelection = (traitId: number) => {
|
||||
setSelectedTraits(prev =>
|
||||
@@ -603,6 +505,7 @@ export default function CowOverviewPage() {
|
||||
)
|
||||
}
|
||||
|
||||
// 본문시작 ====================================================================================================
|
||||
return (
|
||||
<AuthGuard>
|
||||
<SidebarProvider>
|
||||
|
||||
Reference in New Issue
Block a user