diff --git a/src/assets/data/adminConstants.js b/src/assets/data/adminConstants.js index aaea92f..90658f4 100644 --- a/src/assets/data/adminConstants.js +++ b/src/assets/data/adminConstants.js @@ -12,5 +12,6 @@ export const STORAGE_MAIL_COPY = 'copyMailData'; export const STORAGE_BUSINESS_LOG_SEARCH = 'businessLogSearchParam'; export const STORAGE_GAME_LOG_CURRENCY_SEARCH = 'gameLogCurrencySearchParam'; export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem'; +export const BATTLE_EVENT_OPERATION_TIME_WAIT_SECONDS = 300; export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT }; diff --git a/src/assets/data/menuConfig.js b/src/assets/data/menuConfig.js index ccd1bba..0459dd4 100644 --- a/src/assets/data/menuConfig.js +++ b/src/assets/data/menuConfig.js @@ -103,14 +103,14 @@ export const menuConfig = { view: false, authLevel: adminAuthLevel.NONE }, - cryptview: { - title: '크립토 조회', - permissions: { - read: authType.cryptoRead - }, - view: false, - authLevel: adminAuthLevel.NONE - }, + // cryptview: { + // title: '크립토 조회', + // permissions: { + // read: authType.cryptoRead + // }, + // view: false, + // authLevel: adminAuthLevel.NONE + // }, businesslogview: { title: '비즈니스 로그 조회', permissions: { diff --git a/src/components/common/Header/Navi.js b/src/components/common/Header/Navi.js index 2db1244..6dd0d9b 100644 --- a/src/components/common/Header/Navi.js +++ b/src/components/common/Header/Navi.js @@ -1,5 +1,5 @@ -import { NavLink, useNavigate } from 'react-router-dom'; -import arrowIcon from '../../../assets/img/icon/icon-tab.png'; +import { NavLink, useNavigate, useLocation } from 'react-router-dom'; +import { ConfigProvider, Menu, theme } from 'antd'; import styled from 'styled-components'; import { useRecoilValue } from 'recoil'; import { authList } from '../../../store/authList'; @@ -7,7 +7,6 @@ import Modal from '../modal/Modal'; import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components'; import { useEffect, useState } from 'react'; import Button from '../button/Button'; -import { useLocation } from 'react-router-dom'; import { AuthInfo } from '../../../apis'; import { getMenuConfig } from '../../../utils'; import { adminAuthLevel } from '../../../assets/data/types'; @@ -15,47 +14,55 @@ import { adminAuthLevel } from '../../../assets/data/types'; const Navi = () => { const token = sessionStorage.getItem('token'); const userInfo = useRecoilValue(authList); - const menu = getMenuConfig(userInfo); - - const [modalClose, setModalClose] = useState('hidden'); - const [logoutModalClose, setLogoutModalClose] = useState('hidden'); + const menuConfig = getMenuConfig(userInfo); const location = useLocation(); const navigate = useNavigate(); + + const [modalClose, setModalClose] = useState('hidden'); + const [logoutModalClose, setLogoutModalClose] = useState('hidden'); + const [openKeys, setOpenKeys] = useState([]); + const [selectedKeys, setSelectedKeys] = useState([]); + + // 현재 경로에 따라 선택된 메뉴와 열린 서브메뉴 설정 + useEffect(() => { + const path = location.pathname.split('/'); + if (path.length > 1) { + // 첫 번째 경로(예: /usermanage)를 기반으로 openKeys 설정 + setOpenKeys([path[1]]); + + // 전체 경로(예: /usermanage/adminview)를 기반으로 selectedKeys 설정 + if (path.length > 2) { + setSelectedKeys([`${path[1]}/${path[2]}`]); + } else { + setSelectedKeys([path[1]]); + } + } + }, [location.pathname]); const handleToken = async () => { const tokenStatus = await AuthInfo(token); - - tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view'); + if (tokenStatus.message === '잘못된 타입의 토큰입니다.') { + setLogoutModalClose('view'); + } }; useEffect(() => { handleToken(); }, [token]); - const handleTopMenu = e => { - e.preventDefault(); - e.target.classList.toggle('active'); + // 메뉴 아이템 클릭 핸들러 + const handleMenuClick = ({ key }) => { + handleToken(); }; - const handleLink = e => { - let topActive = document.querySelectorAll('nav .active'); - let currentTopMenu = e.target.closest('ul').previousSibling; - for (let i = 0; i < topActive.length; i++) { - if (topActive[i] !== currentTopMenu) { - topActive[i].classList.remove('active'); - } - } - - handleToken(); + // 서브메뉴 열기/닫기 핸들러 + const handleOpenChange = (keys) => { + setOpenKeys(keys); }; // 등록 완료 모달 const handleModalClose = () => { - if (modalClose === 'hidden') { - setModalClose('view'); - } else { - setModalClose('hidden'); - } + setModalClose(modalClose === 'hidden' ? 'view' : 'hidden'); }; // 로그아웃 안내 모달 @@ -65,7 +72,6 @@ const Navi = () => { } else { setLogoutModalClose('hidden'); sessionStorage.removeItem('token'); - navigate('/'); } }; @@ -79,41 +85,56 @@ const Navi = () => { default: return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id); } - } + }; + + const getMenuItems = () => { + return menuConfig + .filter(item => item.access) + .map(item => ({ + key: item.link.substring(1), + label: item.title, + children: item.submenu.map(submenu => ({ + key: `${item.link.substring(1)}/${submenu.link.split('/').pop()}`, + label: ( + { + if (!isClickable(submenu)) { + e.preventDefault(); + handleModalClose(); + } + }} + > + {submenu.title} + + ), + disabled: !isClickable(submenu) + })) + })); + }; return ( <> - + + + + + + {/* 접근 불가 모달 */} @@ -145,61 +166,15 @@ const Navi = () => { export default Navi; -const TopMenu = styled(NavLink)` - padding: 16px 30px; - width: 100%; - text-align: left; - border-bottom: 1px solid #888; - position: relative; - color: #fff; +const StyledNavWrapper = styled.div` +`; - &:before { - content: ''; - display: block; - width: 12px; - height: 12px; - position: absolute; - right: 30px; - top: 50%; - transform: translate(0, -50%); - background: url('${arrowIcon}') -12px 0 no-repeat; - } - &:hover, +const StyledMenu = styled(Menu)` +`; + +const MenuItemLink = styled(NavLink)` + &.active { - background: #444; + font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)}; } - &.active ~ ul { - display: block; - } - &.active:before { - background: url('${arrowIcon}') 0 0 no-repeat; - } -`; - -const SubMenu = styled.ul` - display: none; -`; - -const SubMenuItem = styled.li` - background: #eee; - border-bottom: 1px solid #ccc; - color: #2c2c2c; - a { - width: 100%; - padding: 16px 30px; - color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')}; - text-align: left; - &:hover, - &.active { - color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')}; - font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)}; - } - } -`; - -const BackGround = styled.div` - background: #eee2; - width: 100%; - height: 100%; - z-index: 100; -`; +`; \ No newline at end of file diff --git a/src/components/common/Header/Profile.js b/src/components/common/Header/Profile.js index e78f28e..2afd2aa 100644 --- a/src/components/common/Header/Profile.js +++ b/src/components/common/Header/Profile.js @@ -1,20 +1,24 @@ import { useState, useEffect } from 'react'; -import UserIcon from '../../../assets/img/icon/icon-profile.png'; +import { Layout, Avatar, Button as AntButton, Typography, Tooltip, Breadcrumb } from 'antd'; +import { UserOutlined, LogoutOutlined, HomeOutlined } from '@ant-design/icons' import styled from 'styled-components'; -import Modal from '../modal/Modal'; -import CloseIcon from '../../../assets/img/icon/icon-close.png'; -import Button from '../../common/button/Button'; import { useRecoilState } from 'recoil'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import { AuthLogout, AuthInfo } from '../../../apis'; -import { BtnWrapper, ModalText } from '../../../styles/Components'; import { authList } from '../../../store/authList'; +import { alertTypes } from '../../../assets/data/types'; +import { useAlert } from '../../../context/AlertProvider'; +import { menuConfig } from '../../../assets/data/menuConfig'; + +const { Header } = Layout; +const { Text } = Typography; const Profile = () => { + const location = useLocation(); + const { showModal } = useAlert(); const [infoData, setInfoData] = useRecoilState(authList); - const [errorModal, setErrorModal] = useState('hidden'); const navigate = useNavigate(); @@ -40,91 +44,124 @@ const Profile = () => { // 필수값 입력 모달창 const handleErrorModal = () => { - if (errorModal === 'hidden') { - setErrorModal('view'); - } else { - setErrorModal('hidden'); - } + showModal('USER_LOGOUT_CONFIRM', { + type: alertTypes.confirm, + onConfirm: () => handleLogout() + }); }; + // 카테고리별 첫 번째 아이템 링크 찾기 + const getFirstItemLink = (categoryKey) => { + const category = menuConfig[categoryKey]; + if (!category || !category.items) return `/${categoryKey}`; + + // 첫 번째 visible 아이템 찾기 + const firstVisibleItem = Object.entries(category.items) + .find(([_, item]) => item.view !== false); + + if (!firstVisibleItem) return `/${categoryKey}`; + return `/${categoryKey}/${firstVisibleItem[0]}`; + }; + + const pathSnippets = location.pathname.split('/').filter(i => i); + + const breadcrumbItems = [ + { + title: , + } + ]; + + if (pathSnippets.length > 0) { + // 첫 번째 경로 (메인 카테고리) + const mainCategory = pathSnippets[0]; + if (menuConfig[mainCategory]) { + const firstItemLink = getFirstItemLink(mainCategory); + + breadcrumbItems.push({ + title: {menuConfig[mainCategory].title} + }); + + // 두 번째 경로 (서브 카테고리) + if (pathSnippets.length > 1) { + const subCategory = pathSnippets[1]; + if (menuConfig[mainCategory].items[subCategory]) { + breadcrumbItems.push({ + title: menuConfig[mainCategory].items[subCategory].title + }); + } + } + } + } + return ( <> - - {infoData.name && {infoData.name.length > 20 ? infoData.name.slice(0, 20) + '...' : infoData.name}님} - - 로그아웃 - - - {/* 로그아웃 확인 모달 */} - - - - - - 로그아웃 하시겠습니까? -
- (로그아웃 시 저장되지 않은 값은 초기화 됩니다.) -
- -