From b8015528392918532162e3e5a5dc2c223198399a Mon Sep 17 00:00:00 2001 From: bcjang Date: Tue, 16 Sep 2025 16:43:58 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=9C=EC=9E=91=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20=EC=A1=B0=ED=9A=8C=20=EB=9E=AD=ED=82=B9=20=EC=A0=90?= =?UTF-8?q?=EC=88=98=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RouteInfo.js | 11 +- src/apis/Dictionary.js | 37 ++++ src/assets/data/apis/index.js | 2 + src/assets/data/apis/metaCraftingAPI.json | 11 + src/assets/data/menuConfig.js | 8 + src/assets/data/options.js | 13 ++ src/assets/data/pages/metaCraftingSearch.json | 49 +++++ src/assets/data/types.js | 1 + src/i18n.js | 1 + src/pages/DataManage/MetaCraftingView.js | 208 ++++++++++++++++++ src/pages/DataManage/RankManage.js | 102 +++++++++ src/pages/DataManage/index.js | 4 +- 12 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 src/assets/data/apis/metaCraftingAPI.json create mode 100644 src/assets/data/pages/metaCraftingSearch.json create mode 100644 src/pages/DataManage/MetaCraftingView.js create mode 100644 src/pages/DataManage/RankManage.js diff --git a/src/RouteInfo.js b/src/RouteInfo.js index 89a73dd..a4b2150 100644 --- a/src/RouteInfo.js +++ b/src/RouteInfo.js @@ -13,7 +13,15 @@ import { LogView, } from './pages/UserManage'; import { EconomicIndex, UserIndex } from './pages/IndexManage'; -import { LandInfoView, GameLogView, UserView, BusinessLogView, MetaItemView, RankManage } from './pages/DataManage'; +import { + LandInfoView, + GameLogView, + UserView, + BusinessLogView, + MetaItemView, + RankManage, + MetaCraftingView, +} from './pages/DataManage'; import { Board, RewardEvent, @@ -62,6 +70,7 @@ const RouteInfo = () => { } /> } /> } /> + } /> } /> diff --git a/src/apis/Dictionary.js b/src/apis/Dictionary.js index f4bea94..6f7d8ae 100644 --- a/src/apis/Dictionary.js +++ b/src/apis/Dictionary.js @@ -40,6 +40,43 @@ export const ItemDictionaryExport = async (token, params) => { } }; +export const getCraftingDictionaryList = async (token, searchType, searchData, smallType, recipeType, order, size, currentPage) => { + try { + const response = await Axios.get(`/api/v1/dictionary/craft/list?search_type=${searchType}&search_data=${searchData} + &small_type=${smallType}&recipe_type=${recipeType} + &orderby=${order}&page_no=${currentPage}&page_size=${size}`, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + + return response.data; + } catch (error) { + console.error('getCraftingDictionaryList API error:', error); + throw error; + } +}; + +export const CraftingDictionaryExport = async (token, params) => { + try { + await Axios.get(`/api/v1/dictionary/craft/excel-export?search_type=${params.search_type}&search_data=${params.search_data} + &small_type=${params.small_type}&recipe_type=${params.recipe_type} + &lang=${params.lang}&task_id=${params.taskId}`, { + headers: { Authorization: `Bearer ${token}` }, + responseType: 'blob' + }).then(response => { + responseFileDownload(response, { + defaultFileName: 'craftingDictionary' + }); + }); + } catch (e) { + if (e instanceof Error) { + throw new Error('CraftingDictionaryExport Error', e); + } + } +}; + export const BrandView = async (token) => { try { const res = await Axios.get( diff --git a/src/assets/data/apis/index.js b/src/assets/data/apis/index.js index 51ded41..7582650 100644 --- a/src/assets/data/apis/index.js +++ b/src/assets/data/apis/index.js @@ -3,6 +3,7 @@ import menuBannerAPI from './menuBannerAPI.json'; import historyAPI from './historyAPI.json'; import eventAPI from './eventAPI.json'; import rankingAPI from './rankingAPI.json'; +import metaCraftingAPI from './metaCraftingAPI.json'; export { itemAPI, @@ -10,4 +11,5 @@ export { historyAPI, eventAPI, rankingAPI, + metaCraftingAPI }; \ No newline at end of file diff --git a/src/assets/data/apis/metaCraftingAPI.json b/src/assets/data/apis/metaCraftingAPI.json new file mode 100644 index 0000000..1d47f8f --- /dev/null +++ b/src/assets/data/apis/metaCraftingAPI.json @@ -0,0 +1,11 @@ +{ + "baseUrl": "/api/v1/dictionary/craft", + "endpoints": { + "getCraftingDictionaryList": { + "method": "GET", + "url": "/list", + "dataPath": "data", + "paramFormat": "query" + } + } +} \ No newline at end of file diff --git a/src/assets/data/menuConfig.js b/src/assets/data/menuConfig.js index 0b97020..9d8e3fa 100644 --- a/src/assets/data/menuConfig.js +++ b/src/assets/data/menuConfig.js @@ -119,6 +119,14 @@ export const menuConfig = { view: true, authLevel: adminAuthLevel.NONE }, + craftdictionary: { + title: '제작 아이템 조회', + permissions: { + read: authType.craftingDictionaryRead + }, + view: true, + authLevel: adminAuthLevel.NONE + }, rankmanage: { title: '랭킹 점수 관리', permissions: { diff --git a/src/assets/data/options.js b/src/assets/data/options.js index c380a07..f5dd37b 100644 --- a/src/assets/data/options.js +++ b/src/assets/data/options.js @@ -406,6 +406,19 @@ export const opItemRestore = [ { value: 'IMPOSSIBLE', name: '불가능' }, ]; +export const opPropSmallType = [ + { value: 'ALL', name: '전체' }, + { value: 'FURNITURE', name: 'FURNITURE' }, + { value: 'COOKING', name: 'COOKING' }, + { value: 'CLOTHES', name: 'CLOTHES' }, +]; + +export const opPropRecipeType = [ + { value: 'ALL', name: '전체' }, + { value: 'Basic', name: '기본' }, + { value: 'Add', name: '등록 필요' }, +]; + export const opEquipType = [ { value: 0, name: '미장착' }, { value: 1, name: '의상장착' }, diff --git a/src/assets/data/pages/metaCraftingSearch.json b/src/assets/data/pages/metaCraftingSearch.json new file mode 100644 index 0000000..a378953 --- /dev/null +++ b/src/assets/data/pages/metaCraftingSearch.json @@ -0,0 +1,49 @@ +{ + "initialSearchParams": { + "search_type": "ID", + "search_data": "", + "small_type": "ALL", + "recipe_type": "ALL", + "orderBy": "DESC", + "pageSize": 50, + "currentPage": 1 + }, + + "searchFields": [ + { + "type": "select", + "id": "search_type", + "optionsRef": "itemSearchType", + "col": 1 + }, + { + "type": "text", + "id": "search_data", + "placeholder": "아이템 입력", + "width": "300px", + "col": 1 + }, + { + "type": "select", + "id": "small_type", + "label": "제작 분류", + "optionsRef": "opPropSmallType", + "col": 1 + }, + { + "type": "select", + "id": "recipe_type", + "label": "레시피 필요 여부", + "optionsRef": "opPropRecipeType", + "col": 1 + } + ], + + "apiInfo": { + "endpointName": "getCraftingDictionaryList", + "loadOnMount": true, + "pageField": "page_no", + "pageSizeField": "page_size", + "orderField": "orderBy" + } +} \ No newline at end of file diff --git a/src/assets/data/types.js b/src/assets/data/types.js index 41e99ae..e667e3b 100644 --- a/src/assets/data/types.js +++ b/src/assets/data/types.js @@ -61,6 +61,7 @@ export const authType = { worldEventRead: 59, worldEventUpdate: 60, worldEventDelete: 61, + craftingDictionaryRead: 62, levelReader: 999, diff --git a/src/i18n.js b/src/i18n.js index 0c04af8..121f5f9 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -193,6 +193,7 @@ const resources = { FILE_GAME_LOG_CURRENCY_ITEM: 'Caliverse_Game_Log_Currecy_Item', FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index', FILE_DICTIONARY_ITEM: 'Caliverse_Dictionary_Item', + FILE_DICTIONARY_CRAFTING: 'Caliverse_Dictionary_Crafting', //서버 에러메시지 DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.', NOT_USER: '유저 정보를 확인해주세요.', diff --git a/src/pages/DataManage/MetaCraftingView.js b/src/pages/DataManage/MetaCraftingView.js new file mode 100644 index 0000000..4ee0b12 --- /dev/null +++ b/src/pages/DataManage/MetaCraftingView.js @@ -0,0 +1,208 @@ +import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { AnimatedPageWrapper } from '../../components/common/Layout'; +import { + Title, + TableStyle, + FormWrapper, + TableWrapper, + DownloadContainer, CircularProgressWrapper, +} from '../../styles/Components'; + +import { withAuth } from '../../hooks/hook'; +import { + authType, +} from '../../assets/data'; +import { useTranslation } from 'react-i18next'; +import { + AnimatedTabs, + ViewTableInfo, +} from '../../components/common'; +import { TableSkeleton } from '../../components/Skeleton/TableSkeleton'; +import CircularProgress from '../../components/common/CircularProgress'; + +import { + INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE, +} from '../../assets/data/adminConstants'; +import ExcelExportButton from '../../components/common/button/ExcelExportButton'; +import Pagination from '../../components/common/Pagination/Pagination'; +import { CommonSearchBar } from '../../components/searchBar'; +import { languageNames } from '../../assets/data/types'; +import useCommonSearch from '../../hooks/useCommonSearch'; +import { CraftingDictionaryExport } from '../../apis'; + +const MetaCraftingView = () => { + const token = sessionStorage.getItem('token'); + const { t } = useTranslation(); + const tableRef = useRef(null); + + const [activeLanguage, setActiveLanguage] = useState('ko'); + + const [downloadState, setDownloadState] = useState({ + loading: false, + progress: 0 + }); + + const { + config, + searchParams, + data: dataList, + handleSearch, + handleReset, + handleOrderByChange, + updateSearchParams, + loading, + configLoaded, + handlePageChange, + handlePageSizeChange + } = useCommonSearch("metaCraftingSearch"); + + useEffect(()=>{ + setDownloadState({ + loading: false, + progress: 0 + }); + },[dataList]); + + const tableHeaders = useMemo(() => { + return [ + { id: 'id', label: '제작 분류', width: '60px' }, + { id: 'smallType', label: '프랍 그룹', width: '100px' }, + { id: 'itemId', label: '제작 아이템 ID', width: '100px' }, + { id: 'itemName', label: '제작 아이템명', width: '350px' }, + { id: 'itemValue', label: '제작 아이템 개수', width: '80px' }, + { id: 'recipeType', label: '레시피 필요 여부', width: '80px' }, + { id: 'recipeId', label: '레시피 ID', width: '80px' }, + { id: 'material', label: '제작 시 필요 재료'}, + { id: 'attribute', label: '제작 시 필요 능력치' }, + { id: 'craftingTime', label: '제작 시간', width: '80px' }, + { id: 'beaconReduceTime', label: '비컨 사용 시 감소 시간', width: '100px' }, + { id: 'beaconBonusItemId', label: '비컨 사용 시 획득 아이템ID', width: '100px' }, + { id: 'maxCraftLimit', label: '1회당 제작 최대 가능수', width: '100px' }, + ]; + }, []); + + const renderTableForLanguage = useCallback((languageKey) => { + // 해당 언어의 아이템 리스트 가져오기 + const languageItemList = dataList?.crafting_list?.[languageKey] || []; + + return ( + <> + + + + + {tableHeaders.map(header => ( + {header.label} + ))} + + + + {languageItemList?.map((item, index) => ( + + + {item.id} + {item.type_small} + {item.item_id} + {item.item_name} + {item.item_value} + {item.recipe_type} + {item.recipe_id} + {item.material} + {item.attribute} + {item.crafting_time} + {item.beacon_reduce_time} + {item.beacon_bonus_item_id} + {item.max_craft_limit} + + + ))} + + + + {languageItemList.length > 0 && + + } + + ); + }, [dataList, loading, tableHeaders, searchParams, handlePageChange]); + + // 언어별 탭 아이템 생성 + const tabItems = useMemo(() => { + // 실제 데이터에서 사용 가능한 언어만 탭으로 생성 + const availableLanguages = dataList?.crafting_list ? Object.keys(dataList.crafting_list) : ['ko', 'en', 'ja']; + + return availableLanguages.map(langKey => ({ + key: langKey, + label: languageNames[langKey.charAt(0).toUpperCase() + langKey.slice(1)] || langKey.toUpperCase(), + children: renderTableForLanguage(langKey) + })); + }, [dataList, renderTableForLanguage]); + + + const handleTabChange = (key) => { + setActiveLanguage(key); + }; + + const excelParams = useMemo(() => ({ + ...searchParams, + lang: activeLanguage + }), [searchParams, activeLanguage]); + + return ( + + 제작 아이템 조회 + + { + if (executeSearch) { + handleSearch(newParams); + } else { + updateSearchParams(newParams); + } + }} + onReset={handleReset} + /> + + + + + {downloadState.loading && ( + + + + )} + + + { + loading ? : + + } + + ); +}; + +export default withAuth(authType.craftingDictionaryRead)(MetaCraftingView); \ No newline at end of file diff --git a/src/pages/DataManage/RankManage.js b/src/pages/DataManage/RankManage.js new file mode 100644 index 0000000..75a516f --- /dev/null +++ b/src/pages/DataManage/RankManage.js @@ -0,0 +1,102 @@ +import React, { useState, useEffect, useMemo } from 'react'; + +import { Title } from '../../styles/Components'; +import { AnimatedPageWrapper } from '../../components/common/Layout' + +import styled from 'styled-components'; + +import UserDefaultInfo from '../../components/DataManage/UserDefaultInfo'; +import UserAvatarInfo from '../../components/DataManage/UserAvatarInfo'; +import UserDressInfo from '../../components/DataManage/UserDressInfo'; + +import { authType } from '../../assets/data'; +import { withAuth } from '../../hooks/hook'; +import { TabRankManageList } from '../../assets/data/options'; +import { + UserSearchBar, + useUserSearch, +} from '../../components/searchBar'; +import { AnimatedTabs } from '../../components/common'; +import { UserRankPioneerInfo } from '../../components/DataManage'; + +const RankManage = () => { + const token = sessionStorage.getItem('token'); + + const [infoView, setInfoView] = useState('none'); + const [activeTab, setActiveTab] = useState('PIONEER'); + const [resultData, setResultData] = useState(); + + const { + searchParams, + loading: dataLoading, + data, + handleSearch, + handleReset, + updateSearchParams + } = useUserSearch(token); + + useEffect(() => { + if(data) { + setResultData(data); + setInfoView('flex'); + } else { + setInfoView('none'); + } + }, [data]) + + const tabItems = useMemo(() => { + return TabRankManageList.map(el => ({ + key: el.value, + label: el.name, + children: (() => { + switch(el.value) { + case 'PIONEER': return ; + case 'RUN_RACE': return ; + case 'BATTLE_OBJECT': return ; + default: return null; + } + })() + })); + }, [resultData]); + + const handleTabChange = (key) => { + setActiveTab(key); + }; + + return ( + + 랭킹 점수 관리 + { + if (executeSearch) { + handleSearch(newParams); + } else { + updateSearchParams(newParams); + setResultData(); + } + }} + onReset={handleReset} + /> + + + + + ); +}; + +export default withAuth(authType.rankManagerRead)(RankManage); + +const UserWrapper = styled.div` + display: ${props => props.display}; + ${props => props.display && `flex-flow:column;`} + min-height: calc(100vh - 345px); + overflow: hidden; + padding-left: 40px; + padding-right: 40px; +`; diff --git a/src/pages/DataManage/index.js b/src/pages/DataManage/index.js index 74b2271..772ad81 100644 --- a/src/pages/DataManage/index.js +++ b/src/pages/DataManage/index.js @@ -1,5 +1,7 @@ export { default as UserView } from './UserView'; export { default as LandInfoView } from './LandInfoView'; export { default as GameLogView } from './GameLogView'; -export { default as CryptView } from './CryptView'; export { default as BusinessLogView} from './BusinessLogView'; +export { default as MetaItemView} from './MetaItemView'; +export { default as MetaCraftingView} from './MetaCraftingView'; +export { default as RankManage} from './RankManage';