From ddf7c352ebabbb0dc05d750d46dc7cf9ea646c2a Mon Sep 17 00:00:00 2001 From: "junghoon86.park" Date: Fri, 12 Sep 2025 10:39:41 +0900 Subject: [PATCH] =?UTF-8?q?[=EC=83=81=ED=92=88=20=EC=83=81=EC=84=B8]=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=8F=20=EB=94=94=ED=85=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EA=B1=B4.#3=20=091.=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A7=84=EC=9E=85=EC=8B=9C=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20=ED=8F=AC=EC=BB=A4=EC=8A=A4=20shop=20by=20mobile=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD.=20=09=09-=20spotlightids?= =?UTF-8?q?=EC=B6=94=EA=B0=80.=20=092.=20=EC=83=81=ED=92=88=EC=83=81?= =?UTF-8?q?=EC=84=B8=20scroll=20down=EC=B6=94=EA=B0=80.=E2=80=A8=09=09-=20?= =?UTF-8?q?=ED=95=98=EB=8B=A8=EB=B6=80=20=EB=8F=84=EB=8B=AC=ED=96=88?= =?UTF-8?q?=EC=9D=84=EB=95=8C=20=EB=85=B8=EC=B6=9C=EB=90=98=EC=A7=80?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=B2=98=EB=A6=AC.=20=09=09-=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=EC=8B=9C=20200px=EC=94=A9=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99.=20=093.=20=EB=A6=AC=EB=B7=B0=ED=8C=9D=EC=97=85=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=09=09-=20=ED=98=B8=EB=B2=84=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=99=95=EB=8C=80=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EB=B6=80=EC=9E=90=EC=97=B0=EC=8A=A4=EB=9F=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD.=20=094.=20?= =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=83=81=EC=84=B8=20=EC=9A=B0=EC=B8=A1=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=EC=97=90=EC=84=9C=EC=9D=98=20=ED=8F=AC?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=20=EC=9D=B4=EB=8F=99=EC=8B=9C=20=EC=A2=8C?= =?UTF-8?q?=EC=B8=A1=20=EB=B2=84=ED=8A=BC=EB=B6=80=EB=B6=84=EC=9D=98=20?= =?UTF-8?q?=ED=98=B8=EB=B2=84=EC=B2=98=EB=A6=AC.=20=09=09-=20=ED=8F=AC?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=20=EC=9D=B4=EB=8F=99=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?=EC=9E=90=EC=97=B0=EC=8A=A4=EB=9F=BD=EA=B2=8C=20=ED=98=B8?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=8F=99=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../images/icons/ic_arrow_down_3x_new.png | Bin 0 -> 715 bytes .../src/utils/SpotlightIds.js | 1 + .../src/views/DetailPanel/DetailPanel.jsx | 630 +++++++++++------- .../views/DetailPanel/DetailPanel.module.less | 24 +- .../ProductAllSection/ProductAllSection.jsx | 379 ++++++++--- .../ProductAllSection.module.less | 53 +- .../UserReviewsPopup.module.less | 50 +- .../YouMayAlsoLike/YouMayAlsoLike.jsx | 41 +- .../src/views/UserReview/UserReviewPanel.jsx | 14 +- .../UserReview/UserReviewPanel.module.less | 5 +- 10 files changed, 796 insertions(+), 401 deletions(-) create mode 100644 com.twin.app.shoptime/assets/images/icons/ic_arrow_down_3x_new.png diff --git a/com.twin.app.shoptime/assets/images/icons/ic_arrow_down_3x_new.png b/com.twin.app.shoptime/assets/images/icons/ic_arrow_down_3x_new.png new file mode 100644 index 0000000000000000000000000000000000000000..a67e947b97f61067a643eea7e04b20fd8e85ef03 GIT binary patch literal 715 zcmV;+0yO=JP)0sT za?)b?we^g#V_kH}^Q`>_q0B;}9p^RB$I;u?khMfRzLUbm$#3)v9UI9IJFWwClzzh< z^sFT#?0ELjv#b}Wpu-)tE6D&--nOaG89JQAr6dd~uWT#kE-0Z_Adg8^qTTNe^pssi zL+3zAOT;~S^((JdylWQTCUGGNVE1zc{mi4(e215){gP|!62ToDl`w$&rOpy5q`ddW zV+(viPjt-(QeHp*C|hI;J<&uy@E6AZPDi#_2|bfbL`+k&6eU8}92ibsZHKgx&oN2! z0s7{E!5H&dgf(C%l?Z)A8!wiEJFkq^d?4kG+ZX{iEg1Y)2G<-|%M^f*d`lSsaLs|O zObNKiXZn^1ZoTz`{beQbqdB8<34cR=e(+arA7fRmpJm4Z< z7h9PV;L=JPakQnzrYDJ|%o%Wz?<21cxX4$^hda state.main.productData); const isLoading = useSelector((state) => - fp.pipe(() => state, fp.get('common.appStatus.showLoadingPanel.show'))() + fp.pipe(() => state, fp.get("common.appStatus.showLoadingPanel.show"))() ); const themeData = useSelector((state) => fp.pipe( () => state, - fp.get('home.productData.themeInfo'), - (list) => (list && list[0]) + fp.get("home.productData.themeInfo"), + (list) => list && list[0] )() ); const webOSVersion = useSelector( - (state) => state.common.appStatus.webOSVersion, + (state) => state.common.appStatus.webOSVersion ); const panels = useSelector((state) => state.panels.panels); // FP 방식으로 상태 관리 const [selectedIndex, setSelectedIndex] = useState(0); const localRecentItems = useSelector((state) => - fp.pipe(() => state, fp.get('localSettings.recentItems'))() + fp.pipe(() => state, fp.get("localSettings.recentItems"))() ); const { httpHeader } = useSelector((state) => state.common); const { popupVisible, activePopup } = useSelector( - (state) => state.common.popup, + (state) => state.common.popup ); const [lgCatCd, setLgCatCd] = useState(""); const [themeProductInfo, setThemeProductInfo] = useState(null); const containerRef = useRef(null); - - const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]); - const panelCurationId = useMemo(() => fp.pipe(() => panelInfo, fp.get('curationId'))(), [panelInfo]); - const panelPatnrId = useMemo(() => fp.pipe(() => panelInfo, fp.get('patnrId'))(), [panelInfo]); - const panelPrdtId = useMemo(() => fp.pipe(() => panelInfo, fp.get('prdtId'))(), [panelInfo]); - const panelLiveReqFlag = useMemo(() => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(), [panelInfo]); - const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]); - const productPmtSuptYn = useMemo(() => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), [productData]); - const productGrPrdtProcYn = useMemo(() => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), [productData]); + const panelType = useMemo( + () => fp.pipe(() => panelInfo, fp.get("type"))(), + [panelInfo] + ); + const panelCurationId = useMemo( + () => fp.pipe(() => panelInfo, fp.get("curationId"))(), + [panelInfo] + ); + const panelPatnrId = useMemo( + () => fp.pipe(() => panelInfo, fp.get("patnrId"))(), + [panelInfo] + ); + const panelPrdtId = useMemo( + () => fp.pipe(() => panelInfo, fp.get("prdtId"))(), + [panelInfo] + ); + const panelLiveReqFlag = useMemo( + () => fp.pipe(() => panelInfo, fp.get("liveReqFlag"))(), + [panelInfo] + ); + const panelBgImgNo = useMemo( + () => fp.pipe(() => panelInfo, fp.get("bgImgNo"))(), + [panelInfo] + ); + const productPmtSuptYn = useMemo( + () => fp.pipe(() => productData, fp.get("pmtSuptYn"))(), + [productData] + ); + const productGrPrdtProcYn = useMemo( + () => fp.pipe(() => productData, fp.get("grPrdtProcYn"))(), + [productData] + ); - - const productDataSource = useMemo(() => - fp.pipe( - () => panelType, - type => type === "theme" ? themeData : productData - )(), + const productDataSource = useMemo( + () => + fp.pipe( + () => panelType, + (type) => (type === "theme" ? themeData : productData) + )(), [panelType, themeData, productData] ); @@ -102,17 +134,16 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // FP 방식으로 상태 업데이트 함수들 const updateSelectedIndex = useCallback((newIndex) => { - setSelectedIndex(fp.pipe( - () => newIndex, - index => Math.max(0, Math.min(index, 999)) // 범위 제한 - )()); + setSelectedIndex( + fp.pipe( + () => newIndex, + (index) => Math.max(0, Math.min(index, 999)) // 범위 제한 + )() + ); }, []); const updateThemeItemOverlay = useCallback((isOpen) => { - setOpenThemeItemOverlay(fp.pipe( - () => isOpen, - Boolean - )()); + setOpenThemeItemOverlay(fp.pipe(() => isOpen, Boolean)()); }, []); // FP 방식으로 이벤트 핸들러 정의 @@ -131,27 +162,26 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { }, () => { // 패널 업데이트 조건 체크 - const shouldUpdatePanel = fp.pipe( - () => panels, - fp.get('length'), - length => length === 4 - )() && fp.pipe( - () => panels, - fp.get('1.name'), - name => name === panel_names.PLAYER_PANEL - )(); - + const shouldUpdatePanel = + fp.pipe( + () => panels, + fp.get("length"), + (length) => length === 4 + )() && + fp.pipe( + () => panels, + fp.get("1.name"), + (name) => name === panel_names.PLAYER_PANEL + )(); + if (shouldUpdatePanel) { dispatch( updatePanel({ name: panel_names.PLAYER_PANEL, panelInfo: { - thumbnail: fp.pipe( - () => panelInfo, - fp.get('thumbnailUrl') - )(), + thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(), }, - }), + }) ); } } @@ -161,7 +191,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { ev.stopPropagation(); } }, - [dispatch, panelInfo, panels], + [dispatch, panelInfo, panels] ); // FP 방식으로 스크롤 함수 핸들러 @@ -175,56 +205,63 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => ({ scrollToSection, sectionId }), ({ scrollToSection, sectionId }) => { if (fp.isNotNil(scrollToSection)) { - return { action: 'execute', scrollFunction: scrollToSection, sectionId }; + return { + action: "execute", + scrollFunction: scrollToSection, + sectionId, + }; } else { - return { action: 'store', sectionId }; + return { action: "store", sectionId }; } } )(); // 액션에 따른 처리 - if (scrollAction.action === 'execute') { + if (scrollAction.action === "execute") { scrollAction.scrollFunction(scrollAction.sectionId); } else { console.log( - "DetailPanel: scrollToSection function is null, storing pending scroll", + "DetailPanel: scrollToSection function is null, storing pending scroll" ); setPendingScrollSection(scrollAction.sectionId); } }, - [scrollToSection], + [scrollToSection] ); // ===== 고정 배경 이미지 설정 (detailPanelBg만 사용) ===== // 모든 DetailPanel에서 동일한 배경 이미지(detailpanel-bg-1.png) 사용 useEffect(() => { - console.log('[PartnerId] Partner background info:', { + console.log("[PartnerId] Partner background info:", { panelPatnrId: panelPatnrId, productDataPatnrId: productData?.patnrId, productDataPatncNm: productData?.patncNm, productDataThumbnailUrl960: productData?.thumbnailUrl960, imageUrl: imageUrl, - detailPanelBg: detailPanelBg + detailPanelBg: detailPanelBg, }); - + // 고정 배경 이미지만 설정 (파트너사별 변경 없이) - document.documentElement.style.setProperty('--bg-url', `url(${detailPanelBg})`); + document.documentElement.style.setProperty( + "--bg-url", + `url(${detailPanelBg})` + ); }, [panelPatnrId, productData, imageUrl]); // FP 방식으로 pending scroll 처리 (메모리 누수 방지) useEffect(() => { const shouldExecutePendingScroll = fp.pipe( () => ({ scrollToSection, pendingScrollSection }), - ({ scrollToSection, pendingScrollSection }) => + ({ scrollToSection, pendingScrollSection }) => fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection) )(); if (shouldExecutePendingScroll) { console.log( "DetailPanel: executing pending scroll to:", - pendingScrollSection, + pendingScrollSection ); - + // 메모리 누수 방지를 위한 cleanup 함수 const timeoutId = setTimeout(() => { if (scrollToSection) { @@ -252,14 +289,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { () => { // 테마 데이터 로딩 const isThemeType = panelType === "theme"; - + if (isThemeType) { dispatch( getThemeCurationDetailInfo({ patnrId: panelPatnrId, curationId: panelCurationId, bgImgNo: panelBgImgNo, - }), + }) ); } }, @@ -267,14 +304,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 일반 상품 데이터 로딩 const hasProductId = fp.isNotNil(panelPrdtId); const hasNoCuration = fp.isNil(panelCurationId); - + if (hasProductId && hasNoCuration) { dispatch( getMainCategoryDetail({ patnrId: panelPatnrId, prdtId: panelPrdtId, liveReqFlag: panelLiveReqFlag || "N", - }), + }) ); } } @@ -296,10 +333,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // FP 방식으로 추천 상품 데이터 로딩 (메모리 누수 방지) useEffect(() => { - const shouldLoadRecommendations = fp.pipe( - () => lgCatCd, - fp.isNotEmpty - )(); + const shouldLoadRecommendations = fp.pipe(() => lgCatCd, fp.isNotEmpty)(); if (shouldLoadRecommendations) { dispatch( @@ -308,62 +342,82 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { exclCurationId: panelCurationId, exclPatnrId: panelPatnrId, exclPrdtId: panelPrdtId, - }), + }) ); } }, [panelCurationId, panelPatnrId, panelPrdtId, lgCatCd]); // FP 방식으로 카테고리 규칙 헬퍼 함수들 (curry 적용) - const categoryHelpers = useMemo(() => ({ - createCategoryRule: fp.curry((conditionFn, extractFn, data) => - conditionFn(data) ? extractFn(data) : null - ), - hasProductWithoutCuration: fp.curry((panelCurationId, productData) => - fp.isNotNil(productData) && fp.isNil(panelCurationId) - ), - hasThemeWithPaymentCondition: fp.curry((panelCurationId, themeProductInfo) => { - const hasThemeProduct = fp.isNotNil(themeProductInfo); - const equalToN = fp.curry((expected, actual) => actual === expected)("N"); - const isNoPayment = equalToN(fp.pipe(() => themeProductInfo, fp.get('pmtSuptYn'))()); - const hasCuration = fp.isNotNil(panelCurationId); - return hasThemeProduct && isNoPayment && hasCuration; - }) - }), []); + const categoryHelpers = useMemo( + () => ({ + createCategoryRule: fp.curry((conditionFn, extractFn, data) => + conditionFn(data) ? extractFn(data) : null + ), + hasProductWithoutCuration: fp.curry( + (panelCurationId, productData) => + fp.isNotNil(productData) && fp.isNil(panelCurationId) + ), + hasThemeWithPaymentCondition: fp.curry( + (panelCurationId, themeProductInfo) => { + const hasThemeProduct = fp.isNotNil(themeProductInfo); + const equalToN = fp.curry((expected, actual) => actual === expected)( + "N" + ); + const isNoPayment = equalToN( + fp.pipe(() => themeProductInfo, fp.get("pmtSuptYn"))() + ); + const hasCuration = fp.isNotNil(panelCurationId); + return hasThemeProduct && isNoPayment && hasCuration; + } + ), + }), + [] + ); const getlgCatCd = useCallback(() => { // FP 방식으로 카테고리 코드 결정 - curry 적용으로 더 함수형 개선 const categoryRules = [ // 일반 상품 규칙 (curry 활용) - () => categoryHelpers.createCategoryRule( - categoryHelpers.hasProductWithoutCuration(panelCurationId), - (data) => fp.pipe(() => data, fp.get('catCd'))(), - productData - ), - + () => + categoryHelpers.createCategoryRule( + categoryHelpers.hasProductWithoutCuration(panelCurationId), + (data) => fp.pipe(() => data, fp.get("catCd"))(), + productData + ), + // 테마 상품 규칙 (curry 활용) - () => categoryHelpers.createCategoryRule( - categoryHelpers.hasThemeWithPaymentCondition(panelCurationId), - (data) => fp.pipe(() => data, fp.get('catCd'))(), - themeProductInfo - ) + () => + categoryHelpers.createCategoryRule( + categoryHelpers.hasThemeWithPaymentCondition(panelCurationId), + (data) => fp.pipe(() => data, fp.get("catCd"))(), + themeProductInfo + ), ]; // 첫 번째로 매칭되는 규칙의 결과 사용 (curry의 reduce 활용) const categoryCode = fp.reduce( (result, value) => result || value || "", "", - categoryRules.map(rule => rule()) + categoryRules.map((rule) => rule()) ); - + setLgCatCd(categoryCode); - }, [productData, selectedIndex, panelCurationId, themeProductInfo, categoryHelpers]); + }, [ + productData, + selectedIndex, + panelCurationId, + themeProductInfo, + categoryHelpers, + ]); // FP 방식으로 카테고리 코드 업데이트 (메모리 누수 방지) useEffect(() => { const shouldUpdateCategory = fp.pipe( () => ({ themeProductInfo, productData, panelInfo, selectedIndex }), - ({ themeProductInfo, productData, panelInfo, selectedIndex }) => - fp.isNotNil(themeProductInfo) || fp.isNotNil(productData) || fp.isNotNil(panelInfo) + ({ themeProductInfo, productData, panelInfo, selectedIndex }) => + fp.isNotNil(themeProductInfo) || + fp.isNotNil(productData) || + fp.isNotNil(panelInfo) )(); if (shouldUpdateCategory) { @@ -400,98 +454,147 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 테마/호텔 기반 인덱스 초기화가 필요하면: // - findIndex 유틸을 만들어 매칭 인덱스를 계산 후 setSelectedIndex에 반영하세요. // FP 방식으로 버전 비교 헬퍼 함수 (curry 적용) - const versionComparators = useMemo(() => ({ - isVersionGTE: fp.curry((target, version) => version >= target), - isVersionLT: fp.curry((target, version) => version < target) - }), []); + const versionComparators = useMemo( + () => ({ + isVersionGTE: fp.curry((target, version) => version >= target), + isVersionLT: fp.curry((target, version) => version < target), + }), + [] + ); // FP 방식으로 조건 체크 헬퍼 함수들 (curry 적용) - const conditionCheckers = useMemo(() => ({ - hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)), - equalTo: fp.curry((expected, actual) => actual === expected), - checkAllConditions: fp.curry((conditions, data) => - fp.reduce((acc, condition) => acc && condition, true, conditions.map(fn => fn(data))) - ) - }), []); + const conditionCheckers = useMemo( + () => ({ + hasDataAndCondition: fp.curry( + (conditionFn, data) => fp.isNotNil(data) && conditionFn(data) + ), + equalTo: fp.curry((expected, actual) => actual === expected), + checkAllConditions: fp.curry((conditions, data) => + fp.reduce( + (acc, condition) => acc && condition, + true, + conditions.map((fn) => fn(data)) + ) + ), + }), + [] + ); const getProductType = useCallback(() => { // FP 방식으로 데이터 검증 및 타입 결정 - curry 적용으로 더 함수형 개선 - const createTypeChecker = fp.curry((type, conditions, sideEffect) => + const createTypeChecker = fp.curry((type, conditions, sideEffect) => fp.pipe( () => conditions(), - isValid => isValid ? (() => { - sideEffect && sideEffect(); - return { matched: true, type }; - })() : { matched: false } + (isValid) => + isValid + ? (() => { + sideEffect && sideEffect(); + return { matched: true, type }; + })() + : { matched: false } )() ); const productTypeRules = [ // 테마 타입 체크 - () => createTypeChecker( - "theme", - () => fp.pipe( - () => ({ panelCurationId, themeData }), - ({ panelCurationId, themeData }) => - fp.isNotNil(panelCurationId) && fp.isNotNil(themeData) - )(), - () => { - const themeProduct = fp.pipe( - () => themeData, - fp.get('productInfos'), - fp.get(selectedIndex.toString()) - )(); - setProductType("theme"); - setThemeProductInfo(themeProduct); - } - ), - + () => + createTypeChecker( + "theme", + () => + fp.pipe( + () => ({ panelCurationId, themeData }), + ({ panelCurationId, themeData }) => + fp.isNotNil(panelCurationId) && fp.isNotNil(themeData) + )(), + () => { + const themeProduct = fp.pipe( + () => themeData, + fp.get("productInfos"), + fp.get(selectedIndex.toString()) + )(); + setProductType("theme"); + setThemeProductInfo(themeProduct); + } + ), + // Buy Now 타입 체크 (curry 활용) - () => createTypeChecker( - "buyNow", - () => fp.pipe( - () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), - ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => { - const conditions = [ - () => fp.isNotNil(productData), - () => conditionCheckers.equalTo("Y")(productPmtSuptYn), - () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), - () => fp.isNotNil(panelPrdtId), - () => versionComparators.isVersionGTE("6.0")(webOSVersion) - ]; - return conditionCheckers.checkAllConditions(conditions)({}); - } - )(), - () => setProductType("buyNow") - ), - + () => + createTypeChecker( + "buyNow", + () => + fp.pipe( + () => ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }), + ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }) => { + const conditions = [ + () => fp.isNotNil(productData), + () => conditionCheckers.equalTo("Y")(productPmtSuptYn), + () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), + () => fp.isNotNil(panelPrdtId), + () => versionComparators.isVersionGTE("6.0")(webOSVersion), + ]; + return conditionCheckers.checkAllConditions(conditions)({}); + } + )(), + () => setProductType("buyNow") + ), + // Shop By Mobile 타입 체크 (curry 활용) - () => createTypeChecker( - "shopByMobile", - () => fp.pipe( - () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), - ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => { - if (!productData) return false; - - const isDirectMobile = conditionCheckers.equalTo("N")(productPmtSuptYn); - const conditionalMobileConditions = [ - () => conditionCheckers.equalTo("Y")(productPmtSuptYn), - () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), - () => versionComparators.isVersionLT("6.0")(webOSVersion), - () => fp.isNotNil(panelPrdtId) - ]; - const isConditionalMobile = conditionCheckers.checkAllConditions(conditionalMobileConditions)({}); - - return isDirectMobile || isConditionalMobile; - } - )(), - () => setProductType("shopByMobile") - ) + () => + createTypeChecker( + "shopByMobile", + () => + fp.pipe( + () => ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }), + ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }) => { + if (!productData) return false; + + const isDirectMobile = + conditionCheckers.equalTo("N")(productPmtSuptYn); + const conditionalMobileConditions = [ + () => conditionCheckers.equalTo("Y")(productPmtSuptYn), + () => conditionCheckers.equalTo("N")(productGrPrdtProcYn), + () => versionComparators.isVersionLT("6.0")(webOSVersion), + () => fp.isNotNil(panelPrdtId), + ]; + const isConditionalMobile = + conditionCheckers.checkAllConditions( + conditionalMobileConditions + )({}); + + return isDirectMobile || isConditionalMobile; + } + )(), + () => setProductType("shopByMobile") + ), ]; // FP 방식으로 순차적 타입 체크 const matchedRule = fp.reduce( - (result, rule) => result.matched ? result : rule(), + (result, rule) => (result.matched ? result : rule()), { matched: false }, productTypeRules ); @@ -499,15 +602,27 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 매칭되지 않은 경우 디버깅 정보 출력 if (!matchedRule.matched) { const debugInfo = fp.pipe( - () => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }), - ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => ({ + () => ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }), + ({ + productData, + panelPrdtId, + productPmtSuptYn, + productGrPrdtProcYn, + webOSVersion, + }) => ({ pmtSuptYn: productPmtSuptYn, grPrdtProcYn: productGrPrdtProcYn, prdtId: panelPrdtId, webOSVersion, }) )(); - + console.warn("Unknown product type:", productData); console.warn("Product data properties:", debugInfo); } @@ -530,45 +645,59 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { } }, [getProductType, productData, themeData, panelType]); - const imageUrl = useMemo(() => fp.pipe(() => productData, fp.get('thumbnailUrl960'))(), [productData]); + const imageUrl = useMemo( + () => fp.pipe(() => productData, fp.get("thumbnailUrl960"))(), + [productData] + ); // FP 방식으로 타이틀과 aria-label 메모이제이션 (성능 최적화) - const headerTitle = useMemo(() => fp.pipe( - () => ({ panelPrdtId, productData, panelType, themeData }), - ({ panelPrdtId, productData, panelType, themeData }) => { - const productTitle = fp.pipe( - () => ({ panelPrdtId, productData }), - ({ panelPrdtId, productData }) => - fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get('prdtNm'))() - : null - )(); - - const themeTitle = fp.pipe( - () => ({ panelType, themeData }), - ({ panelType, themeData }) => - panelType === "theme" && fp.pipe(() => themeData, fp.get('curationNm'), fp.isNotNil)() - ? fp.pipe(() => themeData, fp.get('curationNm'))() - : null - )(); - - return productTitle || themeTitle || ""; - } - )(), [panelPrdtId, productData, panelType, themeData]); + const headerTitle = useMemo( + () => + fp.pipe( + () => ({ panelPrdtId, productData, panelType, themeData }), + ({ panelPrdtId, productData, panelType, themeData }) => { + const productTitle = fp.pipe( + () => ({ panelPrdtId, productData }), + ({ panelPrdtId, productData }) => + fp.isNotNil(panelPrdtId) && + fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get("prdtNm"))() + : null + )(); - const ariaLabel = useMemo(() => fp.pipe( - () => ({ panelPrdtId, productData }), - ({ panelPrdtId, productData }) => - fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)() - ? fp.pipe(() => productData, fp.get('prdtNm'))() - : "" - )(), [panelPrdtId, productData]); + const themeTitle = fp.pipe( + () => ({ panelType, themeData }), + ({ panelType, themeData }) => + panelType === "theme" && + fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)() + ? fp.pipe(() => themeData, fp.get("curationNm"))() + : null + )(); + + return productTitle || themeTitle || ""; + } + )(), + [panelPrdtId, productData, panelType, themeData] + ); + + const ariaLabel = useMemo( + () => + fp.pipe( + () => ({ panelPrdtId, productData }), + ({ panelPrdtId, productData }) => + fp.isNotNil(panelPrdtId) && + fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)() + ? fp.pipe(() => productData, fp.get("prdtNm"))() + : "" + )(), + [panelPrdtId, productData] + ); // ===== 파트너사별 배경 이미지 설정 로직 (현재 비활성화) ===== // thumbnailUrl960을 사용하여 파트너사별로 다른 배경 이미지를 설정하는 기능 // Pink Pong 등 특정 파트너사에서만 thumbnailUrl960 데이터가 있어서 배경이 변경됨 // 현재는 고정 배경(detailPanelBg)만 사용하기 위해 주석 처리 - + // FP 방식으로 배경 이미지 설정 (메모리 누수 방지) /* useLayoutEffect(() => { @@ -589,6 +718,15 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { // 언마운트 시 인덱스 초기화가 필요하면: // useEffect(() => () => setSelectedIndex(0), []) + const handleProductAllSectionReady = useCallback(() => { + const spotTime = setTimeout(() => { + Spotlight.focus(SpotlightIds.DETAIL_SHOPBYMOBILE); + }, 100); + return () => { + clearTimeout(spotTime); + }; + }, []); + return (
{ const hasRequiredData = fp.pipe( () => [panelInfo, productDataSource, productType], - data => fp.reduce((acc, item) => acc && fp.isNotNil(item), true, data) + (data) => + fp.reduce( + (acc, item) => acc && fp.isNotNil(item), + true, + data + ) )(); - + return { canRender: !isLoading && hasRequiredData, showLoading: !isLoading && !hasRequiredData, - showNothing: isLoading + showNothing: isLoading, }; } )(); @@ -645,10 +788,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { openThemeItemOverlay={openThemeItemOverlay} setOpenThemeItemOverlay={updateThemeItemOverlay} themeProductInfo={themeProductInfo} + onReady={handleProductAllSectionReady} + isOnRender={renderStates.canRender} /> ); } - + if (renderStates.showLoading) { return (
@@ -656,10 +801,23 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
); } - + return null; - }, [isLoading, panelInfo, productDataSource, productType, selectedIndex, panelPatnrId, panelPrdtId, updateSelectedIndex, openThemeItemOverlay, updateThemeItemOverlay, themeProductInfo])} + }, [ + isLoading, + panelInfo, + productDataSource, + productType, + selectedIndex, + panelPatnrId, + panelPrdtId, + updateSelectedIndex, + openThemeItemOverlay, + updateThemeItemOverlay, + themeProductInfo, + ])} + pipe( when( - () => isVal(productType) && productType === "theme" && isVal(themeProductInfo), + () => + isVal(productType) && + productType === "theme" && + isVal(themeProductInfo), () => themeProductInfo ), defaultTo(productInfo), @@ -91,19 +124,14 @@ const getProductData = curry((productType, themeProductInfo, productInfo) => const deriveFavoriteFlag = curry((favoriteOverride, productData) => pipe( when(isNotNil, identity), - defaultWith(() => - pipe( - get("favorYn"), - defaultTo("N") - )(productData) - ) + defaultWith(() => pipe(get("favorYn"), defaultTo("N"))(productData)) )(favoriteOverride) ); // FP: Pure function to extract review grade and order phone const extractProductMeta = (productInfo) => ({ revwGrd: get("revwGrd", productInfo), - orderPhnNo: get("orderPhnNo", productInfo) + orderPhnNo: get("orderPhnNo", productInfo), }); // 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경 @@ -130,11 +158,22 @@ export default function ProductAllSection({ openThemeItemOverlay, setOpenThemeItemOverlay, themeProductInfo, + onReady, + isOnRender, }) { const dispatch = useDispatch(); + const [currentHeight, setCurrentHeight] = useState(0); + //하단부분까지 갔을때 체크용 + const [documentHeight, setDocumentHeight] = useState(); + const [isBottom, setIsBottom] = useState(false); + //버튼 active 표시용 + const [reviewHeight, setReviewHeight] = useState(0); + const [activeProductBtn, setActiveProductBtn] = useState(false); + const [activeReviewBtn, setActiveReviewBtn] = useState(false); + const [activeYouMayLikeBtn, setActiveYouMayLikeBtn] = useState(false); - const productData = useMemo(() => - getProductData(productType, themeProductInfo, productInfo), + const productData = useMemo( + () => getProductData(productType, themeProductInfo, productInfo), [productType, themeProductInfo, productInfo] ); @@ -143,16 +182,21 @@ export default function ProductAllSection({ previewReviews, stats, isLoading: reviewsLoading, - hasReviews // 리뷰 존재 여부 플래그 추가 + hasReviews, // 리뷰 존재 여부 플래그 추가 } = useReviews(productData.prdtId); // YouMayAlsoLike 데이터 확인 - const youmaylikeProductData = useSelector((state) => state.main.youmaylikeData); - const hasYouMayAlsoLike = youmaylikeProductData && youmaylikeProductData.length > 0; + const youmaylikeProductData = useSelector( + (state) => state.main.youmaylikeData + ); + const hasYouMayAlsoLike = + youmaylikeProductData && youmaylikeProductData.length > 0; // ProductAllSection 마운트 시 showAllReviews 초기화 useEffect(() => { - console.log("[ProductAllSection] Component mounted - resetting showAllReviews to false"); + console.log( + "[ProductAllSection] Component mounted - resetting showAllReviews to false" + ); dispatch(resetShowAllReviews()); }, []); // 빈 dependency array = 마운트 시에만 실행 @@ -163,18 +207,22 @@ export default function ProductAllSection({ hasProductData: !!productData, reviewTotalCount: stats.totalReviews, averageRating: stats.averageRating, - productData: productData + productData: productData, }); dispatch( pushPanel({ name: panel_names.USER_REVIEW_PANEL, panelInfo: { prdtId: productData.prdtId, - productImage: (productData.imgUrls600 && productData.imgUrls600[0]) || (productData.imgUrls && productData.imgUrls[0]) || productData.thumbnailUrl || 'https://placehold.co/150x150', - brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50', - productName: productData.prdtNm || '상품명 정보가 없습니다', + productImage: + (productData.imgUrls600 && productData.imgUrls600[0]) || + (productData.imgUrls && productData.imgUrls[0]) || + productData.thumbnailUrl || + "https://placehold.co/150x150", + brandLogo: productData.patncLogoPath || "https://placehold.co/50x50", + productName: productData.prdtNm || "상품명 정보가 없습니다", avgRating: stats.averageRating || 5, - reviewCount: stats.totalReviews || 0 + reviewCount: stats.totalReviews || 0, }, }) ); @@ -186,30 +234,38 @@ export default function ProductAllSection({ hasProductData: !!productData, productDataPrdtId: productData && productData.prdtId, imgUrls600: productData && productData.imgUrls600, - imgUrls600Length: productData && productData.imgUrls600 && productData.imgUrls600.length, - imgUrls600Type: Array.isArray(productData && productData.imgUrls600) ? 'array' : typeof (productData && productData.imgUrls600), - productData: productData + imgUrls600Length: + productData && productData.imgUrls600 && productData.imgUrls600.length, + imgUrls600Type: Array.isArray(productData && productData.imgUrls600) + ? "array" + : typeof (productData && productData.imgUrls600), + productData: productData, }); }, [productData]); - const { revwGrd, orderPhnNo } = useMemo(() => - extractProductMeta(productInfo), + const { revwGrd, orderPhnNo } = useMemo( + () => extractProductMeta(productInfo), [productInfo] ); // FP: derive favorite flag from props with local override, avoid non-I/O useEffect const [favoriteOverride, setFavoriteOverride] = useState(null); - const favoriteFlag = useMemo(() => - deriveFavoriteFlag(favoriteOverride, productData), + const favoriteFlag = useMemo( + () => deriveFavoriteFlag(favoriteOverride, productData), [favoriteOverride, productData] ); const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false); - // useReviews에서 모든 리뷰 데이터 관리 const reviewTotalCount = stats.totalReviews; - const reviewData = { reviewList: previewReviews, reviewDetail: { totRvwCnt: stats.totalReviews, avgRvwScr: stats.averageRating } }; + const reviewData = { + reviewList: previewReviews, + reviewDetail: { + totRvwCnt: stats.totalReviews, + avgRvwScr: stats.averageRating, + }, + }; // User Reviews 스크롤 핸들러 추가 const handleUserReviewsClick = useCallback( @@ -218,15 +274,16 @@ export default function ProductAllSection({ ); const scrollContainerRef = useRef(null); - const { getScrollTo, scrollTop } = useScrollTo(); + const productDetailRef = useRef(null); //높이값 변경때문 + const descriptionRef = useRef(null); + const reviewRef = useRef(null); + const youMayAlsoLikelRef = useRef(null); + const { getScrollTo, scrollTop } = useScrollTo(); // FP: Pure function for mobile popup state change const handleShopByMobileOpen = useCallback( - pipe( - () => true, - setMobileSendPopupOpen - ), + pipe(() => true, setMobileSendPopupOpen), [] ); @@ -275,7 +332,86 @@ export default function ProductAllSection({ () => scrollToSection("scroll-marker-you-may-also-like"), [scrollToSection] ); + const scrollPositionRef = useRef(0); + const handleArrowClickAlternative = useCallback(() => { + const currentHeight = scrollPositionRef.current; + const scrollAmount = 200; + scrollTop({ + y: currentHeight + scrollAmount, + animate: true, + }); + if (documentHeight) { + const isAtBottom = scrollPositionRef.current + 944 >= documentHeight; + if (isAtBottom !== isBottom) { + setIsBottom(isAtBottom); + } + } + }, []); + + const handleScroll = useCallback( + (e) => { + scrollPositionRef.current = e.scrollTop; + if (documentHeight) { + const isAtBottom = + scrollPositionRef.current + 944 >= + documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + if (isAtBottom !== isBottom) { + setIsBottom(isAtBottom); + } + } + }, + [documentHeight, isBottom, youMayAlsoLikelRef] + ); + + const productFocus = useCallback(() => { + setActiveProductBtn(true); + setActiveReviewBtn(false); + setActiveYouMayLikeBtn(false); + }, []); + + const reviewFocus = useCallback(() => { + setActiveProductBtn(false); + setActiveReviewBtn(true); + setActiveYouMayLikeBtn(false); + }, []); + + const youmaylikeFocus = useCallback(() => { + setActiveProductBtn(false); + setActiveReviewBtn(false); + setActiveYouMayLikeBtn(true); + }, []); + + const _onBlur = useCallback(() => { + setActiveProductBtn(false); + setActiveReviewBtn(false); + setActiveYouMayLikeBtn(false); + }, []); + + useEffect(() => { + setDocumentHeight( + (productDetailRef.current?.scrollHeight || 0) + + (descriptionRef.current?.scrollHeight || 0) + + (reviewRef.current?.scrollHeight || 0) + ); + setReviewHeight( + (productDetailRef.current?.scrollHeight || 0) + + (descriptionRef.current?.scrollHeight || 0) + + (reviewRef.current?.scrollHeight || 0) + ); + }, [ + productDetailRef.current, + descriptionRef.current, + hasReviews, + hasYouMayAlsoLike, + ]); + + //spot관련 + useEffect(() => { + if (onReady && isOnRender) { + onReady(); + } + }, [onReady, isOnRender]); return ( @@ -342,26 +478,31 @@ export default function ProductAllSection({ )} - {orderPhnNo && ( -
-
- {$L("Call to Order")} -
-
-
-
+
+ {orderPhnNo && ( + <> +
+ {$L("Call to Order")}
-
{orderPhnNo}
-
-
- )} +
+
+
+
+
{orderPhnNo}
+
+ + )} +
@@ -369,7 +510,10 @@ export default function ProductAllSection({ {hasReviews && ( @@ -378,7 +522,10 @@ export default function ProductAllSection({ )} {hasYouMayAlsoLike && ( {$L("YOU MAY ALSO LIKE")} @@ -387,7 +534,8 @@ export default function ProductAllSection({ {panelInfo && - panelInfo && panelInfo.type === "theme" && + panelInfo && + panelInfo.type === "theme" && !openThemeItemOverlay && (
- -
- {productData && productData.imgUrls600 && productData.imgUrls600.length > 0 ? ( + {/* */} +
+ {productData && + productData.imgUrls600 && + productData.imgUrls600.length > 0 ? ( productData.imgUrls600.map((image, index) => ( )) @@ -449,21 +605,29 @@ export default function ProductAllSection({ )}
-
+
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */} {hasReviews && ( <> -
-
+
+
@@ -473,21 +637,34 @@ export default function ProductAllSection({ )}
{hasYouMayAlsoLike && ( - <> +
- +
- +
)}
+ {!isBottom && ( +

+ + SCROLL DOWN +

+ )}
- ); } diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less index ca63f147..1017c309 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less @@ -20,9 +20,6 @@ // 1. Left Margin Section - 60px .leftMarginSection { - position: absolute; - left: 0; - top: 0; width: 60px; height: 100%; padding: 0; @@ -32,9 +29,6 @@ // 2. Info Section - 645px .infoSection { - position: absolute; - left: 60px; - top: 0; width: 650px; height: 100%; padding: 0; @@ -49,9 +43,6 @@ // 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바) .contentSection { - position: absolute; - left: 705px; // 60px + 645px - top: 0; width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바 height: 100%; padding: 0; @@ -286,7 +277,8 @@ } // 포커스 상태 추가 - &:focus { + &:focus, + &.active { background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경 outline: 2px solid @PRIMARY_COLOR_RED !important; @@ -403,7 +395,8 @@ font-weight: 400; // Bold에서 Regular로 변경 line-height: 35px; - &:focus { + &:focus, + &.active { background: #c72054; // 포커스시만 빨간색 } } @@ -495,9 +488,6 @@ // 1. Left Margin Section - 60px .leftMarginSection { - position: absolute; - left: 0; - top: 0; width: 60px; height: 100%; padding: 0; @@ -507,9 +497,6 @@ // 2. Info Section - 645px .infoSection { - position: absolute; - left: 60px; - top: 0; width: 650px; height: 100%; padding: 0; @@ -524,9 +511,6 @@ // 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바) .contentSection { - position: absolute; - left: 705px; // 60px + 645px - top: 0; width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바 height: 100%; padding: 0; @@ -943,3 +927,32 @@ } } } + +.arrow { + width: 238px; + height: 75px; + background-color: #656b78; + line-height: 75px; + position: absolute; + left: 1315px; //710px + 1210px /2 + bottom: 2px; + opacity: 0.7; + transform: translateX(-119px); + color: @COLOR_WHITE; + vertical-align: top; + display: flex; + justify-content: center; + align-items: center; + font-size: 24px; + text-align: center; + border-radius: 6px; + &:hover { + background-color: @PRIMARY_COLOR_RED; + opacity: 1; + } + > img { + margin-right: 5px; + width: 26px; + height: 15px; + } +} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviewsPopup/UserReviewsPopup.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviewsPopup/UserReviewsPopup.module.less index b3f35cde..0c364b79 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviewsPopup/UserReviewsPopup.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviewsPopup/UserReviewsPopup.module.less @@ -59,7 +59,7 @@ width: 1060px; // 절대 크기 지정 height: 100%; // 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용 - padding: 30px 20px 30px 20px; + padding: 10px 20px 0px 20px; box-sizing: border-box; margin: 0; overflow-y: auto; @@ -88,12 +88,11 @@ // gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정 .imageGrid { width: 100%; - padding: 0; - margin: 0; + margin: 20px 0 0 0; box-sizing: border-box; display: flex; - justify-content: center; + justify-content: flex-start; align-items: flex-start; overflow: visible; height: 100%; @@ -101,44 +100,45 @@ align-content: flex-start; // padding: 30px 40px; // 좌우 패딩 증가 // padding: 30px 0px 30px 15px; // 좌우 패딩 증가 + .imageItem { //스타일 변경 - // width: 210px; // 크기 약간 증가 - // height: 190px; // 비율 맞춤 - - width: 226px; // 크기 약간 증가 - height: 218px; // 비율 맞춤 + // width: 226px; // 크기 약간 증가 + // height: 218px; // 비율 맞춤 + width: 240px; + height: 240px; border-radius: 12px; position: relative; cursor: pointer; - + display: flex; + justify-content: center; + align-items: center; //스타일 변경 // margin-right: 35px; // 마진 증가로 균등 분배 // margin-bottom: 30px; // 세로 마진도 증가 - margin-right: 20px; // 마진 증가로 균등 분배 - margin-top: 20px; // 세로 마진도 증가 + margin-right: 6px; // 마진 증가로 균등 분배 + > div { + > img { + width: 226px; + height: 220px; + } + } // 4개씩 배치하므로 4번째마다 margin-right 제거 - &:nth-child(4n) { - margin-right: 0; - } &:focus { //확대 이미지 - width: 240px; - height: 240px; - margin-right: 6px; - margin-top: -2px; - // margin-top: -3px; - outline: none; + > div { + > img { + width: 240px; + height: 240px; + outline: none; + } + } &::after { .focused(@boxShadow: 0px, @borderRadius: 12px); // 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED) } - &:nth-child(4n) { - margin-right: 0; - margin-left: -14px; - } } .image { diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx index 765429c9..a1449540 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/YouMayAlsoLike/YouMayAlsoLike.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import React, { useState, useCallback, useEffect, useMemo, useRef } from "react"; import css from "./YouMayAlsoLike.module.less"; import { $L } from "../../../../utils/helperMethods"; import TVerticalPagenator from "../../../../components/TVerticalPagenator/TVerticalPagenator"; @@ -31,14 +31,26 @@ const Container = SpotlightContainerDecorator( "div" ); -export default function YouMayAlsoLike({ productInfo, panelInfo }) { +export default function YouMayAlsoLike({ productInfo, panelInfo, onFocus, onBlur }) { const { getScrollTo, scrollLeft } = useScrollTo(); + const [newYoumaylikeProductData, setNewYoumaylikeProductData] = useState([]); const dispatch = useDispatch(); const focusedContainerIdRef = useRef(null); const youmaylikeProductData = useSelector( (state) => state.main.youmaylikeData ); + //노출 9개로 변경위한 처리건. + useEffect(() => { + if (youmaylikeProductData && youmaylikeProductData.length > 0) { + setNewYoumaylikeProductData( + youmaylikeProductData.slice(0, youmaylikeProductData.length - 1) + ); + } else { + setNewYoumaylikeProductData([]); + } + }, [youmaylikeProductData]); + const panels = useSelector((state) => state.panels.panels); const themeProductInfos = useSelector( (state) => state.home.themeCurationDetailInfoData @@ -63,9 +75,21 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) { focusedContainerIdRef.current = containerId; }, []); + const _onFocus = useCallback(() => { + if (onFocus) { + onFocus(); + } + }, [onFocus]); + + const _onBlur = useCallback(() => { + if (onBlur) { + onBlur(); + } + }, [onBlur]); + return (
- {youmaylikeProductData && youmaylikeProductData.length > 0 && ( + {newYoumaylikeProductData && newYoumaylikeProductData.length > 0 && ( - +
- {youmaylikeProductData?.map((product, index) => { + {newYoumaylikeProductData?.map((product, index) => { const { imgUrl, patnrId, @@ -109,7 +137,6 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) { ); cursorOpen.current.stop(); }; - return ( diff --git a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx index 932c84ea..fae7967e 100644 --- a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx +++ b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx @@ -233,7 +233,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
{ isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'} /> { isActive={currentFilter.type === 'rating' && currentFilter.value === 5} /> { isActive={currentFilter.type === 'rating' && currentFilter.value === 4} /> { isActive={currentFilter.type === 'rating' && currentFilter.value === 3} /> { isActive={currentFilter.type === 'rating' && currentFilter.value === 2} /> { ); }; -export default UserReviewPanel; \ No newline at end of file +export default UserReviewPanel; diff --git a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.module.less b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.module.less index a42d4fba..dd3b0051 100644 --- a/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.module.less +++ b/com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.module.less @@ -102,10 +102,10 @@ height: 150px; border-radius: 14px; object-fit: cover; - margin-right: 15px; } &__content { + margin-left: 15px; flex: 1; align-self: stretch; display: flex; @@ -123,6 +123,9 @@ height: 50px; object-fit: contain; margin-right: 15px; + border-radius: 50px; + border: 1px solid #808080; + box-sizing: border-box; } &__productId {