파일 정리

This commit is contained in:
2025-12-24 22:50:13 +09:00
parent 05d89fdfcd
commit 2877a474eb
22 changed files with 1274 additions and 646 deletions

View File

@@ -182,9 +182,6 @@ function MyCowContent() {
setError(null)
// 마커 타입 정보 (gene.api 제거됨 - 추후 백엔드 구현 시 복구)
const currentMarkerTypes = markerTypes
// 전역 필터 + 랭킹 모드를 기반으로 랭킹 옵션 구성
// 타입을 any로 지정하여 백엔드 API와의 호환성 유지
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -279,94 +276,24 @@ function MyCowContent() {
ranking?: { traits?: { traitName: string; traitEbv: number | null; traitVal: number | null }[] }; // 랭킹 형질
}
const cowsWithMockGenes = response.items.map((item: RankingItem) => {
// 백엔드에서 genes 객체를 배열로 변환
// genes 객체 형식: { "PLAG1": 2, "NCAPG": 1, ... }
// 배열 형식으로 변환: [{ name: "PLAG1", genotype: "AA", favorable: true }, ...]
let genesArray = []
if (item.entity.genes && typeof item.entity.genes === 'object') {
// 백엔드 genes 객체를 배열로 변환
genesArray = Object.entries(item.entity.genes).map(([markerName, count]) => {
const favorableCount = count as number
let genotype = 'N/A'
let favorable = false
// favorableCount에 따라 유전자형 결정
if (favorableCount === 2) {
genotype = 'AA' // 동형 접합 (유리)
favorable = true
} else if (favorableCount === 1) {
genotype = 'AG' // 이형 접합 (중간)
favorable = true
} else {
genotype = 'GG' // 동형 접합 (불리)
favorable = false
}
return {
name: markerName,
genotype,
favorable,
}
})
} else {
// 백엔드에서 genes 데이터가 없으면 mock 생성
genesArray = generateMockGenes()
}
// currentMarkerTypes를 사용하여 동적으로 육량형/육질형 개수 계산
// 동형접합(AA)과 이형접합(AG)을 구분하여 계산
const isHomozygous = (genotype: string) => genotype.length === 2 && genotype[0] === genotype[1]
const quantityHomoCount = genesArray.filter(g =>
currentMarkerTypes[g.name] === 'QTY' && g.favorable && isHomozygous(g.genotype)
).length
const quantityHeteroCount = genesArray.filter(g =>
currentMarkerTypes[g.name] === 'QTY' && g.favorable && !isHomozygous(g.genotype)
).length
const qualityHomoCount = genesArray.filter(g =>
currentMarkerTypes[g.name] === 'QLT' && g.favorable && isHomozygous(g.genotype)
).length
const qualityHeteroCount = genesArray.filter(g =>
currentMarkerTypes[g.name] === 'QLT' && g.favorable && !isHomozygous(g.genotype)
).length
return {
...item.entity, // 실제 cow 데이터
rank: item.rank, // 백엔드에서 계산한 랭킹
rankScore: item.sortValue, // 백엔드에서 계산한 점수
grade: item.grade, // 백엔드에서 계산한 등급 (A~E)
genes: genesArray,
quantityGeneCount: quantityHomoCount + quantityHeteroCount,
qualityGeneCount: qualityHomoCount + qualityHeteroCount,
quantityHomoCount,
quantityHeteroCount,
qualityHomoCount,
qualityHeteroCount,
// 유전체 점수는 sortValue에서 가져옴 (백엔드 랭킹 엔진이 계산한 값)
...item.entity,
rank: item.rank,
rankScore: item.sortValue,
grade: item.grade,
genomeScore: item.sortValue,
geneScore: item.compositeScores?.geneScore,
// 번식 정보 (백엔드에서 가져옴 - 암소만)
// 번식 정보
calvingCount: item.entity.calvingCount,
bcs: item.entity.bcs,
inseminationCount: item.entity.inseminationCount,
// 근친도 (백엔드에서 계산된 근친계수 백분율)
inbreedingPercent: item.entity.inbreedingPercent ?? 0,
// 아비 KPN 번호 (genome trait에서 가져옴)
sireKpn: item.entity.sireKpn ?? null,
// 분석일자
anlysDt: item.entity.anlysDt ?? null,
// 분석불가 사유
unavailableReason: item.entity.unavailableReason ?? null,
// 번식능력검사(MPT) 여부
hasMpt: item.entity.hasMpt ?? false,
// MPT 검사일
mptTestDt: item.entity.mptTestDt ?? null,
// MPT 월령
mptMonthAge: item.entity.mptMonthAge ?? null,
//====================================================================================================================
// 형질 데이터 (백엔드에서 계산됨, 형질명 → 표준화육종가 매핑)
// 백엔드 응답: { traitName: string, traitVal: number, traitEbv: number, traitPercentile: number }
// 형질 데이터
traits: item.ranking?.traits?.reduce((acc: Record<string,
{ breedVal: number | null, traitVal: number | null }>, t: { traitName: string; traitEbv: number | null; traitVal: number | null }) => {
acc[t.traitName] = { breedVal: t.traitEbv, traitVal: t.traitVal };
@@ -389,98 +316,6 @@ function MyCowContent() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters, rankingMode, isFilterSet])
// Mock 유전자 생성 함수 (실제로는 API에서 가져와야 함)
const generateMockGenes = () => {
// 모든 소가 다양한 유전자를 가지도록 더 많은 유전자 풀 생성
const genePool = [
// 육량형 유전자
{ name: 'PLAG1', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
{ name: 'NCAPG', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
{ name: 'LCORL', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
{ name: 'MSTN', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA'] },
{ name: 'IGF1', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
{ name: 'GH1', genotypes: ['CC', 'CT', 'TT'], favorable: ['CC', 'CT'] },
{ name: 'LAP3', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA'] },
{ name: 'ARRDC3', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
// 육질형 유전자
{ name: 'CAPN1', genotypes: ['CC', 'CG', 'GG'], favorable: ['CC', 'CG'] },
{ name: 'CAST', genotypes: ['AA', 'AG', 'GG'], favorable: ['GG', 'AG'] },
{ name: 'FASN', genotypes: ['AA', 'AG', 'GG'], favorable: ['GG', 'AG'] },
{ name: 'SCD', genotypes: ['AA', 'AV', 'VV'], favorable: ['VV', 'AV'] },
{ name: 'FABP4', genotypes: ['AA', 'AG', 'GG'], favorable: ['AA', 'AG'] },
{ name: 'SREBP1', genotypes: ['CC', 'CT', 'TT'], favorable: ['CC', 'CT'] },
{ name: 'DGAT1', genotypes: ['AA', 'AK', 'KK'], favorable: ['KK', 'AK'] },
{ name: 'LEP', genotypes: ['CC', 'CT', 'TT'], favorable: ['TT', 'CT'] },
]
// 모든 유전자를 포함 (랜덤 유전자형)
return genePool.map(gene => {
const genotype = gene.genotypes[Math.floor(Math.random() * gene.genotypes.length)]
return {
name: gene.name,
genotype,
favorable: gene.favorable.includes(genotype),
}
})
}
// ============================================
// 유전자형 판단 및 스타일 정의
// ============================================
/**
* 동형접합 여부 판단
* AA, GG, CC, TT 등 → true
* AG, CT, AK 등 → false
*/
const isHomozygous = (genotype: string): boolean => {
return genotype.length === 2 && genotype[0] === genotype[1]
}
/**
* 유전자 뱃지 스타일 정의
* @param genotype 유전자형 (AA, AG, GG 등)
* @param favorable 우량 유전자 여부
* @param geneCategory 유전자 카테고리 ('QTY': 육량형, 'QLT': 육질형)
*/
type GeneBadgeStyle = {
className: string
icon: 'star' | 'circle' | 'double-circle' | 'minus' | 'none'
}
const getGeneBadgeStyle = (
genotype: string,
favorable: boolean,
geneCategory: 'QTY' | 'QLT'
): GeneBadgeStyle => {
const isHomo = isHomozygous(genotype)
// 1. 동형접합 우량 (AA형) → 진한 색 (육량: 파랑, 육질: 주황)
if (isHomo && favorable) {
return {
className: geneCategory === 'QTY'
? 'bg-blue-600 text-white border-blue-700'
: 'bg-orange-600 text-white border-orange-700',
icon: 'none',
}
}
// 2. 이형접합 우량 (AG형) → 중간 색 (육량: 파랑, 육질: 주황)
if (!isHomo && favorable) {
return {
className: geneCategory === 'QTY'
? 'bg-blue-400 text-white border-blue-500'
: 'bg-orange-400 text-white border-orange-500',
icon: 'none',
}
}
// 3. 불량형 (GG형) → 연한 회색
return {
className: 'bg-gray-300 text-gray-600 border-gray-400',
icon: 'none',
}
}
// ============================================
// 컬럼 스타일은 globals.css의 CSS 변수로 관리됨
@@ -962,10 +797,10 @@ function MyCowContent() {
</th>
{selectedDisplayGenes.length > 0 && (
<th className="cow-table-header bg-blue-50" style={{ width: '140px' }}></th>
<th className="cow-table-header" style={{ width: '140px' }}></th>
)}
{selectedDisplayTraits.length > 0 && (
<th className="cow-table-header bg-teal-50" style={{ width: '140px' }}></th>
<th className="cow-table-header" style={{ width: '140px' }}></th>
)}
</tr>
</thead>
@@ -1022,15 +857,20 @@ function MyCowContent() {
{(() => {
// 번식능력만 있는 개체 판단
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 월령 사용
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 기준 월령
if (analysisFilter === 'mptOnly' || hasMptOnly) {
return cow.mptMonthAge ? `${cow.mptMonthAge}개월` : '-'
if (cow.cowBirthDt && cow.mptTestDt) {
const birthDate = new Date(cow.cowBirthDt)
const refDate = new Date(cow.mptTestDt)
return `${Math.floor((refDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
}
return '-'
}
// 유전체 분석일 기준 월령
if (cow.cowBirthDt && cow.anlysDt) {
const birthDate = new Date(cow.cowBirthDt)
const refDate = new Date(cow.anlysDt)
const ageInMonths = Math.floor((refDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))
return `${ageInMonths}개월`
return `${Math.floor((refDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
}
return '-'
})()}
@@ -1079,7 +919,7 @@ function MyCowContent() {
</td>
{selectedDisplayGenes.length > 0 && (
<td
className="py-2 px-2 text-sm bg-blue-50/30"
className="py-2 px-2 text-sm"
onClick={(e) => e.stopPropagation()}
>
{(() => {
@@ -1092,15 +932,17 @@ function MyCowContent() {
{displayGenes.map((geneName) => {
const gene = cow.genes?.find(g => g.name === geneName)
const genotype = gene?.genotype || '-'
const favorable = gene?.favorable || false
const geneCategory = markerTypes[geneName] as 'QTY' | 'QLT'
const badgeStyle = gene ? getGeneBadgeStyle(genotype, favorable, geneCategory) : null
// 육량형: 파랑, 육질형: 주황
const badgeClass = geneCategory === 'QTY'
? 'bg-blue-500 text-white'
: 'bg-orange-500 text-white'
return (
<div key={geneName} className="flex items-center gap-2">
<span className="text-xs text-slate-600 min-w-[60px]">{geneName}</span>
{gene ? (
<Badge className={`text-xs font-semibold ${badgeStyle?.className}`}>
<Badge className={`text-xs font-semibold ${badgeClass}`}>
{genotype}
</Badge>
) : (
@@ -1134,7 +976,7 @@ function MyCowContent() {
)}
{selectedDisplayTraits.length > 0 && (
<td
className="py-2 px-2 text-sm bg-teal-50/30"
className="py-2 px-2 text-sm"
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col items-start gap-1.5">
@@ -1264,10 +1106,14 @@ function MyCowContent() {
{(() => {
// 번식능력만 있는 개체 판단
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 월령 사용
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 기준 월령
if (analysisFilter === 'mptOnly' || hasMptOnly) {
return cow.mptMonthAge ? `${cow.mptMonthAge}개월` : '-'
if (cow.cowBirthDt && cow.mptTestDt) {
return `${Math.floor((new Date(cow.mptTestDt).getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
}
return '-'
}
// 유전체 분석일 기준 월령
if (cow.cowBirthDt && cow.anlysDt) {
return `${Math.floor((new Date(cow.anlysDt).getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
}
@@ -1344,12 +1190,14 @@ function MyCowContent() {
{displayGenes.map((geneName) => {
const gene = cow.genes?.find(g => g.name === geneName)
const geneCategory = markerTypes[geneName] as 'QTY' | 'QLT'
const genotype = gene?.genotype || 'GG'
const favorable = gene?.favorable || false
const badgeStyle = getGeneBadgeStyle(genotype, favorable, geneCategory)
const genotype = gene?.genotype || '-'
// 육량형: 파랑, 육질형: 주황
const badgeClass = geneCategory === 'QTY'
? 'bg-blue-500 text-white'
: 'bg-orange-500 text-white'
return (
<Badge key={geneName} className={`text-xs px-1.5 py-0.5 font-medium ${badgeStyle.className}`}>
<Badge key={geneName} className={`text-xs px-1.5 py-0.5 font-medium ${badgeClass}`}>
{geneName} {genotype}
</Badge>
)