diff --git a/scripts/config.js b/scripts/config.js
index 2490b88..2063a0d 100644
--- a/scripts/config.js
+++ b/scripts/config.js
@@ -2,40 +2,46 @@
export const ITEMS_PER_PAGE = 20;
-export const VISIBILITY_CONFIG = {
- showUnlisted: false,
- showSold: true,
-};
+// export const VISIBILITY_CONFIG = {
+// showUnlisted: false,
+// showSold: true,
+// };
export const STATUS_META = {
미판매: {
- selectable: false,
- defaultVisible: false,
+ selectable: false, // 체크박스 선택 불가
+ isDefaultActive: false, // 초기 로드 시 미체크 상태
+ isSystemVisible: false, // 아예 리스트/필터에서 제외 (완전 숨김)
soldOut: false,
},
판매예정: {
- selectable: true,
- defaultVisible: true,
+ selectable: false,
+ isDefaultActive: true,
+ isSystemVisible: true,
soldOut: false,
},
판매중: {
selectable: true,
- defaultVisible: true,
+ isDefaultActive: true,
+ isSystemVisible: true,
soldOut: false,
},
판매완료: {
- selectable: true,
- defaultVisible: false,
- soldOut: true,
+ selectable: false,
+ isDefaultActive: false, // 초기에는 안 보이지만, 사용자가 필터 클릭하면 보임
+ isSystemVisible: true,
+ soldOut: true, // 이미지에 SOLD OUT 표시
},
};
-export 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 },
-];
+// STATUS_FILTERS를 수동으로 만들지 않고 META에서 자동으로 생성합니다.
+export const STATUS_FILTERS = Object.keys(STATUS_META)
+ .filter(key => STATUS_META[key].isSystemVisible) // 시스템 가시성이 true인 것만 필터 칩 생성
+ .map(key => ({
+ key: key,
+ label: key === '판매예정' ? '판매 예정' : key,
+ defaultActive: STATUS_META[key].isDefaultActive
+ }));
export const STATUS_ORDER = {
판매중: 0,
diff --git a/scripts/filters.js b/scripts/filters.js
index 8a0112e..e955470 100644
--- a/scripts/filters.js
+++ b/scripts/filters.js
@@ -1,6 +1,6 @@
/** 상태·카테고리·검색 필터 로직 및 UI */
import { state, productsData } from './state.js';
-import { VISIBILITY_CONFIG, STATUS_FILTERS, STATUS_ORDER, STATUS_COLOR, SEARCH_CONFIG } from './config.js';
+import { ITEMS_PER_PAGE, STATUS_META, STATUS_FILTERS, STATUS_ORDER, STATUS_COLOR, SEARCH_CONFIG, } from './config.js';
import { renderProducts } from './productList.js';
function getStatusChipClass(status, isActive) {
@@ -16,9 +16,12 @@ export function renderStatusChips() {
if (!container) return;
container.innerHTML = '';
- STATUS_FILTERS.filter((f) => f.visible).forEach(({ key, label }) => {
+ // 이제 config에서 자동 생성된 STATUS_FILTERS를 사용합니다.
+ STATUS_FILTERS.forEach(({ key, label }) => {
const isActive = state.activeStatuses.has(key);
+
const chip = document.createElement('button');
+ // getStatusChipClass 함수가 기존에 정의되어 있다면 그대로 사용하세요.
chip.className = `status-chip px-3 py-1.5 md:px-4 md:py-2 rounded-full text-xs md:text-sm font-medium transition-all duration-200 border ${getStatusChipClass(key, isActive)}`;
chip.textContent = label;
chip.onclick = () => toggleStatusFilter(key);
@@ -39,37 +42,37 @@ function toggleStatusFilter(status) {
renderStatusChips();
}
+// [핵심] 필터 적용 함수
export function applyFilters() {
- state.currentPage = 1;
+ // [수정] 무조건 1페이지로 초기화하지 않고, 나중에 데이터 개수에 맞춰 계산합니다.
const keyword = state.searchKeyword.toLowerCase();
+ // 1. 데이터 필터링 및 정렬
state.visibleProducts = productsData
.filter((product) => {
- // [1] 가시성 및 상태/카테고리 필터
- if (product.status === '미판매' && !VISIBILITY_CONFIG.showUnlisted) return false;
- if (product.status === '판매완료' && !VISIBILITY_CONFIG.showSold) return false;
+ const meta = STATUS_META[product.status];
+ // [1] 시스템 가시성 체크 (isSystemVisible이 false면 목록에서 완전히 제외)
+ if (!meta || !meta.isSystemVisible) return false;
+
+ // [2] 상태 필터 체크 (사용자가 필터 칩을 클릭해 활성화했는지)
const statusMatch = state.activeStatuses.has(product.status);
+
+ // [3] 카테고리 필터 체크
const categoryMatch = state.activeCategories.has('All') || state.activeCategories.has(product.category);
- // [2] config 설정을 기반으로 한 동적 검색 매칭
+ // [4] 검색어 매칭 로직
const searchMatch =
keyword === '' ||
(() => {
const searchPool = [];
-
if (SEARCH_CONFIG.USE_TITLE) searchPool.push(product.title);
-
if (SEARCH_CONFIG.USE_CUSTOM_TAG && product.customTag) searchPool.push(product.customTag);
-
if (SEARCH_CONFIG.USE_DESCRIPTION && product.description) searchPool.push(product.description);
-
- if (SEARCH_CONFIG.USE_TAGS && product.tags) searchPool.push(...product.tags); // 배열 요소를 풀어서 추가
-
+ if (SEARCH_CONFIG.USE_TAGS && product.tags) searchPool.push(...product.tags);
if (SEARCH_CONFIG.USE_FULL_DESCRIPTION && product.fullDescription) searchPool.push(...product.fullDescription);
- // 검색 풀(Pool)에 있는 단어 중 키워드를 포함하는 게 하나라도 있는지 확인
- return searchPool.some((text) => String(text).toLowerCase().includes(keyword));
+ return searchPool.some((text) => String(text || '').toLowerCase().includes(keyword));
})();
return statusMatch && categoryMatch && searchMatch;
@@ -80,8 +83,22 @@ export function applyFilters() {
return aOrder - bOrder;
});
- renderTotalCount(state.visibleProducts.length);
+ // 2. [추가] 페이지 위치 안전 조정 로직
+ // 필터링된 결과로 가질 수 있는 최대 페이지 계산
+ const totalPages = Math.ceil(state.visibleProducts.length / ITEMS_PER_PAGE);
+ if (state.currentPage > totalPages) {
+ // 만약 필터링 후 전체 페이지가 현재 페이지보다 적어지면 마지막 페이지로 이동
+ state.currentPage = Math.max(1, totalPages);
+ } else if (state.currentPage < 1) {
+ // 혹시 모를 에러 방지용 1페이지 고정
+ state.currentPage = 1;
+ }
+ // ※ 참고: 필터를 걸 때마다 무조건 첫 페이지를 보게 하고 싶다면
+ // 위 로직 대신 단순히 state.currentPage = 1; 을 쓰시면 됩니다.
+
+ // 3. UI 업데이트
+ renderTotalCount(state.visibleProducts.length);
renderProducts(state.currentPage);
}
@@ -100,13 +117,29 @@ export function getCategories(products) {
export function renderCategoryChips(products) {
const container = document.getElementById('filter-chips');
if (!container) return;
- const categories = ['All', ...new Set(products.map((p) => p.category))];
+
+ // [핵심] 시스템 가시성이 true인 상품의 카테고리만 추출합니다.
+ const validCategories = products
+ .filter(p => {
+ const meta = STATUS_META[p.status];
+ // 해당 상태가 정의되어 있고, 시스템에서 보여주기로 한 경우만 포함
+ return meta && meta.isSystemVisible;
+ })
+ .map(p => p.category);
+
+ // 'All'은 항상 포함하고, 필터링된 카테고리들만 중복 제거하여 합침
+ const categories = ['All', ...new Set(validCategories)];
+
container.innerHTML = '';
categories.forEach((cat) => {
const isActive = state.activeCategories.has(cat);
const chip = document.createElement('button');
- chip.className = `filter-chip px-3 py-1.5 md:px-4 md:py-2 rounded-full text-xs md:text-sm font-medium transition border ${isActive ? 'bg-primary text-white border-primary' : 'bg-slate-50 text-slate-600 border-slate-200'}`;
+ chip.className = `filter-chip px-3 py-1.5 md:px-4 md:py-2 rounded-full text-xs md:text-sm font-medium transition border ${
+ isActive
+ ? 'bg-primary text-white border-primary shadow-sm'
+ : 'bg-slate-50 text-slate-600 border-slate-200'
+ }`;
chip.textContent = cat;
chip.dataset.category = cat;
chip.onclick = () => toggleCategory(cat);
@@ -168,4 +201,5 @@ document.getElementById('logo-title')?.addEventListener('click', () => {
// 5. 페이지 최상단으로 스크롤 (선택 사항)
window.scrollTo({ top: 0, behavior: 'smooth' });
-});
\ No newline at end of file
+});
+
diff --git a/scripts/main.js b/scripts/main.js
index 6b4e649..e1b68bf 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -169,12 +169,27 @@ window.toggleSelectAll = (isChecked) => {
};
/** 선택 리셋 */
+// [수정] 기존 resetSelection을 모달 오픈으로 변경
window.resetSelection = () => {
- if (!confirm('선택된 내역을 모두 초기화할까요?')) return;
+ const modal = document.getElementById('selection-reset-modal');
+ modal.classList.remove('hidden');
+ modal.classList.add('flex');
+};
+
+// [추가] 모달 닫기
+window.closeSelectionResetModal = () => {
+ const modal = document.getElementById('selection-reset-modal');
+ modal.classList.add('hidden');
+ modal.classList.remove('flex');
+};
+
+// [추가] 실제 초기화 실행 (기존 로직 그대로)
+window.confirmSelectionReset = () => {
state.selectedIds.clear();
saveSelection(); // 스토리지 동기화
updateSummary();
renderProducts(state.currentPage);
+ window.closeSelectionResetModal();
};
/** 선택 토글 시 스토리지 저장 추가 */
diff --git a/scripts/productList.js b/scripts/productList.js
index d264ef1..49ec6cb 100644
--- a/scripts/productList.js
+++ b/scripts/productList.js
@@ -1,7 +1,19 @@
/** 상품 그리드·페이지네이션 렌더링 */
-import { state } from './state.js';
+import { state, saveSelection } from './state.js';
import { ITEMS_PER_PAGE, STATUS_META, STATUS_COLOR, PRODUCT_CONDITIONS } from './config.js';
import { updateSummary } from './main.js';
+import { openModal } from './modal.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');
@@ -59,11 +71,9 @@ export function renderProducts(page = 1) {
grid.innerHTML = '';
pagedProducts.forEach((product) => {
const isSold = STATUS_META[product.status]?.soldOut === true;
- grid.insertAdjacentHTML(
- 'beforeend',
- `
+ grid.insertAdjacentHTML('beforeend', `
-
+
@@ -80,46 +90,58 @@ export function renderProducts(page = 1) {
${product.description}
- `,
- );
+ `);
});
} else {
- // 테이블 렌더링 (이전과 동일, 가격 포함)
+ // [테이블 렌더링: 스타일 & 체크박스 제어 추가]
tableBody.innerHTML = pagedProducts
.map((product) => {
- const isSelectable = STATUS_META[product.status]?.selectable !== false;
+ const meta = STATUS_META[product.status];
+ const isSold = meta?.soldOut === true;
+ const isSelectable = meta?.selectable !== false;
+
+ // 1. 상태(Condition) 설정 로직
const conditionKey = product.specs.condition;
const conditionConfig = PRODUCT_CONDITIONS[conditionKey];
-
let conditionDisplay = '';
let conditionClass = 'text-slate-500';
if (conditionConfig) {
- // 1. 정의된 Key인 경우 (추천 방식)
conditionDisplay = conditionConfig.label;
conditionClass = conditionConfig.color;
} else if (conditionKey && conditionKey.trim() !== '') {
- // 2. 정의되지 않았지만 텍스트가 있는 경우 (기존 수기 입력 데이터)
conditionDisplay = conditionKey;
} else {
- // 3. 데이터가 비어있는 경우
conditionDisplay = '상세 설명 참고 ℹ️';
conditionClass = 'text-indigo-500 italic';
}
+ // 2. 행 전체 스타일 및 제목 스타일
+ const rowClass = isSold ? 'opacity-50 grayscale-[0.5]' : '';
+ const titleClass = isSold ? 'line-through text-slate-400' : 'text-slate-900 dark:text-white';
+
return `
-
- |
-
- |
- ${product.title} |
- ${conditionDisplay} |
- ₩${product.price.toLocaleString()} |
-
- ${product.status}
- |
-
`;
- })
+
+ |
+
+ |
+ ${product.title} |
+ ${conditionDisplay} |
+
+ ₩${product.price.toLocaleString()}
+ |
+
+ ${product.status}
+ |
+
`;
+ })
.join('');
}
diff --git a/scripts/state.js b/scripts/state.js
index 4991fa8..e658806 100644
--- a/scripts/state.js
+++ b/scripts/state.js
@@ -4,20 +4,19 @@ import { STATUS_META } from './config.js';
export const productsData = products;
-// 초기 로드 시 세션 스토리지에서 선택 내역 불러오기
-const savedIds = JSON.parse(sessionStorage.getItem('selectedProductIds') || '[]');
-
export const state = {
currentPage: 1,
activeCategories: new Set(['All']),
- visibleProducts: [...products],
+ visibleProducts: [], // 초기값은 빈 배열로 두고 main.js나 filter.js에서 첫 계산
searchKeyword: '',
- viewMode: 'grid', // 기본값
- selectedIds: new Set(savedIds),
- activeStatuses: new Set(
+ viewMode: 'grid',
+ selectedIds: new Set(JSON.parse(sessionStorage.getItem('selectedProductIds') || '[]')),
+
+ // visible이 true인 상태만 초기 활성 필터로 저장
+activeStatuses: new Set(
Object.entries(STATUS_META)
- .filter(([_, meta]) => meta.defaultVisible)
- .map(([status]) => status),
+ .filter(([_, meta]) => meta.isSystemVisible && meta.isDefaultActive)
+ .map(([status]) => status)
),
};