'use client' import { AppSidebar } from "@/components/layout/app-sidebar" import { SiteHeader } from "@/components/layout/site-header" import { CowNumberDisplay } from "@/components/common/cow-number-display" import { SidebarInset, SidebarProvider, } from "@/components/ui/sidebar" import { Button } from "@/components/ui/button" 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" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Badge } from "@/components/ui/badge" import { Checkbox } from "@/components/ui/checkbox" 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" import { AuthGuard } from "@/components/auth/auth-guard" /** * 개체 리스트 페이지 */ function MyCowContent() { const [cows, setCows] = useState([]) const [filteredCows, setFilteredCows] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const router = useRouter() const { user } = useAuthStore() const { filters, isLoading: isFilterLoading } = useFilterStore() // 로컬 필터 상태 (검색, 랭킹모드, 정렬) const [searchKeyword, setSearchKeyword] = useState('') const [rankingMode, setRankingMode] = useState<'gene' | 'genome'>('gene') // 유전자순/유전체순 const [sortBy, setSortBy] = useState('rank') // 정렬 기준 (기본: 순위) const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc') // 정렬 방향 const [analysisFilter, setAnalysisFilter] = useState<'all' | 'completed' | 'mptOnly' | 'unavailable'>('all') // 분석 상태 필터 // 커스텀 컬럼 표시 필터 const [selectedDisplayGenes, setSelectedDisplayGenes] = useState([]) // 테이블에 표시할 유전자 const [selectedDisplayTraits, setSelectedDisplayTraits] = useState([]) // 테이블에 표시할 형질 const [availableGenes, setAvailableGenes] = useState([]) // 선택 가능한 유전자 목록 const [availableTraits, setAvailableTraits] = useState([]) // 선택 가능한 형질 목록 (전역 필터 순서 유지) const [expandedRows, setExpandedRows] = useState>(new Set()) // 유전자형 더보기 펼침 상태 (pkCowNo 또는 "pkCowNo-traits" 형태) // 페이지네이션 const [currentPage, setCurrentPage] = useState(1) const itemsPerPage = 12 // 형질 가중치가 1개 이상 설정되어야 필터 활성화 const isFilterSet = filters.isActive && Object.values(filters.traitWeights).some(w => w > 0) // ======================================== // 전역 필터 → 개체 리스트 표시 항목 동기화 // ======================================== 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 || [] 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 || [] 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 () => { if (!isFilterSet) { setLoading(false) // 필터가 설정되지 않았으면 API 호출하지 않음 setCows([]) setFilteredCows([]) return } try { setLoading(true) // 필터가 설정되면 API 호출 setError(null) // 1. 사용자 농장 필터 생성 let farmFilters: { field: string; operator: 'in'; value: number[] }[] = [] try { const userNo = user?.pkUserNo if (userNo) { const { default: apiClient } = await import('@/lib/api-client') const farmsResponse = await apiClient.get(`/farm?userId=${userNo}`) const farms = (farmsResponse.data || farmsResponse) as { pkFarmNo: number }[] if (farms && Array.isArray(farms) && farms.length > 0) { const farmNos = farms.map((f) => f.pkFarmNo) farmFilters = [{ field: 'cow.fkFarmNo', operator: 'in', value: farmNos }] } } } catch (err) { console.error('Failed to fetch farms:', err) } // 2. 랭킹 옵션 구성 // - GENE 모드: 마커 유전자 기반 정렬 (우량동형 → 이형 → 유전체점수) 추후 구현 예정 // - GENOME 모드: 형질 가중치 기반 정렬 let rankingOptions: any const traitConditions = Object.entries(filters.traitWeights) // 형질 가중치 조건 1이 기본 (유전체 점수 계산용) .filter(([, weight]) => weight > 0) .map(([traitNm, weight]) => ({ traitNm, weight })) if (rankingMode === 'gene' && filters.isActive && filters.selectedGenes && filters.selectedGenes.length > 0) { // GENE 모드 rankingOptions = { criteriaType: 'GENE', geneConditions: filters.selectedGenes.map(markerNm => ({ markerNm, order: 'DESC' })), traitConditions } } else if (rankingMode === 'genome' || !filters.isActive || !filters.selectedGenes || filters.selectedGenes.length === 0) { // GENOME 모드 if (!filters.isActive) { rankingOptions = { criteriaType: 'GENOME', traitConditions: [], inbreedingCondition: { maxThreshold: 0, order: 'ASC' } } } else if (rankingMode === 'genome' && traitConditions.length === 0) { rankingOptions = { criteriaType: 'GENOME', traitConditions: [], inbreedingCondition: { maxThreshold: filters.inbreedingThreshold ?? 0, order: 'ASC' } } } else { rankingOptions = { criteriaType: 'GENOME', traitConditions, inbreedingCondition: { maxThreshold: filters.inbreedingThreshold ?? 0, order: 'ASC' } } } } else { // 기본값 (GENOME 모드) rankingOptions = { criteriaType: 'GENOME', traitConditions: [], inbreedingCondition: { maxThreshold: 0, order: 'ASC' } } } // 3. API 호출 및 응답 매핑 const rankingRequest = { filterOptions: { filters: farmFilters }, // 필터옵션과 rankingOptions // 랭킹옵션을 담아서 백엔드로 전달 } const response = await cowApi.getRanking(rankingRequest) 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, t) => { acc[t.traitName] = { breedVal: t.traitEbv, traitVal: t.traitVal } return acc }, {}) || {}, })) setCows(cowsData) setFilteredCows(cowsData) } catch (err) { console.error('개체 데이터 조회 실패:', err) setError(err instanceof Error ? err.message : '데이터를 불러오는데 실패했습니다') } finally { setLoading(false) } } fetchCows() // eslint-disable-next-line react-hooks/exhaustive-deps }, [filters, rankingMode, isFilterSet]) // ======================================== // 클라이언트 측 필터링 및 정렬 // ======================================== useEffect(() => { let result = [...cows] // 1. 검색 필터 if (searchKeyword) { const keyword = searchKeyword.toLowerCase() result = result.filter(cow => (cow.cowId && cow.cowId.toLowerCase().includes(keyword)) || (cow.cowShortNo && cow.cowShortNo.toLowerCase().includes(keyword)) || String(cow.pkCowNo).includes(searchKeyword) ) } // 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) ) }) } // 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) } // 4. 정렬 if (sortBy !== 'none') { switch (sortBy) { case 'rank': result.sort((a, b) => { const rankA = a.rank ?? 9999 const rankB = b.rank ?? 9999 return sortOrder === 'asc' ? rankA - rankB : rankB - rankA }) break case 'number': 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 return sortOrder === 'asc' ? scoreA - scoreB : scoreB - scoreA }) break } } setFilteredCows(result) setCurrentPage(1) }, [searchKeyword, sortBy, sortOrder, cows, filters, analysisFilter]) // 페이지네이션 const totalPages = Math.ceil(filteredCows.length / itemsPerPage) const startIndex = (currentPage - 1) * itemsPerPage const paginatedCows = filteredCows.slice(startIndex, startIndex + itemsPerPage) // handleCowClick - cowId 또는 pkCowNo로 상세 페이지 이동 const handleCowClick = (cowNo: number | string) => { router.push(`/cow/${cowNo}`) } const handlePageChange = (page: number) => { setCurrentPage(page) window.scrollTo({ top: 0, behavior: 'smooth' }) } // 랭킹 가져오기 (백엔드에서 계산된 값 사용) const getRank = (cow: CowWithGenes): number => { return cow.rank ?? 9999 } if (loading) { return (

로딩 중...

) } if (error) { return (

에러: {error}

) } // 필터 미설정 시 안내 화면 if (!isFilterSet) { return (

필터 설정이 필요합니다

개체 목록을 조회하려면 먼저 분석에 사용할 형질(유전체)을 1개 이상 선택해주세요.

) } // 본문시작 ==================================================================================================== return (
{/* 헤더 */}
{/* 제목 */}

개체 목록

{'농장'} 보유 개체 현황

{/* 분석 상태 탭 필터 - 모바일: 2x2 그리드, 데스크톱: 가로 배치 */}
{/* 필터 및 검색 통합 박스 */}
{/* 검색창 */}
setSearchKeyword(e.target.value)} />
{/* 필터 옵션들 - 모바일: 2행, 데스크톱: 1행 */}
{/* 랭킹/정렬 그룹 */}
{/* 표시항목 그룹 */}
{availableGenes.map((gene) => (
{ if (checked) { // 전역 필터 순서대로 추가 (순서 유지) const orderedGenes = availableGenes.filter(g => selectedDisplayGenes.includes(g) || g === gene ) setSelectedDisplayGenes(orderedGenes) } else { setSelectedDisplayGenes(selectedDisplayGenes.filter(g => g !== gene)) } }} />
))}
{selectedDisplayGenes.length}/{availableGenes.length}개

순서는 전역 필터에서 설정하세요

{availableTraits.map((trait) => (
{ if (checked) { // 전역 필터 순서대로 추가 (순서 유지) const orderedTraits = availableTraits.filter(t => selectedDisplayTraits.includes(t) || t === trait ) setSelectedDisplayTraits(orderedTraits) } else { setSelectedDisplayTraits(selectedDisplayTraits.filter(t => t !== trait)) } }} />
))}
{selectedDisplayTraits.length}/{availableTraits.length}개

순서는 전역 필터에서 설정하세요

{/* 리스트 뷰 */}
{/* 데스크톱 테이블 뷰 */} {(
{selectedDisplayGenes.length > 0 && ( )} {selectedDisplayTraits.length > 0 && ( )} {paginatedCows.map((cow) => { const rank = getRank(cow) return ( handleCowClick(cow.cowId)} // cowId 개체번호로 이동 > {selectedDisplayGenes.length > 0 && ( )} {selectedDisplayTraits.length > 0 && ( )} ) })}
순위 개체번호 생년월일 성별 모개체번호 아비 KPN {analysisFilter === 'mptOnly' ? '월령(검사일)' : '월령(분석일)'} {analysisFilter === 'mptOnly' ? '검사일자' : '분석일자'} 선발지수 유전자형형질
{rank}
{(() => { // 번식능력만 있는 개체 판단 (유전체 데이터 없음) const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: cowBirthDt 없으면 MPT로 역산 if ((analysisFilter === 'mptOnly' || hasMptOnly) && !cow.cowBirthDt && cow.mptTestDt && cow.mptMonthAge) { const testDate = new Date(cow.mptTestDt) const birthDate = new Date(testDate) birthDate.setMonth(birthDate.getMonth() - cow.mptMonthAge) return birthDate.toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) } return cow.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) : '-' })()} {cow.cowSex === "수" ? "수소" : "암소"} {cow.damCowId && cow.damCowId !== '0' ? cow.damCowId : '-'} {cow.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'} {(() => { // 번식능력만 있는 개체 판단 const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 기준 월령 if (analysisFilter === 'mptOnly' || hasMptOnly) { if (cow.cowBirthDt && cow.mptTestDt) { const birthDate = new Date(cow.cowBirthDt) const refDate = new Date(cow.mptTestDt) return `${Math.floor((refDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` } return '-' } // 유전체 분석일 기준 월령 if (cow.cowBirthDt && cow.anlysDt) { const birthDate = new Date(cow.cowBirthDt) const refDate = new Date(cow.anlysDt) return `${Math.floor((refDate.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` } return '-' })()} {(() => { // 번식능력만 있는 개체 판단 const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 사용 if (analysisFilter === 'mptOnly' || hasMptOnly) { return cow.mptTestDt ? new Date(cow.mptTestDt).toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) : '-' } // 유전체 탭: unavailableReason 있으면 배지, 없으면 분석일자 if (cow.unavailableReason) { return ( {cow.unavailableReason} ) } return cow.anlysDt ? new Date(cow.anlysDt).toLocaleDateString('ko-KR', { year: '2-digit', month: '2-digit', day: '2-digit' }) : '-' })()} {(cow.genomeScore !== undefined && cow.genomeScore !== null) ? (
{cow.genomeScore.toFixed(2)}
) : ( 분석불가 )}
e.stopPropagation()} > {(() => { const isExpanded = expandedRows.has(cow.pkCowNo) const displayGenes = isExpanded ? selectedDisplayGenes : selectedDisplayGenes.slice(0, 3) const remainingCount = selectedDisplayGenes.length - 3 return (
{displayGenes.map((geneName) => { const gene = cow.genes?.find(g => g.name === geneName) const genotype = gene?.genotype || '-' return (
{geneName} {gene ? ( {genotype} ) : ( - )}
) })} {selectedDisplayGenes.length > 3 && ( )}
) })()}
e.stopPropagation()} >
{(() => { const isExpanded = expandedRows.has(`${cow.pkCowNo}-traits`) const displayTraits = isExpanded ? selectedDisplayTraits : selectedDisplayTraits.slice(0, 3) const remainingCount = selectedDisplayTraits.length - 3 return ( <> {displayTraits.map((trait) => { let traitValue = '-' if (cow.traits && cow.traits[trait] !== undefined) { const traitData = cow.traits[trait] if (typeof traitData === 'object' && traitData.traitVal !== undefined && traitData.traitVal !== null) { traitValue = Number(traitData.traitVal).toFixed(1) } else if (typeof traitData === 'object' && traitData.breedVal !== undefined) { traitValue = Number(traitData.breedVal).toFixed(1) } else if (typeof traitData === 'number') { traitValue = Number(traitData).toFixed(1) } } return (
{TRAIT_DISPLAY_NAMES[trait] || trait} {traitValue}
) })} {selectedDisplayTraits.length > 3 && ( )} ) })()}
)} {/* 모바일 컴팩트 카드 뷰 */} {(
{paginatedCows.map((cow) => { const rank = getRank(cow) const isFemale = cow.cowSex !== '수' return (
handleCowClick(cow.cowId)} > {/* 1행: 순위, 개체번호, 성별, 선발지수 */}
{rank}위 {isFemale ? '암' : '수'}
{cow.genomeScore !== undefined && cow.genomeScore !== null ? ( {cow.genomeScore.toFixed(2)} ) : ( 분석불가 )}
{/* 2행: 기본 정보 */}
생년월일 {(() => { // 번식능력만 있는 개체 판단 const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: cowBirthDt 없으면 MPT로 역산 if ((analysisFilter === 'mptOnly' || hasMptOnly) && !cow.cowBirthDt && cow.mptTestDt && cow.mptMonthAge) { const testDate = new Date(cow.mptTestDt) const birthDate = new Date(testDate) birthDate.setMonth(birthDate.getMonth() - cow.mptMonthAge) return birthDate.toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) } return cow.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : '-' })()}
{(() => { const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt return (analysisFilter === 'mptOnly' || hasMptOnly) ? '월령 (검사일)' : '월령 (분석일)' })()} {(() => { // 번식능력만 있는 개체 판단 const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 기준 월령 if (analysisFilter === 'mptOnly' || hasMptOnly) { if (cow.cowBirthDt && cow.mptTestDt) { return `${Math.floor((new Date(cow.mptTestDt).getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` } return '-' } // 유전체 분석일 기준 월령 if (cow.cowBirthDt && cow.anlysDt) { return `${Math.floor((new Date(cow.anlysDt).getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` } return '-' })()}
{cow.damCowId && cow.damCowId !== '0' ? (() => { const digits = cow.damCowId.replace(/\D/g, '') if (digits.length === 12) { return `${digits.slice(0, 3)} ${digits.slice(3, 7)} ${digits.slice(7, 11)} ${digits.slice(11)}` } return cow.damCowId })() : '-'}
{cow.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'}
{(() => { const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt return (analysisFilter === 'mptOnly' || hasMptOnly) ? '검사일' : (cow.anlysDt ? '분석일' : '분석결과') })()} {(() => { // 번식능력만 있는 개체 판단 const hasMptOnly = cow.hasMpt && !cow.genomeScore && !cow.anlysDt // 번식능력 탭이거나 번식능력만 있는 개체: MPT 검사일 사용 if (analysisFilter === 'mptOnly' || hasMptOnly) { return cow.mptTestDt ? new Date(cow.mptTestDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) : '-' } if (cow.anlysDt) { return new Date(cow.anlysDt).toLocaleDateString('ko-KR', { year: '2-digit', month: 'numeric', day: 'numeric' }) } if (cow.unavailableReason) { return ( {cow.unavailableReason} ) } return '-' })()}
{/* 유전자형 섹션 */} {selectedDisplayGenes.length > 0 && (
e.stopPropagation()}>
유전자 {(() => { const isExpanded = expandedRows.has(`${cow.pkCowNo}-mobile-genes`) // 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 genotype = gene?.genotype || '-' return ( {geneName} {genotype} ) })} {selectedDisplayGenes.length > 4 && ( )} ) })()}
)} {/* 형질 섹션 */} {selectedDisplayTraits.length > 0 && (
e.stopPropagation()}> {(() => { const isExpanded = expandedRows.has(`${cow.pkCowNo}-mobile-traits`) const displayTraits = isExpanded ? selectedDisplayTraits : selectedDisplayTraits.slice(0, 4) const remainingCount = selectedDisplayTraits.length - 4 return ( <>
{displayTraits.map((trait) => { let traitValue = '-' if (cow.traits && cow.traits[trait] !== undefined) { const traitData = cow.traits[trait] if (typeof traitData === 'object' && traitData.traitVal !== undefined && traitData.traitVal !== null) { traitValue = Number(traitData.traitVal).toFixed(1) } else if (typeof traitData === 'object' && traitData.breedVal !== undefined) { traitValue = Number(traitData.breedVal).toFixed(1) } else if (typeof traitData === 'number') { traitValue = Number(traitData).toFixed(1) } } return (
{TRAIT_DISPLAY_NAMES[trait] || trait} {traitValue}
) })}
{selectedDisplayTraits.length > 4 && ( )} ) })()}
)}
) })}
)}
{filteredCows.length === 0 && selectedDisplayGenes.length > 0 && (
{searchKeyword ? '검색 결과가 없습니다.' : '등록된 소가 없습니다.'}
)} {/* 페이지네이션 */} {totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => { // 모바일: 앞뒤 1개씩만, 데스크톱: 앞뒤 2개씩 const isMobileVisible = page === 1 || page === totalPages || (page >= currentPage - 1 && page <= currentPage + 1) if (isMobileVisible) { return ( ) } else if (page === currentPage - 2 || page === currentPage + 2) { return ... } return null })}
)}
) } export default function MyCowPage() { return ( ) }