[251105] fix: BuyNow Option-1

🕐 커밋 시간: 2025. 11. 05. 07:35:13

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +94줄
  • 삭제: -70줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx

🔧 주요 변경 내용:
  • 공통 유틸리티 함수 최적화
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 코드 정리 및 최적화
This commit is contained in:
2025-11-05 07:35:14 +09:00
parent 72b062ad1b
commit 95b709c513
4 changed files with 94 additions and 70 deletions

View File

@@ -25,12 +25,36 @@ export const createMockProductOptionData = (originalProductData) => {
// 실제 상품 가격 정보 사용 (originalProductData의 priceInfo 또는 기본값)
// priceInfo 포맷: "원가|할인가|할인액|할인율|할인율숫자"
const priceInfo = originalProductData.priceInfo || '99999|99999|0|0%|0';
const [regularPrice = '99999', salePrice = regularPrice] = priceInfo.split('|');
// 옵션명/옵션 값은 기존 데이터에서 최대한 활용하고, 없으면 기본값 사용
const baseOptionName =
originalProductData.optNm ||
originalProductData.optTpNm ||
`${prdtNm} Option`;
const optionValueName =
originalProductData.optValueNm ||
originalProductData.optVlNm ||
`${prdtNm} 기본 구성`;
const optionCode =
originalProductData.prodOptCdCval || `MOCK_OPT_${prdtId}_1`;
const optionImage =
originalProductData.thumbnailUrl960 ||
originalProductData.imgUrl ||
originalProductData.thumbnailUrl ||
null;
console.log('[BuyNowDataManipulator] createMockProductOptionData - priceInfo:', priceInfo);
// 옵션 상세 객체
const optionDetail = {
prodOptCval: `${prdtNm}`,
prodOptCdCval: optionCode,
prodOptCval: optionValueName,
optNm: optionValueName,
optPrc: salePrice,
optStkQty: 999,
optImgUrl: optionImage,
priceInfo: priceInfo,
stockCnt: 999,
soldOutYn: 'N',
@@ -41,8 +65,10 @@ export const createMockProductOptionData = (originalProductData) => {
return [
{
// 옵션 ID (기본 값)
prodOptSno: `MOCK_OPT_${prdtId}_1`,
prodOptTpCdCval: 'BASIC',
prodOptSno: optionCode,
prodOptTpCdCval: originalProductData.prodOptTpCdCval || 'BASIC',
optNm: baseOptionName,
optTpNm: originalProductData.optTpNm || baseOptionName,
// 옵션 상세 목록 (기본값 1개) - prdtOptDtl 배열 구조
prdtOptDtl: [optionDetail],

View File

@@ -141,6 +141,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
(isCancelClick) => (ev) => {
fp.pipe(
() => {
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
dispatch(finishVideoPreview());
dispatch(popPanel(panel_names.DETAIL_PANEL));
},

View File

@@ -136,14 +136,15 @@ const BuyOption = ({
// 상품 정보 (props 우선, 없으면 productInfo에서)
const selectedPatnrId = propsSelectedPatnrId || productInfo?.patnrId;
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
const isMockMode = BUYNOW_CONFIG.isMockMode();
// Mock Mode에서 옵션 데이터 처리
const productOptionInfos = useMemo(() => {
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', BUYNOW_CONFIG.isMockMode());
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos);
// API Mode: 기존 로직 100% 유지
if (!BUYNOW_CONFIG.isMockMode()) {
if (!isMockMode) {
console.log('[BuyOption] API Mode - using reduxProductOptionInfos');
return reduxProductOptionInfos;
}
@@ -162,7 +163,9 @@ const BuyOption = ({
// Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용
console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos');
return reduxProductOptionInfos;
}, [reduxProductOptionInfos, productData]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
}, [reduxProductOptionInfos, productData, isMockMode]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
const hasMockOptions =
isMockMode && Array.isArray(productOptionInfos) && productOptionInfos.length > 0;
const logInfo = useMemo(() => {
if (productData) {
// productData가 있으면 SingleOption과 동일하게 처리
@@ -307,7 +310,7 @@ const BuyOption = ({
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
useEffect(() => {
// API Mode: 실제 API 호출
if (!BUYNOW_CONFIG.isMockMode()) {
if (!isMockMode) {
dispatch(
getProductOption({
patnrId: selectedPatnrId,
@@ -324,41 +327,35 @@ const BuyOption = ({
);
}
// Mock Mode: API 호출 하지 않음
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber]);
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);
// 포커스 관리 로직 (SingleOption과 유사)
useEffect(() => {
// if (!isSpotlight) {
// // isSpotlight이 false면 일반적인 BuyOption 포커스
// console.log('[BuyOption] Component mounted - focusing BUY NOW button');
const shouldHandleOptionFocus =
productInfo?.optProdYn === 'Y' || hasMockOptions;
// Spotlight.focus('buy-option-buy-now-button');
// return;
// }
// isSpotlight이 true이고 SingleOption 동작이 필요한 경우 요부분
if (productInfo?.optProdYn === 'N') {
if (!shouldHandleOptionFocus) {
Spotlight.focus('buy-option-buy-now-button');
} else if (productInfo?.optProdYn === 'Y') {
if(
productOptionInfos &&
productOptionInfos?.length > 1
){
Spotlight.focus('buy-option-first-dropdown');
} else if (
productOptionInfos?.legnth > 0 &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1
){
Spotlight.focus('buy-option-second-dropdown');
} else {
Spotlight.focus('buy-option-buy-now-button');
}
return;
}
if (productOptionInfos && productOptionInfos.length > 1) {
Spotlight.focus('buy-option-first-dropdown');
} else if (
productOptionInfos &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1
) {
Spotlight.focus('buy-option-second-dropdown');
} else {
Spotlight.focus('buy-option-buy-now-button');
}
}, [productOptionInfos?.length, productInfo?.length]);
}, [
productOptionInfos,
productInfo?.optProdYn,
hasMockOptions,
selectedBtnOptIdx,
]);
// checkOutValidate 콜백 함수 (SingleOption과 동일한 로직)
function checkOutValidate(response) {
@@ -1318,7 +1315,7 @@ const BuyOption = ({
{/* 동적 옵션 렌더링 */}
{productOptionInfos &&
productOptionInfos?.length > 0 &&
productInfo?.optProdYn === 'Y' && (
(productInfo?.optProdYn === 'Y' || hasMockOptions) && (
<>
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
{productOptionInfos?.length > 1 && (

View File

@@ -213,7 +213,7 @@ const VoiceInputOverlay = ({
const [isSilenceCheckActive, setIsSilenceCheckActive] = useState(false);
const [hasReached5Chars, setHasReached5Chars] = useState(false); // 처음 5글자 도달 추적
// 💬 Bubble 버튼 상태 (true: 버튼 O, false: 텍스트만)
const [isBubbleButton, setIsBubbleButton] = useState(true); // 첫 발화때는 true (Try Saying)
const [isBubbleButton, setIsBubbleButton] = useState(false); // 첫 발화때는 true (Try Saying)
// useSearchHistory Hook 적용 (음성검색 기록 관리)
const { addVoiceSearch } = useSearchHistory();