Files
shoptime/com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx
hyunwoo93.cha e14fa043cb [전체] userInfo -> userNumber 변경 /
reducer appStatus.loginUserData 구조 변경
2024-05-21 16:10:54 +09:00

848 lines
24 KiB
JavaScript

import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import {
useDispatch,
useSelector,
} from 'react-redux';
//아이콘
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 { Cancelable } from '@enact/ui/Cancelable';
//이미지
import shoptimeFullIcon
from '../../../assets/images/icons/ic-lnb-logo-shoptime@3x.png';
import { gnbOpened } from '../../actions/commonActions';
import { updateSpotlightId } from '../../actions/homeActions';
import { resetPanels } from '../../actions/panelActions';
import useLogService from '../../hooks/useLogService';
import useScrollTo from '../../hooks/useScrollTo';
import {
LOG_MENU,
LOG_TP_NO,
panel_names,
} from '../../utils/Config';
import { formatGMTString } from '../../utils/helperMethods';
import { SpotlightIds } from '../../utils/SpotlightIds';
import TScroller from '../TScroller/TScroller';
import CategoryIcon from './iconComponents/CategoryIcon';
import FeaturedBrandIcon from './iconComponents/FeaturedBrandIcon';
import HomeIcon from './iconComponents/HomeIcon';
import HotPicksIcon from './iconComponents/HotPicksIcon';
import MyPageIcon from './iconComponents/MyPageIcon';
import OnSaleIcon from './iconComponents/OnSaleIcon';
import SearchIcon from './iconComponents/SearchIcon';
import TrendingNowIcon from './iconComponents/TrendingNowIcon';
import TabItem from './TabItem';
import TabItemSub from './TabItemSub';
import css from './TabLayout.module.less';
const Container = SpotlightContainerDecorator(
{ enterTo: "default-element", preserveId: true },
"div"
);
const MainContainer = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
const CancelableDiv = Cancelable(
{ modal: true, onCancel: "handleCancel" },
Skinnable(Container)
);
class TabMenuItem {
constructor(
icons = "",
title = "",
spotlightId,
path,
target,
id,
children = []
) {
this.icons = icons;
this.title = title;
this.spotlightId = spotlightId;
this.path = path;
this.target = target;
this.id = id;
this.children = children.map(
(child) =>
new TabMenuItem(
child.icons,
child.title,
child.spotlightId,
child.path,
child.target,
child.id
)
);
}
hasChildren = () => {
return this.children.length > 1;
};
getChildren = () => {
return this.children;
};
}
const deActivateTabJabFunc = (func) => {
func();
};
let deActivateTabJob = new Job(deActivateTabJabFunc, 2000);
const clearPanelSwitching = (ref) => {
if (ref) {
ref.current = false;
}
};
let panelSwitchingJob = new Job(clearPanelSwitching, 500);
const COLLABSED_MAIN = 0;
const ACTIVATED_MAIN = 1;
const ACTIVATED_SUB = 2;
const EXTRA_AREA = 3;
const PANELS_HAS_TAB = [
panel_names.CATEGORY_PANEL,
panel_names.FEATURED_BRANDS_PANEL,
panel_names.HOME_PANEL,
panel_names.HOT_PICKS_PANEL,
panel_names.MY_PAGE_PANEL,
panel_names.ON_SALE_PANEL,
panel_names.SEARCH_PANEL,
panel_names.TRENDING_NOW_PANEL,
];
export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
const { sendLogGNB } = useLogService();
const { getScrollTo, scrollTop } = useScrollTo();
const dispatch = useDispatch();
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 [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);
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 mouseNavOpen = useRef(new Job((func) => func(), 1500));
const mouseMainEntered = useRef(false);
const entryMenuRef = useRef(LOG_MENU.APP);
const nowMenuRef = useRef(null);
const inDtRef = useRef(null);
// const intervalRef = useRef(null);
// useEffect(() => {
// if (window.localStorage.getItem("test")) {
// const params = window.localStorage.getItem("test");
// console.log("pyh params", JSON.parse(params));
// sendLogGNB(JSON.parse(params));
// window.localStorage.removeItem("test");
// }
// }, []);
// useEffect(() => {
// if (nowMenuRef.current === null) {
// nowMenuRef.current = panels.length > 0 ? panels[0]?.name : LOG_MENU.HOME;
// }
// intervalRef.current = setInterval(() => {
// const params = {
// entryMenu: entryMenuRef.current,
// inDt: formatGMTString(inDtRef.current),
// logTpNo: LOG_TP_NO.GNB,
// menuMovSno: "",
// nowMenu: nowMenuRef.current,
// outDt: formatGMTString(new Date()),
// };
// console.log("pyh localStorage", params);
// window.localStorage.setItem("test", JSON.stringify(params));
// }, 10000);
// return () => {
// if (intervalRef.current) {
// console.log("pyh return");
// clearInterval(intervalRef.current);
// }
// };
// }, [panels]);
const getMenuData = (type) => {
let result = [];
switch (type) {
case "GNB":
result =
data?.gnb &&
data.gnb.map((item) => ({
title: item.menuNm,
}));
break;
//카테고리
case 10500:
result =
data?.homeCategory &&
data.homeCategory.map((item) => ({
icons: CategoryIcon,
id: item.lgCatCd,
title: item.lgCatNm,
spotlightId: "spotlight_category",
target: [
{
name: panel_names.CATEGORY_PANEL,
panelInfo: {
lgCatNm: item.lgCatNm,
lgCatCd: item.lgCatCd,
COUNT: item.COUNT,
currentSpot: null,
dropDownTab: 0,
tab: 0,
y: 0,
},
},
],
}));
break;
//브랜드
case 10300:
result =
data?.shortFeaturedBrands &&
data.shortFeaturedBrands.map((item) => ({
icons: FeaturedBrandIcon,
id: item.patnrId,
path: item.patncLogoPath,
spotlightId: "spotlight_featuredbrand",
target: [
{
name: panel_names.FEATURED_BRANDS_PANEL,
panelInfo: { patnrId: item.patnrId },
},
],
}));
break;
//
case 10600:
result = data.mypage
.map((item) => ({
icons: MyPageIcon,
id: item.menuId,
title: item.menuNm,
spotlightId: "spotlight_mypage",
target: [
{
name: panel_names.MY_PAGE_PANEL,
panelInfo: { menuNm: item.menuNm, menuOrd: item.menuOrd },
},
],
}))
.filter((item) => {
if (loginUserData.userNumber === null) {
return item.title !== "My Orders";
}
return true;
});
break;
case 10700:
result = [
{
icons: SearchIcon,
spotlightId: "spotlight_search",
target: [{ name: panel_names.SEARCH_PANEL }],
},
];
break;
case 10100:
result = [
{
icons: HomeIcon,
spotlightId: "spotlight_home",
target: [{ name: panel_names.HOME_PANEL }],
},
];
break;
case 10400:
result = [
{
icons: OnSaleIcon,
spotlightId: "spotlight_onsale",
target: [{ name: panel_names.ON_SALE_PANEL }],
},
];
break;
case 10150:
result = [
{
icons: TrendingNowIcon,
spotlightId: "spotlight_trendingnow",
target: [{ name: panel_names.TRENDING_NOW_PANEL }],
},
];
break;
case 10200:
result = [
{
icons: HotPicksIcon,
spotlightId: "spotlight_hotpicks",
target: [{ name: panel_names.HOT_PICKS_PANEL }],
},
];
break;
}
return result;
};
const dataDivide = useCallback(() => {
if (data) {
for (let i = 0; i < menuItems.length; i++) {
const currentKey = menuItems[i].menuId;
const menuInfo = getMenuData(currentKey || "GNB");
for (let j = 0; j < menuInfo.length; j++) {
if (![10600, 10500, 10300].includes(currentKey)) {
menuItems[i].target = menuInfo[j].target;
}
menuItems[i].spotlightId = menuInfo[j].spotlightId;
menuItems[i].icons = menuInfo[j].icons;
if (currentKey === 10600 || 10500 || 10300) {
menuItems[i].children = menuInfo.map((item) => ({
id: item.id,
title: item.title,
path: item.path,
target: item.target,
spotlightId: `secondDepth-${item.id}`,
}));
}
}
}
}
if (menuItems) {
setTabs(makeTabmenu());
// setInDt(new Date());
inDtRef.current = new Date();
}
}, [menuItems, loginUserData]);
const makeTabmenu = useCallback(() => {
const t = [];
for (let i = 0; i < menuItems.length; i++) {
const tabmenu = new TabMenuItem(
menuItems[i].icons,
menuItems[i].title,
menuItems[i].spotlightId,
menuItems[i].path,
menuItems[i].target,
menuItems[i].id,
menuItems[i].children
);
t.push(tabmenu);
}
return t;
}, [menuItems]);
useEffect(() => {
dataDivide();
}, [menuItems, loginUserData]);
const deActivateTab = useCallback(() => {
setTabFocused([false, false, false, false]);
setMainSelectedIndex(-1);
setMainExpanded(false);
dispatch(gnbOpened(false));
}, [dispatch]);
const onTabHasFocus = useCallback(
(type) => (event) => {
switch (type) {
case COLLABSED_MAIN: {
if (cursorVisible) {
mouseNavOpen.current.start(() => {
setMainExpanded(true);
});
}
break;
}
case ACTIVATED_MAIN: {
if (!cursorVisible) {
const parent = event.target.parentNode;
const children = parent.childNodes;
const index = Array.prototype.indexOf.call(children, event.target);
setMainExpanded(true);
setMainSelectedIndex(index - 1);
setSecondDepthReduce(false);
}
break;
}
case ACTIVATED_SUB: {
if (!cursorVisible) {
}
}
case EXTRA_AREA: {
if (cursorVisible) {
return;
}
}
}
setTabFocused((prevState) => {
const prev = [...prevState];
prev[type] = true;
return prev;
});
},
[cursorVisible, deActivateTab]
);
const onTabBlur = useCallback(
(type) => (event) => {
switch (type) {
case COLLABSED_MAIN: {
if (cursorVisible) {
mouseNavOpen.current.stop();
}
break;
}
case ACTIVATED_MAIN: {
if (!cursorVisible) {
}
break;
}
case ACTIVATED_SUB: {
if (!cursorVisible) {
}
break;
}
case EXTRA_AREA: {
if (cursorVisible) {
deActivateTabJob.stop();
return;
}
}
}
setTabFocused((prevState) => {
const prev = [...prevState];
prev[type] = true;
return prev;
});
},
[cursorVisible]
);
const onFocus = () => {
if (showSubTab) {
setSecondDepthReduce((prev) => !prev);
}
};
const handleNavigation = useCallback(
({ index, target }) => {
setMainSelectedIndex(index);
//같은패널로 클릭이벤트가 호출될때
if (
Array.isArray(target) &&
target[0]?.name &&
panels[0]?.name &&
panels[0]?.name === target[0]?.name
) {
deActivateTab();
setMainExpanded(false);
Spotlight.focus(SpotlightIds.TPANEL);
return;
}
// 홈패널일 경우
if (target && target[0]?.name === panel_names.HOME_PANEL) {
dispatch(
updateSpotlightId({
name: panel_names.HOME_PANEL,
panelInfo: {
currentSpot: null,
currentCatCd: null,
y: null,
},
})
);
dispatch(resetPanels());
deActivateTab();
Spotlight.focus(SpotlightIds.TPANEL);
if (nowMenuRef.current === null) {
nowMenuRef.current =
panels.length > 0 ? panels[0]?.name : LOG_MENU.HOME;
}
sendLogGNB({
entryMenu: entryMenuRef.current,
inDt: inDtRef.current,
logTpNo: LOG_TP_NO.GNB,
menuMovSno: "",
nowMenu: nowMenuRef.current,
outDt: new Date(),
});
entryMenuRef.current = nowMenuRef.current;
nowMenuRef.current = target[0]?.name;
inDtRef.current = new Date();
window.localStorage.removeItem("test");
return;
}
//그 외 나머지
if (target) {
dispatch(resetPanels(target));
deActivateTab();
panelSwitching.current = true;
panelSwitchingJob.start(panelSwitching);
Spotlight.focus(SpotlightIds.TPANEL);
if (nowMenuRef.current === null) {
nowMenuRef.current =
panels.length > 0 ? panels[0]?.name : LOG_MENU.HOME;
}
sendLogGNB({
entryMenu: entryMenuRef.current,
inDt: inDtRef.current,
logTpNo: LOG_TP_NO.GNB,
menuMovSno: "",
nowMenu: nowMenuRef.current,
outDt: new Date(),
});
entryMenuRef.current = nowMenuRef.current;
nowMenuRef.current = target[0]?.name;
inDtRef.current = new Date();
window.localStorage.removeItem("test");
} else if (cursorVisible) {
setMainExpanded(true);
}
},
[cursorVisible, deActivateTab, dispatch, panels, sendLogGNB]
);
const onClickSubItem = useCallback(
({ index, target, title, path }) => {
if (selectedTitle === (title || path)) {
Spotlight.focus(SpotlightIds.TPANEL);
deActivateTab();
return;
}
if (target) {
dispatch(resetPanels(target));
deActivateTab();
panelSwitching.current = true;
panelSwitchingJob.start(panelSwitching);
if (nowMenuRef.current === null) {
nowMenuRef.current =
panels.length > 0 ? panels[0]?.name : LOG_MENU.HOME;
}
sendLogGNB({
entryMenu: entryMenuRef.current,
inDt: inDtRef.current,
logTpNo: LOG_TP_NO.GNB,
menuMovSno: "",
nowMenu: nowMenuRef.current,
outDt: new Date(),
});
entryMenuRef.current = nowMenuRef.current;
nowMenuRef.current = target[0]?.name;
inDtRef.current = new Date();
window.localStorage.removeItem("test");
}
},
[dispatch, deActivateTab, panels, selectedTitle, SpotlightIds]
);
const onClickExtraArea = useCallback(
({ index, target }) => {
deActivateTabJob.startAfter(100, deActivateTab);
},
[deActivateTab]
);
const tabActivated = useMemo(() => {
return mainExpanded || mainSelectedIndex >= 0;
}, [mainExpanded, mainSelectedIndex]);
const showTab = useMemo(() => {
if (!topPanelName || PANELS_HAS_TAB.indexOf(topPanelName) >= 0) {
return true;
}
return false;
}, [topPanelName]);
const showSubTab = useMemo(() => {
if (
tabActivated &&
tabs[mainSelectedIndex] &&
tabs[mainSelectedIndex].hasChildren()
) {
return true; // 서브 탭이 있는 경우
}
return false; // 서브 탭이 없는 경우
}, [tabActivated, mainSelectedIndex, tabs]);
const backKeyHandler = useCallback(
(ev) => {
if (tabActivated) {
Spotlight.focus("tbody");
deActivateTab();
ev?.stopPropagation();
return true;
}
},
[tabActivated, deActivateTab]
);
// 메인탭 라스트 포커스
useEffect(() => {
if (tabActivated) {
dispatch(gnbOpened(true));
if (panels.length === 0) {
Spotlight.focus("spotlight_home");
return;
}
if (lastFocusId) {
Spotlight.focus(lastFocusId);
} else {
if (!subTabLastFocusId) {
Spotlight.focus("spotlight_home");
}
}
}
if (onTabActivated) {
onTabActivated(tabActivated && showTab);
}
}, [tabActivated, showTab, dispatch, panels]);
//서브탭 라스트 포커스
useEffect(() => {
if (panelInfo?.lgCatCd) {
setSubTabLastFocusId(`secondDepth-${panelInfo.lgCatCd}`);
}
}, [panelInfo]);
useEffect(() => {
if (tabActivated && showSubTab && subTabLastFocusId !== null) {
Spotlight.focus(subTabLastFocusId);
}
}, [tabActivated, subTabLastFocusId, mainSelectedIndex]);
// 1Depth > 2Depth로 넘어갈때 서브메뉴가 없을때만 ( 의존성배열 )
useEffect(() => {
if (tabActivated && lastFocusId && !showSubTab) {
Spotlight.focus(lastFocusId);
}
}, [panels, lastFocusId]);
useEffect(() => {
setSecondDepthReduce(false);
if (showSubTab) {
tabs[mainSelectedIndex]?.children.forEach((item) => {
if (item.path) {
setSecondDepthReduce(true);
}
});
}
}, [secondDepthReduce, showSubTab, mainSelectedIndex]);
const onMainMouseEnter = useCallback(
(ev) => {
mouseMainEntered.current = true;
onTabHasFocus(COLLABSED_MAIN)(ev);
},
[onTabHasFocus]
);
const onMainMouseLeave = useCallback(
(ev) => {
mouseMainEntered.current = false;
onTabBlur(COLLABSED_MAIN)(ev);
},
[onTabBlur]
);
const onMainMouseMove = useCallback(
(ev) => {
if (!mouseMainEntered.current) {
onMainMouseEnter(ev);
}
},
[onMainMouseEnter]
);
if (!showTab) {
return null;
}
return (
<div className={classNames(css.tabLayoutWrap, !tabActivated && css.hide)}>
{data && (
<>
{
<CancelableDiv
spotlightRestrict="self-only"
spotlightId="activatedMain"
className={classNames(css.expandedRootContainer)}
handleCancel={backKeyHandler}
>
{/* expanded Main */}
<MainContainer
className={classNames(
css.mainTabContainer,
css.tabWrap,
mainExpanded && css.expanded
)}
onFocus={onTabHasFocus(ACTIVATED_MAIN)}
onBlur={onTabBlur(ACTIVATED_MAIN)}
onMouseMove={onMainMouseMove}
onMouseEnter={onMainMouseEnter}
onMouseLeave={onMainMouseLeave}
>
<h1 className={css.logo}>
<img src={shoptimeFullIcon} alt="" />
</h1>
{tabs.map((item, index) => (
<TabItem
{...item}
key={"tabitemExpanded" + index}
onFocus={onFocus}
spotlightId={item.spotlightId}
setLastFocusId={setLastFocusId}
onClick={handleNavigation}
deActivateTab={deActivateTab}
showSubTab={showSubTab}
icons={item.icons}
expanded={mainExpanded}
mainSelected={
(panels.length === 0 && index === 3) ||
(panels[0]?.name === panel_names.PLAYER_PANEL &&
panels.length === 1 &&
index === 3) ||
(Array.isArray(item.target) &&
item.target[0]?.name &&
panels[0]?.name === item.target[0]?.name)
}
selected={
Array.isArray(item.children[0]?.target) &&
item.children[0]?.target[0]?.name &&
panels[0]?.name === item.children[0]?.target?.[0].name
}
subTabTarget={item.children[index]?.target}
setSelectedTitle={setSelectedTitle}
title={item.title}
index={index}
opened={mainSelectedIndex === index}
tabActivated={tabActivated}
setSubTabLastFocusId={setSubTabLastFocusId}
/>
))}
</MainContainer>
{/* Sub */}
{
<Container
spotlightId="activatedSub"
className={classNames(
css.tabWrap,
css.secondDepthLayout,
secondDepthReduce && css.secondDepthReduce,
!showSubTab && css.hide
)}
onFocus={onTabHasFocus(ACTIVATED_SUB)}
onMouseOver={onTabHasFocus(ACTIVATED_SUB)}
onBlur={onTabBlur(ACTIVATED_SUB)}
onMouseLeave={onTabBlur(ACTIVATED_SUB)}
>
<TScroller
cbScrollTo={getScrollTo}
className={css.scrollWrap}
>
{showSubTab &&
tabs[mainSelectedIndex]?.children.map((item, index) => {
return (
<TabItemSub
{...item}
key={"tabitemSubmenu" + index}
spotlightId={item.spotlightId}
setLastFocusId={setSubTabLastFocusId}
onClick={onClickSubItem}
expanded={mainExpanded}
index={index}
isSubItem={true}
deActivateTab={deActivateTab}
title={item.title + "-sub"}
lgCatCd={item.id}
path={item.path}
showSubTab={showSubTab}
setSelectedTitle={setSelectedTitle}
selected={
Array.isArray(item.target) &&
item.target[0]?.panelInfo &&
panels[0]?.panelInfo === item.target[0]?.panelInfo
}
/>
);
})}
</TScroller>
</Container>
}
{/* Extra Area*/}
{tabActivated && (
<Container
className={classNames(css.tabWrap, css.extraArea)}
onClick={onClickExtraArea}
onFocus={onTabHasFocus(EXTRA_AREA)}
onMouseOver={onTabHasFocus(EXTRA_AREA)}
onBlur={onTabBlur(EXTRA_AREA)}
onMouseLeave={onTabBlur(EXTRA_AREA)}
/>
)}
</CancelableDiv>
}
</>
)}
</div>
);
}