Convert Switch DB for used sales
This commit is contained in:
265
script/nsw.js
265
script/nsw.js
@@ -1,4 +1,6 @@
|
||||
import NSW_DB from '../db/nsw.db.js';
|
||||
import NSW_DB from '../db/nsw.resale.db.js';
|
||||
|
||||
const SHOW_SOLD_BY_DEFAULT = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const gameList = document.getElementById('gameList');
|
||||
@@ -9,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 언어별 UI 텍스트
|
||||
const uiTexts = {
|
||||
ko: {
|
||||
title: 'Switch DB',
|
||||
title: 'Switch 중고 판매 목록',
|
||||
languageSelect: '언어 선택',
|
||||
languageDescription: '선택한 언어로 표시됩니다',
|
||||
korean: '한국어',
|
||||
@@ -18,18 +20,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loading: '로딩중...',
|
||||
tableHeaders: {
|
||||
title: '제목',
|
||||
info: '정보',
|
||||
status: '상태',
|
||||
role: '역할',
|
||||
location: '지역'
|
||||
info: '판매가',
|
||||
status: '판매 상태',
|
||||
role: '가격 범위',
|
||||
location: '지역',
|
||||
},
|
||||
sortOptions: {
|
||||
sortByNoDesc: '순번 최신순',
|
||||
sortByNo: '순번 과거순',
|
||||
sortByDateDesc: '발매일 최신순',
|
||||
sortByDate: '발매일 과거순',
|
||||
sortByPurchaseDateDesc: '구매일 최신순',
|
||||
sortByPurchaseDate: '구매일 과거순',
|
||||
sortByPriceDesc: '판매가 높은순',
|
||||
sortByPrice: '판매가 낮은순',
|
||||
sortByRandom: '무작위',
|
||||
},
|
||||
filter: {
|
||||
@@ -39,7 +41,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
title: 'ゲーム一覧',
|
||||
title: '中古販売リスト',
|
||||
languageSelect: '言語選択',
|
||||
languageDescription: '選択した言語で表示されます',
|
||||
korean: '韓国語',
|
||||
@@ -48,18 +50,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loading: '読み込み中...',
|
||||
tableHeaders: {
|
||||
title: 'タイトル',
|
||||
info: '情報',
|
||||
status: '状態',
|
||||
role: '役割',
|
||||
location: '地域'
|
||||
info: '販売価格',
|
||||
status: '販売状態',
|
||||
role: '価格帯',
|
||||
location: '地域',
|
||||
},
|
||||
sortOptions: {
|
||||
sortByNoDesc: '番号降順',
|
||||
sortByNo: '番号昇順',
|
||||
sortByDateDesc: '発売日降順',
|
||||
sortByDate: '発売日昇順',
|
||||
sortByPurchaseDateDesc: '購入日降順',
|
||||
sortByPurchaseDate: '購入日昇順',
|
||||
sortByPriceDesc: '販売価格降順',
|
||||
sortByPrice: '販売価格昇順',
|
||||
sortByRandom: 'ランダム',
|
||||
},
|
||||
filter: {
|
||||
@@ -79,20 +81,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
country: [],
|
||||
cero: [],
|
||||
},
|
||||
searchText: '',
|
||||
sortBy: 'sortByNoDesc', // 기본 정렬 옵션
|
||||
};
|
||||
|
||||
// 정렬 옵션 정의
|
||||
const sortOptions = [
|
||||
{ name: '순번 최신순', value: 'sortByNoDesc' },
|
||||
{ name: '순번 과거순', value: 'sortByNo' },
|
||||
{ name: '발매일 최신순', value: 'sortByDateDesc' },
|
||||
{ name: '발매일 과거순', value: 'sortByDate' },
|
||||
{ name: '구매일 최신순', value: 'sortByPurchaseDateDesc' },
|
||||
{ name: '구매일 과거순', value: 'sortByPurchaseDate' },
|
||||
{ name: '무작위', value: 'sortByRandom' },
|
||||
];
|
||||
|
||||
// UI 텍스트 업데이트 함수
|
||||
function updateUITexts() {
|
||||
const texts = uiTexts[filterState.language];
|
||||
@@ -128,11 +120,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
languageRadios.forEach(radio => {
|
||||
radio.addEventListener('change', e => {
|
||||
filterState.language = e.target.value;
|
||||
localStorage.setItem('language', filterState.language);
|
||||
updateUITexts();
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
localStorage.setItem('language', filterState.language);
|
||||
|
||||
// 언어 변환 함수
|
||||
function convertLanguage(language) {
|
||||
if (!language) return '';
|
||||
@@ -581,27 +576,91 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
function formatGameData(game) {
|
||||
const suggestedPrice = game.sale?.suggestedPrice;
|
||||
const priceRange = game.sale?.priceRange;
|
||||
|
||||
return {
|
||||
...game,
|
||||
formattedTitle: filterState.language === 'ko' ? game.koTitle || game.title : game.title,
|
||||
formattedReleaseDate: convertReleaseDate(game.release),
|
||||
formattedMaker: convertMaker(game.maker),
|
||||
formattedLanguage: convertLanguage(game.language),
|
||||
formattedTags: convertTags(game.tags),
|
||||
formattedCountry: convertCountry(game.country),
|
||||
formattedSuggestedPrice: formatKRW(suggestedPrice),
|
||||
formattedPriceRange:
|
||||
priceRange?.min && priceRange?.max
|
||||
? `${formatKRW(priceRange.min)} ~ ${formatKRW(priceRange.max)}`
|
||||
: '',
|
||||
formattedPricingBasis: formatPricingBasis(game.sale?.pricingBasis),
|
||||
formattedConfidenceDescription: formatConfidenceDescription(game.sale?.confidence),
|
||||
formattedSaleStatus: formatSaleStatus(game.status),
|
||||
};
|
||||
}
|
||||
|
||||
function getStatusClass(status) {
|
||||
function formatKRW(value) {
|
||||
if (typeof value !== 'number') return '';
|
||||
return `${value.toLocaleString('ko-KR')}원`;
|
||||
}
|
||||
|
||||
function formatSaleStatus(status) {
|
||||
const labels = {
|
||||
ko: {
|
||||
available: '판매 가능',
|
||||
sold: '판매완료',
|
||||
},
|
||||
ja: {
|
||||
available: '販売可',
|
||||
sold: '販売済み',
|
||||
},
|
||||
};
|
||||
|
||||
return labels[filterState.language][status] || status;
|
||||
}
|
||||
|
||||
function formatPricingBasis(pricingBasis) {
|
||||
const labels = {
|
||||
ko: {
|
||||
KR_USED_REFERENCE_ESTIMATE: '국내 중고 시세 참고',
|
||||
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '일본 중고 시세 환산',
|
||||
MANUAL: '직접 입력가',
|
||||
SOLD_HISTORY: '판매 이력 기준',
|
||||
},
|
||||
ja: {
|
||||
KR_USED_REFERENCE_ESTIMATE: '韓国中古相場参考',
|
||||
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '日本中古相場換算',
|
||||
MANUAL: '手入力価格',
|
||||
SOLD_HISTORY: '販売履歴基準',
|
||||
},
|
||||
};
|
||||
|
||||
return labels[filterState.language][pricingBasis] || '';
|
||||
}
|
||||
|
||||
function formatConfidenceDescription(confidence) {
|
||||
const descriptions = {
|
||||
ko: {
|
||||
high: '최근 거래가가 안정적인 편이라 표시가에 가까운 거래를 기대할 수 있습니다.',
|
||||
medium: '중고 시세를 참고한 합리적인 기준가이며 상태에 따라 소폭 조정될 수 있습니다.',
|
||||
'medium-low': '판본, 언어, 구성품에 따라 가격 차이가 있어 확인 후 조정 가능합니다.',
|
||||
low: '거래 사례가 적은 타이틀이라 상태 확인 후 가격 협의 여지가 있습니다.',
|
||||
},
|
||||
ja: {
|
||||
high: '最近の取引価格が安定しており、表示価格に近い取引が期待できます。',
|
||||
medium: '中古相場を参考にした基準価格で、状態により多少調整できます。',
|
||||
'medium-low': '版、言語、付属品により価格差があるため、確認後に調整できます。',
|
||||
low: '取引例が少ないタイトルのため、状態確認後に価格相談できます。',
|
||||
},
|
||||
};
|
||||
|
||||
return descriptions[filterState.language][confidence] || '';
|
||||
}
|
||||
|
||||
function getSaleStatusClass(status) {
|
||||
switch (status) {
|
||||
case 'package':
|
||||
return 'bg-green-100 text-green-800';
|
||||
case 'download':
|
||||
return 'bg-yellow-100 text-yellow-800';
|
||||
case 'expansion':
|
||||
return 'bg-blue-100 text-blue-800';
|
||||
case 'sold':
|
||||
return 'bg-red-100 text-red-800';
|
||||
default:
|
||||
return '';
|
||||
return 'bg-green-100 text-green-800';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,31 +686,31 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<img class="h-10 w-10 rounded-xl object-cover" src="${game.thumbnail}" alt="" />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="font-medium text-gray-900 ${
|
||||
game.status === 'sold' ? 'text-red-600 line-through' : ''
|
||||
}">
|
||||
<div class="font-medium text-gray-900">
|
||||
${game.no}. ${game.formattedTitle}
|
||||
</div>
|
||||
<div class="text-gray-500">${game.formattedReleaseDate}</div>
|
||||
<div class="mt-1 max-w-lg text-xs leading-5 text-gray-500">
|
||||
${game.formattedConfidenceDescription}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="hidden w-3/12 px-3 py-4 text-sm text-gray-500 lg:table-cell">
|
||||
<div class="text-gray-900">${game.formattedMaker}</div>
|
||||
<div class="text-gray-500">${game.formattedLanguage}</div>
|
||||
<td class="hidden w-32 px-3 py-4 text-sm text-gray-500 lg:table-cell">
|
||||
<div class="whitespace-nowrap font-semibold text-gray-900">
|
||||
${game.formattedSuggestedPrice || '-'}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-gray-400">${game.formattedPricingBasis}</div>
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 mx-auto">
|
||||
<div class="flex justify-center rounded-full px-2 text-xs font-semibold leading-5 ${getStatusClass(
|
||||
<td class="w-28 px-3 py-4 text-sm text-gray-500 mx-auto">
|
||||
<div class="flex justify-center whitespace-nowrap rounded-full px-2 text-xs font-semibold leading-5 ${getSaleStatusClass(
|
||||
game.status,
|
||||
)}">
|
||||
${game.status}
|
||||
</div>
|
||||
<div class="text-xs mt-1 text-center text-gray-300 hidden">
|
||||
${game?.purchaseInformation?.date || ''}
|
||||
${game.formattedSaleStatus}
|
||||
</div>
|
||||
</td>
|
||||
<td class="w-3/12 hidden px-3 py-4 text-sm text-gray-500 lg:table-cell">
|
||||
${game.formattedTags}
|
||||
<td class="hidden w-56 px-3 py-4 text-sm text-gray-500 lg:table-cell">
|
||||
<div class="whitespace-nowrap text-gray-900">${game.formattedPriceRange || '-'}</div>
|
||||
<div class="mt-1 line-clamp-2 text-xs text-gray-400">${game.sale?.checkedAt || ''}</div>
|
||||
</td>
|
||||
<td class="hidden py-4 pl-3 pr-4 text-right text-sm font-medium sm:table-cell">
|
||||
<div class="${getCountryClass(game.country)}">
|
||||
@@ -677,6 +736,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
country: [],
|
||||
cero: [],
|
||||
};
|
||||
filterState.searchText = '';
|
||||
document.getElementById('search-input').value = '';
|
||||
|
||||
// 게임 목록 다시 렌더링
|
||||
renderGames();
|
||||
@@ -687,6 +748,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// 필터 체크박스 이벤트 리스너 설정
|
||||
function setupFilterListeners() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
const resetSearch = document.getElementById('reset-search');
|
||||
|
||||
searchForm.addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
filterState.searchText = searchInput.value.trim().toLowerCase();
|
||||
renderGames();
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', event => {
|
||||
filterState.searchText = event.target.value.trim().toLowerCase();
|
||||
renderGames();
|
||||
});
|
||||
|
||||
resetSearch.addEventListener('click', () => {
|
||||
filterState.searchText = '';
|
||||
searchInput.value = '';
|
||||
renderGames();
|
||||
});
|
||||
|
||||
// 언어 필터
|
||||
document.querySelectorAll('input[name="language-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
@@ -695,7 +777,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 상태 필터
|
||||
// 판매 상태 필터
|
||||
document.querySelectorAll('input[name="status-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateFilters('status');
|
||||
@@ -729,48 +811,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 필터링 함수
|
||||
function filterGames(games) {
|
||||
return games.filter(game => {
|
||||
if (filterState.searchText) {
|
||||
const searchableText = [
|
||||
game.title,
|
||||
game.koTitle,
|
||||
game.maker,
|
||||
game.tags,
|
||||
game.country,
|
||||
game.status,
|
||||
String(game.sale?.suggestedPrice || ''),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
if (!searchableText.includes(filterState.searchText)) return false;
|
||||
}
|
||||
|
||||
// 언어 필터
|
||||
if (filterState.filters.language.length > 0) {
|
||||
const hasKorean = game.language.includes('韓国語');
|
||||
const hasKorean = game.language?.includes('韓国語');
|
||||
const hasKoreanFilter = filterState.filters.language.includes('koreanSupport');
|
||||
const hasNotSupportedFilter = filterState.filters.language.includes('koreanNotSupport');
|
||||
|
||||
// 한국어 지원과 미지원이 모두 체크된 경우 모든 게임 표시
|
||||
if (hasKoreanFilter && hasNotSupportedFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 한국어 지원만 체크된 경우 한국어 지원 게임만 표시
|
||||
if (hasKoreanFilter && !hasNotSupportedFilter) {
|
||||
return hasKorean;
|
||||
if (!hasKorean) return false;
|
||||
}
|
||||
|
||||
// 한국어 미지원만 체크된 경우 한국어 미지원 게임만 표시
|
||||
if (!hasKoreanFilter && hasNotSupportedFilter) {
|
||||
return !hasKorean;
|
||||
if (hasKorean) return false;
|
||||
}
|
||||
|
||||
// 아무것도 체크되지 않은 경우 필터링하지 않음
|
||||
return true;
|
||||
}
|
||||
|
||||
// 상태 필터
|
||||
if (filterState.filters.status.length > 0) {
|
||||
const hasExtensionFilter = filterState.filters.status.includes('extension');
|
||||
const hasOtherStatusFilters =
|
||||
filterState.filters.status.filter(status => status !== 'extension').length > 0;
|
||||
if (
|
||||
!SHOW_SOLD_BY_DEFAULT &&
|
||||
filterState.filters.status.length === 0 &&
|
||||
game.status === 'sold'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extension 필터가 체크된 경우
|
||||
if (hasExtensionFilter) {
|
||||
// extension 값이 null이 아닌 게임만 표시
|
||||
if (game.extension === null) return false;
|
||||
}
|
||||
|
||||
// 다른 상태 필터가 체크된 경우
|
||||
if (hasOtherStatusFilters) {
|
||||
const otherStatuses = filterState.filters.status.filter(status => status !== 'extension');
|
||||
if (!otherStatuses.includes(game.status)) return false;
|
||||
}
|
||||
if (
|
||||
filterState.filters.status.length > 0 &&
|
||||
!filterState.filters.status.includes(game.status)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 국가 필터
|
||||
@@ -811,25 +898,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const dateDiff = new Date(aDate) - new Date(bDate);
|
||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
||||
});
|
||||
case 'sortByPurchaseDateDesc':
|
||||
case 'sortByPriceDesc':
|
||||
return [...games].sort((a, b) => {
|
||||
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
|
||||
return !a.purchaseInformation?.date ? 1 : -1;
|
||||
}
|
||||
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
||||
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
||||
const dateDiff = new Date(bDate) - new Date(aDate);
|
||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
||||
const priceDiff = (b.sale?.suggestedPrice || 0) - (a.sale?.suggestedPrice || 0);
|
||||
return priceDiff === 0 ? b.no - a.no : priceDiff;
|
||||
});
|
||||
case 'sortByPurchaseDate':
|
||||
case 'sortByPrice':
|
||||
return [...games].sort((a, b) => {
|
||||
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
|
||||
return !a.purchaseInformation?.date ? 1 : -1;
|
||||
}
|
||||
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
||||
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
||||
const dateDiff = new Date(aDate) - new Date(bDate);
|
||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
||||
const priceDiff = (a.sale?.suggestedPrice || 0) - (b.sale?.suggestedPrice || 0);
|
||||
return priceDiff === 0 ? b.no - a.no : priceDiff;
|
||||
});
|
||||
case 'sortByRandom':
|
||||
return [...games].sort(() => Math.random() - 0.5);
|
||||
|
||||
Reference in New Issue
Block a user