entity 연결 수정 및 코드정리
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Cow } from "@/types/cow.types"
|
||||
import { Cow, CowWithGenes, RankingItem } from "@/types/cow.types"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ChevronLeft, ChevronRight, Search, ChevronsUpDown, Filter, Settings } from "lucide-react"
|
||||
@@ -20,6 +20,7 @@ import { Label } from "@/components/ui/label"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { cowApi } from "@/lib/api/cow.api"
|
||||
import { TRAIT_DISPLAY_NAMES } from "@/constants/traits"
|
||||
import { useAuthStore } from "@/store/auth-store"
|
||||
import { useFilterStore } from "@/store/filter-store"
|
||||
import { AnalysisYearProvider } from "@/contexts/AnalysisYearContext"
|
||||
@@ -28,41 +29,6 @@ import { AuthGuard } from "@/components/auth/auth-guard"
|
||||
/**
|
||||
* 개체 리스트 페이지
|
||||
*/
|
||||
/**
|
||||
* 유전자 정보를 포함한 Cow 타입 (임시 - 실제로는 API에서 가져와야 함)
|
||||
*/
|
||||
interface TraitData {
|
||||
breedVal: number | null
|
||||
traitVal: number | null
|
||||
}
|
||||
|
||||
interface CowWithGenes extends Cow {
|
||||
genes?: {
|
||||
name: string
|
||||
genotype: string
|
||||
favorable: boolean // 유리대립유전자 여부
|
||||
}[]
|
||||
traits?: Record<string, TraitData | number> // 형질명 → { breedVal, traitVal } 또는 number 매핑
|
||||
grade?: 'A' | 'B' | 'C' | 'D' | 'E'
|
||||
quantityGeneCount?: number
|
||||
qualityGeneCount?: number
|
||||
quantityHomoCount?: number // 육량형 동형접합(AA) 개수
|
||||
quantityHeteroCount?: number // 육량형 이형접합(AG) 개수
|
||||
qualityHomoCount?: number // 육질형 동형접합(AA) 개수
|
||||
qualityHeteroCount?: number // 육질형 이형접합(AG) 개수
|
||||
rankScore?: number // 백엔드 랭킹 API에서 받은 점수
|
||||
genomeScore?: number // COMPOSITE 모드에서 유전체 점수
|
||||
geneScore?: number // COMPOSITE 모드에서 유전자 점수
|
||||
rank?: number // 랭킹 순위
|
||||
cowShortNo?: string // 개체 요약번호
|
||||
cowReproType?: string // 번식 타입
|
||||
anlysDt?: string // 분석일자 (유전체)
|
||||
unavailableReason?: string // 분석불가 사유 (부불일치, 모불일치, 모이력제부재 등)
|
||||
hasMpt?: boolean // 번식능력검사(MPT) 여부
|
||||
mptTestDt?: string // MPT 검사일
|
||||
mptMonthAge?: number // MPT 검사일 기준 월령
|
||||
}
|
||||
|
||||
function MyCowContent() {
|
||||
const [cows, setCows] = useState<CowWithGenes[]>([])
|
||||
const [filteredCows, setFilteredCows] = useState<CowWithGenes[]>([])
|
||||
@@ -71,7 +37,6 @@ function MyCowContent() {
|
||||
const router = useRouter()
|
||||
const { user } = useAuthStore()
|
||||
const { filters, isLoading: isFilterLoading } = useFilterStore()
|
||||
const [markerTypes, setMarkerTypes] = useState<Record<string, string>>({}) // 마커명 → 타입(QTY/QLT) 매핑
|
||||
|
||||
// 로컬 필터 상태 (검색, 랭킹모드, 정렬)
|
||||
const [searchKeyword, setSearchKeyword] = useState('')
|
||||
@@ -92,78 +57,69 @@ function MyCowContent() {
|
||||
const itemsPerPage = 12
|
||||
|
||||
|
||||
// 필터가 설정되었는지 확인 (형질 가중치가 1개 이상 설정됨 - 유전체 필수)
|
||||
// 형질 가중치가 1개 이상 설정되어야 필터 활성화
|
||||
const isFilterSet = filters.isActive && Object.values(filters.traitWeights).some(w => w > 0)
|
||||
|
||||
// 마커 타입 정보 로드 (gene.api 제거됨 - 추후 백엔드 구현 시 복구)
|
||||
// ========================================
|
||||
// 전역 필터 → 개체 리스트 표시 항목 동기화
|
||||
// ========================================
|
||||
useEffect(() => {
|
||||
// TODO: gene API 구현 후 마커 타입 정보 로드 복구
|
||||
setMarkerTypes({})
|
||||
}, [])
|
||||
if (isFilterLoading) return // 필터 로딩 중이면 건너뛰기
|
||||
|
||||
// 전역 필터의 선택된 유전자를 선택 가능한 목록으로 설정 + 자동 랭킹 모드 설정
|
||||
// 필터 변경 시 표시 항목 업데이트
|
||||
useEffect(() => {
|
||||
// 필터 로딩 중이면 건너뛰기
|
||||
if (isFilterLoading) {
|
||||
return
|
||||
}
|
||||
const hasGenes = filters.isActive && filters.selectedGenes && filters.selectedGenes.length > 0
|
||||
const hasTraits = filters.isActive && Object.values(filters.traitWeights).some(w => w > 0)
|
||||
|
||||
// 유전자 처리 (고정 항목 우선)
|
||||
// 1. 마커 유전자 표시 목록 동기화 (육량형 , 육질형 추후 추가 연동 예정)
|
||||
if (hasGenes) {
|
||||
const pinnedGenes = filters.pinnedGenes || []
|
||||
const restGenes = filters.selectedGenes.filter(g => !pinnedGenes.includes(g))
|
||||
const orderedGenes = [...pinnedGenes, ...restGenes]
|
||||
|
||||
setAvailableGenes(orderedGenes)
|
||||
// 고정된 항목이 있으면 고정 항목만, 없으면 전체 선택
|
||||
setSelectedDisplayGenes(pinnedGenes.length > 0 ? pinnedGenes : orderedGenes)
|
||||
setAvailableGenes(filters.selectedGenes)
|
||||
const pinnedInOrder = filters.selectedGenes.filter(g => pinnedGenes.includes(g))
|
||||
setSelectedDisplayGenes(pinnedGenes.length > 0 ? pinnedInOrder : filters.selectedGenes)
|
||||
} else {
|
||||
setAvailableGenes([])
|
||||
setSelectedDisplayGenes([])
|
||||
}
|
||||
|
||||
// 형질 처리 (고정 항목 우선)
|
||||
// 2. 형질 표시 목록 동기화
|
||||
if (filters.isActive && filters.selectedTraits && filters.selectedTraits.length > 0) {
|
||||
const pinnedTraits = filters.pinnedTraits || []
|
||||
const restTraits = filters.selectedTraits.filter(t => !pinnedTraits.includes(t))
|
||||
const orderedTraits = [...pinnedTraits, ...restTraits]
|
||||
|
||||
setAvailableTraits(orderedTraits)
|
||||
// 고정된 항목이 있으면 고정 항목만, 없으면 전체 선택
|
||||
setSelectedDisplayTraits(pinnedTraits.length > 0 ? pinnedTraits : orderedTraits)
|
||||
setAvailableTraits(filters.selectedTraits)
|
||||
const pinnedInOrder = filters.selectedTraits.filter(t => pinnedTraits.includes(t))
|
||||
setSelectedDisplayTraits(pinnedTraits.length > 0 ? pinnedInOrder : filters.selectedTraits)
|
||||
} else {
|
||||
setAvailableTraits([])
|
||||
setSelectedDisplayTraits([])
|
||||
}
|
||||
|
||||
// 자동 랭킹 모드 설정
|
||||
// 3. 랭킹 모드 자동 설정
|
||||
// - 유전자 선택됨 → 유전자순 (GENE 모드)
|
||||
// - 형질만 선택됨 → 유전체순 (GENOME 모드)
|
||||
if (filters.isActive) {
|
||||
if (hasGenes) {
|
||||
// 유전자가 선택되어 있으면 → 유전자순 (형질 유무 상관없이)
|
||||
setRankingMode('gene')
|
||||
} else if (hasTraits) {
|
||||
// 유전자 없이 형질만 선택 → 유전체순
|
||||
setRankingMode('genome')
|
||||
}
|
||||
}
|
||||
}, [isFilterLoading, filters.isActive, filters.selectedGenes, filters.selectedTraits, filters.pinnedGenes, filters.pinnedTraits, filters.traitWeights])
|
||||
|
||||
// ========================================
|
||||
// 개체 데이터 조회 (Ranking API)
|
||||
// ========================================
|
||||
useEffect(() => {
|
||||
const fetchCows = async () => {
|
||||
// 필터가 설정되지 않았으면 API 호출하지 않음
|
||||
if (!isFilterSet) {
|
||||
setLoading(false)
|
||||
setLoading(false) // 필터가 설정되지 않았으면 API 호출하지 않음
|
||||
setCows([])
|
||||
setFilteredCows([])
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
// 사용자의 농장 목록 조회하여 농장 필터 생성
|
||||
setLoading(true) // 필터가 설정되면 API 호출
|
||||
setError(null)
|
||||
|
||||
// 1. 사용자 농장 필터 생성
|
||||
let farmFilters: { field: string; operator: 'in'; value: number[] }[] = []
|
||||
try {
|
||||
const userNo = user?.pkUserNo
|
||||
@@ -180,130 +136,72 @@ function MyCowContent() {
|
||||
console.error('Failed to fetch farms:', err)
|
||||
}
|
||||
|
||||
setError(null)
|
||||
|
||||
// 전역 필터 + 랭킹 모드를 기반으로 랭킹 옵션 구성
|
||||
// 타입을 any로 지정하여 백엔드 API와의 호환성 유지
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// 2. 랭킹 옵션 구성
|
||||
// - GENE 모드: 마커 유전자 기반 정렬 (우량동형 → 이형 → 유전체점수) 추후 구현 예정
|
||||
// - GENOME 모드: 형질 가중치 기반 정렬
|
||||
let rankingOptions: any
|
||||
|
||||
// 형질 가중치 조건 (유전체 점수 계산용)
|
||||
const traitConditions = Object.entries(filters.traitWeights)
|
||||
const traitConditions = Object.entries(filters.traitWeights) // 형질 가중치 조건 1이 기본 (유전체 점수 계산용)
|
||||
.filter(([, weight]) => weight > 0)
|
||||
.map(([traitNm, weight]) => ({
|
||||
traitNm,
|
||||
weight // 0-10 가중치 그대로 사용
|
||||
}))
|
||||
.map(([traitNm, weight]) => ({ traitNm, weight }))
|
||||
|
||||
// 랭킹 모드에 따라 criteriaType 결정
|
||||
if (rankingMode === 'gene' && filters.isActive && filters.selectedGenes && filters.selectedGenes.length > 0) {
|
||||
// 유전자순 랭킹 → GENE 모드
|
||||
// 정렬 기준: 1차 AA 개수, 2차 Aa 개수, 3차 유전체 점수
|
||||
// GENE 모드
|
||||
rankingOptions = {
|
||||
criteriaType: 'GENE',
|
||||
geneConditions: filters.selectedGenes.map(markerNm => ({
|
||||
markerNm,
|
||||
order: 'DESC'
|
||||
})),
|
||||
traitConditions // 유전체 점수 계산용
|
||||
geneConditions: filters.selectedGenes.map(markerNm => ({ markerNm, order: 'DESC' })),
|
||||
traitConditions
|
||||
}
|
||||
} else if (rankingMode === 'genome' || !filters.isActive || !filters.selectedGenes || filters.selectedGenes.length === 0) {
|
||||
// 유전체순 랭킹 또는 필터 비활성화 → GENOME 모드
|
||||
// 정렬 기준: 유전체 점수 (형질 가중치 평균)
|
||||
|
||||
// 전역 필터가 비활성화되어 있으면 기본 가중치로 전체 개체 표시
|
||||
// GENOME 모드
|
||||
if (!filters.isActive) {
|
||||
rankingOptions = {
|
||||
criteriaType: 'GENOME',
|
||||
traitConditions: [], // 빈 배열 → 백엔드 기본 가중치 사용
|
||||
inbreedingCondition: {
|
||||
maxThreshold: 0,
|
||||
order: 'ASC'
|
||||
}
|
||||
traitConditions: [],
|
||||
inbreedingCondition: { maxThreshold: 0, order: 'ASC' }
|
||||
}
|
||||
} else if (rankingMode === 'genome' && traitConditions.length === 0) {
|
||||
// 유전체순인데 형질 가중치가 비어있으면 기본 가중치 사용
|
||||
rankingOptions = {
|
||||
criteriaType: 'GENOME',
|
||||
traitConditions: [], // 빈 배열 → 백엔드 기본 가중치 사용
|
||||
inbreedingCondition: {
|
||||
maxThreshold: filters.inbreedingThreshold !== undefined ? filters.inbreedingThreshold : 0,
|
||||
order: 'ASC'
|
||||
}
|
||||
traitConditions: [],
|
||||
inbreedingCondition: { maxThreshold: filters.inbreedingThreshold ?? 0, order: 'ASC' }
|
||||
}
|
||||
} else {
|
||||
// 정상적으로 가중치가 있는 경우
|
||||
rankingOptions = {
|
||||
criteriaType: 'GENOME',
|
||||
traitConditions, // 가중치 > 0인 형질만
|
||||
inbreedingCondition: {
|
||||
maxThreshold: filters.inbreedingThreshold !== undefined ? filters.inbreedingThreshold : 0,
|
||||
order: 'ASC'
|
||||
}
|
||||
traitConditions,
|
||||
inbreedingCondition: { maxThreshold: filters.inbreedingThreshold ?? 0, order: 'ASC' }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 기본값 (유전체순)
|
||||
// 기본값 (GENOME 모드)
|
||||
rankingOptions = {
|
||||
criteriaType: 'GENOME',
|
||||
traitConditions: [], // 빈 배열
|
||||
inbreedingCondition: {
|
||||
maxThreshold: 0,
|
||||
order: 'ASC'
|
||||
}
|
||||
traitConditions: [],
|
||||
inbreedingCondition: { maxThreshold: 0, order: 'ASC' }
|
||||
}
|
||||
}
|
||||
|
||||
// 백엔드 ranking API 호출
|
||||
// 3. API 호출 및 응답 매핑
|
||||
const rankingRequest = {
|
||||
filterOptions: {
|
||||
filters: farmFilters // 농장 필터 적용
|
||||
},
|
||||
rankingOptions
|
||||
filterOptions: { filters: farmFilters }, // 필터옵션과
|
||||
rankingOptions // 랭킹옵션을 담아서 백엔드로 전달
|
||||
}
|
||||
// 백엔드 ranking API 호출
|
||||
const response = await cowApi.getRanking(rankingRequest)
|
||||
|
||||
// ==========================================================================================================
|
||||
// response는 { items: RankingResultItem[], total, criteriaType, timestamp } 형식
|
||||
// items의 각 요소는 { entity, rank, sortValue, grade, details } 형식
|
||||
interface RankingItem {
|
||||
entity: Cow & { genes?: Record<string, number>; calvingCount?: number; bcs?: number; inseminationCount?: number; inbreedingPercent?: number; sireKpn?: string; anlysDt?: string; unavailableReason?: string };
|
||||
rank: number;
|
||||
sortValue: number;
|
||||
grade: string;
|
||||
compositeScores?: { geneScore?: number };
|
||||
ranking?: { traits?: { traitName: string; traitEbv: number | null; traitVal: number | null }[] }; // 랭킹 형질
|
||||
}
|
||||
const cowsWithMockGenes = response.items.map((item: RankingItem) => {
|
||||
return {
|
||||
...item.entity,
|
||||
rank: item.rank,
|
||||
rankScore: item.sortValue,
|
||||
grade: item.grade,
|
||||
genomeScore: item.sortValue,
|
||||
// 번식 정보
|
||||
calvingCount: item.entity.calvingCount,
|
||||
bcs: item.entity.bcs,
|
||||
inseminationCount: item.entity.inseminationCount,
|
||||
inbreedingPercent: item.entity.inbreedingPercent ?? 0,
|
||||
sireKpn: item.entity.sireKpn ?? null,
|
||||
anlysDt: item.entity.anlysDt ?? null,
|
||||
unavailableReason: item.entity.unavailableReason ?? null,
|
||||
hasMpt: item.entity.hasMpt ?? false,
|
||||
mptTestDt: item.entity.mptTestDt ?? null,
|
||||
mptMonthAge: item.entity.mptMonthAge ?? null,
|
||||
// 형질 데이터
|
||||
traits: item.ranking?.traits?.reduce((acc: Record<string,
|
||||
{ breedVal: number | null, traitVal: number | null }>, t: { traitName: string; traitEbv: number | null; traitVal: number | null }) => {
|
||||
acc[t.traitName] = { breedVal: t.traitEbv, traitVal: t.traitVal };
|
||||
return acc
|
||||
}, {}) || {},
|
||||
}
|
||||
})
|
||||
const cowsData = response.items.map((item: RankingItem) => ({
|
||||
...item.entity,
|
||||
rank: item.rank,
|
||||
genomeScore: item.sortValue,
|
||||
inbreedingPercent: item.entity.inbreedingPercent ?? 0,
|
||||
traits: item.ranking?.traits?.reduce((acc: Record<string, { breedVal: number | null, traitVal: number | null }>, t) => {
|
||||
acc[t.traitName] = { breedVal: t.traitEbv, traitVal: t.traitVal }
|
||||
return acc
|
||||
}, {}) || {},
|
||||
}))
|
||||
|
||||
setCows(cowsWithMockGenes)
|
||||
setFilteredCows(cowsWithMockGenes)
|
||||
setCows(cowsData)
|
||||
setFilteredCows(cowsData)
|
||||
} catch (err) {
|
||||
console.error('개체 데이터 조회 실패:', err)
|
||||
setError(err instanceof Error ? err.message : '데이터를 불러오는데 실패했습니다')
|
||||
@@ -316,17 +214,13 @@ function MyCowContent() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters, rankingMode, isFilterSet])
|
||||
|
||||
|
||||
// ============================================
|
||||
// 컬럼 스타일은 globals.css의 CSS 변수로 관리됨
|
||||
// .cow-col-* 클래스 사용
|
||||
// ============================================
|
||||
|
||||
// 필터링 및 정렬
|
||||
// ========================================
|
||||
// 클라이언트 측 필터링 및 정렬
|
||||
// ========================================
|
||||
useEffect(() => {
|
||||
let result = [...cows]
|
||||
|
||||
// 검색 필터 (전체 개체번호 또는 4자리 약식 번호로 검색)
|
||||
// 1. 검색 필터
|
||||
if (searchKeyword) {
|
||||
const keyword = searchKeyword.toLowerCase()
|
||||
result = result.filter(cow =>
|
||||
@@ -336,42 +230,29 @@ function MyCowContent() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 전역 필터 적용 (유전자 기반 분석)
|
||||
// 2. 전역 필터 (유전자 기반)
|
||||
if (filters.isActive && filters.analysisIndex === 'GENE' && filters.selectedGenes.length > 0) {
|
||||
result = result.filter(cow => {
|
||||
if (!cow.genes || !Array.isArray(cow.genes)) return false
|
||||
// 선택된 유전자를 모두 보유한 개체만 표시
|
||||
return filters.selectedGenes.every(selectedGene =>
|
||||
cow.genes!.some(g => g.name === selectedGene)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 전역 필터 적용 (유전능력 기반 분석)
|
||||
// TODO: 유전능력 데이터가 있을 경우 형질 기반 필터링 추가
|
||||
if (filters.isActive && filters.analysisIndex === 'ABILITY' && (filters.selectedTraits?.length ?? 0) > 0) {
|
||||
// 현재는 mock 데이터이므로 필터링하지 않음
|
||||
// 실제로는 API에서 가져온 유전능력 데이터를 기반으로 필터링
|
||||
}
|
||||
|
||||
// 분석 상태 필터
|
||||
// 3. 분석 상태 필터
|
||||
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.unavailableReason !== null && cow.unavailableReason !== undefined)
|
||||
}
|
||||
|
||||
// 정렬 (sortBy가 'none'이면 정렬하지 않음 - 전역 필터 순서 유지)
|
||||
// 4. 정렬
|
||||
if (sortBy !== 'none') {
|
||||
switch (sortBy) {
|
||||
case 'rank':
|
||||
// 순위 정렬
|
||||
result.sort((a, b) => {
|
||||
const rankA = a.rank ?? 9999
|
||||
const rankB = b.rank ?? 9999
|
||||
@@ -379,24 +260,19 @@ function MyCowContent() {
|
||||
})
|
||||
break
|
||||
case 'number':
|
||||
// 개체번호 정렬 (cowId 문자열 기준)
|
||||
result.sort((a, b) => {
|
||||
const comparison = (a.cowId || '').localeCompare(b.cowId || '')
|
||||
return sortOrder === 'asc' ? comparison : -comparison
|
||||
})
|
||||
break
|
||||
case 'age':
|
||||
// 월령 정렬 (생년월일 기준)
|
||||
result.sort((a, b) => {
|
||||
const dateA = a.cowBirthDt ? new Date(a.cowBirthDt).getTime() : 0
|
||||
const dateB = b.cowBirthDt ? new Date(b.cowBirthDt).getTime() : 0
|
||||
// 오름차순: 오래된 것(작은 날짜) → 최근 것(큰 날짜), 즉 나이가 많은 순
|
||||
// 내림차순: 최근 것(큰 날짜) → 오래된 것(작은 날짜), 즉 나이가 적은 순
|
||||
return sortOrder === 'asc' ? dateA - dateB : dateB - dateA
|
||||
})
|
||||
break
|
||||
case 'score':
|
||||
// 점수 정렬
|
||||
result.sort((a, b) => {
|
||||
const scoreA = a.genomeScore ?? 0
|
||||
const scoreB = b.genomeScore ?? 0
|
||||
@@ -407,7 +283,7 @@ function MyCowContent() {
|
||||
}
|
||||
|
||||
setFilteredCows(result)
|
||||
setCurrentPage(1) // 필터 변경시 첫 페이지로
|
||||
setCurrentPage(1)
|
||||
}, [searchKeyword, sortBy, sortOrder, cows, filters, analysisFilter])
|
||||
|
||||
// 페이지네이션
|
||||
@@ -430,31 +306,6 @@ function MyCowContent() {
|
||||
return cow.rank ?? 9999
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 형질 카테고리 정의
|
||||
const traitCategories = {
|
||||
production: {
|
||||
name: '생산',
|
||||
traits: ['12개월령체중', '도체중', '등심단면적', '등지방두께', '근내지방도']
|
||||
},
|
||||
body: {
|
||||
name: '체형',
|
||||
traits: ['체고', '십자', '체장', '흉심', '흉폭', '고장', '요각폭', '좌골폭', '곤폭', '흉위']
|
||||
},
|
||||
weight: {
|
||||
name: '부분육중량',
|
||||
traits: ['안심weight', '등심weight', '채끝weight', '목심weight', '앞다리weight', '우둔weight', '설도weight', '사태weight', '양지weight', '갈비weight']
|
||||
},
|
||||
rate: {
|
||||
name: '부분육비율',
|
||||
traits: ['안심rate', '등심rate', '채끝rate', '목심rate', '앞다리rate', '우둔rate', '설도rate', '사태rate', '양지rate', '갈비rate']
|
||||
}
|
||||
}
|
||||
|
||||
// traitCategories는 형질 카테고리 정보 제공용으로 유지
|
||||
void traitCategories
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
@@ -733,7 +584,7 @@ function MyCowContent() {
|
||||
htmlFor={`trait-${trait}`}
|
||||
className="text-sm font-normal cursor-pointer"
|
||||
>
|
||||
{trait}
|
||||
{TRAIT_DISPLAY_NAMES[trait] || trait}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
@@ -932,17 +783,12 @@ function MyCowContent() {
|
||||
{displayGenes.map((geneName) => {
|
||||
const gene = cow.genes?.find(g => g.name === geneName)
|
||||
const genotype = gene?.genotype || '-'
|
||||
const geneCategory = markerTypes[geneName] as 'QTY' | 'QLT'
|
||||
// 육량형: 파랑, 육질형: 주황
|
||||
const badgeClass = geneCategory === 'QTY'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-orange-500 text-white'
|
||||
|
||||
return (
|
||||
<div key={geneName} className="flex items-center gap-2">
|
||||
<span className="text-xs text-slate-600 min-w-[60px]">{geneName}</span>
|
||||
{gene ? (
|
||||
<Badge className={`text-xs font-semibold ${badgeClass}`}>
|
||||
<Badge className="text-xs font-semibold bg-blue-500 text-white">
|
||||
{genotype}
|
||||
</Badge>
|
||||
) : (
|
||||
@@ -1002,7 +848,7 @@ function MyCowContent() {
|
||||
|
||||
return (
|
||||
<div key={trait} className="flex items-center gap-1.5">
|
||||
<span className="text-[10px] text-muted-foreground min-w-[65px]">{trait}</span>
|
||||
<span className="text-[10px] text-muted-foreground min-w-[65px]">{TRAIT_DISPLAY_NAMES[trait] || trait}</span>
|
||||
<span className="font-semibold text-xs">{traitValue}</span>
|
||||
</div>
|
||||
)
|
||||
@@ -1179,25 +1025,18 @@ function MyCowContent() {
|
||||
<span className="text-xs text-muted-foreground mr-0.5">유전자</span>
|
||||
{(() => {
|
||||
const isExpanded = expandedRows.has(`${cow.pkCowNo}-mobile-genes`)
|
||||
const pinnedGenes = filters.pinnedGenes || []
|
||||
const unpinnedGenes = selectedDisplayGenes.filter(g => !pinnedGenes.includes(g))
|
||||
const allGenes = [...pinnedGenes, ...unpinnedGenes]
|
||||
const displayGenes = isExpanded ? allGenes : allGenes.slice(0, 4)
|
||||
const remainingCount = allGenes.length - 4
|
||||
// selectedDisplayGenes가 이미 전역 필터 순서를 반영하고 있음
|
||||
const displayGenes = isExpanded ? selectedDisplayGenes : selectedDisplayGenes.slice(0, 4)
|
||||
const remainingCount = selectedDisplayGenes.length - 4
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayGenes.map((geneName) => {
|
||||
const gene = cow.genes?.find(g => g.name === geneName)
|
||||
const geneCategory = markerTypes[geneName] as 'QTY' | 'QLT'
|
||||
const genotype = gene?.genotype || '-'
|
||||
// 육량형: 파랑, 육질형: 주황
|
||||
const badgeClass = geneCategory === 'QTY'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-orange-500 text-white'
|
||||
|
||||
return (
|
||||
<Badge key={geneName} className={`text-xs px-1.5 py-0.5 font-medium ${badgeClass}`}>
|
||||
<Badge key={geneName} className="text-xs px-1.5 py-0.5 font-medium bg-blue-500 text-white">
|
||||
{geneName} {genotype}
|
||||
</Badge>
|
||||
)
|
||||
@@ -1251,7 +1090,7 @@ function MyCowContent() {
|
||||
}
|
||||
return (
|
||||
<div key={trait} className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">{trait}</span>
|
||||
<span className="text-muted-foreground">{TRAIT_DISPLAY_NAMES[trait] || trait}</span>
|
||||
<span className="font-medium">{traitValue}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user