[260210]
- 테이블 모드 추가 - 상품 선택하여 엑셀로 견적 내기 기능 추가. - 데이터는 세션 스토리지에 저장
This commit is contained in:
65
index.html
65
index.html
@@ -137,13 +137,58 @@
|
||||
<span class="text-slate-400 dark:text-slate-500 text-xs font-bold uppercase tracking-widest">Total Results</span>
|
||||
<span id="total-count" class="px-2 py-0.5 rounded bg-primary/10 text-primary text-sm font-bold">0</span>
|
||||
</div>
|
||||
<div id="selection-summary" class="hidden flex items-center gap-3 bg-primary/10 px-3 py-1.5 rounded-full border border-primary/20">
|
||||
<span class="text-[11px] font-bold text-primary">
|
||||
<span id="selected-count">0</span>
|
||||
items
|
||||
</span>
|
||||
<div class="w-px h-3 bg-primary/30"></div>
|
||||
<span class="text-[11px] font-bold text-primary" id="selected-total-price">₩0</span>
|
||||
|
||||
<div class="flex items-center gap-1 ml-2">
|
||||
<button onclick="window.exportToExcel()" class="flex items-center gap-1 bg-primary text-white text-[10px] px-2 py-1 rounded-md hover:bg-primary-dark transition-colors">
|
||||
<span class="material-symbols-outlined !text-[14px]">download</span>
|
||||
Export
|
||||
</button>
|
||||
<button onclick="window.resetSelection()" class="flex items-center gap-1 bg-white dark:bg-slate-800 text-slate-500 text-[10px] px-2 py-1 rounded-md border border-slate-200 dark:border-slate-700 hover:bg-slate-50 transition-colors">
|
||||
<span class="material-symbols-outlined !text-[14px]">restart_alt</span>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex bg-slate-100 dark:bg-slate-800 p-1 rounded-lg">
|
||||
<button id="view-grid" class="p-1.5 rounded-md bg-white dark:bg-slate-700 shadow-sm text-primary transition-all">
|
||||
<span class="material-symbols-outlined !text-[20px]">grid_view</span>
|
||||
</button>
|
||||
<button id="view-table" class="p-1.5 rounded-md text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-all">
|
||||
<span class="material-symbols-outlined !text-[20px]">format_list_bulleted</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Table -->
|
||||
|
||||
<!-- Product Grid -->
|
||||
<div class="px-6 md:px-40 py-10">
|
||||
<div id="product-grid" class="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-8">
|
||||
<!-- Product Item -->
|
||||
</div>
|
||||
<div id="product-table-wrapper" class="hidden overflow-x-auto bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-slate-50 dark:bg-slate-800/50 border-b border-slate-200 dark:border-slate-700 text-slate-500 text-[11px] font-bold uppercase">
|
||||
<th class="py-4 px-4 w-12 text-center">
|
||||
<input type="checkbox" id="select-all-current" class="rounded border-slate-300" onchange="window.toggleSelectAll(this.checked)" />
|
||||
</th>
|
||||
<th class="py-4 px-4 text-slate-800 dark:text-slate-200">상품명</th>
|
||||
<th class="py-4 px-4">카테고리</th>
|
||||
<th class="py-4 px-4 text-right">가격</th>
|
||||
<th class="py-4 px-4 text-center">상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="product-table-body" class="text-sm divide-y divide-slate-100 dark:divide-slate-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<div class="px-6 md:px-40 py-12">
|
||||
@@ -225,18 +270,18 @@
|
||||
</div>
|
||||
<!-- Footer / CTA: 항상 하단 고정, 버튼이 설명 위로 겹치지 않음 -->
|
||||
<div class="flex flex-col gap-2 shrink-0 pt-4 pb-6 px-4 sm:px-6 md:px-8 lg:px-10 border-t border-gray-100 dark:border-gray-800 bg-white dark:bg-background-dark">
|
||||
<button id="copy-link-btn" class="w-full flex items-center justify-center gap-2 bg-slate-900 dark:bg-white dark:text-slate-900 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg">
|
||||
<span class="material-symbols-outlined">link</span>
|
||||
<span id="copy-btn-text">상품 링크 복사하기</span>
|
||||
</button>
|
||||
<button id="copy-link-btn" class="w-full flex items-center justify-center gap-2 bg-slate-900 dark:bg-white dark:text-slate-900 text-white font-bold py-4 px-6 rounded-xl transition-all shadow-lg">
|
||||
<span class="material-symbols-outlined">link</span>
|
||||
<span id="copy-btn-text">상품 링크 복사하기</span>
|
||||
</button>
|
||||
|
||||
<a href="https://open.kakao.com/o/sPZ3Cnfi" target="_blank" class="w-full flex items-center justify-center gap-2 bg-[#fae100] text-[#3c1e1e] font-bold py-4 px-6 rounded-xl transition-all shadow-lg hover:bg-[#f7d600]">
|
||||
<span class="material-symbols-outlined">chat_bubble</span>
|
||||
<span>오픈카톡으로 문의하기</span>
|
||||
</a>
|
||||
<a href="https://open.kakao.com/o/sPZ3Cnfi" target="_blank" class="w-full flex items-center justify-center gap-2 bg-[#fae100] text-[#3c1e1e] font-bold py-4 px-6 rounded-xl transition-all shadow-lg hover:bg-[#f7d600]">
|
||||
<span class="material-symbols-outlined">chat_bubble</span>
|
||||
<span>오픈카톡으로 문의하기</span>
|
||||
</a>
|
||||
|
||||
<p class="text-center text-xs text-gray-400 dark:text-gray-500 mt-2 font-medium">링크를 복사해 문의 시 전달해주세요.</p>
|
||||
</div>
|
||||
<p class="text-center text-xs text-gray-400 dark:text-gray-500 mt-2 font-medium">링크를 복사해 문의 시 전달해주세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
156
scripts/main.js
156
scripts/main.js
@@ -1,11 +1,12 @@
|
||||
/** 진입점: 이벤트 바인딩·초기 렌더·URL 모달 처리 */
|
||||
import { state, productsData } from './state.js';
|
||||
import { state, productsData, saveSelection } from './state.js';
|
||||
import {
|
||||
applyFilters,
|
||||
renderStatusChips,
|
||||
renderCategoryChips,
|
||||
bindCategoryFilter,
|
||||
} from './filters.js';
|
||||
import { ITEMS_PER_PAGE, STATUS_META } from './config.js';
|
||||
import { renderProducts, changePage } from './productList.js';
|
||||
import { openModal, closeModal } from './modal.js';
|
||||
import { scrollToImage } from './carousel.js';
|
||||
@@ -18,6 +19,58 @@ window.closeModal = closeModal;
|
||||
window.changePage = changePage;
|
||||
window.scrollToImage = scrollToImage;
|
||||
|
||||
// 뷰 전환 이벤트
|
||||
document.getElementById('view-grid').onclick = () => {
|
||||
state.viewMode = 'grid';
|
||||
updateViewButtons();
|
||||
renderProducts(state.currentPage);
|
||||
};
|
||||
|
||||
document.getElementById('view-table').onclick = () => {
|
||||
state.viewMode = 'table';
|
||||
updateViewButtons();
|
||||
renderProducts(state.currentPage);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// [전역 함수] 체크박스 토글 및 합계 업데이트
|
||||
window.toggleSelectItem = (id) => {
|
||||
if (state.selectedIds.has(id)) state.selectedIds.delete(id);
|
||||
else state.selectedIds.add(id);
|
||||
|
||||
updateSummary();
|
||||
};
|
||||
|
||||
export function updateSummary() {
|
||||
const summary = document.getElementById('selection-summary');
|
||||
const countEl = document.getElementById('selected-count');
|
||||
const priceEl = document.getElementById('selected-total-price');
|
||||
|
||||
// 테이블 모드이면서 선택된 항목이 있을 때만 노출
|
||||
if (state.viewMode === 'table' && state.selectedIds.size > 0) {
|
||||
summary.classList.remove('hidden');
|
||||
summary.classList.add('flex');
|
||||
|
||||
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 {
|
||||
summary.classList.add('hidden');
|
||||
summary.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 입력
|
||||
const searchInput = document.getElementById('search-input');
|
||||
if (searchInput) {
|
||||
@@ -98,3 +151,104 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const newId = Math.random().toString(36).substring(2, 10);
|
||||
console.log(`%c[NUMBER]: ${newId}`, 'color: #137fec; font-weight: bold; border: 1px solid #137fec; padding: 2px 5px; border-radius: 4px;');
|
||||
|
||||
/** 현재 페이지의 '선택 가능한' 항목들만 선택/해제 */
|
||||
window.toggleSelectAll = (isChecked) => {
|
||||
const startIndex = (state.currentPage - 1) * ITEMS_PER_PAGE;
|
||||
const currentPageProducts = state.visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE);
|
||||
|
||||
currentPageProducts.forEach(p => {
|
||||
const isSelectable = STATUS_META[p.status]?.selectable !== false;
|
||||
if (isSelectable) {
|
||||
if (isChecked) state.selectedIds.add(p.id);
|
||||
else state.selectedIds.delete(p.id);
|
||||
}
|
||||
});
|
||||
|
||||
saveSelection(); // 변경사항 저장
|
||||
updateSummary();
|
||||
renderProducts(state.currentPage);
|
||||
};
|
||||
|
||||
/** 선택 리셋 */
|
||||
window.resetSelection = () => {
|
||||
if (!confirm("선택된 내역을 모두 초기화할까요?")) return;
|
||||
state.selectedIds.clear();
|
||||
saveSelection(); // 스토리지 동기화
|
||||
updateSummary();
|
||||
renderProducts(state.currentPage);
|
||||
};
|
||||
|
||||
/** 선택 토글 시 스토리지 저장 추가 */
|
||||
window.toggleSelectItem = (id) => {
|
||||
const product = productsData.find(p => p.id === id);
|
||||
if (!product || STATUS_META[product.status]?.selectable === false) return;
|
||||
|
||||
if (state.selectedIds.has(id)) state.selectedIds.delete(id);
|
||||
else state.selectedIds.add(id);
|
||||
|
||||
saveSelection(); // 변경사항 저장
|
||||
updateSummary();
|
||||
};
|
||||
|
||||
/** 선택된 항목들을 CSV 파일로 내보내기 */
|
||||
window.exportToExcel = () => {
|
||||
if (state.selectedIds.size === 0) {
|
||||
alert("내보낼 상품을 선택해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 선택된 데이터 추출 및 계산
|
||||
let totalCount = 0;
|
||||
let totalPrice = 0;
|
||||
|
||||
const rows = Array.from(state.selectedIds).map(id => {
|
||||
const p = productsData.find(item => item.id === id);
|
||||
if (p) {
|
||||
totalCount += 1;
|
||||
totalPrice += p.price;
|
||||
return [
|
||||
p.id,
|
||||
`"${p.title.replace(/"/g, '""')}"`,
|
||||
p.category,
|
||||
p.price,
|
||||
p.status,
|
||||
`"${p.description.replace(/"/g, '""')}"`
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}).filter(row => row !== null);
|
||||
|
||||
// 2. 헤더 및 푸터(합계) 설정
|
||||
const headers = ["상품 ID", "상품명", "카테고리", "가격", "상태", "상세설명"];
|
||||
|
||||
// 영수증 느낌을 위한 하단 합계 줄
|
||||
const footerEmpty = ["", "", "", "", "", ""]; // 빈 줄
|
||||
const footerTotal = [
|
||||
"TOTAL",
|
||||
`"총 ${totalCount}건의 항목"`,
|
||||
"",
|
||||
totalPrice,
|
||||
"",
|
||||
`"발행일: ${new Date().toLocaleString()}"`
|
||||
];
|
||||
|
||||
// 3. CSV 포맷 생성 (한글 깨짐 방지 BOM 추가)
|
||||
const csvContent = "\uFEFF" + [
|
||||
headers.join(","),
|
||||
...rows.map(row => row.join(",")),
|
||||
footerEmpty.join(","), // 간격 조절용 빈 줄
|
||||
footerTotal.join(",") // 합계 라인
|
||||
].join("\n");
|
||||
|
||||
// 4. 다운로드 실행
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", `inventory_receipt_${timestamp}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
@@ -1,63 +1,100 @@
|
||||
/** 상품 그리드·페이지네이션 렌더링 */
|
||||
import { state } from './state.js';
|
||||
import { ITEMS_PER_PAGE, STATUS_META } from './config.js';
|
||||
import { ITEMS_PER_PAGE, STATUS_META, STATUS_COLOR } from './config.js';
|
||||
import { updateSummary } from './main.js';
|
||||
|
||||
export function renderProducts(page = 1) {
|
||||
const grid = document.getElementById('product-grid');
|
||||
const tableWrapper = document.getElementById('product-table-wrapper');
|
||||
const tableBody = document.getElementById('product-table-body');
|
||||
const summaryBar = document.getElementById('selection-summary');
|
||||
const paginationContainer = document.getElementById('pagination');
|
||||
if (!grid) return;
|
||||
|
||||
// 1. 결과가 0개인 경우 (안내 텍스트만 출력)
|
||||
if (!grid || !tableWrapper) return;
|
||||
|
||||
// 1. 결과가 0개인 경우 (그리드/테이블 공통 안내)
|
||||
if (state.visibleProducts.length === 0) {
|
||||
grid.classList.remove('grid'); // 중앙 정렬을 위해 그리드 해제
|
||||
grid.classList.remove('grid', 'hidden');
|
||||
grid.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-center py-20 w-full text-center">
|
||||
<span class="material-symbols-outlined text-6xl text-slate-300 dark:text-slate-700 mb-4">
|
||||
search_off
|
||||
</span>
|
||||
<span class="material-symbols-outlined text-6xl text-slate-300 dark:text-slate-700 mb-4">search_off</span>
|
||||
<h3 class="text-lg font-bold text-slate-900 dark:text-white mb-2">검색 결과가 없습니다</h3>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm">
|
||||
입력하신 검색어나 선택한 필터를 확인해 주세요.
|
||||
</p>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm">입력하신 검색어나 선택한 필터를 확인해 주세요.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
tableWrapper.classList.add('hidden');
|
||||
if (paginationContainer) paginationContainer.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 결과가 있을 경우 (그리드 복구 및 초기화)
|
||||
grid.classList.add('grid');
|
||||
grid.innerHTML = '';
|
||||
// 2. 뷰 모드에 따른 컨테이너 노출 설정
|
||||
if (state.viewMode === 'grid') {
|
||||
grid.classList.remove('hidden');
|
||||
grid.classList.add('grid');
|
||||
tableWrapper.classList.add('hidden');
|
||||
summaryBar.classList.add('hidden');
|
||||
} else {
|
||||
grid.classList.add('hidden');
|
||||
tableWrapper.classList.remove('hidden');
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
// 3. 현재 페이지 데이터 슬라이싱
|
||||
const startIndex = (page - 1) * ITEMS_PER_PAGE;
|
||||
const pagedProducts = state.visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE);
|
||||
|
||||
// 상품 카드 생성 로직
|
||||
pagedProducts.forEach((product) => {
|
||||
const isSold = STATUS_META[product.status]?.soldOut === true;
|
||||
const cardHtml = `
|
||||
<div class="group flex flex-col gap-4 cursor-pointer" onclick="openModal('${product.id}')">
|
||||
<div class="relative w-full aspect-[4/5] bg-slate-50 dark:bg-slate-800 rounded-xl overflow-hidden shadow-sm group-hover:shadow-md transition-shadow">
|
||||
<div class="w-full h-full bg-center bg-no-repeat bg-cover transform ${isSold ? 'grayscale opacity-80' : 'group-hover:scale-105'} transition-transform duration-500"
|
||||
style="background-image: url('${product.images[0]}')"></div>
|
||||
<div class="absolute top-3 left-3">
|
||||
<span class="px-2 py-1 text-[10px] uppercase tracking-wider font-bold rounded ${isSold ? 'bg-slate-900/10 text-slate-500' : 'bg-primary/10 text-primary'} backdrop-blur-md border border-primary/20">
|
||||
${product.status}
|
||||
</span>
|
||||
if (state.viewMode === 'grid') {
|
||||
grid.innerHTML = '';
|
||||
pagedProducts.forEach((product) => {
|
||||
const isSold = STATUS_META[product.status]?.soldOut === true;
|
||||
grid.insertAdjacentHTML('beforeend', `
|
||||
<div class="group flex flex-col gap-4 cursor-pointer" onclick="openModal('${product.id}')">
|
||||
<div class="relative w-full aspect-[4/5] bg-slate-50 dark:bg-slate-800 rounded-xl overflow-hidden shadow-sm group-hover:shadow-md transition-shadow">
|
||||
<div class="w-full h-full bg-center bg-no-repeat bg-cover transform ${isSold ? 'grayscale opacity-80' : 'group-hover:scale-105'} transition-transform duration-500"
|
||||
style="background-image: url('${product.images[0]}')"></div>
|
||||
<div class="absolute top-3 left-3">
|
||||
<span class="px-2 py-1 text-[10px] uppercase tracking-wider font-bold rounded ${STATUS_COLOR[product.status]} backdrop-blur-md border">
|
||||
${product.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start gap-1">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-slate-900 dark:text-white text-base font-semibold leading-tight ${isSold ? 'line-through text-slate-400' : ''}">${product.title}</h3>
|
||||
<p class="text-slate-900 dark:text-white text-base font-bold text-nowrap">${product.currency}${product.price.toLocaleString()}</p>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm font-normal line-clamp-1">${product.description}</p>
|
||||
</div>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm font-normal line-clamp-1">${product.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.insertAdjacentHTML('beforeend', cardHtml);
|
||||
});
|
||||
`);
|
||||
});
|
||||
} else {
|
||||
// 테이블 렌더링 (이전과 동일, 가격 포함)
|
||||
tableBody.innerHTML = pagedProducts.map(product => {
|
||||
const isSelectable = STATUS_META[product.status]?.selectable !== false;
|
||||
return `
|
||||
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors cursor-pointer" onclick="if(event.target.type !== 'checkbox') openModal('${product.id}')">
|
||||
<td class="py-4 px-4 text-center" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" class="product-check rounded border-slate-300 w-4 h-4 ${isSelectable ? 'cursor-pointer' : 'opacity-20 cursor-not-allowed'}"
|
||||
${state.selectedIds.has(product.id) ? 'checked' : ''}
|
||||
${isSelectable ? '' : 'disabled'}
|
||||
onchange="window.toggleSelectItem('${product.id}')">
|
||||
</td>
|
||||
<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">${product.title}</td>
|
||||
<td class="py-4 px-4 text-slate-500 text-xs">${product.category}</td>
|
||||
<td class="py-4 px-4 text-right font-bold text-slate-900 dark:text-white">₩${product.price.toLocaleString()}</td>
|
||||
<td class="py-4 px-4 text-center">
|
||||
<span class="px-2 py-0.5 rounded text-[10px] font-bold border ${STATUS_COLOR[product.status]}">${product.status}</span>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// [중요] 전체 선택 체크박스 상태 동기화
|
||||
const selectAllCheck = document.getElementById('select-all-current');
|
||||
if (selectAllCheck) {
|
||||
const startIndex = (page - 1) * ITEMS_PER_PAGE;
|
||||
const currentSelectableItems = state.visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE).filter((p) => STATUS_META[p.status]?.selectable !== false);
|
||||
|
||||
selectAllCheck.checked = currentSelectableItems.length > 0 && currentSelectableItems.every((p) => state.selectedIds.has(p.id));
|
||||
}
|
||||
|
||||
renderPagination();
|
||||
}
|
||||
@@ -106,11 +143,11 @@ function resetAllFilters() {
|
||||
// 상태 필터 초기화 (config에서 defaultActive인 것만)
|
||||
import('./config.js').then(({ STATUS_FILTERS }) => {
|
||||
state.activeStatuses.clear();
|
||||
STATUS_FILTERS.filter(f => f.defaultActive).forEach(f => state.activeStatuses.add(f.key));
|
||||
|
||||
STATUS_FILTERS.filter((f) => f.defaultActive).forEach((f) => state.activeStatuses.add(f.key));
|
||||
|
||||
// UI 전체 갱신
|
||||
applyFilters();
|
||||
renderStatusChips();
|
||||
renderCategoryChips(productsData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,24 @@ 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],
|
||||
searchKeyword: '',
|
||||
viewMode: 'grid', // 기본값
|
||||
selectedIds: new Set(savedIds),
|
||||
activeStatuses: new Set(
|
||||
Object.entries(STATUS_META)
|
||||
.filter(([_, meta]) => meta.defaultVisible)
|
||||
.map(([status]) => status),
|
||||
),
|
||||
};
|
||||
|
||||
// 선택 내역이 변경될 때마다 세션 스토리지에 저장하는 헬퍼 함수
|
||||
export function saveSelection() {
|
||||
sessionStorage.setItem('selectedProductIds', JSON.stringify(Array.from(state.selectedIds)));
|
||||
}
|
||||
Reference in New Issue
Block a user