[상품 상세] 리뷰 및 디테일 변경건.#3
1. 상품 상세 진입시 초기 포커스 shop by mobile 로 변경. - spotlightids추가. 2. 상품상세 scroll down추가. - 하단부 도달했을때 노출되지않도록 처리. - 클릭시 200px씩 이동. 3. 리뷰팝업 부분 스타일변경 - 호버시 이미지 확대부분 부자연스럽지 않도록 변경. 4. 상품 상세 우측 부분에서의 포커스 이동시 좌측 버튼부분의 호버처리. - 포커스 이동시에 자연스럽게 호버 이동가능하도록 변경
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 715 B |
@@ -54,4 +54,5 @@ export const SpotlightIds = {
|
|||||||
|
|
||||||
// detailPanel
|
// detailPanel
|
||||||
DETAIL_BUYNOW: "detail_buynow",
|
DETAIL_BUYNOW: "detail_buynow",
|
||||||
|
DETAIL_SHOPBYMOBILE: "detail_shop_by_mobile",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,90 +6,122 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from 'react';
|
||||||
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import Spotlight from "@enact/spotlight";
|
import Spinner from '@enact/sandstone/Spinner';
|
||||||
import { setContainerLastFocusedElement } from "@enact/spotlight/src/container";
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
|
||||||
import { getDeviceAdditionInfo } from "../../actions/deviceActions";
|
|
||||||
|
|
||||||
|
import detailPanelBg
|
||||||
|
from '../../../assets/images/detailpanel/detailpanel-bg-1.png';
|
||||||
|
import indicatorDefaultImage
|
||||||
|
from '../../../assets/images/img-thumb-empty-144@3x.png';
|
||||||
|
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
|
||||||
|
import { getThemeCurationDetailInfo } from '../../actions/homeActions';
|
||||||
import {
|
import {
|
||||||
getMainCategoryDetail,
|
getMainCategoryDetail,
|
||||||
getMainYouMayLike,
|
getMainYouMayLike,
|
||||||
} from "../../actions/mainActions";
|
} from '../../actions/mainActions';
|
||||||
import { popPanel, updatePanel } from "../../actions/panelActions";
|
import {
|
||||||
import { finishVideoPreview } from "../../actions/playActions";
|
popPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../actions/panelActions';
|
||||||
|
import { finishVideoPreview } from '../../actions/playActions';
|
||||||
import {
|
import {
|
||||||
clearProductDetail,
|
clearProductDetail,
|
||||||
getProductOptionId,
|
getProductOptionId,
|
||||||
} from "../../actions/productActions";
|
} from '../../actions/productActions';
|
||||||
|
import TBody from '../../components/TBody/TBody';
|
||||||
import TBody from "../../components/TBody/TBody";
|
import TPanel from '../../components/TPanel/TPanel';
|
||||||
import THeaderCustom from "./components/THeaderCustom";
|
import { panel_names } from '../../utils/Config';
|
||||||
import TPanel from "../../components/TPanel/TPanel";
|
import fp from '../../utils/fp';
|
||||||
|
import {
|
||||||
import { panel_names } from "../../utils/Config";
|
$L,
|
||||||
import { $L, getQRCodeUrl } from "../../utils/helperMethods";
|
getQRCodeUrl,
|
||||||
import fp from "../../utils/fp";
|
} from '../../utils/helperMethods';
|
||||||
import css from "./DetailPanel.module.less";
|
import { SpotlightIds } from '../../utils/SpotlightIds';
|
||||||
import ProductAllSection from "./ProductAllSection/ProductAllSection";
|
import THeaderCustom from './components/THeaderCustom';
|
||||||
import { getThemeCurationDetailInfo } from "../../actions/homeActions";
|
import css from './DetailPanel.module.less';
|
||||||
import indicatorDefaultImage from "../../../assets/images/img-thumb-empty-144@3x.png";
|
import ProductAllSection from './ProductAllSection/ProductAllSection';
|
||||||
import detailPanelBg from "../../../assets/images/detailpanel/detailpanel-bg-1.png";
|
import ThemeItemListOverlay from './ThemeItemListOverlay/ThemeItemListOverlay';
|
||||||
import ThemeItemListOverlay from "./ThemeItemListOverlay/ThemeItemListOverlay";
|
|
||||||
import Spinner from "@enact/sandstone/Spinner";
|
|
||||||
|
|
||||||
export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const productData = useSelector((state) => state.main.productData);
|
const productData = useSelector((state) => state.main.productData);
|
||||||
const isLoading = useSelector((state) =>
|
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) =>
|
const themeData = useSelector((state) =>
|
||||||
fp.pipe(
|
fp.pipe(
|
||||||
() => state,
|
() => state,
|
||||||
fp.get('home.productData.themeInfo'),
|
fp.get("home.productData.themeInfo"),
|
||||||
(list) => (list && list[0])
|
(list) => list && list[0]
|
||||||
)()
|
)()
|
||||||
);
|
);
|
||||||
const webOSVersion = useSelector(
|
const webOSVersion = useSelector(
|
||||||
(state) => state.common.appStatus.webOSVersion,
|
(state) => state.common.appStatus.webOSVersion
|
||||||
);
|
);
|
||||||
const panels = useSelector((state) => state.panels.panels);
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
|
|
||||||
// FP 방식으로 상태 관리
|
// FP 방식으로 상태 관리
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
const localRecentItems = useSelector((state) =>
|
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 { httpHeader } = useSelector((state) => state.common);
|
||||||
const { popupVisible, activePopup } = useSelector(
|
const { popupVisible, activePopup } = useSelector(
|
||||||
(state) => state.common.popup,
|
(state) => state.common.popup
|
||||||
);
|
);
|
||||||
const [lgCatCd, setLgCatCd] = useState("");
|
const [lgCatCd, setLgCatCd] = useState("");
|
||||||
const [themeProductInfo, setThemeProductInfo] = useState(null);
|
const [themeProductInfo, setThemeProductInfo] = useState(null);
|
||||||
|
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
const panelType = useMemo(
|
||||||
const panelType = useMemo(() => fp.pipe(() => panelInfo, fp.get('type'))(), [panelInfo]);
|
() => fp.pipe(() => panelInfo, fp.get("type"))(),
|
||||||
const panelCurationId = useMemo(() => fp.pipe(() => panelInfo, fp.get('curationId'))(), [panelInfo]);
|
[panelInfo]
|
||||||
const panelPatnrId = useMemo(() => fp.pipe(() => panelInfo, fp.get('patnrId'))(), [panelInfo]);
|
);
|
||||||
const panelPrdtId = useMemo(() => fp.pipe(() => panelInfo, fp.get('prdtId'))(), [panelInfo]);
|
const panelCurationId = useMemo(
|
||||||
const panelLiveReqFlag = useMemo(() => fp.pipe(() => panelInfo, fp.get('liveReqFlag'))(), [panelInfo]);
|
() => fp.pipe(() => panelInfo, fp.get("curationId"))(),
|
||||||
const panelBgImgNo = useMemo(() => fp.pipe(() => panelInfo, fp.get('bgImgNo'))(), [panelInfo]);
|
[panelInfo]
|
||||||
const productPmtSuptYn = useMemo(() => fp.pipe(() => productData, fp.get('pmtSuptYn'))(), [productData]);
|
);
|
||||||
const productGrPrdtProcYn = useMemo(() => fp.pipe(() => productData, fp.get('grPrdtProcYn'))(), [productData]);
|
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(
|
||||||
const productDataSource = useMemo(() =>
|
() =>
|
||||||
fp.pipe(
|
fp.pipe(
|
||||||
() => panelType,
|
() => panelType,
|
||||||
type => type === "theme" ? themeData : productData
|
(type) => (type === "theme" ? themeData : productData)
|
||||||
)(),
|
)(),
|
||||||
[panelType, themeData, productData]
|
[panelType, themeData, productData]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,17 +134,16 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
// FP 방식으로 상태 업데이트 함수들
|
// FP 방식으로 상태 업데이트 함수들
|
||||||
const updateSelectedIndex = useCallback((newIndex) => {
|
const updateSelectedIndex = useCallback((newIndex) => {
|
||||||
setSelectedIndex(fp.pipe(
|
setSelectedIndex(
|
||||||
() => newIndex,
|
fp.pipe(
|
||||||
index => Math.max(0, Math.min(index, 999)) // 범위 제한
|
() => newIndex,
|
||||||
)());
|
(index) => Math.max(0, Math.min(index, 999)) // 범위 제한
|
||||||
|
)()
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateThemeItemOverlay = useCallback((isOpen) => {
|
const updateThemeItemOverlay = useCallback((isOpen) => {
|
||||||
setOpenThemeItemOverlay(fp.pipe(
|
setOpenThemeItemOverlay(fp.pipe(() => isOpen, Boolean)());
|
||||||
() => isOpen,
|
|
||||||
Boolean
|
|
||||||
)());
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// FP 방식으로 이벤트 핸들러 정의
|
// FP 방식으로 이벤트 핸들러 정의
|
||||||
@@ -131,27 +162,26 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// 패널 업데이트 조건 체크
|
// 패널 업데이트 조건 체크
|
||||||
const shouldUpdatePanel = fp.pipe(
|
const shouldUpdatePanel =
|
||||||
() => panels,
|
fp.pipe(
|
||||||
fp.get('length'),
|
() => panels,
|
||||||
length => length === 4
|
fp.get("length"),
|
||||||
)() && fp.pipe(
|
(length) => length === 4
|
||||||
() => panels,
|
)() &&
|
||||||
fp.get('1.name'),
|
fp.pipe(
|
||||||
name => name === panel_names.PLAYER_PANEL
|
() => panels,
|
||||||
)();
|
fp.get("1.name"),
|
||||||
|
(name) => name === panel_names.PLAYER_PANEL
|
||||||
|
)();
|
||||||
|
|
||||||
if (shouldUpdatePanel) {
|
if (shouldUpdatePanel) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
panelInfo: {
|
panelInfo: {
|
||||||
thumbnail: fp.pipe(
|
thumbnail: fp.pipe(() => panelInfo, fp.get("thumbnailUrl"))(),
|
||||||
() => panelInfo,
|
|
||||||
fp.get('thumbnailUrl')
|
|
||||||
)(),
|
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +191,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, panelInfo, panels],
|
[dispatch, panelInfo, panels]
|
||||||
);
|
);
|
||||||
|
|
||||||
// FP 방식으로 스크롤 함수 핸들러
|
// FP 방식으로 스크롤 함수 핸들러
|
||||||
@@ -175,56 +205,63 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
() => ({ scrollToSection, sectionId }),
|
() => ({ scrollToSection, sectionId }),
|
||||||
({ scrollToSection, sectionId }) => {
|
({ scrollToSection, sectionId }) => {
|
||||||
if (fp.isNotNil(scrollToSection)) {
|
if (fp.isNotNil(scrollToSection)) {
|
||||||
return { action: 'execute', scrollFunction: scrollToSection, sectionId };
|
return {
|
||||||
|
action: "execute",
|
||||||
|
scrollFunction: scrollToSection,
|
||||||
|
sectionId,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return { action: 'store', sectionId };
|
return { action: "store", sectionId };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
|
|
||||||
// 액션에 따른 처리
|
// 액션에 따른 처리
|
||||||
if (scrollAction.action === 'execute') {
|
if (scrollAction.action === "execute") {
|
||||||
scrollAction.scrollFunction(scrollAction.sectionId);
|
scrollAction.scrollFunction(scrollAction.sectionId);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
"DetailPanel: scrollToSection function is null, storing pending scroll",
|
"DetailPanel: scrollToSection function is null, storing pending scroll"
|
||||||
);
|
);
|
||||||
setPendingScrollSection(scrollAction.sectionId);
|
setPendingScrollSection(scrollAction.sectionId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollToSection],
|
[scrollToSection]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== 고정 배경 이미지 설정 (detailPanelBg만 사용) =====
|
// ===== 고정 배경 이미지 설정 (detailPanelBg만 사용) =====
|
||||||
// 모든 DetailPanel에서 동일한 배경 이미지(detailpanel-bg-1.png) 사용
|
// 모든 DetailPanel에서 동일한 배경 이미지(detailpanel-bg-1.png) 사용
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[PartnerId] Partner background info:', {
|
console.log("[PartnerId] Partner background info:", {
|
||||||
panelPatnrId: panelPatnrId,
|
panelPatnrId: panelPatnrId,
|
||||||
productDataPatnrId: productData?.patnrId,
|
productDataPatnrId: productData?.patnrId,
|
||||||
productDataPatncNm: productData?.patncNm,
|
productDataPatncNm: productData?.patncNm,
|
||||||
productDataThumbnailUrl960: productData?.thumbnailUrl960,
|
productDataThumbnailUrl960: productData?.thumbnailUrl960,
|
||||||
imageUrl: imageUrl,
|
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]);
|
}, [panelPatnrId, productData, imageUrl]);
|
||||||
|
|
||||||
// FP 방식으로 pending scroll 처리 (메모리 누수 방지)
|
// FP 방식으로 pending scroll 처리 (메모리 누수 방지)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldExecutePendingScroll = fp.pipe(
|
const shouldExecutePendingScroll = fp.pipe(
|
||||||
() => ({ scrollToSection, pendingScrollSection }),
|
() => ({ scrollToSection, pendingScrollSection }),
|
||||||
({ scrollToSection, pendingScrollSection }) =>
|
({ scrollToSection, pendingScrollSection }) =>
|
||||||
fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection)
|
fp.isNotNil(scrollToSection) && fp.isNotNil(pendingScrollSection)
|
||||||
)();
|
)();
|
||||||
|
|
||||||
if (shouldExecutePendingScroll) {
|
if (shouldExecutePendingScroll) {
|
||||||
console.log(
|
console.log(
|
||||||
"DetailPanel: executing pending scroll to:",
|
"DetailPanel: executing pending scroll to:",
|
||||||
pendingScrollSection,
|
pendingScrollSection
|
||||||
);
|
);
|
||||||
|
|
||||||
// 메모리 누수 방지를 위한 cleanup 함수
|
// 메모리 누수 방지를 위한 cleanup 함수
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (scrollToSection) {
|
if (scrollToSection) {
|
||||||
@@ -252,14 +289,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
() => {
|
() => {
|
||||||
// 테마 데이터 로딩
|
// 테마 데이터 로딩
|
||||||
const isThemeType = panelType === "theme";
|
const isThemeType = panelType === "theme";
|
||||||
|
|
||||||
if (isThemeType) {
|
if (isThemeType) {
|
||||||
dispatch(
|
dispatch(
|
||||||
getThemeCurationDetailInfo({
|
getThemeCurationDetailInfo({
|
||||||
patnrId: panelPatnrId,
|
patnrId: panelPatnrId,
|
||||||
curationId: panelCurationId,
|
curationId: panelCurationId,
|
||||||
bgImgNo: panelBgImgNo,
|
bgImgNo: panelBgImgNo,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -267,14 +304,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 일반 상품 데이터 로딩
|
// 일반 상품 데이터 로딩
|
||||||
const hasProductId = fp.isNotNil(panelPrdtId);
|
const hasProductId = fp.isNotNil(panelPrdtId);
|
||||||
const hasNoCuration = fp.isNil(panelCurationId);
|
const hasNoCuration = fp.isNil(panelCurationId);
|
||||||
|
|
||||||
if (hasProductId && hasNoCuration) {
|
if (hasProductId && hasNoCuration) {
|
||||||
dispatch(
|
dispatch(
|
||||||
getMainCategoryDetail({
|
getMainCategoryDetail({
|
||||||
patnrId: panelPatnrId,
|
patnrId: panelPatnrId,
|
||||||
prdtId: panelPrdtId,
|
prdtId: panelPrdtId,
|
||||||
liveReqFlag: panelLiveReqFlag || "N",
|
liveReqFlag: panelLiveReqFlag || "N",
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,10 +333,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
// FP 방식으로 추천 상품 데이터 로딩 (메모리 누수 방지)
|
// FP 방식으로 추천 상품 데이터 로딩 (메모리 누수 방지)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldLoadRecommendations = fp.pipe(
|
const shouldLoadRecommendations = fp.pipe(() => lgCatCd, fp.isNotEmpty)();
|
||||||
() => lgCatCd,
|
|
||||||
fp.isNotEmpty
|
|
||||||
)();
|
|
||||||
|
|
||||||
if (shouldLoadRecommendations) {
|
if (shouldLoadRecommendations) {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -308,62 +342,82 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
exclCurationId: panelCurationId,
|
exclCurationId: panelCurationId,
|
||||||
exclPatnrId: panelPatnrId,
|
exclPatnrId: panelPatnrId,
|
||||||
exclPrdtId: panelPrdtId,
|
exclPrdtId: panelPrdtId,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [panelCurationId, panelPatnrId, panelPrdtId, lgCatCd]);
|
}, [panelCurationId, panelPatnrId, panelPrdtId, lgCatCd]);
|
||||||
|
|
||||||
// FP 방식으로 카테고리 규칙 헬퍼 함수들 (curry 적용)
|
// FP 방식으로 카테고리 규칙 헬퍼 함수들 (curry 적용)
|
||||||
const categoryHelpers = useMemo(() => ({
|
const categoryHelpers = useMemo(
|
||||||
createCategoryRule: fp.curry((conditionFn, extractFn, data) =>
|
() => ({
|
||||||
conditionFn(data) ? extractFn(data) : null
|
createCategoryRule: fp.curry((conditionFn, extractFn, data) =>
|
||||||
),
|
conditionFn(data) ? extractFn(data) : null
|
||||||
hasProductWithoutCuration: fp.curry((panelCurationId, productData) =>
|
),
|
||||||
fp.isNotNil(productData) && fp.isNil(panelCurationId)
|
hasProductWithoutCuration: fp.curry(
|
||||||
),
|
(panelCurationId, productData) =>
|
||||||
hasThemeWithPaymentCondition: fp.curry((panelCurationId, themeProductInfo) => {
|
fp.isNotNil(productData) && fp.isNil(panelCurationId)
|
||||||
const hasThemeProduct = fp.isNotNil(themeProductInfo);
|
),
|
||||||
const equalToN = fp.curry((expected, actual) => actual === expected)("N");
|
hasThemeWithPaymentCondition: fp.curry(
|
||||||
const isNoPayment = equalToN(fp.pipe(() => themeProductInfo, fp.get('pmtSuptYn'))());
|
(panelCurationId, themeProductInfo) => {
|
||||||
const hasCuration = fp.isNotNil(panelCurationId);
|
const hasThemeProduct = fp.isNotNil(themeProductInfo);
|
||||||
return hasThemeProduct && isNoPayment && hasCuration;
|
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(() => {
|
const getlgCatCd = useCallback(() => {
|
||||||
// FP 방식으로 카테고리 코드 결정 - curry 적용으로 더 함수형 개선
|
// FP 방식으로 카테고리 코드 결정 - curry 적용으로 더 함수형 개선
|
||||||
const categoryRules = [
|
const categoryRules = [
|
||||||
// 일반 상품 규칙 (curry 활용)
|
// 일반 상품 규칙 (curry 활용)
|
||||||
() => categoryHelpers.createCategoryRule(
|
() =>
|
||||||
categoryHelpers.hasProductWithoutCuration(panelCurationId),
|
categoryHelpers.createCategoryRule(
|
||||||
(data) => fp.pipe(() => data, fp.get('catCd'))(),
|
categoryHelpers.hasProductWithoutCuration(panelCurationId),
|
||||||
productData
|
(data) => fp.pipe(() => data, fp.get("catCd"))(),
|
||||||
),
|
productData
|
||||||
|
),
|
||||||
|
|
||||||
// 테마 상품 규칙 (curry 활용)
|
// 테마 상품 규칙 (curry 활용)
|
||||||
() => categoryHelpers.createCategoryRule(
|
() =>
|
||||||
categoryHelpers.hasThemeWithPaymentCondition(panelCurationId),
|
categoryHelpers.createCategoryRule(
|
||||||
(data) => fp.pipe(() => data, fp.get('catCd'))(),
|
categoryHelpers.hasThemeWithPaymentCondition(panelCurationId),
|
||||||
themeProductInfo
|
(data) => fp.pipe(() => data, fp.get("catCd"))(),
|
||||||
)
|
themeProductInfo
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 첫 번째로 매칭되는 규칙의 결과 사용 (curry의 reduce 활용)
|
// 첫 번째로 매칭되는 규칙의 결과 사용 (curry의 reduce 활용)
|
||||||
const categoryCode = fp.reduce(
|
const categoryCode = fp.reduce(
|
||||||
(result, value) => result || value || "",
|
(result, value) => result || value || "",
|
||||||
"",
|
"",
|
||||||
categoryRules.map(rule => rule())
|
categoryRules.map((rule) => rule())
|
||||||
);
|
);
|
||||||
|
|
||||||
setLgCatCd(categoryCode);
|
setLgCatCd(categoryCode);
|
||||||
}, [productData, selectedIndex, panelCurationId, themeProductInfo, categoryHelpers]);
|
}, [
|
||||||
|
productData,
|
||||||
|
selectedIndex,
|
||||||
|
panelCurationId,
|
||||||
|
themeProductInfo,
|
||||||
|
categoryHelpers,
|
||||||
|
]);
|
||||||
|
|
||||||
// FP 방식으로 카테고리 코드 업데이트 (메모리 누수 방지)
|
// FP 방식으로 카테고리 코드 업데이트 (메모리 누수 방지)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldUpdateCategory = fp.pipe(
|
const shouldUpdateCategory = fp.pipe(
|
||||||
() => ({ themeProductInfo, productData, panelInfo, selectedIndex }),
|
() => ({ themeProductInfo, productData, panelInfo, selectedIndex }),
|
||||||
({ themeProductInfo, productData, panelInfo, selectedIndex }) =>
|
({ themeProductInfo, productData, panelInfo, selectedIndex }) =>
|
||||||
fp.isNotNil(themeProductInfo) || fp.isNotNil(productData) || fp.isNotNil(panelInfo)
|
fp.isNotNil(themeProductInfo) ||
|
||||||
|
fp.isNotNil(productData) ||
|
||||||
|
fp.isNotNil(panelInfo)
|
||||||
)();
|
)();
|
||||||
|
|
||||||
if (shouldUpdateCategory) {
|
if (shouldUpdateCategory) {
|
||||||
@@ -400,98 +454,147 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 테마/호텔 기반 인덱스 초기화가 필요하면:
|
// 테마/호텔 기반 인덱스 초기화가 필요하면:
|
||||||
// - findIndex 유틸을 만들어 매칭 인덱스를 계산 후 setSelectedIndex에 반영하세요.
|
// - findIndex 유틸을 만들어 매칭 인덱스를 계산 후 setSelectedIndex에 반영하세요.
|
||||||
// FP 방식으로 버전 비교 헬퍼 함수 (curry 적용)
|
// FP 방식으로 버전 비교 헬퍼 함수 (curry 적용)
|
||||||
const versionComparators = useMemo(() => ({
|
const versionComparators = useMemo(
|
||||||
isVersionGTE: fp.curry((target, version) => version >= target),
|
() => ({
|
||||||
isVersionLT: fp.curry((target, version) => version < target)
|
isVersionGTE: fp.curry((target, version) => version >= target),
|
||||||
}), []);
|
isVersionLT: fp.curry((target, version) => version < target),
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// FP 방식으로 조건 체크 헬퍼 함수들 (curry 적용)
|
// FP 방식으로 조건 체크 헬퍼 함수들 (curry 적용)
|
||||||
const conditionCheckers = useMemo(() => ({
|
const conditionCheckers = useMemo(
|
||||||
hasDataAndCondition: fp.curry((conditionFn, data) => fp.isNotNil(data) && conditionFn(data)),
|
() => ({
|
||||||
equalTo: fp.curry((expected, actual) => actual === expected),
|
hasDataAndCondition: fp.curry(
|
||||||
checkAllConditions: fp.curry((conditions, data) =>
|
(conditionFn, data) => fp.isNotNil(data) && conditionFn(data)
|
||||||
fp.reduce((acc, condition) => acc && condition, true, conditions.map(fn => fn(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(() => {
|
const getProductType = useCallback(() => {
|
||||||
// FP 방식으로 데이터 검증 및 타입 결정 - curry 적용으로 더 함수형 개선
|
// FP 방식으로 데이터 검증 및 타입 결정 - curry 적용으로 더 함수형 개선
|
||||||
const createTypeChecker = fp.curry((type, conditions, sideEffect) =>
|
const createTypeChecker = fp.curry((type, conditions, sideEffect) =>
|
||||||
fp.pipe(
|
fp.pipe(
|
||||||
() => conditions(),
|
() => conditions(),
|
||||||
isValid => isValid ? (() => {
|
(isValid) =>
|
||||||
sideEffect && sideEffect();
|
isValid
|
||||||
return { matched: true, type };
|
? (() => {
|
||||||
})() : { matched: false }
|
sideEffect && sideEffect();
|
||||||
|
return { matched: true, type };
|
||||||
|
})()
|
||||||
|
: { matched: false }
|
||||||
)()
|
)()
|
||||||
);
|
);
|
||||||
|
|
||||||
const productTypeRules = [
|
const productTypeRules = [
|
||||||
// 테마 타입 체크
|
// 테마 타입 체크
|
||||||
() => createTypeChecker(
|
() =>
|
||||||
"theme",
|
createTypeChecker(
|
||||||
() => fp.pipe(
|
"theme",
|
||||||
() => ({ panelCurationId, themeData }),
|
() =>
|
||||||
({ panelCurationId, themeData }) =>
|
fp.pipe(
|
||||||
fp.isNotNil(panelCurationId) && fp.isNotNil(themeData)
|
() => ({ panelCurationId, themeData }),
|
||||||
)(),
|
({ panelCurationId, themeData }) =>
|
||||||
() => {
|
fp.isNotNil(panelCurationId) && fp.isNotNil(themeData)
|
||||||
const themeProduct = fp.pipe(
|
)(),
|
||||||
() => themeData,
|
() => {
|
||||||
fp.get('productInfos'),
|
const themeProduct = fp.pipe(
|
||||||
fp.get(selectedIndex.toString())
|
() => themeData,
|
||||||
)();
|
fp.get("productInfos"),
|
||||||
setProductType("theme");
|
fp.get(selectedIndex.toString())
|
||||||
setThemeProductInfo(themeProduct);
|
)();
|
||||||
}
|
setProductType("theme");
|
||||||
),
|
setThemeProductInfo(themeProduct);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
// Buy Now 타입 체크 (curry 활용)
|
// Buy Now 타입 체크 (curry 활용)
|
||||||
() => createTypeChecker(
|
() =>
|
||||||
"buyNow",
|
createTypeChecker(
|
||||||
() => fp.pipe(
|
"buyNow",
|
||||||
() => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }),
|
() =>
|
||||||
({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => {
|
fp.pipe(
|
||||||
const conditions = [
|
() => ({
|
||||||
() => fp.isNotNil(productData),
|
productData,
|
||||||
() => conditionCheckers.equalTo("Y")(productPmtSuptYn),
|
panelPrdtId,
|
||||||
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn),
|
productPmtSuptYn,
|
||||||
() => fp.isNotNil(panelPrdtId),
|
productGrPrdtProcYn,
|
||||||
() => versionComparators.isVersionGTE("6.0")(webOSVersion)
|
webOSVersion,
|
||||||
];
|
}),
|
||||||
return conditionCheckers.checkAllConditions(conditions)({});
|
({
|
||||||
}
|
productData,
|
||||||
)(),
|
panelPrdtId,
|
||||||
() => setProductType("buyNow")
|
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 활용)
|
// Shop By Mobile 타입 체크 (curry 활용)
|
||||||
() => createTypeChecker(
|
() =>
|
||||||
"shopByMobile",
|
createTypeChecker(
|
||||||
() => fp.pipe(
|
"shopByMobile",
|
||||||
() => ({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }),
|
() =>
|
||||||
({ productData, panelPrdtId, productPmtSuptYn, productGrPrdtProcYn, webOSVersion }) => {
|
fp.pipe(
|
||||||
if (!productData) return false;
|
() => ({
|
||||||
|
productData,
|
||||||
const isDirectMobile = conditionCheckers.equalTo("N")(productPmtSuptYn);
|
panelPrdtId,
|
||||||
const conditionalMobileConditions = [
|
productPmtSuptYn,
|
||||||
() => conditionCheckers.equalTo("Y")(productPmtSuptYn),
|
productGrPrdtProcYn,
|
||||||
() => conditionCheckers.equalTo("N")(productGrPrdtProcYn),
|
webOSVersion,
|
||||||
() => versionComparators.isVersionLT("6.0")(webOSVersion),
|
}),
|
||||||
() => fp.isNotNil(panelPrdtId)
|
({
|
||||||
];
|
productData,
|
||||||
const isConditionalMobile = conditionCheckers.checkAllConditions(conditionalMobileConditions)({});
|
panelPrdtId,
|
||||||
|
productPmtSuptYn,
|
||||||
return isDirectMobile || isConditionalMobile;
|
productGrPrdtProcYn,
|
||||||
}
|
webOSVersion,
|
||||||
)(),
|
}) => {
|
||||||
() => setProductType("shopByMobile")
|
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 방식으로 순차적 타입 체크
|
// FP 방식으로 순차적 타입 체크
|
||||||
const matchedRule = fp.reduce(
|
const matchedRule = fp.reduce(
|
||||||
(result, rule) => result.matched ? result : rule(),
|
(result, rule) => (result.matched ? result : rule()),
|
||||||
{ matched: false },
|
{ matched: false },
|
||||||
productTypeRules
|
productTypeRules
|
||||||
);
|
);
|
||||||
@@ -499,15 +602,27 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 매칭되지 않은 경우 디버깅 정보 출력
|
// 매칭되지 않은 경우 디버깅 정보 출력
|
||||||
if (!matchedRule.matched) {
|
if (!matchedRule.matched) {
|
||||||
const debugInfo = fp.pipe(
|
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,
|
pmtSuptYn: productPmtSuptYn,
|
||||||
grPrdtProcYn: productGrPrdtProcYn,
|
grPrdtProcYn: productGrPrdtProcYn,
|
||||||
prdtId: panelPrdtId,
|
prdtId: panelPrdtId,
|
||||||
webOSVersion,
|
webOSVersion,
|
||||||
})
|
})
|
||||||
)();
|
)();
|
||||||
|
|
||||||
console.warn("Unknown product type:", productData);
|
console.warn("Unknown product type:", productData);
|
||||||
console.warn("Product data properties:", debugInfo);
|
console.warn("Product data properties:", debugInfo);
|
||||||
}
|
}
|
||||||
@@ -530,45 +645,59 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
}
|
}
|
||||||
}, [getProductType, productData, themeData, panelType]);
|
}, [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 메모이제이션 (성능 최적화)
|
// FP 방식으로 타이틀과 aria-label 메모이제이션 (성능 최적화)
|
||||||
const headerTitle = useMemo(() => fp.pipe(
|
const headerTitle = useMemo(
|
||||||
() => ({ panelPrdtId, productData, panelType, themeData }),
|
() =>
|
||||||
({ panelPrdtId, productData, panelType, themeData }) => {
|
fp.pipe(
|
||||||
const productTitle = fp.pipe(
|
() => ({ panelPrdtId, productData, panelType, themeData }),
|
||||||
() => ({ panelPrdtId, productData }),
|
({ panelPrdtId, productData, panelType, themeData }) => {
|
||||||
({ panelPrdtId, productData }) =>
|
const productTitle = fp.pipe(
|
||||||
fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)()
|
() => ({ panelPrdtId, productData }),
|
||||||
? fp.pipe(() => productData, fp.get('prdtNm'))()
|
({ panelPrdtId, productData }) =>
|
||||||
: null
|
fp.isNotNil(panelPrdtId) &&
|
||||||
)();
|
fp.pipe(() => productData, fp.get("prdtNm"), fp.isNotNil)()
|
||||||
|
? fp.pipe(() => productData, fp.get("prdtNm"))()
|
||||||
const themeTitle = fp.pipe(
|
: null
|
||||||
() => ({ 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(
|
const themeTitle = fp.pipe(
|
||||||
() => ({ panelPrdtId, productData }),
|
() => ({ panelType, themeData }),
|
||||||
({ panelPrdtId, productData }) =>
|
({ panelType, themeData }) =>
|
||||||
fp.isNotNil(panelPrdtId) && fp.pipe(() => productData, fp.get('prdtNm'), fp.isNotNil)()
|
panelType === "theme" &&
|
||||||
? fp.pipe(() => productData, fp.get('prdtNm'))()
|
fp.pipe(() => themeData, fp.get("curationNm"), fp.isNotNil)()
|
||||||
: ""
|
? fp.pipe(() => themeData, fp.get("curationNm"))()
|
||||||
)(), [panelPrdtId, productData]);
|
: 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을 사용하여 파트너사별로 다른 배경 이미지를 설정하는 기능
|
// thumbnailUrl960을 사용하여 파트너사별로 다른 배경 이미지를 설정하는 기능
|
||||||
// Pink Pong 등 특정 파트너사에서만 thumbnailUrl960 데이터가 있어서 배경이 변경됨
|
// Pink Pong 등 특정 파트너사에서만 thumbnailUrl960 데이터가 있어서 배경이 변경됨
|
||||||
// 현재는 고정 배경(detailPanelBg)만 사용하기 위해 주석 처리
|
// 현재는 고정 배경(detailPanelBg)만 사용하기 위해 주석 처리
|
||||||
|
|
||||||
// FP 방식으로 배경 이미지 설정 (메모리 누수 방지)
|
// FP 방식으로 배경 이미지 설정 (메모리 누수 방지)
|
||||||
/*
|
/*
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -589,6 +718,15 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// 언마운트 시 인덱스 초기화가 필요하면:
|
// 언마운트 시 인덱스 초기화가 필요하면:
|
||||||
// useEffect(() => () => setSelectedIndex(0), [])
|
// useEffect(() => () => setSelectedIndex(0), [])
|
||||||
|
|
||||||
|
const handleProductAllSectionReady = useCallback(() => {
|
||||||
|
const spotTime = setTimeout(() => {
|
||||||
|
Spotlight.focus(SpotlightIds.DETAIL_SHOPBYMOBILE);
|
||||||
|
}, 100);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(spotTime);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<TPanel
|
<TPanel
|
||||||
@@ -621,13 +759,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
({ isLoading, panelInfo, productDataSource, productType }) => {
|
({ isLoading, panelInfo, productDataSource, productType }) => {
|
||||||
const hasRequiredData = fp.pipe(
|
const hasRequiredData = fp.pipe(
|
||||||
() => [panelInfo, productDataSource, productType],
|
() => [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 {
|
return {
|
||||||
canRender: !isLoading && hasRequiredData,
|
canRender: !isLoading && hasRequiredData,
|
||||||
showLoading: !isLoading && !hasRequiredData,
|
showLoading: !isLoading && !hasRequiredData,
|
||||||
showNothing: isLoading
|
showNothing: isLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
@@ -645,10 +788,12 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
openThemeItemOverlay={openThemeItemOverlay}
|
openThemeItemOverlay={openThemeItemOverlay}
|
||||||
setOpenThemeItemOverlay={updateThemeItemOverlay}
|
setOpenThemeItemOverlay={updateThemeItemOverlay}
|
||||||
themeProductInfo={themeProductInfo}
|
themeProductInfo={themeProductInfo}
|
||||||
|
onReady={handleProductAllSectionReady}
|
||||||
|
isOnRender={renderStates.canRender}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderStates.showLoading) {
|
if (renderStates.showLoading) {
|
||||||
return (
|
return (
|
||||||
<div className={css.loadingContainer}>
|
<div className={css.loadingContainer}>
|
||||||
@@ -656,10 +801,23 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [isLoading, panelInfo, productDataSource, productType, selectedIndex, panelPatnrId, panelPrdtId, updateSelectedIndex, openThemeItemOverlay, updateThemeItemOverlay, themeProductInfo])}
|
}, [
|
||||||
|
isLoading,
|
||||||
|
panelInfo,
|
||||||
|
productDataSource,
|
||||||
|
productType,
|
||||||
|
selectedIndex,
|
||||||
|
panelPatnrId,
|
||||||
|
panelPrdtId,
|
||||||
|
updateSelectedIndex,
|
||||||
|
openThemeItemOverlay,
|
||||||
|
updateThemeItemOverlay,
|
||||||
|
themeProductInfo,
|
||||||
|
])}
|
||||||
</TBody>
|
</TBody>
|
||||||
|
|
||||||
<ThemeItemListOverlay
|
<ThemeItemListOverlay
|
||||||
productInfo={productDataSource}
|
productInfo={productDataSource}
|
||||||
isOpen={openThemeItemOverlay}
|
isOpen={openThemeItemOverlay}
|
||||||
|
|||||||
@@ -5,13 +5,29 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-image:
|
background-image:
|
||||||
|
// linear-gradient(0deg, #222222, #222222),
|
||||||
|
// linear-gradient(180deg, rgba(34, 34, 34, 0) 89.93%, #222222 103.61%),
|
||||||
|
// linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
|
||||||
|
// linear-gradient(
|
||||||
|
// 270deg,
|
||||||
|
// rgba(0, 0, 0, 0) 43.07%,
|
||||||
|
// rgba(0, 0, 0, 0.539) 73.73%,
|
||||||
|
// rgba(0, 0, 0, 0.7) 100%
|
||||||
|
// ),
|
||||||
|
linear-gradient(0deg, rgba(0, 0, 0, 0.4)),
|
||||||
|
linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0.77) 70%,
|
||||||
|
rgba(0, 0, 0, 1) 100%
|
||||||
|
),
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
270deg,
|
270deg,
|
||||||
rgba(0, 0, 0, 0) 43.07%,
|
rgba(0, 0, 0, 0) 0%,
|
||||||
rgba(0, 0, 0, 0.539) 73.73%,
|
rgba(0, 0, 0, 0.77) 70%,
|
||||||
rgba(0, 0, 0, 0.7) 100%
|
rgba(0, 0, 0, 1) 100%
|
||||||
),
|
),
|
||||||
linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
|
//1차 변경본 너무 어두움 확인필요
|
||||||
var(--bg-url);
|
var(--bg-url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,72 @@
|
|||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
import { PropTypes } from 'prop-types';
|
||||||
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
/* eslint-disable react/jsx-no-bind */
|
/* eslint-disable react/jsx-no-bind */
|
||||||
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
||||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
import SpotlightContainerDecorator
|
||||||
import Spottable from "@enact/spotlight/Spottable";
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import React, { useCallback, useRef, useState, useMemo, useEffect } from "react";
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
|
||||||
import Spotlight from "@enact/spotlight";
|
|
||||||
import { PropTypes } from "prop-types";
|
|
||||||
|
|
||||||
|
//image
|
||||||
|
import arrowDown
|
||||||
|
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||||
|
import { pushPanel } from '../../../actions/panelActions';
|
||||||
|
import { resetShowAllReviews } from '../../../actions/productActions';
|
||||||
// ProductInfoSection imports
|
// ProductInfoSection imports
|
||||||
import TButton from "../../../components/TButton/TButton";
|
import TButton from '../../../components/TButton/TButton';
|
||||||
import { $L } from "../../../utils/helperMethods";
|
import useReviews from '../../../hooks/useReviews/useReviews';
|
||||||
|
import useScrollTo from '../../../hooks/useScrollTo';
|
||||||
|
import { panel_names } from '../../../utils/Config';
|
||||||
import {
|
import {
|
||||||
curry, pipe, when, isVal, isNotNil, defaultTo, defaultWith, get, identity, isEmpty, isNil, andThen, tap
|
andThen,
|
||||||
} from "../../../utils/fp";
|
curry,
|
||||||
import { resetShowAllReviews } from "../../../actions/productActions";
|
defaultTo,
|
||||||
import useReviews from "../../../hooks/useReviews/useReviews";
|
defaultWith,
|
||||||
import { pushPanel } from "../../../actions/panelActions";
|
get,
|
||||||
import { panel_names } from "../../../utils/Config";
|
identity,
|
||||||
import ViewAllReviewsButton from "../ProductContentSection/UserReviews/ViewAllReviewsButton";
|
isEmpty,
|
||||||
import FavoriteBtn from "../components/FavoriteBtn";
|
isNil,
|
||||||
import StarRating from "../components/StarRating";
|
isNotNil,
|
||||||
import ProductTag from "../components/ProductTag";
|
isVal,
|
||||||
import DetailMobileSendPopUp from "../components/DetailMobileSendPopUp";
|
pipe,
|
||||||
import { SpotlightIds } from "../../../utils/SpotlightIds";
|
tap,
|
||||||
import QRCode from "../ProductInfoSection/QRCode/QRCode";
|
when,
|
||||||
import ProductOverview from "../ProductOverview/ProductOverview";
|
} from '../../../utils/fp';
|
||||||
|
import { $L } from '../../../utils/helperMethods';
|
||||||
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
|
import ShowUserReviews from '../../UserReview/ShowUserReviews';
|
||||||
|
import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
|
||||||
|
import DetailMobileSendPopUp from '../components/DetailMobileSendPopUp';
|
||||||
|
import FavoriteBtn from '../components/FavoriteBtn';
|
||||||
|
import ProductTag from '../components/ProductTag';
|
||||||
|
import StarRating from '../components/StarRating';
|
||||||
// ProductContentSection imports
|
// ProductContentSection imports
|
||||||
import TScrollerDetail from "../components/TScroller/TScrollerDetail";
|
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
||||||
import CustomScrollbar from "../components/CustomScrollbar/CustomScrollbar";
|
import ProductDescription
|
||||||
import useScrollTo from "../../../hooks/useScrollTo";
|
from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||||
import ProductDetail from "../ProductContentSection/ProductDetail/ProductDetail.new";
|
import ProductDetail
|
||||||
import UserReviews from "../ProductContentSection/UserReviews/UserReviews";
|
from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||||
import YouMayAlsoLike from "../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike";
|
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
||||||
import ProductDescription from "../ProductContentSection/ProductDescription/ProductDescription";
|
import ViewAllReviewsButton
|
||||||
import ShowUserReviews from "../../UserReview/ShowUserReviews";
|
from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
||||||
|
import YouMayAlsoLike
|
||||||
|
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||||
|
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
||||||
|
import ProductOverview from '../ProductOverview/ProductOverview';
|
||||||
// CSS imports
|
// CSS imports
|
||||||
// import infoCSS from "../ProductInfoSection/ProductInfoSection.module.less";
|
// import infoCSS from "../ProductInfoSection/ProductInfoSection.module.less";
|
||||||
// import contentCSS from "../ProductContentSection/ProductContentSection.module.less";
|
// import contentCSS from "../ProductContentSection/ProductContentSection.module.less";
|
||||||
@@ -46,9 +77,9 @@ const Container = SpotlightContainerDecorator(
|
|||||||
enterTo: "last-focused",
|
enterTo: "last-focused",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
leaveFor: { right: "content-scroller-container" },
|
leaveFor: { right: "content-scroller-container" },
|
||||||
spotlightDirection: "vertical"
|
spotlightDirection: "vertical",
|
||||||
},
|
},
|
||||||
"div",
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
const ContentContainer = SpotlightContainerDecorator(
|
const ContentContainer = SpotlightContainerDecorator(
|
||||||
@@ -56,12 +87,12 @@ const ContentContainer = SpotlightContainerDecorator(
|
|||||||
enterTo: "default-element",
|
enterTo: "default-element",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
leaveFor: {
|
leaveFor: {
|
||||||
left: "spotlight-product-info-section-container"
|
left: "spotlight-product-info-section-container",
|
||||||
},
|
},
|
||||||
restrict: "none",
|
restrict: "none",
|
||||||
spotlightDirection: "vertical"
|
spotlightDirection: "vertical",
|
||||||
},
|
},
|
||||||
"div",
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
const HorizontalContainer = SpotlightContainerDecorator(
|
const HorizontalContainer = SpotlightContainerDecorator(
|
||||||
@@ -69,17 +100,19 @@ const HorizontalContainer = SpotlightContainerDecorator(
|
|||||||
enterTo: "last-focused",
|
enterTo: "last-focused",
|
||||||
preserveld: true,
|
preserveld: true,
|
||||||
defaultElement: "spotlight-product-info-section-container",
|
defaultElement: "spotlight-product-info-section-container",
|
||||||
spotlightDirection: "horizontal"
|
spotlightDirection: "horizontal",
|
||||||
},
|
},
|
||||||
"div",
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// FP: Pure function to determine product data based on type
|
// FP: Pure function to determine product data based on type
|
||||||
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
||||||
pipe(
|
pipe(
|
||||||
when(
|
when(
|
||||||
() => isVal(productType) && productType === "theme" && isVal(themeProductInfo),
|
() =>
|
||||||
|
isVal(productType) &&
|
||||||
|
productType === "theme" &&
|
||||||
|
isVal(themeProductInfo),
|
||||||
() => themeProductInfo
|
() => themeProductInfo
|
||||||
),
|
),
|
||||||
defaultTo(productInfo),
|
defaultTo(productInfo),
|
||||||
@@ -91,19 +124,14 @@ const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
|||||||
const deriveFavoriteFlag = curry((favoriteOverride, productData) =>
|
const deriveFavoriteFlag = curry((favoriteOverride, productData) =>
|
||||||
pipe(
|
pipe(
|
||||||
when(isNotNil, identity),
|
when(isNotNil, identity),
|
||||||
defaultWith(() =>
|
defaultWith(() => pipe(get("favorYn"), defaultTo("N"))(productData))
|
||||||
pipe(
|
|
||||||
get("favorYn"),
|
|
||||||
defaultTo("N")
|
|
||||||
)(productData)
|
|
||||||
)
|
|
||||||
)(favoriteOverride)
|
)(favoriteOverride)
|
||||||
);
|
);
|
||||||
|
|
||||||
// FP: Pure function to extract review grade and order phone
|
// FP: Pure function to extract review grade and order phone
|
||||||
const extractProductMeta = (productInfo) => ({
|
const extractProductMeta = (productInfo) => ({
|
||||||
revwGrd: get("revwGrd", productInfo),
|
revwGrd: get("revwGrd", productInfo),
|
||||||
orderPhnNo: get("orderPhnNo", productInfo)
|
orderPhnNo: get("orderPhnNo", productInfo),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
|
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
|
||||||
@@ -130,11 +158,22 @@ export default function ProductAllSection({
|
|||||||
openThemeItemOverlay,
|
openThemeItemOverlay,
|
||||||
setOpenThemeItemOverlay,
|
setOpenThemeItemOverlay,
|
||||||
themeProductInfo,
|
themeProductInfo,
|
||||||
|
onReady,
|
||||||
|
isOnRender,
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch();
|
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(() =>
|
const productData = useMemo(
|
||||||
getProductData(productType, themeProductInfo, productInfo),
|
() => getProductData(productType, themeProductInfo, productInfo),
|
||||||
[productType, themeProductInfo, productInfo]
|
[productType, themeProductInfo, productInfo]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -143,16 +182,21 @@ export default function ProductAllSection({
|
|||||||
previewReviews,
|
previewReviews,
|
||||||
stats,
|
stats,
|
||||||
isLoading: reviewsLoading,
|
isLoading: reviewsLoading,
|
||||||
hasReviews // 리뷰 존재 여부 플래그 추가
|
hasReviews, // 리뷰 존재 여부 플래그 추가
|
||||||
} = useReviews(productData.prdtId);
|
} = useReviews(productData.prdtId);
|
||||||
|
|
||||||
// YouMayAlsoLike 데이터 확인
|
// YouMayAlsoLike 데이터 확인
|
||||||
const youmaylikeProductData = useSelector((state) => state.main.youmaylikeData);
|
const youmaylikeProductData = useSelector(
|
||||||
const hasYouMayAlsoLike = youmaylikeProductData && youmaylikeProductData.length > 0;
|
(state) => state.main.youmaylikeData
|
||||||
|
);
|
||||||
|
const hasYouMayAlsoLike =
|
||||||
|
youmaylikeProductData && youmaylikeProductData.length > 0;
|
||||||
|
|
||||||
// ProductAllSection 마운트 시 showAllReviews 초기화
|
// ProductAllSection 마운트 시 showAllReviews 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("[ProductAllSection] Component mounted - resetting showAllReviews to false");
|
console.log(
|
||||||
|
"[ProductAllSection] Component mounted - resetting showAllReviews to false"
|
||||||
|
);
|
||||||
dispatch(resetShowAllReviews());
|
dispatch(resetShowAllReviews());
|
||||||
}, []); // 빈 dependency array = 마운트 시에만 실행
|
}, []); // 빈 dependency array = 마운트 시에만 실행
|
||||||
|
|
||||||
@@ -163,18 +207,22 @@ export default function ProductAllSection({
|
|||||||
hasProductData: !!productData,
|
hasProductData: !!productData,
|
||||||
reviewTotalCount: stats.totalReviews,
|
reviewTotalCount: stats.totalReviews,
|
||||||
averageRating: stats.averageRating,
|
averageRating: stats.averageRating,
|
||||||
productData: productData
|
productData: productData,
|
||||||
});
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
pushPanel({
|
pushPanel({
|
||||||
name: panel_names.USER_REVIEW_PANEL,
|
name: panel_names.USER_REVIEW_PANEL,
|
||||||
panelInfo: {
|
panelInfo: {
|
||||||
prdtId: productData.prdtId,
|
prdtId: productData.prdtId,
|
||||||
productImage: (productData.imgUrls600 && productData.imgUrls600[0]) || (productData.imgUrls && productData.imgUrls[0]) || productData.thumbnailUrl || 'https://placehold.co/150x150',
|
productImage:
|
||||||
brandLogo: productData.patncLogoPath || 'https://placehold.co/50x50',
|
(productData.imgUrls600 && productData.imgUrls600[0]) ||
|
||||||
productName: productData.prdtNm || '상품명 정보가 없습니다',
|
(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,
|
avgRating: stats.averageRating || 5,
|
||||||
reviewCount: stats.totalReviews || 0
|
reviewCount: stats.totalReviews || 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -186,30 +234,38 @@ export default function ProductAllSection({
|
|||||||
hasProductData: !!productData,
|
hasProductData: !!productData,
|
||||||
productDataPrdtId: productData && productData.prdtId,
|
productDataPrdtId: productData && productData.prdtId,
|
||||||
imgUrls600: productData && productData.imgUrls600,
|
imgUrls600: productData && productData.imgUrls600,
|
||||||
imgUrls600Length: productData && productData.imgUrls600 && productData.imgUrls600.length,
|
imgUrls600Length:
|
||||||
imgUrls600Type: Array.isArray(productData && productData.imgUrls600) ? 'array' : typeof (productData && productData.imgUrls600),
|
productData && productData.imgUrls600 && productData.imgUrls600.length,
|
||||||
productData: productData
|
imgUrls600Type: Array.isArray(productData && productData.imgUrls600)
|
||||||
|
? "array"
|
||||||
|
: typeof (productData && productData.imgUrls600),
|
||||||
|
productData: productData,
|
||||||
});
|
});
|
||||||
}, [productData]);
|
}, [productData]);
|
||||||
|
|
||||||
const { revwGrd, orderPhnNo } = useMemo(() =>
|
const { revwGrd, orderPhnNo } = useMemo(
|
||||||
extractProductMeta(productInfo),
|
() => extractProductMeta(productInfo),
|
||||||
[productInfo]
|
[productInfo]
|
||||||
);
|
);
|
||||||
|
|
||||||
// FP: derive favorite flag from props with local override, avoid non-I/O useEffect
|
// FP: derive favorite flag from props with local override, avoid non-I/O useEffect
|
||||||
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
const [favoriteOverride, setFavoriteOverride] = useState(null);
|
||||||
const favoriteFlag = useMemo(() =>
|
const favoriteFlag = useMemo(
|
||||||
deriveFavoriteFlag(favoriteOverride, productData),
|
() => deriveFavoriteFlag(favoriteOverride, productData),
|
||||||
[favoriteOverride, productData]
|
[favoriteOverride, productData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false);
|
const [mobileSendPopupOpen, setMobileSendPopupOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
// useReviews에서 모든 리뷰 데이터 관리
|
// useReviews에서 모든 리뷰 데이터 관리
|
||||||
const reviewTotalCount = stats.totalReviews;
|
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 스크롤 핸들러 추가
|
// User Reviews 스크롤 핸들러 추가
|
||||||
const handleUserReviewsClick = useCallback(
|
const handleUserReviewsClick = useCallback(
|
||||||
@@ -218,15 +274,16 @@ export default function ProductAllSection({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const scrollContainerRef = useRef(null);
|
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
|
// FP: Pure function for mobile popup state change
|
||||||
const handleShopByMobileOpen = useCallback(
|
const handleShopByMobileOpen = useCallback(
|
||||||
pipe(
|
pipe(() => true, setMobileSendPopupOpen),
|
||||||
() => true,
|
|
||||||
setMobileSendPopupOpen
|
|
||||||
),
|
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -275,7 +332,86 @@ export default function ProductAllSection({
|
|||||||
() => scrollToSection("scroll-marker-you-may-also-like"),
|
() => scrollToSection("scroll-marker-you-may-also-like"),
|
||||||
[scrollToSection]
|
[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 (
|
return (
|
||||||
<HorizontalContainer className={css.detailArea}>
|
<HorizontalContainer className={css.detailArea}>
|
||||||
@@ -342,26 +478,31 @@ export default function ProductAllSection({
|
|||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{orderPhnNo && (
|
<div className={css.callToOrderSection}>
|
||||||
<div className={css.callToOrderSection}>
|
{orderPhnNo && (
|
||||||
<div className={css.callToOrderText}>
|
<>
|
||||||
{$L("Call to Order")}
|
<div className={css.callToOrderText}>
|
||||||
</div>
|
{$L("Call to Order")}
|
||||||
<div className={css.phoneSection}>
|
|
||||||
<div className={css.phoneIconContainer}>
|
|
||||||
<div className={css.phoneIcon} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className={css.phoneNumber}>{orderPhnNo}</div>
|
<div className={css.phoneSection}>
|
||||||
</div>
|
<div className={css.phoneIconContainer}>
|
||||||
</div>
|
<div className={css.phoneIcon} />
|
||||||
)}
|
</div>
|
||||||
|
<div className={css.phoneNumber}>{orderPhnNo}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
className={css.actionButtonsWrapper}
|
className={css.actionButtonsWrapper}
|
||||||
spotlightId="product-info-button-container"
|
spotlightId="product-info-button-container"
|
||||||
>
|
>
|
||||||
<TButton
|
<TButton
|
||||||
className={css.productDetailsButton}
|
className={classNames(
|
||||||
|
css.productDetailsButton,
|
||||||
|
activeProductBtn ? css.active : ""
|
||||||
|
)}
|
||||||
onClick={handleProductDetailsClick}
|
onClick={handleProductDetailsClick}
|
||||||
spotlightId="product-details-button"
|
spotlightId="product-details-button"
|
||||||
>
|
>
|
||||||
@@ -369,7 +510,10 @@ export default function ProductAllSection({
|
|||||||
</TButton>
|
</TButton>
|
||||||
{hasReviews && (
|
{hasReviews && (
|
||||||
<TButton
|
<TButton
|
||||||
className={css.userReviewsButton}
|
className={classNames(
|
||||||
|
css.userReviewsButton,
|
||||||
|
activeReviewBtn ? css.active : ""
|
||||||
|
)}
|
||||||
onClick={handleUserReviewsClick}
|
onClick={handleUserReviewsClick}
|
||||||
spotlightId="user-reviews-button"
|
spotlightId="user-reviews-button"
|
||||||
>
|
>
|
||||||
@@ -378,7 +522,10 @@ export default function ProductAllSection({
|
|||||||
)}
|
)}
|
||||||
{hasYouMayAlsoLike && (
|
{hasYouMayAlsoLike && (
|
||||||
<TButton
|
<TButton
|
||||||
className={css.youMayLikeButton}
|
className={classNames(
|
||||||
|
css.youMayLikeButton,
|
||||||
|
activeYouMayLikeBtn ? css.active : ""
|
||||||
|
)}
|
||||||
onClick={handleYouMayAlsoLikeClick}
|
onClick={handleYouMayAlsoLikeClick}
|
||||||
>
|
>
|
||||||
{$L("YOU MAY ALSO LIKE")}
|
{$L("YOU MAY ALSO LIKE")}
|
||||||
@@ -387,7 +534,8 @@ export default function ProductAllSection({
|
|||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{panelInfo &&
|
{panelInfo &&
|
||||||
panelInfo && panelInfo.type === "theme" &&
|
panelInfo &&
|
||||||
|
panelInfo.type === "theme" &&
|
||||||
!openThemeItemOverlay && (
|
!openThemeItemOverlay && (
|
||||||
<TButton
|
<TButton
|
||||||
className={css.themeButton}
|
className={css.themeButton}
|
||||||
@@ -425,15 +573,23 @@ export default function ProductAllSection({
|
|||||||
spotlightId="main-content-scroller"
|
spotlightId="main-content-scroller"
|
||||||
spotlightDisabled={false}
|
spotlightDisabled={false}
|
||||||
spotlightRestrict="none"
|
spotlightRestrict="none"
|
||||||
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className={css.productDetail}>
|
<div className={css.productDetail}>
|
||||||
<div
|
<div
|
||||||
id="scroll-marker-product-details"
|
id="scroll-marker-product-details"
|
||||||
className={css.scrollMarker}
|
className={css.scrollMarker}
|
||||||
></div>
|
></div>
|
||||||
<LayoutSample onClick={handleLayoutSampleClick} />
|
{/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
|
||||||
<div id="product-details-section">
|
<div
|
||||||
{productData && productData.imgUrls600 && productData.imgUrls600.length > 0 ? (
|
id="product-details-section"
|
||||||
|
ref={productDetailRef}
|
||||||
|
onFocus={productFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
>
|
||||||
|
{productData &&
|
||||||
|
productData.imgUrls600 &&
|
||||||
|
productData.imgUrls600.length > 0 ? (
|
||||||
productData.imgUrls600.map((image, index) => (
|
productData.imgUrls600.map((image, index) => (
|
||||||
<ProductDetail
|
<ProductDetail
|
||||||
key={`product-detail-${index}`}
|
key={`product-detail-${index}`}
|
||||||
@@ -441,7 +597,7 @@ export default function ProductAllSection({
|
|||||||
...productData,
|
...productData,
|
||||||
singleImage: image,
|
singleImage: image,
|
||||||
imageIndex: index,
|
imageIndex: index,
|
||||||
totalImages: productData.imgUrls600.length
|
totalImages: productData.imgUrls600.length,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@@ -449,21 +605,29 @@ export default function ProductAllSection({
|
|||||||
<ProductDetail productInfo={productData} />
|
<ProductDetail productInfo={productData} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div id="product-description-section">
|
<div id="product-description-section" ref={descriptionRef}>
|
||||||
<ProductDescription productInfo={productData} />
|
<ProductDescription productInfo={productData} />
|
||||||
</div>
|
</div>
|
||||||
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
|
{/* 리뷰가 있을 때만 UserReviews 섹션 표시 */}
|
||||||
{hasReviews && (
|
{hasReviews && (
|
||||||
<>
|
<>
|
||||||
<div id="scroll-marker-user-reviews" className={css.scrollMarker}></div>
|
<div
|
||||||
<div id="user-reviews-section">
|
id="scroll-marker-user-reviews"
|
||||||
|
className={css.scrollMarker}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
id="user-reviews-section"
|
||||||
|
ref={reviewRef}
|
||||||
|
onFocus={reviewFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
>
|
||||||
<UserReviews
|
<UserReviews
|
||||||
productInfo={productData}
|
productInfo={productData}
|
||||||
panelInfo={panelInfo}
|
panelInfo={panelInfo}
|
||||||
reviewsData={{
|
reviewsData={{
|
||||||
previewReviews: previewReviews.slice(0, 5), // 처음 5개만
|
previewReviews: previewReviews.slice(0, 5), // 처음 5개만
|
||||||
stats: stats,
|
stats: stats,
|
||||||
isLoading: reviewsLoading
|
isLoading: reviewsLoading,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -473,21 +637,34 @@ export default function ProductAllSection({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasYouMayAlsoLike && (
|
{hasYouMayAlsoLike && (
|
||||||
<>
|
<div ref={youMayAlsoLikelRef}>
|
||||||
<div
|
<div
|
||||||
id="scroll-marker-you-may-also-like"
|
id="scroll-marker-you-may-also-like"
|
||||||
className={css.scrollMarker}
|
className={css.scrollMarker}
|
||||||
></div>
|
></div>
|
||||||
<div id="you-may-also-like-section">
|
<div id="you-may-also-like-section">
|
||||||
<YouMayAlsoLike productInfo={productData} panelInfo={panelInfo} />
|
<YouMayAlsoLike
|
||||||
|
productInfo={productData}
|
||||||
|
panelInfo={panelInfo}
|
||||||
|
onFocus={youmaylikeFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TScrollerDetail>
|
</TScrollerDetail>
|
||||||
</div>
|
</div>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
|
{!isBottom && (
|
||||||
|
<p
|
||||||
|
className={classNames(css.arrow, css.arrowBottom)}
|
||||||
|
onClick={handleArrowClickAlternative}
|
||||||
|
>
|
||||||
|
<img src={arrowDown} />
|
||||||
|
SCROLL DOWN
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</HorizontalContainer>
|
</HorizontalContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,6 @@
|
|||||||
|
|
||||||
// 1. Left Margin Section - 60px
|
// 1. Left Margin Section - 60px
|
||||||
.leftMarginSection {
|
.leftMarginSection {
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -32,9 +29,6 @@
|
|||||||
|
|
||||||
// 2. Info Section - 645px
|
// 2. Info Section - 645px
|
||||||
.infoSection {
|
.infoSection {
|
||||||
position: absolute;
|
|
||||||
left: 60px;
|
|
||||||
top: 0;
|
|
||||||
width: 650px;
|
width: 650px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -49,9 +43,6 @@
|
|||||||
|
|
||||||
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
||||||
.contentSection {
|
.contentSection {
|
||||||
position: absolute;
|
|
||||||
left: 705px; // 60px + 645px
|
|
||||||
top: 0;
|
|
||||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -286,7 +277,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 포커스 상태 추가
|
// 포커스 상태 추가
|
||||||
&:focus {
|
&:focus,
|
||||||
|
&.active {
|
||||||
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
|
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
|
||||||
outline: 2px solid @PRIMARY_COLOR_RED !important;
|
outline: 2px solid @PRIMARY_COLOR_RED !important;
|
||||||
|
|
||||||
@@ -403,7 +395,8 @@
|
|||||||
font-weight: 400; // Bold에서 Regular로 변경
|
font-weight: 400; // Bold에서 Regular로 변경
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
|
|
||||||
&:focus {
|
&:focus,
|
||||||
|
&.active {
|
||||||
background: #c72054; // 포커스시만 빨간색
|
background: #c72054; // 포커스시만 빨간색
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,9 +488,6 @@
|
|||||||
|
|
||||||
// 1. Left Margin Section - 60px
|
// 1. Left Margin Section - 60px
|
||||||
.leftMarginSection {
|
.leftMarginSection {
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -507,9 +497,6 @@
|
|||||||
|
|
||||||
// 2. Info Section - 645px
|
// 2. Info Section - 645px
|
||||||
.infoSection {
|
.infoSection {
|
||||||
position: absolute;
|
|
||||||
left: 60px;
|
|
||||||
top: 0;
|
|
||||||
width: 650px;
|
width: 650px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -524,9 +511,6 @@
|
|||||||
|
|
||||||
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
// 3. Content Section - 1180px (1114px 콘텐츠 + 66px 스크롤바)
|
||||||
.contentSection {
|
.contentSection {
|
||||||
position: absolute;
|
|
||||||
left: 705px; // 60px + 645px
|
|
||||||
top: 0;
|
|
||||||
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
width: 1210px; // 30px 마진 + 1114px 콘텐츠 + 66px 스크롤바
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
width: 1060px; // 절대 크기 지정
|
width: 1060px; // 절대 크기 지정
|
||||||
height: 100%;
|
height: 100%;
|
||||||
// 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용
|
// 좌측 30px, 우측 66px(스크롤바) 패딩을 명시적으로 적용
|
||||||
padding: 30px 20px 30px 20px;
|
padding: 10px 20px 0px 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -88,12 +88,11 @@
|
|||||||
// gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정
|
// gap 대신 margin 사용 (TV 호환성) - 화면을 적절히 채우도록 조정
|
||||||
.imageGrid {
|
.imageGrid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 20px 0 0 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -101,44 +100,45 @@
|
|||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
// padding: 30px 40px; // 좌우 패딩 증가
|
// padding: 30px 40px; // 좌우 패딩 증가
|
||||||
// padding: 30px 0px 30px 15px; // 좌우 패딩 증가
|
// padding: 30px 0px 30px 15px; // 좌우 패딩 증가
|
||||||
|
|
||||||
.imageItem {
|
.imageItem {
|
||||||
//스타일 변경
|
//스타일 변경
|
||||||
// width: 210px; // 크기 약간 증가
|
// width: 226px; // 크기 약간 증가
|
||||||
// height: 190px; // 비율 맞춤
|
// height: 218px; // 비율 맞춤
|
||||||
|
width: 240px;
|
||||||
width: 226px; // 크기 약간 증가
|
height: 240px;
|
||||||
height: 218px; // 비율 맞춤
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
//스타일 변경
|
//스타일 변경
|
||||||
// margin-right: 35px; // 마진 증가로 균등 분배
|
// margin-right: 35px; // 마진 증가로 균등 분배
|
||||||
// margin-bottom: 30px; // 세로 마진도 증가
|
// margin-bottom: 30px; // 세로 마진도 증가
|
||||||
margin-right: 20px; // 마진 증가로 균등 분배
|
margin-right: 6px; // 마진 증가로 균등 분배
|
||||||
margin-top: 20px; // 세로 마진도 증가
|
> div {
|
||||||
|
> img {
|
||||||
|
width: 226px;
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4개씩 배치하므로 4번째마다 margin-right 제거
|
// 4개씩 배치하므로 4번째마다 margin-right 제거
|
||||||
&:nth-child(4n) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
//확대 이미지
|
//확대 이미지
|
||||||
width: 240px;
|
> div {
|
||||||
height: 240px;
|
> img {
|
||||||
margin-right: 6px;
|
width: 240px;
|
||||||
margin-top: -2px;
|
height: 240px;
|
||||||
// margin-top: -3px;
|
outline: none;
|
||||||
outline: none;
|
}
|
||||||
|
}
|
||||||
&::after {
|
&::after {
|
||||||
.focused(@boxShadow: 0px, @borderRadius: 12px);
|
.focused(@boxShadow: 0px, @borderRadius: 12px);
|
||||||
// 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED)
|
// 프로젝트 표준 포커스 스타일 사용 (4px solid @PRIMARY_COLOR_RED)
|
||||||
}
|
}
|
||||||
&:nth-child(4n) {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: -14px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
|||||||
@@ -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 css from "./YouMayAlsoLike.module.less";
|
||||||
import { $L } from "../../../../utils/helperMethods";
|
import { $L } from "../../../../utils/helperMethods";
|
||||||
import TVerticalPagenator from "../../../../components/TVerticalPagenator/TVerticalPagenator";
|
import TVerticalPagenator from "../../../../components/TVerticalPagenator/TVerticalPagenator";
|
||||||
@@ -31,14 +31,26 @@ const Container = SpotlightContainerDecorator(
|
|||||||
"div"
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function YouMayAlsoLike({ productInfo, panelInfo }) {
|
export default function YouMayAlsoLike({ productInfo, panelInfo, onFocus, onBlur }) {
|
||||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
|
const [newYoumaylikeProductData, setNewYoumaylikeProductData] = useState([]);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const focusedContainerIdRef = useRef(null);
|
const focusedContainerIdRef = useRef(null);
|
||||||
|
|
||||||
const youmaylikeProductData = useSelector(
|
const youmaylikeProductData = useSelector(
|
||||||
(state) => state.main.youmaylikeData
|
(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 panels = useSelector((state) => state.panels.panels);
|
||||||
const themeProductInfos = useSelector(
|
const themeProductInfos = useSelector(
|
||||||
(state) => state.home.themeCurationDetailInfoData
|
(state) => state.home.themeCurationDetailInfoData
|
||||||
@@ -63,9 +75,21 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
|
|||||||
focusedContainerIdRef.current = containerId;
|
focusedContainerIdRef.current = containerId;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const _onFocus = useCallback(() => {
|
||||||
|
if (onFocus) {
|
||||||
|
onFocus();
|
||||||
|
}
|
||||||
|
}, [onFocus]);
|
||||||
|
|
||||||
|
const _onBlur = useCallback(() => {
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur();
|
||||||
|
}
|
||||||
|
}, [onBlur]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{youmaylikeProductData && youmaylikeProductData.length > 0 && (
|
{newYoumaylikeProductData && newYoumaylikeProductData.length > 0 && (
|
||||||
<TVerticalPagenator
|
<TVerticalPagenator
|
||||||
spotlightId={"detail_youMayAlsoLike_area"}
|
spotlightId={"detail_youMayAlsoLike_area"}
|
||||||
data-wheel-point={true}
|
data-wheel-point={true}
|
||||||
@@ -74,10 +98,14 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
|
|||||||
onFocusedContainerId={onFocusedContainerId}
|
onFocusedContainerId={onFocusedContainerId}
|
||||||
topMargin={36}
|
topMargin={36}
|
||||||
>
|
>
|
||||||
<Container className={css.container}>
|
<Container
|
||||||
|
className={css.container}
|
||||||
|
onFocus={_onFocus}
|
||||||
|
onBlur={_onBlur}
|
||||||
|
>
|
||||||
<THeader title={$L("YOU MAY ALSO LIKE")} className={css.tHeader} />
|
<THeader title={$L("YOU MAY ALSO LIKE")} className={css.tHeader} />
|
||||||
<div className={css.renderCardContainer}>
|
<div className={css.renderCardContainer}>
|
||||||
{youmaylikeProductData?.map((product, index) => {
|
{newYoumaylikeProductData?.map((product, index) => {
|
||||||
const {
|
const {
|
||||||
imgUrl,
|
imgUrl,
|
||||||
patnrId,
|
patnrId,
|
||||||
@@ -109,7 +137,6 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
|
|||||||
);
|
);
|
||||||
cursorOpen.current.stop();
|
cursorOpen.current.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TItemCard
|
<TItemCard
|
||||||
key={prdtId}
|
key={prdtId}
|
||||||
@@ -130,7 +157,7 @@ export default function YouMayAlsoLike({ productInfo, panelInfo }) {
|
|||||||
productName={prdtNm}
|
productName={prdtNm}
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
label={
|
label={
|
||||||
index * 1 + 1 + " of " + youmaylikeProductData.length
|
index * 1 + 1 + " of " + newYoumaylikeProductData.length
|
||||||
}
|
}
|
||||||
lastLabel=" go to detail, button"
|
lastLabel=" go to detail, button"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={css.reviewsSection__filters__group}>
|
<div className={css.reviewsSection__filters__group}>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`All stars(${filterCounts?.rating?.all || reviewCount || 0})`}
|
text={`All star(${filterCounts?.rating?.all || reviewCount || 0})`}
|
||||||
onClick={handleAllStarsFilter}
|
onClick={handleAllStarsFilter}
|
||||||
spotlightId="filter-all-stars"
|
spotlightId="filter-all-stars"
|
||||||
ariaLabel="Filter by all star ratings"
|
ariaLabel="Filter by all star ratings"
|
||||||
@@ -241,7 +241,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'}
|
isActive={currentFilter.type === 'rating' && currentFilter.value === 'all'}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`5 stars (${filterCounts?.rating?.[5] || 0})`}
|
text={`5 star (${filterCounts?.rating?.[5] || 0})`}
|
||||||
onClick={handle5StarsFilter}
|
onClick={handle5StarsFilter}
|
||||||
spotlightId="filter-5-stars"
|
spotlightId="filter-5-stars"
|
||||||
ariaLabel="Filter by 5 star ratings"
|
ariaLabel="Filter by 5 star ratings"
|
||||||
@@ -250,7 +250,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 5}
|
isActive={currentFilter.type === 'rating' && currentFilter.value === 5}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`4 stars (${filterCounts?.rating?.[4] || 0})`}
|
text={`4 star (${filterCounts?.rating?.[4] || 0})`}
|
||||||
onClick={handle4StarsFilter}
|
onClick={handle4StarsFilter}
|
||||||
spotlightId="filter-4-stars"
|
spotlightId="filter-4-stars"
|
||||||
ariaLabel="Filter by 4 star ratings"
|
ariaLabel="Filter by 4 star ratings"
|
||||||
@@ -259,7 +259,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 4}
|
isActive={currentFilter.type === 'rating' && currentFilter.value === 4}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`3 stars (${filterCounts?.rating?.[3] || 0})`}
|
text={`3 star (${filterCounts?.rating?.[3] || 0})`}
|
||||||
onClick={handle3StarsFilter}
|
onClick={handle3StarsFilter}
|
||||||
spotlightId="filter-3-stars"
|
spotlightId="filter-3-stars"
|
||||||
ariaLabel="Filter by 3 star ratings"
|
ariaLabel="Filter by 3 star ratings"
|
||||||
@@ -268,7 +268,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 3}
|
isActive={currentFilter.type === 'rating' && currentFilter.value === 3}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`2 stars (${filterCounts?.rating?.[2] || 0})`}
|
text={`2 star (${filterCounts?.rating?.[2] || 0})`}
|
||||||
onClick={handle2StarsFilter}
|
onClick={handle2StarsFilter}
|
||||||
spotlightId="filter-2-stars"
|
spotlightId="filter-2-stars"
|
||||||
ariaLabel="Filter by 2 star ratings"
|
ariaLabel="Filter by 2 star ratings"
|
||||||
@@ -277,7 +277,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
isActive={currentFilter.type === 'rating' && currentFilter.value === 2}
|
isActive={currentFilter.type === 'rating' && currentFilter.value === 2}
|
||||||
/>
|
/>
|
||||||
<FilterItemButton
|
<FilterItemButton
|
||||||
text={`1 stars (${filterCounts?.rating?.[1] || 0})`}
|
text={`1 star (${filterCounts?.rating?.[1] || 0})`}
|
||||||
onClick={handle1StarsFilter}
|
onClick={handle1StarsFilter}
|
||||||
spotlightId="filter-1-stars"
|
spotlightId="filter-1-stars"
|
||||||
ariaLabel="Filter by 1 star ratings"
|
ariaLabel="Filter by 1 star ratings"
|
||||||
@@ -374,4 +374,4 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserReviewPanel;
|
export default UserReviewPanel;
|
||||||
|
|||||||
@@ -102,10 +102,10 @@
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
margin-right: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
|
margin-left: 15px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -123,6 +123,9 @@
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
|
border-radius: 50px;
|
||||||
|
border: 1px solid #808080;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__productId {
|
&__productId {
|
||||||
|
|||||||
Reference in New Issue
Block a user