개체분석 상태 값 수정
This commit is contained in:
@@ -1,43 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import { useSearchParams, useParams, useRouter } from "next/navigation"
|
||||
import { AuthGuard } from "@/components/auth/auth-guard"
|
||||
import { CowNumberDisplay } from "@/components/common/cow-number-display"
|
||||
import { AppSidebar } from "@/components/layout/app-sidebar"
|
||||
import { SiteHeader } from "@/components/layout/site-header"
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import { ComparisonAveragesDto, TraitComparisonAveragesDto, cowApi, genomeApi, geneApi, GeneDetail, GenomeRequestDto, mptApi, MptDto } from "@/lib/api"
|
||||
import { CowDetail } from "@/types/cow.types"
|
||||
import { GenomeTrait } from "@/types/genome.types"
|
||||
import { useGlobalFilter } from "@/contexts/GlobalFilterContext"
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowUp,
|
||||
BarChart3,
|
||||
CheckCircle2,
|
||||
Download,
|
||||
Dna,
|
||||
Activity,
|
||||
X,
|
||||
XCircle,
|
||||
Search,
|
||||
} from 'lucide-react'
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useGlobalFilter } from "@/contexts/GlobalFilterContext"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { ComparisonAveragesDto, cowApi, geneApi, GeneDetail, genomeApi, GenomeRequestDto, TraitComparisonAveragesDto } from "@/lib/api"
|
||||
import { getInvalidMessage, getInvalidReason, isExcludedCow, isValidGenomeAnalysis } from "@/lib/utils/genome-analysis-config"
|
||||
import { CowDetail } from "@/types/cow.types"
|
||||
import { GenomeTrait } from "@/types/genome.types"
|
||||
import {
|
||||
Activity,
|
||||
ArrowLeft,
|
||||
BarChart3,
|
||||
CheckCircle2,
|
||||
ChevronUp,
|
||||
Dna,
|
||||
Search,
|
||||
X,
|
||||
XCircle
|
||||
} from 'lucide-react'
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation"
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CategoryEvaluationCard } from "./genome/_components/category-evaluation-card"
|
||||
import { TraitComparison } from "./genome/_components/genome-integrated-comparison"
|
||||
import { NormalDistributionChart } from "./genome/_components/normal-distribution-chart"
|
||||
import { TraitDistributionCharts } from "./genome/_components/trait-distribution-charts"
|
||||
import { TraitComparison } from "./genome/_components/genome-integrated-comparison"
|
||||
import { CowNumberDisplay } from "@/components/common/cow-number-display"
|
||||
import { isValidGenomeAnalysis, getInvalidReason, getInvalidMessage } from "@/lib/utils/genome-analysis-config"
|
||||
import { AuthGuard } from "@/components/auth/auth-guard"
|
||||
import { MptTable } from "./reproduction/_components/mpt-table"
|
||||
|
||||
// 형질명 → 카테고리 매핑 (한우 35개 형질)
|
||||
const TRAIT_CATEGORY_MAP: Record<string, string> = {
|
||||
@@ -254,6 +251,74 @@ export default function CowOverviewPage() {
|
||||
const [geneSortBy, setGeneSortBy] = useState<'snpName' | 'chromosome' | 'position' | 'snpType' | 'allele1' | 'allele2' | 'remarks'>('snpName')
|
||||
const [geneSortOrder, setGeneSortOrder] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
// 부 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'
|
||||
: 'gap-1 text-xs px-2 py-1'
|
||||
const iconSize = size === 'lg' ? 'w-4 h-4' : 'w-3 h-3'
|
||||
|
||||
// 분석불가 개체 먼저 체크 (EXCLUDED_COW_IDS 또는 DB에서 '분석불가'/'정보없음'으로 저장된 경우)
|
||||
if (isExcludedCow(cow?.cowId) || chipSireName === '분석불가' || chipSireName === '정보없음') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-slate-400 text-white font-semibold rounded-full shrink-0`}>
|
||||
<XCircle className={iconSize} />
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-primary text-primary-foreground font-semibold rounded-full shrink-0`}>
|
||||
<CheckCircle2 className={iconSize} />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-red-500 text-white font-semibold rounded-full shrink-0`}>
|
||||
<XCircle className={iconSize} />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 모 개체 배지 렌더링 (일치/불일치/이력제부재)
|
||||
const renderDamBadge = (chipDamName: string | null | undefined, size: 'sm' | 'lg' = 'lg') => {
|
||||
// 분석불가 개체는 어미 배지 표시 안 함
|
||||
if (isExcludedCow(cow?.cowId)) return null
|
||||
|
||||
const sizeClasses = size === 'lg'
|
||||
? 'gap-1.5 text-sm px-3 py-1.5'
|
||||
: 'gap-1 text-xs px-2 py-1'
|
||||
const iconSize = size === 'lg' ? 'w-4 h-4' : 'w-3 h-3'
|
||||
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-primary text-primary-foreground font-semibold rounded-full shrink-0`}>
|
||||
<CheckCircle2 className={iconSize} />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-red-500 text-white font-semibold rounded-full shrink-0`}>
|
||||
<XCircle className={iconSize} />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className={`flex items-center ${sizeClasses} bg-amber-500 text-white font-semibold rounded-full shrink-0`}>
|
||||
<XCircle className={iconSize} />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 유전자 데이터 지연 로드 함수
|
||||
const loadGeneData = async () => {
|
||||
if (geneDataLoaded || geneDataLoading) return // 이미 로드했거나 로딩 중이면 스킵
|
||||
@@ -264,10 +329,12 @@ export default function CowOverviewPage() {
|
||||
const geneList = geneDataResult || []
|
||||
setGeneData(geneList)
|
||||
setGeneDataLoaded(true)
|
||||
setHasGeneData(geneList.length > 0)
|
||||
} catch (geneErr) {
|
||||
console.error('유전자 데이터 조회 실패:', geneErr)
|
||||
setGeneData([])
|
||||
setGeneDataLoaded(true)
|
||||
setHasGeneData(false)
|
||||
} finally {
|
||||
setGeneDataLoading(false)
|
||||
}
|
||||
@@ -318,6 +385,11 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
setCow(cowDetail)
|
||||
|
||||
// dataStatus에서 데이터 존재 여부 설정 (백엔드에서 가벼운 COUNT 쿼리로 확인)
|
||||
if (cowData.dataStatus) {
|
||||
setHasGeneData(cowData.dataStatus.hasGeneData)
|
||||
}
|
||||
|
||||
// 유전체 데이터 가져오기
|
||||
const genomeDataResult = await genomeApi.findByCowNo(cowNo)
|
||||
setGenomeData(genomeDataResult)
|
||||
@@ -333,9 +405,6 @@ export default function CowOverviewPage() {
|
||||
setGenomeRequest(null)
|
||||
}
|
||||
|
||||
// 유전자(SNP) 데이터는 탭 클릭 시 로드 (지연 로딩)
|
||||
setHasGeneData(true) // 탭은 보여주되, 데이터는 나중에 로드
|
||||
|
||||
// 번식능력 데이터 (현재는 목업 - 추후 API 연동)
|
||||
// TODO: 번식능력 API 연동
|
||||
setHasReproductionData(false)
|
||||
@@ -579,8 +648,8 @@ export default function CowOverviewPage() {
|
||||
>
|
||||
<Dna className="hidden sm:block h-6 w-6 shrink-0" />
|
||||
<span className="font-bold text-sm sm:text-xl">유전자</span>
|
||||
<span className={`text-xs sm:text-sm px-1.5 sm:px-2.5 py-0.5 sm:py-1 rounded font-semibold shrink-0 ${hasGeneData && isValidGenomeAnalysis(genomeRequest?.chipSireName, genomeRequest?.chipDamName, cow?.cowId) ? 'bg-green-500 text-white' : 'bg-slate-300 text-slate-600'}`}>
|
||||
{hasGeneData && isValidGenomeAnalysis(genomeRequest?.chipSireName, genomeRequest?.chipDamName, cow?.cowId) ? '완료' : '미검사'}
|
||||
<span className={`text-xs sm:text-sm px-1.5 sm:px-2.5 py-0.5 sm:py-1 rounded font-semibold shrink-0 ${hasGeneData && !(isExcludedCow(cow?.cowId) || genomeRequest?.chipSireName === '분석불가' || genomeRequest?.chipSireName === '정보없음') ? 'bg-green-500 text-white' : 'bg-slate-300 text-slate-600'}`}>
|
||||
{hasGeneData && !(isExcludedCow(cow?.cowId) || genomeRequest?.chipSireName === '분석불가' || genomeRequest?.chipSireName === '정보없음') ? '완료' : '미검사'}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
@@ -695,30 +764,7 @@ export default function CowOverviewPage() {
|
||||
<span className="text-2xl font-bold text-foreground break-all">
|
||||
{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
|
||||
</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-slate-400 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -731,34 +777,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-amber-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
// 정보없음(null/undefined)일 때는 배지 표시 안함
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -770,30 +789,7 @@ export default function CowOverviewPage() {
|
||||
<span className="text-base font-bold text-foreground break-all">
|
||||
{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
|
||||
</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-slate-400 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
@@ -804,34 +800,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-base font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-amber-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
// 정보없음(null/undefined)일 때는 배지 표시 안함
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1062,30 +1031,7 @@ export default function CowOverviewPage() {
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-between gap-3">
|
||||
<span className="text-2xl font-bold text-foreground">{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-slate-400 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -1098,34 +1044,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-amber-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
// 정보없음(null/undefined)일 때는 배지 표시 안함
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1135,30 +1054,7 @@ export default function CowOverviewPage() {
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">부 KPN</span>
|
||||
<div className="flex-1 px-4 py-3.5 flex items-center justify-between gap-2">
|
||||
<span className="text-base font-bold text-foreground">{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-slate-400 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
@@ -1169,34 +1065,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-base font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-amber-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
// 정보없음(null/undefined)일 때는 배지 표시 안함
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1333,30 +1202,7 @@ export default function CowOverviewPage() {
|
||||
<span className="text-2xl font-bold text-foreground break-all">
|
||||
{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
|
||||
</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-slate-400 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -1369,33 +1215,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-primary text-primary-foreground text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-red-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 bg-amber-500 text-white text-sm font-semibold px-3 py-1.5 rounded-full shrink-0">
|
||||
<XCircle className="w-4 h-4" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1407,30 +1227,7 @@ export default function CowOverviewPage() {
|
||||
<span className="text-base font-bold text-foreground break-all">
|
||||
{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
|
||||
</span>
|
||||
{(() => {
|
||||
const chipSireName = genomeRequest?.chipSireName
|
||||
if (chipSireName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipSireName && chipSireName !== '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-slate-400 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<span>분석불가</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
{renderSireBadge(genomeRequest?.chipSireName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
@@ -1441,33 +1238,7 @@ export default function CowOverviewPage() {
|
||||
) : (
|
||||
<span className="text-base font-bold text-foreground">-</span>
|
||||
)}
|
||||
{(() => {
|
||||
const chipDamName = genomeRequest?.chipDamName
|
||||
if (chipDamName === '일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-primary text-primary-foreground text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
<span>일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '불일치') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>불일치</span>
|
||||
</span>
|
||||
)
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
return (
|
||||
<span className="flex items-center gap-1 bg-amber-500 text-white text-xs font-semibold px-2 py-1 rounded-full shrink-0">
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>이력제부재</span>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
{renderDamBadge(genomeRequest?.chipDamName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1477,8 +1248,8 @@ export default function CowOverviewPage() {
|
||||
{/* 유전자 검색 및 필터 섹션 */}
|
||||
<h3 className="text-lg lg:text-xl font-bold text-foreground">유전자 분석 결과</h3>
|
||||
|
||||
{/* 친자확인 결과에 따른 분기 */}
|
||||
{isValidGenomeAnalysis(genomeRequest?.chipSireName, genomeRequest?.chipDamName, cow?.cowId) ? (
|
||||
{/* 유전자 탭 분기: 분석불가/정보없음만 차단, 불일치/이력제부재는 유전자 데이터 표시 */}
|
||||
{!(isExcludedCow(cow?.cowId) || genomeRequest?.chipSireName === '분석불가' || genomeRequest?.chipSireName === '정보없음') ? (
|
||||
<>
|
||||
<div className="flex flex-col gap-3 sm:gap-3 p-3.5 max-sm:p-3 sm:px-4 sm:py-3 rounded-xl bg-slate-50/50 border border-slate-200/50">
|
||||
{/* 검색창 */}
|
||||
@@ -1867,15 +1638,154 @@ export default function CowOverviewPage() {
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Card className="bg-slate-50 border border-border rounded-2xl">
|
||||
<CardContent className="p-8 text-center">
|
||||
<Dna className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-foreground mb-2">유전자 분석 데이터 없음</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
이 개체는 아직 유전자(SNP) 분석이 완료되지 않았습니다.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<>
|
||||
{/* 개체 정보 섹션 */}
|
||||
<h3 className="text-lg lg:text-xl font-bold text-foreground">개체 정보</h3>
|
||||
|
||||
<Card className="bg-white border border-border shadow-sm rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-0">
|
||||
{/* 데스크탑: 가로 그리드 */}
|
||||
<div className="hidden lg:grid lg:grid-cols-4 divide-x divide-border">
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">개체번호</span>
|
||||
</div>
|
||||
<div className="px-5 py-4">
|
||||
<CowNumberDisplay cowId={cowNo} variant="highlight" className="text-2xl font-bold text-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">생년월일</span>
|
||||
</div>
|
||||
<div className="px-5 py-4">
|
||||
<span className="text-2xl font-bold text-foreground">
|
||||
{cow?.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR') : '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">월령</span>
|
||||
</div>
|
||||
<div className="px-5 py-4">
|
||||
<span className="text-2xl font-bold text-foreground">
|
||||
{cow?.cowBirthDt
|
||||
? `${Math.floor((new Date().getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
|
||||
: '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">분석일자</span>
|
||||
</div>
|
||||
<div className="px-5 py-4">
|
||||
<span className="text-2xl font-bold text-foreground">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 모바일: 좌우 배치 리스트 */}
|
||||
<div className="lg:hidden divide-y divide-border">
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">개체번호</span>
|
||||
<div className="flex-1 px-4 py-3.5">
|
||||
<CowNumberDisplay cowId={cowNo} variant="highlight" className="text-base font-bold text-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">생년월일</span>
|
||||
<span className="flex-1 px-4 py-3.5 text-base font-bold text-foreground">
|
||||
{cow?.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR') : '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">월령</span>
|
||||
<span className="flex-1 px-4 py-3.5 text-base font-bold text-foreground">
|
||||
{cow?.cowBirthDt
|
||||
? `${Math.floor((new Date().getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월`
|
||||
: '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">분석일자</span>
|
||||
<span className="flex-1 px-4 py-3.5 text-base font-bold text-foreground">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 혈통정보 섹션 */}
|
||||
<h3 className="text-lg lg:text-xl font-bold text-foreground">혈통정보</h3>
|
||||
|
||||
<Card className="bg-white border border-border shadow-sm rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-0">
|
||||
{/* 데스크탑: 가로 그리드 */}
|
||||
<div className="hidden lg:grid lg:grid-cols-2 divide-x divide-border">
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">부 KPN번호</span>
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-between gap-3">
|
||||
<span className="text-2xl font-bold text-foreground">{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}</span>
|
||||
{renderSireBadge(genomeRequest?.chipSireName)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-muted/50 px-5 py-3 border-b border-border">
|
||||
<span className="text-base font-semibold text-muted-foreground">모 개체번호</span>
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-between gap-3">
|
||||
{cow?.damCowId && cow.damCowId !== '0' ? (
|
||||
<CowNumberDisplay cowId={cow.damCowId} variant="highlight" className="text-2xl font-bold text-foreground" />
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-foreground">-</span>
|
||||
)}
|
||||
{renderDamBadge(genomeRequest?.chipDamName)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 모바일: 세로 리스트 */}
|
||||
<div className="lg:hidden divide-y divide-border">
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">부 KPN</span>
|
||||
<div className="flex-1 px-4 py-3.5 flex items-center justify-between gap-2">
|
||||
<span className="text-base font-bold text-foreground">{cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}</span>
|
||||
{renderSireBadge(genomeRequest?.chipSireName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="w-28 shrink-0 bg-muted/50 px-4 py-3.5 text-base font-medium text-muted-foreground">모 개체번호</span>
|
||||
<div className="flex-1 px-4 py-3.5 flex items-center justify-between gap-2">
|
||||
{cow?.damCowId && cow.damCowId !== '0' ? (
|
||||
<CowNumberDisplay cowId={cow.damCowId} variant="highlight" className="text-base font-bold text-foreground" />
|
||||
) : (
|
||||
<span className="text-base font-bold text-foreground">-</span>
|
||||
)}
|
||||
{renderDamBadge(genomeRequest?.chipDamName, 'sm')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 유전자 분석 결과 섹션 */}
|
||||
<h3 className="text-lg lg:text-xl font-bold text-foreground">유전자 분석 결과</h3>
|
||||
<Card className="bg-slate-100 border border-slate-300 rounded-2xl">
|
||||
<CardContent className="p-8 text-center">
|
||||
<Dna className="h-12 w-12 text-slate-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-2">
|
||||
{genomeRequest ? '유전자 분석 불가' : '유전자 분석불가'}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
{genomeRequest
|
||||
? getInvalidMessage(genomeRequest?.chipSireName, genomeRequest?.chipDamName, cow?.cowId).replace('유전체', '유전자')
|
||||
: '이 개체는 아직 유전자(SNP) 분석이 진행되지 않았습니다.'
|
||||
}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
@@ -1955,14 +1865,14 @@ export default function CowOverviewPage() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 플로팅 맨 위로 버튼 */}
|
||||
{/* 플로팅 맨 위로 버튼 - 글래스모피즘 */}
|
||||
{showScrollTop && (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 z-50 w-12 h-12 bg-primary text-white rounded-full shadow-lg hover:bg-primary/90 transition-all duration-300 flex items-center justify-center hover:scale-110 active:scale-95"
|
||||
className="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 z-50 w-10 h-10 sm:w-12 sm:h-12 bg-white/80 backdrop-blur-md border border-white/50 text-slate-700 rounded-full shadow-lg hover:bg-white/90 transition-all duration-300 flex items-center justify-center hover:scale-110 active:scale-95"
|
||||
aria-label="맨 위로"
|
||||
>
|
||||
<ArrowUp className="w-6 h-6" />
|
||||
<ChevronUp className="w-5 h-5 sm:w-6 sm:h-6" />
|
||||
</button>
|
||||
)}
|
||||
</SidebarProvider>
|
||||
|
||||
Reference in New Issue
Block a user