[250924] feat: views - TabLayout.jsx, homeReducer.js, CartPanel.jsx 등 ...
🕐 커밋 시간: 2025. 09. 24. 10:33:07 📊 변경 통계: • 총 파일: 6개 • 추가: +140줄 • 삭제: -120줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx + com.twin.app.shoptime/src/views/CartPanel/CartSidebar.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx ~ com.twin.app.shoptime/src/reducers/homeReducer.js ~ com.twin.app.shoptime/src/views/CartPanel/CartPanel.jsx ~ com.twin.app.shoptime/src/views/CartPanel/CartPanel.module.less 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx (javascript): ✅ Added: deActivateTabJobFunc() ❌ Deleted: deActivateTabJabFunc() 📄 com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx (javascript): ✅ Added: CartSidebar(), formatPrice() 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 핵심 비즈니스 로직 개선
This commit is contained in:
@@ -1,36 +1,22 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
// import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
|
||||
|
||||
//아이콘
|
||||
import { Job } from '@enact/core/util';
|
||||
//enact
|
||||
import Skinnable from '@enact/sandstone/Skinnable';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import { Cancelable } from '@enact/ui/Cancelable';
|
||||
|
||||
import shoptimeFullIconRuc
|
||||
from '../../../assets/images/icons/ic-lnb-logo-shoptime-ruc-white.png';
|
||||
import shoptimeFullIconRuc from '../../../assets/images/icons/ic-lnb-logo-shoptime-ruc-white.png';
|
||||
//이미지
|
||||
import shoptimeFullIcon
|
||||
from '../../../assets/images/icons/ic-lnb-logo-shoptime@3x.png';
|
||||
import shoptimeFullIcon from '../../../assets/images/icons/ic-lnb-logo-shoptime@3x.png';
|
||||
import { gnbOpened } from '../../actions/commonActions';
|
||||
import {
|
||||
checkEnterThroughGNB,
|
||||
resetHomeInfo,
|
||||
} from '../../actions/homeActions';
|
||||
import { checkEnterThroughGNB, resetHomeInfo } from '../../actions/homeActions';
|
||||
import { resetPanels } from '../../actions/panelActions';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useScrollTo from '../../hooks/useScrollTo';
|
||||
@@ -45,36 +31,19 @@ import MyPageIcon from './iconComponents/MyPageIcon';
|
||||
import OnSaleIcon from './iconComponents/OnSaleIcon';
|
||||
import SearchIcon from './iconComponents/SearchIcon';
|
||||
import TrendingNowIcon from './iconComponents/TrendingNowIcon';
|
||||
import CartIcon from './iconComponents/CartIcon';
|
||||
import TabItem from './TabItem';
|
||||
import TabItemSub from './TabItemSub';
|
||||
import css from './TabLayout.module.less';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "default-element" },
|
||||
"div"
|
||||
);
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div');
|
||||
|
||||
const MainContainer = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
const MainContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
const CancelableDiv = Cancelable(
|
||||
{ modal: true, onCancel: "handleCancel" },
|
||||
Skinnable(Container)
|
||||
);
|
||||
const CancelableDiv = Cancelable({ modal: true, onCancel: 'handleCancel' }, Skinnable(Container));
|
||||
|
||||
class TabMenuItem {
|
||||
constructor(
|
||||
icons = "",
|
||||
title = "",
|
||||
spotlightId,
|
||||
path,
|
||||
patncNm,
|
||||
target,
|
||||
id,
|
||||
children = []
|
||||
) {
|
||||
constructor(icons = '', title = '', spotlightId, path, patncNm, target, id, children = []) {
|
||||
this.icons = icons;
|
||||
this.title = title;
|
||||
this.spotlightId = spotlightId;
|
||||
@@ -105,10 +74,10 @@ class TabMenuItem {
|
||||
};
|
||||
}
|
||||
|
||||
const deActivateTabJabFunc = (func) => {
|
||||
const deActivateTabJobFunc = (func) => {
|
||||
func();
|
||||
};
|
||||
let deActivateTabJob = new Job(deActivateTabJabFunc, 2000);
|
||||
let deActivateTabJob = new Job(deActivateTabJobFunc, 2000);
|
||||
|
||||
const clearPanelSwitching = (ref) => {
|
||||
if (ref) {
|
||||
@@ -131,40 +100,42 @@ const PANELS_HAS_TAB = [
|
||||
panel_names.ON_SALE_PANEL,
|
||||
panel_names.SEARCH_PANEL,
|
||||
panel_names.TRENDING_NOW_PANEL,
|
||||
panel_names.JUST_FOR_YOU_PANEL,
|
||||
];
|
||||
|
||||
export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
const { getScrollTo, scrollTop } = useScrollTo();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// 🔽 GNB 포커스 추적 - 전역 상태 사용 (HomeBanner, HomePanel과 동일한 인스턴스)
|
||||
// const focusHistory = useFocusHistory({
|
||||
// enableLogging: true,
|
||||
// useGlobalState: true,
|
||||
// logPrefix: '[FocusHistory]',
|
||||
// });
|
||||
const [mainExpanded, setMainExpanded] = useState(false);
|
||||
const [mainSelectedIndex, setMainSelectedIndex] = useState(-1);
|
||||
const [secondDepthReduce, setSecondDepthReduce] = useState(false);
|
||||
const [lastFocusId, setLastFocusId] = useState(null);
|
||||
const [selectedTitle, setSelectedTitle] = useState("");
|
||||
const [selectedTitle, setSelectedTitle] = useState('');
|
||||
const [selectedSubItemId, setSelectedSubItemId] = useState(null);
|
||||
const [selectedSubIndex, setSelectedSubIndex] = useState(-1);
|
||||
const [subTabLastFocusId, setSubTabLastFocusId] = useState(null);
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [tabFocused, setTabFocused] = useState([false, false, false]); //COLLABSED_MAIN, ACTIVATED_MAIN, ACTIVATED_SUB
|
||||
const panelSwitching = useRef(null);
|
||||
const cursorVisible = useSelector(
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
);
|
||||
const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible);
|
||||
const cursorVisibleRef = usePrevious(cursorVisible);
|
||||
const data = useSelector((state) => state.home.menuData?.data);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const { loginUserData } = useSelector((state) => state.common.appStatus);
|
||||
const menuItems = useSelector((state) => state.home.menuItems);
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus.webOSVersion
|
||||
);
|
||||
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
|
||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||
const broadcast = useSelector(
|
||||
(state) => state.common.broadcast,
|
||||
(newState) => newState?.type !== "deActivateTab" // 'deActivateTab'일 때만 리렌더링 허용
|
||||
(newState) => newState?.type !== 'deActivateTab' // 'deActivateTab'일 때만 리렌더링 허용
|
||||
);
|
||||
const deviceCountryCode = httpHeader["X-Device-Country"];
|
||||
const deviceCountryCode = httpHeader['X-Device-Country'];
|
||||
const mouseNavOpen = useRef(new Job((func) => func(), 1000));
|
||||
const mouseMainEntered = useRef(false);
|
||||
const scrollTopJobRef = useRef(new Job((func) => func(), 0));
|
||||
@@ -173,7 +144,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
let result = [];
|
||||
|
||||
switch (type) {
|
||||
case "GNB":
|
||||
case 'GNB':
|
||||
result =
|
||||
data?.gnb &&
|
||||
data.gnb.map((item) => ({
|
||||
@@ -189,7 +160,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
icons: CategoryIcon,
|
||||
id: item.lgCatCd,
|
||||
title: item.lgCatNm,
|
||||
spotlightId: "spotlight_category",
|
||||
spotlightId: 'spotlight_category',
|
||||
target: [
|
||||
{
|
||||
name: panel_names.CATEGORY_PANEL,
|
||||
@@ -216,11 +187,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
id: item.patnrId,
|
||||
path: item.patncLogoPath,
|
||||
patncNm: item.patncNm,
|
||||
spotlightId: "spotlight_featuredbrand",
|
||||
spotlightId: 'spotlight_featuredbrand',
|
||||
target: [
|
||||
{
|
||||
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||
panelInfo: { from: "gnb", patnrId: item.patnrId },
|
||||
panelInfo: { from: 'gnb', patnrId: item.patnrId },
|
||||
},
|
||||
],
|
||||
}));
|
||||
@@ -232,7 +203,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
icons: MyPageIcon,
|
||||
id: item.menuId,
|
||||
title: item.menuNm,
|
||||
spotlightId: "spotlight_mypage",
|
||||
spotlightId: 'spotlight_mypage',
|
||||
target: [
|
||||
{
|
||||
name: panel_names.MY_PAGE_PANEL,
|
||||
@@ -245,13 +216,10 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
],
|
||||
}))
|
||||
.filter((item) => {
|
||||
if (!loginUserData.userNumber && item.title === "My Orders") {
|
||||
if (!loginUserData.userNumber && item.title === 'My Orders') {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
webOSVersion < "6.0" &&
|
||||
(item.title === "My Orders" || item.title === "My Info")
|
||||
) {
|
||||
if (webOSVersion < '6.0' && (item.title === 'My Orders' || item.title === 'My Info')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -262,7 +230,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
result = [
|
||||
{
|
||||
icons: SearchIcon,
|
||||
spotlightId: "spotlight_search",
|
||||
spotlightId: 'spotlight_search',
|
||||
target: [{ name: panel_names.SEARCH_PANEL }],
|
||||
},
|
||||
];
|
||||
@@ -272,7 +240,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
result = [
|
||||
{
|
||||
icons: HomeIcon,
|
||||
spotlightId: "spotlight_home",
|
||||
spotlightId: 'spotlight_home',
|
||||
target: [{ name: panel_names.HOME_PANEL }],
|
||||
},
|
||||
];
|
||||
@@ -282,7 +250,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
result = [
|
||||
{
|
||||
icons: OnSaleIcon,
|
||||
spotlightId: "spotlight_onsale",
|
||||
spotlightId: 'spotlight_onsale',
|
||||
target: [{ name: panel_names.ON_SALE_PANEL }],
|
||||
},
|
||||
];
|
||||
@@ -292,7 +260,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
result = [
|
||||
{
|
||||
icons: TrendingNowIcon,
|
||||
spotlightId: "spotlight_trendingnow",
|
||||
spotlightId: 'spotlight_trendingnow',
|
||||
target: [{ name: panel_names.TRENDING_NOW_PANEL }],
|
||||
},
|
||||
];
|
||||
@@ -302,11 +270,21 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
result = [
|
||||
{
|
||||
icons: HotPicksIcon,
|
||||
spotlightId: "spotlight_hotpicks",
|
||||
spotlightId: 'spotlight_hotpicks',
|
||||
target: [{ name: panel_names.HOT_PICKS_PANEL }],
|
||||
},
|
||||
];
|
||||
break;
|
||||
|
||||
case 10800:
|
||||
result = [
|
||||
{
|
||||
icons: CartIcon,
|
||||
spotlightId: 'spotlight_cart',
|
||||
target: [{ name: panel_names.CART_PANEL }],
|
||||
},
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -316,7 +294,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
if (data) {
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
const currentKey = menuItems[i].menuId;
|
||||
const menuInfo = getMenuData(currentKey || "GNB");
|
||||
const menuInfo = getMenuData(currentKey || 'GNB');
|
||||
|
||||
for (let j = 0; j < menuInfo.length; j++) {
|
||||
if (![10600, 10500, 10300].includes(currentKey)) {
|
||||
@@ -375,6 +353,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
|
||||
const onTabHasFocus = useCallback(
|
||||
(type) => (event) => {
|
||||
// 🔽 GNB 영역으로 포커스가 들어올 때 추적
|
||||
// if (type === ACTIVATED_MAIN || type === ACTIVATED_SUB) {
|
||||
// focusHistory.enqueue('gnb');
|
||||
// }
|
||||
|
||||
switch (type) {
|
||||
case COLLABSED_MAIN: {
|
||||
if (cursorVisibleRef.current) {
|
||||
@@ -567,7 +550,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
}, [mainExpanded, mainSelectedIndex]);
|
||||
|
||||
const logoImg = useMemo(() => {
|
||||
if (deviceCountryCode === "RU") {
|
||||
if (deviceCountryCode === 'RU') {
|
||||
return shoptimeFullIconRuc;
|
||||
} else return shoptimeFullIcon;
|
||||
}, [deviceCountryCode]);
|
||||
@@ -580,11 +563,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
}, [topPanelName]);
|
||||
|
||||
const showSubTab = useMemo(() => {
|
||||
if (
|
||||
tabActivated &&
|
||||
tabs[mainSelectedIndex] &&
|
||||
tabs[mainSelectedIndex].hasChildren()
|
||||
) {
|
||||
if (tabActivated && tabs[mainSelectedIndex] && tabs[mainSelectedIndex].hasChildren()) {
|
||||
return true; // 서브 탭이 있는 경우
|
||||
}
|
||||
return false; // 서브 탭이 없는 경우
|
||||
@@ -612,7 +591,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
dispatch(gnbOpened(true));
|
||||
|
||||
if (panels.length === 0) {
|
||||
Spotlight.focus("spotlight_home");
|
||||
Spotlight.focus('spotlight_home');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -622,7 +601,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
//
|
||||
else {
|
||||
if (!subTabLastFocusId) {
|
||||
Spotlight.focus("spotlight_home");
|
||||
Spotlight.focus('spotlight_home');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +615,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
useEffect(() => {
|
||||
if (!panelInfo) {
|
||||
setMainSelectedIndex(-1);
|
||||
setLastFocusId("spotlight_home");
|
||||
setLastFocusId('spotlight_home');
|
||||
setSubTabLastFocusId(null);
|
||||
return;
|
||||
}
|
||||
@@ -653,17 +632,11 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
subTarget = panelInfo.lgCatCd;
|
||||
}
|
||||
// case: Featured Brands 2depth
|
||||
else if (
|
||||
topPanelName === panel_names.FEATURED_BRANDS_PANEL &&
|
||||
panelInfo?.patnrId
|
||||
) {
|
||||
else if (topPanelName === panel_names.FEATURED_BRANDS_PANEL && panelInfo?.patnrId) {
|
||||
subTarget = panelInfo.patnrId;
|
||||
}
|
||||
// case: My Info 2depth
|
||||
else if (
|
||||
topPanelName === panel_names.MY_PAGE_PANEL &&
|
||||
panelInfo?.menuId
|
||||
) {
|
||||
else if (topPanelName === panel_names.MY_PAGE_PANEL && panelInfo?.menuId) {
|
||||
subTarget = panelInfo.menuId;
|
||||
}
|
||||
}
|
||||
@@ -688,8 +661,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
}, [tabActivated, subTabLastFocusId, mainSelectedIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
const hasFeaturedBrands =
|
||||
tabs[mainSelectedIndex]?.children[0]?.path !== undefined;
|
||||
const hasFeaturedBrands = tabs[mainSelectedIndex]?.children[0]?.path !== undefined;
|
||||
|
||||
const SCROLL_OFFSET_INDEX = hasFeaturedBrands ? 8 : 9;
|
||||
|
||||
@@ -707,9 +679,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
tabs[mainSelectedIndex]?.children.length - 1 >= selectedSubIndex
|
||||
) {
|
||||
const targetScrollIndex = selectedSubIndex - SCROLL_OFFSET_INDEX;
|
||||
scrollTopJobRef.current.start(() =>
|
||||
scrollTop({ y: y * targetScrollIndex })
|
||||
);
|
||||
scrollTopJobRef.current.start(() => scrollTop({ y: y * targetScrollIndex }));
|
||||
|
||||
return () => scrollTopJobRef.current.stop();
|
||||
}
|
||||
@@ -742,8 +712,8 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
}, [cursorVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (broadcast?.type === "deActivateTab") {
|
||||
console.log("TabLayout deactivateTab by broadcast");
|
||||
if (broadcast?.type === 'deActivateTab') {
|
||||
console.log('TabLayout deactivateTab by broadcast');
|
||||
deActivateTab();
|
||||
}
|
||||
}, [broadcast]);
|
||||
@@ -775,7 +745,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
|
||||
const moveFocusToMainTab = useCallback(
|
||||
(e) => {
|
||||
if (e.key === "ArrowLeft" && showSubTab && lastFocusId) {
|
||||
if (e.key === 'ArrowLeft' && showSubTab && lastFocusId) {
|
||||
Spotlight.focus(lastFocusId);
|
||||
}
|
||||
},
|
||||
@@ -818,16 +788,14 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
<img
|
||||
src={logoImg}
|
||||
alt=""
|
||||
className={classNames(
|
||||
deviceCountryCode === "RU" && css.rucLogo
|
||||
)}
|
||||
className={classNames(deviceCountryCode === 'RU' && css.rucLogo)}
|
||||
/>
|
||||
</h1>
|
||||
|
||||
{tabs.map((item, index) => (
|
||||
<TabItem
|
||||
{...item}
|
||||
key={"tabitemExpanded" + index}
|
||||
key={'tabitemExpanded' + index}
|
||||
onFocus={onFocus}
|
||||
spotlightId={item.spotlightId}
|
||||
setLastFocusId={setLastFocusId}
|
||||
@@ -837,11 +805,10 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
icons={item.icons}
|
||||
expanded={mainExpanded}
|
||||
mainSelected={
|
||||
(panels.length === 0 &&
|
||||
item.spotlightId === "spotlight_home") ||
|
||||
(panels.length === 0 && item.spotlightId === 'spotlight_home') ||
|
||||
(panels[0]?.name === panel_names.PLAYER_PANEL &&
|
||||
panels.length === 1 &&
|
||||
item.spotlightId === "spotlight_home") ||
|
||||
item.spotlightId === 'spotlight_home') ||
|
||||
(Array.isArray(item.target) &&
|
||||
item.target[0]?.name &&
|
||||
panels[0]?.name === item.target[0]?.name)
|
||||
@@ -878,19 +845,14 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
onMouseLeave={onTabBlur(ACTIVATED_SUB)}
|
||||
onKeyDown={moveFocusToMainTab}
|
||||
>
|
||||
<TScroller
|
||||
cbScrollTo={getScrollTo}
|
||||
className={css.scrollWrap}
|
||||
>
|
||||
<TScroller cbScrollTo={getScrollTo} className={css.scrollWrap}>
|
||||
{showSubTab &&
|
||||
tabs[mainSelectedIndex]?.children.map((item, index) => {
|
||||
return (
|
||||
<TabItemSub
|
||||
{...item}
|
||||
mainMenuTitle={
|
||||
tabs && tabs[mainSelectedIndex]?.title
|
||||
}
|
||||
key={"tabitemSubmenu" + index}
|
||||
mainMenuTitle={tabs && tabs[mainSelectedIndex]?.title}
|
||||
key={'tabitemSubmenu' + index}
|
||||
spotlightId={item.spotlightId}
|
||||
setLastFocusId={setSubTabLastFocusId}
|
||||
onClick={onClickSubItem}
|
||||
@@ -898,7 +860,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
index={index}
|
||||
isSubItem={true}
|
||||
deActivateTab={deActivateTab}
|
||||
title={item.title + "-sub"}
|
||||
title={item.title + '-sub'}
|
||||
itemId={item.id}
|
||||
path={item.path}
|
||||
patncNm={item.patncNm}
|
||||
@@ -912,10 +874,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
||||
panels[0]?.panelInfo === item.target[0]?.panelInfo
|
||||
}
|
||||
label={
|
||||
index * 1 +
|
||||
1 +
|
||||
" of " +
|
||||
tabs[mainSelectedIndex]?.children.length
|
||||
index * 1 + 1 + ' of ' + tabs[mainSelectedIndex]?.children.length
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ const initialState = {
|
||||
homeTopDisplayInfo: {},
|
||||
bannerData: {
|
||||
data: {},
|
||||
status: "idle",
|
||||
status: 'idle',
|
||||
},
|
||||
defaultFocus: null,
|
||||
enterThroughGNB: false,
|
||||
@@ -19,8 +19,8 @@ const initialState = {
|
||||
bannerIndices: {},
|
||||
themeMenuShelfInfoData: null,
|
||||
homeInfo: null,
|
||||
curationId: "",
|
||||
curationTitle: "",
|
||||
curationId: '',
|
||||
curationTitle: '',
|
||||
playerControl: {
|
||||
ownerId: null,
|
||||
videoInfo: null,
|
||||
@@ -71,6 +71,13 @@ export const homeReducer = (state = initialState, action) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cart 메뉴 임시 추가
|
||||
menuItems.push({
|
||||
title: 'Cart',
|
||||
menuId: 10800,
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
menuData: action.payload,
|
||||
@@ -88,15 +95,13 @@ export const homeReducer = (state = initialState, action) => {
|
||||
const homeTopDisplayInfo = action.payload?.homeTopDisplayInfos?.[0] ?? {};
|
||||
const bannerData = action.payload?.homeMainContentsBannerInfos?.[0] ?? {};
|
||||
|
||||
const curationTitle =
|
||||
action.payload?.homeMainContentsBannerInfos?.[0].curtNm;
|
||||
const curationId =
|
||||
action.payload?.homeMainContentsBannerInfos?.[0].curationId;
|
||||
const curationTitle = action.payload?.homeMainContentsBannerInfos?.[0].curtNm;
|
||||
const curationId = action.payload?.homeMainContentsBannerInfos?.[0].curationId;
|
||||
|
||||
if (bannerData.bannerInfos) {
|
||||
for (let i = 0; i < bannerData.bannerInfos.length; i++) {
|
||||
const info = bannerData.bannerInfos[i];
|
||||
if (info.shptmDspyTpNm === "Random") {
|
||||
if (info.shptmDspyTpNm === 'Random') {
|
||||
const dataLength = info.bannerDetailInfos.length;
|
||||
info.randomIndex = Math.floor(Math.random() * dataLength);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
import React from "react";
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import TPanel from "../../components/TPanel/TPanel";
|
||||
import TPanel from '../../components/TPanel/TPanel';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import THeader from '../../components/THeader/THeader';
|
||||
import { popPanel } from '../../actions/panelActions';
|
||||
import CartSidebar from './CartSidebar';
|
||||
|
||||
export default function CartPanel({spotlightId}) {
|
||||
return <TPanel spotlightId={spotlightId}>Cart</TPanel>;
|
||||
import css from './CartPanel.module.less';
|
||||
|
||||
export default function CartPanel({ spotlightId }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onBackClick = useCallback(() => {
|
||||
dispatch(popPanel());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<TPanel spotlightId={spotlightId} isTabActivated={false}>
|
||||
<TBody className={css.tbody}>
|
||||
<THeader className={css.theader} title="CART" onBackButton onClick={onBackClick} />
|
||||
<div className={css.Wrap}>
|
||||
<div className={css.leftSection}>
|
||||
<CartSidebar />
|
||||
</div>
|
||||
<div className={css.rightSection}>{/* 오른쪽 상품 영역 */}</div>
|
||||
</div>
|
||||
</TBody>
|
||||
</TPanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
.tbody {
|
||||
.size(@w: 1920px, @h: 100%);
|
||||
.theader {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding-left: 60px;
|
||||
z-index: 10;
|
||||
}
|
||||
.Wrap {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.leftSection {
|
||||
.size(@w: 600px, @h: inherit);
|
||||
background-color: #2c343f;
|
||||
position: sticky;
|
||||
top: 90px;
|
||||
height: calc(100vh - 90px);
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
71
com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx
Normal file
71
com.twin.app.shoptime/src/views/CartPanel/CartSidebar.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
|
||||
import TButton from '../../components/TButton/TButton';
|
||||
import css from './CartSidebar.module.less';
|
||||
|
||||
const CartSidebar = () => {
|
||||
const mockData = {
|
||||
itemCount: 3,
|
||||
subtotal: 199.97,
|
||||
optionTotal: 29.99,
|
||||
shippingHandling: 19.99,
|
||||
orderTotalBeforeTax: 249.95,
|
||||
};
|
||||
|
||||
const formatPrice = (price) => {
|
||||
return `$${price.toFixed(2)}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={css.sidebar}>
|
||||
<div className={css.summarySection}>
|
||||
<div className={css.header}>
|
||||
<div className={css.title}>Subtotal</div>
|
||||
<span className={css.itemCount}>{mockData.itemCount} Items</span>
|
||||
</div>
|
||||
|
||||
<div className={css.priceList}>
|
||||
<div className={css.priceItem}>
|
||||
<span className={css.label}>Subtotal</span>
|
||||
<span className={css.value}>{formatPrice(mockData.subtotal)}</span>
|
||||
</div>
|
||||
<div className={css.priceItem}>
|
||||
<span className={css.label}>Option</span>
|
||||
<span className={css.value}>{formatPrice(mockData.optionTotal)}</span>
|
||||
</div>
|
||||
<div className={css.priceItem}>
|
||||
<span className={css.label}>S&H</span>
|
||||
<span className={css.value}>{formatPrice(mockData.shippingHandling)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.totalRow}>
|
||||
<span className={css.totalLabel}>Order Total (Before Tax)</span>
|
||||
<span className={css.totalValue}>{formatPrice(mockData.orderTotalBeforeTax)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.notesSection}>
|
||||
<p className={css.note}>
|
||||
• Final costs will be available on your Order Review before you place order
|
||||
</p>
|
||||
<p className={css.note}>
|
||||
• By proceeding, you agree to ShopTime's General Terms of Use and acknowledge the Privacy
|
||||
Statement
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={css.buttonSection}>
|
||||
<TButton
|
||||
className={css.checkoutButton}
|
||||
spotlightId="cart-checkout-button"
|
||||
onClick={() => console.log('Checkout clicked')}
|
||||
>
|
||||
Checkout
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CartSidebar;
|
||||
@@ -0,0 +1,119 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 60px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.summarySection {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 480px;
|
||||
height: 40px;
|
||||
margin: 0 60px 20px;
|
||||
padding: 9px 0;
|
||||
// background-color: #000;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 148px;
|
||||
height: 22px;
|
||||
margin: 0 218px 0 0;
|
||||
font-family: LGSmartUI;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.itemCount {
|
||||
font-size: 14px;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.priceList {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.priceItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.priceItem:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.totalRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #4a5568;
|
||||
}
|
||||
|
||||
.totalLabel {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.totalValue {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.notesSection {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.note:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.checkoutButton {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #007bff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
Reference in New Issue
Block a user