필터 및 화면 수정사항 반영
This commit is contained in:
@@ -542,10 +542,10 @@ export function CategoryEvaluationCard({
|
||||
const selectedTrait = traitChartData.find(t => t.name === selectedTraitName)
|
||||
if (!selectedTrait) return null
|
||||
return (
|
||||
<div className="mx-4 mb-4 p-4 bg-slate-50 rounded-xl border border-slate-200 animate-fade-in">
|
||||
<div className="mx-2 mb-4 p-3 bg-slate-50 rounded-xl border border-slate-200 animate-fade-in">
|
||||
{/* 헤더: 형질명 + 닫기 */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="px-4 py-1.5 bg-slate-200 text-slate-700 text-base font-bold rounded-full">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="px-4 py-1.5 bg-slate-200 text-slate-700 text-sm font-bold rounded-full">
|
||||
{selectedTrait.shortName} 조회 기준
|
||||
</span>
|
||||
<button
|
||||
@@ -556,25 +556,25 @@ export function CategoryEvaluationCard({
|
||||
</button>
|
||||
</div>
|
||||
{/* 3개 카드 그리드 */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{/* 보은군 카드 */}
|
||||
<div className="flex flex-col items-center justify-center p-3 bg-white rounded-xl border-2 border-emerald-300 shadow-sm">
|
||||
<span className="text-xs text-muted-foreground mb-1 font-medium">보은군 평균</span>
|
||||
<span className="text-lg font-bold text-emerald-600">
|
||||
<div className="flex flex-col items-center justify-center px-3 py-4 bg-white rounded-xl border-2 border-emerald-300 shadow-sm">
|
||||
<span className="text-xs text-slate-600 mb-1 font-semibold whitespace-nowrap">보은군 평균</span>
|
||||
<span className="text-xl font-bold text-emerald-600">
|
||||
{selectedTrait.regionEpd?.toFixed(2) ?? '-'}
|
||||
</span>
|
||||
</div>
|
||||
{/* 농가 카드 */}
|
||||
<div className="flex flex-col items-center justify-center p-3 bg-white rounded-xl border-2 border-[#1F3A8F]/30 shadow-sm">
|
||||
<span className="text-xs text-muted-foreground mb-1 font-medium">농가 평균</span>
|
||||
<span className="text-lg font-bold text-[#1F3A8F]">
|
||||
<div className="flex flex-col items-center justify-center px-3 py-4 bg-white rounded-xl border-2 border-[#1F3A8F]/30 shadow-sm">
|
||||
<span className="text-xs text-slate-600 mb-1 font-semibold whitespace-nowrap">농가 평균</span>
|
||||
<span className="text-xl font-bold text-[#1F3A8F]">
|
||||
{selectedTrait.farmEpd?.toFixed(2) ?? '-'}
|
||||
</span>
|
||||
</div>
|
||||
{/* 개체 카드 */}
|
||||
<div className="flex flex-col items-center justify-center p-3 bg-white rounded-xl border-2 border-[#1482B0]/30 shadow-sm">
|
||||
<span className="text-xs text-muted-foreground mb-1 font-medium">내 개체</span>
|
||||
<span className="text-lg font-bold text-[#1482B0]">
|
||||
<div className="flex flex-col items-center justify-center px-3 py-4 bg-white rounded-xl border-2 border-[#1482B0]/30 shadow-sm">
|
||||
<span className="text-xs text-slate-600 mb-1 font-semibold whitespace-nowrap">내 개체</span>
|
||||
<span className="text-xl font-bold text-[#1482B0]">
|
||||
{selectedTrait.epd?.toFixed(2) ?? '-'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -811,10 +811,10 @@ export function NormalDistributionChart({
|
||||
return Math.max(chartX + halfWidth + 5, Math.min(x, chartX + chartWidth - halfWidth - 5))
|
||||
}
|
||||
|
||||
// 배지 크기 (더 크게)
|
||||
// 배지 크기 (더 크게) - 모바일에서 텍스트가 한 줄로 나오도록 너비 확보
|
||||
const cowBadgeW = isMobile ? 105 : 135
|
||||
const avgBadgeW = isMobile ? 100 : 135
|
||||
const regionBadgeW = isMobile ? 105 : 145
|
||||
const avgBadgeW = isMobile ? 118 : 135
|
||||
const regionBadgeW = isMobile ? 125 : 145
|
||||
const badgeH = isMobile ? 42 : 48
|
||||
|
||||
// Y 위치 계산 - 겹치지 않게 배치
|
||||
|
||||
@@ -17,6 +17,7 @@ import { GenomeTrait } from "@/types/genome.types"
|
||||
import { useGlobalFilter } from "@/contexts/GlobalFilterContext"
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowUp,
|
||||
BarChart3,
|
||||
CheckCircle2,
|
||||
Download,
|
||||
@@ -156,6 +157,7 @@ export default function CowOverviewPage() {
|
||||
const [geneDataLoading, setGeneDataLoading] = useState(false) // 유전자 데이터 로딩 중
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [activeTab, setActiveTab] = useState<string>('genome')
|
||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||
|
||||
// 검사 상태
|
||||
const [hasGenomeData, setHasGenomeData] = useState(false)
|
||||
@@ -220,6 +222,20 @@ export default function CowOverviewPage() {
|
||||
}
|
||||
}, [filters.isActive, firstPinnedTrait, chartFilterTrait])
|
||||
|
||||
// 스크롤 투 탑 버튼 표시 여부
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setShowScrollTop(window.scrollY > 400)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
// 맨 위로 스크롤
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
// 유전자 탭 필터 상태
|
||||
const [geneSearchInput, setGeneSearchInput] = useState('') // 실시간 입력값
|
||||
const [geneSearchKeyword, setGeneSearchKeyword] = useState('') // 디바운스된 검색값
|
||||
@@ -1938,6 +1954,17 @@ export default function CowOverviewPage() {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 플로팅 맨 위로 버튼 */}
|
||||
{showScrollTop && (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 z-50 w-12 h-12 bg-primary text-white rounded-full shadow-lg hover:bg-primary/90 transition-all duration-300 flex items-center justify-center hover:scale-110 active:scale-95"
|
||||
aria-label="맨 위로"
|
||||
>
|
||||
<ArrowUp className="w-6 h-6" />
|
||||
</button>
|
||||
)}
|
||||
</SidebarProvider>
|
||||
</AuthGuard>
|
||||
)
|
||||
|
||||
@@ -973,7 +973,7 @@ function MyCowContent() {
|
||||
})() : '-'}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.cowSex === "암" ? "암소" : "수소"}
|
||||
{cow.cowSex === "수" ? "수소" : "암소"}
|
||||
</td>
|
||||
<td className="cow-table-cell">
|
||||
{cow.damCowId && cow.damCowId !== '0' ? cow.damCowId : '-'}
|
||||
@@ -1132,7 +1132,7 @@ function MyCowContent() {
|
||||
<div className="md:hidden space-y-2.5">
|
||||
{paginatedCows.map((cow) => {
|
||||
const rank = getRank(cow)
|
||||
const isFemale = cow.cowSex === '암'
|
||||
const isFemale = cow.cowSex !== '수'
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
162
frontend/src/app/demo/floating-button/page.tsx
Normal file
162
frontend/src/app/demo/floating-button/page.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ArrowUp, ChevronUp, ChevronsUp, MoveUp } from 'lucide-react'
|
||||
|
||||
export default function FloatingButtonDemo() {
|
||||
const [selectedStyle, setSelectedStyle] = useState<number>(1)
|
||||
|
||||
const styles = [
|
||||
{
|
||||
id: 1,
|
||||
name: '기본 프라이머리',
|
||||
className: 'bg-primary text-white shadow-lg hover:bg-primary/90',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '그라데이션 블루',
|
||||
className: 'bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-xl shadow-blue-500/30 hover:shadow-blue-500/50',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '글래스모피즘',
|
||||
className: 'bg-white/80 backdrop-blur-md border border-white/50 text-slate-700 shadow-lg hover:bg-white/90',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '아웃라인',
|
||||
className: 'bg-white border-2 border-primary text-primary hover:bg-primary hover:text-white',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '미니멀 다크',
|
||||
className: 'bg-slate-800 text-white shadow-lg hover:bg-slate-700',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '소프트 그레이',
|
||||
className: 'bg-slate-100 text-slate-600 shadow-md hover:bg-slate-200 hover:text-slate-800',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '그린 그라데이션',
|
||||
className: 'bg-gradient-to-r from-emerald-500 to-teal-600 text-white shadow-xl shadow-emerald-500/30',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '네온 퍼플',
|
||||
className: 'bg-violet-600 text-white shadow-xl shadow-violet-500/40 hover:shadow-violet-500/60',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: '오렌지 웜',
|
||||
className: 'bg-gradient-to-r from-orange-400 to-rose-500 text-white shadow-xl shadow-orange-500/30',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: '심플 화이트',
|
||||
className: 'bg-white text-slate-500 shadow-xl border border-slate-200 hover:text-primary hover:border-primary',
|
||||
},
|
||||
]
|
||||
|
||||
const icons = [
|
||||
{ id: 'arrow', icon: ArrowUp, name: 'ArrowUp' },
|
||||
{ id: 'chevron', icon: ChevronUp, name: 'ChevronUp' },
|
||||
{ id: 'chevrons', icon: ChevronsUp, name: 'ChevronsUp' },
|
||||
{ id: 'move', icon: MoveUp, name: 'MoveUp' },
|
||||
]
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState('arrow')
|
||||
const SelectedIconComponent = icons.find(i => i.id === selectedIcon)?.icon || ArrowUp
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50 p-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl font-bold mb-2">플로팅 버튼 스타일 데모</h1>
|
||||
<p className="text-slate-600 mb-8">원하는 스타일을 선택해보세요. 우하단에 실제 버튼이 표시됩니다.</p>
|
||||
|
||||
{/* 아이콘 선택 */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-semibold mb-4">아이콘 선택</h2>
|
||||
<div className="flex gap-3">
|
||||
{icons.map((icon) => {
|
||||
const IconComp = icon.icon
|
||||
return (
|
||||
<button
|
||||
key={icon.id}
|
||||
onClick={() => setSelectedIcon(icon.id)}
|
||||
className={`flex flex-col items-center gap-2 p-4 rounded-xl border-2 transition-all ${
|
||||
selectedIcon === icon.id
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-slate-200 bg-white hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
<IconComp className="w-6 h-6" />
|
||||
<span className="text-xs text-slate-600">{icon.name}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 스타일 선택 그리드 */}
|
||||
<h2 className="text-lg font-semibold mb-4">스타일 선택</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-12">
|
||||
{styles.map((style) => (
|
||||
<button
|
||||
key={style.id}
|
||||
onClick={() => setSelectedStyle(style.id)}
|
||||
className={`p-4 rounded-xl border-2 transition-all ${
|
||||
selectedStyle === style.id
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-slate-200 bg-white hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-center mb-3">
|
||||
<div
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center transition-all ${style.className}`}
|
||||
>
|
||||
<SelectedIconComponent className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-center">{style.name}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 코드 표시 */}
|
||||
<div className="bg-slate-900 rounded-xl p-6 mb-8">
|
||||
<p className="text-slate-400 text-sm mb-2">선택한 스타일 코드:</p>
|
||||
<code className="text-green-400 text-sm break-all">
|
||||
{`className="${styles.find(s => s.id === selectedStyle)?.className}"`}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* 스크롤 테스트용 더미 콘텐츠 */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">스크롤 테스트</h2>
|
||||
<p className="text-slate-600">아래로 스크롤해서 버튼 동작을 확인하세요.</p>
|
||||
{Array.from({ length: 20 }).map((_, i) => (
|
||||
<div key={i} className="p-6 bg-white rounded-xl border border-slate-200">
|
||||
<h3 className="font-semibold mb-2">더미 콘텐츠 #{i + 1}</h3>
|
||||
<p className="text-slate-600">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 실제 플로팅 버튼 */}
|
||||
<button
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||
className={`fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full flex items-center justify-center transition-all duration-300 hover:scale-110 active:scale-95 ${
|
||||
styles.find(s => s.id === selectedStyle)?.className
|
||||
}`}
|
||||
aria-label="맨 위로"
|
||||
>
|
||||
<SelectedIconComponent className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user