Update resale pricing data

This commit is contained in:
2026-05-21 11:22:20 +09:00
parent e0318cbf91
commit a69b655995
9 changed files with 556 additions and 21628 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -43,10 +43,8 @@
- `country` (string): `KOR` | `JPN`
- `cero` (string|null): CERO 등급
- `sale` (object):
- `suggestedPrice` (number): 추천 판매가(KRW)
- `priceRange` (object): `{ min, max }`
- `pricingBasis`, `confidence`, `checkedAt`, `memo`
- `priceVerified` (boolean): 실제 시세 확인 완료 여부. `true`이면 목록·상세 제목 앞 노란 별 표시
- `currency` (string): `KRW`
- `suggestedPrice` (`number` | `"none"`): 수기 입력된 판매가(KRW). 미입력 항목은 `"none"`.
## 필터/정렬 동작
- 필터:

View File

@@ -4,7 +4,8 @@
- 레거시 페이지/리소스(아미보, 세일 관련) 제거 후 문서와 실제 구조 정합성 점검이 필요하다.
## 다음 작업
- `sale.priceVerified: true``itemCondition` 값을 타이틀별로 실제 데이터에 맞게 채운다.
- `sale.suggestedPrice`를 타이틀별로 수기 확인해 숫자 가격으로 순차 입력한다.
- `itemCondition` 값을 타이틀별로 실제 데이터에 맞게 채운다.
- 목록/상세 페이지의 경로 및 텍스트 다국어 키를 재검증한다.
- 필터 조합(언어+상태+국가+CERO) 테스트 케이스를 작성한다.
- 데이터 스키마 검증 스크립트를 추가해 누락 필드와 오탈자를 자동 확인한다.

View File

@@ -1,5 +1,10 @@
# 작업 이력
## v2026.05.21-01
- `db/nsw.resale.db.js` 기존 추정 판매가·가격 근거 필드를 제거하고 `sale.suggestedPrice: "none"` 상태로 초기화
- `script/nsw.js`, `script/nsw-detail.js` 가격 미입력 항목에 “가격은 순차적으로 작성중입니다.” 안내 표시
- 가격 정렬 시 숫자 가격이 있는 항목만 우선 정렬하고 미입력 항목은 뒤로 배치
## v2026.05.19-02
- `script/nsw.js`, `script/nsw-detail.js` 가격 확인 별(⭐)과 순번 사이 공백 추가

View File

@@ -308,11 +308,14 @@
<div class="lg:col-span-3">
<div class="sm:px-6 lg:px-8">
<div class="flex flex-col">
<div class="flex items-center justify-between sm:flex-auto sm:-mx-6 lg:-mx-4">
<div class="flex flex-wrap items-center justify-between gap-3 sm:flex-auto sm:-mx-6 lg:-mx-4">
<p class="mt-2 sm:mt-0 text-sm text-gray-700">
개수:
<span id="gameCount">0</span>
</p>
<p id="priceProgressNotice" class="mt-2 max-w-md text-sm text-gray-500 sm:mt-0">
우선 이런 게임들이 있습니다. 가격은 순차적으로 작성중입니다.
</p>
<div class="sort">
<div class="flex items-center gap-2">
<label id="sort-label" class="block text-sm/6 font-medium text-gray-900">
@@ -542,6 +545,6 @@
</main>
</div>
</div>
<script type="module" src="./script/nsw.js?v=20260519-mobile-search"></script>
<script type="module" src="./script/nsw.js?v=20260521-price-pending"></script>
</body>
</html>

View File

@@ -37,6 +37,7 @@ document.addEventListener('DOMContentLoaded', () => {
iarc: '심의 등급',
releaseDate: '출시일',
suggestedPrice: '판매가',
pricePending: '-',
priceRange: '판매가 범위',
pricingBasis: '가격 참고 기준',
checkedAt: '기준일',
@@ -63,6 +64,7 @@ document.addEventListener('DOMContentLoaded', () => {
iarc: 'IARC',
releaseDate: '配信日',
suggestedPrice: '推奨販売価格',
pricePending: '-',
priceRange: '価格帯',
pricingBasis: '価格参考基準',
checkedAt: '確認日',
@@ -78,7 +80,7 @@ document.addEventListener('DOMContentLoaded', () => {
const currentTexts = texts[language];
function formatKRW(value) {
if (typeof value !== 'number') return '';
if (typeof value !== 'number') return currentTexts.pricePending;
return `${value.toLocaleString('ko-KR')}`;
}

View File

@@ -1,6 +1,6 @@
import NSW_DB from '../db/nsw.resale.db.js?v=20260519-mobile-search';
import NSW_DB from '../db/nsw.resale.db.js?v=20260521-price-pending';
window.NSW_APP_VERSION = '20260519-mobile-search';
window.NSW_APP_VERSION = '20260521-price-pending';
const SHOW_SOLD_BY_DEFAULT = false;
const SHOW_RECOMMENDED_PRICE_RANGE_BY_DEFAULT = false;
@@ -23,6 +23,8 @@ document.addEventListener('DOMContentLoaded', () => {
korean: '한국어',
japanese: '일본어',
count: '개수',
listIntro: '우선 이런 게임들이 있습니다. 가격은 순차적으로 작성중입니다.',
pricePending: '-',
loading: '로딩중...',
tableHeaders: {
title: '제목',
@@ -54,6 +56,8 @@ document.addEventListener('DOMContentLoaded', () => {
korean: '韓国語',
japanese: '日本語',
count: '件数',
listIntro: 'まずは所持タイトルを掲載しています。価格は順次入力中です。',
pricePending: '-',
loading: '読み込み中...',
tableHeaders: {
title: 'タイトル',
@@ -120,6 +124,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 로딩 텍스트 업데이트
loading.textContent = texts.loading;
document.getElementById('priceProgressNotice').textContent = texts.listIntro;
// 필터 텍스트 업데이트
document.getElementById('resetFilters').textContent = texts.filter.reset;
@@ -630,10 +635,15 @@ document.addEventListener('DOMContentLoaded', () => {
}
function formatKRW(value) {
if (typeof value !== 'number') return '';
if (typeof value !== 'number') return uiTexts[filterState.language].pricePending;
return `${value.toLocaleString('ko-KR')}`;
}
function getSuggestedPriceValue(game) {
const suggestedPrice = game.sale?.suggestedPrice;
return typeof suggestedPrice === 'number' ? suggestedPrice : null;
}
function formatSaleStatus(status) {
const labels = {
ko: {
@@ -766,8 +776,8 @@ document.addEventListener('DOMContentLoaded', () => {
</div>
</div>
</td>
<td class="hidden w-32 px-3 py-4 text-sm text-gray-500 sm:table-cell">
<div class="whitespace-nowrap font-semibold text-gray-900">
<td class="hidden w-56 px-3 py-4 text-sm text-gray-500 sm:table-cell">
<div class="font-semibold leading-5 text-gray-900">
${game.formattedSuggestedPrice || '-'}
</div>
<div class="mt-1 text-xs text-gray-400">${game.formattedPricingBasis}</div>
@@ -1094,12 +1104,22 @@ document.addEventListener('DOMContentLoaded', () => {
});
case 'sortByPriceDesc':
return [...games].sort((a, b) => {
const priceDiff = (b.sale?.suggestedPrice || 0) - (a.sale?.suggestedPrice || 0);
const aPrice = getSuggestedPriceValue(a);
const bPrice = getSuggestedPriceValue(b);
if (aPrice === null && bPrice === null) return b.no - a.no;
if (aPrice === null) return 1;
if (bPrice === null) return -1;
const priceDiff = bPrice - aPrice;
return priceDiff === 0 ? b.no - a.no : priceDiff;
});
case 'sortByPrice':
return [...games].sort((a, b) => {
const priceDiff = (a.sale?.suggestedPrice || 0) - (b.sale?.suggestedPrice || 0);
const aPrice = getSuggestedPriceValue(a);
const bPrice = getSuggestedPriceValue(b);
if (aPrice === null && bPrice === null) return b.no - a.no;
if (aPrice === null) return 1;
if (bPrice === null) return -1;
const priceDiff = aPrice - bPrice;
return priceDiff === 0 ? b.no - a.no : priceDiff;
});
case 'sortByRandom':