/** 상품 그리드·페이지네이션 렌더링 */ 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. 체크박스 전역 핸들러 등록 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; // 1. 결과가 0개인 경우 안내 if (state.visibleProducts.length === 0) { grid.classList.remove('grid'); grid.classList.add('hidden'); tableWrapper.classList.add('hidden'); // 검색 결과 없음 메시지를 표시할 별도의 컨테이너가 없다면 grid 영역을 빌려 씁니다. const emptyMsg = `

검색 결과가 없습니다

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

`; grid.innerHTML = emptyMsg; grid.classList.remove('hidden'); if (paginationContainer) paginationContainer.innerHTML = ''; return; } // 2. 뷰 모드 설정 및 컨테이너 노출 정리 (hidden/flex/grid 충돌 방지) 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(); // 테이블일 때만 요약바 노출 여부 결정 } // 3. 현재 페이지 데이터 계산 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) => { // 1. 상태 판별 const isSold = STATUS_META[product.status]?.soldOut === true; const isNonSale = product.status === '미판매'; // 상태값이 '미판매'일 때 // 2. 스펙(Condition) 정보 추출 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.images .map( (_, i) => `
`, ) .join('')}
` : '' }

${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); }); }