[260212]
- 태그 AND 검색 도입 - UI/UX 디자인 개선 (칩 & 배지) - 모바일 최적화 및 레이아웃 - 성능 및 리소스 최적화 (Zero-Dependency 아이콘) - 데이터 안정성 및 기타 - 그 외 오류 복구 - Tailwind CDN 제거
This commit is contained in:
112
scripts/main.js
112
scripts/main.js
@@ -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) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user