import products from '/data.js';
const ITEMS_PER_PAGE = 8;
let currentPage = 1;
let activeCategories = new Set(['All']);
let visibleProducts = products;
let searchKeyword = '';
const VISIBILITY_CONFIG = {
showUnlisted: false, // ๐ฅ ๋ฏธํ๋งค ๋
ธ์ถ ์ฌ๋ถ
showSold: true,
};
const STATUS_META = {
๋ฏธํ๋งค: {
selectable: false, // ๊ธฐ๋ณธ ํํฐ์ ์ ๋ธ
defaultVisible: false,
soldOut: false,
},
ํ๋งค์์ : {
selectable: true,
defaultVisible: true,
soldOut: false,
},
ํ๋งค์ค: {
selectable: true,
defaultVisible: true,
soldOut: false,
},
ํ๋งค์๋ฃ: {
selectable: true,
defaultVisible: false,
soldOut: true,
},
};
const STATUS_FILTERS = [
{
key: 'ํ๋งค์ค',
label: 'ํ๋งค์ค',
defaultActive: true,
visible: true,
},
{
key: 'ํ๋งค์์ ',
label: 'ํ๋งค ์์ ',
defaultActive: true,
visible: true,
},
{
key: '๋ฏธํ๋งค',
label: '๋ฏธํ๋งค',
defaultActive: false,
visible: VISIBILITY_CONFIG.showUnlisted,
},
{
key: 'ํ๋งค์๋ฃ',
label: 'ํ๋งค์๋ฃ',
defaultActive: false,
visible: VISIBILITY_CONFIG.showSold,
},
];
const STATUS_ORDER = {
ํ๋งค์ค: 0,
ํ๋งค์์ : 1,
๋ฏธํ๋งค: 2,
ํ๋งค์๋ฃ: 3, // ๐ฅ ํญ์ ๋งจ ๋ค
};
const STATUS_COLOR = {
ํ๋งค์ค: 'bg-primary/10 text-primary border-primary/30',
ํ๋งค์์ : 'bg-amber-400/10 text-amber-600 border-amber-400/30',
ํ๋งค์๋ฃ: 'bg-slate-400/10 text-slate-500 border-slate-400/30',
๋ฏธํ๋งค: 'bg-slate-200/10 text-slate-400 border-slate-300/30',
};
let activeStatuses = new Set(
Object.entries(STATUS_META)
.filter(([_, meta]) => meta.defaultVisible)
.map(([status]) => status),
);
function renderStatusChips() {
const container = document.getElementById('status-chips');
if (!container) return;
container.innerHTML = '';
STATUS_FILTERS.filter((f) => f.visible).forEach(({ key, label }) => {
const isActive = activeStatuses.has(key);
const baseColor = STATUS_COLOR[key] ?? '';
const chip = document.createElement('button');
chip.className = `
status-chip px-4 py-2 rounded-full text-sm font-medium transition
border
${isActive ? baseColor : 'bg-slate-50 text-slate-600 border-slate-200'}
`;
chip.textContent = label;
chip.onclick = () => {
toggleStatusFilter(key);
};
container.appendChild(chip);
});
}
function toggleStatusFilter(status) {
if (activeStatuses.has(status)) {
activeStatuses.delete(status);
} else {
activeStatuses.add(status);
}
// ์ต์ 1๊ฐ๋ ์ ์ง
if (activeStatuses.size === 0) {
STATUS_FILTERS.filter((f) => f.defaultActive).forEach((f) => activeStatuses.add(f.key));
}
applyFilters();
renderStatusChips();
}
const searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
searchKeyword = e.target.value.trim().toLowerCase();
applyFilters();
});
}
function applyFilters() {
currentPage = 1;
visibleProducts = products
.filter((product) => {
// ๐ ๋ฏธํ๋งค ๊ฐ์ ์ฐจ๋จ
if (product.status === '๋ฏธํ๋งค' && !VISIBILITY_CONFIG.showUnlisted) {
return false;
}
// ๐ ํ๋งค์๋ฃ ๊ธฐ๋ณธ ์จ๊น
if (product.status === 'ํ๋งค์๋ฃ' && !VISIBILITY_CONFIG.showSold) {
return false;
}
const statusMatch = activeStatuses.has(product.status);
const categoryMatch = activeCategories.has('All') || activeCategories.has(product.category);
const searchMatch = searchKeyword === '' || product.title.toLowerCase().includes(searchKeyword);
return statusMatch && categoryMatch && searchMatch;
})
// ๐ฅ ์ฌ๊ธฐ์ ์ ๋ ฌ
.sort((a, b) => {
const aOrder = STATUS_ORDER[a.status] ?? 999;
const bOrder = STATUS_ORDER[b.status] ?? 999;
return aOrder - bOrder;
});
renderProducts(currentPage);
}
/**
* 1. ์ํ ๋ชฉ๋ก ๋ ๋๋ง
*/
export function renderProducts(page) {
const grid = document.getElementById('product-grid');
if (!grid) return;
grid.innerHTML = '';
const startIndex = (page - 1) * ITEMS_PER_PAGE;
const pagedProducts = visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE);
pagedProducts.forEach((product) => {
const isSold = STATUS_META[product.status]?.soldOut === true;
const cardHtml = `
${product.title}
${product.currency}${product.price.toLocaleString()}
${product.description}
`;
grid.insertAdjacentHTML('beforeend', cardHtml);
});
renderPagination();
}
/**
* 2. ๋ชจ๋ฌ ์ด๊ธฐ ๋ฐ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ
*/
window.openModal = (id) => {
const product = products.find((p) => p.id === id);
if (!product) return;
const modal = document.getElementById('product-modal');
const images = product.images;
// ๋ฌดํ ๋ฃจํ๋ฅผ ์ํด ์ฒ์๊ณผ ๋์ ํด๋ก ์ถ๊ฐ [๋ง์ง๋ง ์ด๋ฏธ์ง, ...์๋ณธ ์ด๋ฏธ์ง..., ์ฒซ ์ด๋ฏธ์ง]
const loopImages = [images[images.length - 1], ...images, images[0]];
const mainImagesHtml = loopImages
.map(
(img) => `
`,
)
.join('');
// 2. ์ฌ์ด๋ ์ธ๋ค์ผ ๋์ ์์ฑ
const thumbnailsHtml = product.images
.map(
(img, idx) => `
`,
)
.join('');
// 3. ํ์ด์ง๋ค์ด์
๋ํธ ๋์ ์์ฑ
const dotsHtml = product.images
.map(
(_, idx) => `
`,
)
.join('');
// HTML ์ฃผ์
document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml;
document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml;
document.getElementById('modal-dots').innerHTML = dotsHtml;
// ํ
์คํธ ์ ๋ณด ์ฃผ์
(ID๋ค ๋ง์ถฐ์ฃผ์ธ์)
document.getElementById('modal-title').textContent = product.title;
document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`;
// ... ๋๋จธ์ง ์ ๋ณด ์ฃผ์
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// ๋๋๊ทธ ๊ธฐ๋ฅ ๋ค์ ์ฐ๊ฒฐ
const container = document.getElementById('modal-main-carousel-container');
const carousel = document.getElementById('modal-main-carousel');
carousel.innerHTML = mainImagesHtml;
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// ์ด๊ธฐ ์์น ์ค์ (ํด๋ก ๋ ๋ง์ง๋ง ์ด๋ฏธ์ง ๋ค์์ธ '์ง์ง ์ฒซ ๋ฒ์งธ' ์ด๋ฏธ์ง๋ก ์ด๋)
// const initialIndex = 1;
container.style.scrollBehavior = 'auto';
container.scrollLeft = container.clientWidth;
// ๋๋๊ทธ ๋ฐ ๋ฌดํ ๋ฃจํ ๊ฐ์ ์์
initBetterCarousel(container, images.length);
};
function initBetterCarousel(container, originalLength) {
let isDragging = false;
let startX = 0;
let startScroll = 0;
let startTime = 0;
const width = () => container.clientWidth;
container.addEventListener('mousedown', start);
container.addEventListener('touchstart', start, { passive: true });
function start(e) {
isDragging = true;
startX = e.touches ? e.touches[0].pageX : e.pageX;
startScroll = container.scrollLeft;
startTime = Date.now();
}
container.addEventListener('mousemove', move);
container.addEventListener('touchmove', move, { passive: false });
function move(e) {
if (!isDragging) return;
const x = e.touches ? e.touches[0].pageX : e.pageX;
container.scrollLeft = startScroll - (x - startX);
}
container.addEventListener('mouseup', end);
container.addEventListener('mouseleave', end);
container.addEventListener('touchend', end);
function end(e) {
if (!isDragging) return;
isDragging = false;
const delta = container.scrollLeft - startScroll;
const elapsed = Date.now() - startTime;
const direction = Math.abs(delta) > width() * 0.1 || elapsed < 200 ? (delta > 0 ? 1 : -1) : 0;
let index = Math.round(startScroll / width()) + direction;
container.style.scrollBehavior = 'smooth';
container.scrollTo({ left: index * width() });
// ๋ฌดํ ๋ฃจํ ๋ณด์
setTimeout(() => {
container.style.scrollBehavior = 'auto';
if (index === 0) {
container.scrollLeft = width() * originalLength;
}
if (index === originalLength + 1) {
container.scrollLeft = width();
}
syncModalUI(originalLength);
}, 300);
}
}
/**
* ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋์ UI(Dots, Thumbs) ๋๊ธฐํ
*/
function syncModalUI(originalLength) {
const container = document.getElementById('modal-main-carousel-container');
const index = getRealIndex(container, originalLength);
document.querySelectorAll('.modal-thumb-item').forEach((t, i) => {
t.classList.toggle('border-primary', i === index);
t.classList.toggle('opacity-100', i === index);
t.classList.toggle('opacity-70', i !== index);
});
document.querySelectorAll('.modal-dot-item').forEach((d, i) => {
d.classList.toggle('bg-primary', i === index);
d.classList.toggle('w-4', i === index);
d.classList.toggle('bg-gray-300', i !== index);
d.classList.toggle('w-2', i !== index);
});
ensureThumbnailVisible(index);
}
/**
* 3. ๋ชจ๋ฌ ๋ด ์ด๋ฏธ์ง ์คํฌ๋กค ๋ฐ UI ๋๊ธฐํ
*/
window.scrollToImage = (index) => {
const container = document.getElementById('modal-main-carousel-container');
if (!container) return;
container.scrollTo({
left: container.clientWidth * (index + 1), // ๐ฅ ์ค์
behavior: 'smooth',
});
};
/**
* 5. ๊ธฐํ (ํ์ด์ง๋ค์ด์
, ๋ชจ๋ฌ ๋ซ๊ธฐ)
*/
function renderPagination() {
const container = document.getElementById('pagination');
if (!container) return;
const totalPages = Math.ceil(visibleProducts.length / ITEMS_PER_PAGE);
let html = ``;
for (let i = 1; i <= totalPages; i++) {
html += ``;
}
html += ``;
container.innerHTML = html;
}
window.changePage = (page) => {
currentPage = page;
renderProducts(currentPage);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
window.closeModal = () => {
document.getElementById('product-modal').classList.add('hidden');
document.body.style.overflow = 'auto';
};
// ์ด๊ธฐ ์คํ
document.addEventListener('DOMContentLoaded', () => renderProducts(currentPage));
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
const modal = document.getElementById('product-modal');
if (!modal || modal.classList.contains('hidden')) return;
closeModal();
});
const thumbnailContainer = document.getElementById('modal-thumbnails');
function ensureThumbnailVisible(index) {
const container = document.getElementById('modal-thumbnails');
if (!container) return;
const thumbs = container.querySelectorAll('.modal-thumb-item');
const active = thumbs[index];
if (!active) return;
const cRect = container.getBoundingClientRect();
const tRect = active.getBoundingClientRect();
const isVisible = tRect.top >= cRect.top && tRect.bottom <= cRect.bottom;
if (!isVisible) {
active.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}
function getRealIndex(container, originalLength) {
let rawIndex = Math.round(container.scrollLeft / container.clientWidth);
let index = rawIndex - 1; // ํด๋ก ๋ณด์
if (index < 0) index = originalLength - 1;
if (index >= originalLength) index = 0;
return index;
}
// ์นดํ
๊ณ ๋ฆฌ ํํฐ
function getCategories(products) {
return ['All', ...new Set(products.map((p) => p.category))];
}
function renderCategoryChips(products) {
const container = document.getElementById('filter-chips');
if (!container) return;
const categories = ['All', ...new Set(products.map((p) => p.category))];
container.innerHTML = '';
categories.forEach((cat) => {
const isActive = activeCategories.has(cat);
const chip = document.createElement('button');
chip.className = `
filter-chip px-4 py-2 rounded-full text-sm font-medium transition
border
${isActive
? 'bg-primary text-white border-primary'
: 'bg-slate-50 text-slate-600 border-slate-200'}
`;
chip.textContent = cat;
chip.dataset.category = cat;
chip.onclick = () => {
toggleCategory(cat);
};
container.appendChild(chip);
});
}
function toggleCategory(category) {
if (category === 'All') {
activeCategories.clear();
activeCategories.add('All');
} else {
activeCategories.delete('All');
activeCategories.has(category)
? activeCategories.delete(category)
: activeCategories.add(category);
if (activeCategories.size === 0) {
activeCategories.add('All');
}
}
renderCategoryChips(products);
applyFilters();
}
function bindCategoryFilter(products) {
const chips = document.querySelectorAll('.filter-chip');
chips.forEach((chip) => {
chip.addEventListener('click', () => {
const category = chip.dataset.category;
if (category === 'All') {
activeCategories.clear();
activeCategories.add('All');
} else {
activeCategories.delete('All');
if (activeCategories.has(category)) {
activeCategories.delete(category);
} else {
activeCategories.add(category);
}
// ์๋ฌด ๊ฒ๋ ์์ผ๋ฉด All๋ก ๋ณต๊ท
if (activeCategories.size === 0) {
activeCategories.add('All');
}
}
applyFilters();
});
});
}
// ์นดํ
๊ณ ๋ฆฌ ํํฐ
renderCategoryChips(products);
bindCategoryFilter(products);
// updateChipUI();
// ์ํ ํํฐ (์ ์ฑ
๊ธฐ๋ฐ)
renderStatusChips();
// ๐ฅ ์ต์ด ํํฐ ์ ์ฉ (์ด๊ฒ ์ฒซ ๋ ๋)
applyFilters();