번식 능력 검사 리스트 및 보고서 수정
This commit is contained in:
@@ -56,8 +56,11 @@ interface CowWithGenes extends Cow {
|
||||
rank?: number // 랭킹 순위
|
||||
cowShortNo?: string // 개체 요약번호
|
||||
cowReproType?: string // 번식 타입
|
||||
anlysDt?: string // 분석일자
|
||||
anlysDt?: string // 분석일자 (유전체)
|
||||
unavailableReason?: string // 분석불가 사유 (부불일치, 모불일치, 모이력제부재 등)
|
||||
hasMpt?: boolean // 번식능력검사(MPT) 여부
|
||||
mptTestDt?: string // MPT 검사일
|
||||
mptMonthAge?: number // MPT 검사일 기준 월령
|
||||
}
|
||||
|
||||
function MyCowContent() {
|
||||
@@ -75,7 +78,7 @@ function MyCowContent() {
|
||||
const [rankingMode, setRankingMode] = useState<'gene' | 'genome'>('gene') // 유전자순/유전체순
|
||||
const [sortBy, setSortBy] = useState<string>('rank') // 정렬 기준 (기본: 순위)
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc') // 정렬 방향
|
||||
const [analysisFilter, setAnalysisFilter] = useState<'all' | 'completed' | 'unavailable'>('all') // 분석 상태 필터
|
||||
const [analysisFilter, setAnalysisFilter] = useState<'all' | 'completed' | 'mptOnly' | 'unavailable'>('all') // 분석 상태 필터
|
||||
|
||||
// 커스텀 컬럼 표시 필터
|
||||
const [selectedDisplayGenes, setSelectedDisplayGenes] = useState<string[]>([]) // 테이블에 표시할 유전자
|
||||
@@ -355,6 +358,12 @@ function MyCowContent() {
|
||||
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 }
|
||||
@@ -513,9 +522,14 @@ function MyCowContent() {
|
||||
|
||||
// 분석 상태 필터
|
||||
if (analysisFilter === 'completed') {
|
||||
// 유전체 완료
|
||||
result = result.filter(cow => cow.genomeScore !== undefined && cow.genomeScore !== null)
|
||||
} else if (analysisFilter === 'mptOnly') {
|
||||
// 번식능력 검사 완료 (유전체 유무 상관없이)
|
||||
result = result.filter(cow => cow.hasMpt === true)
|
||||
} else if (analysisFilter === 'unavailable') {
|
||||
result = result.filter(cow => cow.genomeScore === undefined || cow.genomeScore === null)
|
||||
// 유전체 분석불가 (부불일치, 모불일치 등)
|
||||
result = result.filter(cow => cow.unavailableReason !== null && cow.unavailableReason !== undefined)
|
||||
}
|
||||
|
||||
// 정렬 (sortBy가 'none'이면 정렬하지 않음 - 전역 필터 순서 유지)
|
||||
@@ -684,11 +698,11 @@ function MyCowContent() {
|
||||
<p className="text-base max-sm:text-sm text-slate-500 mt-1">{'농장'} 보유 개체 현황</p>
|
||||
</div>
|
||||
|
||||
{/* 분석 상태 탭 필터 */}
|
||||
<div className="flex rounded-lg border border-slate-200 bg-slate-50 p-1.5 gap-1.5 max-sm:p-1 max-sm:gap-1">
|
||||
{/* 분석 상태 탭 필터 - 모바일: 2x2 그리드, 데스크톱: 가로 배치 */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 rounded-lg border border-slate-200 bg-slate-50 p-1.5 gap-1.5 max-sm:p-1 max-sm:gap-1">
|
||||
<button
|
||||
onClick={() => setAnalysisFilter('all')}
|
||||
className={`flex-1 px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'all'
|
||||
className={`px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'all'
|
||||
? 'bg-white text-slate-900 shadow-sm border border-slate-200'
|
||||
: 'text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
@@ -697,26 +711,38 @@ function MyCowContent() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setAnalysisFilter('completed')}
|
||||
className={`flex-1 px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'completed'
|
||||
className={`px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'completed'
|
||||
? 'bg-white text-emerald-600 shadow-sm border border-slate-200'
|
||||
: 'text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-1.5 max-sm:gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
완료 <span className="font-bold">{cows.filter(c => c.genomeScore !== undefined && c.genomeScore !== null).length}</span>
|
||||
유전체 <span className="font-bold">{cows.filter(c => c.genomeScore !== undefined && c.genomeScore !== null).length}</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setAnalysisFilter('unavailable')}
|
||||
className={`flex-1 px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'unavailable'
|
||||
? 'bg-white text-slate-600 shadow-sm border border-slate-200'
|
||||
onClick={() => setAnalysisFilter('mptOnly')}
|
||||
className={`px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'mptOnly'
|
||||
? 'bg-white text-amber-600 shadow-sm border border-slate-200'
|
||||
: 'text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-1.5 max-sm:gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-slate-400"></span>
|
||||
미검사 <span className="font-bold">{cows.filter(c => c.genomeScore === undefined || c.genomeScore === null).length}</span>
|
||||
<span className="w-2 h-2 rounded-full bg-amber-500"></span>
|
||||
번식능력 <span className="font-bold">{cows.filter(c => c.hasMpt === true).length}</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setAnalysisFilter('unavailable')}
|
||||
className={`px-3 py-2.5 max-sm:px-2 max-sm:py-2 rounded-md text-base max-sm:text-sm font-medium transition-colors ${analysisFilter === 'unavailable'
|
||||
? 'bg-white text-red-600 shadow-sm border border-slate-200'
|
||||
: 'text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-center gap-1.5 max-sm:gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-red-400"></span>
|
||||
분석불가 <span className="font-bold">{cows.filter(c => c.unavailableReason !== null && c.unavailableReason !== undefined).length}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -923,11 +949,15 @@ function MyCowContent() {
|
||||
<th className="cow-table-header" style={{ width: '50px' }}>순위</th>
|
||||
<th className="cow-table-header" style={{ width: '220px' }}>개체번호</th>
|
||||
<th className="cow-table-header" style={{ width: '90px' }}>생년월일</th>
|
||||
<th className="cow-table-header" style={{ width: '70px' }}>월령</th>
|
||||
<th className="cow-table-header" style={{ width: '60px' }}>성별</th>
|
||||
<th className="cow-table-header" style={{ width: '100px' }}>모개체번호</th>
|
||||
<th className="cow-table-header" style={{ width: '90px' }}>아비 KPN</th>
|
||||
<th className="cow-table-header" style={{ width: '90px' }}>분석일자</th>
|
||||
<th className="cow-table-header" style={{ width: '80px' }}>
|
||||
{analysisFilter === 'mptOnly' ? '월령(검사일)' : '월령(분석일)'}
|
||||
</th>
|
||||
<th className="cow-table-header" style={{ width: '90px' }}>
|
||||
{analysisFilter === 'mptOnly' ? '검사일자' : '분석일자'}
|
||||
</th>
|
||||
<th className="cow-table-header border-r-2 border-r-gray-300" style={{ width: '100px' }}>
|
||||
선발지수
|
||||
</th>
|
||||
@@ -958,19 +988,26 @@ function MyCowContent() {
|
||||
</div>
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.cowBirthDt && new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.cowBirthDt ? (() => {
|
||||
const birthDate = new Date(cow.cowBirthDt)
|
||||
const today = new Date()
|
||||
const ageInMonths = Math.floor((today.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))
|
||||
return `${ageInMonths}개월`
|
||||
})() : '-'}
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단 (유전체 데이터 없음)
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: cowBirthDt 없으면 MPT로 역산
|
||||
if ((analysisFilter === 'mptOnly' || hasMptOnly) && !cow.cowBirthDt && cow.mptTestDt && cow.mptMonthAge) {
|
||||
const testDate = new Date(cow.mptTestDt)
|
||||
const birthDate = new Date(testDate)
|
||||
birthDate.setMonth(birthDate.getMonth() - cow.mptMonthAge)
|
||||
return birthDate.toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
}
|
||||
return cow.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) : '-'
|
||||
})()}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.cowSex === "수" ? "수소" : "암소"}
|
||||
@@ -982,19 +1019,52 @@ function MyCowContent() {
|
||||
{cow.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.anlysDt ? new Date(cow.anlysDt).toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) : cow.unavailableReason ? (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${
|
||||
cow.unavailableReason.includes('부') ? 'bg-red-100 text-red-700' :
|
||||
cow.unavailableReason.includes('모') ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
{cow.unavailableReason}
|
||||
</span>
|
||||
) : '-'}
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 월령 사용
|
||||
if (analysisFilter === 'mptOnly' || hasMptOnly) {
|
||||
return cow.mptMonthAge ? `${cow.mptMonthAge}개월` : '-'
|
||||
}
|
||||
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 '-'
|
||||
})()}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 사용
|
||||
if (analysisFilter === 'mptOnly' || hasMptOnly) {
|
||||
return cow.mptTestDt ? new Date(cow.mptTestDt).toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) : '-'
|
||||
}
|
||||
// 유전체 탭: unavailableReason 있으면 배지, 없으면 분석일자
|
||||
if (cow.unavailableReason) {
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${
|
||||
cow.unavailableReason.includes('부') ? 'bg-red-100 text-red-700' :
|
||||
cow.unavailableReason.includes('모') ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
{cow.unavailableReason}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return cow.anlysDt ? new Date(cow.anlysDt).toLocaleDateString('ko-KR', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) : '-'
|
||||
})()}
|
||||
</td>
|
||||
<td className="cow-table-cell border-r-2 border-r-gray-300 !py-2 !px-0.5">
|
||||
{(cow.genomeScore !== undefined && cow.genomeScore !== null) ? (
|
||||
@@ -1169,13 +1239,40 @@ function MyCowContent() {
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">생년월일</span>
|
||||
<span className="font-medium">
|
||||
{cow.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : '-'}
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: cowBirthDt 없으면 MPT로 역산
|
||||
if ((analysisFilter === 'mptOnly' || hasMptOnly) && !cow.cowBirthDt && cow.mptTestDt && cow.mptMonthAge) {
|
||||
const testDate = new Date(cow.mptTestDt)
|
||||
const birthDate = new Date(testDate)
|
||||
birthDate.setMonth(birthDate.getMonth() - cow.mptMonthAge)
|
||||
return birthDate.toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' })
|
||||
}
|
||||
return cow.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : '-'
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">월령</span>
|
||||
<span className="text-muted-foreground">
|
||||
{(() => {
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
return (analysisFilter === 'mptOnly' || hasMptOnly) ? '월령 (검사일)' : '월령 (분석일)'
|
||||
})()}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{cow.cowBirthDt ? `${Math.floor((new Date().getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` : '-'}
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 월령 사용
|
||||
if (analysisFilter === 'mptOnly' || hasMptOnly) {
|
||||
return cow.mptMonthAge ? `${cow.mptMonthAge}개월` : '-'
|
||||
}
|
||||
if (cow.cowBirthDt && cow.anlysDt) {
|
||||
return `${Math.floor((new Date(cow.anlysDt).getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
|
||||
}
|
||||
return '-'
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
@@ -1195,17 +1292,36 @@ function MyCowContent() {
|
||||
<span className="font-medium">{cow.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">{cow.anlysDt ? '분석일' : '분석결과'}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{(() => {
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
return (analysisFilter === 'mptOnly' || hasMptOnly) ? '검사일' : (cow.anlysDt ? '분석일' : '분석결과')
|
||||
})()}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{cow.anlysDt ? new Date(cow.anlysDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : cow.unavailableReason ? (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${
|
||||
cow.unavailableReason.includes('부') ? 'bg-red-100 text-red-700' :
|
||||
cow.unavailableReason.includes('모') ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
{cow.unavailableReason}
|
||||
</span>
|
||||
) : '-'}
|
||||
{(() => {
|
||||
// 번식능력만 있는 개체 판단
|
||||
const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt
|
||||
// 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 사용
|
||||
if (analysisFilter === 'mptOnly' || hasMptOnly) {
|
||||
return cow.mptTestDt ? new Date(cow.mptTestDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : '-'
|
||||
}
|
||||
if (cow.anlysDt) {
|
||||
return new Date(cow.anlysDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' })
|
||||
}
|
||||
if (cow.unavailableReason) {
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${
|
||||
cow.unavailableReason.includes('부') ? 'bg-red-100 text-red-700' :
|
||||
cow.unavailableReason.includes('모') ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
{cow.unavailableReason}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return '-'
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user