제작 아이템 조회

랭킹 점수 관리
This commit is contained in:
2025-09-16 16:43:58 +09:00
parent 3169055646
commit b801552839
12 changed files with 445 additions and 2 deletions

View File

@@ -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 = () => {
<Route path="gamelogview" element={<GameLogView />} />
<Route path="businesslogview" element={<BusinessLogView />} />
<Route path="itemdictionary" element={<MetaItemView />} />
<Route path="craftdictionary" element={<MetaCraftingView />} />
<Route path="rankmanage" element={<RankManage />} />
</Route>
<Route path="/servicemanage">

View File

@@ -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(

View File

@@ -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
};

View File

@@ -0,0 +1,11 @@
{
"baseUrl": "/api/v1/dictionary/craft",
"endpoints": {
"getCraftingDictionaryList": {
"method": "GET",
"url": "/list",
"dataPath": "data",
"paramFormat": "query"
}
}
}

View File

@@ -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: {

View File

@@ -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: '의상장착' },

View File

@@ -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"
}
}

View File

@@ -61,6 +61,7 @@ export const authType = {
worldEventRead: 59,
worldEventUpdate: 60,
worldEventDelete: 61,
craftingDictionaryRead: 62,
levelReader: 999,

View File

@@ -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: '유저 정보를 확인해주세요.',

View File

@@ -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 (
<>
<TableWrapper>
<TableStyle ref={tableRef}>
<thead>
<tr>
{tableHeaders.map(header => (
<th key={header.id} width={header.width}>{header.label}</th>
))}
</tr>
</thead>
<tbody>
{languageItemList?.map((item, index) => (
<Fragment key={`${languageKey}-${index}`}>
<tr>
<td>{item.id}</td>
<td>{item.type_small}</td>
<td>{item.item_id}</td>
<td>{item.item_name}</td>
<td>{item.item_value}</td>
<td>{item.recipe_type}</td>
<td>{item.recipe_id}</td>
<td>{item.material}</td>
<td>{item.attribute}</td>
<td>{item.crafting_time}</td>
<td>{item.beacon_reduce_time}</td>
<td>{item.beacon_bonus_item_id}</td>
<td>{item.max_craft_limit}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
{languageItemList.length > 0 &&
<Pagination
postsPerPage={searchParams.pageSize}
totalPosts={dataList?.total_all}
setCurrentPage={handlePageChange}
currentPage={searchParams?.currentPage}
pageLimit={INITIAL_PAGE_LIMIT}
/>
}
</>
);
}, [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 (
<AnimatedPageWrapper>
<Title>제작 아이템 조회</Title>
<FormWrapper>
<CommonSearchBar
config={config}
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<ViewTableInfo orderType="asc" total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
<DownloadContainer>
<ExcelExportButton
functionName="CraftingDictionaryExport"
params={excelParams}
fileName={t('FILE_DICTIONARY_CRAFTING')}
onLoadingChange={setDownloadState}
dataSize={dataList?.total_all}
/>
{downloadState.loading && (
<CircularProgressWrapper>
<CircularProgress
progress={downloadState.progress}
size={36}
progressColor="#4A90E2"
/>
</CircularProgressWrapper>
)}
</DownloadContainer>
</ViewTableInfo>
{
loading ? <TableSkeleton width='100%' count={40} /> :
<AnimatedTabs
items={tabItems}
activeKey={activeLanguage}
onChange={handleTabChange}
tabPosition="left"
/>
}
</AnimatedPageWrapper>
);
};
export default withAuth(authType.craftingDictionaryRead)(MetaCraftingView);

View File

@@ -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 <UserRankPioneerInfo userInfo={resultData} />;
case 'RUN_RACE': return <UserAvatarInfo userInfo={resultData} />;
case 'BATTLE_OBJECT': return <UserDressInfo userInfo={resultData} />;
default: return null;
}
})()
}));
}, [resultData]);
const handleTabChange = (key) => {
setActiveTab(key);
};
return (
<AnimatedPageWrapper>
<Title>랭킹 점수 관리</Title>
<UserSearchBar
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
setResultData();
}
}}
onReset={handleReset}
/>
<UserWrapper display={infoView}>
<AnimatedTabs
items={tabItems}
activeKey={activeTab}
onChange={handleTabChange}
tabPosition="center"
/>
</UserWrapper>
</AnimatedPageWrapper>
);
};
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;
`;

View File

@@ -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';