유전자 DB 연동 및 필터검색 수정
This commit is contained in:
@@ -24,7 +24,6 @@ export class GeneService {
|
|||||||
cowId,
|
cowId,
|
||||||
delDt: IsNull(),
|
delDt: IsNull(),
|
||||||
},
|
},
|
||||||
relations: ['genomeRequest'],
|
|
||||||
order: {
|
order: {
|
||||||
chromosome: 'ASC',
|
chromosome: 'ASC',
|
||||||
position: 'ASC',
|
position: 'ASC',
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ export default function CowOverviewPage() {
|
|||||||
const [cow, setCow] = useState<CowDetail | null>(null)
|
const [cow, setCow] = useState<CowDetail | null>(null)
|
||||||
const [genomeData, setGenomeData] = useState<GenomeTrait[]>([])
|
const [genomeData, setGenomeData] = useState<GenomeTrait[]>([])
|
||||||
const [geneData, setGeneData] = useState<GeneDetail[]>([])
|
const [geneData, setGeneData] = useState<GeneDetail[]>([])
|
||||||
|
const [geneDataLoaded, setGeneDataLoaded] = useState(false) // 유전자 데이터 로드 여부
|
||||||
|
const [geneDataLoading, setGeneDataLoading] = useState(false) // 유전자 데이터 로딩 중
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [activeTab, setActiveTab] = useState<string>('genome')
|
const [activeTab, setActiveTab] = useState<string>('genome')
|
||||||
|
|
||||||
@@ -193,6 +195,8 @@ export default function CowOverviewPage() {
|
|||||||
const [showFarm] = useState(true)
|
const [showFarm] = useState(true)
|
||||||
const [showAllTraits] = useState(false)
|
const [showAllTraits] = useState(false)
|
||||||
const [selectedTraits, setSelectedTraits] = useState<number[]>([])
|
const [selectedTraits, setSelectedTraits] = useState<number[]>([])
|
||||||
|
const [geneCurrentPage, setGeneCurrentPage] = useState(1)
|
||||||
|
const GENES_PER_PAGE = 50
|
||||||
|
|
||||||
// 농가/보은군 비교 하이라이트 모드
|
// 농가/보은군 비교 하이라이트 모드
|
||||||
const [highlightMode, setHighlightMode] = useState<'farm' | 'region' | null>(null)
|
const [highlightMode, setHighlightMode] = useState<'farm' | 'region' | null>(null)
|
||||||
@@ -215,12 +219,50 @@ export default function CowOverviewPage() {
|
|||||||
}, [filters.isActive, firstPinnedTrait, chartFilterTrait])
|
}, [filters.isActive, firstPinnedTrait, chartFilterTrait])
|
||||||
|
|
||||||
// 유전자 탭 필터 상태
|
// 유전자 탭 필터 상태
|
||||||
const [geneSearchKeyword, setGeneSearchKeyword] = useState('')
|
const [geneSearchInput, setGeneSearchInput] = useState('') // 실시간 입력값
|
||||||
|
const [geneSearchKeyword, setGeneSearchKeyword] = useState('') // 디바운스된 검색값
|
||||||
const [geneTypeFilter, setGeneTypeFilter] = useState<'all' | 'QTY' | 'QLT'>('all')
|
const [geneTypeFilter, setGeneTypeFilter] = useState<'all' | 'QTY' | 'QLT'>('all')
|
||||||
|
|
||||||
|
// 검색어 디바운스 (300ms) 실시간 필터링 너무 느림
|
||||||
|
// 타이핑이 멈추고 0.3초 후에 검색이 실행
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setGeneSearchKeyword(geneSearchInput)
|
||||||
|
setGeneCurrentPage(1)
|
||||||
|
}, 300)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [geneSearchInput])
|
||||||
const [genotypeFilter, setGenotypeFilter] = useState<'all' | 'homozygous' | 'heterozygous'>('all')
|
const [genotypeFilter, setGenotypeFilter] = useState<'all' | 'homozygous' | 'heterozygous'>('all')
|
||||||
const [geneSortBy, setGeneSortBy] = useState<'snpName' | 'chromosome' | 'position' | 'genotype'>('snpName')
|
const [geneSortBy, setGeneSortBy] = useState<'snpName' | 'chromosome' | 'position' | 'snpType' | 'allele1' | 'allele2' | 'remarks'>('snpName')
|
||||||
const [geneSortOrder, setGeneSortOrder] = useState<'asc' | 'desc'>('asc')
|
const [geneSortOrder, setGeneSortOrder] = useState<'asc' | 'desc'>('asc')
|
||||||
|
|
||||||
|
// 유전자 데이터 지연 로드 함수
|
||||||
|
const loadGeneData = async () => {
|
||||||
|
if (geneDataLoaded || geneDataLoading) return // 이미 로드했거나 로딩 중이면 스킵
|
||||||
|
|
||||||
|
setGeneDataLoading(true)
|
||||||
|
try {
|
||||||
|
const geneDataResult = await geneApi.findByCowId(cowNo)
|
||||||
|
const geneList = geneDataResult || []
|
||||||
|
setGeneData(geneList)
|
||||||
|
setGeneDataLoaded(true)
|
||||||
|
} catch (geneErr) {
|
||||||
|
console.error('유전자 데이터 조회 실패:', geneErr)
|
||||||
|
setGeneData([])
|
||||||
|
setGeneDataLoaded(true)
|
||||||
|
} finally {
|
||||||
|
setGeneDataLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 탭 변경 핸들러
|
||||||
|
const handleTabChange = (value: string) => {
|
||||||
|
setActiveTab(value)
|
||||||
|
if (value === 'gene' && !geneDataLoaded) {
|
||||||
|
loadGeneData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 농가/보은군 배지 클릭 시 차트로 스크롤 + 하이라이트
|
// 농가/보은군 배지 클릭 시 차트로 스크롤 + 하이라이트
|
||||||
const handleComparisonClick = (mode: 'farm' | 'region') => {
|
const handleComparisonClick = (mode: 'farm' | 'region') => {
|
||||||
// 토글: 같은 모드 클릭 시 해제
|
// 토글: 같은 모드 클릭 시 해제
|
||||||
@@ -273,19 +315,8 @@ export default function CowOverviewPage() {
|
|||||||
setGenomeRequest(null)
|
setGenomeRequest(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 유전자(SNP) 데이터 가져오기
|
// 유전자(SNP) 데이터는 탭 클릭 시 로드 (지연 로딩)
|
||||||
try {
|
setHasGeneData(true) // 탭은 보여주되, 데이터는 나중에 로드
|
||||||
const geneDataResult = await geneApi.findByCowId(cowNo)
|
|
||||||
const geneList = geneDataResult || []
|
|
||||||
setGeneData(geneList)
|
|
||||||
// 데이터 없어도 UI는 보여줌
|
|
||||||
setHasGeneData(true)
|
|
||||||
} catch (geneErr) {
|
|
||||||
console.error('유전자 데이터 조회 실패:', geneErr)
|
|
||||||
setGeneData([])
|
|
||||||
// 데이터 없어도 UI는 보여줌
|
|
||||||
setHasGeneData(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 번식능력 데이터 (현재는 목업 - 추후 API 연동)
|
// 번식능력 데이터 (현재는 목업 - 추후 API 연동)
|
||||||
// TODO: 번식능력 API 연동
|
// TODO: 번식능력 API 연동
|
||||||
@@ -512,7 +543,7 @@ export default function CowOverviewPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 탭 네비게이션 */}
|
{/* 탭 네비게이션 */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||||
<TabsList className="w-full grid grid-cols-3 bg-transparent p-0 h-auto gap-0 rounded-none border-b border-border">
|
<TabsList className="w-full grid grid-cols-3 bg-transparent p-0 h-auto gap-0 rounded-none border-b border-border">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="genome"
|
value="genome"
|
||||||
@@ -1172,7 +1203,14 @@ export default function CowOverviewPage() {
|
|||||||
|
|
||||||
{/* 유전자 분석 탭 */}
|
{/* 유전자 분석 탭 */}
|
||||||
<TabsContent value="gene" className="mt-6 space-y-6">
|
<TabsContent value="gene" className="mt-6 space-y-6">
|
||||||
{hasGeneData ? (
|
{geneDataLoading ? (
|
||||||
|
<div className="flex items-center justify-center h-64 md:h-96">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
||||||
|
<p className="text-muted-foreground">데이터를 불러오는 중...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : hasGeneData ? (
|
||||||
<>
|
<>
|
||||||
{/* 개체 정보 섹션 (유전체 탭과 동일) */}
|
{/* 개체 정보 섹션 (유전체 탭과 동일) */}
|
||||||
<h3 className="text-lg lg:text-xl font-bold text-foreground">개체 정보</h3>
|
<h3 className="text-lg lg:text-xl font-bold text-foreground">개체 정보</h3>
|
||||||
@@ -1422,10 +1460,10 @@ export default function CowOverviewPage() {
|
|||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="SNP명, 염색체 검색..."
|
placeholder="SNP 이름, 염색체 검색..."
|
||||||
className="pl-9 h-11 max-sm:h-10 text-base max-sm:text-sm border-slate-200 bg-white focus:border-blue-400 focus:ring-blue-100"
|
className="pl-9 h-11 max-sm:h-10 text-base max-sm:text-sm border-slate-200 bg-white focus:border-blue-400 focus:ring-blue-100"
|
||||||
value={geneSearchKeyword}
|
value={geneSearchInput}
|
||||||
onChange={(e) => setGeneSearchKeyword(e.target.value)}
|
onChange={(e) => setGeneSearchInput(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1469,23 +1507,26 @@ export default function CowOverviewPage() {
|
|||||||
<div className="flex items-center gap-2 sm:ml-auto">
|
<div className="flex items-center gap-2 sm:ml-auto">
|
||||||
<Select
|
<Select
|
||||||
value={geneSortBy}
|
value={geneSortBy}
|
||||||
onValueChange={(value: 'snpName' | 'chromosome' | 'position' | 'genotype') => setGeneSortBy(value)}
|
onValueChange={(value: 'snpName' | 'chromosome' | 'position' | 'snpType' | 'allele1' | 'allele2' | 'remarks') => setGeneSortBy(value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[110px] h-9 text-sm border-slate-200 bg-white">
|
<SelectTrigger className="w-[160px] h-9 text-sm border-slate-200 bg-white">
|
||||||
<SelectValue placeholder="정렬 기준" />
|
<SelectValue placeholder="정렬 기준" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="snpName">SNP명</SelectItem>
|
<SelectItem value="snpName">SNP 이름</SelectItem>
|
||||||
<SelectItem value="chromosome">염색체</SelectItem>
|
<SelectItem value="chromosome">염색체 위치</SelectItem>
|
||||||
<SelectItem value="position">위치</SelectItem>
|
<SelectItem value="position">Position</SelectItem>
|
||||||
<SelectItem value="genotype">유전자형</SelectItem>
|
<SelectItem value="snpType">SNP 구분</SelectItem>
|
||||||
|
<SelectItem value="allele1">첫번째 대립유전자</SelectItem>
|
||||||
|
<SelectItem value="allele2">두번째 대립유전자</SelectItem>
|
||||||
|
<SelectItem value="remarks">설명</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
<Select
|
||||||
value={geneSortOrder}
|
value={geneSortOrder}
|
||||||
onValueChange={(value: 'asc' | 'desc') => setGeneSortOrder(value)}
|
onValueChange={(value: 'asc' | 'desc') => setGeneSortOrder(value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[100px] h-9 text-sm border-slate-200 bg-white">
|
<SelectTrigger className="w-[110px] h-9 text-sm border-slate-200 bg-white">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -1500,13 +1541,23 @@ export default function CowOverviewPage() {
|
|||||||
{/* 유전자 테이블/카드 */}
|
{/* 유전자 테이블/카드 */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const filteredData = geneData.filter(gene => {
|
const filteredData = geneData.filter(gene => {
|
||||||
// 검색 필터
|
// 검색 필터 (테이블의 모든 필드 검색)
|
||||||
if (geneSearchKeyword) {
|
if (geneSearchKeyword) {
|
||||||
const keyword = geneSearchKeyword.toLowerCase()
|
const keyword = geneSearchKeyword.toLowerCase()
|
||||||
const snpName = (gene.snpName || '').toLowerCase()
|
const snpName = (gene.snpName || '').toLowerCase()
|
||||||
const chromosome = (gene.chromosome || '').toLowerCase()
|
const chromosome = (gene.chromosome || '').toLowerCase()
|
||||||
const position = (gene.position || '').toLowerCase()
|
const position = (gene.position || '').toLowerCase()
|
||||||
if (!snpName.includes(keyword) && !chromosome.includes(keyword) && !position.includes(keyword)) {
|
const snpType = (gene.snpType || '').toLowerCase()
|
||||||
|
const allele1 = (gene.allele1 || '').toLowerCase()
|
||||||
|
const allele2 = (gene.allele2 || '').toLowerCase()
|
||||||
|
const remarks = (gene.remarks || '').toLowerCase()
|
||||||
|
if (!snpName.includes(keyword) &&
|
||||||
|
!chromosome.includes(keyword) &&
|
||||||
|
!position.includes(keyword) &&
|
||||||
|
!snpType.includes(keyword) &&
|
||||||
|
!allele1.includes(keyword) &&
|
||||||
|
!allele2.includes(keyword) &&
|
||||||
|
!remarks.includes(keyword)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1537,9 +1588,21 @@ export default function CowOverviewPage() {
|
|||||||
aVal = parseInt(a.position || '0') || 0
|
aVal = parseInt(a.position || '0') || 0
|
||||||
bVal = parseInt(b.position || '0') || 0
|
bVal = parseInt(b.position || '0') || 0
|
||||||
break
|
break
|
||||||
case 'genotype':
|
case 'snpType':
|
||||||
aVal = `${a.allele1 || ''}${a.allele2 || ''}`
|
aVal = a.snpType || ''
|
||||||
bVal = `${b.allele1 || ''}${b.allele2 || ''}`
|
bVal = b.snpType || ''
|
||||||
|
break
|
||||||
|
case 'allele1':
|
||||||
|
aVal = a.allele1 || ''
|
||||||
|
bVal = b.allele1 || ''
|
||||||
|
break
|
||||||
|
case 'allele2':
|
||||||
|
aVal = a.allele2 || ''
|
||||||
|
bVal = b.allele2 || ''
|
||||||
|
break
|
||||||
|
case 'remarks':
|
||||||
|
aVal = a.remarks || ''
|
||||||
|
bVal = b.remarks || ''
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1554,26 +1617,124 @@ export default function CowOverviewPage() {
|
|||||||
: strB.localeCompare(strA)
|
: strB.localeCompare(strA)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 페이지네이션 계산
|
||||||
|
const totalPages = Math.ceil(sortedData.length / GENES_PER_PAGE)
|
||||||
|
const startIndex = (geneCurrentPage - 1) * GENES_PER_PAGE
|
||||||
|
const endIndex = startIndex + GENES_PER_PAGE
|
||||||
const displayData = sortedData.length > 0
|
const displayData = sortedData.length > 0
|
||||||
? sortedData.slice(0, 50)
|
? sortedData.slice(startIndex, endIndex)
|
||||||
: Array(10).fill(null)
|
: Array(10).fill(null)
|
||||||
|
|
||||||
|
// 페이지네이션 UI 컴포넌트
|
||||||
|
const PaginationUI = () => {
|
||||||
|
if (sortedData.length <= GENES_PER_PAGE) return null
|
||||||
|
|
||||||
|
// 표시할 페이지 번호들 계산 (현재 페이지 기준 앞뒤 2개씩)
|
||||||
|
const getPageNumbers = () => {
|
||||||
|
const pages: (number | string)[] = []
|
||||||
|
const showPages = 5
|
||||||
|
let start = Math.max(1, geneCurrentPage - 2)
|
||||||
|
let end = Math.min(totalPages, start + showPages - 1)
|
||||||
|
|
||||||
|
if (end - start < showPages - 1) {
|
||||||
|
start = Math.max(1, end - showPages + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start > 1) {
|
||||||
|
pages.push(1)
|
||||||
|
if (start > 2) pages.push('...')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < totalPages) {
|
||||||
|
if (end < totalPages - 1) pages.push('...')
|
||||||
|
pages.push(totalPages)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-3 bg-muted/30 border-t flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
전체 {sortedData.length.toLocaleString()}개 중 {startIndex + 1}-{Math.min(endIndex, sortedData.length)}번째
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setGeneCurrentPage(1)}
|
||||||
|
disabled={geneCurrentPage === 1}
|
||||||
|
className="px-2"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setGeneCurrentPage(p => Math.max(1, p - 1))}
|
||||||
|
disabled={geneCurrentPage === 1}
|
||||||
|
className="px-2"
|
||||||
|
>
|
||||||
|
‹
|
||||||
|
</Button>
|
||||||
|
{getPageNumbers().map((page, idx) => (
|
||||||
|
typeof page === 'number' ? (
|
||||||
|
<Button
|
||||||
|
key={idx}
|
||||||
|
variant={geneCurrentPage === page ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setGeneCurrentPage(page)}
|
||||||
|
className="px-3 min-w-[36px]"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<span key={idx} className="px-1 text-muted-foreground">...</span>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setGeneCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||||
|
disabled={geneCurrentPage === totalPages}
|
||||||
|
className="px-2"
|
||||||
|
>
|
||||||
|
›
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setGeneCurrentPage(totalPages)}
|
||||||
|
disabled={geneCurrentPage === totalPages}
|
||||||
|
className="px-2"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 데스크톱: 테이블 */}
|
{/* 데스크톱: 테이블 */}
|
||||||
<Card className="hidden lg:block bg-white border border-border shadow-sm rounded-2xl overflow-hidden">
|
<Card className="hidden lg:block bg-white border border-border shadow-sm rounded-2xl overflow-hidden">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<div>
|
<div>
|
||||||
<table className="w-full">
|
<table className="w-full table-fixed">
|
||||||
<thead className="bg-muted/50 border-b border-border">
|
<thead className="bg-muted/50 border-b border-border">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">SNP 이름</th>
|
<th className="px-4 py-3 text-center text-base font-semibold text-muted-foreground w-[22%]">SNP 이름</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">염색체 위치</th>
|
<th className="px-3 py-3 text-center text-base font-semibold text-muted-foreground w-[10%]">염색체 위치</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">Position</th>
|
<th className="px-3 py-3 text-center text-base font-semibold text-muted-foreground w-[11%]">Position</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">SNP 구분</th>
|
<th className="px-3 py-3 text-center text-base font-semibold text-muted-foreground w-[11%]">SNP 구분</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">첫번째 대립유전자</th>
|
<th className="px-2 py-3 text-center text-base font-semibold text-muted-foreground w-[13%]">첫번째 대립유전자</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">두번째 대립유전자</th>
|
<th className="px-2 py-3 text-center text-base font-semibold text-muted-foreground w-[13%]">두번째 대립유전자</th>
|
||||||
<th className="px-5 py-3 text-center text-base font-semibold text-muted-foreground">설명</th>
|
<th className="px-4 py-3 text-center text-base font-semibold text-muted-foreground w-[20%]">설명</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border">
|
<tbody className="divide-y divide-border">
|
||||||
@@ -1581,36 +1742,32 @@ export default function CowOverviewPage() {
|
|||||||
if (!gene) {
|
if (!gene) {
|
||||||
return (
|
return (
|
||||||
<tr key={idx} className="hover:bg-muted/30">
|
<tr key={idx} className="hover:bg-muted/30">
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-4 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-3 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-3 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-3 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-2 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-2 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
<td className="px-5 py-4 text-base text-center text-muted-foreground">-</td>
|
<td className="px-4 py-3 text-base text-center text-muted-foreground">-</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<tr key={idx} className="hover:bg-muted/30">
|
<tr key={idx} className="hover:bg-muted/30">
|
||||||
<td className="px-5 py-4 text-center text-base font-medium text-foreground">{gene.snpName || '-'}</td>
|
<td className="px-4 py-3 text-center text-base font-medium text-foreground">{gene.snpName || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-foreground">{gene.chromosome || '-'}</td>
|
<td className="px-3 py-3 text-center text-base text-foreground">{gene.chromosome || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-foreground">{gene.position || '-'}</td>
|
<td className="px-3 py-3 text-center text-base text-foreground">{gene.position || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-foreground">{gene.snpType || '-'}</td>
|
<td className="px-3 py-3 text-center text-base text-foreground">{gene.snpType || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-foreground">{gene.allele1 || '-'}</td>
|
<td className="px-2 py-3 text-center text-base text-foreground">{gene.allele1 || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-foreground">{gene.allele2 || '-'}</td>
|
<td className="px-2 py-3 text-center text-base text-foreground">{gene.allele2 || '-'}</td>
|
||||||
<td className="px-5 py-4 text-center text-base text-muted-foreground">{gene.remarks || '-'}</td>
|
<td className="px-4 py-3 text-center text-base text-muted-foreground">{gene.remarks || '-'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{geneData.length > 50 && (
|
<PaginationUI />
|
||||||
<div className="px-4 py-3 bg-muted/30 text-center text-sm text-muted-foreground border-t">
|
|
||||||
전체 {geneData.length}개 중 50개 표시
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -1652,11 +1809,9 @@ export default function CowOverviewPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{geneData.length > 50 && (
|
</div>
|
||||||
<div className="px-4 py-3 text-center text-sm text-muted-foreground">
|
<div className="lg:hidden">
|
||||||
전체 {geneData.length}개 중 50개 표시
|
<PaginationUI />
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ export const geneApi = {
|
|||||||
* GET /gene/:cowId
|
* GET /gene/:cowId
|
||||||
*/
|
*/
|
||||||
findByCowId: async (cowId: string): Promise<GeneDetail[]> => {
|
findByCowId: async (cowId: string): Promise<GeneDetail[]> => {
|
||||||
const response = await apiClient.get<GeneDetail[]>(`/gene/${cowId}`);
|
return await apiClient.get(`/gene/${cowId}`);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,8 +53,7 @@ export const geneApi = {
|
|||||||
* GET /gene/summary/:cowId
|
* GET /gene/summary/:cowId
|
||||||
*/
|
*/
|
||||||
getGeneSummary: async (cowId: string): Promise<GeneSummary> => {
|
getGeneSummary: async (cowId: string): Promise<GeneSummary> => {
|
||||||
const response = await apiClient.get<GeneSummary>(`/gene/summary/${cowId}`);
|
return await apiClient.get(`/gene/summary/${cowId}`);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +61,7 @@ export const geneApi = {
|
|||||||
* GET /gene/request/:requestNo
|
* GET /gene/request/:requestNo
|
||||||
*/
|
*/
|
||||||
findByRequestNo: async (requestNo: number): Promise<GeneDetail[]> => {
|
findByRequestNo: async (requestNo: number): Promise<GeneDetail[]> => {
|
||||||
const response = await apiClient.get<GeneDetail[]>(`/gene/request/${requestNo}`);
|
return await apiClient.get(`/gene/request/${requestNo}`);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,8 +69,7 @@ export const geneApi = {
|
|||||||
* GET /gene/detail/:geneDetailNo
|
* GET /gene/detail/:geneDetailNo
|
||||||
*/
|
*/
|
||||||
findOne: async (geneDetailNo: number): Promise<GeneDetail> => {
|
findOne: async (geneDetailNo: number): Promise<GeneDetail> => {
|
||||||
const response = await apiClient.get<GeneDetail>(`/gene/detail/${geneDetailNo}`);
|
return await apiClient.get(`/gene/detail/${geneDetailNo}`);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,8 +77,7 @@ export const geneApi = {
|
|||||||
* POST /gene
|
* POST /gene
|
||||||
*/
|
*/
|
||||||
create: async (data: Partial<GeneDetail>): Promise<GeneDetail> => {
|
create: async (data: Partial<GeneDetail>): Promise<GeneDetail> => {
|
||||||
const response = await apiClient.post<GeneDetail>('/gene', data);
|
return await apiClient.post('/gene', data);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +85,6 @@ export const geneApi = {
|
|||||||
* POST /gene/bulk
|
* POST /gene/bulk
|
||||||
*/
|
*/
|
||||||
createBulk: async (dataList: Partial<GeneDetail>[]): Promise<GeneDetail[]> => {
|
createBulk: async (dataList: Partial<GeneDetail>[]): Promise<GeneDetail[]> => {
|
||||||
const response = await apiClient.post<GeneDetail[]>('/gene/bulk', dataList);
|
return await apiClient.post('/gene/bulk', dataList);
|
||||||
return response.data;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user