인스턴스 조회 추가
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,3 +32,5 @@ yarn-error.log*
|
||||
/.idea/misc.xml
|
||||
/.idea/modules.xml
|
||||
/.idea/vcs.xml
|
||||
/.idea/git_toolbox_blame.xml
|
||||
/.idea/git_toolbox_prj.xml
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
MetaItemView,
|
||||
RankManage,
|
||||
MetaCraftingView,
|
||||
RankInfoView,
|
||||
MetaInstanceView
|
||||
} from './pages/DataManage';
|
||||
import {
|
||||
Board,
|
||||
@@ -72,6 +74,8 @@ const RouteInfo = () => {
|
||||
<Route path="itemdictionary" element={<MetaItemView />} />
|
||||
<Route path="craftdictionary" element={<MetaCraftingView />} />
|
||||
<Route path="rankmanage" element={<RankManage />} />
|
||||
<Route path="rankview" element={<RankInfoView />} />
|
||||
<Route path="instancedictionary" element={<MetaInstanceView />} />
|
||||
</Route>
|
||||
<Route path="/servicemanage">
|
||||
<Route path="board" element={<Board />} />
|
||||
|
||||
@@ -77,6 +77,43 @@ export const CraftingDictionaryExport = async (token, params) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getInstanceDictionaryList = async (token, searchType, searchData, contentsType, accessType, order, size, currentPage) => {
|
||||
try {
|
||||
const response = await Axios.get(`/api/v1/dictionary/instance/list?search_type=${searchType}&search_data=${searchData}
|
||||
&contents_type=${contentsType}&access_type=${accessType}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('getInstanceDictionaryList API error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const InstanceDictionaryExport = async (token, params) => {
|
||||
try {
|
||||
await Axios.get(`/api/v1/dictionary/instance/excel-export?search_type=${params.search_type}&search_data=${params.search_data}
|
||||
&contents_type=${params.contents_type}&access_type=${params.access_type}
|
||||
&lang=${params.lang}&task_id=${params.taskId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob'
|
||||
}).then(response => {
|
||||
responseFileDownload(response, {
|
||||
defaultFileName: 'instanceDictionary'
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('InstanceDictionaryExport Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const BrandView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//AI api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
|
||||
export const AnalyzeAI = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post('/api/v1/ai/analyze', params, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('analyzeAI Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import historyAPI from './historyAPI.json';
|
||||
import eventAPI from './eventAPI.json';
|
||||
import rankingAPI from './rankingAPI.json';
|
||||
import metaCraftingAPI from './metaCraftingAPI.json';
|
||||
import metaInstanceAPI from './metaInstanceAPI.json';
|
||||
|
||||
export {
|
||||
itemAPI,
|
||||
@@ -11,5 +12,6 @@ export {
|
||||
historyAPI,
|
||||
eventAPI,
|
||||
rankingAPI,
|
||||
metaCraftingAPI
|
||||
metaCraftingAPI,
|
||||
metaInstanceAPI
|
||||
};
|
||||
11
src/assets/data/apis/metaInstanceAPI.json
Normal file
11
src/assets/data/apis/metaInstanceAPI.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/dictionary/instance",
|
||||
"endpoints": {
|
||||
"getInstanceDictionaryList": {
|
||||
"method": "GET",
|
||||
"url": "/list",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "query"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export const menuConfig = {
|
||||
permissions: {
|
||||
read: authType.gameLogRead
|
||||
},
|
||||
view: false,
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
businesslogview: {
|
||||
@@ -127,6 +127,14 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
instancedictionary: {
|
||||
title: '인스턴스 조회',
|
||||
permissions: {
|
||||
read: authType.instanceDictionaryRead
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
rankmanage: {
|
||||
title: '랭킹 점수 관리',
|
||||
permissions: {
|
||||
|
||||
49
src/assets/data/pages/metaInstanceSearch.json
Normal file
49
src/assets/data/pages/metaInstanceSearch.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"initialSearchParams": {
|
||||
"search_type": "ID",
|
||||
"search_data": "",
|
||||
"contents_type": "ALL",
|
||||
"access_type": "ALL",
|
||||
"orderBy": "DESC",
|
||||
"pageSize": 50,
|
||||
"currentPage": 1
|
||||
},
|
||||
|
||||
"searchFields": [
|
||||
{
|
||||
"type": "select",
|
||||
"id": "search_type",
|
||||
"optionsRef": "instanceSearchType",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "search_data",
|
||||
"placeholder": "인스턴스 입력",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "contents_type",
|
||||
"label": "컨텐츠 타입",
|
||||
"optionsRef": "opInstanceContentsType",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "access_type",
|
||||
"label": "입장 방식",
|
||||
"optionsRef": "opInstanceAccessType",
|
||||
"col": 1
|
||||
}
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"endpointName": "getInstanceDictionaryList",
|
||||
"loadOnMount": true,
|
||||
"pageField": "page_no",
|
||||
"pageSizeField": "page_size",
|
||||
"orderField": "orderBy"
|
||||
}
|
||||
}
|
||||
16
src/i18n.js
16
src/i18n.js
@@ -35,6 +35,8 @@ const resources = {
|
||||
SAVE_COMPLETED: '저장이 완료되었습니다.',
|
||||
SAVE_CONFIRM: '저장 하시겠습니까?',
|
||||
UPDATE_CONFIRM: '수정하시겠습니까?',
|
||||
UPDATE_VALUE: '변경할 값을 입력해주세요.',
|
||||
UPDATE_VALUE_COMMENT: '변경할 값을 입력해주세요.\n({{comment}})',
|
||||
LENGTH_TEXT_LIMIT_100: '요청사유는 100글자 까지만 입력하실 수 있습니다.({{count}}/100)',
|
||||
LENGTH_NUMBER_POINT_2: '숫자, 소수점 둘째자리',
|
||||
EXCEL_SELECT: 'Excel 파일을 선택해주세요.',
|
||||
@@ -55,8 +57,12 @@ const resources = {
|
||||
DOWNLOAD_COMPLETE: '다운이 완료되었습니다.',
|
||||
DOWNLOAD_FAIL: '다운이 실패하였습니다.',
|
||||
DELETE_STATUS_ONLY_WAIT: '대기상태의 데이터만 삭제가 가능합니다.',
|
||||
UPDATE_STATUS_ONLY_RUNNING: '진행상태의 데이터만 새로고침이 가능합니다.',
|
||||
TABLE_DATA_NOT_FOUND: '데이터가 없습니다.',
|
||||
ITEM_ID_EMPTY_WARNING: '아이템 아이디를 입력해주세요.',
|
||||
//login
|
||||
PASSWORD_INIT_COMPLETE: '비밀번호 초기화 메일이 발송되었습니다.\r\n메일을 확인해주세요.',
|
||||
PASSWORD_INIT_ERROR: '비밀번호 초기화에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.\n오류가 지속될 경우, 담당자에게 문의해주세요.',
|
||||
//user
|
||||
NICKNAME_CHANGES_CONFIRM: '닉네임을 변경하시겠습니까?',
|
||||
NICKNAME_CHANGES_COMPLETE: '닉네임 변경이 완료되었습니다.',
|
||||
@@ -148,6 +154,15 @@ const resources = {
|
||||
SCHEDULE_SELECT_DELETE: "선택된 스케줄을 삭제하시겠습니까?",
|
||||
SCHEDULE_REGIST_CONFIRM: "스케줄을 등록하시겠습니까?",
|
||||
SCHEDULE_UPDATE_CONFIRM: "스케줄을 수정하시겠습니까?",
|
||||
SCHEDULE_MODAL_START_DIFF_BASE_WARNING: "기준시간은 시작 시간보다 작을 수 없습니다.",
|
||||
SCHEDULE_MODAL_START_DIFF_END_WARNING: "종료시간은 시작 시간보다 작을 수 없습니다.",
|
||||
SCHEDULE_REFRESH_GUID_NULL_WARNING: "재조회 후 다시 시도해주세요.",
|
||||
SCHEDULE_SELECT_UPDATE: "선택된 스케줄의 랭킹을 강제 새로고침 하시겠습니까?",
|
||||
SCHEDULE_SELECT_INIT: "선택된 스케줄의 랭킹을 강제 초기화 하시겠습니까?",
|
||||
SCHEDULE_SELECT_SNAPSHOT: "선택된 스케줄의 랭킹을 강제 스냅샷 하시겠습니까?",
|
||||
SCHEDULE_REFRESH_COMPLETE: '랭킹 새로고침을 서버에 요청하였습니다.\n변경사항은 잠시 후 확인해주세요.',
|
||||
SCHEDULE_INIT_COMPLETE: '랭킹 초기화를 서버에 요청하였습니다.\n변경사항은 잠시 후 확인해주세요.',
|
||||
SCHEDULE_SNAPSHOT_COMPLETE: '랭킹 스냅샷을 서버에 요청하였습니다.\n변경사항은 잠시 후 확인해주세요.',
|
||||
//메뉴 배너
|
||||
MENU_BANNER_TITLE: "메뉴 배너 관리",
|
||||
MENU_BANNER_CREATE: "메뉴 배너 등록",
|
||||
@@ -185,6 +200,7 @@ const resources = {
|
||||
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
|
||||
FILE_BUSINESS_LOG: 'Caliverse_Log',
|
||||
FILE_BATTLE_EVENT: 'Caliverse_Battle_Event.xlsx',
|
||||
FILE_RANKING_SNAPSHOT: 'Caliverse_Ranking_Snapshot.xlsx',
|
||||
FILE_GAME_LOG_CURRENCY: 'Caliverse_Game_Log_Currency',
|
||||
FILE_GAME_LOG_USER_CREATE: 'Caliverse_Game_Log_User_Create',
|
||||
FILE_GAME_LOG_USER_LOGIN: 'Caliverse_Game_Log_User_Login',
|
||||
|
||||
207
src/pages/DataManage/MetaInstanceView.js
Normal file
207
src/pages/DataManage/MetaInstanceView.js
Normal file
@@ -0,0 +1,207 @@
|
||||
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,
|
||||
} 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';
|
||||
|
||||
const MetaInstanceView = () => {
|
||||
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("metaInstanceSearch");
|
||||
|
||||
useEffect(()=>{
|
||||
setDownloadState({
|
||||
loading: false,
|
||||
progress: 0
|
||||
});
|
||||
},[dataList]);
|
||||
|
||||
const tableHeaders = useMemo(() => {
|
||||
return [
|
||||
{ id: 'instance_id', label: '인스턴스 ID', width: '100px' },
|
||||
{ id: 'instance_name', label: '인스턴스 명', width: '200px' },
|
||||
{ id: 'owner', label: '소유권', width: '200px' },
|
||||
{ id: 'building_id', label: '빌딩 ID', width: '100px' },
|
||||
{ id: 'building_socket', label: '빌딩 소켓 넘버', width: '80px' },
|
||||
{ id: 'contents_type', label: '컨텐츠 타입', width: '150px' },
|
||||
{ id: 'map_id', label: '맵 ID', width: '80px' },
|
||||
{ id: 'limit_count', label: '제한 인원 수', width: '100px'},
|
||||
{ id: 'over_limit', label: '정원 초과 시 추가 생성 여부', width: '90px' },
|
||||
{ id: 'access_type', label: '입장 방식', width: '80px' },
|
||||
{ id: 'access_id', label: '입장시 필요 아이템', width: '100px' },
|
||||
{ id: 'voice_chat', label: '음성채팅 옵션', width: '100px' },
|
||||
{ id: 'view_type', label: '시야 타입', width: '100px' },
|
||||
];
|
||||
}, []);
|
||||
|
||||
const renderTableForLanguage = useCallback((languageKey) => {
|
||||
// 해당 언어의 아이템 리스트 가져오기
|
||||
const languageInstanceList = dataList?.instance_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>
|
||||
{languageInstanceList?.map((item, index) => (
|
||||
<Fragment key={`${languageKey}-${index}`}>
|
||||
<tr>
|
||||
<td>{item.instance_id}</td>
|
||||
<td>{item.instance_name || '-'}</td>
|
||||
<td>{item.owner || '-'}</td>
|
||||
<td>{item.building_id || '-'}</td>
|
||||
<td>{item.building_socket}</td>
|
||||
<td>{item.contents_type}</td>
|
||||
<td>{item.map_id}</td>
|
||||
<td>{item.limit_count}</td>
|
||||
<td>{item.over_limit}</td>
|
||||
<td>{item.access_type}</td>
|
||||
<td>{item.access_id}</td>
|
||||
<td>{item.voice_chat}</td>
|
||||
<td>{item.view_type}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
{languageInstanceList.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?.instance_list ? Object.keys(dataList.instance_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="InstanceDictionaryExport"
|
||||
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.instanceDictionaryRead)(MetaInstanceView);
|
||||
@@ -6,3 +6,4 @@ export { default as MetaItemView} from './MetaItemView';
|
||||
export { default as MetaCraftingView} from './MetaCraftingView';
|
||||
export { default as RankManage} from './RankManage';
|
||||
export { default as RankInfoView} from './RankInfoView';
|
||||
export { default as MetaInstanceView} from './MetaInstanceView';
|
||||
|
||||
@@ -180,7 +180,7 @@ export const responseFileDownload = (response, options = {}) => {
|
||||
const contentType = response.headers['content-type'] || response.headers['Content-Type'];
|
||||
const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition'];
|
||||
|
||||
// Excel, CSV, ZIP 파일 형식 검증 (CSV 추가)
|
||||
// Excel, CSV, ZIP 파일 형식 검증
|
||||
const isValidType = contentType && (
|
||||
contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') ||
|
||||
contentType.includes('text/csv') ||
|
||||
@@ -263,4 +263,39 @@ export const calculateTotals = (data) => {
|
||||
});
|
||||
return acc;
|
||||
}, {}) || {};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 밀리초를 시:분:초 형식(HH:MM:SS)으로 변환
|
||||
* @param {number} milliseconds - 변환할 밀리초
|
||||
* @returns {string} HH:MM:SS 형식의 시간 문자열
|
||||
*/
|
||||
export const formatTimeFromMilliseconds = (milliseconds) => {
|
||||
if (milliseconds === null || milliseconds === undefined || isNaN(milliseconds)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const totalSeconds = Math.floor(milliseconds / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 초를 시:분:초 형식(HH:MM:SS)으로 변환
|
||||
* @param {number} totalSeconds - 변환할 초
|
||||
* @returns {string} HH:MM:SS 형식의 시간 문자열
|
||||
*/
|
||||
export const formatTimeFromSeconds = (totalSeconds) => {
|
||||
if (totalSeconds === null || totalSeconds === undefined || isNaN(totalSeconds)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user