Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-02-05 10:49:22 +09:00
2 changed files with 42 additions and 2 deletions

View File

@@ -508,7 +508,7 @@ export function OrderRegistration({
// 폼 콘텐츠 렌더링
const renderFormContent = useCallback(
() => (
<div className="space-y-6 max-w-4xl">
<div className="space-y-6">
{/* Validation 에러 Alert */}
{Object.keys(fieldErrors).length > 0 && (
<Alert className="bg-red-50 border-red-200">

View File

@@ -296,6 +296,7 @@ export function IntegratedListTemplateV2<T = any>({
const [accumulatedMobileData, setAccumulatedMobileData] = useState<T[]>([]);
const [lastAccumulatedPage, setLastAccumulatedPage] = useState(0);
const mobileScrollSentinelRef = useRef<HTMLDivElement>(null);
const mobileCardAreaRef = useRef<HTMLDivElement>(null);
// 클라이언트 사이드 인피니티용 (allData가 있는 경우)
const [clientDisplayCount, setClientDisplayCount] = useState(mobileDisplayCount || 20);
@@ -404,6 +405,45 @@ export function IntegratedListTemplateV2<T = any>({
return () => observer.disconnect();
}, [isServerSidePagination, allData, clientDisplayCount, enableMobileInfinityScroll, isMobileLoading, pagination.currentPage, pagination.totalPages, handleLoadMoreClient, handleLoadMoreMobile]);
// ===== 모바일 카드 영역 내부 스크롤 컨테인먼트 =====
// 카드 영역이 뷰포트 남은 높이만큼만 차지하고, 내부에서만 스크롤되도록 설정
// → 헤더/검색/탭은 항상 보이고, 카드만 스크롤
useEffect(() => {
const el = mobileCardAreaRef.current;
if (!el) return;
const applyScrollContainment = () => {
// xl(1280px) 이상은 데스크톱 → 테이블+페이지네이션 사용, 컨테인먼트 해제
if (window.innerWidth >= 1280) {
el.style.maxHeight = '';
el.style.overflowY = '';
el.style.overscrollBehavior = '';
return;
}
const rect = el.getBoundingClientRect();
const available = window.innerHeight - rect.top - 16; // 16px 하단 여유
if (available > 200) {
el.style.maxHeight = `${available}px`;
el.style.overflowY = 'auto';
el.style.overscrollBehavior = 'contain'; // 스크롤 누수 방지
}
};
// 페이지 스크롤을 최상단으로 리셋 후 정확한 위치 측정
window.scrollTo(0, 0);
el.scrollTop = 0;
const raf = requestAnimationFrame(applyScrollContainment);
window.addEventListener('resize', applyScrollContainment);
return () => {
cancelAnimationFrame(raf);
window.removeEventListener('resize', applyScrollContainment);
};
}, [activeTab, isLoading]);
const startIndex = (pagination.currentPage - 1) * pagination.itemsPerPage;
const allSelected = selectedItems.size === data.length && data.length > 0;
@@ -773,7 +813,7 @@ export function IntegratedListTemplateV2<T = any>({
)}
{/* 모바일/태블릿/소형 노트북 (~1279px) 카드 뷰 */}
<div className="xl:hidden space-y-4 md:space-y-0 md:grid md:grid-cols-2 md:gap-4 lg:grid-cols-3">
<div ref={mobileCardAreaRef} className="xl:hidden space-y-4 md:space-y-0 md:grid md:grid-cols-2 md:gap-4 lg:grid-cols-3">
{isLoading ? (
<div className="col-span-full">
<MobileCardGridSkeleton count={6} showCheckbox={showCheckbox} />