INIT
This commit is contained in:
206
frontend/src/components/genome/gene-possession-status.tsx
Normal file
206
frontend/src/components/genome/gene-possession-status.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
'use client'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useAnalysisYear } from "@/contexts/AnalysisYearContext"
|
||||
import { useGlobalFilter } from "@/contexts/GlobalFilterContext"
|
||||
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 } = useGlobalFilter()
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user