diff --git a/negodata/front/src/features/products/components/PriceUpdateModal.tsx b/negodata/front/src/features/products/components/PriceUpdateModal.tsx index 4939418..21a18e1 100644 --- a/negodata/front/src/features/products/components/PriceUpdateModal.tsx +++ b/negodata/front/src/features/products/components/PriceUpdateModal.tsx @@ -53,8 +53,8 @@ const toMallPrices = (byMall: LowestPriceEntryByMall): MallPrices => { type ItemState = | { kind: 'pending' } // 접수됨 — 결과 대기 | { kind: 'done'; price: number; malls: MallPrices } - | { kind: 'notfound'; malls: MallPrices } // 동일 상품 판정 실패(기존 값 유지) - | { kind: 'failed'; reason?: string } // 요청 자체가 접수되지 않음 — 재시도 대상 + | { kind: 'notfound'; malls: MallPrices } // 검색은 됐으나 같은 상품이 없었음(기존 값 유지) + | { kind: 'failed'; reason?: string } // 접수 실패 또는 검색 실패(사이트 차단) — 재시도 대상 | { kind: 'timeout' } // 폴링 상한 초과 — 서버는 계속 검색 중 | { kind: 'stopped' }; // 사용자가 지켜보기를 중단 — 서버는 계속 검색 중 @@ -256,6 +256,13 @@ export function PriceUpdateModal({ open, products, selectedIds, onDone, onClose // by_mall 이 비어 오는 옛 이력 대비 — 최소한 대표 최저가는 보이도록 폴백을 채운다. if (Object.keys(malls).length === 0) malls.etc = fresh.lp_price; setItem(id, { kind: 'done', price: fresh.lp_price, malls }); + } else if (fresh.fail_reason === 'error') { + // 검색 자체가 실패한 경우(모든 소스 차단). '못 찾음'과 뜻이 완전히 다르다 — + // 상품이 없다는 게 아니라 **확인을 못 했다**는 뜻이라, 재시도 대상으로 분류한다. + // (LPS 가 outcome='error' 로 내려준다. '변동 없음'으로 뭉뚱그리면 사용자는 + // '검색은 됐는데 더 싼 게 없구나'로 오해한다) + // 문구는 '탐색 실패 사유: {reason}' 형태로 붙는다 — 사유만 간결하게 담는다. + setItem(id, { kind: 'failed', reason: '사이트 차단으로 가격을 확인하지 못했습니다 (잠시 후 다시 시도해 주세요)' }); } else { setItem(id, { kind: 'notfound', malls }); }