/** * productList.js * 상품 그리드/테이블 렌더링, 지연 로딩 및 페이지네이션 제어 */ import { state, saveSelection } from './state.js'; import { ITEMS_PER_PAGE, STATUS_META, STATUS_COLOR, PRODUCT_CONDITIONS } from './config.js'; import { updateSummary } from './main.js'; // ========================================================================== // 1. 터치 및 호버 인터랙션 (Interaction) // ========================================================================== window.isDragging = false; let touchStartX = 0; let touchStartY = 0; const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; /** 터치 시작: 드래그 여부 판단 초기화 */ window.handleTouchStart = function (e) { window.isDragging = false; touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; }; /** 터치 이동: 일정 거리 이상 움직이면 클릭이 아닌 드래그로 간주 */ window.handleTouchMove = function (e) { const touchX = e.touches[0].clientX; const touchY = e.touches[0].clientY; if (Math.abs(touchX - touchStartX) > 10 || Math.abs(touchY - touchStartY) > 10) { window.isDragging = true; } }; window.handleTouchEnd = function (e) {}; /** 썸네일 호버: 마우스 위치에 따른 이미지 교체 및 인디케이터 업데이트 */ window.handleThumbnailHover = function (e, id) { if (isTouchDevice) return; const product = state.visibleProducts.find((p) => p.id === id); if (!product || !product.images || product.images.length <= 1) return; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const sectionWidth = rect.width / product.images.length; const index = Math.floor(x / sectionWidth); const thumb = document.getElementById(`thumb-${id}`); const indicators = document.querySelector(`#indicator-${id}`)?.children; if (thumb && product.images[index]) { thumb.style.backgroundImage = `url("${product.images[index]}")`; } if (indicators) { Array.from(indicators).forEach((dot, i) => { dot.classList.toggle('bg-white', i === index); dot.classList.toggle('scale-125', i === index); dot.classList.toggle('bg-white/40', i !== index); }); } }; /** 호버 해제: 첫 번째 이미지로 복구 */ window.handleThumbnailLeave = function (id) { if (isTouchDevice) return; const product = state.visibleProducts.find((p) => p.id === id); const thumb = document.getElementById(`thumb-${id}`); const indicators = document.querySelector(`#indicator-${id}`)?.children; if (thumb && product) thumb.style.backgroundImage = `url("${product.images[0]}")`; if (indicators) { Array.from(indicators).forEach((dot, i) => { dot.classList.toggle('bg-white', i === 0); dot.classList.toggle('scale-125', i === 0); dot.classList.toggle('bg-white/40', i !== 0); }); } }; /** 개별 체크박스 토글 */ window.toggleSelectItem = function (id) { if (state.selectedIds.has(id)) state.selectedIds.delete(id); else state.selectedIds.add(id); saveSelection(); renderProducts(state.currentPage); updateSummary(); }; // ========================================================================== // 2. 메인 렌더링 컨트롤러 (Main Rendering) // ========================================================================== /** * 상태에 따른 상품 목록 렌더링 실행 * @param {number} page - 현재 페이지 번호 */ export function renderProducts(page = 1) { const grid = document.getElementById('product-grid'); const tableWrapper = document.getElementById('product-table-wrapper'); const paginationContainer = document.getElementById('pagination'); if (!grid || !tableWrapper) return; // [1] 결과 없음 처리 if (state.visibleProducts.length === 0) { renderEmpty(grid, tableWrapper, paginationContainer); return; } // [2] 페이지 데이터 슬라이싱 const startIndex = (page - 1) * ITEMS_PER_PAGE; const pagedProducts = state.visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE); // [3] 뷰 모드에 따른 렌더링 if (state.viewMode === 'grid') { renderGridView(grid, tableWrapper, pagedProducts); } else { renderTableView(grid, tableWrapper, pagedProducts); } updateSelectAllCheckbox(page); renderPagination(); } /** 검색 결과가 없을 때의 UI */ function renderEmpty(grid, tableWrapper, paginationContainer) { grid.classList.remove('grid'); grid.classList.add('hidden'); tableWrapper.classList.add('hidden'); const emptyMsg = `

검색 결과가 없습니다

입력하신 검색어나 선택한 필터를 확인해 주세요.

`; grid.innerHTML = emptyMsg; grid.classList.remove('hidden'); if (paginationContainer) paginationContainer.innerHTML = ''; } // ========================================================================== // 3. 그리드 & 테이블 뷰 상세 (View Details) // ========================================================================== /** 그리드 뷰 렌더링 */ function renderGridView(grid, tableWrapper, products) { grid.classList.replace('hidden', 'grid'); tableWrapper.classList.add('hidden'); const summaryBar = document.getElementById('selection-summary'); if (summaryBar) summaryBar.classList.add('hidden'); grid.innerHTML = products.map(product => createProductCardHTML(product)).join(''); setupLazyLoading(); } /** 그리드 개별 카드 HTML */ function createProductCardHTML(product) { const isSold = STATUS_META[product.status]?.soldOut === true; const isNonSale = product.status === '미판매'; const conditionConfig = PRODUCT_CONDITIONS[product.specs?.condition]; const conditionDisplay = conditionConfig ? conditionConfig.label : product.specs?.condition || ''; return `
${product.status}
${!isSold && product.images?.length > 1 ? ` ` : ''}

${product.title}

${isNonSale ? 'Not for Sale' : `${product.currency || '₩'}${product.price.toLocaleString()}`}

${conditionDisplay ? `${conditionDisplay}` : ''}

${product.description}

`; } /** 테이블 뷰 렌더링 */ function renderTableView(grid, tableWrapper, products) { grid.classList.add('hidden'); tableWrapper.classList.remove('hidden'); const tableBody = document.getElementById('product-table-body'); tableBody.innerHTML = products.map(product => createTableRowHTML(product)).join(''); updateSummary(); } /** 테이블 개별 행 HTML */ function createTableRowHTML(product) { const meta = STATUS_META[product.status]; const isSold = meta?.soldOut === true; const isSelectable = meta?.selectable !== false; const conditionConfig = PRODUCT_CONDITIONS[product.specs.condition]; const conditionDisplay = conditionConfig ? conditionConfig.label : '상세 설명 참고 ℹ️'; const conditionClass = conditionConfig ? conditionConfig.color : 'text-slate-500'; return ` ${product.title} ${conditionDisplay} ₩${product.price.toLocaleString()} ${product.status} `; } // ========================================================================== // 4. 유틸리티 및 페이지네이션 (Utils) // ========================================================================== /** 전체 선택 체크박스 상태 동기화 */ function updateSelectAllCheckbox(page) { const selectAllCheck = document.getElementById('select-all-current'); if (!selectAllCheck) return; const startIndex = (page - 1) * ITEMS_PER_PAGE; const currentSelectableItems = state.visibleProducts .slice(startIndex, startIndex + ITEMS_PER_PAGE) .filter((p) => STATUS_META[p.status]?.selectable !== false); selectAllCheck.checked = currentSelectableItems.length > 0 && currentSelectableItems.every((p) => state.selectedIds.has(p.id)); } /** Intersection Observer를 이용한 썸네일 지연 로딩 */ function setupLazyLoading() { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const card = entry.target; const productId = card.getAttribute('data-id'); const product = state.visibleProducts.find((p) => p.id === productId); const thumb = document.getElementById(`thumb-${productId}`); if (product && thumb) { thumb.style.backgroundImage = `url("${product.images[0]}")`; // 마우스 호버를 대비해 나머지 이미지 미리 로드 if (!STATUS_META[product.status]?.soldOut && product.images.length > 1) { product.images.slice(1).forEach(url => { const img = new Image(); img.src = url; }); } } observer.unobserve(card); } }); }, { threshold: 0.1 }); document.querySelectorAll('.product-card').forEach((card) => observer.observe(card)); } /** 페이지네이션 버튼 렌더링 */ export function renderPagination() { const container = document.getElementById('pagination'); if (!container) return; const totalPages = Math.ceil(state.visibleProducts.length / ITEMS_PER_PAGE); const { currentPage } = state; let html = ``; for (let i = 1; i <= totalPages; i++) { html += ``; } html += ``; container.innerHTML = html; } /** 페이지 변경 처리 */ export function changePage(page) { state.currentPage = page; renderProducts(page); window.scrollTo({ top: 0, behavior: 'smooth' }); }