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', GET_MY_INFO_SHIPPING_SEARCH: 'GET_MY_INFO_SHIPPING_SEARCH',
// cart actions // cart actions
GET_MY_INFO_CART_SEARCH: 'GET_MY_INFO_CART_SEARCH',
ADD_TO_CART: 'ADD_TO_CART', ADD_TO_CART: 'ADD_TO_CART',
REMOVE_FROM_CART: 'REMOVE_FROM_CART', REMOVE_FROM_CART: 'REMOVE_FROM_CART',
UPDATE_CART_ITEM: 'UPDATE_CART_ITEM', UPDATE_CART_ITEM: 'UPDATE_CART_ITEM',
CLEAR_CART: 'CLEAR_CART', 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 // appData actions
ADD_MAIN_INDEX: 'ADD_MAIN_INDEX', ADD_MAIN_INDEX: 'ADD_MAIN_INDEX',
@@ -309,10 +314,7 @@ export const types = {
SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY', SET_BANNER_VISIBILITY: 'SET_BANNER_VISIBILITY',
// 🔽 [추가] JustForYou 상품 관리 부분 // 🔽 [추가] JustForYou 상품 관리 부분
GET_RECENTLY_SAW_ITEM: 'GET_RECENTLY_SAW_ITEM', JUSTFORYOU: 'JUSTFORYOU',
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',
// 🔽 Voice Conductor 관련 액션 타입 // 🔽 Voice Conductor 관련 액션 타입
VOICE_REGISTER_SUCCESS: 'VOICE_REGISTER_SUCCESS', VOICE_REGISTER_SUCCESS: 'VOICE_REGISTER_SUCCESS',

View File

@@ -1,6 +1,7 @@
import { URLS } from '../api/apiConfig'; import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios'; import { TAxios } from '../api/TAxios';
import { types } from './actionTypes'; 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 - 장바구니 상품 정보 * @param {Object} props - 장바구니 상품 정보

View File

@@ -1,6 +1,7 @@
import { URLS } from '../api/apiConfig'; import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios'; import { TAxios } from '../api/TAxios';
import { types } from './actionTypes'; import { types } from './actionTypes';
import { getReAuthenticationCode } from './deviceActions';
/** /**
* PDF를 이미지로 변환 (재시도 로직 포함) * PDF를 이미지로 변환 (재시도 로직 포함)
@@ -80,55 +81,61 @@ export const convertPdfToImage =
return; return;
} }
let imageUrl; if(response.data.type !== "image/png"){
try { dispatch(getReAuthenticationCode());
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(); attemptConversion();
} else { return;
console.error( }
`❌ [EnergyLabel] Final failure after ${attempts} attempts (image error):`,
pdfUrl let imageUrl;
); try {
dispatch({ if (response.data instanceof Blob) {
type: types.CONVERT_PDF_TO_IMAGE_FAILURE, if (response.data.size === 0) {
payload: { pdfUrl, error }, throw new Error('Invalid image data (empty blob)');
}); }
callback && callback(error, null); 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) => { const onFail = (error) => {

View File

@@ -1,36 +1,16 @@
import { URLS } from '../api/apiConfig'; import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios'; import { TAxios } from '../api/TAxios';
import { import { get } from '../utils/fp';
curry,
get,
set,
} from '../utils/fp';
import { types } from './actionTypes'; import { types } from './actionTypes';
import { changeAppStatus } from './commonActions'; import { changeAppStatus } from './commonActions';
// 최근 본 아이템 관련 액션 export const justForYou = (callback) => (dispatch, getState) => {
export const getRecentlySawItem = (data) => ({ const macAddress = getState().common.macAddress;
type: types.GET_RECENTLY_SAW_ITEM, const macAddr = macAddress?.wired || macAddress?.wifi || "00:1A:2B:3C:4D:5E";
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) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getJustForYouInfo onSuccess", response.data); console.log("JustForYou onSuccess", response.data);
dispatch({ dispatch({
type: types.GET_JUSTFORYOU_INFO, type: types.JUSTFORYOU,
payload: get("data.data", response), payload: get("data.data", response),
}); });
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
@@ -38,7 +18,7 @@ export const getJustForYouInfo = (callback) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getJustForYouInfo onFail", error); console.error("JustForYou onFail", error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
callback && callback(); callback && callback();
}; };
@@ -46,10 +26,10 @@ export const getJustForYouInfo = (callback) => (dispatch, getState) => {
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", "post",
URLS.GET_JUSTFORYOU_INFO, URLS.JUSTFORYOU,
{},
{}, {},
{macAddr},
onSuccess, onSuccess,
onFail onFail
); );

View File

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

View File

@@ -20,10 +20,15 @@ export const URLS = {
GET_MY_INFO_CARD_SEARCH: "/lgsp/v1/myinfo/card/search.lge", GET_MY_INFO_CARD_SEARCH: "/lgsp/v1/myinfo/card/search.lge",
// cart controller // cart controller
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge", ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge",
REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge", REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge",
UPDATE_CART_ITEM: "/lgsp/v1/myinfo/cart/update.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 // shipping controller
GET_MY_INFO_SHIPPING_SEARCH: "/lgsp/v1/myinfo/address/shipping/search.lge", 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", PAYMENT_TOTAL_CANCEL: "/lgsp/v1/myinfo/order/paymentTotalCancel.lge",
// foryou controller // foryou controller
GET_JUSTFORYOU_INFO: "/lgsp/v1/justforyou/list.lge", JUSTFORYOU: "/lgsp/v1/recommend/justforyou.lge",
// emp controller // emp controller
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge", GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { types } from "../actions/actionTypes"; import { types } from '../actions/actionTypes';
/** /**
* Cart Reducer 초기 상태 * Cart Reducer 초기 상태
@@ -20,7 +20,7 @@ const initialState = {
*/ */
export const cartReducer = (state = initialState, action) => { export const cartReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
// 장바구니 조회 // 장바구니 조회 - API에서 가져온 전체 목록
case types.GET_MY_INFO_CART_SEARCH: case types.GET_MY_INFO_CART_SEARCH:
return { return {
...state, ...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: case types.ADD_TO_CART:
return { return {
...state, ...state,
@@ -43,7 +80,7 @@ export const cartReducer = (state = initialState, action) => {
error: null, error: null,
}; };
// 장바구니에서 상품 제거 // 장바구니에서 상품 제거 (removeFromCart)
case types.REMOVE_FROM_CART: case types.REMOVE_FROM_CART:
return { return {
...state, ...state,
@@ -55,19 +92,19 @@ export const cartReducer = (state = initialState, action) => {
error: null, error: null,
}; };
// 장바구니 상품 수량 업데이트 // 장바구니 상품 수량 업데이트 (updateCartItem)
case types.UPDATE_CART_ITEM: case types.UPDATE_CART_ITEM:
return { return {
...state, ...state,
lastAction: { lastAction: {
type: "update", type: "updateQty",
data: action.payload, data: action.payload,
timestamp: Date.now(), timestamp: Date.now(),
}, },
error: null, error: null,
}; };
// 장바구니 전체 비우기 // 장바구니 전체 비우기 (clearCart - 로컬 상태 전용)
case types.CLEAR_CART: case types.CLEAR_CART:
return { return {
...state, ...state,

View File

@@ -5,50 +5,16 @@ import {
set, set,
} from '../utils/fp'; } from '../utils/fp';
// 초기 상태 정의
// const initialState = {
// recentlySawItems: [],
// likeBrandProducts: [],
// moreToConciderAtThisPrice: [],
// };
const initialState = { const initialState = {
justForYouInfo: {}, recommendInfo: {},
}; };
// const foryouReducer = (state = initialState, action) => { const recommendInfo = curry((state, action) =>
// switch (action.type) { set("recommendInfo", get("payload", action), state)
// // 최근 본 아이템
// 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 handlers = { const handlers = {
[types.GET_JUSTFORYOU_INFO]: justForYouInfo, [types.JUSTFORYOU]: recommendInfo,
}; };
export const foryouReducer = (state = initialState, action = {}) => { export const foryouReducer = (state = initialState, action = {}) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,6 @@ export default memo(function OrderItemCard({
const priceTotalData = useSelector( const priceTotalData = useSelector(
(state) => state.checkout?.checkoutTotalData (state) => state.checkout?.checkoutTotalData
); );
console.log("###priceTotalData",priceTotalData);
const formattedPrices = useMemo(() => { const formattedPrices = useMemo(() => {
return { return {
@@ -83,7 +82,7 @@ export default memo(function OrderItemCard({
<p className={css.priceWrap}> <p className={css.priceWrap}>
<span className={css.itemPrice}> <span className={css.itemPrice}>
<span className={css.discountedPrice}> <span className={css.discountedPrice}>
$ {(price * prodQty).toLocaleString('en-US', { $ {(Number(price) * Number(prodQty)).toLocaleString('en-US', {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 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 Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; 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 { popPanel } from '../../../actions/panelActions';
import TButton from '../../../components/TButton/TButton'; import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp'; import TPopUp from '../../../components/TPopUp/TPopUp';
import TQRCode from '../../../components/TQRCode/TQRCode'; import TQRCode from '../../../components/TQRCode/TQRCode';
import useScrollTo from '../../../hooks/useScrollTo'; import useScrollTo from '../../../hooks/useScrollTo';
import useScrollTopByDistance from '../../../hooks/useScrollTopByDistance'; import useScrollTopByDistance from '../../../hooks/useScrollTopByDistance';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import * as Config from '../../../utils/Config'; import * as Config from '../../../utils/Config';
import { $L, getQRCodeUrl } from '../../../utils/helperMethods'; import {
$L,
getQRCodeUrl,
} from '../../../utils/helperMethods';
import BillingAddressCard from '../components/BillingAddressCard'; import BillingAddressCard from '../components/BillingAddressCard';
import PaymentCard from '../components/PaymentCard'; import PaymentCard from '../components/PaymentCard';
import ShippingAddressCard from '../components/ShippingAddressCard'; import ShippingAddressCard from '../components/ShippingAddressCard';
import Subject from '../components/Subject'; import Subject from '../components/Subject';
import css from './InformationContainer.module.less'; import css from './InformationContainer.module.less';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
@@ -282,9 +297,12 @@ export default function InformationContainer({
ADD/EDIT ADD/EDIT
</TButton> </TButton>
{/* BillingAddressCard disabled due to infinite render loop in Mock Mode */} {/* 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 Mock Billing Address
</div> </div> */}
{checkoutData?.billingAddressList && (
<BillingAddressCard list={checkoutData.billingAddressList} onFocus={handleFocus} />
)}
</div> </div>
<div className={css.listBox}> <div className={css.listBox}>
<Subject title="PATMENT METHOD" /> <Subject title="PATMENT METHOD" />

View File

@@ -165,7 +165,7 @@ export default function OrderItemsSideBar({
patncLogPath={item.patncLogPath} patncLogPath={item.patncLogPath}
prdtId={item.prdtId} prdtId={item.prdtId}
expsPrdtNo={item.expsPrdtNo} expsPrdtNo={item.expsPrdtNo}
price={item.price >= item.originalPrice ? item.originalPrice : item.price} price={item.price2 >= item.price3 ? item.price3 : item.price2}
currSign={item.currSign} currSign={item.currSign}
currSignLoc={item.currSignLoc} currSignLoc={item.currSignLoc}
shippingCharge={item.shippingCharge} 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 {
import { popPanel } from "../../../actions/panelActions"; setHidePopup,
import TButton from "../../../components/TButton/TButton"; setShowPopup,
import TPopUp from "../../../components/TPopUp/TPopUp"; } from '../../../actions/commonActions';
import * as Config from "../../../utils/Config"; 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 { import {
$L, $L,
formatCurrencyValue, formatCurrencyValue,
getErrorMessage, getErrorMessage,
} from "../../../utils/helperMethods"; } from '../../../utils/helperMethods';
import css from "./SummaryContainer.module.less"; import css from './SummaryContainer.module.less';
import { sendLogTotalRecommend } from "../../../actions/logActions";
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" }, { enterTo: "last-focused" },
@@ -51,12 +62,12 @@ export default function SummaryContainer({
// Mock Mode: priceTotalData가 없으면 가짜 데이터 제공 // Mock Mode: priceTotalData가 없으면 가짜 데이터 제공
const effectivePriceTotalData = hasValidPriceTotalData ? priceTotalData : { const effectivePriceTotalData = hasValidPriceTotalData ? priceTotalData : {
totProdPrc: 521.66, totProdPrc: 0.00,
totDcAmt: 0, totDcAmt: 0,
totDlvrAmt: 0, totDlvrAmt: 0,
ordPmtNoTaxAmt: 521.66, ordPmtNoTaxAmt: 0.0,
ordTotTaxAmt: 50, ordTotTaxAmt: 0,
ordPmtReqAmt: 571.66 ordPmtReqAmt: 0.00,
}; };
const items = useMemo( const items = useMemo(

View File

@@ -185,6 +185,9 @@ export default function ProductAllSection({
const [isShowQRCode, setIsShowQRCode] = useState(true); const [isShowQRCode, setIsShowQRCode] = useState(true);
const timerRef = useRef(null); const timerRef = useRef(null);
//구매 하단 토스트 노출 확인을 위한 용도
const [openToast, setOpenToast] = useState(false);
// 모든 timeout/timer를 추적하기 위한 ref // 모든 timeout/timer를 추적하기 위한 ref
const timersRef = useRef([]); const timersRef = useRef([]);
@@ -378,31 +381,36 @@ export default function ProductAllSection({
prdtNm: productData?.prdtNm, prdtNm: productData?.prdtNm,
hasProductData: !!productData, 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( setOpenToast(true);
showToast({ }
id: productData.prdtId, }, [dispatch, productData, openToast]);
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]);
//닫히도록 //닫히도록
const handleCloseToast = useCallback(() => { const handleCloseToast = useCallback(() => {
dispatch(clearAllToasts()) dispatch(clearAllToasts());
setOpenToast(false);
},[dispatch]) },[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 { Job } from '@enact/core/util';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import { clearThemeDetail } from '../../../../actions/homeActions'; 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 { finishVideoPreview } from '../../../../actions/playActions';
import THeader from '../../../../components/THeader/THeader'; import THeader from '../../../../components/THeader/THeader';
import TItemCard from '../../../../components/TItemCard/TItemCard'; import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator'; import TVerticalPagenator
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList'; from '../../../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList
from '../../../../components/TVirtualGridList/TVirtualGridList';
import useScrollTo from '../../../../hooks/useScrollTo'; 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 { $L } from '../../../../utils/helperMethods';
import css from './YouMayAlsoLike.module.less'; import css from './YouMayAlsoLike.module.less';
@@ -136,6 +156,7 @@ export default function YouMayAlsoLike({
patncNm, patncNm,
brndNm, brndNm,
lgCatCd, lgCatCd,
euEnrgLblInfos,
} = product; } = product;
const handleItemClick = () => { const handleItemClick = () => {
@@ -166,8 +187,9 @@ export default function YouMayAlsoLike({
cursorOpen.current.stop(); cursorOpen.current.stop();
}; };
return ( return (
<TItemCard <TItemCardNew
key={prdtId} key={prdtId}
className={css.itemCardNew}
contextName={LOG_CONTEXT_NAME.YOUMAYLIKE} contextName={LOG_CONTEXT_NAME.YOUMAYLIKE}
messageId={LOG_MESSAGE_ID.CONTENTCLICK} messageId={LOG_MESSAGE_ID.CONTENTCLICK}
productId={prdtId} productId={prdtId}
@@ -185,6 +207,7 @@ export default function YouMayAlsoLike({
onClick={handleItemClick} onClick={handleItemClick}
label={index * 1 + 1 + ' of ' + newYoumaylikeProductData.length} label={index * 1 + 1 + ' of ' + newYoumaylikeProductData.length}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos}
/> />
); );
})} })}

View File

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

View File

@@ -1,19 +1,59 @@
import React, { import React, {
useCallback, useCallback,
useMemo, useMemo,
useState,
} from 'react'; } from 'react';
import classNames from 'classnames'; 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 usePriceInfo from '../../../../hooks/usePriceInfo';
import * as Config from '../../../../utils/Config';
import { $L } from '../../../../utils/helperMethods'; import { $L } from '../../../../utils/helperMethods';
import { SpotlightIds } from '../../../../utils/SpotlightIds';
import BuyNowPriceDisplay from './BuyNowPriceDisplay/BuyNowPriceDisplay'; import BuyNowPriceDisplay from './BuyNowPriceDisplay/BuyNowPriceDisplay';
import css from './ProductPriceDisplay.module.less'; import css from './ProductPriceDisplay.module.less';
import ShopByMobilePriceDisplay import ShopByMobilePriceDisplay
from './ShopByMobilePriceDisplay/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 }) { 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( const webOSVersion = useSelector(
(state) => state.common.appStatus.webOSVersion (state) => state.common.appStatus.webOSVersion
); );
@@ -27,6 +67,7 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
patnrName, patnrName,
installmentMonths, installmentMonths,
orderPhnNo, orderPhnNo,
euEnrgLblInfos,
} = productInfo; } = productInfo;
const { const {
@@ -65,6 +106,67 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
); );
}, [productType, productInfo?.pmtSuptYn, webOSVersion]); }, [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 ( return (
<> <>
{productType && productInfo && ( {productType && productInfo && (
@@ -91,8 +193,125 @@ export default function ProductPriceDisplay({ productType, productInfo }) {
isDiscounted={isDiscounted} 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> </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 import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator'; from '@enact/spotlight/SpotlightContainerDecorator';
import { addToCart } from '../../../actions/cartActions'; import {
addToCart,
insertMyinfoCart,
} from '../../../actions/cartActions';
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions'; import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
import { import {
changeAppStatus, changeAppStatus,
@@ -33,7 +36,10 @@ import {
popPanel, popPanel,
pushPanel, pushPanel,
} from '../../../actions/panelActions'; } from '../../../actions/panelActions';
import { clearAllVideoTimers, finishVideoPreview } from '../../../actions/playActions'; import {
clearAllVideoTimers,
finishVideoPreview,
} from '../../../actions/playActions';
import { import {
getProductOption, getProductOption,
getProductOptionId, getProductOptionId,
@@ -1081,17 +1087,18 @@ const BuyOption = ({
if (!isMock) { if (!isMock) {
dispatch( dispatch(
addToCart({ insertMyinfoCart({
mbrNo: userNumber, mbrNo: userNumber,
patnrId: selectedPatnrId, patnrId: selectedPatnrId,
prdtId: selectedPrdtId, prdtId: selectedPrdtId,
prodQty: String(effectiveQuantity), prodQty: Number(effectiveQuantity),
prdtOpt: { prdtOpt: [{
prodOptCdCval: optionForUse?.prodOptCdCval || '',
prodOptCval: optionLabel,
prodOptSno: productOptionInfos?.[0]?.prodOptSno || '', prodOptSno: productOptionInfos?.[0]?.prodOptSno || '',
prodOptCdCval: optionForUse?.prodOptCdCval || '',
prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '', prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '',
}, prodOptCval: optionLabel,
}],
}) })
); );
@@ -1405,7 +1412,7 @@ const BuyOption = ({
{/* 동적 옵션 렌더링 */} {/* 동적 옵션 렌더링 */}
{productOptionInfos && {productOptionInfos &&
productOptionInfos?.length > 0 && productOptionInfos?.length > 0 &&
(productInfo?.optProdYn === 'Y' || hasMockOptions) && ( productInfo?.optProdYn === 'Y' && (
<> <>
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */} {/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
{productOptionInfos?.length > 1 && ( {productOptionInfos?.length > 1 && (

View File

@@ -68,17 +68,60 @@ const BestSeller = ({
const { cursorVisible } = useSelector((state) => state.common.appStatus); const { cursorVisible } = useSelector((state) => state.common.appStatus);
const bestSellerDatas = useSelector( 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 [drawChk, setDrawChk] = useState(false);
const [firstChk, setFirstChk] = useState(0); 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]); const orderStyle = useMemo(() => ({ order: order }), [order]);
useEffect(() => { useEffect(() => {
setDrawChk(true); setDrawChk(true);
}, [bestSellerDatas]); }, [bestSellerNewDatas]);
const handleCardClick = useCallback( const handleCardClick = useCallback(
(patnrId, prdtId) => () => { (patnrId, prdtId) => () => {
@@ -177,21 +220,6 @@ const BestSeller = ({
} }
}, [handleShelfFocus]); }, [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 ( return (
<Container <Container
@@ -213,8 +241,8 @@ const BestSeller = ({
cbScrollTo={getScrollTo} cbScrollTo={getScrollTo}
noScrollByWheel noScrollByWheel
> >
{bestSellerNewData && {bestItemNewData &&
bestSellerNewData.map( bestItemNewData.map(
( (
{ {
prdtId, prdtId,
@@ -226,8 +254,8 @@ const BestSeller = ({
offerInfo, offerInfo,
brndNm, brndNm,
patncNm, patncNm,
catNm, //catNm, 없음
//foryou, foryou,
euEnrgLblInfos, euEnrgLblInfos,
}, },
itemIndex itemIndex
@@ -251,7 +279,7 @@ const BestSeller = ({
shelfTitle={shelfTitle} shelfTitle={shelfTitle}
patnerName={patncNm} patnerName={patncNm}
brandName={brndNm} brandName={brndNm}
catNm={catNm} // catNm={catNm}
imageAlt={prdtId} imageAlt={prdtId}
imageSource={imgUrl} imageSource={imgUrl}
priceInfo={priceInfo} priceInfo={priceInfo}
@@ -265,11 +293,11 @@ const BestSeller = ({
offerInfo={offerInfo} offerInfo={offerInfo}
spotlightId={"bestsellerItem" + itemIndex} spotlightId={"bestsellerItem" + itemIndex}
firstLabel={rankText} firstLabel={rankText}
label={itemIndex * 1 + 1 + " of " + bestSellerNewData.length} label={itemIndex * 1 + 1 + " of " + bestItemNewData.length}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
euEnrgLblInfos={euEnrgLblInfos} euEnrgLblInfos={euEnrgLblInfos}
> >
{/* {foryou === true && <Tag text={"For You"} />} */} {foryou === true && <Tag text={"For You"} />}
</TItemCardNew> </TItemCardNew>
); );
} }

View File

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

View File

@@ -107,7 +107,7 @@ export default function RollingUnit({
const nowMenu = useSelector((state) => state.common.menu.nowMenu); const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu); const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const introTermsAgree = useSelector( const introTermsAgree = useSelector(
(state) => state.common.termsFlag.optionalTerms (state) => state.common.optionalTermsAgree
); );
const homeCategory = useSelector( const homeCategory = useSelector(
(state) => state.home.menuData?.data?.homeCategory (state) => state.home.menuData?.data?.homeCategory
@@ -120,7 +120,7 @@ export default function RollingUnit({
// 컴포넌트 상단에서 필터링 // 컴포넌트 상단에서 필터링
const filteredRollingData = useMemo(() => { const filteredRollingData = useMemo(() => {
return rollingData.filter( return rollingData.filter(
(item) => introTermsAgree === "Y" || item.shptmLnkTpCd !== "DSP00510" (item) => introTermsAgree === true || item.shptmLnkTpCd !== "DSP00510"
); );
}, [rollingData, introTermsAgree]); }, [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 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 Spotlight from '@enact/spotlight';
import { import {
@@ -23,20 +33,31 @@ import {
getHomeMainContents, getHomeMainContents,
updateHomeInfo, updateHomeInfo,
} from '../../actions/homeActions'; } from '../../actions/homeActions';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions'; import {
import { getSubCategory, getTop20Show } from '../../actions/mainActions'; sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import {
getSubCategory,
getTop20Show,
} from '../../actions/mainActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions'; import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
import { finishVideoPreview, shrinkVideoTo1px, expandVideoFrom1px } from '../../actions/playActions';
import { updatePanel } from '../../actions/panelActions'; import { updatePanel } from '../../actions/panelActions';
import {
expandVideoFrom1px,
finishVideoPreview,
shrinkVideoTo1px,
} from '../../actions/playActions';
import { getBestSeller } from '../../actions/productActions'; import { getBestSeller } from '../../actions/productActions';
import TBody from '../../components/TBody/TBody'; import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton'; import TButton, { TYPES } from '../../components/TButton/TButton';
import TPanel from '../../components/TPanel/TPanel'; import TPanel from '../../components/TPanel/TPanel';
import TPopUp from '../../components/TPopUp/TPopUp'; import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator'; import TVerticalPagenator
from '../../components/TVerticalPagenator/TVerticalPagenator';
import useDebugKey from '../../hooks/useDebugKey'; import useDebugKey from '../../hooks/useDebugKey';
import usePrevious from '../../hooks/usePrevious';
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory'; import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
import usePrevious from '../../hooks/usePrevious';
import { useVideoPlay } from '../../hooks/useVideoPlay/useVideoPlay'; import { useVideoPlay } from '../../hooks/useVideoPlay/useVideoPlay';
import { useVideoMove } from '../../hooks/useVideoTransition/useVideoMove'; import { useVideoMove } from '../../hooks/useVideoTransition/useVideoMove';
import { import {
@@ -55,7 +76,7 @@ import css from '../HomePanel/HomePanel.module.less';
import PopularShow from '../HomePanel/PopularShow/PopularShow'; import PopularShow from '../HomePanel/PopularShow/PopularShow';
import SubCategory from '../HomePanel/SubCategory/SubCategory'; import SubCategory from '../HomePanel/SubCategory/SubCategory';
import EventPopUpBanner from './EventPopUpBanner/EventPopUpBanner'; import EventPopUpBanner from './EventPopUpBanner/EventPopUpBanner';
import { applyMiddleware } from 'redux'; import PickedForYou from './PickedForYou/PickedForYou';
export const TEMPLATE_CODE_CONF = { export const TEMPLATE_CODE_CONF = {
TOP: 'DSP00101', TOP: 'DSP00101',
@@ -63,6 +84,7 @@ export const TEMPLATE_CODE_CONF = {
ON_SALE: 'DSP00103', ON_SALE: 'DSP00103',
POPULAR_SHOW: 'DSP00104', POPULAR_SHOW: 'DSP00104',
BEST_SELLER: 'DSP00105', BEST_SELLER: 'DSP00105',
PICK_FOR_YOU: 'DSP00106',
}; };
const HomePanel = ({ isOnTop }) => { const HomePanel = ({ isOnTop }) => {
@@ -251,6 +273,9 @@ const HomePanel = ({ isOnTop }) => {
case TEMPLATE_CODE_CONF.BEST_SELLER: case TEMPLATE_CODE_CONF.BEST_SELLER:
nowMenu = LOG_MENU.HOME_BEST_SELLER; nowMenu = LOG_MENU.HOME_BEST_SELLER;
break; break;
case TEMPLATE_CODE_CONF.PICK_FOR_YOU:
nowMenu = LOG_MENU.HOME_PICKED_FOR_YOU;
break;
default: default:
nowMenu = LOG_MENU.HOME_TOP; nowMenu = LOG_MENU.HOME_TOP;
break; break;
@@ -384,6 +409,24 @@ const HomePanel = ({ isOnTop }) => {
); );
} else break; } 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 && ( {loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
@@ -593,6 +636,7 @@ const HomePanel = ({ isOnTop }) => {
pageSize: 10, pageSize: 10,
tabType: 'CAT00102', tabType: 'CAT00102',
filterType: 'CAT00202', filterType: 'CAT00202',
recommendIncFlag: 'Y',
}, },
1 1
) )

View File

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

View File

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

View File

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

View File

@@ -47,11 +47,11 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
const cbChangePageRef = useRef(null); const cbChangePageRef = useRef(null);
const shelfInfos = useSelector( const shelfInfos = useSelector(
(state) => state.foryou?.justForYouInfo?.shelfInfos (state) => state.foryou?.recommendInfo?.recommendProduct
); );
const justForYouInfo = useSelector( const justForYouInfo = useSelector(
(state) => state.foryou?.justForYouInfo?.justForYouInfo (state) => state.foryou?.recommendInfo?.justForYouInfo
); );
const onClick = useCallback(() => { const onClick = useCallback(() => {
@@ -81,24 +81,18 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
const product = productInfos[index]; const product = productInfos[index];
const { const {
contentId, prdtNm,
title, imgUrl,
thumbnail, priceInfo,
price, patncNm,
dcPrice, patnrId,
partnerName, prdtId,
partnerLogo,
} = product; } = product;
const handleItemClick = () => { const handleItemClick = () => {
// Extract product ID from contentId if needed
const tokens = contentId.split("_");
const patnrId = tokens?.[4] || "";
const prdtId = tokens?.[5] || "";
dispatch( dispatch(
pushPanel({ pushPanel({
name: panel_names.DETAIL_PANEL, name: panel_names.DETAIL_PANEL,
// panelInfo: { prdtId: contentId },
panelInfo: { panelInfo: {
patnrId: patnrId, patnrId: patnrId,
prdtId: prdtId, prdtId: prdtId,
@@ -109,16 +103,14 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
return ( return (
<TItemCardNew <TItemCardNew
key={contentId} key={prdtId}
patnerName={partnerName} patnerName={patncNm}
imageAlt={title} imageAlt={prdtNm}
imageSource={thumbnail} imageSource={imgUrl}
label={`${index + 1} of ${productInfos.length}`} label={`${index + 1} of ${productInfos.length}`}
lastLabel=" go to detail, button" lastLabel=" go to detail, button"
dcPrice={dcPrice} priceInfo={priceInfo}
originPrice={price} productName={prdtNm}
productName={title}
productId={contentId}
onClick={handleItemClick} onClick={handleItemClick}
spotlightId={`spotlightId-${shelfId}-${shelfExpsOrd}-${index}`} spotlightId={`spotlightId-${shelfId}-${shelfExpsOrd}-${index}`}
{...rest} {...rest}
@@ -169,7 +161,7 @@ const JustForYouTestPanel = ({ panelInfo, ...rest }) => {
key={shelf.shelfId} key={shelf.shelfId}
className={classNames( className={classNames(
css.itemsContainer, css.itemsContainer,
shelfIndex === 0 && css.itemsContinerFirst shelfIndex === 1 && css.itemsContinerFirst
)} )}
spotlightId={`justForYouList_${shelf.shelfExpsOrd}`} spotlightId={`justForYouList_${shelf.shelfExpsOrd}`}
data-wheel-point 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 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 Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable'; import Spottable from '@enact/spotlight/Spottable';
import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png'; 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 CustomImage from '../../components/CustomImage/CustomImage';
import TButtonTab, { LIST_TYPE } from '../../components/TButtonTab/TButtonTab'; import TButtonTab, { LIST_TYPE } from '../../components/TButtonTab/TButtonTab';
import TDropDown from '../../components/TDropDown/TDropDown'; 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 { panel_names } from '../../utils/Config';
import { $L } from '../../utils/helperMethods'; import { $L } from '../../utils/helperMethods';
import { SpotlightIds } from '../../utils/SpotlightIds'; import { SpotlightIds } from '../../utils/SpotlightIds';
@@ -24,8 +36,6 @@ import HowAboutTheseSmall from './HowAboutThese/HowAboutThese.small';
import css from './SearchResults.new.v2.module.less'; import css from './SearchResults.new.v2.module.less';
import ItemCard from './SearchResultsNew/ItemCard'; import ItemCard from './SearchResultsNew/ItemCard';
import ShowCard from './SearchResultsNew/ShowCard'; import ShowCard from './SearchResultsNew/ShowCard';
import Dropdown from '@enact/sandstone/Dropdown';
const ITEMS_PER_PAGE = 10; const ITEMS_PER_PAGE = 10;
@@ -926,7 +936,7 @@ const SearchResultsNew = ({
No matches, Showing suggestions No matches, Showing suggestions
</div> </div>
)} )}
{hasShopperHouseItems && (
<Dropdown <Dropdown
className={classNames( className={classNames(
css.dropdown, css.dropdown,
@@ -938,7 +948,7 @@ const SearchResultsNew = ({
> >
{filterMethods} {filterMethods}
</Dropdown> </Dropdown>
)}
</TabContainer> </TabContainer>
{/* 아이템/쇼 컨텐츠 */} {/* 아이템/쇼 컨텐츠 */}

View File

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