v2026.03.30-01 레포지토리 재연결
This commit is contained in:
533
script/amiibo.js
Normal file
533
script/amiibo.js
Normal file
@@ -0,0 +1,533 @@
|
||||
import AMIIBO_DB from '../db/amiibo.db.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const gameList = document.getElementById('gameList');
|
||||
const gameCount = document.getElementById('gameCount');
|
||||
const loading = document.getElementById('loading');
|
||||
const languageRadios = document.querySelectorAll('input[name="language"]');
|
||||
|
||||
// 언어별 UI 텍스트
|
||||
const uiTexts = {
|
||||
ko: {
|
||||
title: 'Amiibo DB',
|
||||
languageSelect: '언어 선택',
|
||||
languageDescription: '선택한 언어로 표시됩니다',
|
||||
korean: '한국어',
|
||||
japanese: '일본어',
|
||||
count: '개수',
|
||||
loading: '로딩중...',
|
||||
tableHeaders: {
|
||||
title: '제목',
|
||||
info: '정보',
|
||||
status: '상태',
|
||||
role: '역할',
|
||||
},
|
||||
sortOptions: {
|
||||
sortByNoDesc: '순번 최신순',
|
||||
sortByNo: '순번 과거순',
|
||||
sortByDateDesc: '발매일 최신순',
|
||||
sortByDate: '발매일 과거순',
|
||||
sortByPurchaseDateDesc: '구매일 최신순',
|
||||
sortByPurchaseDate: '구매일 과거순',
|
||||
sortByRandom: '무작위',
|
||||
},
|
||||
filter: {
|
||||
reset: '필터 초기화',
|
||||
koreanSupport: '한국어 지원',
|
||||
koreanNotSupport: '한국어 미지원',
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
title: 'Amiibo',
|
||||
languageSelect: '言語選択',
|
||||
languageDescription: '選択した言語で表示されます',
|
||||
korean: '韓国語',
|
||||
japanese: '日本語',
|
||||
count: '件数',
|
||||
loading: '読み込み中...',
|
||||
tableHeaders: {
|
||||
title: 'タイトル',
|
||||
info: '情報',
|
||||
status: '状態',
|
||||
role: '役割',
|
||||
},
|
||||
sortOptions: {
|
||||
sortByNoDesc: '番号降順',
|
||||
sortByNo: '番号昇順',
|
||||
sortByDateDesc: '発売日降順',
|
||||
sortByDate: '発売日昇順',
|
||||
sortByPurchaseDateDesc: '購入日降順',
|
||||
sortByPurchaseDate: '購入日昇順',
|
||||
sortByRandom: 'ランダム',
|
||||
},
|
||||
filter: {
|
||||
reset: 'フィルターリセット',
|
||||
koreanSupport: '韓国語対応',
|
||||
koreanNotSupport: '韓国語非対応',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 필터 상태 관리 객체
|
||||
const filterState = {
|
||||
language: 'ko',
|
||||
filters: {
|
||||
language: [],
|
||||
status: [],
|
||||
country: [],
|
||||
cero: [],
|
||||
},
|
||||
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];
|
||||
|
||||
// 제목 업데이트
|
||||
document.querySelector('h1').textContent = texts.title;
|
||||
|
||||
// 언어 선택 섹션 업데이트
|
||||
document.querySelector('label.text-base').textContent = texts.languageSelect;
|
||||
document.querySelector('p.text-sm').textContent = texts.languageDescription;
|
||||
document.querySelector('label[for="ko"]').textContent = texts.korean;
|
||||
document.querySelector('label[for="ja"]').textContent = texts.japanese;
|
||||
|
||||
// 테이블 헤더 업데이트
|
||||
const headers = document.querySelectorAll('th');
|
||||
headers[0].textContent = texts.tableHeaders.title;
|
||||
headers[1].textContent = texts.tableHeaders.info;
|
||||
headers[2].textContent = texts.tableHeaders.status;
|
||||
headers[3].textContent = texts.tableHeaders.role;
|
||||
|
||||
// 로딩 텍스트 업데이트
|
||||
loading.textContent = texts.loading;
|
||||
|
||||
// 필터 텍스트 업데이트
|
||||
document.getElementById('resetFilters').textContent = texts.filter.reset;
|
||||
document.querySelector('label[for="korean-support"]').textContent = texts.filter.koreanSupport;
|
||||
document.querySelector('label[for="korean-not-support"]').textContent =
|
||||
texts.filter.koreanNotSupport;
|
||||
}
|
||||
|
||||
// 언어 변경 이벤트 리스너
|
||||
languageRadios.forEach(radio => {
|
||||
radio.addEventListener('change', e => {
|
||||
filterState.language = e.target.value;
|
||||
updateUITexts();
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
// 출시일 변환 함수
|
||||
function convertReleaseDate(releaseDate) {
|
||||
if (!releaseDate) return '';
|
||||
if (filterState.language !== 'ko') return releaseDate;
|
||||
|
||||
const date = new Date(releaseDate.replace(/年|月/g, '/').replace('日', ''));
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
|
||||
return `${year}년 ${month}월 ${day}일`;
|
||||
}
|
||||
|
||||
// 국가 변환 함수
|
||||
function convertCountry(country) {
|
||||
if (!country) return '';
|
||||
|
||||
switch (country) {
|
||||
case 'JPN':
|
||||
return filterState.language === 'ko' ? '일본판' : '日本版';
|
||||
case 'KOR':
|
||||
return filterState.language === 'ko' ? '한국판' : '韓国版';
|
||||
default:
|
||||
return country;
|
||||
}
|
||||
}
|
||||
|
||||
function formatGameData(game) {
|
||||
return {
|
||||
...game,
|
||||
formattedTitle: filterState.language === 'ko' ? game.koTitle || game.title : game.title,
|
||||
formattedSeries: game.series,
|
||||
formattedReleaseDate: game.release,
|
||||
formattedStatus: game.status,
|
||||
formattedCount: game.count,
|
||||
};
|
||||
}
|
||||
|
||||
function getStatusClass(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';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getCountryClass(country) {
|
||||
return country === 'JPN'
|
||||
? 'text-red-600 hover:text-red-900'
|
||||
: 'text-indigo-600 hover:text-indigo-900';
|
||||
}
|
||||
|
||||
function createGameRow(amiibo, index) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = `cursor-pointer hover:bg-indigo-50 ${index % 2 === 0 ? '' : 'bg-gray-50'}`;
|
||||
|
||||
// 클릭 시 nsw-detail.html로 이동 (no 쿼리 포함)
|
||||
tr.addEventListener('click', () => {
|
||||
openImageModal(amiibo.image, amiibo.formattedTitle);
|
||||
});
|
||||
|
||||
tr.innerHTML = `
|
||||
<td class="py-4 pl-4 pr-3 text-sm">
|
||||
<div class="flex items-center">
|
||||
<div class="h-10 w-10 flex-shrink-0">
|
||||
<img class="h-10 w-10 rounded-xl object-cover" src="${amiibo.image}" alt="" />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="font-medium text-gray-900 ${
|
||||
amiibo.status === 'sold' ? 'text-red-600 line-through' : ''
|
||||
}">
|
||||
${amiibo.no}. ${amiibo.formattedTitle}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">${amiibo.formattedSeries}</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">${amiibo.formattedStatus.isHave ? "보유" : "미보유" }</div>
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-500 mx-auto">
|
||||
<div class="flex flex-col justify-center rounded-full px-2 text-xs font-semibold leading-5 ${getStatusClass(
|
||||
amiibo.status,
|
||||
)}">
|
||||
${amiibo.formattedStatus.isHave ? `
|
||||
<div class="text-gray-500">${amiibo.formattedStatus.isOpen ? "개봉" : "미개봉" }</div>
|
||||
<div class="text-gray-500">${amiibo.formattedStatus.isDamaged ? "박스손상" : "" }</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="text-xs mt-1 text-center text-gray-300 hidden">
|
||||
${amiibo?.formattedReleaseDate}
|
||||
</div>
|
||||
</td>
|
||||
<td class="hidden py-4 pl-3 pr-4 text-right text-sm font-medium sm:table-cell">
|
||||
<div href="#" class="${getCountryClass(amiibo.country)}">
|
||||
${amiibo.formattedCount}
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
// 필터 초기화 함수
|
||||
function resetFilters() {
|
||||
// 모든 체크박스 해제
|
||||
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
});
|
||||
|
||||
// 필터 상태 초기화
|
||||
filterState.filters = {
|
||||
language: [],
|
||||
status: [],
|
||||
country: [],
|
||||
cero: [],
|
||||
};
|
||||
|
||||
// 게임 목록 다시 렌더링
|
||||
renderGames();
|
||||
}
|
||||
|
||||
// 필터 초기화 버튼 이벤트 리스너 설정
|
||||
document.getElementById('resetFilters').addEventListener('click', resetFilters);
|
||||
|
||||
// 필터 체크박스 이벤트 리스너 설정
|
||||
function setupFilterListeners() {
|
||||
// 언어 필터
|
||||
document.querySelectorAll('input[name="language-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateFilters('language');
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
// 상태 필터
|
||||
document.querySelectorAll('input[name="status-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateFilters('status');
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
// 국가 필터
|
||||
document.querySelectorAll('input[name="country-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateFilters('country');
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
// CERO 필터
|
||||
document.querySelectorAll('input[name="cero-filter"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
updateFilters('cero');
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 필터 상태 업데이트
|
||||
function updateFilters(type) {
|
||||
const checkboxes = document.querySelectorAll(`input[name="${type}-filter"]:checked`);
|
||||
filterState.filters[type] = Array.from(checkboxes).map(checkbox => checkbox.value);
|
||||
}
|
||||
|
||||
// 필터링 함수
|
||||
function filterGames(games) {
|
||||
return games.filter(game => {
|
||||
// 언어 필터
|
||||
if (filterState.filters.language.length > 0) {
|
||||
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 (!hasKoreanFilter && hasNotSupportedFilter) {
|
||||
return !hasKorean;
|
||||
}
|
||||
|
||||
// 아무것도 체크되지 않은 경우 필터링하지 않음
|
||||
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;
|
||||
|
||||
// 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.country.length > 0 &&
|
||||
!filterState.filters.country.includes(game.country)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CERO 필터
|
||||
if (filterState.filters.cero.length > 0 && !filterState.filters.cero.includes(game.cero)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 정렬 함수
|
||||
function sortGames(games) {
|
||||
switch (filterState.sortBy) {
|
||||
case 'sortByNoDesc':
|
||||
return [...games].sort((a, b) => b.no - a.no);
|
||||
case 'sortByNo':
|
||||
return [...games].sort((a, b) => a.no - b.no);
|
||||
case 'sortByDateDesc':
|
||||
return [...games].sort((a, b) => {
|
||||
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||
const bDate = new Date(b.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||
const dateDiff = new Date(bDate) - new Date(aDate);
|
||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
||||
});
|
||||
case 'sortByDate':
|
||||
return [...games].sort((a, b) => {
|
||||
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||
const bDate = new Date(b.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||
const dateDiff = new Date(aDate) - new Date(bDate);
|
||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
||||
});
|
||||
case 'sortByPurchaseDateDesc':
|
||||
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;
|
||||
});
|
||||
case 'sortByPurchaseDate':
|
||||
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;
|
||||
});
|
||||
case 'sortByRandom':
|
||||
return [...games].sort(() => Math.random() - 0.5);
|
||||
default:
|
||||
return games;
|
||||
}
|
||||
}
|
||||
|
||||
// 정렬 UI 초기화
|
||||
function setupSortUI() {
|
||||
const sortButton = document.getElementById('sort-button');
|
||||
const sortOptions = document.getElementById('sort-options');
|
||||
const sortLabel = document.getElementById('sort-label');
|
||||
|
||||
// 정렬 버튼 클릭 이벤트
|
||||
sortButton.addEventListener('click', () => {
|
||||
sortOptions.classList.toggle('hidden');
|
||||
const isExpanded = sortOptions.classList.contains('hidden') ? 'false' : 'true';
|
||||
sortButton.setAttribute('aria-expanded', isExpanded);
|
||||
});
|
||||
|
||||
// 정렬 옵션 클릭 이벤트
|
||||
sortOptions.querySelectorAll('li').forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const value = option.getAttribute('data-value');
|
||||
const name = uiTexts[filterState.language].sortOptions[value];
|
||||
|
||||
// 선택된 옵션 업데이트
|
||||
sortOptions.querySelectorAll('li').forEach(li => {
|
||||
li.querySelector('span').classList.remove('font-semibold');
|
||||
li.querySelector('svg')?.parentElement?.classList.add('hidden');
|
||||
});
|
||||
|
||||
option.querySelector('span').classList.add('font-semibold');
|
||||
const checkIcon = option.querySelector('svg')?.parentElement;
|
||||
if (checkIcon) checkIcon.classList.remove('hidden');
|
||||
|
||||
// 정렬 상태 업데이트
|
||||
filterState.sortBy = value;
|
||||
sortButton.querySelector('span').textContent = name;
|
||||
sortOptions.classList.add('hidden');
|
||||
sortButton.setAttribute('aria-expanded', 'false');
|
||||
|
||||
// 게임 목록 다시 렌더링
|
||||
renderGames();
|
||||
});
|
||||
});
|
||||
|
||||
// 외부 클릭 시 드롭다운 닫기
|
||||
document.addEventListener('click', e => {
|
||||
if (!sortButton.contains(e.target) && !sortOptions.contains(e.target)) {
|
||||
sortOptions.classList.add('hidden');
|
||||
sortButton.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// 정렬 옵션 텍스트 업데이트
|
||||
function updateSortOptionsText() {
|
||||
const texts = uiTexts[filterState.language].sortOptions;
|
||||
sortLabel.textContent = filterState.language === 'ko' ? '정렬' : '並び替え';
|
||||
sortButton.querySelector('span').textContent = texts[filterState.sortBy];
|
||||
|
||||
sortOptions.querySelectorAll('li').forEach(option => {
|
||||
const value = option.getAttribute('data-value');
|
||||
option.querySelector('span').textContent = texts[value];
|
||||
});
|
||||
}
|
||||
|
||||
// 언어 변경 시 정렬 옵션 텍스트 업데이트
|
||||
languageRadios.forEach(radio => {
|
||||
radio.addEventListener('change', e => {
|
||||
filterState.language = e.target.value;
|
||||
updateSortOptionsText();
|
||||
});
|
||||
});
|
||||
|
||||
// 초기 정렬 옵션 텍스트 설정
|
||||
updateSortOptionsText();
|
||||
}
|
||||
|
||||
// 게임 렌더링 함수 수정
|
||||
function renderGames() {
|
||||
loading.classList.remove('hidden');
|
||||
|
||||
// 데이터 포맷팅
|
||||
const formattedGames = AMIIBO_DB.map(formatGameData);
|
||||
|
||||
// 필터링 적용
|
||||
const filteredGames = filterGames(formattedGames);
|
||||
|
||||
// 정렬 적용
|
||||
const sortedGames = sortGames(filteredGames);
|
||||
|
||||
// 게임 개수 업데이트
|
||||
gameCount.textContent = `${sortedGames.length} ${uiTexts[filterState.language].count}`;
|
||||
|
||||
// 테이블 내용 업데이트
|
||||
gameList.innerHTML = '';
|
||||
sortedGames.forEach((game, index) => {
|
||||
gameList.appendChild(createGameRow(game, index));
|
||||
});
|
||||
|
||||
loading.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 초기화
|
||||
setupFilterListeners();
|
||||
setupSortUI();
|
||||
updateUITexts();
|
||||
renderGames();
|
||||
});
|
||||
|
||||
function openImageModal(imageUrl, title) {
|
||||
const modal = document.getElementById('imageModal');
|
||||
const modalImage = document.getElementById('modalImage');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
|
||||
modalImage.src = imageUrl;
|
||||
modalTitle.textContent = title;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('imageModal').addEventListener('click', () => {
|
||||
document.getElementById('imageModal').classList.add('hidden');
|
||||
});
|
||||
Reference in New Issue
Block a user