[260222]
필터 로직 개선 최종 스크립트 코드 정리
This commit is contained in:
326
scripts/modal.js
326
scripts/modal.js
@@ -1,72 +1,127 @@
|
||||
/** 상품 상세 모달 (열기/닫기·콘텐츠 채우기·링크 복사) */
|
||||
/**
|
||||
* modal.js
|
||||
* 상품 상세 모달의 제어(열기/닫기), 데이터 렌더링 및 캐러셀 초기화
|
||||
*/
|
||||
|
||||
import { productsData } from './state.js';
|
||||
import { initBetterCarousel } from './carousel.js';
|
||||
import { TAG_STYLES, TAG_DEFAULT_STYLE, PRODUCT_CONDITIONS } from './config.js';
|
||||
|
||||
// ==========================================================================
|
||||
// 1. 모달 메인 제어 (Open / Close)
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* 특정 상품의 상세 모달을 오픈
|
||||
* @param {string} id - 상품 고유 ID
|
||||
*/
|
||||
export function openModal(id) {
|
||||
const product = productsData.find((p) => p.id === id);
|
||||
if (!product) return;
|
||||
|
||||
const modal = document.getElementById('product-modal');
|
||||
modal.classList.replace('hidden', 'flex');
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
// [1] UI 콘텐츠 채우기
|
||||
renderModalImages(product);
|
||||
renderModalInfo(product);
|
||||
setupCopyLink(product);
|
||||
|
||||
// [2] 히스토리 및 브라우저 상태 제어
|
||||
window.history.pushState({ modalOpen: true }, '', '');
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
// [3] 캐러셀 초기화
|
||||
const container = document.getElementById('modal-main-carousel-container');
|
||||
const images = product.images;
|
||||
container.style.scrollBehavior = 'auto';
|
||||
container.scrollLeft = container.clientWidth; // 루프 캐러셀을 위한 초기 위치 설정
|
||||
|
||||
initBetterCarousel(container, images.length);
|
||||
}
|
||||
|
||||
/** 모달 닫기 및 상태 초기화 */
|
||||
export function closeModal() {
|
||||
const modal = document.getElementById('product-modal');
|
||||
|
||||
// 스크롤 위치 초기화
|
||||
const carouselContainer = document.getElementById('modal-main-carousel-container');
|
||||
if (carouselContainer) carouselContainer.scrollLeft = 0;
|
||||
|
||||
const contentScroll = document.getElementById('modal-content-scroll');
|
||||
if (contentScroll) contentScroll.scrollTo(0, 0);
|
||||
|
||||
// UI 닫기
|
||||
modal.classList.add('hidden');
|
||||
document.body.classList.remove('modal-open');
|
||||
|
||||
// 히스토리 정리
|
||||
if (window.history.state && window.history.state.modalOpen) {
|
||||
window.history.back();
|
||||
}
|
||||
const cleanUrl = window.location.origin + window.location.pathname;
|
||||
window.history.replaceState(null, '', cleanUrl);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 2. 상세 데이터 렌더링 (Data Rendering)
|
||||
// ==========================================================================
|
||||
|
||||
/** 모달 내 이미지 영역(메인, 썸네일, 인디케이터) 생성 */
|
||||
function renderModalImages(product) {
|
||||
const images = product.images;
|
||||
const loopImages = [images[images.length - 1], ...images, images[0]];
|
||||
const mainImagesHtml = loopImages
|
||||
.map(
|
||||
(img) => `
|
||||
<div class="shrink-0 w-full h-full snap-center flex items-center justify-center p-0 md:p-4 select-none">
|
||||
<div class="w-full h-full max-w-full max-h-full rounded-xl md:rounded-2xl overflow-hidden flex items-center justify-center">
|
||||
<img src="${img}" draggable="false" class="max-w-full max-h-full w-auto h-auto object-contain sm:object-cover pointer-events-none rounded-md">
|
||||
</div>
|
||||
|
||||
// 메인 캐러셀 HTML
|
||||
const mainImagesHtml = loopImages.map(img => `
|
||||
<div class="shrink-0 w-full h-full snap-center flex items-center justify-center p-0 md:p-4 select-none">
|
||||
<div class="w-full h-full max-w-full max-h-full rounded-xl md:rounded-2xl overflow-hidden flex items-center justify-center">
|
||||
<img src="${img}" draggable="false" class="max-w-full max-h-full w-auto h-auto object-contain sm:object-cover pointer-events-none rounded-md">
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
const thumbnailsHtml = product.images
|
||||
.map(
|
||||
(img, idx) => `
|
||||
<div onclick="scrollToImage(${idx})"
|
||||
class="modal-thumb-item size-16 rounded-lg border-2 ${idx === 0 ? 'border-primary' : 'border-transparent'}
|
||||
bg-cover bg-center overflow-hidden cursor-pointer ${idx === 0 ? 'opacity-100' : 'opacity-70'}
|
||||
hover:opacity-100 transition-all shrink-0"
|
||||
style="background-image: url('${img}');"></div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
// 하단 썸네일 HTML
|
||||
const thumbnailsHtml = images.map((img, idx) => `
|
||||
<div onclick="scrollToImage(${idx})"
|
||||
class="modal-thumb-item size-16 rounded-lg border-2 ${idx === 0 ? 'border-primary' : 'border-transparent'}
|
||||
bg-cover bg-center overflow-hidden cursor-pointer ${idx === 0 ? 'opacity-100' : 'opacity-70'}
|
||||
hover:opacity-100 transition-all shrink-0"
|
||||
style="background-image: url('${img}');"></div>
|
||||
`).join('');
|
||||
|
||||
const dotsHtml = product.images
|
||||
.map(
|
||||
(_, idx) => `
|
||||
<div class="modal-dot-item ${idx === 0 ? 'w-4 bg-primary' : 'w-2 bg-gray-300 dark:bg-gray-700'} h-2 rounded-full transition-all"></div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
// 인디케이터 도트 HTML
|
||||
const dotsHtml = images.map((_, idx) => `
|
||||
<div class="modal-dot-item ${idx === 0 ? 'w-4 bg-primary' : 'w-2 bg-gray-300 dark:bg-gray-700'} h-2 rounded-full transition-all"></div>
|
||||
`).join('');
|
||||
|
||||
document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml;
|
||||
document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml;
|
||||
document.getElementById('modal-dots').innerHTML = dotsHtml;
|
||||
document.getElementById('modal-title').textContent = product.title;
|
||||
}
|
||||
|
||||
/** 상품 텍스트 정보 및 가격/상태 렌더링 */
|
||||
function renderModalInfo(product) {
|
||||
document.getElementById('modal-title').textContent = product.title;
|
||||
|
||||
// 카테고리
|
||||
const modalCategory = document.getElementById('modal-category');
|
||||
if (modalCategory) modalCategory.textContent = product.category;
|
||||
|
||||
// 상품 상태(Status) 뱃지 스타일
|
||||
const modalStatus = document.getElementById('modal-status');
|
||||
if (modalStatus) {
|
||||
modalStatus.textContent = product.status;
|
||||
const statusStyles = {
|
||||
판매중: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
|
||||
판매예정: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
||||
판매완료: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
|
||||
미판매: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400',
|
||||
};
|
||||
modalStatus.textContent = product.status;
|
||||
modalStatus.className = 'inline-flex shrink-0 px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-md sm:rounded-lg text-[10px] sm:text-[11px] md:text-xs font-bold uppercase tracking-wider whitespace-nowrap ' + (statusStyles[product.status] || statusStyles['미판매']);
|
||||
}
|
||||
|
||||
// 커스텀 태그
|
||||
const customTagElement = document.getElementById('modal-custom-tag');
|
||||
const tagText = product.customTag?.trim();
|
||||
if (tagText) {
|
||||
@@ -77,81 +132,13 @@ export function openModal(id) {
|
||||
customTagElement.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 구매일자: 값이 있을 때만 행 노출
|
||||
const modalDate = document.getElementById('modal-date');
|
||||
const modalDateRow = document.getElementById('modal-date-row');
|
||||
const pDate = product.specs?.purchaseDate;
|
||||
if (pDate && String(pDate).trim() !== '' && String(pDate) !== 'null') {
|
||||
if (modalDate) modalDate.textContent = pDate;
|
||||
if (modalDateRow) {
|
||||
modalDateRow.classList.remove('hidden');
|
||||
modalDateRow.classList.add('flex');
|
||||
}
|
||||
} else {
|
||||
if (modalDateRow) {
|
||||
modalDateRow.classList.add('hidden');
|
||||
modalDateRow.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
// 상세 스펙 (구매일자, 제품상태)
|
||||
renderSpecs(product);
|
||||
|
||||
// 제품 상태(specs.condition): 값이 있을 때만 행 노출
|
||||
const conditionKey = product.specs?.condition;
|
||||
const isVerified = product.specs?.isVerified;
|
||||
// 가격 표시 로직
|
||||
renderPrice(product);
|
||||
|
||||
// PRODUCT_CONDITIONS에서 라벨을 가져오되, 없으면 원본 키 표시
|
||||
const conditionData = PRODUCT_CONDITIONS[conditionKey];
|
||||
// 데이터가 객체라면 .label을 쓰고, 아니면 원본 키를 씁니다.
|
||||
const conditionLabel = conditionData?.label || conditionKey || '';
|
||||
|
||||
const conditionValueEl = document.getElementById('modal-condition');
|
||||
const conditionRowEl = document.getElementById('modal-condition-row');
|
||||
const conditionRowWrap = conditionRowEl?.parentElement; // 라벨+값 전체 행
|
||||
const verifiedIcon = document.getElementById('modal-verified-icon');
|
||||
if (conditionKey && String(conditionKey).trim() !== '') {
|
||||
if (conditionValueEl) conditionValueEl.textContent = conditionLabel;
|
||||
if (conditionRowWrap) {
|
||||
conditionRowWrap.classList.remove('hidden');
|
||||
conditionRowWrap.classList.add('flex');
|
||||
}
|
||||
if (verifiedIcon) {
|
||||
if (isVerified) verifiedIcon.classList.remove('hidden');
|
||||
else verifiedIcon.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
if (conditionRowWrap) {
|
||||
conditionRowWrap.classList.add('hidden');
|
||||
conditionRowWrap.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
|
||||
// 가격 표시 로직 수정
|
||||
const priceValueEl = document.getElementById('modal-price');
|
||||
const priceRowEl = document.getElementById('modal-price-row');
|
||||
const priceRowWrap = priceRowEl?.parentElement;
|
||||
|
||||
if (priceValueEl && priceRowWrap) {
|
||||
// [중요] 새로운 상품을 열 때마다 일단 hidden을 제거하여 초기화합니다.
|
||||
priceRowWrap.classList.remove('hidden');
|
||||
|
||||
// 1. 최우선 순위: 미판매 상태 체크
|
||||
if (product.status === '미판매') {
|
||||
priceValueEl.textContent = 'NOT FOR SALE';
|
||||
priceValueEl.classList.add('text-gray-500');
|
||||
}
|
||||
// 2. 가격 데이터가 아예 없는 경우 (null, undefined, 빈 문자열)
|
||||
// 숫자 0은 가격으로 인정하고 싶다면 (product.price === null || product.price === undefined)로 씁니다.
|
||||
else if (product.price === null || product.price === undefined || product.price === '') {
|
||||
priceRowWrap.classList.add('hidden');
|
||||
}
|
||||
// 3. 가격이 존재하는 경우 (0원을 포함하여 값이 있는 경우)
|
||||
else {
|
||||
const currency = product.currency || '₩';
|
||||
priceValueEl.textContent = `${currency}${Number(product.price).toLocaleString()}`;
|
||||
priceValueEl.classList.remove('text-gray-500');
|
||||
}
|
||||
}
|
||||
|
||||
// (선택 사항) 미판매 상품일 때 상세 설명 부분에 대체 텍스트를 넣거나 숨기고 싶다면:
|
||||
// 상세 설명
|
||||
const modalDesc = document.getElementById('modal-desc');
|
||||
if (modalDesc) {
|
||||
if (product.status === '미판매') {
|
||||
@@ -160,72 +147,83 @@ export function openModal(id) {
|
||||
modalDesc.innerHTML = Array.isArray(product.fullDescription) ? product.fullDescription.join('<br>') : product.fullDescription || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 상품 스펙 행(Row) 노출 제어 */
|
||||
function renderSpecs(product) {
|
||||
// 구매일자
|
||||
const modalDateRow = document.getElementById('modal-date-row');
|
||||
const pDate = product.specs?.purchaseDate;
|
||||
if (pDate && String(pDate).trim() !== '' && String(pDate) !== 'null') {
|
||||
document.getElementById('modal-date').textContent = pDate;
|
||||
modalDateRow?.classList.replace('hidden', 'flex');
|
||||
} else {
|
||||
modalDateRow?.classList.replace('flex', 'hidden');
|
||||
}
|
||||
|
||||
// 제품 상태(Condition)
|
||||
const conditionKey = product.specs?.condition;
|
||||
const conditionRowWrap = document.getElementById('modal-condition-row')?.parentElement;
|
||||
if (conditionKey) {
|
||||
const conditionLabel = PRODUCT_CONDITIONS[conditionKey]?.label || conditionKey;
|
||||
document.getElementById('modal-condition').textContent = conditionLabel;
|
||||
conditionRowWrap?.classList.replace('hidden', 'flex');
|
||||
|
||||
const verifiedIcon = document.getElementById('modal-verified-icon');
|
||||
if (verifiedIcon) verifiedIcon.classList.toggle('hidden', !product.specs?.isVerified);
|
||||
} else {
|
||||
conditionRowWrap?.classList.replace('flex', 'hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/** 가격 정보 렌더링 정책 적용 */
|
||||
function renderPrice(product) {
|
||||
const priceValueEl = document.getElementById('modal-price');
|
||||
const priceRowWrap = document.getElementById('modal-price-row')?.parentElement;
|
||||
if (!priceValueEl || !priceRowWrap) return;
|
||||
|
||||
priceRowWrap.classList.remove('hidden');
|
||||
|
||||
if (product.status === '미판매') {
|
||||
priceValueEl.textContent = 'NOT FOR SALE';
|
||||
priceValueEl.classList.add('text-gray-500');
|
||||
} else if (product.price === null || product.price === undefined || product.price === '') {
|
||||
priceRowWrap.classList.add('hidden');
|
||||
} else {
|
||||
const currency = product.currency || '₩';
|
||||
priceValueEl.textContent = `${currency}${Number(product.price).toLocaleString()}`;
|
||||
priceValueEl.classList.remove('text-gray-500');
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 3. 유틸리티 및 이벤트 리스너 (Utils)
|
||||
// ==========================================================================
|
||||
|
||||
/** 상품 공유 링크 복사 로직 설정 */
|
||||
function setupCopyLink(product) {
|
||||
const copyBtn = document.getElementById('copy-link-btn');
|
||||
const copyBtnText = document.getElementById('copy-btn-text');
|
||||
if (copyBtn) {
|
||||
copyBtn.onclick = () => {
|
||||
const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`;
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = '링크가 복사되었습니다!';
|
||||
copyBtn.classList.add('!bg-green-600');
|
||||
setTimeout(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = '상품 링크 복사하기';
|
||||
copyBtn.classList.remove('!bg-green-600');
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
}
|
||||
if (!copyBtn) return;
|
||||
|
||||
window.history.pushState({ modalOpen: true }, '', '');
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
// document.body.style.overflow = 'hidden';
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
const container = document.getElementById('modal-main-carousel-container');
|
||||
container.style.scrollBehavior = 'auto';
|
||||
container.scrollLeft = container.clientWidth;
|
||||
|
||||
initBetterCarousel(container, images.length);
|
||||
copyBtn.onclick = () => {
|
||||
const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`;
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = '링크가 복사되었습니다!';
|
||||
copyBtn.classList.add('!bg-green-600');
|
||||
setTimeout(() => {
|
||||
if (copyBtnText) copyBtnText.textContent = '상품 링크 복사하기';
|
||||
copyBtn.classList.remove('!bg-green-600');
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function closeModal() {
|
||||
/** 뒤로가기(브라우저/제스처) 시 모달 닫기 처리 */
|
||||
window.addEventListener('popstate', () => {
|
||||
const modal = document.getElementById('product-modal');
|
||||
|
||||
// 1. 이미지 캐러셀 영역 초기화
|
||||
const carouselContainer = document.getElementById('modal-main-carousel-container');
|
||||
if (carouselContainer) {
|
||||
carouselContainer.scrollLeft = 0;
|
||||
}
|
||||
|
||||
// 2. 우측 상세 정보 스크롤 영역 초기화 (추가된 부분)
|
||||
const contentScroll = document.getElementById('modal-content-scroll');
|
||||
if (contentScroll) {
|
||||
contentScroll.scrollTo(0, 0); // 스크롤을 맨 위로!
|
||||
}
|
||||
|
||||
// 모달 닫기 로직
|
||||
modal.classList.add('hidden');
|
||||
document.body.classList.remove('modal-open');
|
||||
|
||||
// 히스토리 및 URL 정리
|
||||
if (window.history.state && window.history.state.modalOpen) {
|
||||
window.history.back();
|
||||
}
|
||||
const cleanUrl = window.location.origin + window.location.pathname;
|
||||
window.history.replaceState(null, '', cleanUrl);
|
||||
}
|
||||
|
||||
// --- 뒤로가기 감지 이벤트 리스너 ---
|
||||
// 사용자가 브라우저 뒤로가기 버튼(또는 모바일 뒤로가기 제스처)을 누를 때 실행됩니다.
|
||||
window.addEventListener('popstate', (event) => {
|
||||
const modal = document.getElementById('product-modal');
|
||||
// 모달이 열려있는 상태에서 뒤로가기가 발생했다면 모달만 닫음
|
||||
if (!modal.classList.contains('hidden')) {
|
||||
// 이때 closeModal()을 호출하되, 이미 뒤로 이동한 상태이므로
|
||||
// closeModal 내부의 history.back()이 중복 실행되지 않게 주의
|
||||
modal.classList.add('hidden');
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user