Merge branch 'detail_v3' of http://gitlab.t-win.kr/ifheone/shoptime into detail_v3

This commit is contained in:
2025-11-13 13:37:35 +09:00
38 changed files with 1131 additions and 478 deletions

View File

@@ -76,11 +76,16 @@ export const types = {
GET_MY_INFO_SHIPPING_SEARCH: 'GET_MY_INFO_SHIPPING_SEARCH',
// cart actions
GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_CART_ITEM: 'UPDATE_CART_ITEM',
CLEAR_CART: 'CLEAR_CART',
//cart api action
GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
INSERT_MY_INFO_CART : "INSERT_MY_INFO_CART",
DELETE_MY_INFO_CART : "DELETE_MY_INFO_CART",
DELETE_ALL_MY_INFO_CART : "DELETE_ALL_MY_INFO_CART",
UPDATE_MY_INFO_CART : "UPDATE_MY_INFO_CART",
// appData actions
ADD_MAIN_INDEX: 'ADD_MAIN_INDEX',
@@ -309,10 +314,7 @@ export const types = {
SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY',
// 🔽 [추가] JustForYou 상품 관리 부분
GET_RECENTLY_SAW_ITEM: 'GET_RECENTLY_SAW_ITEM',
GET_LIKE_BRAND_PRODUCT: 'GET_LIKE_BRAND_PRODUCT',
GET_MORE_TO_CONCIDER_AT_THIS_PRICE: 'GET_MORE_TO_CONCIDER_AT_THIS_PRICE',
GET_JUSTFORYOU_INFO: 'GET_JUSTFORYOU_INFO',
JUSTFORYOU: 'JUSTFORYOU',
// 🔽 Voice Conductor 관련 액션 타입
VOICE_REGISTER_SUCCESS: 'VOICE_REGISTER_SUCCESS',

View File

@@ -1,6 +1,7 @@
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import { types } from './actionTypes';
import { showError } from './commonActions';
/**
* 회원 장바구니 정보 조회
@@ -50,6 +51,160 @@ export const getMyInfoCartSearch = (props) => (dispatch, getState) => {
);
};
/**
* 장바구니 상품 추가 API
*/
export const insertMyinfoCart = (props) => (dispatch, getState) => {
const { mbrNo, patnrId, prdtId, prdtOpt, prodQty } = props;
const onSuccess = (response) => {
console.log("✅ insertMyinfoCart API 성공:", response.data.retCode);
// if (response.data?.retCode !== '0' && response.data.retCode !== 0) {
// console.error("❌ retCode 에러:", response.data.retCode);
// console.error("에러 메시지:", response.data.retMsg);
// return;
// }
if (response.data.retCode === 0) {
dispatch({
type: types.INSERT_MY_INFO_CART,
payload: response.data.data,
});
dispatch(getMyInfoCartSearch({ mbrNo }));
} else {
dispatch(
showError(
response.data.retCode,
response.data.retMsg,
false,
null,
null
)
);
console.error("❌ retCode 에러:", response.data.retCode);
console.error("에러 메시지:", response.data.retMsg);
}
};
const onFail = (error) => {
console.error("insertMyinfoCart OnFail: ", error);
};
TAxios(
dispatch,
getState,
"post",
URLS.INSERT_MY_INFO_CART,
{},
{ mbrNo, patnrId, prdtId, prdtOpt, prodQty },
onSuccess,
onFail
);
};
/**
* 장바구니 상품 삭제 API
*/
export const deleteMyinfoCart = (props) => (dispatch, getState) => {
const { mbrNo, patnrId, prdtId, prodSno } = props;
const onSuccess = (response) => {
console.log("deleteMyinfoCart onSuccess: ", response.data);
dispatch({
type: types.DELETE_MY_INFO_CART,
payload: response.data.data,
});
// 장바구니 목록 재조회
dispatch(getMyInfoCartSearch({ mbrNo }));
};
const onFail = (error) => {
console.error("deleteMyinfoCart OnFail: ", error);
};
TAxios(
dispatch,
getState,
"post",
URLS.DELETE_MY_INFO_CART,
{},
{ mbrNo, patnrId, prdtId, prodSno },
onSuccess,
onFail
);
};
/**
* 장바구니 상품 전체 삭제
*/
export const deleteAllMyinfoCart = (props) => (dispatch, getState) => {
const { mbrNo } = props;
const onSuccess = (response) => {
console.log("deleteAllMyinfoCart onSuccess: ", response.data);
dispatch({
type: types.DELETE_ALL_MY_INFO_CART,
payload: response.data.data,
});
// 장바구니 목록 재조회
dispatch(getMyInfoCartSearch({ mbrNo }));
};
const onFail = (error) => {
console.error("deleteAllMyinfoCart OnFail: ", error);
};
TAxios(
dispatch,
getState,
"post",
URLS.DELETE_ALL_MY_INFO_CART,
{},
{ mbrNo },
onSuccess,
onFail
);
};
/**
* 장바구니 상품 수정
*/
export const updateMyinfoCart = (props) => (dispatch, getState) => {
const { mbrNo, patnrId, prdtId, prodQty, prodSno } = props;
const onSuccess = (response) => {
console.log("updateMyinfoCart onSuccess: ", response.data);
dispatch({
type: types.UPDATE_MY_INFO_CART,
payload: response.data.data,
});
// 장바구니 목록 재조회
dispatch(getMyInfoCartSearch({ mbrNo }));
};
const onFail = (error) => {
console.error("updateMyinfoCart OnFail: ", error);
};
TAxios(
dispatch,
getState,
"post",
URLS.UPDATE_MY_INFO_CART,
{},
{ mbrNo, patnrId, prdtId, prodQty, prodSno },
onSuccess,
onFail
);
};
/**
* 장바구니에 상품 추가
* @param {Object} props - 장바구니 상품 정보

View File

@@ -1,6 +1,7 @@
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import { types } from './actionTypes';
import { getReAuthenticationCode } from './deviceActions';
/**
* PDF를 이미지로 변환 (재시도 로직 포함)
@@ -80,55 +81,61 @@ export const convertPdfToImage =
return;
}
let imageUrl;
try {
if (response.data instanceof Blob) {
if (response.data.size === 0) {
throw new Error('Invalid image data (empty blob)');
}
imageUrl = URL.createObjectURL(response.data);
} else if (response.data instanceof ArrayBuffer) {
if (response.data.byteLength === 0) {
throw new Error('Invalid image data (empty buffer)');
}
const blob = new Blob([response.data], { type: 'image/png' });
imageUrl = URL.createObjectURL(blob);
} else {
const blob = new Blob([response.data], { type: 'image/png' });
if (blob.size === 0) {
throw new Error('Invalid image data (empty blob)');
}
imageUrl = URL.createObjectURL(blob);
}
console.log(`✅ [EnergyLabel] Conversion successful on attempt ${attempts}:`, pdfUrl);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_SUCCESS,
payload: { pdfUrl, imageUrl },
});
callback && callback(null, imageUrl);
} catch (error) {
console.error(`❌ [EnergyLabel] Image creation failed on attempt ${attempts}:`, error);
// 이미지 생성 실패도 재시도
if (attempts < maxRetries + 1) {
console.log(
`🔄 [EnergyLabel] Retrying due to image creation error... (${attempts}/${maxRetries + 1})`
);
if(response.data.type !== "image/png"){
dispatch(getReAuthenticationCode());
attemptConversion();
} else {
console.error(
`❌ [EnergyLabel] Final failure after ${attempts} attempts (image error):`,
pdfUrl
);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: { pdfUrl, error },
});
callback && callback(error, null);
return;
}
let imageUrl;
try {
if (response.data instanceof Blob) {
if (response.data.size === 0) {
throw new Error('Invalid image data (empty blob)');
}
imageUrl = URL.createObjectURL(response.data);
} else if (response.data instanceof ArrayBuffer) {
if (response.data.byteLength === 0) {
throw new Error('Invalid image data (empty buffer)');
}
const blob = new Blob([response.data], { type: 'image/png' });
imageUrl = URL.createObjectURL(blob);
} else {
const blob = new Blob([response.data], { type: 'image/png' });
if (blob.size === 0) {
throw new Error('Invalid image data (empty blob)');
}
imageUrl = URL.createObjectURL(blob);
}
console.log(`✅ [EnergyLabel] Conversion successful on attempt ${attempts}:`, pdfUrl);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_SUCCESS,
payload: { pdfUrl, imageUrl },
});
callback && callback(null, imageUrl);
} catch (error) {
console.error(`❌ [EnergyLabel] Image creation failed on attempt ${attempts}:`, error);
// 이미지 생성 실패도 재시도
if (attempts < maxRetries + 1) {
console.log(
`🔄 [EnergyLabel] Retrying due to image creation error... (${attempts}/${maxRetries + 1})`
);
attemptConversion();
} else {
console.error(
`❌ [EnergyLabel] Final failure after ${attempts} attempts (image error):`,
pdfUrl
);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: { pdfUrl, error },
});
callback && callback(error, null);
}
}
}
};
const onFail = (error) => {

View File

@@ -1,36 +1,16 @@
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import {
curry,
get,
set,
} from '../utils/fp';
import { get } from '../utils/fp';
import { types } from './actionTypes';
import { changeAppStatus } from './commonActions';
// 최근 본 아이템 관련 액션
export const getRecentlySawItem = (data) => ({
type: types.GET_RECENTLY_SAW_ITEM,
payload: data,
});
// 좋아하는 브랜드 제품 관련 액션
export const getLikeBrandProduct = (data) => ({
type: types.GET_LIKE_BRAND_PRODUCT,
payload: data,
});
// 이 가격대에서 고려할 만한 더 많은 제품 관련 액션
export const getMoreToConciderAtThisPrice = (data) => ({
type: types.GET_MORE_TO_CONCIDER_AT_THIS_PRICE,
payload: data,
});
export const getJustForYouInfo = (callback) => (dispatch, getState) => {
export const justForYou = (callback) => (dispatch, getState) => {
const macAddress = getState().common.macAddress;
const macAddr = macAddress?.wired || macAddress?.wifi || "00:1A:2B:3C:4D:5E";
const onSuccess = (response) => {
console.log("getJustForYouInfo onSuccess", response.data);
console.log("JustForYou onSuccess", response.data);
dispatch({
type: types.GET_JUSTFORYOU_INFO,
type: types.JUSTFORYOU,
payload: get("data.data", response),
});
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
@@ -38,7 +18,7 @@ export const getJustForYouInfo = (callback) => (dispatch, getState) => {
};
const onFail = (error) => {
console.error("getJustForYouInfo onFail", error);
console.error("JustForYou onFail", error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
callback && callback();
};
@@ -46,10 +26,10 @@ export const getJustForYouInfo = (callback) => (dispatch, getState) => {
TAxios(
dispatch,
getState,
"get",
URLS.GET_JUSTFORYOU_INFO,
{},
"post",
URLS.JUSTFORYOU,
{},
{macAddr},
onSuccess,
onFail
);

View File

@@ -1,10 +1,18 @@
import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios';
import { convertUtcToLocal } from '../components/MediaPlayer/util';
import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../utils/Config';
import {
CATEGORY_DATA_MAX_RESULTS_LIMIT,
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
} from '../utils/Config';
import * as HelperMethods from '../utils/helperMethods';
import { types } from './actionTypes';
import { addReservation, changeAppStatus, deleteReservation } from './commonActions';
import {
addReservation,
changeAppStatus,
deleteReservation,
} from './commonActions';
//IF-LGSP-007
export const getMainLiveShow = (props) => (dispatch, getState) => {
@@ -155,7 +163,7 @@ let lastSubCategoryParams = {};
export const getSubCategory =
(params, pageNo = 1, key = null, clear = false) =>
(dispatch, getState) => {
const { lgCatCd, patnrIdList, tabType, filterType } = params;
const { lgCatCd, patnrIdList, tabType, filterType, recommendIncFlag } = params;
let pageSize = params.pageSize || CATEGORY_DATA_MAX_RESULTS_LIMIT;
if (pageNo === 1) {
@@ -179,21 +187,34 @@ export const getSubCategory =
if (pageNo === 1) {
getSubCategoryKey = new Date();
currentKey = getSubCategoryKey;
// ✅ recommendProduct 분리
const { recommendProduct, ...restData } = response.data.data;
dispatch({
type: types.GET_SUB_CATEGORY,
payload: response.data.data,
payload: {
...restData,
recommendProduct,
},
categoryParams: {
lgCatCd,
patnrIdList,
tabType,
filterType,
recommendIncFlag,
pageSize,
},
});
} else if (getSubCategoryKey === currentKey) {
const { recommendProduct, ...restData } = response.data.data;
dispatch({
type: types.GET_SUB_CATEGORY,
payload: response.data.data,
payload: {
...restData,
recommendProduct,
},
append: true,
startIndex: (pageNo - 1) * pageSize,
});
@@ -212,7 +233,7 @@ export const getSubCategory =
getState,
'get',
URLS.GET_SUB_CATEGORY,
{ lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType },
{ lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType,recommendIncFlag },
{},
onSuccess,
onFail

View File

@@ -20,10 +20,15 @@ export const URLS = {
GET_MY_INFO_CARD_SEARCH: "/lgsp/v1/myinfo/card/search.lge",
// cart controller
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge",
REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge",
UPDATE_CART_ITEM: "/lgsp/v1/myinfo/cart/update.lge",
// cart api
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
INSERT_MY_INFO_CART: "/lgsp/v1/myinfo/cart/add.lge",
DELETE_MY_INFO_CART : "/lgsp/v1/myinfo/cart/delete.lge",
DELETE_ALL_MY_INFO_CART : "/lgsp/v1/myinfo/cart/deleteAll.lge",
UPDATE_MY_INFO_CART : "/lgsp/v1/myinfo/cart/update.lge",
// shipping controller
GET_MY_INFO_SHIPPING_SEARCH: "/lgsp/v1/myinfo/address/shipping/search.lge",
@@ -142,7 +147,8 @@ export const URLS = {
PAYMENT_TOTAL_CANCEL: "/lgsp/v1/myinfo/order/paymentTotalCancel.lge",
// foryou controller
GET_JUSTFORYOU_INFO: "/lgsp/v1/justforyou/list.lge",
JUSTFORYOU: "/lgsp/v1/recommend/justforyou.lge",
// emp controller
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",

View File

@@ -245,7 +245,7 @@
> p {
font-weight: bold;
font-size: 30px;
font-size: 28px;
color: @PRIMARY_COLOR_RED;
margin-top: 8px;

View File

@@ -86,7 +86,7 @@ export const removeDotAndColon = (string) => {
};
const parsePrice = (price) => {
return parseFloat(price?.replace(/[^0-9.-]+/g, "") || "0");
return parseFloat(price?.replace(/[^0-9.-]+/g, "").replace("£","") || "0");
};
const generateMockEnergyLabels = (productId) => {
@@ -474,12 +474,18 @@ export default memo(function TItemCardNew({
className={classNames(
css.descWrap,
catNm && css.hstNmWrap,
parsePrice(originPrice) >= 1000 &&
parsePrice(dcPrice) >= 1000 &&
(parsePrice(originalPrice) >= 1000 || parsePrice(originPrice) >= 1000 ) &&
(parsePrice(discountedPrice) >= 1000 || parsePrice(dcPrice) >= 1000 ) &&
euEnrgLblInfos?.length > 0 &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== null &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== undefined &&
(countryCode === "GB" || countryCode === "DE") &&
css.labelBox,
parsePrice(originPrice) >= 10000 &&
parsePrice(dcPrice) >= 10000 &&
(parsePrice(originalPrice) >= 10000 || parsePrice(originPrice) >= 10000 ) &&
(parsePrice(discountedPrice) >= 10000 || parsePrice(dcPrice) >= 10000 ) &&
euEnrgLblInfos?.length > 0 &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== null &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== undefined &&
(countryCode === "GB" || countryCode === "DE") &&
css.labelOverBox
)}
@@ -525,11 +531,11 @@ export default memo(function TItemCardNew({
)}
</div>
{(() => {
const hasValidApiData =
{/* const hasValidApiData =
euEnrgLblInfos?.length > 0 &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== null &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== undefined;
euEnrgLblInfos[0]?.enrgLblIcnUrl !== undefined; */}
const hasValidApiData = true;
let energyLabels;
if (CURRENT_ENERGY_LABEL_MODE === ENERGY_LABEL_MODE.API_ONLY) {
@@ -550,7 +556,11 @@ export default memo(function TItemCardNew({
}
return (
(countryCode === "GB" || countryCode === "DE") && (
(countryCode === "GB" || countryCode === "DE") &&
(euEnrgLblInfos?.length > 0 &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== null &&
euEnrgLblInfos[0]?.enrgLblIcnUrl !== undefined) &&
(
<div className={css.labelImgBox}>
{type !== "videoShow" &&
energyLabels

View File

@@ -1,4 +1,4 @@
import { types } from "../actions/actionTypes";
import { types } from '../actions/actionTypes';
/**
* Cart Reducer 초기 상태
@@ -20,7 +20,7 @@ const initialState = {
*/
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
// 장바구니 조회
// 장바구니 조회 - API에서 가져온 전체 목록
case types.GET_MY_INFO_CART_SEARCH:
return {
...state,
@@ -32,6 +32,43 @@ export const cartReducer = (state = initialState, action) => {
};
// 장바구니에 상품 추가
case types.INSERT_MY_INFO_CART:
return {
...state,
getMyinfoCartSearch: {
...state.getMyinfoCartSearch,
cartList: [...state.getMyinfoCartSearch.cartList, action.payload],
totalCount: (state.getMyinfoCartSearch.totalCount || 0) + 1,
},
};
// 장바구니에서 상품 삭제
case types.DELETE_MY_INFO_CART:
return {
...state,
getMyinfoCartSearch: {
...state.getMyinfoCartSearch,
cartList: (state.getMyinfoCartSearch.cartList || []).filter(
item => item.prodSno !== action.payload.prodSno
),
totalCount: Math.max(0, (state.getMyinfoCartSearch.totalCount || 0) - 1),
},
};
case types.UPDATE_MY_INFO_CART:
return {
...state,
getMyinfoCartSearch: {
...state.getMyinfoCartSearch,
cartList: (state.getMyinfoCartSearch.cartList || []).map(item =>
item.prodSno === action.payload.prodSno
? { ...item, ...action.payload }
: item
),
},
};
// 장바구니에 상품 추가 (addToCart - 로컬 상태용)
case types.ADD_TO_CART:
return {
...state,
@@ -43,7 +80,7 @@ export const cartReducer = (state = initialState, action) => {
error: null,
};
// 장바구니에서 상품 제거
// 장바구니에서 상품 제거 (removeFromCart)
case types.REMOVE_FROM_CART:
return {
...state,
@@ -55,19 +92,19 @@ export const cartReducer = (state = initialState, action) => {
error: null,
};
// 장바구니 상품 수량 업데이트
// 장바구니 상품 수량 업데이트 (updateCartItem)
case types.UPDATE_CART_ITEM:
return {
...state,
lastAction: {
type: "update",
type: "updateQty",
data: action.payload,
timestamp: Date.now(),
},
error: null,
};
// 장바구니 전체 비우기
// 장바구니 전체 비우기 (clearCart - 로컬 상태 전용)
case types.CLEAR_CART:
return {
...state,

View File

@@ -5,50 +5,16 @@ import {
set,
} from '../utils/fp';
// 초기 상태 정의
// const initialState = {
// recentlySawItems: [],
// likeBrandProducts: [],
// moreToConciderAtThisPrice: [],
// };
const initialState = {
justForYouInfo: {},
recommendInfo: {},
};
// const foryouReducer = (state = initialState, action) => {
// switch (action.type) {
// // 최근 본 아이템
// case types.GET_RECENTLY_SAW_ITEM:
// return {
// ...state,
// recentlySawItems: action.payload,
// };
// // 좋아하는 브랜드 제품
// case types.GET_LIKE_BRAND_PRODUCT:
// return {
// ...state,
// likeBrandProducts: action.payload,
// };
// // 이 가격대에서 고려할 만한 더 많은 제품
// case types.GET_MORE_TO_CONCIDER_AT_THIS_PRICE:
// return {
// ...state,
// moreToConciderAtThisPrice: action.payload,
// };
// default:
// return state;
// }
// };
const justForYouInfo = curry((state, action) =>
set("justForYouInfo", get("payload", action), state)
const recommendInfo = curry((state, action) =>
set("recommendInfo", get("payload", action), state)
);
const handlers = {
[types.GET_JUSTFORYOU_INFO]: justForYouInfo,
[types.JUSTFORYOU]: recommendInfo,
};
export const foryouReducer = (state = initialState, action = {}) => {

View File

@@ -10,11 +10,13 @@ const initialState = {
showDetailInfo: [],
showNowInfo: null,
featuredShowsInfos: {},
recommendProduct: null,
categoryParams: {
lgCatCd: null,
patnrIdList: null,
tabType: null,
filterType: null,
recommendIncFlag: null,
pageSize: CATEGORY_DATA_MAX_RESULTS_LIMIT,
},
};
@@ -39,6 +41,7 @@ export const mainReducer = (state = initialState, action) => {
categoryFilterCd: action.payload.categoryFilterCd,
topShowInfo: action.payload.topShowInfo,
partnerInfos: action.payload.partnerInfos,
recommendProduct: action.payload.recommendProduct,
categoryParams: action.categoryParams,
};
}
@@ -63,6 +66,7 @@ export const mainReducer = (state = initialState, action) => {
categoryFilterCd: action.payload.categoryFilterCd,
topShowInfo: action.payload.topShowInfo,
partnerInfos: action.payload.partnerInfos,
recommendProduct: action.payload.recommendProduct,
categoryParams: action.categoryParams,
};
}
@@ -93,6 +97,7 @@ export const mainReducer = (state = initialState, action) => {
categoryFilterCd: action.payload.categoryFilterCd,
topShowInfo: action.payload.topShowInfo,
partnerInfos: action.payload.partnerInfos,
recommendProduct: action.payload.recommendProduct,
};
} else {
return {
@@ -107,6 +112,7 @@ export const mainReducer = (state = initialState, action) => {
categoryFilterCd: action.payload.categoryFilterCd,
topShowInfo: action.payload.topShowInfo,
partnerInfos: action.payload.partnerInfos,
recommendProduct: action.payload.recommendProduct,
categoryParams: action.categoryParams,
};
}
@@ -206,6 +212,7 @@ export const mainReducer = (state = initialState, action) => {
return {
...state,
subCategoryData: {},
recommendProduct: null,
};
default:

View File

@@ -79,6 +79,7 @@ export const ACTIVE_POPUP = {
exitPopup: 'exitPopup',
favoritePopup: 'favoritePopup',
loginPopup: 'loginPopup',
logoutPopup: 'logoutPopup',
noShowPopup: 'noShowPopup',
optionPopup: 'optionPopup',
qrPopup: 'qrPopup',
@@ -292,6 +293,7 @@ export const LOG_MENU = {
HOME_ON_SALE: 'Home/On Sale',
HOME_POPULAR_SHOWS: 'Home/Popular Shows',
HOME_BEST_SELLER: 'Home/Best Sellers',
HOME_PICKED_FOR_YOU: 'Home/Picked For You',
TRENDING_NOW_POPULAR_SHOWS: 'Trending Now/Popular Shows',
TRENDING_NOW_BEST_SELLER: 'Trending Now/Best Sellers',

View File

@@ -46,7 +46,8 @@ export default function CartPanel({ spotlightId, scrollOptions = [], panelInfo }
);
// Mock Mode 여부 확인 및 적절한 데이터 선택
const isMockMode = BUYNOW_CONFIG.isMockMode();
// const isMockMode = BUYNOW_CONFIG.isMockMode();
const isMockMode = false;
const displayCartData = useMemo(() => {
return isMockMode ? mockCartData : cartData;
}, [isMockMode, mockCartData, cartData]);

View File

@@ -11,19 +11,17 @@ import {
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import logoImage from '../../../assets/images/ic-partners-qvc@3x.png';
import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
import {
deleteMyinfoCart,
removeFromCart,
updateCartItem,
updateMyinfoCart,
} from '../../actions/cartActions';
import {
removeFromMockCart,
setMockCartItemQuantity,
updateSelectedItems,
} from '../../actions/mockCartActions';
import CustomImage from '../../components/CustomImage/CustomImage';
@@ -97,6 +95,7 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
// 항상 호출되어야 하는 Hook들
const fallbackCartData = useSelector((state) => state.cart.getMyinfoCartSearch.cartInfo);
const selectedItems = useSelector((state) => state.mockCart.selectedItems || []);
const userNumber = useSelector((state) => state.common.appStatus.loginUserData.userNumber);
// 실제 장바구니 데이터와 Mock 데이터 중 선택
const cartData = cartInfo || fallbackCartData;
@@ -138,12 +137,12 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
// 파트너사별 총합 계산
const calculatePartnerTotal = (items) => {
const productTotal = items.reduce((sum, item) =>
sum + (parseFloat(item.price3 || item.price2 || 0) * item.prodQty), 0
sum + (parseFloat(Number(item.price3) !== 0 ? Number(item.price3) : Number(item.price2) !== 0 ? Number(item.price2) : 0) * item.prodQty), 0
);
// const optionTotal = items.reduce((sum, item) =>
// sum + (parseFloat(item.optPrc.replace("$","") || 0) * item.prodQty), 0
// );
const optionTotal = 0;
const optionTotal = items.reduce((sum, item) =>
sum + (parseFloat(Number(item.price5) !== 0 ? Number(item.price5) : Number(item.optPrc) !== 0 ? Number(item.optPrc) : 0) * item.prodQty), 0
);
const shippingTotal = items.reduce((sum, item) =>
sum + parseFloat((item.shippingCharge) * item.prodQty || 0), 0
);
@@ -157,32 +156,38 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
};
// 수량 조절 핸들러
const handleDecreseClick = useCallback((prodSno, currentQty) => {
const handleDecreseClick = useCallback((prodSno, patnrId, prdtId, currentQty) => {
if (currentQty > 1) {
const newQty = currentQty - 1;
if (isMockMode) {
dispatch(setMockCartItemQuantity(prodSno, newQty));
// dispatch(setMockCartItemQuantity(prodSno, newQty));
} else {
// 실제 API 호출을 위한 사용자 정보 필요
const { userNumber } = store.getState().common.appStatus.loginUserData;
if (userNumber) {
dispatch(updateCartItem({ mbrNo: userNumber, cartSno: prodSno, prodQty: newQty }));
dispatch(updateMyinfoCart({
mbrNo: userNumber,
patnrId,
prdtId,
prodSno,
prodQty: newQty
}));
}
}
}
}, [dispatch, isMockMode]);
const handleIncreseClick = useCallback((prodSno, currentQty) => {
const handleIncreseClick = useCallback((prodSno, patnrId, prdtId, currentQty) => {
const newQty = currentQty + 1;
if (isMockMode) {
dispatch(setMockCartItemQuantity(prodSno, newQty));
// dispatch(setMockCartItemQuantity(prodSno, newQty));
} else {
// 실제 API 호출을 위한 사용자 정보 필요
const { userNumber } = store.getState().common.appStatus.loginUserData;
if (userNumber) {
dispatch(updateCartItem({ mbrNo: userNumber, cartSno: prodSno, prodQty: newQty }));
dispatch(updateMyinfoCart({ mbrNo: userNumber, patnrId, prdtId, prodSno, prodQty: newQty }));
}
}
}, [dispatch, isMockMode]);
@@ -264,11 +269,20 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
[scrollTop]
);
//장바구니 삭제
const deleteCart = useCallback((patnrId, prdtId, prodSno) => {
dispatch(deleteMyinfoCart({
mbrNo : userNumber,
patnrId: String(patnrId),
prdtId : String(prdtId),
prodSno : String(prodSno)
}))
},[dispatch])
return (
<>
{Object.entries(groupedCartData).map(([partnerKey, group], index) => {
const totals = calculatePartnerTotal(group.items);
return (
<Container key={partnerKey} className={css.productBox}>
{index === 0 && (
@@ -299,12 +313,14 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} +
{/* Option ${totals.optionTotal.toLocaleString('en-US', {
{totals.optionTotal > 0 && (
<>
Option ${totals.optionTotal.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} + */}
Option $0.00 +
})} +
</>
)}
S&H ${totals.shippingTotal.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
@@ -372,20 +388,22 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
)}
<div className={css.accountBox}>
<span className={css.account}>
${parseFloat(item.price3 || item.price2 || 0).toLocaleString('en-US', {
${parseFloat(Number(item.price3) > 0 ? Number(item.price3) : Number(item.price2) > 0 ? Number(item.price2) : 0).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}
</span>
<div className={css.accountInfo}>
{item.price2 && (
<span className={css.originalAcc}>
{item.price2 && (parseFloat(item.price2) > 0 && parseFloat(item.price3) > 0 && parseFloat(item.price2) !== parseFloat(item.price3)) && (
<>
${parseFloat(item.price2).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}
</span>
</>
)}
</span>
{item.price5 && parseFloat(item.price5) > 0 && (
<span className={css.optionAcc}>
OPTION : ${parseFloat(item.price5).toLocaleString('en-US', {
@@ -411,13 +429,13 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
item.prodQty === 1 ? css.dimm : ""
)}
size="cartEa"
onClick={() => handleDecreseClick(item.prodSno, item.prodQty)}
onClick={() => handleDecreseClick(item.prodSno, item.patnrId, item.prdtId, item.prodQty)}
spotlightId={"pd_ea_decrese"}
spotlightDisabled={item.prodQty === 1}
/>
<div className={css.ea}>{item.prodQty}</div>
<TButton
onClick={() => handleIncreseClick(item.prodSno, item.prodQty)}
onClick={() => handleIncreseClick(item.prodSno, item.patnrId, item.prdtId, item.prodQty)}
className={css.plusBox}
spotlightId={"pd_ea_increse"}
size="cartEa"
@@ -430,7 +448,7 @@ const CartProduct = ({ cartInfo, getScrollTo, scrollTop }) => {
<TButton
className={css.trashImg}
size="cartTrash"
onClick={() => handleDeleteClick(item.prodSno)}
onClick={() => deleteCart(item.patnrId, item.prdtId, item.prodSno)}
/>
</div>
</div>

View File

@@ -175,16 +175,16 @@ const CartSidebar = ({ cartInfo }) => {
// API Mode: 실제 장바구니 데이터로 계산
const itemCount = displayCartInfo.reduce((sum, item) => sum + (item.prodQty || 1), 0);
const subtotal = displayCartInfo.reduce((sum, item) => {
const price = parseFloat(item.price3 || item.price2 || 0);
const price = parseFloat(Number(item.price3) !== 0 ? Number(item.price3) : Number(item.price2) !== 0 ? Number(item.price2) : 0);
return sum + (price * (item.prodQty || 1));
}, 0);
const optionTotal = displayCartInfo.reduce((sum, item) => {
const optionPrice = parseFloat(item.price5 || item.optPrc || 0);
const optionPrice = parseFloat(Number(item.price5) || Number(item.optPrc) || 0);
return sum + (optionPrice * (item.prodQty || 1));
}, 0);
const shippingHandling = displayCartInfo.reduce((sum, item) =>
sum + parseFloat(item.shippingCharge || 0), 0
);
const shippingHandling = displayCartInfo.reduce((sum, item) => {
return sum + parseFloat(Number(item.shippingCharge) * (item.prodQty || 1))
}, 0);
return {
itemCount,

View File

@@ -79,6 +79,7 @@ export default function ItemContents({ onClick }) {
lastLabel=" go to detail, button"
data-wheel-point={index >= 5}
euEnrgLblInfos={euEnrgLblInfos}
className={css.itemCardNewCate}
/>
);
})}

View File

@@ -6,7 +6,7 @@
display: flex;
flex-wrap: wrap;
margin-top: 34px;
> div {
> div.itemCardNewCate {
margin: 0 15px 15px 0;
}
}

View File

@@ -30,7 +30,10 @@ import {
sendLogPaymentEntry,
sendLogTotalRecommend,
} from '../../actions/logActions';
import { popPanel, updatePanel } from '../../actions/panelActions';
import {
popPanel,
updatePanel,
} from '../../actions/panelActions';
import TBody from '../../components/TBody/TBody';
import TButton from '../../components/TButton/TButton';
import TButtonScroller from '../../components/TButtonScroller/TButtonScroller';
@@ -615,7 +618,7 @@ export default function CheckOutPanel({ panelInfo }) {
<TBody className={css.tbody} cbScrollTo={getScrollToBody}>
<THeader className={css.theader} title="CHECKOUT" onBackButton onClick={onBackClick} />
<div className={css.Wrap}>
{BUYNOW_CONFIG.isMockMode() ? (
{/* {BUYNOW_CONFIG.isMockMode() ? (
<SummaryContainerMock
setPlaceOrderPopup={setPlaceOrderPopup}
empTermsData={empTermsData}
@@ -629,7 +632,7 @@ export default function CheckOutPanel({ panelInfo }) {
defaultPrice={panelInfo?.defaultPrice}
fromCartPanel={panelInfo?.fromCartPanel}
/>
) : (
) : ( */}
<SummaryContainer
setPlaceOrderPopup={setPlaceOrderPopup}
empTermsData={empTermsData}
@@ -638,8 +641,8 @@ export default function CheckOutPanel({ panelInfo }) {
currSignLoc={currSignLoc}
doSendLogPaymentEntry={doSendLogPaymentEntry}
/>
)}
{BUYNOW_CONFIG.isMockMode() ? (
{/* )} */}
{/* {BUYNOW_CONFIG.isMockMode() ? (
<InformationContainerMock
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
@@ -647,14 +650,14 @@ export default function CheckOutPanel({ panelInfo }) {
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
orderItemsCount={orderItemsCount}
/>
) : (
) : ( */}
<InformationContainer
toggleOrderSideBar={toggleOrderSideBar}
toggleOfferSideBar={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
doSendLogMyInfoEdit={doSendLogMyInfoEdit}
/>
)}
{/* )} */}
</div>
</TBody>
</TPanel>

View File

@@ -41,7 +41,6 @@ export default memo(function OrderItemCard({
const priceTotalData = useSelector(
(state) => state.checkout?.checkoutTotalData
);
console.log("###priceTotalData",priceTotalData);
const formattedPrices = useMemo(() => {
return {
@@ -83,7 +82,7 @@ export default memo(function OrderItemCard({
<p className={css.priceWrap}>
<span className={css.itemPrice}>
<span className={css.discountedPrice}>
$ {(price * prodQty).toLocaleString('en-US', {
$ {(Number(price) * Number(prodQty)).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}

View File

@@ -1,27 +1,42 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { setHidePopup, setShowPopup } from '../../../actions/commonActions';
import {
setHidePopup,
setShowPopup,
} from '../../../actions/commonActions';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { popPanel } from '../../../actions/panelActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import TQRCode from '../../../components/TQRCode/TQRCode';
import useScrollTo from '../../../hooks/useScrollTo';
import useScrollTopByDistance from '../../../hooks/useScrollTopByDistance';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import * as Config from '../../../utils/Config';
import { $L, getQRCodeUrl } from '../../../utils/helperMethods';
import {
$L,
getQRCodeUrl,
} from '../../../utils/helperMethods';
import BillingAddressCard from '../components/BillingAddressCard';
import PaymentCard from '../components/PaymentCard';
import ShippingAddressCard from '../components/ShippingAddressCard';
import Subject from '../components/Subject';
import css from './InformationContainer.module.less';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
@@ -282,9 +297,12 @@ export default function InformationContainer({
ADD/EDIT
</TButton>
{/* BillingAddressCard disabled due to infinite render loop in Mock Mode */}
<div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
{/* <div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
Mock Billing Address
</div>
</div> */}
{checkoutData?.billingAddressList && (
<BillingAddressCard list={checkoutData.billingAddressList} onFocus={handleFocus} />
)}
</div>
<div className={css.listBox}>
<Subject title="PATMENT METHOD" />

View File

@@ -165,7 +165,7 @@ export default function OrderItemsSideBar({
patncLogPath={item.patncLogPath}
prdtId={item.prdtId}
expsPrdtNo={item.expsPrdtNo}
price={item.price >= item.originalPrice ? item.originalPrice : item.price}
price={item.price2 >= item.price3 ? item.price3 : item.price2}
currSign={item.currSign}
currSignLoc={item.currSignLoc}
shippingCharge={item.shippingCharge}

View File

@@ -1,21 +1,32 @@
import React, { useCallback, useEffect, useMemo } from "react";
import React, {
useCallback,
useEffect,
useMemo,
} from 'react';
import { useDispatch, useSelector } from "react-redux";
import {
useDispatch,
useSelector,
} from 'react-redux';
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import { setHidePopup, setShowPopup } from "../../../actions/commonActions";
import { popPanel } from "../../../actions/panelActions";
import TButton from "../../../components/TButton/TButton";
import TPopUp from "../../../components/TPopUp/TPopUp";
import * as Config from "../../../utils/Config";
import {
setHidePopup,
setShowPopup,
} from '../../../actions/commonActions';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { popPanel } from '../../../actions/panelActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import * as Config from '../../../utils/Config';
import {
$L,
formatCurrencyValue,
getErrorMessage,
} from "../../../utils/helperMethods";
import css from "./SummaryContainer.module.less";
import { sendLogTotalRecommend } from "../../../actions/logActions";
} from '../../../utils/helperMethods';
import css from './SummaryContainer.module.less';
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
@@ -51,12 +62,12 @@ export default function SummaryContainer({
// Mock Mode: priceTotalData가 없으면 가짜 데이터 제공
const effectivePriceTotalData = hasValidPriceTotalData ? priceTotalData : {
totProdPrc: 521.66,
totProdPrc: 0.00,
totDcAmt: 0,
totDlvrAmt: 0,
ordPmtNoTaxAmt: 521.66,
ordTotTaxAmt: 50,
ordPmtReqAmt: 571.66
ordPmtNoTaxAmt: 0.0,
ordTotTaxAmt: 0,
ordPmtReqAmt: 0.00,
};
const items = useMemo(

View File

@@ -185,6 +185,9 @@ export default function ProductAllSection({
const [isShowQRCode, setIsShowQRCode] = useState(true);
const timerRef = useRef(null);
//구매 하단 토스트 노출 확인을 위한 용도
const [openToast, setOpenToast] = useState(false);
// 모든 timeout/timer를 추적하기 위한 ref
const timersRef = useRef([]);
@@ -378,31 +381,36 @@ export default function ProductAllSection({
prdtNm: productData?.prdtNm,
hasProductData: !!productData,
});
if(openToast === false){
dispatch(
showToast({
id: productData.prdtId,
message: '',
type: 'buyOption',
duration: 0,
position: 'bottom-center',
// 🚀 BuyOption에 전달할 props 데이터
productInfo: productData,
selectedPatnrId: productData?.patnrId,
selectedPrdtId: productData?.prdtId,
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
onToastClose: () => {
setTimeout(() => {
setOpenToast(false);
Spotlight.focus('detail-buy-now-button');
}, 100);
},
})
);
dispatch(
showToast({
id: productData.prdtId,
message: '',
type: 'buyOption',
duration: 0,
position: 'bottom-center',
// 🚀 BuyOption에 전달할 props 데이터
productInfo: productData,
selectedPatnrId: productData?.patnrId,
selectedPrdtId: productData?.prdtId,
// BuyOption Toast가 닫힐 때 BUY NOW 버튼으로 포커스 복구
onToastClose: () => {
setTimeout(() => {
Spotlight.focus('detail-buy-now-button');
}, 100);
},
})
);
}, [dispatch, productData]);
setOpenToast(true);
}
}, [dispatch, productData, openToast]);
//닫히도록
const handleCloseToast = useCallback(() => {
dispatch(clearAllToasts())
dispatch(clearAllToasts());
setOpenToast(false);
},[dispatch])
// 스크롤 컨테이너의 클릭 이벤트 추적용 로깅

View File

@@ -1,20 +1,40 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { Job } from '@enact/core/util';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { clearThemeDetail } from '../../../../actions/homeActions';
import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions';
import {
popPanel,
pushPanel,
updatePanel,
} from '../../../../actions/panelActions';
import { finishVideoPreview } from '../../../../actions/playActions';
import THeader from '../../../../components/THeader/THeader';
import TItemCard from '../../../../components/TItemCard/TItemCard';
import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
import TVerticalPagenator
from '../../../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList
from '../../../../components/TVirtualGridList/TVirtualGridList';
import useScrollTo from '../../../../hooks/useScrollTo';
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
import {
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
panel_names,
} from '../../../../utils/Config';
import { $L } from '../../../../utils/helperMethods';
import css from './YouMayAlsoLike.module.less';
@@ -136,6 +156,7 @@ export default function YouMayAlsoLike({
patncNm,
brndNm,
lgCatCd,
euEnrgLblInfos,
} = product;
const handleItemClick = () => {
@@ -166,8 +187,9 @@ export default function YouMayAlsoLike({
cursorOpen.current.stop();
};
return (
<TItemCard
<TItemCardNew
key={prdtId}
className={css.itemCardNew}
contextName={LOG_CONTEXT_NAME.YOUMAYLIKE}
messageId={LOG_MESSAGE_ID.CONTENTCLICK}
productId={prdtId}
@@ -185,6 +207,7 @@ export default function YouMayAlsoLike({
onClick={handleItemClick}
label={index * 1 + 1 + ' of ' + newYoumaylikeProductData.length}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/>
);
})}

View File

@@ -62,7 +62,7 @@
display: flex;
flex-wrap: wrap;
// margin-top: 34px;
> div {
> div.itemCardNew {
/* item card */
margin: 0 15px 15px 0;
.size(@w:360px,@h:494px);
@@ -81,7 +81,7 @@
> div:nth-child(2) {
margin-top: 15px;
/* desc wrapper */
> div > h3 {
> div > div > h3 {
/* title */
color: rgba(234, 234, 234, 1);
.size(@w:100%,@h:64px);

View File

@@ -1,19 +1,59 @@
import React, {
useCallback,
useMemo,
useState,
} from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import Spottable from '@enact/spotlight/Spottable';
import {
setHidePopup,
setShowPopup,
} from '../../../../actions/commonActions';
import {
clearConvertedImage,
convertPdfToImage,
} from '../../../../actions/convertActions';
import CustomImage from '../../../../components/CustomImage/CustomImage';
import TPopUp from '../../../../components/TPopUp/TPopUp';
import usePriceInfo from '../../../../hooks/usePriceInfo';
import * as Config from '../../../../utils/Config';
import { $L } from '../../../../utils/helperMethods';
import { SpotlightIds } from '../../../../utils/SpotlightIds';
import BuyNowPriceDisplay from './BuyNowPriceDisplay/BuyNowPriceDisplay';
import css from './ProductPriceDisplay.module.less';
import ShopByMobilePriceDisplay
from './ShopByMobilePriceDisplay/ShopByMobilePriceDisplay';
const SpottableComponent = Spottable("div");
const STRING_CONF = {
ENERGY_LOADING: "Loading energy label...",
ENERGY_ERROR: "Failed to load energy label",
};
export default function ProductPriceDisplay({ productType, productInfo }) {
const dispatch = useDispatch();
const [currentPdfUrl, setCurrentPdfUrl] = useState(null);
const cursorVisible = useSelector(
(state) => state.common.appStatus.cursorVisible
);
const { activePopup, popupVisible } = useSelector(
(state) => state.common.popup
);
const convert = useSelector((state) => state.convert);
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
const webOSVersion = useSelector(
(state) => state.common.appStatus.webOSVersion
);
@@ -27,6 +67,7 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
patnrName,
installmentMonths,
orderPhnNo,
euEnrgLblInfos,
} = productInfo;
const {
@@ -65,6 +106,67 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
);
}, [productType, productInfo?.pmtSuptYn, webOSVersion]);
const handleClosePopup = useCallback(() => {
if (convert?.convertedImage && convert.convertedImage.startsWith("blob:")) {
URL.revokeObjectURL(convert.convertedImage);
}
dispatch(setHidePopup());
dispatch(clearConvertedImage());
setCurrentPdfUrl(null);
}, [dispatch, convert?.convertedImage]);
const onEnergyClick = useCallback(
(e, pdfUrl) => {
e.stopPropagation();
setCurrentPdfUrl(pdfUrl);
// PNG 이미지는 직접 표시
if (pdfUrl.endsWith(".png")) {
// console.log(`📸 [EnergyLabel] Displaying PNG directly:`, pdfUrl);
dispatch({
type: "CONVERT_PDF_TO_IMAGE_SUCCESS",
payload: { pdfUrl, imageUrl: pdfUrl },
});
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
setTimeout(() => {
Spotlight.focus(SpotlightIds.TPOPUP);
}, 250);
return;
}
// PDF 변환 시작 (최대 5회 재시도, 60초 타임아웃)
// console.log(`📄 [EnergyLabel] Starting PDF conversion:`, pdfUrl);
dispatch(
convertPdfToImage(
pdfUrl,
(error, imageUrl) => {
if (error) {
console.error(
"[EnergyLabel] 최종 변환 실패:",
error.message || error
);
// 실패해도 팝업은 열어서 에러 메시지 표시
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
setTimeout(() => {
Spotlight.focus(SpotlightIds.TPOPUP);
}, 250);
} else {
console.log(`[EnergyLabel] PDF 변환 완료, 팝업 표시`);
dispatch(setShowPopup(Config.ACTIVE_POPUP.energyPopup));
setTimeout(() => {
Spotlight.focus(SpotlightIds.TPOPUP);
}, 250);
}
},
5, // 최대 5회 재시도
60000 // 60초 타임아웃
)
);
},
[dispatch]
);
return (
<>
{productType && productInfo && (
@@ -91,8 +193,125 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
isDiscounted={isDiscounted}
/>
)}
<div className={css.enrgLbImgBox}>
{euEnrgLblInfos && (countryCode === "GB" || countryCode === "DE") && euEnrgLblInfos.map((item, index)=>{
return(
<SpottableComponent
key={index}
spotlightDisabled={Boolean(!cursorVisible)}
onClick={(e) => onEnergyClick(e, item.enrgLblUrl)}
aria-label={`Energy Efficiency ${item.enrgGrade || ""}`}
className={css.enrgLbImg}
>
<CustomImage
alt={`Energy Label ${item.enrgGrade || index + 1}`}
delay={0}
src={item.enrgLblIcnUrl}
/>
</SpottableComponent>
)
})}
</div>
</div>
)}
{(() => {
// 팝업이 표시되어야 하는 조건 검증
const isEnergyPopup = activePopup === Config.ACTIVE_POPUP.energyPopup;
const hasPdfUrl = !!currentPdfUrl;
const shouldShowPopup = isEnergyPopup && hasPdfUrl;
if (!shouldShowPopup) {
// console.log('[EnergyLabel] Popup not showing:', {
// isEnergyPopup,
// hasPdfUrl,
// popupVisible,
// });
return null;
}
// console.log('[EnergyLabel] Rendering popup:', {
// popupVisible,
// activePopup,
// currentPdfUrl,
// isConverting: convert?.isConverting,
// hasImage: !!convert?.convertedImage,
// hasError: !!convert?.error,
// });
return (
<TPopUp
kind="energyPopup"
title={$L("Energy Efficiency")}
hasText
open={popupVisible}
hasButton
button1Text={$L("CLOSE")}
onClose={handleClosePopup}
>
<div className={css.energyPopupContent}>
{convert ? (
<>
<div className={css.energyImagesContainer}>
{convert.convertedImage ? (
<img
alt="Energy Label"
src={convert.convertedImage}
className={css.energyImage}
/>
) : convert.error ? (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p style={{ fontSize: "0.8em", marginTop: "10px" }}>
{convert.error?.message || String(convert.error)}
</p>
</div>
) : convert.isConverting ? (
<div>
<p>{$L(STRING_CONF.ENERGY_LOADING)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Converting PDF to image... (attempt in progress)
</p>
</div>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Unknown state - no image or error
</p>
</div>
)}
</div>
</>
) : (
<div>
<p>{$L(STRING_CONF.ENERGY_ERROR)}</p>
<p
style={{
fontSize: "0.8em",
marginTop: "10px",
color: "#999",
}}
>
Convert reducer state not found
</p>
</div>
)}
</div>
</TPopUp>
);
})()}
</>
);
}

View File

@@ -99,3 +99,90 @@
}
}
}
.enrgLbImgBox {
display:flex;
justify-content: flex-start;
align-items: center;
.enrgLbImg {
width:62px;
height:38px;
border:4px solid transparent;
&:focus {
border: 4px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
border-radius: 12px;
box-sizing: border-box;
}
> img {
width:100%;
}
}
}
.popupContainer {
.header {
.size(@w: 780px , @h: 102px);
.flex(@display: flex, @justifyCenter: center, @alignCenter: center, @direction: row);
background-color: #e7ebef;
> h3 {
font-size: 36px;
color: #222222;
font-weight: bold;
}
}
.qrcodeContainer {
padding: 30px 0;
display: flex;
flex-direction: column;
align-items: center;
.qrcode {
.size(@w: 360px , @h: 360px);
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 0 0 1px #dadada inset;
margin-bottom: 41px;
}
> h3 {
display: flex;
text-align: center;
word-break: break-word;
line-height: 1.27;
}
.popupBtn {
.size(@w: 300px , @h: 78px);
margin-top: 38px;
}
}
}
// 🔽 에너지 라벨 팝업 스타일 (1920x1080 TV 화면 기준)
.energyPopupContent {
width: 100%;
max-height: 800px; // 팝업 타이틀/버튼 영역 제외한 콘텐츠 최대 높이
overflow-y: auto;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.energyImagesContainer {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.energyImage {
max-width: 100%;
max-height: 750px; // 1080px - 타이틀(~120px) - 버튼(~120px) - 여백(~90px)
width: auto;
height: auto;
object-fit: contain; // 비율 유지하면서 컨테이너에 맞춤
}

View File

@@ -15,7 +15,10 @@ import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import { addToCart } from '../../../actions/cartActions';
import {
addToCart,
insertMyinfoCart,
} from '../../../actions/cartActions';
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
import {
changeAppStatus,
@@ -33,7 +36,10 @@ import {
popPanel,
pushPanel,
} from '../../../actions/panelActions';
import { clearAllVideoTimers, finishVideoPreview } from '../../../actions/playActions';
import {
clearAllVideoTimers,
finishVideoPreview,
} from '../../../actions/playActions';
import {
getProductOption,
getProductOptionId,
@@ -1081,17 +1087,18 @@ const BuyOption = ({
if (!isMock) {
dispatch(
addToCart({
insertMyinfoCart({
mbrNo: userNumber,
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodQty: String(effectiveQuantity),
prdtOpt: {
prodOptCdCval: optionForUse?.prodOptCdCval || '',
prodOptCval: optionLabel,
prodQty: Number(effectiveQuantity),
prdtOpt: [{
prodOptSno: productOptionInfos?.[0]?.prodOptSno || '',
prodOptCdCval: optionForUse?.prodOptCdCval || '',
prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '',
},
prodOptCval: optionLabel,
}],
})
);
@@ -1405,7 +1412,7 @@ const BuyOption = ({
{/* 동적 옵션 렌더링 */}
{productOptionInfos &&
productOptionInfos?.length > 0 &&
(productInfo?.optProdYn === 'Y' || hasMockOptions) && (
productInfo?.optProdYn === 'Y' && (
<>
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
{productOptionInfos?.length > 1 && (

View File

@@ -68,17 +68,60 @@ const BestSeller = ({
const { cursorVisible } = useSelector((state) => state.common.appStatus);
const bestSellerDatas = useSelector(
(state) => state.product.bestSellerData?.bestSeller
(state) => state.product.bestSellerData?.bestSeller
);
const bestSellerNewDatas = useSelector(
(state) =>
state.foryou?.recommendInfo?.recommendProduct
);
const [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0);
const [bestInfos, setBestInfos] = useState(null);
const [bestItemNewData, setBestItemNewData] = useState([]);
useEffect(()=>{
setBestInfos(
bestSellerNewDatas?.filter(
(item) => item.recommendTpCd === "BESTSELLER"
) || [] // 기본값으로 빈 배열 설정
)
},[bestSellerNewDatas])
useEffect(() => {
if (!bestInfos || bestInfos.length === 0) {
const baseData = bestSellerDatas?.map((item) => ({
...item,
foryou: false,
})) || [];
setBestItemNewData(baseData);
return;
}
const recommendedData = bestInfos[0].productInfos?.map((item) => ({
...item,
foryou: true,
})) || [];
const recommendedPrdtIds = new Set(recommendedData.map(item => item.prdtId));
const baseData = bestSellerDatas?.map((item) => ({
...item,
foryou: recommendedPrdtIds.has(item.prdtId),
})) || [];
setBestItemNewData(baseData);
}, [bestSellerDatas, bestInfos]);
const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(() => {
setDrawChk(true);
}, [bestSellerDatas]);
}, [bestSellerNewDatas]);
const handleCardClick = useCallback(
(patnrId, prdtId) => () => {
@@ -177,21 +220,6 @@ const BestSeller = ({
}
}, [handleShelfFocus]);
const [bestSellerNewData, setBestSellerNewData] = useState([]);
const _randomProduct = useCallback(() => {
const randomChk = Math.round(Math.random()) === 0 ? false : true;
return randomChk;
}, []);
useEffect(() => {
setBestSellerNewData(
bestSellerDatas?.map((item) => ({
...item,
// foryou: _randomProduct(),
}))
);
}, [bestSellerDatas]);
return (
<Container
@@ -213,8 +241,8 @@ const BestSeller = ({
cbScrollTo={getScrollTo}
noScrollByWheel
>
{bestSellerNewData &&
bestSellerNewData.map(
{bestItemNewData &&
bestItemNewData.map(
(
{
prdtId,
@@ -226,8 +254,8 @@ const BestSeller = ({
offerInfo,
brndNm,
patncNm,
catNm,
//foryou,
//catNm, 없음
foryou,
euEnrgLblInfos,
},
itemIndex
@@ -251,7 +279,7 @@ const BestSeller = ({
shelfTitle={shelfTitle}
patnerName={patncNm}
brandName={brndNm}
catNm={catNm}
// catNm={catNm}
imageAlt={prdtId}
imageSource={imgUrl}
priceInfo={priceInfo}
@@ -265,11 +293,11 @@ const BestSeller = ({
offerInfo={offerInfo}
spotlightId={"bestsellerItem" + itemIndex}
firstLabel={rankText}
label={itemIndex * 1 + 1 + " of " + bestSellerNewData.length}
label={itemIndex * 1 + 1 + " of " + bestItemNewData.length}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
>
{/* {foryou === true && <Tag text={"For You"} />} */}
{foryou === true && <Tag text={"For You"} />}
</TItemCardNew>
);
}

View File

@@ -28,7 +28,7 @@ import {
setOptionalTermsUserDecision,
updateOptionalTermsAgreement,
} from '../../../actions/commonActions';
import { getJustForYouInfo } from '../../../actions/forYouActions';
import { justForYou } from '../../../actions/forYouActions';
import {
fetchCurrentUserHomeTerms,
setDefaultFocus,
@@ -39,11 +39,11 @@ import {
pushPanel,
} from '../../../actions/panelActions';
import {
clearAllVideoTimers,
releasePlayControl,
requestPlayControl,
startVideoPlayer,
startVideoPlayerNew,
clearAllVideoTimers,
} from '../../../actions/playActions';
import CustomImage from '../../../components/CustomImage/CustomImage';
// import TButtonScroller from "../../../components/TButtonScroller/TButtonScroller";
@@ -83,7 +83,7 @@ export default function HomeBanner({
}) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getJustForYouInfo());
dispatch(justForYou());
}, [dispatch]);
const homeTopDisplayInfo = useSelector(
(state) => state.home.homeTopDisplayInfo

View File

@@ -107,7 +107,7 @@ export default function RollingUnit({
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const introTermsAgree = useSelector(
(state) => state.common.termsFlag.optionalTerms
(state) => state.common.optionalTermsAgree
);
const homeCategory = useSelector(
(state) => state.home.menuData?.data?.homeCategory
@@ -120,7 +120,7 @@ export default function RollingUnit({
// 컴포넌트 상단에서 필터링
const filteredRollingData = useMemo(() => {
return rollingData.filter(
(item) => introTermsAgree === "Y" || item.shptmLnkTpCd !== "DSP00510"
(item) => introTermsAgree === true || item.shptmLnkTpCd !== "DSP00510"
);
}, [rollingData, introTermsAgree]);

View File

@@ -1,7 +1,17 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { applyMiddleware } from 'redux';
import Spotlight from '@enact/spotlight';
import {
@@ -23,20 +33,31 @@ import {
getHomeMainContents,
updateHomeInfo,
} from '../../actions/homeActions';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
import {
sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import {
getSubCategory,
getTop20Show,
} from '../../actions/mainActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
import { finishVideoPreview, shrinkVideoTo1px, expandVideoFrom1px } from '../../actions/playActions';
import { updatePanel } from '../../actions/panelActions';
import {
expandVideoFrom1px,
finishVideoPreview,
shrinkVideoTo1px,
} from '../../actions/playActions';
import { getBestSeller } from '../../actions/productActions';
import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton';
import TPanel from '../../components/TPanel/TPanel';
import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
import TVerticalPagenator
from '../../components/TVerticalPagenator/TVerticalPagenator';
import useDebugKey from '../../hooks/useDebugKey';
import usePrevious from '../../hooks/usePrevious';
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
import usePrevious from '../../hooks/usePrevious';
import { useVideoPlay } from '../../hooks/useVideoPlay/useVideoPlay';
import { useVideoMove } from '../../hooks/useVideoTransition/useVideoMove';
import {
@@ -55,7 +76,7 @@ import css from '../HomePanel/HomePanel.module.less';
import PopularShow from '../HomePanel/PopularShow/PopularShow';
import SubCategory from '../HomePanel/SubCategory/SubCategory';
import EventPopUpBanner from './EventPopUpBanner/EventPopUpBanner';
import { applyMiddleware } from 'redux';
import PickedForYou from './PickedForYou/PickedForYou';
export const TEMPLATE_CODE_CONF = {
TOP: 'DSP00101',
@@ -63,6 +84,7 @@ export const TEMPLATE_CODE_CONF = {
ON_SALE: 'DSP00103',
POPULAR_SHOW: 'DSP00104',
BEST_SELLER: 'DSP00105',
PICK_FOR_YOU: 'DSP00106',
};
const HomePanel = ({ isOnTop }) => {
@@ -251,6 +273,9 @@ const HomePanel = ({ isOnTop }) => {
case TEMPLATE_CODE_CONF.BEST_SELLER:
nowMenu = LOG_MENU.HOME_BEST_SELLER;
break;
case TEMPLATE_CODE_CONF.PICK_FOR_YOU:
nowMenu = LOG_MENU.HOME_PICKED_FOR_YOU;
break;
default:
nowMenu = LOG_MENU.HOME_TOP;
break;
@@ -384,6 +409,24 @@ const HomePanel = ({ isOnTop }) => {
);
} else break;
}
case TEMPLATE_CODE_CONF.PICK_FOR_YOU: {
if (bestSellerDatas && bestSellerDatas.length > 0) {
return (
<PickedForYou
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
}
})}
{loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
@@ -593,6 +636,7 @@ const HomePanel = ({ isOnTop }) => {
pageSize: 10,
tabType: 'CAT00102',
filterType: 'CAT00202',
recommendIncFlag: 'Y',
},
1
)

View File

@@ -16,12 +16,9 @@ import {
} from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import {
pushPanel,
updatePanel,
} from '../../../actions/panelActions';
import { pushPanel } from '../../../actions/panelActions';
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
import TItemCard from '../../../components/TItemCard/TItemCard';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller';
import useScrollReset from '../../../hooks/useScrollReset';
import useScrollTo from '../../../hooks/useScrollTo';
@@ -30,10 +27,7 @@ import {
LOG_MESSAGE_ID,
panel_names,
} from '../../../utils/Config';
import {
$L,
scaleW,
} from '../../../utils/helperMethods';
import { $L } from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds';
import css from './PickedForYou.module.less';
@@ -52,7 +46,7 @@ const PickedForYou = ({
shelfLocation,
shelfTitle,
}) => {
console.log("###Test pjh 여기지 ?");
const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset(
scrollLeft,
@@ -63,18 +57,28 @@ const PickedForYou = ({
const { cursorVisible } = useSelector((state) => state.common.appStatus);
const justForYouDatas = useSelector(
(state) => state.product.bestSellerData?.bestSeller
);
const justForYouDatasNew = useSelector(
(state) => state.foryou?.recommendInfo?.recommendProduct
)
const [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0);
const [pickedForYou, setPickedForYou] = useState(null);
const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(()=>{
setPickedForYou(
justForYouDatasNew?.filter(
(item) => item.recommendTpCd === "PICKEDFORYOU"
)
)
},[justForYouDatasNew])
useEffect(() => {
setDrawChk(true);
}, [justForYouDatas]);
}, [justForYouDatasNew]);
const handleCardClick = useCallback(
(patnrId, prdtId) => () => {
@@ -193,25 +197,26 @@ const PickedForYou = ({
cbScrollTo={getScrollTo}
noScrollByWheel
>
{justForYouDatas &&
justForYouDatas.map(
{pickedForYou &&
pickedForYou?.[0] &&
pickedForYou?.[0].productInfos.map(
(
{
prdtId,
imgUrl,
priceInfo,
prdtNm,
rankOrd,
patnrId,
offerInfo,
brndNm,
patncNm,
catNm,
euEnrgLblInfos
},
itemIndex
) => {
return (
<TItemCard
<TItemCardNew
key={"subItem" + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
@@ -226,15 +231,15 @@ const PickedForYou = ({
imageSource={imgUrl}
priceInfo={priceInfo}
productName={prdtNm}
isBestSeller={true}
productId={prdtId}
onFocus={handleFocus(itemIndex)}
onBlur={handleBlur(itemIndex)}
onClick={handleCardClick(patnrId, prdtId)}
offerInfo={offerInfo}
spotlightId={"bestsellerItem" + itemIndex}
label={itemIndex * 1 + 1 + " of " + justForYouDatas.length}
label={itemIndex * 1 + 1 + " of " + pickedForYou?.[0].productInfos.length}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/>
);
}

View File

@@ -71,17 +71,57 @@ const PopularShow = ({
const { cursorVisible } = useSelector((state) => state.common.appStatus);
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
const recommendInfo = useSelector((state) => state.foryou?.recommendInfo?.recommendShow);
const [drawChk, setDrawChk] = useState(false);
const orderStyle = useMemo(() => ({ order: order }), [order]);
const [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0);
const [showInfos, setShowInfos] = useState(null);
const [showNewInfos, setShowNewInfos] = useState([]);
useEffect(() => {
setDrawChk(true);
}, [topInfos]);
useEffect(()=>{
setShowInfos(
recommendInfo?.filter(
(item) => item.recommendTpCd === "POPULARSHOW"
) || []
)
},[recommendInfo])
useEffect(() => {
if (!showInfos || showInfos.length === 0) {
const baseData = topInfos?.map((item) => ({
...item,
foryou: false,
})) || [];
setShowNewInfos(baseData);
return;
}
const recommendedData = showInfos[0].showInfos?.map((item) => ({
...item,
foryou: true,
})) || [];
const recommendedPrdtIds = new Set(recommendedData?.map(item => item.showId));
const baseData = topInfos?.map((item) => ({
...item,
foryou: recommendedPrdtIds.has(item.showId),
})) || [];
setShowNewInfos([ ...baseData]);
}, [topInfos, showInfos]);
const handleCardClick = useCallback(
(patnrId, showId, catCd, showUrl) => () => {
dispatch(
@@ -189,22 +229,6 @@ const PopularShow = ({
}
}, [handleShelfFocus]);
const [topInfosNewData, setTopInfosNewData] = useState([]);
const _randomProduct = useCallback(() => {
const randomChk = Math.round(Math.random()) === 0 ? false : true;
return randomChk;
}, []);
useEffect(() => {
setTopInfosNewData(
topInfos?.map((item) => ({
...item,
// foryou: _randomProduct(),
}))
);
}, [topInfos]);
return (
<Container
className={css.popularShow}
@@ -225,8 +249,8 @@ const PopularShow = ({
cbScrollTo={getScrollTo}
noScrollByWheel
>
{topInfosNewData &&
topInfosNewData.map(
{showNewInfos &&
showNewInfos.map(
(
{
showId,
@@ -240,7 +264,7 @@ const PopularShow = ({
patncNm,
catCd,
showUrl,
//foryou,
foryou,
},
itemIndex
) => {
@@ -258,9 +282,11 @@ const PopularShow = ({
contentId={showId}
contentTitle={showNm}
imageSource={
(thumbnailUrl && thumbnailUrl960) ?
thumbnailUrl !== thumbnailUrl960
? thumbnailUrl960
: thumbnailUrl
: thumbnailUrl
}
imageAlt={showNm}
productName={showNm}
@@ -277,10 +303,10 @@ const PopularShow = ({
onBlur={handleBlur(itemIndex)}
onClick={handleCardClick(patnrId, showId, catCd, showUrl)}
firstLabel={patncNm + " "}
label={itemIndex * 1 + 1 + " of " + topInfos.length}
label={itemIndex * 1 + 1 + " of " + showNewInfos.length}
lastLabel=" go to detail, button"
>
{/* {foryou === true && <Tag text={"For You"} />} */}
{foryou === true && <Tag text={"For You"} />}
</TItemCardNew>
);
}

View File

@@ -16,7 +16,6 @@ import {
import Spottable from '@enact/spotlight/Spottable';
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import { getRecentlySawItem } from '../../../actions/forYouActions';
import { sendLogCuration } from '../../../actions/logActions';
import { getSubCategory } from '../../../actions/mainActions';
import { pushPanel } from '../../../actions/panelActions';
@@ -71,6 +70,10 @@ export default memo(function SubCategory({
(state) => state.main.subCategoryData?.categoryItemInfos
);
const foruItemInfos = useSelector(
(state) => state.main.recommendProduct[0]?.productInfos
);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const [currentLgCatCd, setCurrentLgCatCd] = useState(catCd ? catCd : null);
@@ -110,6 +113,7 @@ export default memo(function SubCategory({
pageSize: "10",
tabType: "CAT00102",
filterType: "CAT00202",
recommendIncFlag: 'Y',
},
1
)
@@ -117,7 +121,7 @@ export default memo(function SubCategory({
}
setDrawChk(true);
}
}, [currentLgCatCd, dispatch]);
}, [currentLgCatCd, dispatch, firstChk]);
useEffect(() => {
if (!nowMenuRef.current || !nowMenuRef.current.startsWith("Home")) {
@@ -134,7 +138,7 @@ export default memo(function SubCategory({
dispatch(sendLogCuration(params));
}
}, [categoryInfos, currentLgCatCd, currentLgCatNm]);
}, [categoryInfos, currentLgCatCd, currentLgCatNm, dispatch, nowMenuRef]);
const handleCategoryNav = useCallback(
(lgCatCd, lgCatNm) => {
@@ -149,7 +153,7 @@ export default memo(function SubCategory({
setContainerLastFocusedElement(null, [SpotlightIds.HOME_CATEGORY_NAV]);
},
[currentLgCatCd]
[currentLgCatCd, handleScrollReset]
);
const handleFocus = useCallback(
@@ -160,7 +164,7 @@ export default memo(function SubCategory({
handleScrollReset();
}
},
[_handleItemFocus]
[_handleItemFocus, handleScrollReset]
);
const handleBlur = useCallback(
@@ -169,7 +173,7 @@ export default memo(function SubCategory({
handleStopScrolling();
}
},
[]
[handleStopScrolling]
);
const handleCardClick = useCallback(
@@ -181,7 +185,7 @@ export default memo(function SubCategory({
})
);
},
[]
[dispatch, spotlightId]
);
const handleScrollRight = useCallback((e) => {
@@ -189,7 +193,7 @@ export default memo(function SubCategory({
const x = container.scrollWidth - container.clientWidth;
setTimeout(() => scrollLeft({ x, animate: true }));
}, []);
}, [scrollLeft]);
const handleMoreCardClick = useCallback((e) => {
const lgCatCd = e.currentTarget?.getAttribute("data-catcd-num");
@@ -208,7 +212,7 @@ export default memo(function SubCategory({
})
);
}
}, []);
}, [dispatch]);
const _handleItemFocus = useCallback(() => {
if (handleItemFocus) {
@@ -222,27 +226,30 @@ export default memo(function SubCategory({
}
}, [handleShelfFocus]);
const _randomProduct = useCallback(() => {
const randomChk = Math.round(Math.random()) === 0 ? false : true;
return randomChk;
}, []);
useEffect(() => {
setCategoryItemNewData(
categoryItemInfos?.subCatItemList?.map((item) => ({
if (!foruItemInfos || foruItemInfos.length === 0) {
const baseData = categoryItemInfos?.subCatItemList?.map((item) => ({
...item,
foryou: _randomProduct(),
}))
);
}, [categoryItemInfos?.subCatItemList]);
foryou: false,
})) || [];
setCategoryItemNewData(baseData);
return;
}
useEffect(() => {
dispatch(
getRecentlySawItem(
categoryItemNewData.filter((item) => item.foryou === true)
)
);
}, [categoryItemNewData, dispatch]);
const recommendedData = foruItemInfos?.map((item) => ({
...item,
foryou: true,
})) || [];
const recommendedPrdtIds = new Set(recommendedData.map(item => item.prdtId));
const baseData = categoryItemInfos?.subCatItemList?.map((item) => ({
...item,
foryou: recommendedPrdtIds.has(item.prdtId),
})) || [];
setCategoryItemNewData([...baseData]);
}, [categoryItemInfos?.subCatItemList, foruItemInfos]);
return (
<Container
@@ -267,59 +274,6 @@ export default memo(function SubCategory({
cbScrollTo={getScrollTo}
noScrollByWheel
>
{/* {categoryItemInfos &&
categoryItemInfos?.subCatItemList.map(
(
{
prdtId,
imgUrl,
priceInfo,
prdtNm,
patnrId,
offerInfo,
brndNm,
patncNm,
},
itemIndex
) => {
return (
<TItemCardNew
key={"subItem" + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
catNm={categoryItemInfos.catNm}
order={itemIndex + 1}
shelfId={spotlightId}
shelfLocation={shelfLocation}
shelfTitle={shelfTitle}
brandName={brndNm}
patnerName={patncNm}
imageAlt={prdtId}
imageSource={imgUrl}
priceInfo={priceInfo}
productName={prdtNm}
productId={prdtId}
onFocus={handleFocus(itemIndex)}
onBlur={handleBlur(itemIndex)}
onClick={handleCardClick(patnrId, prdtId)}
offerInfo={offerInfo}
data-catcd-num={currentLgCatCd}
data-catcd-nm={currentLgCatNm}
label={
itemIndex * 1 +
1 +
" of " +
categoryItemInfos?.subCatItemList.length
}
lastLabel=" go to detail, button"
>
<Tag text={"For You"} />
</TItemCardNew>
);
}
)} 원본 보관*/}
{categoryItemNewData &&
categoryItemNewData.map(
(
@@ -332,7 +286,8 @@ export default memo(function SubCategory({
offerInfo,
brndNm,
patncNm,
//foryou,
foryou,
euEnrgLblInfos,
},
itemIndex
) => {
@@ -341,7 +296,7 @@ export default memo(function SubCategory({
key={"subItem" + itemIndex}
contextName={LOG_CONTEXT_NAME.HOME}
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
catNm={categoryItemInfos.catNm}
catNm={categoryItemInfos?.catNm}
order={itemIndex + 1}
shelfId={spotlightId}
shelfLocation={shelfLocation}
@@ -363,11 +318,12 @@ export default memo(function SubCategory({
itemIndex * 1 +
1 +
" of " +
categoryItemInfos?.subCatItemList.length
(categoryItemNewData?.length || 0)
}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
>
{/* {foryou === true && <Tag text={"For You"} />} */}
{foryou === true && <Tag text={"For You"} />}
</TItemCardNew>
);
}

View File

@@ -47,11 +47,11 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
const cbChangePageRef = useRef(null);
const shelfInfos = useSelector(
(state) => state.foryou?.justForYouInfo?.shelfInfos
(state) => state.foryou?.recommendInfo?.recommendProduct
);
const justForYouInfo = useSelector(
(state) => state.foryou?.justForYouInfo?.justForYouInfo
(state) => state.foryou?.recommendInfo?.justForYouInfo
);
const onClick = useCallback(() => {
@@ -81,24 +81,18 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
const product = productInfos[index];
const {
contentId,
title,
thumbnail,
price,
dcPrice,
partnerName,
partnerLogo,
prdtNm,
imgUrl,
priceInfo,
patncNm,
patnrId,
prdtId,
} = product;
const handleItemClick = () => {
// Extract product ID from contentId if needed
const tokens = contentId.split("_");
const patnrId = tokens?.[4] || "";
const prdtId = tokens?.[5] || "";
dispatch(
pushPanel({
name: panel_names.DETAIL_PANEL,
// panelInfo: { prdtId: contentId },
panelInfo: {
patnrId: patnrId,
prdtId: prdtId,
@@ -109,16 +103,14 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
return (
<TItemCardNew
key={contentId}
patnerName={partnerName}
imageAlt={title}
imageSource={thumbnail}
key={prdtId}
patnerName={patncNm}
imageAlt={prdtNm}
imageSource={imgUrl}
label={`${index + 1} of ${productInfos.length}`}
lastLabel=" go to detail, button"
dcPrice={dcPrice}
originPrice={price}
productName={title}
productId={contentId}
priceInfo={priceInfo}
productName={prdtNm}
onClick={handleItemClick}
spotlightId={`spotlightId-${shelfId}-${shelfExpsOrd}-${index}`}
{...rest}
@@ -169,7 +161,7 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
key={shelf.shelfId}
className={classNames(
css.itemsContainer,
shelfIndex === 0 && css.itemsContinerFirst
shelfIndex === 1 && css.itemsContinerFirst
)}
spotlightId={`justForYouList_${shelf.shelfExpsOrd}`}
data-wheel-point

View File

@@ -1,10 +1,21 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Dropdown from '@enact/sandstone/Dropdown';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png';
@@ -14,7 +25,8 @@ import { hideShopperHouseError } from '../../actions/searchActions';
import CustomImage from '../../components/CustomImage/CustomImage';
import TButtonTab, { LIST_TYPE } from '../../components/TButtonTab/TButtonTab';
import TDropDown from '../../components/TDropDown/TDropDown';
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
import TVirtualGridList
from '../../components/TVirtualGridList/TVirtualGridList';
import { panel_names } from '../../utils/Config';
import { $L } from '../../utils/helperMethods';
import { SpotlightIds } from '../../utils/SpotlightIds';
@@ -24,8 +36,6 @@ import HowAboutTheseSmall from './HowAboutThese/HowAboutThese.small';
import css from './SearchResults.new.v2.module.less';
import ItemCard from './SearchResultsNew/ItemCard';
import ShowCard from './SearchResultsNew/ShowCard';
import Dropdown from '@enact/sandstone/Dropdown';
const ITEMS_PER_PAGE = 10;
@@ -926,7 +936,7 @@ const SearchResultsNew = ({
No matches, Showing suggestions
</div>
)}
{hasShopperHouseItems && (
<Dropdown
className={classNames(
css.dropdown,
@@ -938,7 +948,7 @@ const SearchResultsNew = ({
>
{filterMethods}
</Dropdown>
)}
</TabContainer>
{/* 아이템/쇼 컨텐츠 */}

View File

@@ -4,7 +4,10 @@ import { useDispatch } from 'react-redux';
import Spotlight from '@enact/spotlight';
import { pushPanel, updatePanel } from '../../../actions/panelActions';
import {
pushPanel,
updatePanel,
} from '../../../actions/panelActions';
import { navigateToDetailPanel } from '../../../actions/panelNavigationActions';
import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller';
@@ -62,7 +65,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
<>
<TScroller className={css.container} spotlightId={SpotlightIds.SEARCH_ITEM}>
{itemInfo.map((item, index) => {
const { thumbnail, title, dcPrice, price, soldout, contentId } = item;
const { thumbnail, title, dcPrice, price, soldout, contentId,euEnrgLblInfos } = item;
const tokens = contentId && contentId.split('_');
const patnrId = tokens?.[4] || '';
const prdtId = tokens?.[5] || '';
@@ -83,6 +86,7 @@ const ItemCard = ({ onClick, itemInfo, searchQuery }) => {
{...(index === 0 ? { 'data-spotlight-up': 'searchtabContainer' } : {})}
label={index * 1 + 1 + ' of ' + itemInfo.length + 1}
lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/>
);
})}