Files
genome2025/frontend/src/components/genome/gene-possession-status.tsx
2025-12-24 08:25:44 +09:00

207 lines
7.6 KiB
TypeScript

'use client'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { useEffect, useState } from "react"
import { useAnalysisYear } from "@/contexts/AnalysisYearContext"
import { useFilterStore } from "@/store/filter-store"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Filter, ChevronDown, ChevronUp } from "lucide-react"
interface GeneData {
geneName: string
geneType: '육량' | '육질' // 유전자 분류
farmRate: number // 우리 농장 우량형(AA) 보유율
regionAvgRate: number // 지역 평균
}
interface GenePossessionStatusProps {
farmNo: number | null
}
export function GenePossessionStatus({ farmNo }: GenePossessionStatusProps) {
const { selectedYear } = useAnalysisYear()
const { filters } = useFilterStore()
const [allGenes, setAllGenes] = useState<GeneData[]>([])
const [loading, setLoading] = useState(true)
const [isExpanded, setIsExpanded] = useState(false)
// 선택된 유전자 확인
const selectedGenes = filters.selectedGenes || []
const hasFilter = selectedGenes.length > 0
useEffect(() => {
const fetchData = async () => {
setLoading(true)
// TODO: 백엔드 API 연동 시 실제 데이터 fetch
// 현재는 목업 데이터 사용 (전체 유전자 리스트)
const mockAllGenes: GeneData[] = [
// 육량 관련
{ geneName: 'PLAG1', geneType: '육량', farmRate: 85, regionAvgRate: 72 },
{ geneName: 'NCAPG', geneType: '육량', farmRate: 82, regionAvgRate: 75 },
{ geneName: 'LCORL', geneType: '육량', farmRate: 78, regionAvgRate: 68 },
{ geneName: 'LAP3', geneType: '육량', farmRate: 65, regionAvgRate: 58 },
// 육질 관련
{ geneName: 'FABP4', geneType: '육질', farmRate: 88, regionAvgRate: 70 },
{ geneName: 'SCD', geneType: '육질', farmRate: 80, regionAvgRate: 72 },
{ geneName: 'DGAT1', geneType: '육질', farmRate: 75, regionAvgRate: 65 },
{ geneName: 'FASN', geneType: '육질', farmRate: 70, regionAvgRate: 62 },
{ geneName: 'CAPN1', geneType: '육질', farmRate: 82, regionAvgRate: 68 },
{ geneName: 'CAST', geneType: '육질', farmRate: 77, regionAvgRate: 64 },
]
// 선택된 유전자 중 목업 데이터에 없는 유전자가 있다면 추가
if (selectedGenes.length > 0) {
selectedGenes.forEach(geneName => {
if (!mockAllGenes.find(g => g.geneName === geneName)) {
// 선택된 유전자가 목업 데이터에 없으면 기본값으로 추가
mockAllGenes.push({
geneName: geneName,
geneType: geneName.includes('PLAG') || geneName.includes('NCAPG') || geneName.includes('LCORL') || geneName.includes('LAP') ? '육량' : '육질',
farmRate: Math.floor(Math.random() * 30) + 60, // 60-90 사이 랜덤값
regionAvgRate: Math.floor(Math.random() * 20) + 55, // 55-75 사이 랜덤값
})
}
})
}
setAllGenes(mockAllGenes)
setLoading(false)
}
fetchData()
}, [selectedYear, farmNo, selectedGenes])
if (loading) {
return (
<div className="h-[300px] flex items-center justify-center">
<p className="text-muted-foreground"> ...</p>
</div>
)
}
if (!farmNo) {
return (
<div className="h-[300px] flex items-center justify-center">
<div className="text-center">
<p className="text-muted-foreground mb-2"> </p>
<p className="text-sm text-muted-foreground"> </p>
</div>
</div>
)
}
// 필터에 따라 표시할 유전자 선택
const allDisplayGenes = hasFilter
? allGenes.filter(g => selectedGenes.includes(g.geneName))
: allGenes.slice(0, 6) // TOP 6 (보유율 높은 순으로 이미 정렬됨)
// 접기/펼치기 적용 (4개 기준)
// 단, 선택된 유전자가 있을 때는 모두 표시
const DISPLAY_LIMIT = 4
const displayGenes = hasFilter || isExpanded ? allDisplayGenes : allDisplayGenes.slice(0, DISPLAY_LIMIT)
const hasMore = !hasFilter && allDisplayGenes.length > DISPLAY_LIMIT
return (
<div className="space-y-4">
{/* 필터 배지 표시 */}
{hasFilter && (
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-1.5 text-xs text-gray-600">
<Filter className="h-3.5 w-3.5" />
<span className="font-medium"> :</span>
</div>
{selectedGenes.map(gene => (
<Badge
key={gene}
variant="secondary"
className="text-xs font-medium bg-blue-50 text-blue-700 border-blue-200"
>
{gene}
</Badge>
))}
</div>
)}
{/* 유전자별 바 차트 */}
<div className="space-y-2.5">
{displayGenes.map((gene, index) => (
<div key={gene.geneName} className="space-y-1">
{/* 유전자명 + 타입 배지 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-800 min-w-[60px]">
{gene.geneName}
</span>
<Badge
variant="outline"
className={`text-xs px-2 py-0 ${
gene.geneType === '육량'
? 'bg-blue-50 text-blue-700 border-blue-200'
: 'bg-purple-50 text-purple-700 border-purple-200'
}`}
>
{gene.geneType}
</Badge>
</div>
<span className="text-sm font-bold text-gray-900">
{gene.farmRate}%
</span>
</div>
{/* 프로그레스 바 */}
<div className="relative h-7 bg-gray-100 rounded-full overflow-hidden">
{/* 우리 농장 */}
<div
className={`absolute h-full transition-all duration-800 ${
gene.geneType === '육량' ? 'bg-blue-500' : 'bg-purple-500'
}`}
style={{ width: `${gene.farmRate}%` }}
/>
{/* 지역 평균 표시 (점선) */}
<div
className="absolute h-full border-l-2 border-dashed border-gray-400"
style={{ left: `${gene.regionAvgRate}%` }}
title={`지역 평균: ${gene.regionAvgRate}%`}
/>
</div>
{/* 지역 평균 레이블 */}
<div className="flex justify-end">
<span className="text-xs text-gray-500">
: {gene.regionAvgRate}%
</span>
</div>
</div>
))}
</div>
{/* 더보기/접기 버튼 */}
{hasMore && (
<div className="flex justify-center">
<Button
variant="ghost"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
className="text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100"
>
{isExpanded ? (
<>
<ChevronUp className="h-4 w-4 mr-1" />
</>
) : (
<>
<ChevronDown className="h-4 w-4 mr-1" />
{allDisplayGenes.length - DISPLAY_LIMIT}
</>
)}
</Button>
</div>
)}
</div>
)
}