Compare commits
4 Commits
b801552839
...
760153c700
| Author | SHA1 | Date | |
|---|---|---|---|
| 760153c700 | |||
| 8dae810e3a | |||
| ac9bcdda8b | |||
| 3264e94093 |
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 />} />
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { Layout, LoginLayout, MainLayout } from './components/common/Layout';
|
||||
import LoginBg from './assets/img/login-bg.png';
|
||||
import { Login } from './pages/Login';
|
||||
import LoginFail from './pages/LoginFail';
|
||||
import { AccountEdit, AccountRegist, PasswordReset } from './pages/Account';
|
||||
import Main from './pages/Main';
|
||||
import {
|
||||
AdminView,
|
||||
AuthSetting,
|
||||
AuthSettingUpdate,
|
||||
CaliumRequest,
|
||||
CaliumRequestRegist,
|
||||
LogView,
|
||||
} from './pages/UserManage';
|
||||
import { EconomicIndex, UserIndex } from './pages/IndexManage';
|
||||
import { ContentsView, CryptView, GameLogView, UserView } from './pages/DataManage';
|
||||
import {
|
||||
Board,
|
||||
Event,
|
||||
EventRegist,
|
||||
Items,
|
||||
Mail,
|
||||
MailRegist,
|
||||
ReportList,
|
||||
UserBlock,
|
||||
UserBlockRegist,
|
||||
WhiteList,
|
||||
} from './pages/ServiceManage';
|
||||
|
||||
const RouteInfo = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<LoginLayout $bgimg={LoginBg} $padding="50px" />}>
|
||||
<Route path="/" element={<Login />} />
|
||||
<Route path="/fail" element={<LoginFail />} />
|
||||
<Route path="/account/regist" element={<AccountRegist />} />
|
||||
<Route path="/account/pwdreset" element={<PasswordReset />} />
|
||||
<Route path="/account/edit" element={<AccountEdit />} />
|
||||
</Route>
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="/main" element={<Main />} />
|
||||
</Route>
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/usermanage/">
|
||||
<Route path="adminview" element={<AdminView />} />
|
||||
<Route path="logview" element={<LogView />} />
|
||||
<Route path="authsetting" element={<AuthSetting />} />
|
||||
<Route path="authsetting/:id" element={<AuthSettingUpdate />} />
|
||||
<Route path="caliumrequest" element={<CaliumRequest />} />
|
||||
</Route>
|
||||
<Route path="/indexmanage">
|
||||
<Route path="userindex" element={<UserIndex />} />
|
||||
<Route path="economicindex" element={<EconomicIndex />} />
|
||||
</Route>
|
||||
<Route path="/datamanage">
|
||||
<Route path="userview" element={<UserView />} />
|
||||
<Route path="contentsview" element={<ContentsView />} />
|
||||
<Route path="gamelogview" element={<GameLogView />} />
|
||||
<Route path="cryptview" element={<CryptView />} />
|
||||
</Route>
|
||||
<Route path="/servicemanage">
|
||||
<Route path="board" element={<Board />} />
|
||||
<Route path="whitelist" element={<WhiteList />} />
|
||||
<Route path="mail" element={<Mail />} />
|
||||
<Route path="mail/mailregist" element={<MailRegist />} />
|
||||
<Route path="userblock" element={<UserBlock />} />
|
||||
<Route path="userblock/userblockregist" element={<UserBlockRegist />} />
|
||||
<Route path="reportlist" element={<ReportList />} />
|
||||
<Route path="items" element={<Items />} />
|
||||
<Route path="event" element={<Event />} />
|
||||
<Route path="event/eventregist" element={<EventRegist />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default RouteInfo;
|
||||
@@ -85,10 +85,10 @@ export const AdminDeleteUser = async (token, params) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const AdminChangePw = async (token, params) => {
|
||||
export const AdminChangePw = async ( params) => {
|
||||
try {
|
||||
const res = await Axios.post('/api/v1/admin/init-password', params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
headers: { },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
118
src/apis/Rank.js
118
src/apis/Rank.js
@@ -21,6 +21,40 @@ export const RankingScheduleView = async (token, title, content, status, startDa
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingScheduleSimpleView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/rank/schedule/simple-list`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data.list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleSimpleView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingSnapshotView = async (token, guid) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/rank/snapshot/list?guid=${guid}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data.ranking_snapshot_list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingSnapshotView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 전투시스템 상세보기
|
||||
export const RankingScheduleDetailView = async (token, id) => {
|
||||
try {
|
||||
@@ -36,6 +70,20 @@ export const RankingScheduleDetailView = async (token, id) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const RankerListView = async (token, guid, snapshot) => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/rank/ranker/list?guid=${guid}&snapshot=${snapshot}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankerListView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 랭킹스케줄 등록
|
||||
export const RankingScheduleSingleRegist = async (token, params) => {
|
||||
try {
|
||||
@@ -96,4 +144,74 @@ export const RankingDataView = async (token) => {
|
||||
throw new Error('RankingDataView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingInfoView = async (token, guid) => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/rank/info?guid=${guid}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingInfoView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankerInfoModify = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/rank/info`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankerInfoModify Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingUpdate = async (token, guid) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/rank/ranking/${guid}`, {},{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingUpdate Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingInit = async (token, guid) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/rank/ranking/init/${guid}`, {},{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingInit Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingSnapshot = async (token, guid) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/rank/ranking/snapshot/${guid}`, {},{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingSnapshot 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,15 +153,27 @@ export const FieldLabels = {
|
||||
'create_by': '생성자',
|
||||
'status': '상태',
|
||||
'deleted': '삭제 여부',
|
||||
'guid': 'GUID',
|
||||
'meta_id': '메타 ID',
|
||||
'start_dt': '시작일',
|
||||
'end_dt': '종료일',
|
||||
'base_dt': '기준일',
|
||||
'title': '제목',
|
||||
|
||||
// 이벤트 필드 관련
|
||||
'eventId': '이벤트 ID',
|
||||
'eventName': '이벤트 명',
|
||||
'event_name': '이벤트 명',
|
||||
'repeatType': '반복 타입',
|
||||
'eventOperationTime': '운영 시간(초)',
|
||||
'eventStartDt': '시작 시간',
|
||||
'eventEndDt': '종료 시간',
|
||||
'repeat_type': '반복 타입',
|
||||
'eventOperationTime': '진행 시간(분)',
|
||||
'event_operation_time': '진행 시간(분)',
|
||||
'eventStartDt': '이벤트 시작일',
|
||||
'event_start_dt': '이벤트 시작일',
|
||||
'eventEndDt': '이벤트 종료일',
|
||||
'event_end_dt': '이벤트 종료일',
|
||||
'roundTime': '라운드 시간(초)',
|
||||
'round_time': '라운드 시간(초)',
|
||||
'roundCount': '라운드 수',
|
||||
'hotTime': '핫타임',
|
||||
'configId': '설정 ID',
|
||||
@@ -203,6 +215,20 @@ export const FieldLabels = {
|
||||
'gacha_group_id': '랜덤박스 그룹 ID',
|
||||
'ugq_action': 'UGQ 사용 가능 여부',
|
||||
'linked_land': '연결된 랜드 ID',
|
||||
|
||||
//스케줄
|
||||
'refresh_interval': '새로고침 주기',
|
||||
'initialization_interval': '초기화 주기',
|
||||
'snapshot_interval': '스냅샷 주기',
|
||||
'event_action_id': '이벤트 액션 그룹',
|
||||
'global_event_action_id': '이벤트 액션 그룹',
|
||||
'personal_event_action_id': '개인제작 액션 그룹',
|
||||
'max_point': '기여도 목표점수',
|
||||
|
||||
//메뉴
|
||||
'image_list': '이미지 목록',
|
||||
'is_link': '링크 여부',
|
||||
'order_id': '정렬'
|
||||
};
|
||||
|
||||
export const historyTables = {
|
||||
|
||||
@@ -100,7 +100,7 @@ export const menuConfig = {
|
||||
permissions: {
|
||||
read: authType.gameLogRead
|
||||
},
|
||||
view: false,
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
businesslogview: {
|
||||
@@ -127,10 +127,27 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
instancedictionary: {
|
||||
title: '인스턴스 조회',
|
||||
permissions: {
|
||||
read: authType.instanceDictionaryRead
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
rankmanage: {
|
||||
title: '랭킹 점수 관리',
|
||||
permissions: {
|
||||
read: authType.rankManagerRead
|
||||
read: authType.rankManagerRead,
|
||||
update: authType.rankManagerUpdate,
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
rankview: {
|
||||
title: '랭킹 시스템 조회',
|
||||
permissions: {
|
||||
read: authType.rankInfoRead,
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
|
||||
@@ -45,9 +45,9 @@ export const TabUserIndexList = [
|
||||
];
|
||||
|
||||
export const TabRankManageList = [
|
||||
{ value: 'PIONEER', name: '개척자 랭킹 보드' },
|
||||
{ value: 'RUN_RACE', name: '점프 러너 랭킹 보드' },
|
||||
{ value: 'BATTLE_OBJECT', name: '컴뱃 존 랭킹 보드' }
|
||||
{ value: 'RANK', name: '랭킹 보드' },
|
||||
// { value: 'RUN_RACE', name: '점프 러너 랭킹 보드' },
|
||||
// { value: 'BATTLE_OBJECT', name: '컴뱃 존 랭킹 보드' }
|
||||
];
|
||||
|
||||
export const mailSendType = [
|
||||
@@ -245,6 +245,12 @@ export const itemSearchType = [
|
||||
{ value: 'NAME', name: '아이템명' },
|
||||
];
|
||||
|
||||
export const instanceSearchType = [
|
||||
{ value: 'ID', name: '인스턴스 ID' },
|
||||
{ value: 'NAME', name: '인스턴스명' },
|
||||
{ value: 'BUILDING', name: '빌딩 ID' },
|
||||
];
|
||||
|
||||
export const blockType = [
|
||||
{ value: '', name: '선택' },
|
||||
{ value: 'Access_Restrictions', name: '접근 제한' },
|
||||
@@ -419,6 +425,22 @@ export const opPropRecipeType = [
|
||||
{ value: 'Add', name: '등록 필요' },
|
||||
];
|
||||
|
||||
export const opInstanceContentsType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'Concert', name: 'Concert' },
|
||||
{ value: 'Movie', name: 'Movie' },
|
||||
{ value: 'Meeting', name: 'Meeting' },
|
||||
{ value: 'MyHome', name: 'MyHome' },
|
||||
{ value: 'Normal', name: 'Normal' },
|
||||
];
|
||||
|
||||
export const opInstanceAccessType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'Public', name: 'Public' },
|
||||
{ value: 'Item', name: 'Item' },
|
||||
{ value: 'Belong', name: 'Belong' },
|
||||
];
|
||||
|
||||
export const opEquipType = [
|
||||
{ value: 0, name: '미장착' },
|
||||
{ value: 1, name: '의상장착' },
|
||||
@@ -654,6 +676,18 @@ export const opCommonStatus = [
|
||||
{ value: 'RUNNING', name: '진행중' },
|
||||
]
|
||||
|
||||
export const opRankingType = [
|
||||
{ value: 'PIONEER', name: '개척자' },
|
||||
{ value: 'RUNNER1', name: '점프러너 - 맵1' },
|
||||
{ value: 'RUNNER2', name: '점프러너 - 맵2' },
|
||||
{ value: 'RUNNER3', name: '점프러너 - 맵3' },
|
||||
{ value: 'RUNNER4', name: '점프러너 - 맵4' },
|
||||
{ value: 'BATTLE_FFA', name: '컴뱃존 - FFA' },
|
||||
{ value: 'BATTLE_TEAM', name: '컴뱃존 - TEAM' },
|
||||
{ value: 'EVENT_CONTRIBUTION', name: '월드 이벤트 - 기여도' },
|
||||
{ value: 'EVENT_CRAFT', name: '월드 이벤트 - 개인 제작' },
|
||||
]
|
||||
|
||||
// export const logAction = [
|
||||
// { value: "None", name: "ALL" },
|
||||
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" },
|
||||
@@ -1128,6 +1162,10 @@ export const logAction = [
|
||||
{ value: "QuestMailSend", name: "QuestMailSend" },
|
||||
{ value: "QuestMainTask", name: "QuestMainTask" },
|
||||
{ value: "QuestTaskUpdate", name: "QuestTaskUpdate" },
|
||||
{ value: "RankingStart", name: "RankingStart" },
|
||||
{ value: "RankingFinish", name: "RankingFinish" },
|
||||
{ value: "RankingScoreUpdate", name: "RankingScoreUpdate" },
|
||||
{ value: "RankingEventActionScore", name: "RankingEventActionScore" },
|
||||
{ value: "RefuseFriendRequest", name: "RefuseFriendRequest" },
|
||||
{ value: "RenameFriendFolder", name: "RenameFriendFolder" },
|
||||
{ value: "RenameMyhome", name: "RenameMyhome" },
|
||||
@@ -1199,6 +1237,7 @@ export const logAction = [
|
||||
{ value: "UserLogout", name: "UserLogout" },
|
||||
{ value: "UserLogoutSnapShot", name: "UserLogoutSnapShot" },
|
||||
{ value: "UserReport", name: "UserReport" },
|
||||
{ value: "WorldEventActionScore", name: "WorldEventActionScore" },
|
||||
{ value: "Warp", name: "Warp" },
|
||||
{ value: "igmApiLogin", name: "igmApiLogin" }
|
||||
];
|
||||
@@ -1239,6 +1278,7 @@ export const logDomain = [
|
||||
{ value: "Cart", name: "Cart" },
|
||||
{ value: "Currency", name: "Currency" },
|
||||
{ value: "CustomDefineUi", name: "CustomDefineUi" },
|
||||
{ value: "EventActionScore", name: "EventActionScore" },
|
||||
{ value: "EscapePosition", name: "EscapePosition" },
|
||||
{ value: "Friend", name: "Friend" },
|
||||
{ value: "Farming", name: "Farming" },
|
||||
@@ -1275,6 +1315,8 @@ export const logDomain = [
|
||||
{ value: "QuestMain", name: "QuestMain" },
|
||||
{ value: "QuestUgq", name: "QuestUgq" },
|
||||
{ value: "QuestMail", name: "QuestMail" },
|
||||
{ value: "Ranking", name: "Ranking" },
|
||||
{ value: "Ranker", name: "Ranker" },
|
||||
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
||||
{ value: "Rental", name: "Rental" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,30 @@
|
||||
"orderType": "desc",
|
||||
"pageType": "default",
|
||||
"buttons": [
|
||||
{
|
||||
"id": "update",
|
||||
"text": "강제 새로고침",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "rankingUpdate",
|
||||
"action": "update"
|
||||
},
|
||||
{
|
||||
"id": "init",
|
||||
"text": "강제 초기화",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "rankingUpdate",
|
||||
"action": "rankingInit"
|
||||
},
|
||||
{
|
||||
"id": "snapshot",
|
||||
"text": "강제 스냅샷",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "rankingUpdate",
|
||||
"action": "rankingSnapshot"
|
||||
},
|
||||
{
|
||||
"id": "delete",
|
||||
"text": "선택 삭제",
|
||||
|
||||
@@ -62,6 +62,8 @@ export const authType = {
|
||||
worldEventUpdate: 60,
|
||||
worldEventDelete: 61,
|
||||
craftingDictionaryRead: 62,
|
||||
rankInfoRead: 63,
|
||||
instanceDictionaryRead: 64,
|
||||
|
||||
|
||||
levelReader: 999,
|
||||
|
||||
99
src/components/DataManage/RankPioneerInfo.js
Normal file
99
src/components/DataManage/RankPioneerInfo.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserToolView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const RankPioneerInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [rowData, setRowData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if(dataList && dataList.slot_list)
|
||||
setRowData([
|
||||
{ title: 'GUID', itemNo: dataList.slot_list.slot1?.tool_id, itemName: dataList?.slot_list.slot1?.tool_name },
|
||||
{ title: '아바타명', itemNo: dataList.slot_list.slot2?.tool_id, itemName: dataList?.slot_list.slot2?.tool_name },
|
||||
{ title: '점수', itemNo: dataList.slot_list.slot3?.tool_id, itemName: dataList?.slot_list.slot3?.tool_name },
|
||||
])
|
||||
}, [dataList])
|
||||
|
||||
const fetchData = async () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
await UserToolView(token, userInfo.guid).then(data => {
|
||||
setDataList(data);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton width='30%' count={4} /> :
|
||||
<>
|
||||
<ToolWrapper>
|
||||
<UserInfoTable $maxwidth="570px">
|
||||
<colgroup>
|
||||
<col width="120" />
|
||||
<col width="30%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{rowData && rowData.map((el, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<th>{el.title}</th>
|
||||
<td>{el.itemNo}</td>
|
||||
<td>{el.itemName}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</UserInfoTable>
|
||||
</ToolWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RankPioneerInfo;
|
||||
|
||||
const UserInfoTable = styled.table`
|
||||
width: 100%;
|
||||
max-width: ${props => props.$maxwidth || 'auto'};
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
th {
|
||||
width: 120px;
|
||||
background: #888;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
td {
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ToolWrapper = styled.div`
|
||||
${UserInfoTable} {
|
||||
td {
|
||||
border-left: 1px solid #d9d9d9;
|
||||
}
|
||||
}
|
||||
`;
|
||||
84
src/components/DataManage/RankingSnapshotInfo.js
Normal file
84
src/components/DataManage/RankingSnapshotInfo.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { RankInfoSearchBar, useRankInfoSearch } from '../searchBar';
|
||||
import { useDataFetch } from '../../hooks/hook';
|
||||
import { RankingScheduleSimpleView } from '../../apis';
|
||||
import { FormWrapper, TableStyle, TableWrapper } from '../../styles/Components';
|
||||
import { ExcelDownButton, ViewTableInfo } from '../common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatTimeFromSeconds } from '../../utils';
|
||||
|
||||
const RankingSnapshotInfo = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const tableRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
config,
|
||||
loading: dataLoading,
|
||||
searchParams,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams,
|
||||
snapshotData
|
||||
} = useRankInfoSearch(token);
|
||||
|
||||
const {
|
||||
data: rankingScheduleData
|
||||
} = useDataFetch(() => RankingScheduleSimpleView(token), [token]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<RankInfoSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
scheduleData={rankingScheduleData}
|
||||
snapshotData={snapshotData}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo>
|
||||
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_RANKING_SNAPSHOT')} />
|
||||
</ViewTableInfo>
|
||||
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||
<TableWrapper>
|
||||
<TableStyle ref={tableRef}>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">순위</th>
|
||||
<th width="120">account ID</th>
|
||||
<th width="200">GUID</th>
|
||||
<th width="150">아바타명</th>
|
||||
<th width="80">점수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList?.ranker_List?.map((rank, index) => (
|
||||
<tr key={index}>
|
||||
<td>{rank.rank}</td>
|
||||
<td>{rank.account_id}</td>
|
||||
<td>{rank.user_guid}</td>
|
||||
<td>{rank.nickname}</td>
|
||||
<td>{rank.score_type === "Time" ? formatTimeFromSeconds(rank.score) : rank.score}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RankingSnapshotInfo;
|
||||
|
||||
201
src/components/DataManage/UserRankInfo.js
Normal file
201
src/components/DataManage/UserRankInfo.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import NicknameChangeModal from '../../components/DataManage/NicknameChangeModal';
|
||||
import { UserInfoView } from '../../apis/Users';
|
||||
import { authType } from '../../assets/data';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../store/authList';
|
||||
import { UserDefault } from '../../styles/ModuleComponents';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
import { UserInfoSkeleton } from '../Skeleton/UserInfoSkeleton';
|
||||
import { useModal } from '../../hooks/hook';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { Button, Col, Descriptions, InputNumber, Row, Space, Table } from 'antd';
|
||||
import { RankerInfoModify, RankingInfoView } from '../../apis';
|
||||
import { InputItem, TextInput } from '../../styles/Components';
|
||||
import CustomConfirmModal from '../common/modal/CustomConfirmModal';
|
||||
import InputConfirmModal from '../common/modal/InputConfirmModal';
|
||||
import { opRankingType } from '../../assets/data/options';
|
||||
import { formatTimeFromSeconds } from '../../utils';
|
||||
|
||||
const UserRankInfo = ({ userInfo }) => {
|
||||
const { t } = useTranslation();
|
||||
const authInfo = useRecoilValue(authList);
|
||||
const token = sessionStorage.getItem('token');
|
||||
const {showModal, showToast} = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
valueChange: 'hidden'
|
||||
});
|
||||
const [dataList, setDataList] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [authUpdate, setAuthUpdate] = useState(false);
|
||||
const [selectRow, setSelectRow] = useState();
|
||||
const [updateValue, setUpdateValue] = useState();
|
||||
const [comment, setComment] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
setAuthUpdate(authInfo?.auth_list?.some(auth => auth.id === authType.rankManagerUpdate));
|
||||
}, [authInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, [userInfo]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
await RankingInfoView(token, userInfo.guid).then(data => {
|
||||
setDataList(data.user_ranking_info);
|
||||
}).catch(error => {
|
||||
showToast(error, {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const findAndFormatScore = (rankingType) => {
|
||||
if (!dataList?.rankingItems) return '';
|
||||
const item = dataList.rankingItems.find(item => item.rankingType === rankingType);
|
||||
|
||||
if (!item || item.score === null || item.score === undefined) return '';
|
||||
|
||||
// scoreType이 "Time"이면 시:분:초 형식으로 변환 (초 단위로 가정)
|
||||
if (item.scoreType === "Time") {
|
||||
return formatTimeFromSeconds(item.score);
|
||||
}
|
||||
|
||||
// 그 외의 경우는 숫자 그대로 반환
|
||||
return item.score.toString();
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataIndex: 'label',
|
||||
width: 200,
|
||||
onCell: () => ({
|
||||
style: {
|
||||
backgroundColor: '#f0f0f0',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
dataIndex: 'content',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
dataIndex: 'action',
|
||||
},
|
||||
];
|
||||
|
||||
const generateTableData = () => {
|
||||
if (!dataList) return [];
|
||||
|
||||
const baseData = [
|
||||
{
|
||||
key: 'guid',
|
||||
label: 'GUID',
|
||||
content: userInfo?.guid || '',
|
||||
action: null
|
||||
},
|
||||
{
|
||||
key: 'nickname',
|
||||
label: '아바타명',
|
||||
content: dataList?.nickname || userInfo?.nickname || '',
|
||||
action: null
|
||||
}
|
||||
];
|
||||
|
||||
// opRankingType을 기반으로 동적 생성
|
||||
const rankingData = opRankingType.map(rankType => {
|
||||
const formattedScore = findAndFormatScore(rankType.value);
|
||||
const hasScore = formattedScore !== '';
|
||||
|
||||
return {
|
||||
key: rankType.value.toLowerCase(),
|
||||
label: rankType.name,
|
||||
content: formattedScore,
|
||||
action: authUpdate && hasScore ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => handleSubmit('valueChange', rankType.value)}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
) : null
|
||||
};
|
||||
});
|
||||
|
||||
return [...baseData, ...rankingData];
|
||||
};
|
||||
|
||||
const data = generateTableData();
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
let params = {};
|
||||
|
||||
switch (type) {
|
||||
case "valueChange":
|
||||
setSelectRow(param);
|
||||
const comment = dataList.rankingItems.find(item => item.rankingType === param)?.scoreType === 'Time' ? '초단위로 입력해주세요.' : '점수';
|
||||
setComment(comment);
|
||||
handleModalView('valueChange');
|
||||
break;
|
||||
case "valueChangeConfirm":
|
||||
const item = dataList.rankingItems.find(item => item.rankingType === selectRow);
|
||||
params.guid = item.guid;
|
||||
params.score = updateValue;
|
||||
params.user_guid = userInfo.guid;
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RankerInfoModify(token, params);
|
||||
}).then(data => {
|
||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||
fetchData();
|
||||
}).catch(error => {
|
||||
showToast(error, {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleModalClose('valueChange');
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
loading ? <TableSkeleton width='30%' count={5} /> :
|
||||
<>
|
||||
<div>
|
||||
<UserDefault>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
showHeader={false}
|
||||
bordered
|
||||
/>
|
||||
</UserDefault>
|
||||
</div>
|
||||
<InputConfirmModal
|
||||
inputType='number'
|
||||
view={modalState.valueChangeModal}
|
||||
handleSubmit={() => handleSubmit('valueChangeConfirm')}
|
||||
handleCancel={() => handleModalClose('valueChange')}
|
||||
handleClose={() => handleModalClose('valueChange')}
|
||||
value={updateValue}
|
||||
setValue={setUpdateValue}
|
||||
inputText={t('UPDATE_VALUE_COMMENT',{comment:comment})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default UserRankInfo;
|
||||
24
src/components/DataManage/index.js
Normal file
24
src/components/DataManage/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export { default as UserDefaultInfo } from './UserDefaultInfo';
|
||||
export { default as UserAvatarInfo } from './UserAvatarInfo';
|
||||
export { default as UserDressInfo } from './UserDressInfo';
|
||||
export { default as UserToolInfo } from './UserToolInfo';
|
||||
export { default as UserInventoryInfo } from './UserInventoryInfo';
|
||||
export { default as UserMailInfo } from './UserMailInfo';
|
||||
export { default as UserMyHomeInfo } from './UserMyHomeInfo';
|
||||
export { default as UserFriendInfo } from './UserFriendInfo';
|
||||
export { default as UserTattooInfo } from './UserTattooInfo';
|
||||
export { default as UserQuestInfo } from './UserQuestInfo';
|
||||
export { default as UserClaimInfo } from './UserClaimInfo';
|
||||
export { default as CurrencyLogContent } from './CurrencyLogContent';
|
||||
export { default as ItemLogContent } from './ItemLogContent';
|
||||
export { default as CurrencyItemLogContent } from './CurrencyItemLogContent';
|
||||
export { default as UserLoginLogContent } from './UserLoginLogContent';
|
||||
export { default as UserCreateLogContent } from './UserCreateLogContent';
|
||||
export { default as UserSnapshotLogContent } from './UserSnapshotLogContent';
|
||||
export { default as RankPioneerInfo } from './RankPioneerInfo';
|
||||
export { default as LandDetailModal } from './LandDetailModal';
|
||||
export { default as MailDetailModal } from './MailDetailModal';
|
||||
export { default as QuestDetailModal } from './QuestDetailModal';
|
||||
export { default as NicknameChangeModal } from './NicknameChangeModal';
|
||||
export { default as UserRankInfo } from './UserRankInfo';
|
||||
export { default as RankingSnapshotInfo } from './RankingSnapshotInfo';
|
||||
47
src/components/common/modal/InputConfirmModal.js
Normal file
47
src/components/common/modal/InputConfirmModal.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { BtnWrapper, ButtonClose, InputItem, ModalText } from '../../../styles/Components';
|
||||
import Button from '../button/Button';
|
||||
import Modal from './Modal';
|
||||
import { Input, InputNumber } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const InputConfirmModal = ({view, handleClose, handleCancel, handleSubmit, inputText, inputType, value, setValue}) => {
|
||||
return (
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
<InputItem>
|
||||
<p>{inputText}</p>
|
||||
{ inputType === 'number' &&
|
||||
<InputNumber
|
||||
style={{width: '100%'}}
|
||||
value={value}
|
||||
min={0}
|
||||
step={1}
|
||||
onChange={(value) => setValue(value)}
|
||||
/>}
|
||||
{ inputType === 'text' &&
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
/>}
|
||||
|
||||
</InputItem>
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancel} />
|
||||
<Button
|
||||
text="확인"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
width="100%"
|
||||
handleClick={handleSubmit}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default InputConfirmModal;
|
||||
@@ -161,7 +161,7 @@ const LogDetailModal = ({ detailView,
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{getFieldLabel(item.sourceInfo.operationType)}</td>
|
||||
<td>{item.fieldName}</td>
|
||||
<td>{getFieldLabel(item.fieldName)}</td>
|
||||
<td>{formatValue(item.newValue)}</td>
|
||||
<td>{formatValue(item.oldValue)}</td>
|
||||
<td>{item.sourceInfo.worker}</td>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import LoginModal from './LoginModal';
|
||||
import Button from '../../components/common/button/Button';
|
||||
@@ -7,14 +7,22 @@ import Modal from '../common/modal/Modal';
|
||||
|
||||
import { Title, BtnWrapper, ButtonClose } from '../../styles/Components';
|
||||
import { TextInput } from '../../styles/Components';
|
||||
import { AuthLogin } from '../../apis';
|
||||
import { AdminChangePw, AuthLogin } from '../../apis';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { authList } from '../../store/authList';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import ToastAlert from '../common/alert/ToastAlert';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Loading from '../common/Loading';
|
||||
|
||||
const LoginForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [stateModal, setStateModal] = useState('hidden');
|
||||
const [errorText, setErrorText] = useState('');
|
||||
const navigate = useNavigate();
|
||||
const [toast, setToast] = useState(null)
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleModal = () => {
|
||||
if (stateModal === 'hidden') {
|
||||
@@ -28,6 +36,25 @@ const LoginForm = () => {
|
||||
|
||||
const values = watch();
|
||||
|
||||
const showToast = (message, type = alertTypes.info) => {
|
||||
const toastData = {
|
||||
id: Date.now(),
|
||||
message,
|
||||
type,
|
||||
position: 'top-center'
|
||||
};
|
||||
setToast(toastData);
|
||||
|
||||
// 5초 후 자동으로 토스트 제거
|
||||
setTimeout(() => {
|
||||
setToast(null);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const closeToast = () => {
|
||||
setToast(null);
|
||||
};
|
||||
|
||||
const onSubmit = async data => {
|
||||
const result = await AuthLogin(data);
|
||||
setErrorText(result.data.message);
|
||||
@@ -45,6 +72,24 @@ const LoginForm = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordInit = async () => {
|
||||
setIsLoading(true);
|
||||
await AdminChangePw({ email: values.email })
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
showToast(t('PASSWORD_INIT_COMPLETE'), alertTypes.success);
|
||||
} else {
|
||||
showToast(t('PASSWORD_INIT_ERROR'), alertTypes.error);
|
||||
}
|
||||
}).catch(err => {
|
||||
showToast(t('API_FAIL'), alertTypes.error);
|
||||
}).finally(() => {
|
||||
setIsLoading(false);
|
||||
handleModal('hidden');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper action="" $flow="column" onSubmit={handleSubmit(onSubmit)}>
|
||||
@@ -93,15 +138,37 @@ const LoginForm = () => {
|
||||
text="확인"
|
||||
theme="line"
|
||||
size="large"
|
||||
width="100%"
|
||||
width="50%"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handleModal('hidden');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="비밀번호 초기화"
|
||||
theme="line"
|
||||
size="large"
|
||||
width="50%"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
handlePasswordInit();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
)}
|
||||
{toast && (
|
||||
<ToastAlert
|
||||
key={toast.id}
|
||||
id={toast.id}
|
||||
message={toast.message}
|
||||
type={toast.type}
|
||||
position={toast.position}
|
||||
onClose={closeToast}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoading && <Loading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,8 +59,8 @@ const EventModal = ({ modalType, detailView, handleDetailView, content, setDetai
|
||||
|
||||
const opEventActionMode = useMemo(() => {
|
||||
return eventActionData?.map(item => ({
|
||||
value: item.id,
|
||||
name: `${item.description}(${item.id})`
|
||||
value: item.groupId,
|
||||
name: `${item.description}(${item.groupId})`
|
||||
})) || [];
|
||||
}, [eventActionData]);
|
||||
|
||||
|
||||
@@ -79,8 +79,8 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
|
||||
const opEventActionMode = useMemo(() => {
|
||||
return eventActionData?.map(item => ({
|
||||
value: item.id,
|
||||
name: `${item.description}(${item.id})`
|
||||
value: item.groupId,
|
||||
name: `${item.description}(${item.groupId})`
|
||||
})) || [];
|
||||
}, [eventActionData]);
|
||||
|
||||
@@ -96,8 +96,9 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
if (!checkCondition()) return;
|
||||
|
||||
// const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||
// const startDt = resultData.event_start_dt;
|
||||
// const endDt = resultData.event_end_dt;
|
||||
const startDt = resultData.start_dt;
|
||||
const endDt = resultData.end_dt;
|
||||
const baseDt = resultData.base_dt;
|
||||
// if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||
// return;
|
||||
@@ -106,12 +107,16 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// //화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
|
||||
// if (modalType === TYPE_REGISTRY && startDt < new Date()) {
|
||||
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||
// return;
|
||||
// }
|
||||
|
||||
if(startDt > baseDt){
|
||||
showToast('SCHEDULE_MODAL_START_DIFF_BASE_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
if(startDt > endDt){
|
||||
showToast('SCHEDULE_MODAL_START_DIFF_END_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
showModal(isView('modify') ? 'SCHEDULE_UPDATE_CONFIRM' : 'SCHEDULE_REGIST_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
@@ -132,7 +137,11 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||
if(data.data.message){
|
||||
showToast(data.data.message, {type: alertTypes.error});
|
||||
}else{
|
||||
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
@@ -147,7 +156,11 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||
if(data.data.message){
|
||||
showToast(data.data.message, {type: alertTypes.error});
|
||||
}else{
|
||||
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
@@ -168,7 +181,6 @@ const RankingModal = ({ modalType, detailView, handleDetailView, content, setDet
|
||||
&& resultData.event_action_id > 0
|
||||
&& resultData.title !== ''
|
||||
&& resultData.refresh_interval > 0
|
||||
&& resultData.initialization_interval > 0
|
||||
&& resultData.snapshot_interval > 0
|
||||
);
|
||||
};
|
||||
@@ -368,9 +380,9 @@ export const initData = {
|
||||
start_dt: '',
|
||||
end_dt: '',
|
||||
base_dt: '',
|
||||
refresh_interval: 60,
|
||||
initialization_interval: 0,
|
||||
snapshot_interval: 1440,
|
||||
refresh_interval: 5,
|
||||
initialization_interval: 240,
|
||||
snapshot_interval: 60,
|
||||
meta_id: '',
|
||||
event_action_id: '',
|
||||
}
|
||||
|
||||
162
src/components/searchBar/RankInfoSearchBar.js
Normal file
162
src/components/searchBar/RankInfoSearchBar.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import { InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { RankingSnapshotView, RankerListView } from '../../apis';
|
||||
|
||||
export const useRankInfoSearch = (token) => {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
guid: '',
|
||||
snapshot: '',
|
||||
orderBy: 'DESC',
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
const [snapshotData, setSnapshotData] = useState([]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(searchParams); // 컴포넌트 마운트 시 초기 데이터 로드
|
||||
}, [token]);
|
||||
|
||||
const fetchSnapshotData = useCallback(async (guid) => {
|
||||
if (!guid) {
|
||||
setSnapshotData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await RankingSnapshotView(
|
||||
token,
|
||||
guid
|
||||
);
|
||||
setSnapshotData(result || []);
|
||||
} catch (error) {
|
||||
console.error('Error fetching snapshot data:', error);
|
||||
setSnapshotData([]);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.guid) {
|
||||
fetchSnapshotData(searchParams.guid);
|
||||
} else {
|
||||
setSnapshotData([]);
|
||||
}
|
||||
}, [searchParams.guid, fetchSnapshotData]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
try {
|
||||
if(params.snapshot === ''){
|
||||
setData();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const result = await RankerListView(
|
||||
token,
|
||||
params.guid,
|
||||
params.snapshot,
|
||||
);
|
||||
setData(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error fetching auction data:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
currentPage: newParams.currentPage || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
return await fetchData(updatedParams);
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const resetParams = {
|
||||
guid: '',
|
||||
snapshot: '',
|
||||
orderBy: 'DESC',
|
||||
currentPage: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ currentPage: newPage });
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ orderBy: newOrder });
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
snapshotData,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const RankInfoSearchBar = ({ searchParams, onSearch, onReset, scheduleData, snapshotData }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputLabel>랭킹스케줄</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.guid} onChange={e => onSearch({ guid: e.target.value, snapshot: '' }, false)}>
|
||||
<option value=""></option>
|
||||
{scheduleData?.map((data, index) => (
|
||||
<option key={index} value={data.guid}>
|
||||
{data.title}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
<InputLabel>스냅샷</InputLabel>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.snapshot} onChange={e => onSearch({ snapshot: e.target.value }, false)}>
|
||||
<option value="">
|
||||
{!searchParams.guid ? '먼저 랭킹스케줄을 선택하세요' :
|
||||
snapshotData === '' || snapshotData === undefined ? '로딩 중...' :
|
||||
'스냅샷을 선택하세요'}
|
||||
</option>
|
||||
|
||||
{snapshotData?.map((data, index) => (
|
||||
<option key={index} value={data.snapshot_index}>
|
||||
{data.snapshot_index}({data.snapshot_time})
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
return <SearchBarLayout firstColumnData={searchList} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default RankInfoSearchBar;
|
||||
@@ -40,6 +40,7 @@ import RankManageSearchBar, { useRankManageSearch } from './RankManageSearchBar'
|
||||
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
||||
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||
import UserSearchBar, {useUserSearch} from './UserSearchBar';
|
||||
import RankInfoSearchBar, {useRankInfoSearch} from './RankInfoSearchBar';
|
||||
|
||||
// 모든 SearchBar 컴포넌트 export
|
||||
export {
|
||||
@@ -101,5 +102,7 @@ export {
|
||||
useRankManageSearch,
|
||||
UserSearchBar,
|
||||
useUserSearch,
|
||||
RankInfoSearchBar,
|
||||
useRankInfoSearch,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { INITIAL_PAGE_SIZE } from '../assets/data/adminConstants';
|
||||
|
||||
/**
|
||||
* RDS 스타일의 페이지네이션을 위한 훅
|
||||
@@ -12,7 +13,7 @@ export const useRDSPagination = (fetchFunction, initialState = {}) => {
|
||||
// 페이지네이션 상태
|
||||
const [pagination, setPagination] = useState({
|
||||
currentPage: initialState.currentPage || 1,
|
||||
pageSize: initialState.pageSize || 10,
|
||||
pageSize: initialState.pageSize || INITIAL_PAGE_SIZE,
|
||||
totalItems: initialState.totalItems || 0,
|
||||
totalPages: initialState.totalPages || 0
|
||||
});
|
||||
|
||||
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);
|
||||
52
src/pages/DataManage/RankInfoView.js
Normal file
52
src/pages/DataManage/RankInfoView.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Title} from '../../styles/Components';
|
||||
|
||||
import { authType } from '../../assets/data';
|
||||
import { withAuth } from '../../hooks/hook';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
||||
import { Col, Divider, Row } from 'antd';
|
||||
import {RankingSnapshotInfo} from '../../components/DataManage';
|
||||
|
||||
const RankInfoView = () => {
|
||||
|
||||
|
||||
return (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>랭킹 시스템 조회</Title>
|
||||
<Row
|
||||
gutter={[24, 16]}
|
||||
style={{
|
||||
minHeight: 'calc(100vh - 200px)',
|
||||
alignItems: 'stretch'
|
||||
}}
|
||||
>
|
||||
<Col
|
||||
span={11}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<RankingSnapshotInfo />
|
||||
</Col>
|
||||
<Col span={0} style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Divider type="vertical" style={{ height: '100%', margin: 0 }} />
|
||||
</Col>
|
||||
<Col
|
||||
span={12}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<RankingSnapshotInfo />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(authType.rankInfoRead)(RankInfoView);
|
||||
@@ -5,10 +5,6 @@ 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';
|
||||
@@ -17,13 +13,13 @@ import {
|
||||
useUserSearch,
|
||||
} from '../../components/searchBar';
|
||||
import { AnimatedTabs } from '../../components/common';
|
||||
import { UserRankPioneerInfo } from '../../components/DataManage';
|
||||
import { UserRankInfo } from '../../components/DataManage';
|
||||
|
||||
const RankManage = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [infoView, setInfoView] = useState('none');
|
||||
const [activeTab, setActiveTab] = useState('PIONEER');
|
||||
const [activeTab, setActiveTab] = useState('RANK');
|
||||
const [resultData, setResultData] = useState();
|
||||
|
||||
const {
|
||||
@@ -50,9 +46,7 @@ const RankManage = () => {
|
||||
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} />;
|
||||
case 'RANK': return <UserRankInfo userInfo={resultData} />;
|
||||
default: return null;
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -5,3 +5,5 @@ export { default as BusinessLogView} from './BusinessLogView';
|
||||
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';
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
import React, { useState, Fragment, useEffect } from 'react';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
TextInput,
|
||||
SelectInput,
|
||||
Label,
|
||||
InputLabel,
|
||||
Textarea,
|
||||
SearchBarAlert,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { EventIsItem, EventSingleRegist } from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AppendRegistBox, AppendRegistTable, AreaBtnClose,
|
||||
BtnDelete,
|
||||
Item,
|
||||
ItemList, LangArea, ModalItem, ModalItemList, RegistGroup,
|
||||
RegistInputItem,
|
||||
RegistInputRow, RegistNotice, RegistTable,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType, benItems, currencyItemCode } from '../../assets/data';
|
||||
import DateTimeInput from '../../components/common/input/DateTimeInput';
|
||||
import { timeDiffMinute } from '../../utils';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||
|
||||
const EventRegist = () => {
|
||||
const navigate = useNavigate();
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { showToast, showModal } = useAlert();
|
||||
const { withLoading} = useLoading();
|
||||
|
||||
const [item, setItem] = useState(''); // 아이템 값
|
||||
const [itemCount, setItemCount] = useState(''); // 아이템 개수
|
||||
const [resource, setResource] = useState(currencyCodeTypes.gold); // 자원 값
|
||||
const [resourceCount, setResourceCount] = useState(''); // 자원 개수
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [itemCheckMsg, setItemCheckMsg] = useState('');
|
||||
|
||||
const [time, setTime] = useState({
|
||||
start_hour: '00',
|
||||
start_min: '00',
|
||||
end_hour: '00',
|
||||
end_min: '00',
|
||||
}); //시간 정보
|
||||
|
||||
const [resultData, setResultData] = useState({
|
||||
is_reserve: false,
|
||||
start_dt: '',
|
||||
end_dt: '',
|
||||
event_type: 'ATTD',
|
||||
mail_list: [
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
language: 'KO',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
language: 'EN',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
language: 'JA',
|
||||
}
|
||||
],
|
||||
item_list: [],
|
||||
guid: '',
|
||||
}); //데이터 정보
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
useEffect(() => {
|
||||
setItemCheckMsg('');
|
||||
}, [item]);
|
||||
|
||||
const combineDateTime = (date, hour, min) => {
|
||||
if (!date) return null;
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hour, min);
|
||||
};
|
||||
|
||||
// 날짜 처리
|
||||
const handleDateChange = (data, type) => {
|
||||
const date = new Date(data);
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, time[`${type}_hour`], time[`${type}_min`]),
|
||||
});
|
||||
};
|
||||
|
||||
// 시간 처리
|
||||
const handleTimeChange = (e, type) => {
|
||||
const { id, value } = e.target;
|
||||
const newTime = { ...time, [`${type}_${id}`]: value };
|
||||
setTime(newTime);
|
||||
|
||||
const date = resultData[`${type}_dt`] ? resultData[`${type}_dt`] : new Date();
|
||||
|
||||
setResultData({
|
||||
...resultData,
|
||||
[`${type}_dt`]: combineDateTime(date, newTime[`${type}_hour`], newTime[`${type}_min`]),
|
||||
});
|
||||
};
|
||||
|
||||
// 아이템 수량 숫자 체크
|
||||
const handleItemCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setItemCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setItemCount(plusNum);
|
||||
} else {
|
||||
setItemCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
const handleItemList = async () => {
|
||||
if(benItems.includes(item)){
|
||||
showToast('MAIL_ITEM_ADD_BEN', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
if(item.length === 0 || itemCount.length === 0) return;
|
||||
|
||||
const token = sessionStorage.getItem('token');
|
||||
const result = await EventIsItem(token, {item: item});
|
||||
|
||||
if(result.data.result === "ERROR"){
|
||||
setItemCheckMsg(t('NOT_ITEM'));
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
|
||||
if (itemIndex !== -1) {
|
||||
showToast('MAIL_ITEM_ADD_DUPL', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
};
|
||||
|
||||
// 추가된 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 입력창 삭제
|
||||
const onLangDelete = language => {
|
||||
let filterList = resultData.mail_list && resultData.mail_list.filter(el => el.language !== language);
|
||||
|
||||
if (filterList.length === 1) {
|
||||
setBtnValidation(true);
|
||||
} else {
|
||||
setBtnValidation(false);
|
||||
}
|
||||
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 수량 숫자 체크
|
||||
const handleResourceCount = e => {
|
||||
if (e.target.value === '0' || e.target.value === '-0') {
|
||||
setResourceCount('1');
|
||||
e.target.value = '1';
|
||||
} else if (e.target.value < 0) {
|
||||
let plusNum = Math.abs(e.target.value);
|
||||
setResourceCount(plusNum);
|
||||
} else {
|
||||
setResourceCount(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
const handleResourceList = (e) => {
|
||||
if(resource.length === 0 || resourceCount.length === 0) return;
|
||||
|
||||
const itemIndex = resultData.item_list.findIndex(
|
||||
(item) => item.item === resource
|
||||
);
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||
} else {
|
||||
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
|
||||
resultData.item_list.push(newItem);
|
||||
}
|
||||
setResourceCount('');
|
||||
};
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
if(timeDiff < 60) {
|
||||
showToast('EVENT_TIME_LIMIT_ADD', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
showModal('', {
|
||||
type: alertTypes.confirmChildren,
|
||||
onConfirm: () => handleSubmit('registConfirm'),
|
||||
children: <ModalItem>
|
||||
{t('EVENT_REGIST_CONFIRM')}
|
||||
{resultData.item_list && (
|
||||
<ModalItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name} {data.item_cnt.toLocaleString()}
|
||||
</span>
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ModalItemList>
|
||||
)}
|
||||
</ModalItem>
|
||||
});
|
||||
break;
|
||||
|
||||
case "registConfirm":
|
||||
await withLoading(async () => {
|
||||
return await EventSingleRegist(token, resultData);
|
||||
}).then((result) => {
|
||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||
}).catch(() => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
callbackPage();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const callbackPage = () => {
|
||||
navigate('/servicemanage/event');
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.mail_list.every(data => data.content !== '' && data.title !== '') &&
|
||||
(resultData.start_dt.length !== 0) &&
|
||||
(resultData.end_dt.length !== 0)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedPageWrapper>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
||||
<AuthModal/>
|
||||
) : (
|
||||
<>
|
||||
<Title>이벤트 등록</Title>
|
||||
<RegistGroup>
|
||||
<RegistInputRow>
|
||||
<RegistInputItem>
|
||||
<InputLabel>이벤트 타입</InputLabel>
|
||||
<SelectInput onChange={e => setResultData({ ...resultData, event_type: e.target.value })} value={resultData.event_type}>
|
||||
<option value="ATTD">출석 이벤트</option>
|
||||
</SelectInput>
|
||||
</RegistInputItem>
|
||||
<DateTimeInput
|
||||
title="시작 시간"
|
||||
dateName="시작 일자"
|
||||
selectedDate={resultData.start_dt}
|
||||
handleSelectedDate={data => handleDateChange(data, 'start')}
|
||||
onChange={e => handleTimeChange(e, 'start')}
|
||||
/>
|
||||
<DateTimeInput
|
||||
title="종료 시간"
|
||||
dateName="종료 일자"
|
||||
selectedDate={resultData.end_dt}
|
||||
handleSelectedDate={data => handleDateChange(data, 'end')}
|
||||
onChange={e => handleTimeChange(e, 'end')}
|
||||
/>
|
||||
</RegistInputRow>
|
||||
</RegistGroup>
|
||||
{resultData.mail_list.map((data, idx) => {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<AppendRegistBox>
|
||||
<LangArea>
|
||||
언어 : {data.language}
|
||||
{btnValidation === false ? (
|
||||
<AreaBtnClose
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onLangDelete(data.language);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<AreaBtnClose opacity="10%" />
|
||||
)}
|
||||
</LangArea>
|
||||
<RegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>제목</Label>
|
||||
</th>
|
||||
<td>
|
||||
<RegistInputItem>
|
||||
<TextInput
|
||||
placeholder="우편 제목 입력"
|
||||
maxLength="30"
|
||||
id={data.language}
|
||||
value={data.title}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 30) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].title = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
}}
|
||||
/>
|
||||
</RegistInputItem>
|
||||
<RegistNotice $color={data.title.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({data.title.length}/30자)</RegistNotice>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<Label>내용</Label>
|
||||
</th>
|
||||
<td>
|
||||
<Textarea
|
||||
maxLength="2000"
|
||||
value={data.content}
|
||||
id={data.language}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 2000) {
|
||||
return;
|
||||
}
|
||||
let list = [...resultData.mail_list];
|
||||
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id);
|
||||
list[findIndex].content = e.target.value.trimStart();
|
||||
|
||||
setResultData({ ...resultData, mail_list: list });
|
||||
}}
|
||||
/>
|
||||
<RegistNotice $color={data.content.length > 1999 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({data.content.length}/2000자)</RegistNotice>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</RegistTable>
|
||||
</AppendRegistBox>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<AppendRegistBox>
|
||||
<AppendRegistTable>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>아이템 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<RegistInputItem>
|
||||
<TextInput placeholder="Item Meta id 입력" value={item} onChange={e => setItem(e.target.value.trimStart())} />
|
||||
<TextInput placeholder="수량" type="number" value={itemCount} onChange={e => handleItemCount(e)} width="100px" />
|
||||
<Button text="추가" theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'} handleClick={handleItemList} width="100px" height="35px" />
|
||||
{itemCheckMsg && <SearchBarAlert>{itemCheckMsg}</SearchBarAlert>}
|
||||
</RegistInputItem>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="120">
|
||||
<Label>자원 첨부</Label>
|
||||
</th>
|
||||
<td>
|
||||
<RegistInputItem>
|
||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
|
||||
{currencyItemCode.filter(data => data.value !== currencyCodeTypes.calium).map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput placeholder="수량" type="number" value={resourceCount} onChange={e => handleResourceCount(e)} width="200px" />
|
||||
<Button text="추가" theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'} handleClick={handleResourceList} width="100px" height="35px" />
|
||||
</RegistInputItem>
|
||||
|
||||
<div>
|
||||
{resultData.item_list && (
|
||||
<ItemList>
|
||||
{resultData.item_list.map((data, index) => {
|
||||
return (
|
||||
<Item key={index}>
|
||||
<span>
|
||||
{data.item_name}[{data.item}] ({data.item_cnt})
|
||||
</span>
|
||||
<BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</ItemList>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</AppendRegistTable>
|
||||
</AppendRegistBox>
|
||||
{isNullValue && (
|
||||
<SearchBarAlert $align="right" $padding="0 0 15px">
|
||||
{t('NULL_MSG')}
|
||||
</SearchBarAlert>
|
||||
)}
|
||||
|
||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
handleClick={() => showModal('EVENT_REGIST_CANCEL', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => callbackPage()
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
text="등록"
|
||||
theme={checkCondition() ? 'primary' : 'disable'}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</>
|
||||
)}
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventRegist;
|
||||
|
||||
@@ -15,8 +15,8 @@ import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import {
|
||||
EventActionView,
|
||||
LogHistory,
|
||||
RankingDataView, RankingScheduleDelete,
|
||||
RankingScheduleDetailView,
|
||||
RankingDataView, RankingInit, RankingScheduleDelete,
|
||||
RankingScheduleDetailView, RankingSnapshot, RankingUpdate,
|
||||
} from '../../apis';
|
||||
import { CommonSearchBar } from '../../components/ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
@@ -101,6 +101,102 @@ const Ranking = () => {
|
||||
handleModalView('detail');
|
||||
});
|
||||
break;
|
||||
case "update":
|
||||
const sel = selectedRows[0];
|
||||
const guid = sel.guid;
|
||||
if(guid === null || guid === undefined || guid === "") {
|
||||
showToast('SCHEDULE_REFRESH_GUID_NULL_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
if(sel.status !== CommonStatus.running) {
|
||||
showToast('UPDATE_STATUS_ONLY_RUNNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
showModal('SCHEDULE_SELECT_UPDATE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('updateConfirm', guid)
|
||||
});
|
||||
break;
|
||||
case "updateConfirm":
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RankingUpdate(token, item);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('SCHEDULE_REFRESH_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast(data.data.message, {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
});
|
||||
break;
|
||||
case "rankingInit":
|
||||
const initSel = selectedRows[0];
|
||||
const initGuid = initSel.guid;
|
||||
if(initGuid === null || initGuid === undefined || initGuid === "") {
|
||||
showToast('SCHEDULE_REFRESH_GUID_NULL_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
if(initSel.status !== CommonStatus.running) {
|
||||
showToast('UPDATE_STATUS_ONLY_RUNNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
showModal('SCHEDULE_SELECT_INIT', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('rankingInitConfirm', initGuid)
|
||||
});
|
||||
break;
|
||||
case "rankingInitConfirm":
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RankingInit(token, item);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('SCHEDULE_INIT_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast(data.data.message, {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
});
|
||||
break;
|
||||
case "rankingSnapshot":
|
||||
const snapshotSel = selectedRows[0];
|
||||
const snapshotSGuid = snapshotSel.guid;
|
||||
if(snapshotSGuid === null || snapshotSGuid === undefined || snapshotSGuid === "") {
|
||||
showToast('SCHEDULE_REFRESH_GUID_NULL_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
if(snapshotSel.status !== CommonStatus.running) {
|
||||
showToast('UPDATE_STATUS_ONLY_RUNNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
showModal('SCHEDULE_SELECT_SNAPSHOT', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('rankingSnapshotConfirm', snapshotSGuid)
|
||||
});
|
||||
break;
|
||||
case "rankingSnapshotConfirm":
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RankingSnapshot(token, item);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('SCHEDULE_SNAPSHOT_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast(data.data.message, {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
|
||||
showModal('SCHEDULE_SELECT_DELETE', {
|
||||
|
||||
@@ -14,11 +14,15 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
|
||||
function AdminView() {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const navigate = useNavigate();
|
||||
const {withLoading} = useLoading();
|
||||
const {showToast} = useLoading();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
@@ -258,14 +262,24 @@ function AdminView() {
|
||||
handleInitialModalClose();
|
||||
};
|
||||
|
||||
const handlePasswordInitialize = () => {
|
||||
AdminChangePw(token, { email: selectedEmail });
|
||||
const handlePasswordInitialize = async () => {
|
||||
await withLoading(async () => {
|
||||
return await AdminChangePw({ email: selectedEmail });
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
showToast('PASSWORD_INIT_COMPLETE', { type: alertTypes.success });
|
||||
} else {
|
||||
showToast('PASSWORD_INIT_ERROR', { type: alertTypes.error });
|
||||
}
|
||||
}).error(err => {
|
||||
showToast('API_FAIL', { type: alertTypes.error });
|
||||
}).finally(() => {
|
||||
handleInitialModalClose();
|
||||
handleConfirmeModalClose();
|
||||
});
|
||||
|
||||
// console.log(selectedEmail);
|
||||
|
||||
setConfirmText('비밀번호 초기화가');
|
||||
handleInitialModalClose();
|
||||
handleConfirmeModalClose();
|
||||
};
|
||||
// 전체 선택 구현
|
||||
const handleAllSelect = () => {
|
||||
|
||||
@@ -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