/** 상품 그리드·페이지네이션 렌더링 */ import { state, saveSelection } from './state.js'; import { ITEMS_PER_PAGE, STATUS_META, STATUS_COLOR, PRODUCT_CONDITIONS } from './config.js'; import { updateSummary } from './main.js'; // --- 터치 및 드래그 관련 전역 변수 및 핸들러 --- 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; // 10px 이상 움직이면 드래그(스크롤)로 간주 if (Math.abs(touchX - touchStartX) > 10 || Math.abs(touchY - touchStartY) > 10) { window.isDragging = true; } }; window.handleTouchEnd = function (e) { // 필요한 경우 추가 로직 작성 가능 (현재는 isDragging 상태 유지만으로 충분) }; // 썸네일 호버 핸들러 (PC에서만 동작하도록 수정) 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) => { if (i === index) { dot.classList.add('bg-white', 'scale-125'); dot.classList.remove('bg-white/40'); } else { dot.classList.remove('bg-white', 'scale-125'); dot.classList.add('bg-white/40'); } }); } }; 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) => { if (i === 0) { dot.classList.add('bg-white', 'scale-125'); dot.classList.remove('bg-white/40'); } else { dot.classList.remove('bg-white', 'scale-125'); dot.classList.add('bg-white/40'); } }); } }; // 1. 체크박스 전역 핸들러 등록 window.toggleSelectItem = function (id) { if (state.selectedIds.has(id)) { state.selectedIds.delete(id); } else { state.selectedIds.add(id); } saveSelection(); renderProducts(state.currentPage); updateSummary(); }; export function renderProducts(page = 1) { const grid = document.getElementById('product-grid'); const tableWrapper = document.getElementById('product-table-wrapper'); const tableBody = document.getElementById('product-table-body'); const summaryBar = document.getElementById('selection-summary'); const paginationContainer = document.getElementById('pagination'); if (!grid || !tableWrapper) return; if (state.visibleProducts.length === 0) { 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 = ''; return; } if (state.viewMode === 'grid') { grid.classList.remove('hidden'); grid.classList.add('grid'); tableWrapper.classList.add('hidden'); if (summaryBar) { summaryBar.classList.remove('flex'); summaryBar.classList.add('hidden'); } } else { grid.classList.remove('grid'); grid.classList.add('hidden'); tableWrapper.classList.remove('hidden'); updateSummary(); } const startIndex = (page - 1) * ITEMS_PER_PAGE; const pagedProducts = state.visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE); if (state.viewMode === 'grid') { grid.innerHTML = ''; pagedProducts.forEach((product) => { const isSold = STATUS_META[product.status]?.soldOut === true; const isNonSale = product.status === '미판매'; const conditionKey = product.specs?.condition; const conditionConfig = PRODUCT_CONDITIONS[conditionKey]; const conditionDisplay = conditionConfig ? conditionConfig.label : conditionKey || ''; grid.insertAdjacentHTML( 'beforeend', `
${product.status}
${ !isSold && product.images?.length > 1 ? ` ` : '' }

${product.title}

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

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

${product.description}

`, ); }); setupLazyLoading(); } else { // 테이블 렌더링 로직 (생략 없이 유지) tableBody.innerHTML = pagedProducts .map((product) => { const meta = STATUS_META[product.status]; const isSold = meta?.soldOut === true; const isSelectable = meta?.selectable !== false; const conditionKey = product.specs.condition; const conditionConfig = PRODUCT_CONDITIONS[conditionKey]; let conditionDisplay = conditionConfig ? conditionConfig.label : conditionKey || '상세 설명 참고 ℹ️'; let conditionClass = conditionConfig ? conditionConfig.color : 'text-slate-500'; return ` ${product.title} ${conditionDisplay} ₩${product.price.toLocaleString()} ${product.status} `; }) .join(''); } const selectAllCheck = document.getElementById('select-all-current'); if (selectAllCheck) { 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)); } if (typeof renderPagination === 'function') renderPagination(); } function setupLazyLoading() { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const card = entry.target; const productId = card.getAttribute('data-id'); // state.js 등에서 가져온 데이터 활용 const product = state.visibleProducts.find((p) => p.id === productId); const thumb = document.getElementById(`thumb-${productId}`); if (product && thumb) { // 1. 첫 번째 이미지 로드 thumb.style.backgroundImage = `url("${product.images[0]}")`; // 2. 나머지 이미지 프리로드 (반짝임 방지) 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' }); } /** 모든 필터를 초기 상태로 되돌리는 함수 */ function resetAllFilters() { state.searchKeyword = ''; const searchInput = document.getElementById('search-input'); if (searchInput) searchInput.value = ''; state.activeCategories.clear(); state.activeCategories.add('All'); // 상태 필터 초기화 (config에서 defaultActive인 것만) import('./config.js').then(({ STATUS_FILTERS }) => { state.activeStatuses.clear(); STATUS_FILTERS.filter((f) => f.defaultActive).forEach((f) => state.activeStatuses.add(f.key)); // UI 전체 갱신 applyFilters(); renderStatusChips(); renderCategoryChips(productsData); }); }