- 태그 AND 검색 도입
- UI/UX 디자인 개선 (칩 & 배지)
- 모바일 최적화 및 레이아웃
- 성능 및 리소스 최적화 (Zero-Dependency 아이콘)
- 데이터 안정성 및 기타
- 그 외 오류 복구
- Tailwind CDN 제거
This commit is contained in:
2026-02-12 17:25:56 +09:00
parent a7817d2113
commit 555321fe70
10 changed files with 990 additions and 428 deletions

View File

@@ -1,6 +1,6 @@
/** 진입점: 이벤트 바인딩·초기 렌더·URL 모달 처리 */
import { state, productsData, saveSelection } from './state.js';
import { applyFilters, renderStatusChips, renderCategoryChips, bindCategoryFilter } from './filters.js';
import { applyFilters, renderStatusChips, renderCategoryChips, bindCategoryFilter, renderTagChips } from './filters.js';
import { ITEMS_PER_PAGE, STATUS_META } from './config.js';
import { renderProducts, changePage } from './productList.js';
import { openModal, closeModal } from './modal.js';
@@ -8,7 +8,7 @@ import { scrollToImage } from './carousel.js';
console.log('Total products loaded:', productsData.length);
// HTML onclick에서 사용
// HTML onclick에서 사용하기 위한 전역 등록
window.openModal = openModal;
window.closeModal = closeModal;
window.changePage = changePage;
@@ -17,12 +17,12 @@ window.scrollToImage = scrollToImage;
let fadeTimers = {};
// 뷰 전환 이벤트
// 뷰 전환 이벤트 바인딩
document.getElementById('view-grid').onclick = () => {
state.viewMode = 'grid';
updateViewButtons();
renderProducts(state.currentPage);
};
document.getElementById('view-table').onclick = () => {
state.viewMode = 'table';
updateViewButtons();
@@ -31,10 +31,25 @@ document.getElementById('view-table').onclick = () => {
function updateViewButtons() {
const isGrid = state.viewMode === 'grid';
document.getElementById('view-grid').classList.toggle('bg-white', isGrid);
document.getElementById('view-grid').classList.toggle('text-primary', isGrid);
document.getElementById('view-table').classList.toggle('bg-white', !isGrid);
document.getElementById('view-table').classList.toggle('text-primary', !isGrid);
const gridBtn = document.getElementById('view-grid');
const tableBtn = document.getElementById('view-table');
if (!gridBtn || !tableBtn) return;
const active = ['bg-white', 'dark:bg-slate-700', 'shadow-sm', 'text-primary'];
const inactive = ['text-slate-400'];
if (isGrid) {
gridBtn.classList.add(...active);
gridBtn.classList.remove(...inactive);
tableBtn.classList.add(...inactive);
tableBtn.classList.remove(...active);
} else {
tableBtn.classList.add(...active);
tableBtn.classList.remove(...inactive);
gridBtn.classList.add(...inactive);
gridBtn.classList.remove(...active);
}
}
// [전역 함수] 체크박스 토글 및 합계 업데이트
@@ -45,30 +60,25 @@ window.toggleSelectItem = (id) => {
updateSummary();
};
/** 선택 요약 바 업데이트 (모바일 레이아웃 대응) */
export function updateSummary() {
const summary = document.getElementById('selection-summary');
const summaryBar = document.getElementById('selection-summary');
const countEl = document.getElementById('selected-count');
const priceEl = document.getElementById('selected-total-price');
if (!summary) return;
// 테이블 모드이면서 선택된 항목이 있을 때만 노출
if (state.viewMode === 'table' && state.selectedIds.size > 0) {
// [수정] flex를 추가할 때 hidden은 확실히 제거
summary.classList.remove('hidden');
summary.classList.add('flex');
if (state.selectedIds.size > 0) {
summaryBar.classList.remove('hidden');
summaryBar.classList.add('flex'); // flex-wrap은 HTML에 이미 적용됨
countEl.textContent = state.selectedIds.size;
const total = Array.from(state.selectedIds).reduce((sum, id) => {
const p = productsData.find((item) => item.id === id);
return sum + (p ? p.price : 0);
}, 0);
countEl.textContent = state.selectedIds.size;
priceEl.textContent = `${total.toLocaleString()}`;
} else {
// [수정] hidden을 추가할 때 flex는 확실히 제거
summary.classList.remove('flex');
summary.classList.add('hidden');
summaryBar.classList.add('hidden');
summaryBar.classList.remove('flex');
}
}
@@ -137,15 +147,53 @@ themeToggleBtn.addEventListener('click', function () {
updateIcons();
});
// 초기 렌더
renderCategoryChips(productsData);
bindCategoryFilter(productsData);
renderStatusChips();
applyFilters();
document.addEventListener('DOMContentLoaded', () => {
console.log('Total products loaded:', productsData.length);
// 1. UI 컴포넌트 렌더링
renderCategoryChips(productsData);
bindCategoryFilter(productsData);
renderStatusChips();
renderTagChips();
// 2. 초기 데이터 계산 및 첫 페이지 렌더링 (순서 중요)
applyFilters();
renderProducts(state.currentPage);
checkUrlAndOpenModal();
updateViewButtons();
updateSummary();
// 테마 설정 (기존 로직 유지)
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
updateIcons();
// 3. 태그 토글 로직 (SVG 회전 포함)
const toggleBtn = document.getElementById('toggle-tags');
const tagContainer = document.getElementById('tag-container');
if (toggleBtn && tagContainer) {
toggleBtn.onclick = () => {
const isExpanded = tagContainer.classList.contains('expanded');
const svgIcon = toggleBtn.querySelector('svg');
if (isExpanded) {
tagContainer.style.maxHeight = tagContainer.scrollHeight + 'px';
requestAnimationFrame(() => {
tagContainer.style.maxHeight = '34px';
tagContainer.classList.remove('expanded');
if (svgIcon) svgIcon.style.transform = 'rotate(0deg)';
});
} else {
tagContainer.style.maxHeight = tagContainer.scrollHeight + 'px';
tagContainer.classList.add('expanded');
if (svgIcon) svgIcon.style.transform = 'rotate(180deg)';
setTimeout(() => {
if (tagContainer.classList.contains('expanded')) tagContainer.style.maxHeight = 'none';
}, 300);
}
};
}
});
// 데이터용 새 ID 생성기
@@ -308,7 +356,7 @@ function updateThumbnailWithFade(productId, newImageUrl, index) {
window.handleThumbnailLeave = (productId) => {
currentHoverIndex = -1; // 인덱스 초기화
resetThumbnail(productId);
};
@@ -332,7 +380,7 @@ function resetThumbnail(productId) {
// 2. 페이드 레이어를 즉시 숨김 (transition 방해 금지)
fadeThumb.style.transition = 'none';
fadeThumb.style.opacity = '0';
// 3. 두 레이어 모두 첫 번째 이미지로 강제 일치
mainThumb.style.backgroundImage = firstImg;
fadeThumb.style.backgroundImage = firstImg;
@@ -342,7 +390,7 @@ function resetThumbnail(productId) {
fadeThumb.style.transition = 'opacity 0.3s ease-in-out';
}, 50);
}
if (indicator) updateIndicatorUI(indicator, 0);
}
@@ -356,7 +404,6 @@ function updateIndicatorUI(indicator, activeIndex) {
});
}
// 터치 상태 관리를 위한 변수
let touchStartX = 0;
let isDragging = false;
@@ -387,7 +434,7 @@ window.handleTouchMove = (e, productId) => {
const mainThumb = document.getElementById(`thumb-${productId}`);
const fadeThumb = document.getElementById(`thumb-fade-${productId}`);
if (mainThumb && fadeThumb) {
// 드래그 중에는 페이드 없이 즉시 교체 (반응성 우선)
mainThumb.style.backgroundImage = `url("${product.images[index]}")`;
@@ -418,4 +465,3 @@ function updateIndicator(productId, index) {
});
}
}