Compare commits
7 Commits
5143b45610
...
3169055646
| Author | SHA1 | Date | |
|---|---|---|---|
| 3169055646 | |||
| 5d2e1918d1 | |||
| 4407fdc6b6 | |||
| b01c5cd410 | |||
| 63b3704e89 | |||
| f78a4912a6 | |||
| e25bcdc86e |
@@ -13,11 +13,11 @@ import {
|
||||
LogView,
|
||||
} from './pages/UserManage';
|
||||
import { EconomicIndex, UserIndex } from './pages/IndexManage';
|
||||
import { LandInfoView, CryptView, GameLogView, UserView, BusinessLogView, } from './pages/DataManage';
|
||||
import { LandInfoView, GameLogView, UserView, BusinessLogView, MetaItemView, RankManage } from './pages/DataManage';
|
||||
import {
|
||||
Board,
|
||||
Event,
|
||||
EventRegist,
|
||||
RewardEvent,
|
||||
RewardEventRegist,
|
||||
Items,
|
||||
Mail,
|
||||
MailRegist,
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
UserBlockRegist,
|
||||
LandAuction,
|
||||
BattleEvent,
|
||||
MenuBanner, MenuBannerRegist,
|
||||
MenuBanner, MenuBannerRegist, Ranking,
|
||||
Event
|
||||
} from './pages/ServiceManage';
|
||||
|
||||
const RouteInfo = () => {
|
||||
@@ -59,8 +60,9 @@ const RouteInfo = () => {
|
||||
<Route path="userview" element={<UserView />} />
|
||||
<Route path="landview" element={<LandInfoView />} />
|
||||
<Route path="gamelogview" element={<GameLogView />} />
|
||||
<Route path="cryptview" element={<CryptView />} />
|
||||
<Route path="businesslogview" element={<BusinessLogView />} />
|
||||
<Route path="itemdictionary" element={<MetaItemView />} />
|
||||
<Route path="rankmanage" element={<RankManage />} />
|
||||
</Route>
|
||||
<Route path="/servicemanage">
|
||||
<Route path="board" element={<Board />} />
|
||||
@@ -70,12 +72,14 @@ const RouteInfo = () => {
|
||||
<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 path="rewardevent" element={<RewardEvent />} />
|
||||
<Route path="rewardevent/eventregist" element={<RewardEventRegist />} />
|
||||
<Route path="landauction" element={<LandAuction />} />
|
||||
<Route path="battleevent" element={<BattleEvent />} />
|
||||
<Route path="menubanner" element={<MenuBanner />} />
|
||||
<Route path="menubanner/menubannerregist" element={<MenuBannerRegist />} />
|
||||
<Route path="ranking" element={<Ranking />} />
|
||||
<Route path="event" element={<Event />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
58
src/apis/Dictionary.js
Normal file
58
src/apis/Dictionary.js
Normal file
@@ -0,0 +1,58 @@
|
||||
//운영 정보 관리 - 백과사전 api 연결
|
||||
|
||||
import { Axios, responseFileDownload } from '../utils';
|
||||
|
||||
// 아이템 백과사전 조회
|
||||
export const getItemDictionaryList = async (token, searchType, searchData, largeType, smallType, brand, gender, order, size, currentPage) => {
|
||||
try {
|
||||
const response = await Axios.get(`/api/v1/dictionary/item/list?search_type=${searchType}&search_data=${searchData}
|
||||
&large_type=${largeType}&small_type=${smallType}&brand=${brand}&gender=${gender}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('getItemDictionaryList API error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const ItemDictionaryExport = async (token, params) => {
|
||||
try {
|
||||
await Axios.get(`/api/v1/dictionary/item/excel-export?search_type=${params.search_type}&search_data=${params.search_data}
|
||||
&large_type=${params.large_type}&small_type=${params.small_type}&brand=${params.brand}&gender=${params.gender}
|
||||
&lang=${params.lang}&task_id=${params.taskId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob'
|
||||
}).then(response => {
|
||||
responseFileDownload(response, {
|
||||
defaultFileName: 'itemDictionary'
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('ItemDictionaryExport Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const BrandView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/dictionary/brand/list`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data.brand_list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('BrandView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
//운영서비스 관리 - 이벤트 api 연결
|
||||
//운영서비스 관리 - 통합 이벤트 api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
// 이벤트 리스트 조회
|
||||
export const EventView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
||||
export const EventView = async (token, searchData, status, startDate, endDate, order, size, currentPage) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/event/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}&orderby=${order}&page_no=${currentPage}
|
||||
&page_size=${size}`,
|
||||
`/api/v1/world-event/list?search_data=${searchData}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
@@ -24,11 +24,11 @@ export const EventView = async (token, title, content, status, startDate, endDat
|
||||
// 이벤트 상세보기
|
||||
export const EventDetailView = async (token, id) => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/event/detail/${id}`, {
|
||||
const res = await Axios.get(`/api/v1/world-event/detail/${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data.detail;
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('EventDetailView Error', e);
|
||||
@@ -39,11 +39,11 @@ export const EventDetailView = async (token, id) => {
|
||||
// 이벤트 등록
|
||||
export const EventSingleRegist = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/event`, params, {
|
||||
const res = await Axios.post(`/api/v1/world-event`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res;
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('EventSingleRegist Error', e);
|
||||
@@ -51,10 +51,10 @@ export const EventSingleRegist = async (token, params) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 우편 수정
|
||||
// 이벤트 수정
|
||||
export const EventModify = async (token, id, params) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/event/${id}`, params, {
|
||||
const res = await Axios.put(`/api/v1/world-event/${id}`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
@@ -66,15 +66,14 @@ export const EventModify = async (token, id, params) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 우편 삭제
|
||||
export const EventDelete = async (token, params, id) => {
|
||||
// 이벤트 삭제
|
||||
export const EventDelete = async (token, id) => {
|
||||
try {
|
||||
const res = await Axios.delete(`/api/v1/event/delete`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
data: { list: params },
|
||||
const res = await Axios.delete(`/api/v1/world-event/delete?id=${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
return res.data.data.list;
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('EventDelete Error', e);
|
||||
@@ -82,17 +81,20 @@ export const EventDelete = async (token, params, id) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 우편 아이템 확인
|
||||
export const EventIsItem = async (token, params) => {
|
||||
// 이벤트 메타데이터 조회
|
||||
export const EventActionView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/event/item`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const res = await Axios.get(
|
||||
`/api/v1/dictionary/event-action/list`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res;
|
||||
return res.data.data.event_action_list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('EventIsItem Error', e);
|
||||
throw new Error('EventActionView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,7 @@ export const LogViewList = async (token, searchType, searchKey, historyType, sta
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data;
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('LogViewList Error', e);
|
||||
|
||||
@@ -111,7 +111,6 @@ export const getCurrencyDetailList = async (token, searchType, searchData, tranI
|
||||
|
||||
export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
||||
try {
|
||||
console.log(params);
|
||||
await Axios.post(`/api/v1/log/currency/detail/excel-export`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob',
|
||||
@@ -129,9 +128,9 @@ export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getItemDetailList = async (token, searchType, searchData, tranId, logAction, itemLargeType, itemSmallType, countDeltaType, startDate, endDate, order, size, currentPage) => {
|
||||
export const getItemDetailList = async (token, searchType, searchData, itemId, tranId, logAction, itemLargeType, itemSmallType, countDeltaType, startDate, endDate, order, size, currentPage) => {
|
||||
try {
|
||||
const response = await Axios.get(`/api/v1/log/item/detail/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}
|
||||
const response = await Axios.get(`/api/v1/log/item/detail/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}&item_id=${itemId}
|
||||
&log_action=${logAction}&item_large_type=${itemLargeType}&item_small_type=${itemSmallType}&count_delta_type=${countDeltaType}&start_dt=${startDate}&end_dt=${endDate}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||
headers: {
|
||||
@@ -149,7 +148,6 @@ export const getItemDetailList = async (token, searchType, searchData, tranId, l
|
||||
|
||||
export const GameItemDetailLogExport = async (token, params, fileName) => {
|
||||
try {
|
||||
console.log(params);
|
||||
await Axios.post(`/api/v1/log/item/detail/excel-export`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob',
|
||||
@@ -187,7 +185,7 @@ export const getCurrencyItemList = async (token, searchType, searchData, tranId,
|
||||
|
||||
export const GameCurrencyItemLogExport = async (token, params, fileName) => {
|
||||
try {
|
||||
console.log(params);
|
||||
|
||||
await Axios.post(`/api/v1/log/currency-item/excel-export`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
responseType: 'blob',
|
||||
|
||||
99
src/apis/Rank.js
Normal file
99
src/apis/Rank.js
Normal file
@@ -0,0 +1,99 @@
|
||||
//운영서비스 관리 - 랭킹 스케줄 api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
// 리스트 조회
|
||||
export const RankingScheduleView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/rank/schedule/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 전투시스템 상세보기
|
||||
export const RankingScheduleDetailView = async (token, id) => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/rank/schedule/detail/${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleDetailView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 랭킹스케줄 등록
|
||||
export const RankingScheduleSingleRegist = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/rank/schedule`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleSingleRegist Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 랭킹스케줄 수정
|
||||
export const RankingScheduleModify = async (token, id, params) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/rank/schedule/${id}`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleModify Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 랭킹스케줄 삭제
|
||||
export const RankingScheduleDelete = async (token, id) => {
|
||||
try {
|
||||
const res = await Axios.delete(`/api/v1/rank/schedule/delete?id=${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingScheduleDelete Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const RankingDataView = async (token) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/dictionary/ranking/list`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data.ranking_list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RankingDataView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
98
src/apis/RewardEvent.js
Normal file
98
src/apis/RewardEvent.js
Normal file
@@ -0,0 +1,98 @@
|
||||
//운영서비스 관리 - 이벤트 api 연결
|
||||
|
||||
import { Axios } from '../utils';
|
||||
|
||||
// 이벤트 리스트 조회
|
||||
export const RewardEventView = async (token, title, content, status, startDate, endDate, order, size, currentPage) => {
|
||||
try {
|
||||
const res = await Axios.get(
|
||||
`/api/v1/event/list?title=${title}&content=${content}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
|
||||
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
return res.data.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RewardEventView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 상세보기
|
||||
export const RewardEventDetailView = async (token, id) => {
|
||||
try {
|
||||
const res = await Axios.get(`/api/v1/event/detail/${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data.data.detail;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RewardEventDetailView Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 등록
|
||||
export const RewardEventSingleRegist = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/event`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RewardEventSingleRegist Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 우편 수정
|
||||
export const RewardEventModify = async (token, id, params) => {
|
||||
try {
|
||||
const res = await Axios.put(`/api/v1/event/${id}`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RewardEventModify Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 우편 삭제
|
||||
export const RewardEventDelete = async (token, params, id) => {
|
||||
try {
|
||||
const res = await Axios.delete(`/api/v1/event/delete`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
data: { list: params },
|
||||
});
|
||||
|
||||
return res.data.data.list;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('RewardEventDelete Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 우편 아이템 확인
|
||||
export const EventIsItem = async (token, params) => {
|
||||
try {
|
||||
const res = await Axios.post(`/api/v1/event/item`, params, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('EventIsItem Error', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,7 @@ export const UserView = async (token, searchType, searchKey) => {
|
||||
{ headers: { Authorization: `Bearer ${token}` } },
|
||||
);
|
||||
|
||||
return res.data.data.result;
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new Error('UserView Error', e);
|
||||
|
||||
@@ -12,12 +12,16 @@ export * from './BlackList';
|
||||
export * from './Users';
|
||||
export * from './Indicators';
|
||||
export * from './Item';
|
||||
export * from './Event';
|
||||
export * from './RewardEvent';
|
||||
export * from './Calium';
|
||||
export * from './Land';
|
||||
export * from './Menu';
|
||||
// export * from './OpenAI';
|
||||
export * from './Log';
|
||||
export * from './Data';
|
||||
export * from './Dictionary';
|
||||
export * from './Rank';
|
||||
export * from './Event';
|
||||
|
||||
const apiModules = {};
|
||||
const allApis = {};
|
||||
|
||||
18
src/assets/data/apis/eventAPI.json
Normal file
18
src/assets/data/apis/eventAPI.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/world-event",
|
||||
"endpoints": {
|
||||
"EventView": {
|
||||
"method": "GET",
|
||||
"url": "/list",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "query"
|
||||
},
|
||||
"EventDetailView": {
|
||||
"method": "GET",
|
||||
"url": "/detail/:id",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"LogViewList": {
|
||||
"method": "GET",
|
||||
"url": "/list",
|
||||
"dataPath": "data.data",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "query"
|
||||
},
|
||||
"LogviewDetail": {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import itemAPI from './itemAPI.json';
|
||||
import menuBannerAPI from './menuBannerAPI.json';
|
||||
import historyAPI from './historyAPI.json';
|
||||
import eventAPI from './eventAPI.json';
|
||||
import rankingAPI from './rankingAPI.json';
|
||||
|
||||
export {
|
||||
itemAPI,
|
||||
menuBannerAPI,
|
||||
historyAPI
|
||||
historyAPI,
|
||||
eventAPI,
|
||||
rankingAPI,
|
||||
};
|
||||
18
src/assets/data/apis/rankingAPI.json
Normal file
18
src/assets/data/apis/rankingAPI.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"baseUrl": "/api/v1/rank/schedule",
|
||||
"endpoints": {
|
||||
"RankingScheduleView": {
|
||||
"method": "GET",
|
||||
"url": "/list",
|
||||
"dataPath": "data",
|
||||
"paramFormat": "query"
|
||||
},
|
||||
"RankingScheduleDetailView": {
|
||||
"method": "GET",
|
||||
"url": "/detail/:id",
|
||||
"dataPath": "data.data",
|
||||
"paramFormat": "query",
|
||||
"paramMapping": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export const STATUS_STYLES = {
|
||||
},
|
||||
};
|
||||
|
||||
export const logFieldLabels = {
|
||||
export const FieldLabels = {
|
||||
// DynamoDB 필드
|
||||
'attribFieldName': '속성 명',
|
||||
'pk': '파티션 키',
|
||||
@@ -181,16 +181,40 @@ export const logFieldLabels = {
|
||||
'ffa_hot_time': 'FFA 핫타임',
|
||||
'round_count': '라운드 수',
|
||||
|
||||
//dictionary
|
||||
'max_count': '최대 보유 가능 수량',
|
||||
'stack_max_count': '최대 스택 가능 수량',
|
||||
'expire_type': '아이템 만료 타입',
|
||||
'expire_start_dt': '만료 시작 시간',
|
||||
'expire_end_dt': '만료 종료 시간',
|
||||
'expire_time_sec': '만료 시간 연장 여부',
|
||||
'user_tradable': '유저 간 거래 가능 여부',
|
||||
'system_tradable': '상점에서 판매 가능 여부',
|
||||
'throwable': '버리기 가능 여부',
|
||||
'cart_buy': '상점에서 구매 가능 여부',
|
||||
'rarity': '희귀도',
|
||||
'default_attrib': '기본 속성',
|
||||
'attrib_random_group': '랜덤 그룹',
|
||||
'item_set': '아이템 세트',
|
||||
'buff': '아이템 사용 시 획득 버프',
|
||||
'dress_slot_type': '착용 부위',
|
||||
'product_link': '제품 URL',
|
||||
'prop_small_type': '제작 아이템 그룹',
|
||||
'gacha_group_id': '랜덤박스 그룹 ID',
|
||||
'ugq_action': 'UGQ 사용 가능 여부',
|
||||
'linked_land': '연결된 랜드 ID',
|
||||
};
|
||||
|
||||
export const historyTables = {
|
||||
userBlock: 'black_list',
|
||||
landAuction: 'land_auction',
|
||||
landOwnerChange: 'land_ownership_changes',
|
||||
event: 'event',
|
||||
rewardEvent: 'event',
|
||||
mail: 'mail',
|
||||
notice: 'notice',
|
||||
battleEvent: 'battle_event',
|
||||
caliumRequest: 'calium_request',
|
||||
menuBanner: 'menu_banner',
|
||||
event: 'world_event',
|
||||
rankingSchedule: 'ranking_schedule',
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export {authType, ivenTabType, modalTypes, TabUserList, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
||||
export {authType, ivenTabType, modalTypes, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
||||
export {
|
||||
mailSendType,
|
||||
mailType,
|
||||
@@ -27,6 +27,7 @@ export {
|
||||
opYNType,
|
||||
opUserSessionType,
|
||||
opMailType,
|
||||
amountDeltaType
|
||||
amountDeltaType,
|
||||
TabUserList
|
||||
} from './options'
|
||||
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'
|
||||
@@ -103,14 +103,6 @@ export const menuConfig = {
|
||||
view: false,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
// cryptview: {
|
||||
// title: '크립토 조회',
|
||||
// permissions: {
|
||||
// read: authType.cryptoRead
|
||||
// },
|
||||
// view: false,
|
||||
// authLevel: adminAuthLevel.NONE
|
||||
// },
|
||||
businesslogview: {
|
||||
title: '비즈니스 로그 조회',
|
||||
permissions: {
|
||||
@@ -118,6 +110,22 @@ export const menuConfig = {
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
itemdictionary: {
|
||||
title: '아이템 백과사전 조회',
|
||||
permissions: {
|
||||
read: authType.itemDictionaryRead
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
rankmanage: {
|
||||
title: '랭킹 점수 관리',
|
||||
permissions: {
|
||||
read: authType.rankManagerRead
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -154,17 +162,17 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
reportlist: {
|
||||
title: '신고내역',
|
||||
permissions: {
|
||||
read: authType.reportRead,
|
||||
update: authType.reportUpdate,
|
||||
delete: authType.reportDelete
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
event: {
|
||||
// reportlist: {
|
||||
// title: '신고내역',
|
||||
// permissions: {
|
||||
// read: authType.reportRead,
|
||||
// update: authType.reportUpdate,
|
||||
// delete: authType.reportDelete
|
||||
// },
|
||||
// view: true,
|
||||
// authLevel: adminAuthLevel.NONE
|
||||
// },
|
||||
rewardevent: {
|
||||
title: '보상 이벤트 관리',
|
||||
permissions: {
|
||||
read: authType.eventRead,
|
||||
@@ -214,6 +222,26 @@ export const menuConfig = {
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
ranking: {
|
||||
title: '랭킹 스케줄러',
|
||||
permissions: {
|
||||
read: authType.rankingRead,
|
||||
update: authType.rankingUpdate,
|
||||
delete: authType.rankingDelete
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
event: {
|
||||
title: '통합 이벤트 관리',
|
||||
permissions: {
|
||||
read: authType.worldEventRead,
|
||||
update: authType.worldEventUpdate,
|
||||
delete: authType.worldEventDelete
|
||||
},
|
||||
view: true,
|
||||
authLevel: adminAuthLevel.NONE
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,20 @@ export const languageType = [
|
||||
{ value: 'JA', name: '일본어' },
|
||||
];
|
||||
|
||||
export const TabUserList = [
|
||||
{ value: 'BASIC', name: '기본정보' },
|
||||
{ value: 'AVATAR', name: '아바타' },
|
||||
{ value: 'CLOTH', name: '의상' },
|
||||
{ value: 'TOOL', name: '도구' },
|
||||
{ value: 'INVENTORY', name: '인벤토리' },
|
||||
{ value: 'MAIL', name: '우편' },
|
||||
{ value: 'MYHOME', name: '마이홈' },
|
||||
{ value: 'FRIEND', name: '친구목록' },
|
||||
{ value: 'TATTOO', name: '타투' },
|
||||
{ value: 'QUEST', name: '퀘스트' }
|
||||
// { value: 'CLAIM', name: '클레임' }
|
||||
];
|
||||
|
||||
export const TabGameLogList = [
|
||||
{ value: 'CURRENCY', name: '재화 로그' },
|
||||
{ value: 'ITEM', name: '아이템 로그' },
|
||||
@@ -30,6 +44,12 @@ export const TabUserIndexList = [
|
||||
// { value: 'PLAYTIME', name: '플레이타임' },
|
||||
];
|
||||
|
||||
export const TabRankManageList = [
|
||||
{ value: 'PIONEER', name: '개척자 랭킹 보드' },
|
||||
{ value: 'RUN_RACE', name: '점프 러너 랭킹 보드' },
|
||||
{ value: 'BATTLE_OBJECT', name: '컴뱃 존 랭킹 보드' }
|
||||
];
|
||||
|
||||
export const mailSendType = [
|
||||
{ value: 'ALL', name: '전체' },
|
||||
{ value: 'RESERVE_SEND', name: '예약 발송' },
|
||||
@@ -220,6 +240,11 @@ export const landSearchType = [
|
||||
{ value: 'NAME', name: '랜드명' },
|
||||
];
|
||||
|
||||
export const itemSearchType = [
|
||||
{ value: 'ID', name: '아이템ID' },
|
||||
{ value: 'NAME', name: '아이템명' },
|
||||
];
|
||||
|
||||
export const blockType = [
|
||||
{ value: '', name: '선택' },
|
||||
{ value: 'Access_Restrictions', name: '접근 제한' },
|
||||
@@ -388,7 +413,7 @@ export const opEquipType = [
|
||||
{ value: 3, name: '타투장착' },
|
||||
]
|
||||
|
||||
export const opItemType = [
|
||||
export const opItemLargeType = [
|
||||
{ value: 'TOOL', name: '도구' },
|
||||
{ value: 'EXPENDABLE', name: '소모품' },
|
||||
{ value: 'TICKET', name: '티켓' },
|
||||
@@ -400,6 +425,95 @@ export const opItemType = [
|
||||
{ value: 'CURRENCY', name: '재화' },
|
||||
{ value: 'PRODUCT', name: '제품' },
|
||||
{ value: 'BEAUTY', name: '뷰티' },
|
||||
{ value: 'SET_BOX', name: '세트 박스' },
|
||||
]
|
||||
|
||||
export const opItemSmallType = [
|
||||
{ value: 'HANDMIRROR', name: '손거울' },
|
||||
{ value: 'LIGHTSTICK', name: '응원봉' },
|
||||
{ value: 'FIRECRACKER', name: '폭죽총' },
|
||||
{ value: 'LIGHTSABER', name: '광선검' },
|
||||
{ value: 'REGISTER_ITEM_SOCIAL_ACTION', name: '소셜액션 등록형 아이템' },
|
||||
{ value: 'REGISTER_ITEM_INTERIOR', name: '인테리어 등록형 아이템' },
|
||||
{ value: 'SAIYAN_AURA', name: '초사이어인 오라' },
|
||||
{ value: 'TICKET', name: '소모형 티켓' },
|
||||
{ value: 'RANDOMBOX', name: '골드(재화) 가챠 랜덤 박스' },
|
||||
{ value: 'SHIRT', name: '상의' },
|
||||
{ value: 'DRESS', name: '드레스' },
|
||||
{ value: 'OUTER', name: '겉옷' },
|
||||
{ value: 'PANTS', name: '하의' },
|
||||
{ value: 'GLOVES', name: '장갑' },
|
||||
{ value: 'RING', name: '반지' },
|
||||
{ value: 'BRACELET', name: '팔찌' },
|
||||
{ value: 'BAG', name: '가방(크로스백)' },
|
||||
{ value: 'BACKPACK', name: '가방(백팩)' },
|
||||
{ value: 'CAP', name: '모자' },
|
||||
{ value: 'MASK', name: '가면' },
|
||||
{ value: 'GLASSES', name: '안경' },
|
||||
{ value: 'EARRING', name: '귀걸이' },
|
||||
{ value: 'NECKLACE', name: '목걸이' },
|
||||
{ value: 'SHOES', name: '신발' },
|
||||
{ value: 'SOCKS', name: '양말' },
|
||||
{ value: 'ANKLET', name: '발찌' },
|
||||
{ value: 'OFFICECHAIR', name: '의자' },
|
||||
{ value: 'WALLMOUNTTV', name: '벽걸이TV' },
|
||||
{ value: 'OUTDOORCHAIR', name: '야외용의자' },
|
||||
{ value: 'TV', name: 'TV' },
|
||||
{ value: 'VIGNETTE', name: '소품' },
|
||||
{ value: 'COOKWARE', name: '조리도구' },
|
||||
{ value: 'KITCHEN_TOOL', name: '부엌도구' },
|
||||
{ value: 'LAPTOP', name: '노트북' },
|
||||
{ value: 'OUTDOOR_GOODS', name: '캠핑도구' },
|
||||
{ value: 'BED', name: '침대' },
|
||||
{ value: 'DECO', name: '소형장식품' },
|
||||
{ value: 'FURNITURE', name: '(러그)가구' },
|
||||
{ value: 'MUSIC', name: '악기/음향' },
|
||||
{ value: 'SHELF_S', name: '소형 선반(TV다이)' },
|
||||
{ value: 'SHELF_L', name: '대형 선반(금고,책장)' },
|
||||
{ value: 'SOFA_SINGLE', name: '1인용 소파' },
|
||||
{ value: 'SOFA_COUCH', name: '카우치 소파' },
|
||||
{ value: 'LIGHT_CEILING', name: '천정 조명' },
|
||||
{ value: 'LIGHT_FLOOR', name: '스탠드 조명' },
|
||||
{ value: 'LIGHT_TABLE', name: '탁상 조명' },
|
||||
{ value: 'LIGHT_PENDENT', name: '팬던트 조명' },
|
||||
{ value: 'TABLE_S', name: '소형 테이블' },
|
||||
{ value: 'TABLE_L', name: '대형 테이블' },
|
||||
{ value: 'TABLE_LIVINGROOM', name: '거실 테이블' },
|
||||
{ value: 'TABLE_OFFICE', name: '사무용 테이블' },
|
||||
{ value: 'LEISURE_APPLIANCE', name: '스포츠/여가' },
|
||||
{ value: 'INDUCTION', name: '인덕션' },
|
||||
{ value: 'MICROWAVE', name: '전자레인지' },
|
||||
{ value: 'LARGE_APPLIANCE', name: '대형가전' },
|
||||
{ value: 'COSMETIC', name: '화장품' },
|
||||
{ value: 'CHEST', name: '앞면' },
|
||||
{ value: 'LEFT_ARM', name: '왼팔' },
|
||||
{ value: 'RIGHT_ARM', name: '오른팔' },
|
||||
{ value: 'BACK', name: '후면' },
|
||||
{ value: 'LEFT_LEG', name: '왼다리' },
|
||||
{ value: 'RIGHT_LEG', name: '오른다리' },
|
||||
{ value: 'CARTRIDGE', name: '속성 카트리지' },
|
||||
{ value: 'BUFF_DRINK', name: '드링크(물약) 아이템' },
|
||||
{ value: 'INTERPHONE', name: '인터폰' },
|
||||
{ value: 'MEGAPHONE', name: '확성기' },
|
||||
{ value: 'CURRENCY', name: 'CURRENCY' },
|
||||
{ value: 'NFTLAND', name: 'NFTLAND' },
|
||||
{ value: 'SUMMONSTONE', name: '소환석' },
|
||||
{ value: 'GOLD', name: '골드' },
|
||||
{ value: 'SAPPHIRE', name: '사파이어' },
|
||||
{ value: 'CALIUM', name: '칼리움' },
|
||||
{ value: 'BEAM', name: '빔' },
|
||||
{ value: 'RUBY', name: '루비' },
|
||||
{ value: 'LIGHT_LIMITED', name: '언리얼 라이트 사용 조명' },
|
||||
{ value: 'SPEAKER', name: '재생 기능성 스피커' },
|
||||
{ value: 'SETBOX', name: '세트박스' },
|
||||
{ value: 'DRESS_SHOES', name: '드레스+신발' },
|
||||
{ value: 'SHOULDERBAG', name: '숄더백' },
|
||||
{ value: 'RECIPE', name: '레시피' }
|
||||
]
|
||||
|
||||
export const opGender = [
|
||||
{ value: 'MALE', name: '남성' },
|
||||
{ value: 'FEMALE', name: '여성' },
|
||||
]
|
||||
|
||||
export const opHistoryType = [
|
||||
@@ -521,7 +635,10 @@ export const opLogAction = [
|
||||
|
||||
export const opCommonStatus = [
|
||||
{ value: 'SUCCESS', name: '성공' },
|
||||
{ value: 'FAIL', name: '실패' }
|
||||
{ value: 'FAIL', name: '실패' },
|
||||
{ value: 'WAIT', name: '대기' },
|
||||
{ value: 'END', name: '종료' },
|
||||
{ value: 'RUNNING', name: '진행중' },
|
||||
]
|
||||
|
||||
// export const logAction = [
|
||||
@@ -820,6 +937,7 @@ export const opCommonStatus = [
|
||||
|
||||
export const logAction = [
|
||||
{ value: "None", name: "전체" },
|
||||
{ value: "AdminToolQuestTaskForceComplete", name: "AdminToolQuestTaskForceComplete" },
|
||||
{ value: "AIChatDeleteCharacter", name: "AIChatDeleteCharacter" },
|
||||
{ value: "AIChatDeleteUser", name: "AIChatDeleteUser" },
|
||||
{ value: "AIChatGetCharacter", name: "AIChatGetCharacter" },
|
||||
@@ -854,12 +972,12 @@ export const logAction = [
|
||||
{ value: "BeaconShopUpdateDailyCount", name: "BeaconShopUpdateDailyCount" },
|
||||
{ value: "BeaconShopDeleteRecord", name: "BeaconShopDeleteRecord" },
|
||||
{ value: "BeaconShopDeactiveItems", name: "BeaconShopDeactiveItems" },
|
||||
{ value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
||||
// { value: "BrokerApiAdmin", name: "BrokerApiAdmin" },
|
||||
{ value: "BrokerApiPlanetAuth", name: "BrokerApiPlanetAuth" },
|
||||
{ value: "BrokerApiUserExchangeOrderCompleted", name: "BrokerApiUserExchangeOrderCompleted" },
|
||||
{ value: "BrokerApiUserExchangeOrderCreated", name: "BrokerApiUserExchangeOrderCreated" },
|
||||
{ value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
||||
{ value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
||||
// { value: "BrokerApiUserSystemMailSend", name: "BrokerApiUserSystemMailSend" },
|
||||
// { value: "BrokerApiUserEchoSystemRequest", name: "BrokerApiUserEchoSystemRequest" },
|
||||
{ value: "BrokerApiUserLogin", name: "BrokerApiUserLogin" },
|
||||
{ value: "BuffAdd", name: "BuffAdd" },
|
||||
{ value: "BuffDelete", name: "BuffDelete" },
|
||||
@@ -909,6 +1027,7 @@ export const logAction = [
|
||||
{ value: "ClaimReward", name: "ClaimReward" },
|
||||
{ value: "ConvertCalium", name: "ConvertCalium" },
|
||||
{ value: "ConvertExchangeCalium", name: "ConvertExchangeCalium" },
|
||||
{ value: "ContentsMove", name: "ContentsMove" },
|
||||
{ value: "CraftFinish", name: "CraftFinish" },
|
||||
{ value: "CraftHelp", name: "CraftHelp" },
|
||||
{ value: "CraftRecipeRegister", name: "CraftRecipeRegister" },
|
||||
@@ -935,6 +1054,12 @@ export const logAction = [
|
||||
{ value: "FriendAdd", name: "FriendAdd" },
|
||||
{ value: "FriendDelete", name: "FriendDelete" },
|
||||
{ value: "GainLandProfit", name: "GainLandProfit" },
|
||||
{ value: "GameModeObjectInteraction", name: "GameModeObjectInteraction" },
|
||||
{ value: "GameModeObjectStateUpdate", name: "GameModeObjectStateUpdate" },
|
||||
{ value: "GameModeUserDead", name: "GameModeUserDead" },
|
||||
{ value: "GameModeUserRespawn", name: "GameModeUserRespawn" },
|
||||
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||
{ value: "GameModeAddMatchCount", name: "GameModeAddMatchCount" },
|
||||
{ value: "InviteParty", name: "InviteParty" },
|
||||
{ value: "ItemBuy", name: "ItemBuy" },
|
||||
{ value: "ItemDestroy", name: "ItemDestroy" },
|
||||
@@ -965,8 +1090,17 @@ export const logAction = [
|
||||
{ value: "MailRead", name: "MailRead" },
|
||||
{ value: "MailSend", name: "MailSend" },
|
||||
{ value: "MailTaken", name: "MailTaken" },
|
||||
{ value: "MatchReserve", name: "MatchReserve" },
|
||||
{ value: "MatchCancel", name: "MatchCancel" },
|
||||
{ value: "MatchResult", name: "MatchResult" },
|
||||
{ value: "MatchRoomUserJoin", name: "MatchRoomUserJoin" },
|
||||
{ value: "MatchRoomUserQuit", name: "MatchRoomUserQuit" },
|
||||
{ value: "MatchRoomCreate", name: "MatchRoomCreate" },
|
||||
{ value: "MatchRoomUpdate", name: "MatchRoomUpdate" },
|
||||
{ value: "MatchRoomDestroy", name: "MatchRoomDestroy" },
|
||||
{ value: "ModifyLandInfo", name: "ModifyLandInfo" },
|
||||
{ value: "MoneyChange", name: "MoneyChange" },
|
||||
{ value: "MoveToBeacon", name: "MoveToBeacon" },
|
||||
{ value: "ProductGive", name: "ProductGive" },
|
||||
{ value: "ProductOpenFailed", name: "ProductOpenFailed" },
|
||||
{ value: "ProductOpenSuccess", name: "ProductOpenSuccess" },
|
||||
@@ -990,6 +1124,11 @@ export const logAction = [
|
||||
{ value: "ReplySummonParty", name: "ReplySummonParty" },
|
||||
{ value: "ReservationEnterToServer", name: "ReservationEnterToServer" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||
{ value: "RunRaceResultSummary", name: "RunRaceResultSummary" },
|
||||
{ value: "SaveMyhome", name: "SaveMyhome" },
|
||||
{ value: "SeasonPassBuyCharged", name: "SeasonPassBuyCharged" },
|
||||
{ value: "SeasonPassStartNew", name: "SeasonPassStartNew" },
|
||||
@@ -1039,6 +1178,7 @@ export const logAction = [
|
||||
{ value: "UpdateGameOption", name: "UpdateGameOption" },
|
||||
{ value: "UpdateLanguage", name: "UpdateLanguage" },
|
||||
{ value: "UpdateUgcNpcLike", name: "UpdateUgcNpcLike" },
|
||||
{ value: "UpdateGameModePlayerRegulation", name: "UpdateGameModePlayerRegulation" },
|
||||
{ value: "UserBlock", name: "UserBlock" },
|
||||
{ value: "UserBlockCancel", name: "UserBlockCancel" },
|
||||
{ value: "UserCreate", name: "UserCreate" },
|
||||
@@ -1091,6 +1231,9 @@ export const logDomain = [
|
||||
{ value: "Farming", name: "Farming" },
|
||||
{ value: "FarmingReward", name: "FarmingReward" },
|
||||
{ value: "GameLogInOut", name: "GameLogInOut" },
|
||||
{ value: "GameObjectInteraction", name: "GameObjectInteraction" },
|
||||
{ value: "GameModePenalty", name: "GameModePenalty" },
|
||||
{ value: "GameModePlayRegulation", name: "GameModePlayRegulation" },
|
||||
{ value: "Item", name: "Item" },
|
||||
{ value: "IgmApi", name: "IgmApi" },
|
||||
{ value: "MyHome", name: "MyHome" },
|
||||
@@ -1102,6 +1245,9 @@ export const logDomain = [
|
||||
{ value: "Mail", name: "Mail" },
|
||||
{ value: "MailStoragePeriodExpired", name: "MailStoragePeriodExpired" },
|
||||
{ value: "MailProfile", name: "MailProfile" },
|
||||
{ value: "MatchUser", name: "MatchUser" },
|
||||
{ value: "MatchServerUser", name: "MatchServerUser" },
|
||||
{ value: "MatchRoom", name: "MatchRoom" },
|
||||
{ value: "Party", name: "Party" },
|
||||
{ value: "PartyMember", name: "PartyMember" },
|
||||
{ value: "PartyVote", name: "PartyVote" },
|
||||
@@ -1119,6 +1265,11 @@ export const logDomain = [
|
||||
{ value: "RenewalShopProducts", name: "RenewalShopProducts" },
|
||||
{ value: "Rental", name: "Rental" },
|
||||
{ value: "RewardProp", name: "RewardProp" },
|
||||
{ value: "RunRaceFinishReward", name: "RunRaceFinishReward" },
|
||||
{ value: "RunRaceRespawnReward", name: "RunRaceRespawnReward" },
|
||||
{ value: "RunRaceUnFinishReward", name: "RunRaceUnFinishReward" },
|
||||
{ value: "RunRaceCheckPointAbusing", name: "RunRaceCheckPointAbusing" },
|
||||
{ value: "RunRaceRewardSummary", name: "RunRaceRewardSummary" },
|
||||
{ value: "Stage", name: "Stage" },
|
||||
{ value: "SocialAction", name: "SocialAction" },
|
||||
{ value: "SeasonPass", name: "SeasonPass" },
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"initialSearchParams": {
|
||||
"searchTitle": "",
|
||||
"searchContent": "",
|
||||
"searchData": "",
|
||||
"status": "ALL",
|
||||
"startDate": "",
|
||||
"endDate": "",
|
||||
@@ -13,51 +12,37 @@
|
||||
"searchFields": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchTitle",
|
||||
"label": "우편 제목",
|
||||
"id": "searchData",
|
||||
"label": "제목",
|
||||
"placeholder": "제목 입력",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "period",
|
||||
"startDateId": "startDate",
|
||||
"endDateId": "endDate",
|
||||
"label": "조회 일자",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchContent",
|
||||
"label": "우편 내용",
|
||||
"placeholder": "우편 내용(공백으로 구분)",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "status",
|
||||
"label": "이벤트 상태",
|
||||
"optionsRef": "eventStatus",
|
||||
"col": 2
|
||||
"label": "상태",
|
||||
"optionsRef": "opMenuBannerStatus",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "period",
|
||||
"startDateId": "startDate",
|
||||
"endDateId": "endDate",
|
||||
"label": "기간",
|
||||
"col": 1
|
||||
}
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"functionName": "EventView",
|
||||
"endpointName": "EventView",
|
||||
"loadOnMount": true,
|
||||
"paramsMapping": [
|
||||
"searchTitle",
|
||||
"searchContent",
|
||||
"status",
|
||||
"paramTransforms": [
|
||||
{"param": "startDate", "transform": "toISOString"},
|
||||
{"param": "endDate", "transform": "toISOString"},
|
||||
"orderBy",
|
||||
"pageSize",
|
||||
"currentPage"
|
||||
{"param": "endDate", "transform": "toISOString"}
|
||||
],
|
||||
"pageField": "currentPage",
|
||||
"pageSizeField": "pageSize",
|
||||
"pageField": "page_no",
|
||||
"pageSizeField": "page_size",
|
||||
"orderField": "orderBy"
|
||||
}
|
||||
}
|
||||
119
src/assets/data/pages/eventTable.json
Normal file
119
src/assets/data/pages/eventTable.json
Normal file
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"id": "eventTable",
|
||||
"selection": {
|
||||
"type": "single",
|
||||
"idField": "id"
|
||||
},
|
||||
"header": {
|
||||
"countType": "total",
|
||||
"orderType": "desc",
|
||||
"pageType": "default",
|
||||
"buttons": [
|
||||
{
|
||||
"id": "delete",
|
||||
"text": "선택 삭제",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "worldEventDelete",
|
||||
"action": "delete"
|
||||
},
|
||||
{
|
||||
"id": "register",
|
||||
"text": "통합 이벤트 등록",
|
||||
"theme": "primary",
|
||||
"requiredAuth": "worldEventUpdate",
|
||||
"action": "regist"
|
||||
}
|
||||
]
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "checkbox",
|
||||
"type": "checkbox",
|
||||
"width": "40px",
|
||||
"title": ""
|
||||
},
|
||||
{
|
||||
"id": "row_num",
|
||||
"type": "text",
|
||||
"width": "70px",
|
||||
"title": "번호"
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"type": "status",
|
||||
"width": "120px",
|
||||
"title": "상태",
|
||||
"option_name": "opCommonStatus"
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"type": "text",
|
||||
"title": "제목",
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"id": "personal_event_action_id",
|
||||
"type": "text",
|
||||
"width": "150px",
|
||||
"title": "개인제작 이벤트 모드"
|
||||
},
|
||||
{
|
||||
"id": "global_event_action_id",
|
||||
"type": "text",
|
||||
"width": "150px",
|
||||
"title": "기여도 이벤트 모드"
|
||||
},
|
||||
{
|
||||
"id": "max_point",
|
||||
"type": "text",
|
||||
"width": "150px",
|
||||
"title": "기여도 목표점수"
|
||||
},
|
||||
{
|
||||
"id": "start_dt",
|
||||
"type": "date",
|
||||
"width": "220px",
|
||||
"title": "시작일(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "end_dt",
|
||||
"type": "date",
|
||||
"width": "220px",
|
||||
"title": "종료일(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "detail",
|
||||
"type": "button",
|
||||
"width": "120px",
|
||||
"title": "상세보기",
|
||||
"text": "상세보기",
|
||||
"action": {
|
||||
"type": "modal",
|
||||
"target": "detailModal",
|
||||
"dataParam": {
|
||||
"id": "id"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "history",
|
||||
"type": "button",
|
||||
"width": "120px",
|
||||
"title": "히스토리",
|
||||
"text": "히스토리"
|
||||
}
|
||||
],
|
||||
"sort": {
|
||||
"defaultColumn": "row_num",
|
||||
"defaultDirection": "desc"
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
"searchType": "ID",
|
||||
"searchData": "",
|
||||
"historyType": "",
|
||||
"startDate": "",
|
||||
"endDate": "",
|
||||
"startDate": "today",
|
||||
"endDate": "today",
|
||||
"orderBy": "DESC",
|
||||
"pageSize": 50,
|
||||
"currentPage": 1
|
||||
@@ -37,7 +37,10 @@
|
||||
"startDateId": "startDate",
|
||||
"endDateId": "endDate",
|
||||
"label": "기간",
|
||||
"col": 2
|
||||
"col": 2,
|
||||
"width": "500px",
|
||||
"format": "YYYY-MM-DD HH:mm:ss",
|
||||
"showTime": true
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
48
src/assets/data/pages/rankingSearch.json
Normal file
48
src/assets/data/pages/rankingSearch.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"initialSearchParams": {
|
||||
"searchData": "",
|
||||
"status": "ALL",
|
||||
"startDate": "",
|
||||
"endDate": "",
|
||||
"orderBy": "DESC",
|
||||
"pageSize": 50,
|
||||
"currentPage": 1
|
||||
},
|
||||
|
||||
"searchFields": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchData",
|
||||
"label": "제목",
|
||||
"placeholder": "제목 입력",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "status",
|
||||
"label": "상태",
|
||||
"optionsRef": "opMenuBannerStatus",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "period",
|
||||
"startDateId": "startDate",
|
||||
"endDateId": "endDate",
|
||||
"label": "기간",
|
||||
"col": 1
|
||||
}
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"endpointName": "RankingScheduleView",
|
||||
"loadOnMount": true,
|
||||
"paramTransforms": [
|
||||
{"param": "startDate", "transform": "toISOString"},
|
||||
{"param": "endDate", "transform": "toISOString"}
|
||||
],
|
||||
"pageField": "page_no",
|
||||
"pageSizeField": "page_size",
|
||||
"orderField": "orderBy"
|
||||
}
|
||||
}
|
||||
140
src/assets/data/pages/rankingTable.json
Normal file
140
src/assets/data/pages/rankingTable.json
Normal file
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"id": "rankingTable",
|
||||
"selection": {
|
||||
"type": "single",
|
||||
"idField": "id"
|
||||
},
|
||||
"header": {
|
||||
"countType": "total",
|
||||
"orderType": "desc",
|
||||
"pageType": "default",
|
||||
"buttons": [
|
||||
{
|
||||
"id": "delete",
|
||||
"text": "선택 삭제",
|
||||
"theme": "line",
|
||||
"disableWhen": "noSelection",
|
||||
"requiredAuth": "rankingDelete",
|
||||
"action": "delete"
|
||||
},
|
||||
{
|
||||
"id": "register",
|
||||
"text": "랭킹 스케줄 등록",
|
||||
"theme": "primary",
|
||||
"requiredAuth": "rankingUpdate",
|
||||
"action": "regist"
|
||||
}
|
||||
]
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "checkbox",
|
||||
"type": "checkbox",
|
||||
"width": "40px",
|
||||
"title": ""
|
||||
},
|
||||
{
|
||||
"id": "row_num",
|
||||
"type": "text",
|
||||
"width": "70px",
|
||||
"title": "번호"
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"type": "status",
|
||||
"width": "100px",
|
||||
"title": "상태",
|
||||
"option_name": "opCommonStatus"
|
||||
},
|
||||
{
|
||||
"id": "title",
|
||||
"type": "text",
|
||||
"title": "제목"
|
||||
},
|
||||
{
|
||||
"id": "meta_id",
|
||||
"type": "text",
|
||||
"title": "랭킹모드",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"id": "event_action_id",
|
||||
"type": "text",
|
||||
"title": "이벤트 액션 그룹",
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"id": "start_dt",
|
||||
"type": "date",
|
||||
"width": "200px",
|
||||
"title": "시작일(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "end_dt",
|
||||
"type": "date",
|
||||
"width": "200px",
|
||||
"title": "종료일(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "base_dt",
|
||||
"type": "date",
|
||||
"width": "200px",
|
||||
"title": "기준일(KST)",
|
||||
"format": {
|
||||
"type": "function",
|
||||
"name": "convertKTC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "refresh_interval",
|
||||
"type": "text",
|
||||
"width": "120px",
|
||||
"title": "새로고침 주기"
|
||||
},
|
||||
{
|
||||
"id": "initialization_interval",
|
||||
"type": "text",
|
||||
"width": "120px",
|
||||
"title": "초기화 주기"
|
||||
},
|
||||
{
|
||||
"id": "snapshot_interval",
|
||||
"type": "text",
|
||||
"width": "120px",
|
||||
"title": "스냅샷 주기"
|
||||
},
|
||||
{
|
||||
"id": "detail",
|
||||
"type": "button",
|
||||
"width": "120px",
|
||||
"title": "상세보기",
|
||||
"text": "상세보기",
|
||||
"action": {
|
||||
"type": "modal",
|
||||
"target": "detailModal",
|
||||
"dataParam": {
|
||||
"id": "id"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "history",
|
||||
"type": "button",
|
||||
"width": "120px",
|
||||
"title": "히스토리",
|
||||
"text": "히스토리"
|
||||
}
|
||||
],
|
||||
"sort": {
|
||||
"defaultColumn": "row_num",
|
||||
"defaultDirection": "desc"
|
||||
}
|
||||
}
|
||||
63
src/assets/data/pages/rewardEventSearch.json
Normal file
63
src/assets/data/pages/rewardEventSearch.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"initialSearchParams": {
|
||||
"searchTitle": "",
|
||||
"searchContent": "",
|
||||
"status": "ALL",
|
||||
"startDate": "",
|
||||
"endDate": "",
|
||||
"orderBy": "DESC",
|
||||
"pageSize": 50,
|
||||
"currentPage": 1
|
||||
},
|
||||
|
||||
"searchFields": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchTitle",
|
||||
"label": "우편 제목",
|
||||
"placeholder": "제목 입력",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "period",
|
||||
"startDateId": "startDate",
|
||||
"endDateId": "endDate",
|
||||
"label": "조회 일자",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "searchContent",
|
||||
"label": "우편 내용",
|
||||
"placeholder": "우편 내용(공백으로 구분)",
|
||||
"width": "300px",
|
||||
"col": 1
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "status",
|
||||
"label": "이벤트 상태",
|
||||
"optionsRef": "eventStatus",
|
||||
"col": 2
|
||||
}
|
||||
],
|
||||
|
||||
"apiInfo": {
|
||||
"functionName": "RewardEventView",
|
||||
"loadOnMount": true,
|
||||
"paramsMapping": [
|
||||
"searchTitle",
|
||||
"searchContent",
|
||||
"status",
|
||||
{"param": "startDate", "transform": "toISOString"},
|
||||
{"param": "endDate", "transform": "toISOString"},
|
||||
"orderBy",
|
||||
"pageSize",
|
||||
"currentPage"
|
||||
],
|
||||
"pageField": "currentPage",
|
||||
"pageSizeField": "pageSize",
|
||||
"orderField": "orderBy"
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,15 @@ export const authType = {
|
||||
menuBannerRead: 50,
|
||||
menuBannerUpdate: 51,
|
||||
menuBannerDelete: 52,
|
||||
itemDictionaryRead: 53,
|
||||
rankManagerRead: 54,
|
||||
rankManagerUpdate: 55,
|
||||
rankingRead: 56,
|
||||
rankingUpdate: 57,
|
||||
rankingDelete: 58,
|
||||
worldEventRead: 59,
|
||||
worldEventUpdate: 60,
|
||||
worldEventDelete: 61,
|
||||
|
||||
|
||||
levelReader: 999,
|
||||
@@ -75,20 +84,6 @@ export const adminAuthLevel = {
|
||||
DEVELOPER: "Developer",
|
||||
}
|
||||
|
||||
export const TabUserList = [
|
||||
{ title: '기본정보' },
|
||||
{ title: '아바타' },
|
||||
{ title: '의상' },
|
||||
{ title: '도구' },
|
||||
{ title: '인벤토리' },
|
||||
{ title: '우편' },
|
||||
{ title: '마이홈' },
|
||||
{ title: '친구목록' },
|
||||
{ title: '타투' },
|
||||
{ title: '퀘스트' },
|
||||
// { title: '클레임' },
|
||||
];
|
||||
|
||||
export const ivenTabType = {
|
||||
CLOTH: "cloth",
|
||||
PROP: "prop",
|
||||
|
||||
@@ -93,12 +93,6 @@ const UserInfoTable = styled.table`
|
||||
font-size: 13px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
tr:first-child {
|
||||
th,
|
||||
td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
th,
|
||||
td {
|
||||
height: 36px;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
|
||||
import { UserTattooView } from '../../apis/Users';
|
||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||
|
||||
const UserTatttooInfo = ({ userInfo }) => {
|
||||
const UserTattooInfo = ({ userInfo }) => {
|
||||
const [dataList, setDataList] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -62,7 +62,7 @@ const UserTatttooInfo = ({ userInfo }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTatttooInfo;
|
||||
export default UserTattooInfo;
|
||||
|
||||
const UserDefaultTable = styled.table`
|
||||
border: 1px solid #e8eaec;
|
||||
|
||||
@@ -32,6 +32,18 @@ const ImageUploadBtn = ({ disabled,
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const koreanRegex = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;
|
||||
if (koreanRegex.test(file.name)) {
|
||||
showToast('FILE_KOREAN_NAME_WARNING', {
|
||||
type: alertTypes.warning
|
||||
});
|
||||
if (document.querySelector('#fileinput')) {
|
||||
document.querySelector('#fileinput').value = '';
|
||||
}
|
||||
onFileDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// 이미지 파일 확장자 체크
|
||||
const fileExt = file.name.split('.').pop().toLowerCase();
|
||||
if (fileExt !== 'png' && fileExt !== 'jpg' && fileExt !== 'jpeg') {
|
||||
|
||||
@@ -1,79 +1,29 @@
|
||||
import React from 'react';
|
||||
import DatePickerComponent from './DatePickerComponent';
|
||||
import { DatePickerWrapper } from '../../../styles/Components';
|
||||
import {
|
||||
FormRowGroup,
|
||||
FormLabel,
|
||||
DateContainer,
|
||||
DateTimeWrapper,
|
||||
DateTimeGroup,
|
||||
} from '../../../styles/ModuleComponents';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DatePicker } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const DateRangePicker = ({
|
||||
label,
|
||||
startDate,
|
||||
endDate,
|
||||
onStartDateChange,
|
||||
onEndDateChange,
|
||||
pastDate = new Date(),
|
||||
disabled,
|
||||
startLabel = '시작 일자',
|
||||
endLabel = '종료 일자',
|
||||
setAlert,
|
||||
value,
|
||||
onChange,
|
||||
format,
|
||||
showTime = true,
|
||||
size = 'middle',
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleStartDate = (date) => {
|
||||
const newDate = new Date(date);
|
||||
onStartDateChange(newDate);
|
||||
};
|
||||
|
||||
const handleEndDate = (date) => {
|
||||
let newDate = new Date(date);
|
||||
|
||||
if (startDate && newDate < startDate) {
|
||||
setAlert(t('DATE_START_DIFF_END'));
|
||||
newDate = new Date(startDate);
|
||||
}
|
||||
|
||||
onEndDateChange(newDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormRowGroup>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<DateTimeWrapper>
|
||||
<DateTimeGroup>
|
||||
<DateContainer>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name={startLabel}
|
||||
handleSelectedDate={handleStartDate}
|
||||
selectedDate={startDate}
|
||||
pastDate={pastDate}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
</DateContainer>
|
||||
</DateTimeGroup>
|
||||
|
||||
<DateTimeGroup>
|
||||
<DateContainer>
|
||||
<DatePickerWrapper>
|
||||
<DatePickerComponent
|
||||
name={endLabel}
|
||||
handleSelectedDate={handleEndDate}
|
||||
selectedDate={endDate}
|
||||
pastDate={pastDate}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</DatePickerWrapper>
|
||||
</DateContainer>
|
||||
</DateTimeGroup>
|
||||
</DateTimeWrapper>
|
||||
</FormRowGroup>
|
||||
<RangePicker
|
||||
showTime={showTime}
|
||||
value={value ? [dayjs(value[0]), dayjs(value[1])] : [null, null]}
|
||||
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
||||
onChange={onChange}
|
||||
placeholder={['시작 일시', '종료 일시']}
|
||||
size={size}
|
||||
allowClear={false}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default DateRangePicker;
|
||||
205
src/components/common/Header/Navi_bak.js
Normal file
205
src/components/common/Header/Navi_bak.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import arrowIcon from '../../../assets/img/icon/icon-tab.png';
|
||||
import styled from 'styled-components';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { authList } from '../../../store/authList';
|
||||
import Modal from '../modal/Modal';
|
||||
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Button from '../button/Button';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AuthInfo } from '../../../apis';
|
||||
import { getMenuConfig } from '../../../utils';
|
||||
import { adminAuthLevel } from '../../../assets/data/types';
|
||||
|
||||
const Navi = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const menu = getMenuConfig(userInfo);
|
||||
|
||||
const [modalClose, setModalClose] = useState('hidden');
|
||||
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleToken = async () => {
|
||||
const tokenStatus = await AuthInfo(token);
|
||||
|
||||
tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleToken();
|
||||
}, [token]);
|
||||
|
||||
const handleTopMenu = e => {
|
||||
e.preventDefault();
|
||||
e.target.classList.toggle('active');
|
||||
};
|
||||
|
||||
const handleLink = e => {
|
||||
let topActive = document.querySelectorAll('nav .active');
|
||||
let currentTopMenu = e.target.closest('ul').previousSibling;
|
||||
for (let i = 0; i < topActive.length; i++) {
|
||||
if (topActive[i] !== currentTopMenu) {
|
||||
topActive[i].classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
handleToken();
|
||||
};
|
||||
|
||||
// 등록 완료 모달
|
||||
const handleModalClose = () => {
|
||||
if (modalClose === 'hidden') {
|
||||
setModalClose('view');
|
||||
} else {
|
||||
setModalClose('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// 로그아웃 안내 모달
|
||||
const handleConfirmClose = () => {
|
||||
if (logoutModalClose === 'hidden') {
|
||||
setLogoutModalClose('view');
|
||||
} else {
|
||||
setLogoutModalClose('hidden');
|
||||
sessionStorage.removeItem('token');
|
||||
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
const isClickable = (submenu) => {
|
||||
switch (userInfo.auth_level_type) {
|
||||
case adminAuthLevel.DEVELOPER:
|
||||
case adminAuthLevel.READER:
|
||||
case adminAuthLevel.MASTER:
|
||||
return true;
|
||||
default:
|
||||
return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<ul>
|
||||
{menu.map((item, idx) => {
|
||||
return (
|
||||
<li key={idx}>
|
||||
{item.access && (
|
||||
<TopMenu to={item.link} onClick={handleTopMenu}>
|
||||
{item.title}
|
||||
</TopMenu>
|
||||
)}
|
||||
<SubMenu>
|
||||
{item.submenu && userInfo &&
|
||||
item.submenu.map((submenu, idx) => {
|
||||
return (
|
||||
<SubMenuItem key={idx} $isclickable={isClickable(submenu) ? 'true' : 'false'}>
|
||||
<NavLink
|
||||
to={isClickable(submenu) ? submenu.link : location.pathname}
|
||||
onClick={e => {
|
||||
isClickable(submenu) ? handleLink(e) : handleModalClose();
|
||||
}}>
|
||||
{submenu.title}
|
||||
</NavLink>
|
||||
</SubMenuItem>
|
||||
);
|
||||
})}
|
||||
</SubMenu>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
{/* 접근 불가 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleModalClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
해당 메뉴에 대한 조회 권한이 없습니다.
|
||||
<br />
|
||||
권한 등급을 변경 후 다시 이용해주세요.
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleModalClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
|
||||
{/* 로그아웃 안내 모달 */}
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={logoutModalClose}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={handleConfirmClose} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">로그아웃 되었습니다.</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmClose} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navi;
|
||||
|
||||
const TopMenu = styled(NavLink)`
|
||||
padding: 16px 30px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #888;
|
||||
position: relative;
|
||||
color: #fff;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
background: url('${arrowIcon}') -12px 0 no-repeat;
|
||||
}
|
||||
&:hover,
|
||||
&.active {
|
||||
background: #444;
|
||||
}
|
||||
&.active ~ ul {
|
||||
display: block;
|
||||
}
|
||||
&.active:before {
|
||||
background: url('${arrowIcon}') 0 0 no-repeat;
|
||||
}
|
||||
`;
|
||||
|
||||
const SubMenu = styled.ul`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
const SubMenuItem = styled.li`
|
||||
background: #eee;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #2c2c2c;
|
||||
a {
|
||||
width: 100%;
|
||||
padding: 16px 30px;
|
||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
||||
text-align: left;
|
||||
&:hover,
|
||||
&.active {
|
||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
||||
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BackGround = styled.div`
|
||||
background: #eee2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
`;
|
||||
@@ -125,6 +125,7 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
||||
value={currentValue}
|
||||
onChange={(value) => onChange(key, value, handler)}
|
||||
placeholder={placeholder || `${label} 선택`}
|
||||
popupMatchSelectWidth={false}
|
||||
>
|
||||
{options && options.map((option) => (
|
||||
<Select.Option key={option.value} value={option.value}>
|
||||
|
||||
55
src/components/common/Layout/DetailInfo.js
Normal file
55
src/components/common/Layout/DetailInfo.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Card, Descriptions } from 'antd';
|
||||
import { getFieldLabel } from '../../../utils';
|
||||
|
||||
const InfoCard = ({
|
||||
title,
|
||||
data,
|
||||
keyPrefix = 'item',
|
||||
size = 'small',
|
||||
column = 1,
|
||||
bordered = true,
|
||||
type = 'inner'
|
||||
}) => {
|
||||
|
||||
if (!data ||
|
||||
typeof data !== 'object' ||
|
||||
Object.keys(data).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = Object.entries(data).map(([key, value]) => ({
|
||||
key: `${keyPrefix}-${key}`,
|
||||
label: getFieldLabel(key),
|
||||
children: (() => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(', ');
|
||||
}
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
return String(value);
|
||||
})()
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card
|
||||
size={size}
|
||||
title={title}
|
||||
type={type}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Descriptions
|
||||
bordered={bordered}
|
||||
column={column}
|
||||
size={size}
|
||||
items={items}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoCard;
|
||||
@@ -4,5 +4,6 @@ import MainLayout from './MainLayout';
|
||||
import AnimatedPageWrapper from './AnimatedPageWrapper';
|
||||
import DetailGrid from './DetailGrid';
|
||||
import DetailLayout from './DetailLayout';
|
||||
import InfoCard from './DetailInfo'
|
||||
|
||||
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout };
|
||||
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout, InfoCard };
|
||||
|
||||
@@ -26,7 +26,7 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
|
||||
</SearchRow>
|
||||
)}
|
||||
{isSearch &&
|
||||
<SearchRow>
|
||||
<SearchRow direction={direction}>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
|
||||
<Button theme="reset" handleClick={onReset} type="button" />
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const DotsButton = styled.button`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 점 스타일링 */
|
||||
.dot {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: #333;
|
||||
margin: 2px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const VerticalDotsButton = ({ text, type = 'button', errorMessage, handleClick, size, width, height, borderColor, disabled, name }) => {
|
||||
|
||||
return (
|
||||
<DotsButton
|
||||
onSubmit={e => e.preventDefault()}
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
bordercolor={borderColor}
|
||||
width={width}
|
||||
height={height}
|
||||
name={name}
|
||||
>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
</DotsButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerticalDotsButton;
|
||||
@@ -1,43 +1,79 @@
|
||||
import React from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
// 통합된 애니메이션 탭 컴포넌트
|
||||
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||
const AnimatedTabs = ({ items, activeKey, onChange, tabPosition = 'center' }) => {
|
||||
// 각 항목의 children을 애니메이션 래퍼로 감싸기
|
||||
const tabItems = items.map(item => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
children: (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeKey}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 30
|
||||
}}
|
||||
>
|
||||
{item.children}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
<AnimatedContent key={`content-${item.key}`}>
|
||||
{item.children}
|
||||
</AnimatedContent>
|
||||
)
|
||||
|
||||
// children: (
|
||||
// <AnimatePresence mode="wait">
|
||||
// <motion.div
|
||||
// key={activeKey}
|
||||
// initial={{ opacity: 0, x: 50 }}
|
||||
// animate={{ opacity: 1, x: 0 }}
|
||||
// exit={{ opacity: 0, x: -50 }}
|
||||
// transition={{
|
||||
// type: "spring",
|
||||
// stiffness: 300,
|
||||
// damping: 30
|
||||
// }}
|
||||
// >
|
||||
// {item.children}
|
||||
// </motion.div>
|
||||
// </AnimatePresence>
|
||||
// )
|
||||
}));
|
||||
|
||||
return (
|
||||
<StyledTabs
|
||||
type="card"
|
||||
activeKey={activeKey}
|
||||
onChange={onChange}
|
||||
centered={true}
|
||||
centered={tabPosition === 'center'}
|
||||
items={tabItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const slideInRight = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const AnimatedContent = styled.div`
|
||||
animation: ${slideInRight} 0.3s ease-out;
|
||||
|
||||
/* 대안으로 더 부드러운 페이드 인 효과 */
|
||||
/* animation: ${fadeIn} 0.4s ease-out; */
|
||||
`;
|
||||
|
||||
|
||||
// const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||
// return (
|
||||
// <StyledTabs
|
||||
@@ -76,16 +112,12 @@ const StyledTabs = styled(Tabs)`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
//align-items: center;
|
||||
|
||||
.ant-tabs-nav {
|
||||
margin-bottom: 16px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.ant-tabs-nav-wrap {
|
||||
justify-content: center;
|
||||
}
|
||||
//.ant-tabs-nav {
|
||||
// margin-bottom: 16px;
|
||||
// width: 80%;
|
||||
//}
|
||||
|
||||
.ant-tabs-tab {
|
||||
padding: 8px 16px;
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const AIMessageInput = ({ onSendMessage }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const textareaRef = useRef(null);
|
||||
const modalRef = useRef(null);
|
||||
|
||||
// 텍스트 영역 높이 자동 조절
|
||||
useEffect(() => {
|
||||
if (textareaRef.current && isOpen) {
|
||||
textareaRef.current.style.height = 'auto';
|
||||
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
|
||||
}
|
||||
}, [message, isOpen]);
|
||||
|
||||
// 모달 외부 클릭시 닫기
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
// 모달 열기
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
// 모달이 열린 후 텍스트 영역에 포커스
|
||||
setTimeout(() => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 모달 닫기
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
setMessage('');
|
||||
};
|
||||
|
||||
// 메시지 전송 처리
|
||||
const handleSendMessage = () => {
|
||||
if (message.trim() && !isSending) {
|
||||
setIsSending(true);
|
||||
|
||||
// 메시지 전송 처리
|
||||
if (onSendMessage) {
|
||||
onSendMessage(message);
|
||||
}
|
||||
|
||||
// 입력 초기화 및 상태 업데이트
|
||||
setMessage('');
|
||||
setIsSending(false);
|
||||
|
||||
// 모달 닫기
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
// 엔터 키 처리 (Shift+Enter 줄바꿈, Enter 전송)
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 메뉴 버튼 */}
|
||||
<MenuButton onClick={openModal}>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
<div className="dot"></div>
|
||||
</MenuButton>
|
||||
|
||||
{/* 모달 오버레이 */}
|
||||
<ModalOverlay isOpen={isOpen}>
|
||||
<InputContainer ref={modalRef} isOpen={isOpen}>
|
||||
<MessageInput
|
||||
ref={textareaRef}
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="메시지를 입력하세요..."
|
||||
rows={1}
|
||||
/>
|
||||
|
||||
<SendButton
|
||||
onClick={handleSendMessage}
|
||||
disabled={!message.trim() || isSending}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
||||
</svg>
|
||||
</SendButton>
|
||||
</InputContainer>
|
||||
</ModalOverlay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIMessageInput;
|
||||
|
||||
const ModalOverlay = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: ${props => props.isOpen ? 'flex' : 'none'};
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
// 메인 컨테이너
|
||||
const InputContainer = styled.div`
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
animation: ${props => props.isOpen ? 'slideUp 0.3s ease-out' : 'none'};
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// 메시지 입력 영역
|
||||
const MessageInput = styled.textarea`
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
max-height: 200px;
|
||||
padding: 16px 60px 16px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
|
||||
&::placeholder {
|
||||
color: #9e9ea7;
|
||||
}
|
||||
`;
|
||||
|
||||
// 전송 버튼
|
||||
const SendButton = styled.button`
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background-color: #5436DA;
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #4527D0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #DADCE0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: white;
|
||||
}
|
||||
`;
|
||||
|
||||
// 메뉴 버튼 (세로 점 세개)
|
||||
const MenuButton = styled.button`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: #666;
|
||||
margin: 2px 0;
|
||||
}
|
||||
`;
|
||||
311
src/components/modal/EventModal.js
Normal file
311
src/components/modal/EventModal.js
Normal file
@@ -0,0 +1,311 @@
|
||||
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import {
|
||||
FormStatusBar,
|
||||
FormStatusLabel,
|
||||
FormStatusWarning,
|
||||
FormButtonContainer,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { DetailLayout, Modal} from '../common';
|
||||
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||
import { convertKTCDate } from '../../utils';
|
||||
import {
|
||||
opCommonStatus,
|
||||
} from '../../assets/data/options';
|
||||
import { alertTypes, commonStatus } from '../../assets/data/types';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { EventModify, EventSingleRegist } from '../../apis';
|
||||
|
||||
const EventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, eventActionData }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { showToast, showModal } = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [resultData, setResultData] = useState(initData);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
id: content.id,
|
||||
title: content.title,
|
||||
global_event_action_id: content.global_event_action_id,
|
||||
personal_event_action_id: content.personal_event_action_id,
|
||||
status: content.status,
|
||||
max_point: content.max_point,
|
||||
start_dt: convertKTCDate(content.start_dt),
|
||||
end_dt: convertKTCDate(content.end_dt)
|
||||
});
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
const opEventActionMode = useMemo(() => {
|
||||
return eventActionData?.map(item => ({
|
||||
value: item.id,
|
||||
name: `${item.description}(${item.id})`
|
||||
})) || [];
|
||||
}, [eventActionData]);
|
||||
|
||||
const handleReset = () => {
|
||||
setDetailData({});
|
||||
setResultData(initData);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||
const startDt = resultData.start_dt;
|
||||
const endDt = resultData.end_dt;
|
||||
// if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||
// return;
|
||||
// }
|
||||
// if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
|
||||
// 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;
|
||||
// }
|
||||
|
||||
showModal(isView('modify') ? 'BATTLE_EVENT_UPDATE_CONFIRM' : 'BATTLE_EVENT_REGIST_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('registConfirm')
|
||||
});
|
||||
|
||||
break;
|
||||
case "registConfirm":
|
||||
|
||||
const params = {
|
||||
...resultData
|
||||
};
|
||||
|
||||
if(isView('modify')){
|
||||
await withLoading( async () => {
|
||||
return await EventModify(token, content?.id, params);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleReset();
|
||||
});
|
||||
}
|
||||
else{
|
||||
await withLoading( async () => {
|
||||
return await EventSingleRegist(token, params);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleReset();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.start_dt !== ''
|
||||
&& resultData.end_dt !== ''
|
||||
&& resultData.title !== ''
|
||||
&& resultData.global_event_action_id > 0
|
||||
&& resultData.personal_event_action_id > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === commonStatus.wait);
|
||||
case "registry":
|
||||
case "mode":
|
||||
return modalType === TYPE_REGISTRY
|
||||
case "start_dt":
|
||||
case "end_dt":
|
||||
case "max_point":
|
||||
case "name":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === commonStatus.wait));
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status !== commonStatus.wait);
|
||||
}
|
||||
}
|
||||
|
||||
const itemGroups = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
row: 0,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'text',
|
||||
key: 'title',
|
||||
label: '이벤트명',
|
||||
disabled: !isView('name'),
|
||||
width: '300px',
|
||||
},
|
||||
{
|
||||
row: 1,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'date',
|
||||
key: 'start_dt',
|
||||
label: '시작일시',
|
||||
disabled: !isView('start_dt'),
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
width: '200px',
|
||||
showTime: true
|
||||
},
|
||||
{
|
||||
row: 1,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'date',
|
||||
key: 'end_dt',
|
||||
label: '종료일시',
|
||||
disabled: !isView('end_dt'),
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
width: '200px',
|
||||
showTime: true
|
||||
},
|
||||
{
|
||||
row: 4,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'select',
|
||||
key: 'global_event_action_id',
|
||||
label: '기여도 이벤트 모드',
|
||||
disabled: !isView('mode'),
|
||||
width: '150px',
|
||||
options: opEventActionMode
|
||||
},
|
||||
{
|
||||
row: 4,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'number',
|
||||
key: 'max_point',
|
||||
label: '기여도 목표점수',
|
||||
disabled: !isView('max_point'),
|
||||
width: '150px'
|
||||
},
|
||||
{
|
||||
row: 5,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'select',
|
||||
key: 'personal_event_action_id',
|
||||
label: '개인제작 이벤트 모드',
|
||||
disabled: !isView('mode'),
|
||||
width: '150px',
|
||||
options: opEventActionMode
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "통합 이벤트 등록" : isView('modify') ? "통합 이벤트 수정" : "통합 이벤트 상세"}</Title>
|
||||
<DetailLayout
|
||||
itemGroups={itemGroups}
|
||||
formData={resultData}
|
||||
onChange={setResultData}
|
||||
disabled={false}
|
||||
columnCount={4}
|
||||
/>
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {opCommonStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('EVENT_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
handleClick={() => showModal('CANCEL_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleReset()
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
title: '',
|
||||
start_dt: '',
|
||||
end_dt: '',
|
||||
global_event_action_id: '',
|
||||
personal_event_action_id: '',
|
||||
max_point: 0
|
||||
}
|
||||
|
||||
export default EventModal;
|
||||
|
||||
379
src/components/modal/RankingModal.js
Normal file
379
src/components/modal/RankingModal.js
Normal file
@@ -0,0 +1,379 @@
|
||||
import React, { useState, Fragment, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Button from '../common/button/Button';
|
||||
|
||||
import {
|
||||
Title,
|
||||
BtnWrapper,
|
||||
SearchBarAlert, SelectInput,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import {
|
||||
FormInput,
|
||||
FormLabel,
|
||||
MessageWrapper,
|
||||
FormRowGroup,
|
||||
FormStatusBar,
|
||||
FormStatusLabel,
|
||||
FormStatusWarning,
|
||||
FormButtonContainer,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { DetailLayout, Modal, SingleDatePicker, SingleTimePicker } from '../common';
|
||||
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||
import { convertKTCDate } from '../../utils';
|
||||
import {
|
||||
battleEventHotTime,
|
||||
battleEventRoundCount,
|
||||
battleEventStatus,
|
||||
battleRepeatType, opCommonStatus,
|
||||
} from '../../assets/data/options';
|
||||
import { BattleEventModify, BattleEventSingleRegist } from '../../apis/Battle';
|
||||
import { alertTypes, battleEventStatusType, commonStatus } from '../../assets/data/types';
|
||||
import { isValidDayRange } from '../../utils/date';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { RankingScheduleModify, RankingScheduleSingleRegist } from '../../apis';
|
||||
|
||||
const RankingModal = ({ modalType, detailView, handleDetailView, content, setDetailData, rankingData, eventActionData }) => {
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { showToast, showModal } = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
|
||||
const [isNullValue, setIsNullValue] = useState(false);
|
||||
const [resultData, setResultData] = useState(initData);
|
||||
|
||||
useEffect(() => {
|
||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||
setResultData({
|
||||
guid: content.guid,
|
||||
id: content.id,
|
||||
title: content.title,
|
||||
meta_id: content.meta_id,
|
||||
event_action_id: content.event_action_id,
|
||||
refresh_interval: content.refresh_interval,
|
||||
initialization_interval: content.initialization_interval,
|
||||
snapshot_interval: content.snapshot_interval,
|
||||
status: content.status,
|
||||
start_dt: convertKTCDate(content.start_dt),
|
||||
end_dt: convertKTCDate(content.end_dt),
|
||||
base_dt: convertKTCDate(content.base_dt),
|
||||
});
|
||||
}
|
||||
}, [modalType, content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkCondition()) {
|
||||
setIsNullValue(false);
|
||||
} else {
|
||||
setIsNullValue(true);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
const opRankingMode = useMemo(() => {
|
||||
return rankingData?.map(item => ({
|
||||
value: item.id,
|
||||
name: `${item.desc}(${item.id})`
|
||||
})) || [];
|
||||
}, [rankingData]);
|
||||
|
||||
const opEventActionMode = useMemo(() => {
|
||||
return eventActionData?.map(item => ({
|
||||
value: item.id,
|
||||
name: `${item.description}(${item.id})`
|
||||
})) || [];
|
||||
}, [eventActionData]);
|
||||
|
||||
const handleReset = () => {
|
||||
setDetailData({});
|
||||
setResultData(initData);
|
||||
handleDetailView();
|
||||
}
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!checkCondition()) return;
|
||||
|
||||
// const minAllowedTime = new Date(new Date().getTime() + 10 * 60000);
|
||||
// const startDt = resultData.event_start_dt;
|
||||
// const endDt = resultData.event_end_dt;
|
||||
// if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
|
||||
// showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
|
||||
// return;
|
||||
// }
|
||||
// if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
|
||||
// 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;
|
||||
// }
|
||||
|
||||
showModal(isView('modify') ? 'SCHEDULE_UPDATE_CONFIRM' : 'SCHEDULE_REGIST_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('registConfirm')
|
||||
});
|
||||
|
||||
break;
|
||||
case "registConfirm":
|
||||
|
||||
const params = {
|
||||
...resultData
|
||||
};
|
||||
|
||||
if(isView('modify')){
|
||||
await withLoading( async () => {
|
||||
return await RankingScheduleModify(token, content?.id, params);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('UPDATE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleReset();
|
||||
});
|
||||
}
|
||||
else{
|
||||
await withLoading( async () => {
|
||||
return await RankingScheduleSingleRegist(token, params);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('REGIST_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleReset();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const checkCondition = () => {
|
||||
return (
|
||||
resultData.start_dt !== ''
|
||||
&& resultData.end_dt !== ''
|
||||
&& resultData.base_dt !== ''
|
||||
&& resultData.meta_id > 0
|
||||
&& resultData.event_action_id > 0
|
||||
&& resultData.title !== ''
|
||||
&& resultData.refresh_interval > 0
|
||||
&& resultData.initialization_interval > 0
|
||||
&& resultData.snapshot_interval > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isView = (label) => {
|
||||
switch (label) {
|
||||
case "modify":
|
||||
return modalType === TYPE_MODIFY && (content?.status === commonStatus.wait);
|
||||
case "registry":
|
||||
return modalType === TYPE_REGISTRY
|
||||
case "start_dt":
|
||||
case "end_dt":
|
||||
case "base_dt":
|
||||
case "name":
|
||||
case "refresh_interval":
|
||||
case "init_interval":
|
||||
case "snapshot_interval":
|
||||
case "mode":
|
||||
case "eventActionMode":
|
||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === commonStatus.wait));
|
||||
default:
|
||||
return modalType === TYPE_MODIFY && (content?.status !== commonStatus.wait);
|
||||
}
|
||||
}
|
||||
|
||||
const itemGroups = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
row: 0,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'text',
|
||||
key: 'title',
|
||||
label: '스케줄러명',
|
||||
disabled: !isView('name'),
|
||||
width: '300px',
|
||||
},
|
||||
{
|
||||
row: 1,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'date',
|
||||
key: 'start_dt',
|
||||
label: '시작일시',
|
||||
disabled: !isView('start_dt'),
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
width: '200px',
|
||||
showTime: true
|
||||
},
|
||||
{
|
||||
row: 1,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'date',
|
||||
key: 'end_dt',
|
||||
label: '종료일시',
|
||||
disabled: !isView('end_dt'),
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
width: '200px',
|
||||
showTime: true
|
||||
},
|
||||
{
|
||||
row: 2,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'date',
|
||||
key: 'base_dt',
|
||||
label: '기준일시',
|
||||
disabled: !isView('base_dt'),
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
width: '200px',
|
||||
showTime: true
|
||||
},
|
||||
{
|
||||
row: 2,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'number',
|
||||
key: 'refresh_interval',
|
||||
label: '새로고침 주기(분)',
|
||||
disabled: !isView('refresh_interval'),
|
||||
width: '100px',
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
row: 3,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'number',
|
||||
key: 'initialization_interval',
|
||||
label: '초기화 주기(분)',
|
||||
disabled: !isView('init_interval'),
|
||||
width: '100px',
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
row: 3,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'number',
|
||||
key: 'snapshot_interval',
|
||||
label: '스냅샷 주기(분)',
|
||||
disabled: !isView('snapshot_interval'),
|
||||
width: '100px',
|
||||
min: 0,
|
||||
},
|
||||
|
||||
{
|
||||
row: 4,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'select',
|
||||
key: 'meta_id',
|
||||
label: '랭킹 모드',
|
||||
disabled: !isView('mode'),
|
||||
width: '150px',
|
||||
options: opRankingMode
|
||||
},
|
||||
{
|
||||
row: 4,
|
||||
col: 2,
|
||||
colSpan: 2,
|
||||
type: 'select',
|
||||
key: 'event_action_id',
|
||||
label: '이벤트 액션 그룹',
|
||||
disabled: !isView('eventActionMode'),
|
||||
width: '150px',
|
||||
options: opEventActionMode
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="760px" $view={detailView}>
|
||||
<Title $align="center">{isView('registry') ? "랭킹 스케줄러 등록" : isView('modify') ? "랭킹 스케줄러 수정" : "랭킹 스케줄러 상세"}</Title>
|
||||
<DetailLayout
|
||||
itemGroups={itemGroups}
|
||||
formData={resultData}
|
||||
onChange={setResultData}
|
||||
disabled={false}
|
||||
columnCount={4}
|
||||
/>
|
||||
{!isView() && isNullValue && <SearchBarAlert $marginTop="25px" $align="right">{t('REQUIRED_VALUE_CHECK')}</SearchBarAlert>}
|
||||
<BtnWrapper $gap="10px" $marginTop="10px">
|
||||
<FormStatusBar>
|
||||
<FormStatusLabel>
|
||||
현재상태: {opCommonStatus.find(data => data.value === content?.status)?.name || "등록"}
|
||||
</FormStatusLabel>
|
||||
<FormStatusWarning>
|
||||
{isView('registry') ? '' : t('SCHEDULE_MODAL_STATUS_WARNING')}
|
||||
</FormStatusWarning>
|
||||
</FormStatusBar>
|
||||
<FormButtonContainer $gap="5px">
|
||||
{isView() ?
|
||||
<Button
|
||||
text="확인"
|
||||
name="확인버튼"
|
||||
theme="line"
|
||||
handleClick={() => handleReset()}
|
||||
/>
|
||||
:
|
||||
<>
|
||||
<Button
|
||||
text="취소"
|
||||
theme="line"
|
||||
handleClick={() => showModal('CANCEL_CONFIRM', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleReset()
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
text={isView('modify') ? "수정" : "등록"}
|
||||
name="등록버튼"
|
||||
theme={
|
||||
checkCondition()
|
||||
? 'primary'
|
||||
: 'disable'
|
||||
}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</FormButtonContainer>
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const initData = {
|
||||
guid: '',
|
||||
title: '',
|
||||
start_dt: '',
|
||||
end_dt: '',
|
||||
base_dt: '',
|
||||
refresh_interval: 60,
|
||||
initialization_interval: 0,
|
||||
snapshot_interval: 1440,
|
||||
meta_id: '',
|
||||
event_action_id: '',
|
||||
}
|
||||
|
||||
export default RankingModal;
|
||||
|
||||
540
src/components/modal/RewardEventDetailModal.js
Normal file
540
src/components/modal/RewardEventDetailModal.js
Normal file
@@ -0,0 +1,540 @@
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
|
||||
import { Input, Button as AntButton, Select, Alert, Space, Card, Row, Col } from 'antd';
|
||||
import { Title, BtnWrapper } from '../../styles/Components';
|
||||
import Button from '../common/button/Button';
|
||||
import Modal from '../common/modal/Modal';
|
||||
import { EventIsItem, RewardEventModify } from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
||||
import {
|
||||
DetailRegistInfo, DetailState
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { convertKTC, timeDiffMinute, convertKTCDate } from '../../utils';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { DetailLayout } from '../common';
|
||||
|
||||
const RewardEventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const token = sessionStorage.getItem('token');
|
||||
const {withLoading} = useLoading();
|
||||
const {showModal, showToast} = useAlert();
|
||||
|
||||
const id = content && content.id;
|
||||
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate);
|
||||
|
||||
const [activeLanguage, setActiveLanguage] = useState('KO');
|
||||
const [item, setItem] = useState('');
|
||||
const [itemCount, setItemCount] = useState(1);
|
||||
const [resource, setResource] = useState('19010001');
|
||||
const [resourceCount, setResourceCount] = useState(1);
|
||||
|
||||
const [resultData, setResultData] = useState({});
|
||||
|
||||
// 과거 판단
|
||||
const [isPast, setIsPast] = useState(false);
|
||||
const [isChanged, setIsChanged] = useState(false);
|
||||
|
||||
const [btnValidation, setBtnValidation] = useState(false);
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [itemCheckMsg, setItemCheckMsg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if(content){
|
||||
const start_dt_KTC = convertKTCDate(content.start_dt)
|
||||
const end_dt_KTC = convertKTCDate(content.end_dt)
|
||||
|
||||
setResultData({
|
||||
start_dt: start_dt_KTC,
|
||||
end_dt: end_dt_KTC,
|
||||
event_type: content.event_type,
|
||||
mail_list: content.mail_list,
|
||||
item_list: content.item_list,
|
||||
status: content.status,
|
||||
delete_desc: content.delete_desc
|
||||
});
|
||||
|
||||
start_dt_KTC < (new Date) ? setIsPast(true) : setIsPast(false);
|
||||
content.mail_list.length === 1 && setBtnValidation(true);
|
||||
}
|
||||
|
||||
setItem('');
|
||||
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!updateAuth || isPast){
|
||||
setIsReadOnly(true);
|
||||
}else{
|
||||
setIsReadOnly(false);
|
||||
}
|
||||
}, [updateAuth, isPast]);
|
||||
|
||||
useEffect(() => {
|
||||
setItemCheckMsg('');
|
||||
}, [item]);
|
||||
|
||||
const getLanguageTabItems = () => {
|
||||
return resultData.mail_list?.map(mail => ({
|
||||
key: mail.language,
|
||||
label: mail.language,
|
||||
children: (
|
||||
<div style={{ padding: '10px', minHeight: '400px', height: 'auto' }}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Col span={24}>
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '8px',
|
||||
fontWeight: 'bold',
|
||||
color: 'rgba(0, 0, 0, 0.85)',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
제목 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={mail.title || ''}
|
||||
placeholder="우편 제목을 입력하세요"
|
||||
maxLength={30}
|
||||
readOnly={isReadOnly}
|
||||
onChange={(e) => updateMailData(mail.language, 'title', e.target.value.trimStart())}
|
||||
showCount
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div>
|
||||
<label style={{
|
||||
display: 'block',
|
||||
marginBottom: '8px',
|
||||
fontWeight: 'bold',
|
||||
color: 'rgba(0, 0, 0, 0.85)',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
내용 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||
</label>
|
||||
<Input.TextArea
|
||||
value={mail.content || ''}
|
||||
placeholder="우편 내용을 입력하세요"
|
||||
readOnly={isReadOnly}
|
||||
rows={8}
|
||||
maxLength={2000}
|
||||
showCount
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length > 2000) return;
|
||||
updateMailData(mail.language, 'content', e.target.value.trimStart());
|
||||
}}
|
||||
style={{
|
||||
resize: 'vertical',
|
||||
minHeight: '200px',
|
||||
maxHeight: '400px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
),
|
||||
closable: resultData.mail_list?.length > 1 && !isReadOnly, // 마지막 하나가 아니고 읽기전용이 아닐 때만 삭제 가능
|
||||
})) || [];
|
||||
};
|
||||
|
||||
const updateMailData = (language, field, value) => {
|
||||
const updatedMailList = resultData.mail_list.map(mail =>
|
||||
mail.language === language
|
||||
? { ...mail, [field]: value }
|
||||
: mail
|
||||
);
|
||||
setResultData({ ...resultData, mail_list: updatedMailList });
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
const handleTabClose = (targetKey) => {
|
||||
if (resultData.mail_list.length <= 1) return;
|
||||
|
||||
const filterList = resultData.mail_list.filter(el => el.language !== targetKey);
|
||||
setResultData({ ...resultData, mail_list: filterList });
|
||||
|
||||
// 삭제된 탭이 현재 활성 탭이었다면 첫 번째 탭으로 변경
|
||||
if (activeLanguage === targetKey) {
|
||||
setActiveLanguage(filterList[0]?.language || 'KO');
|
||||
}
|
||||
setIsChanged(true);
|
||||
};
|
||||
|
||||
// 아이템 추가
|
||||
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 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) {
|
||||
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL'));
|
||||
return;
|
||||
}
|
||||
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
|
||||
|
||||
resultData.item_list.push(newItem);
|
||||
|
||||
setIsChanged(true);
|
||||
setItem('');
|
||||
setItemCount('');
|
||||
};
|
||||
|
||||
// 아이템 삭제
|
||||
const onItemRemove = id => {
|
||||
let filterList = resultData.item_list && resultData.item_list.filter(item => item !== resultData.item_list[id]);
|
||||
setIsChanged(true);
|
||||
|
||||
setResultData({ ...resultData, item_list: filterList });
|
||||
};
|
||||
|
||||
// 자원 추가
|
||||
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);
|
||||
}
|
||||
setIsChanged(true);
|
||||
setResource('')
|
||||
setResourceCount('');
|
||||
};
|
||||
|
||||
// 확인 버튼 후 다 초기화
|
||||
const handleReset = () => {
|
||||
setBtnValidation(false);
|
||||
setIsChanged(false);
|
||||
};
|
||||
|
||||
const conditionCheck = () => {
|
||||
return (
|
||||
content && content.mail_list.every(data => data.content !== '' && data.title !== '') &&
|
||||
isChanged
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "submit":
|
||||
if (!conditionCheck()) return;
|
||||
|
||||
showModal('MAIL_UPDATE_SAVE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleSubmit('updateConfirm')
|
||||
});
|
||||
break;
|
||||
case "updateConfirm":
|
||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
||||
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
|
||||
if(content.add_flag || timeDiff <= 30){
|
||||
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
withLoading( async () => {
|
||||
return await RewardEventModify(token, id, resultData);
|
||||
}).catch(error => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||
handleDetailView();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const detailState = (status) => {
|
||||
switch (status) {
|
||||
case commonStatus.wait:
|
||||
return <DetailState>대기</DetailState>;
|
||||
case commonStatus.running:
|
||||
return <DetailState>진행중</DetailState>;
|
||||
case commonStatus.finish:
|
||||
return <DetailState result={commonStatus.finish}>완료</DetailState>;
|
||||
case commonStatus.fail:
|
||||
return <DetailState result={commonStatus.fail}>실패</DetailState>;
|
||||
case commonStatus.delete:
|
||||
return <DetailState result={commonStatus.delete}>삭제</DetailState>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 아이템 목록 렌더링 컴포넌트
|
||||
const renderItemList = () => {
|
||||
return (
|
||||
<div>
|
||||
{resultData.item_list && resultData.item_list.length > 0 && (
|
||||
<Space wrap>
|
||||
{resultData.item_list.map((data, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
title={data.item_name}
|
||||
size="small"
|
||||
extra={
|
||||
!isReadOnly && (
|
||||
<AntButton
|
||||
type="text"
|
||||
danger
|
||||
size="small"
|
||||
onClick={() => onItemRemove(index)}
|
||||
>
|
||||
X
|
||||
</AntButton>
|
||||
)
|
||||
}
|
||||
style={{ minWidth: '150px' }}
|
||||
>
|
||||
<div>
|
||||
<div>{data.item}</div>
|
||||
<div>수량: {data.item_cnt}</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 아이템 추가 컴포넌트
|
||||
const renderItemAdd = () => {
|
||||
return (
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input
|
||||
placeholder="Item Meta id 입력"
|
||||
value={item}
|
||||
onChange={(e) => setItem(e.target.value.trimStart())}
|
||||
disabled={isReadOnly}
|
||||
style={{ width: '200px' }}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="수량"
|
||||
value={itemCount}
|
||||
onChange={(e) => setItemCount(e.target.value)}
|
||||
disabled={isReadOnly}
|
||||
style={{ width: '120px' }}
|
||||
min={1}
|
||||
/>
|
||||
<AntButton
|
||||
type="primary"
|
||||
onClick={handleItemList}
|
||||
disabled={itemCount.length === 0 || item.length === 0 || isReadOnly}
|
||||
>
|
||||
추가
|
||||
</AntButton>
|
||||
</Space.Compact>
|
||||
);
|
||||
};
|
||||
|
||||
// 자원 추가 컴포넌트
|
||||
const renderResourceAdd = () => {
|
||||
return (
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Select
|
||||
value={resource}
|
||||
onChange={setResource}
|
||||
disabled={isReadOnly}
|
||||
style={{ width: '200px' }}
|
||||
placeholder="자원 선택"
|
||||
>
|
||||
{currencyItemCode.map((data, index) => (
|
||||
<Select.Option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="수량"
|
||||
value={resourceCount}
|
||||
disabled={isReadOnly}
|
||||
onChange={(e) => setResourceCount(e.target.value)}
|
||||
style={{ width: '120px' }}
|
||||
min={1}
|
||||
/>
|
||||
<AntButton
|
||||
type="primary"
|
||||
onClick={handleResourceList}
|
||||
disabled={resourceCount.length === 0 || resource.length === 0 || isReadOnly}
|
||||
>
|
||||
추가
|
||||
</AntButton>
|
||||
</Space.Compact>
|
||||
);
|
||||
};
|
||||
|
||||
const itemGroups = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
row: 0,
|
||||
col: 0,
|
||||
colSpan: 2,
|
||||
type: 'dateRange',
|
||||
keys: {
|
||||
start: 'start_dt',
|
||||
end: 'end_dt'
|
||||
},
|
||||
label: '이벤트 기간',
|
||||
disabled: isReadOnly,
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
showTime: true,
|
||||
startLabel: '시작 일시',
|
||||
endLabel: '종료 일시'
|
||||
},
|
||||
{
|
||||
row: 0,
|
||||
col: 2,
|
||||
colSpan: 1,
|
||||
type: 'custom',
|
||||
key: 'status',
|
||||
label: '이벤트 상태',
|
||||
render: () => detailState(resultData.status)
|
||||
},
|
||||
...(resultData.status === commonStatus.delete ? [{
|
||||
row: 0,
|
||||
col: 3,
|
||||
colSpan: 1,
|
||||
type: 'display',
|
||||
key: 'delete_desc',
|
||||
label: '삭제 사유',
|
||||
value: resultData.delete_desc || ''
|
||||
}] : [{
|
||||
row: 0,
|
||||
col: 3,
|
||||
colSpan: 1,
|
||||
type: 'custom',
|
||||
key: 'empty_space',
|
||||
label: '',
|
||||
render: () => <div></div>
|
||||
}]),
|
||||
{
|
||||
row: 1,
|
||||
col: 0,
|
||||
colSpan: 4,
|
||||
type: 'tab',
|
||||
key: 'language_tabs',
|
||||
tabItems: getLanguageTabItems(),
|
||||
activeKey: activeLanguage,
|
||||
onTabChange: setActiveLanguage,
|
||||
onTabClose: handleTabClose
|
||||
},
|
||||
{
|
||||
row: 2,
|
||||
col: 0,
|
||||
colSpan: 4,
|
||||
type: 'custom',
|
||||
key: 'item_add',
|
||||
label: '아이템 추가',
|
||||
render: renderItemAdd
|
||||
},
|
||||
{
|
||||
row: 3,
|
||||
col: 0,
|
||||
colSpan: 4,
|
||||
type: 'custom',
|
||||
key: 'resource_add',
|
||||
label: '자원 추가',
|
||||
render: renderResourceAdd
|
||||
},
|
||||
{
|
||||
row: 4,
|
||||
col: 0,
|
||||
colSpan: 4,
|
||||
type: 'custom',
|
||||
key: 'item_list',
|
||||
render: renderItemList
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal min="960px" $view={detailView}>
|
||||
<Title $align="center">이벤트 상세 정보</Title>
|
||||
{content &&
|
||||
<DetailRegistInfo>
|
||||
<span>등록자 : {content.create_by}</span>
|
||||
<span>등록일 : {convertKTC(content.create_dt, false)}</span>
|
||||
{typeof content.update_by !== 'undefined' && (
|
||||
<>
|
||||
<span>수정자 : {content.update_by}</span>
|
||||
<span>수정일 : {convertKTC(content.update_dt, false)}</span>
|
||||
</>
|
||||
)}
|
||||
</DetailRegistInfo>
|
||||
}
|
||||
|
||||
<DetailLayout
|
||||
itemGroups={itemGroups}
|
||||
formData={resultData}
|
||||
onChange={setResultData}
|
||||
disabled={false}
|
||||
columnCount={4}
|
||||
/>
|
||||
|
||||
{itemCheckMsg && (
|
||||
<Alert
|
||||
message={itemCheckMsg}
|
||||
type="error"
|
||||
style={{ marginTop: '8px', width: '300px' }}
|
||||
/>
|
||||
)}
|
||||
<BtnWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||
<Button
|
||||
text="확인"
|
||||
theme="line"
|
||||
name="확인버튼"
|
||||
handleClick={() => {
|
||||
handleDetailView();
|
||||
handleReset();
|
||||
setDetailData('');
|
||||
}}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<Button
|
||||
type="submit"
|
||||
text="수정"
|
||||
id="수정버튼"
|
||||
theme={conditionCheck() ? 'primary' : 'disable'}
|
||||
handleClick={() => handleSubmit('submit')}
|
||||
/>
|
||||
)}
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RewardEventDetailModal;
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { logAction, logDomain, userSearchType2 } from '../../assets/data/options';
|
||||
import { BusinessLogList } from '../../apis/Log';
|
||||
import {SearchFilter} from '../ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { DatePicker } from 'antd';
|
||||
import DateRangePicker from '../common/Date/DateRangePicker';
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
export const useBusinessLogSearch = (token, initialPageSize) => {
|
||||
const {showToast} = useAlert();
|
||||
@@ -16,16 +20,8 @@ export const useBusinessLogSearch = (token, initialPageSize) => {
|
||||
log_action: 'None',
|
||||
log_domain: 'BASE',
|
||||
tran_id: '',
|
||||
start_dt: (() => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
return date;
|
||||
})(),
|
||||
end_dt: (() => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
return date;
|
||||
})(),
|
||||
start_dt: dayjs().subtract(1, 'day').startOf('day'),
|
||||
end_dt: dayjs().subtract(1, 'day').endOf('day'),
|
||||
filters: [],
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
@@ -92,16 +88,14 @@ export const useBusinessLogSearch = (token, initialPageSize) => {
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'GUID',
|
||||
search_data: '',
|
||||
log_action: 'None',
|
||||
log_domain: 'BASE',
|
||||
tran_id: '',
|
||||
start_dt: now,
|
||||
end_dt: now,
|
||||
start_dt: dayjs().subtract(1, 'day').startOf('day'),
|
||||
end_dt: dayjs().subtract(1, 'day').endOf('day'),
|
||||
filters: [],
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
@@ -197,11 +191,11 @@ const BusinessLogSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>일자</InputLabel>
|
||||
<SearchPeriod
|
||||
startDate={searchParams.start_dt}
|
||||
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||
endDate={searchParams.end_dt}
|
||||
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||
<DateRangePicker
|
||||
value={[searchParams.start_dt, searchParams.end_dt]}
|
||||
onChange={(dates) => {
|
||||
onSearch({ start_dt: dates[0], end_dt: dates[1] }, false);
|
||||
}}
|
||||
/>
|
||||
</>,
|
||||
];
|
||||
|
||||
@@ -3,9 +3,10 @@ import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { Fragment } from 'react';
|
||||
import { getOptionsArray } from '../../utils';
|
||||
import { PageSkeleton } from '../Skeleton/SearchSkeleton';
|
||||
import { DateRangePicker } from '../common';
|
||||
|
||||
const renderSearchField = (field, searchParams, onSearch) => {
|
||||
const { type, id, label, placeholder, width, optionsRef } = field;
|
||||
const { type, id, label, placeholder, width, optionsRef, format, showTime } = field;
|
||||
|
||||
switch (type) {
|
||||
case 'text':
|
||||
@@ -47,14 +48,45 @@ const renderSearchField = (field, searchParams, onSearch) => {
|
||||
);
|
||||
|
||||
case 'period':
|
||||
const startDateValue = searchParams[field.startDateId];
|
||||
const endDateValue = searchParams[field.endDateId];
|
||||
|
||||
// 날짜 값이 있을 때만 배열로 변환
|
||||
const rangeValue = (startDateValue && endDateValue) ?
|
||||
[startDateValue, endDateValue] :
|
||||
null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{label && label !== 'undefined' && <InputLabel $require={field.required}>{label}</InputLabel>}
|
||||
<SearchPeriod
|
||||
startDate={searchParams[field.startDateId]}
|
||||
handleStartDate={date => onSearch({ [field.startDateId]: date }, false)}
|
||||
endDate={searchParams[field.endDateId]}
|
||||
handleEndDate={date => onSearch({ [field.endDateId]: date }, false)}
|
||||
<DateRangePicker
|
||||
value={rangeValue}
|
||||
onChange={(dates) => {
|
||||
if (dates && dates.length === 2) {
|
||||
let startDate = dates[0];
|
||||
let endDate = dates[1];
|
||||
if(!showTime) {
|
||||
// 시작 날짜는 00:00:00으로 설정
|
||||
startDate = startDate.startOf('day');
|
||||
// 종료 날짜는 23:59:59로 설정
|
||||
endDate = endDate.endOf('day');
|
||||
}
|
||||
|
||||
onSearch({
|
||||
[field.startDateId]: startDate.format('YYYY-MM-DD HH:mm:ss'),
|
||||
[field.endDateId]: endDate.format('YYYY-MM-DD HH:mm:ss')
|
||||
}, false);
|
||||
} else {
|
||||
onSearch({
|
||||
[field.startDateId]: '',
|
||||
[field.endDateId]: ''
|
||||
}, false);
|
||||
}
|
||||
}}
|
||||
showTime={showTime ||false}
|
||||
format={format || "YYYY-MM-DD"}
|
||||
placeholder={['시작일', '종료일']}
|
||||
style={{ width: width || '280px' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
217
src/components/searchBar/ItemDictionarySearchBar.js
Normal file
217
src/components/searchBar/ItemDictionarySearchBar.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
itemSearchType,
|
||||
logAction,
|
||||
logDomain, opGender,
|
||||
opItemLargeType,
|
||||
opItemSmallType,
|
||||
userSearchType2,
|
||||
} from '../../assets/data/options';
|
||||
import { BusinessLogList } from '../../apis/Log';
|
||||
import {SearchFilter} from '../ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { getItemDictionaryList } from '../../apis';
|
||||
|
||||
export const useItemDictionarySearch = (token, initialPageSize) => {
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'ID',
|
||||
search_data: '',
|
||||
large_type: 'ALL',
|
||||
small_type: 'ALL',
|
||||
brand: 'ALL',
|
||||
gender: 'ALL',
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
page_no: 1
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
//초기 데이터 로드 안함
|
||||
// const initialLoad = async () => {
|
||||
// await fetchData(searchParams);
|
||||
// };
|
||||
|
||||
// initialLoad();
|
||||
}, [token]);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await getItemDictionaryList(
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data,
|
||||
params.large_type,
|
||||
params.small_type,
|
||||
params.brand,
|
||||
params.gender,
|
||||
params.order_by,
|
||||
params.page_size,
|
||||
params.page_no
|
||||
);
|
||||
if(result.result === "ERROR"){
|
||||
showToast(result.result, {type: alertTypes.error});
|
||||
}
|
||||
setData(result.data);
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
showToast('error', {type: alertTypes.error});
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams,
|
||||
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'ID',
|
||||
search_data: '',
|
||||
large_type: 'ALL',
|
||||
small_type: 'ALL',
|
||||
brand: 'ALL',
|
||||
gender: 'ALL',
|
||||
order_by: 'ASC',
|
||||
page_size: initialPageSize,
|
||||
page_no: 1
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [initialPageSize, fetchData]);
|
||||
|
||||
const handlePageChange = useCallback(async (newPage) => {
|
||||
return await handleSearch({ page_no: newPage }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||
return await handleSearch({ order_by: newOrder }, true);
|
||||
}, [handleSearch]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const ItemDictionarySearchBar = ({ searchParams, onSearch, onReset, brandData }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams, true);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||
{itemSearchType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.search_type === 'ID' ? '아이템 ID 입력' : '아이템명 입력'}
|
||||
value={searchParams.search_data}
|
||||
width="260px"
|
||||
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>대분류</InputLabel>
|
||||
<SelectInput value={searchParams.large_type} onChange={e => onSearch({ large_type: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opItemLargeType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>소분류</InputLabel>
|
||||
<SelectInput value={searchParams.small_type} onChange={e => onSearch({ small_type: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opItemSmallType.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
];
|
||||
|
||||
const optionList = [
|
||||
<>
|
||||
<InputLabel>브랜드</InputLabel>
|
||||
<SelectInput value={searchParams.brand} onChange={e => onSearch({ brand: e.target.value })}>
|
||||
<option value='ALL'>전체</option>
|
||||
{brandData?.map((data, index) => (
|
||||
<option key={index} value={data.id}>
|
||||
{data.brandDesc}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>성별</InputLabel>
|
||||
<SelectInput value={searchParams.gender} onChange={e => onSearch({ gender: e.target.value }, false)} >
|
||||
<option value='ALL'>전체</option>
|
||||
{opGender.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
</>,
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default ItemDictionarySearchBar;
|
||||
@@ -19,6 +19,7 @@ export const useItemLogSearch = (token, initialPageSize) => {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'GUID',
|
||||
search_data: '',
|
||||
item_id: '',
|
||||
tran_id: '',
|
||||
log_action: 'None',
|
||||
item_large_type: 'None',
|
||||
@@ -59,6 +60,7 @@ export const useItemLogSearch = (token, initialPageSize) => {
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data,
|
||||
params.item_id,
|
||||
params.tran_id,
|
||||
params.log_action,
|
||||
params.item_large_type,
|
||||
@@ -114,6 +116,7 @@ export const useItemLogSearch = (token, initialPageSize) => {
|
||||
const resetParams = {
|
||||
search_type: 'GUID',
|
||||
search_data: '',
|
||||
item_id: '',
|
||||
tran_id: '',
|
||||
log_action: 'None',
|
||||
item_large_type: 'None',
|
||||
@@ -190,6 +193,21 @@ const ItemLogSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
onChange={e => onSearch({ tran_id: e.target.value }, false)}
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>아이템 ID</InputLabel>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder='아이템 ID 입력'
|
||||
value={searchParams.item_id}
|
||||
width="150px"
|
||||
onChange={e => {
|
||||
// 숫자만 허용하는 정규식
|
||||
const numericValue = e.target.value.replace(/[^0-9]/g, '');
|
||||
onSearch({ item_id: numericValue }, false);
|
||||
}}
|
||||
|
||||
/>
|
||||
</>,
|
||||
<>
|
||||
<InputLabel>LargeType</InputLabel>
|
||||
<SelectInput value={searchParams.item_large_type} onChange={e => onSearch({ item_large_type: e.target.value }, false)} >
|
||||
|
||||
124
src/components/searchBar/RankManageSearchBar.js
Normal file
124
src/components/searchBar/RankManageSearchBar.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import { TextInput, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
userSearchType2,
|
||||
} from '../../assets/data/options';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { getItemDictionaryList, UserView } from '../../apis';
|
||||
|
||||
export const useRankManageSearch = (token) => {
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await UserView(
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data
|
||||
);
|
||||
if(result.result === "NOT_USER"){
|
||||
showToast(result.result, {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
setData(result.data.result);
|
||||
return result.data.result;
|
||||
} catch (error) {
|
||||
showToast('error', {type: alertTypes.error});
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [fetchData]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
const RankManageSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
onSearch(searchParams, true);
|
||||
};
|
||||
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||
{userSearchType2.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.search_type === 'GUID' ? 'GUID 입력' : searchParams.search_type === 'ACCOUNT' ? 'ACCOUNT 입력' : '닉네임 입력'}
|
||||
value={searchParams.search_data}
|
||||
width="260px"
|
||||
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSearch({ search_data: e.target.value }, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default RankManageSearchBar;
|
||||
@@ -1,57 +1,124 @@
|
||||
import { styled } from 'styled-components';
|
||||
import Button from '../common/button/Button';
|
||||
import { TextInput, SelectInput, InputGroup } from '../../styles/Components';
|
||||
import { SearchBarLayout } from '../common/SearchBar';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
userSearchType2,
|
||||
} from '../../assets/data/options';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { UserView } from '../../apis';
|
||||
|
||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||
export const useUserSearch = (token) => {
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const UserSearchBar = () => {
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<SearchbarStyle>
|
||||
<SearchItem>
|
||||
<InputLabel>유저 조회</InputLabel>
|
||||
<TextInput type="text" width="300px" placeholder="조회 대상 유저의 GUID를 입력하세요." />
|
||||
</SearchItem>
|
||||
<BtnWrapper $gap="8px">
|
||||
<Button theme="reset" />
|
||||
<Button
|
||||
theme="primary"
|
||||
text="검색"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</BtnWrapper>
|
||||
</SearchbarStyle>
|
||||
</FormWrapper>
|
||||
</>
|
||||
);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
const fetchData = useCallback(async (params) => {
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await UserView(
|
||||
token,
|
||||
params.search_type,
|
||||
params.search_data
|
||||
);
|
||||
if(result.result === "NOT_USER"){
|
||||
showToast(result.result, {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
setData(result.data.result);
|
||||
return result.data.result;
|
||||
} catch (error) {
|
||||
showToast('error', {type: alertTypes.error});
|
||||
throw error;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const updateSearchParams = useCallback((newParams) => {
|
||||
setSearchParams(prev => ({
|
||||
...prev,
|
||||
...newParams
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||
const updatedParams = {
|
||||
...searchParams,
|
||||
...newParams
|
||||
};
|
||||
updateSearchParams(updatedParams);
|
||||
|
||||
if (executeSearch) {
|
||||
return await fetchData(updatedParams);
|
||||
}
|
||||
return null;
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
const handleReset = useCallback(async () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() - 1);
|
||||
const resetParams = {
|
||||
search_type: 'GUID',
|
||||
search_data: ''
|
||||
};
|
||||
setSearchParams(resetParams);
|
||||
return await fetchData(resetParams);
|
||||
}, [fetchData]);
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
loading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams
|
||||
};
|
||||
};
|
||||
|
||||
export default UserSearchBar;
|
||||
const UserSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||
const handleSubmit = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const SearchbarStyle = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
margin-bottom: 40px;
|
||||
`;
|
||||
const SearchItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-right: 50px;
|
||||
onSearch(searchParams, true);
|
||||
};
|
||||
|
||||
${TextInput}, ${SelectInput} {
|
||||
height: 35px;
|
||||
}
|
||||
${TextInput} {
|
||||
padding: 0 10px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
const searchList = [
|
||||
<>
|
||||
<InputGroup>
|
||||
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||
{userSearchType2.map((data, index) => (
|
||||
<option key={index} value={data.value}>
|
||||
{data.name}
|
||||
</option>
|
||||
))}
|
||||
</SelectInput>
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder={searchParams.search_type === 'GUID' ? 'GUID 입력' : searchParams.search_type === 'ACCOUNT' ? 'ACCOUNT 입력' : '닉네임 입력'}
|
||||
value={searchParams.search_data}
|
||||
width="260px"
|
||||
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onSearch({ search_data: e.target.value }, true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
</>
|
||||
];
|
||||
|
||||
return <SearchBarLayout firstColumnData={searchList} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export default UserSearchBar;
|
||||
@@ -4,7 +4,6 @@ import VBPSearchBar from './VBPSearchBar';
|
||||
import DecoSearchBar from './DecoSearchBar';
|
||||
import ItemSearchBar from './ItemSearchBar';
|
||||
import LandSearchBar from './LandSearchBar';
|
||||
import UserSearchBar from './UserSearchBar';
|
||||
import DailySearchBar from './DailySearchBar';
|
||||
import CommonSearchBar from './CommonSearchBar';
|
||||
import CreditSearchBar from './CreditSearchBar';
|
||||
@@ -36,8 +35,11 @@ import UserCreateLogSearchBar, { useUserCreateLogSearch } from './UserCreateLogS
|
||||
import UserLoginLogSearchBar, { useUserLoginLogSearch } from './UserLoginLogSearchBar';
|
||||
import UserSnapshotLogSearchBar, { useUserSnapshotLogSearch } from './UserSnapshotLogSearchBar';
|
||||
import AssetsIndexSearchBar, { useAssetsIndexSearch } from './AssetsIndexSearchBar';
|
||||
import ItemDictionarySearchBar, { useItemDictionarySearch } from './ItemDictionarySearchBar';
|
||||
import RankManageSearchBar, { useRankManageSearch } from './RankManageSearchBar';
|
||||
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
||||
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||
import UserSearchBar, {useUserSearch} from './UserSearchBar';
|
||||
|
||||
// 모든 SearchBar 컴포넌트 export
|
||||
export {
|
||||
@@ -46,7 +48,6 @@ export {
|
||||
DecoSearchBar,
|
||||
ItemSearchBar,
|
||||
LandSearchBar,
|
||||
UserSearchBar,
|
||||
DailySearchBar,
|
||||
CommonSearchBar,
|
||||
CreditSearchBar,
|
||||
@@ -93,6 +94,12 @@ export {
|
||||
UserSnapshotLogSearchBar,
|
||||
useUserSnapshotLogSearch,
|
||||
AssetsIndexSearchBar,
|
||||
useAssetsIndexSearch
|
||||
useAssetsIndexSearch,
|
||||
ItemDictionarySearchBar,
|
||||
useItemDictionarySearch,
|
||||
RankManageSearchBar,
|
||||
useRankManageSearch,
|
||||
UserSearchBar,
|
||||
useUserSearch,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useAlert } from '../context/AlertProvider';
|
||||
import { alertTypes } from '../assets/data/types';
|
||||
import * as APIConfigs from '../assets/data/apis'
|
||||
import { callAPI } from '../utils/apiService';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const useCommonSearch = (configPath) => {
|
||||
const [config, setConfig] = useState(null);
|
||||
@@ -24,7 +25,9 @@ export const useCommonSearch = (configPath) => {
|
||||
|
||||
// 초기 검색 파라미터 설정
|
||||
if (configData.initialSearchParams) {
|
||||
setSearchParams(configData.initialSearchParams);
|
||||
const processedParams = processTodayValues(configData.initialSearchParams);
|
||||
setSearchParams(processedParams);
|
||||
|
||||
}
|
||||
|
||||
// API 엔드포인트 설정 가져오기
|
||||
@@ -50,6 +53,27 @@ export const useCommonSearch = (configPath) => {
|
||||
fetchConfig();
|
||||
}, [configPath]);
|
||||
|
||||
const processTodayValues = useCallback((params) => {
|
||||
const processedParams = { ...params };
|
||||
|
||||
Object.keys(processedParams).forEach(key => {
|
||||
if (processedParams[key] === 'today') {
|
||||
if (key.toLowerCase().includes('start')) {
|
||||
// start가 포함된 키는 00:00:00으로 설정
|
||||
processedParams[key] = dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (key.toLowerCase().includes('end')) {
|
||||
// end가 포함된 키는 23:59:59로 설정
|
||||
processedParams[key] = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||
} else {
|
||||
// 그 외는 현재 시간으로 설정
|
||||
processedParams[key] = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return processedParams;
|
||||
}, []);
|
||||
|
||||
// 파라미터 값 변환 (날짜 등)
|
||||
const transformParams = useCallback((params) => {
|
||||
if (!config || !config.apiInfo || !config.apiInfo.paramTransforms) {
|
||||
|
||||
@@ -40,8 +40,8 @@ export const useRDSPagination = (fetchFunction, initialState = {}) => {
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
currentPage: page,
|
||||
totalItems: response.total_all || response.total || 0,
|
||||
totalPages: response.total_pages || Math.ceil((response.total_all || response.total || 0) / pagination.pageSize)
|
||||
totalItems: response?.total_all || response?.total || 0,
|
||||
totalPages: response?.total_pages || Math.ceil((response?.total_all || response?.total || 0) / pagination.pageSize)
|
||||
}));
|
||||
|
||||
return response;
|
||||
|
||||
11
src/i18n.js
11
src/i18n.js
@@ -141,6 +141,13 @@ const resources = {
|
||||
BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.",
|
||||
BATTLE_EVENT_STATUS_RUNNING_WARNING: "이벤트 진행중에는 중단할 수 없습니다.",
|
||||
BATTLE_EVENT_MODAL_STATUS_WARNING: "이벤트가 중단일때만 수정이 가능합니다.",
|
||||
//이벤트
|
||||
EVENT_MODAL_STATUS_WARNING: "이벤트가 대기일때만 수정이 가능합니다.",
|
||||
//랭킹 스케줄
|
||||
SCHEDULE_MODAL_STATUS_WARNING: "스케줄이 대기일때만 수정이 가능합니다.",
|
||||
SCHEDULE_SELECT_DELETE: "선택된 스케줄을 삭제하시겠습니까?",
|
||||
SCHEDULE_REGIST_CONFIRM: "스케줄을 등록하시겠습니까?",
|
||||
SCHEDULE_UPDATE_CONFIRM: "스케줄을 수정하시겠습니까?",
|
||||
//메뉴 배너
|
||||
MENU_BANNER_TITLE: "메뉴 배너 관리",
|
||||
MENU_BANNER_CREATE: "메뉴 배너 등록",
|
||||
@@ -163,6 +170,7 @@ const resources = {
|
||||
FILE_IMAGE_UPLOAD_ERROR: "이미지 업로드 중 오류가 발생했습니다.",
|
||||
FILE_NOT_EXIT_ERROR: "유효하지 않은 파일입니다.",
|
||||
FILE_SIZE_OVER_ERROR: "파일의 사이즈가 5MB를 초과하였습니다.",
|
||||
FILE_KOREAN_NAME_WARNING: "한글이름이 들어간 파일은 등록할 수 없습니다.",
|
||||
//파일명칭
|
||||
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
|
||||
FILE_INDEX_USER_RETENTION: 'Caliverse_Retention.csv',
|
||||
@@ -171,6 +179,7 @@ const resources = {
|
||||
FILE_INDEX_ITEM_ACQUIRE: 'Caliverse_Item_Acquire.csv',
|
||||
FILE_INDEX_ITEM_CONSUME: 'Caliverse_Item_Consume.csv',
|
||||
FILE_INDEX_ASSETS_CURRENCY: 'Caliverse_Assets_Currency.csv',
|
||||
FILE_INDEX_STAT_CURRENCY: 'Caliverse_Stat_Currency.csv',
|
||||
FILE_INDEX_ASSETS_ITEM: 'Caliverse_Assets_Item.csv',
|
||||
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',
|
||||
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
|
||||
@@ -183,8 +192,10 @@ const resources = {
|
||||
FILE_GAME_LOG_ITEM: 'Caliverse_Game_Log_Item',
|
||||
FILE_GAME_LOG_CURRENCY_ITEM: 'Caliverse_Game_Log_Currecy_Item',
|
||||
FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index',
|
||||
FILE_DICTIONARY_ITEM: 'Caliverse_Dictionary_Item',
|
||||
//서버 에러메시지
|
||||
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
||||
NOT_USER: '유저 정보를 확인해주세요.',
|
||||
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
||||
NICKNAME_NUMBER_ERROR: '닉네임은 첫번째 글자에 숫자를 허용하지 않습니다.',
|
||||
NICKNAME_SPECIALCHAR_ERROR: '닉네임은 특수문자를 사용할 수 없습니다.',
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
import { Title, TableStyle, ButtonClose, ModalText, BtnWrapper } from '../../styles/Components';
|
||||
|
||||
import UserSearchBar from '../../components/searchBar/UserSearchBar';
|
||||
import Modal from '../../components/common/modal/Modal';
|
||||
import Button from '../../components/common/button/Button';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
const CryptView = () => {
|
||||
const navigate = useNavigate();
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const mokupData = [
|
||||
{
|
||||
getDate: '2023-08-06 18:50:03',
|
||||
getLocation: '유저 거래',
|
||||
itemName: '빛나는 가죽 장갑',
|
||||
serialNo: 'Serial_number',
|
||||
itemCode: 'Item_code',
|
||||
tradeKey: 'User_trade_key',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 15) ? (
|
||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
|
||||
<BtnWrapper $justify="flex-end">
|
||||
<ButtonClose onClick={() => navigate(-1)} />
|
||||
</BtnWrapper>
|
||||
<ModalText $align="center">
|
||||
해당 메뉴에 대한 조회 권한이 없습니다.
|
||||
<br />
|
||||
권한 등급을 변경 후 다시 이용해주세요.
|
||||
</ModalText>
|
||||
<BtnWrapper $gap="10px">
|
||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
|
||||
</BtnWrapper>
|
||||
</Modal>
|
||||
) : (
|
||||
<>
|
||||
<Title>크립토조회</Title>
|
||||
<UserSearchBar />
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="170">획득일자</th>
|
||||
<th width="170">획득처</th>
|
||||
<th width="200">아이템명</th>
|
||||
<th width="250">시리얼 넘버</th>
|
||||
<th width="250">고유 코드</th>
|
||||
<th width="250">거래 key</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mokupData.map((data, index) => (
|
||||
<Fragment key={index}>
|
||||
<tr>
|
||||
<td>{new Date(data.getDate).toLocaleString()}</td>
|
||||
<td>{data.getLocation}</td>
|
||||
<td>{data.itemName}</td>
|
||||
<td>{data.serialNo}</td>
|
||||
<td>{data.itemCode}</td>
|
||||
<td>{data.tradeKey}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CryptView;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
max-height: calc(100vh - 287px);
|
||||
overflow: auto;
|
||||
border-top: 1px solid #000;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
thead {
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
${TableStyle} {
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
289
src/pages/DataManage/MetaItemView.js
Normal file
289
src/pages/DataManage/MetaItemView.js
Normal file
@@ -0,0 +1,289 @@
|
||||
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { AnimatedPageWrapper, InfoCard } from '../../components/common/Layout';
|
||||
import {
|
||||
Title,
|
||||
TableStyle,
|
||||
FormWrapper,
|
||||
TableWrapper,
|
||||
TableActionButton,
|
||||
TableDetailRow,
|
||||
TableDetailContainer,
|
||||
TableDetailFlex,
|
||||
TableDetailColumn,
|
||||
DownloadContainer, CircularProgressWrapper,
|
||||
} from '../../styles/Components';
|
||||
|
||||
import { useDataFetch, withAuth } from '../../hooks/hook';
|
||||
import {
|
||||
authType,
|
||||
} from '../../assets/data';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AnimatedTabs,
|
||||
ViewTableInfo,
|
||||
} from '../../components/common';
|
||||
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
||||
import CircularProgress from '../../components/common/CircularProgress';
|
||||
|
||||
import {
|
||||
INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE,
|
||||
} from '../../assets/data/adminConstants';
|
||||
import ExcelExportButton from '../../components/common/button/ExcelExportButton';
|
||||
import Pagination from '../../components/common/Pagination/Pagination';
|
||||
import { useItemDictionarySearch, ItemDictionarySearchBar } from '../../components/searchBar';
|
||||
import { numberFormatter } from '../../utils';
|
||||
import { BrandView, ItemDictionaryExport } from '../../apis';
|
||||
import { opGender, opItemLargeType, opItemSmallType } from '../../assets/data/options';
|
||||
import { languageNames } from '../../assets/data/types';
|
||||
|
||||
const MetaItemView = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const { t } = useTranslation();
|
||||
const tableRef = useRef(null);
|
||||
|
||||
const [expandedRows, setExpandedRows] = useState({});
|
||||
const [activeLanguage, setActiveLanguage] = useState('ko');
|
||||
|
||||
const [downloadState, setDownloadState] = useState({
|
||||
loading: false,
|
||||
progress: 0
|
||||
});
|
||||
|
||||
const {
|
||||
searchParams,
|
||||
loading: dataLoading,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handleOrderByChange,
|
||||
handlePageSizeChange,
|
||||
updateSearchParams
|
||||
} = useItemDictionarySearch(token, INITIAL_PAGE_SIZE);
|
||||
|
||||
const {
|
||||
data: brandData
|
||||
} = useDataFetch(() => BrandView(token), [token]);
|
||||
|
||||
useEffect(()=>{
|
||||
setDownloadState({
|
||||
loading: false,
|
||||
progress: 0
|
||||
});
|
||||
},[dataList]);
|
||||
|
||||
const toggleRowExpand = (index) => {
|
||||
setExpandedRows(prev => ({
|
||||
...prev,
|
||||
[index]: !prev[index]
|
||||
}));
|
||||
};
|
||||
|
||||
const tableHeaders = useMemo(() => {
|
||||
return [
|
||||
{ id: 'largeType', label: '대분류', width: '100px' },
|
||||
{ id: 'smallType', label: '소분류', width: '120px' },
|
||||
{ id: 'itemName', label: '아이템명', width: '150px' },
|
||||
{ id: 'itemId', label: '아이템 ID', width: '120px' },
|
||||
{ id: 'brand', label: '브랜드', width: '150px' },
|
||||
{ id: 'gender', label: '성별', width: '100px' },
|
||||
{ id: 'currencySellType', label: '판매시 획득 재화', width: '120px' },
|
||||
{ id: 'currencySellPrice', label: '판매시 획득 재화량', width: '120px' },
|
||||
{ id: 'currencyBuyType', label: '구매시 획득 재화', width: '120px' },
|
||||
{ id: 'currencyBuyPrice', label: '구매시 획득 재화량', width: '120px' },
|
||||
{ id: 'currencyBuyRate', label: '구매시 할인율', width: '120px' },
|
||||
{ id: 'details', label: '상세 정보', width: '100px' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
const renderDetailInfo = useCallback((data, title, keyPrefix, index) => {
|
||||
if (!data) return null;
|
||||
|
||||
if (typeof data === 'object' && !Array.isArray(data)) {
|
||||
return (
|
||||
<InfoCard
|
||||
title={title}
|
||||
data={data}
|
||||
keyPrefix={`${keyPrefix}-${index}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const flattenedData = data.reduce((acc, info, dataIndex) => {
|
||||
Object.entries(info).forEach(([key, value]) => {
|
||||
acc[`${dataIndex + 1}_${key}`] = value;
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title={title}
|
||||
data={flattenedData}
|
||||
keyPrefix={`${keyPrefix}-${index}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
// 언어별 테이블 렌더링
|
||||
const renderTableForLanguage = useCallback((languageKey) => {
|
||||
// 해당 언어의 아이템 리스트 가져오기
|
||||
const languageItemList = dataList?.item_list?.[languageKey] || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||
<>
|
||||
<TableWrapper>
|
||||
<TableStyle ref={tableRef}>
|
||||
<thead>
|
||||
<tr>
|
||||
{tableHeaders.map(header => (
|
||||
<th key={header.id} width={header.width}>{header.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{languageItemList.length > 0 ? languageItemList.map((item, index) => (
|
||||
<Fragment key={`${languageKey}-${index}`}>
|
||||
<tr>
|
||||
<td>{opItemLargeType.find(data => data.value === item.type_large)?.name}</td>
|
||||
<td>{opItemSmallType.find(data => data.value === item.type_small)?.name}</td>
|
||||
<td>{item.item_name}</td>
|
||||
<td>{item.item_id}</td>
|
||||
<td>{item.brand === '' ? '-' : item.brand}</td>
|
||||
<td>{opGender.find(data => data.value === item.gender)?.name || '남녀공용'}</td>
|
||||
<td>{item.sell_type}</td>
|
||||
<td>{numberFormatter.formatCurrency(item.sell_price)}</td>
|
||||
<td>{item.buy_type}</td>
|
||||
<td>{numberFormatter.formatCurrency(item.buy_price)}</td>
|
||||
<td>{item.buy_discount_rate}</td>
|
||||
<td>
|
||||
<TableActionButton onClick={() => toggleRowExpand(`${languageKey}-${index}`)}>
|
||||
{expandedRows[`${languageKey}-${index}`] ? '접기' : '상세보기'}
|
||||
</TableActionButton>
|
||||
</td>
|
||||
</tr>
|
||||
{expandedRows[`${languageKey}-${index}`] && (
|
||||
<TableDetailRow>
|
||||
<td colSpan={tableHeaders.length}>
|
||||
<TableDetailContainer>
|
||||
<TableDetailFlex>
|
||||
<TableDetailColumn>
|
||||
{renderDetailInfo(item.country, "Count 정보", "country", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.expire, "아이템 만료 정보", "expire", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.trade, "Trade 정보", "trade", `${languageKey}-${index}`)}
|
||||
</TableDetailColumn>
|
||||
<TableDetailColumn>
|
||||
{renderDetailInfo(item.attrib, "속성 정보", "attrib", `${languageKey}-${index}`)}
|
||||
{renderDetailInfo(item.etc, "기타 정보", "etc", `${languageKey}-${index}`)}
|
||||
</TableDetailColumn>
|
||||
</TableDetailFlex>
|
||||
</TableDetailContainer>
|
||||
</td>
|
||||
</TableDetailRow>
|
||||
)}
|
||||
</Fragment>
|
||||
)) : (
|
||||
<tr>
|
||||
<td colSpan={tableHeaders.length} style={{ textAlign: 'center', padding: '20px' }}>
|
||||
해당 언어의 데이터가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
{languageItemList.length > 0 &&
|
||||
<Pagination
|
||||
postsPerPage={searchParams.page_size}
|
||||
totalPosts={dataList?.total_all}
|
||||
setCurrentPage={handlePageChange}
|
||||
currentPage={searchParams.page_no}
|
||||
pageLimit={INITIAL_PAGE_LIMIT}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}, [dataList, dataLoading, expandedRows, tableHeaders, searchParams, handlePageChange]);
|
||||
|
||||
// 언어별 탭 아이템 생성
|
||||
const tabItems = useMemo(() => {
|
||||
// 실제 데이터에서 사용 가능한 언어만 탭으로 생성
|
||||
const availableLanguages = dataList?.item_list ? Object.keys(dataList.item_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>
|
||||
<ItemDictionarySearchBar
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
brandData={brandData}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo orderType="asc" total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||
<DownloadContainer>
|
||||
<ExcelExportButton
|
||||
functionName="ItemDictionaryExport"
|
||||
params={excelParams}
|
||||
fileName={t('FILE_DICTIONARY_ITEM')}
|
||||
onLoadingChange={setDownloadState}
|
||||
dataSize={dataList?.total_all}
|
||||
/>
|
||||
{downloadState.loading && (
|
||||
<CircularProgressWrapper>
|
||||
<CircularProgress
|
||||
progress={downloadState.progress}
|
||||
size={36}
|
||||
progressColor="#4A90E2"
|
||||
/>
|
||||
</CircularProgressWrapper>
|
||||
)}
|
||||
</DownloadContainer>
|
||||
</ViewTableInfo>
|
||||
{
|
||||
dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||
<AnimatedTabs
|
||||
items={tabItems}
|
||||
activeKey={activeLanguage}
|
||||
onChange={handleTabChange}
|
||||
tabPosition="left"
|
||||
/>
|
||||
}
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(authType.itemDictionaryRead)(MetaItemView);
|
||||
@@ -1,130 +1,149 @@
|
||||
import { useState, Fragment } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { TabScroll, Title } from '../../styles/Components';
|
||||
import { TabItem, TabScroll, TabWrapper, Title } from '../../styles/Components';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import UserViewSearchBar from '../../components/searchBar/UserViewSearchBar';
|
||||
import UserDefaultInfo from '../../components/DataManage/UserDefaultInfo';
|
||||
import UserAvatarInfo from '../../components/DataManage/UserAvatarInfo';
|
||||
import UserDressInfo from '../../components/DataManage/UserDressInfo';
|
||||
import UserToolInfo from '../../components/DataManage/UserToolInfo';
|
||||
import UserInventoryInfo from '../../components/DataManage/UserInventoryInfo';
|
||||
import UserMailInfo from '../../components/DataManage/UserMailInfo';
|
||||
import UserMyHomeInfo from '../../components/DataManage/UserMyHomeInfo';
|
||||
import UserFriendInfo from '../../components/DataManage/UserFriendInfo';
|
||||
import UserTatttooInfo from '../../components/DataManage/UserTattooInfo';
|
||||
import UserQuestInfo from '../../components/DataManage/UserQuestInfo';
|
||||
import UserClaimInfo from '../../components/DataManage/UserClaimInfo';
|
||||
import {
|
||||
UserDefaultInfo,
|
||||
UserAvatarInfo,
|
||||
UserDressInfo,
|
||||
UserToolInfo,
|
||||
UserInventoryInfo,
|
||||
UserMailInfo,
|
||||
UserMyHomeInfo,
|
||||
UserFriendInfo,
|
||||
UserTattooInfo,
|
||||
UserQuestInfo,
|
||||
UserClaimInfo
|
||||
} from '../../components/DataManage';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { authList } from '../../store/authList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import { authType, TabUserList } from '../../assets/data';
|
||||
import { UserSearchBar, useUserSearch } from '../../components/searchBar';
|
||||
import { withAuth } from '../../hooks/hook';
|
||||
import { AnimatedTabs } from '../../components/common';
|
||||
|
||||
const UserView = () => {
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [infoView, setInfoView] = useState('none');
|
||||
const [activeContent, setActiveContent] = useState('기본정보');
|
||||
const [resultData, setResultData] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('BASIC');
|
||||
const [resultData, setResultData] = useState();
|
||||
|
||||
const handleTab = title => {
|
||||
setActiveContent(title);
|
||||
const {
|
||||
searchParams,
|
||||
loading: dataLoading,
|
||||
data,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
updateSearchParams
|
||||
} = useUserSearch(token);
|
||||
|
||||
useEffect(() => {
|
||||
if(data) {
|
||||
setResultData(data);
|
||||
setInfoView('flex');
|
||||
} else {
|
||||
setInfoView('none');
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const tabItems = useMemo(() => {
|
||||
return TabUserList.map(el => ({
|
||||
key: el.value,
|
||||
label: el.name,
|
||||
children: (() => {
|
||||
switch(el.value) {
|
||||
case 'BASIC': return <UserDefaultInfo userInfo={resultData} />;
|
||||
case 'AVATAR': return <UserAvatarInfo userInfo={resultData} />;
|
||||
case 'CLOTH': return <UserDressInfo userInfo={resultData} />;
|
||||
case 'TOOL': return <UserToolInfo userInfo={resultData} />;
|
||||
case 'INVENTORY': return <UserInventoryInfo userInfo={resultData} />;
|
||||
case 'MAIL': return <UserMailInfo userInfo={resultData} />;
|
||||
case 'MYHOME': return <UserMyHomeInfo userInfo={resultData} />;
|
||||
case 'FRIEND': return <UserFriendInfo userInfo={resultData} />;
|
||||
case 'TATTOO': return <UserTattooInfo userInfo={resultData} />;
|
||||
case 'QUEST': return <UserQuestInfo userInfo={resultData} />;
|
||||
case 'CLAIM': return <UserClaimInfo userInfo={resultData} />;
|
||||
default: return null;
|
||||
}
|
||||
})()
|
||||
}));
|
||||
}, [resultData]);
|
||||
|
||||
const handleTabChange = (key) => {
|
||||
setActiveTab(key);
|
||||
};
|
||||
|
||||
// const handleTab = (e, content) => {
|
||||
// e.preventDefault();
|
||||
// setActiveTab(content);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.userSearchRead) ? (
|
||||
<AuthModal />
|
||||
) : (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>유저조회</Title>
|
||||
<UserViewSearchBar setInfoView={setInfoView} resultData={resultData} handleTab={handleTab} setResultData={setResultData} />
|
||||
<UserWrapper display={infoView}>
|
||||
<TabScroll>
|
||||
<UserTabWrapper>
|
||||
{TabUserList.map((el, idx) => {
|
||||
return (
|
||||
<UserTab key={idx} $state={el.title === activeContent ? 'active' : 'unactive'} onClick={() => handleTab(el.title)}>
|
||||
{el.title}
|
||||
</UserTab>
|
||||
);
|
||||
})}
|
||||
</UserTabWrapper>
|
||||
</TabScroll>
|
||||
<UserTabInfo>
|
||||
{activeContent === '기본정보' && <UserDefaultInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '아바타' && <UserAvatarInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '의상' && <UserDressInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '도구' && <UserToolInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '인벤토리' && <UserInventoryInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '우편' && <UserMailInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '마이홈' && <UserMyHomeInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '친구목록' && <UserFriendInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '타투' && <UserTatttooInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '퀘스트' && <UserQuestInfo userInfo={resultData && resultData} />}
|
||||
{activeContent === '클레임' && <UserClaimInfo userInfo={resultData && resultData} />}
|
||||
</UserTabInfo>
|
||||
</UserWrapper>
|
||||
</AnimatedPageWrapper>
|
||||
)}
|
||||
</>
|
||||
<AnimatedPageWrapper>
|
||||
<Title>유저조회</Title>
|
||||
<UserSearchBar
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
setResultData();
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
<UserWrapper display={infoView}>
|
||||
<AnimatedTabs
|
||||
items={tabItems}
|
||||
activeKey={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabPosition="center"
|
||||
/>
|
||||
|
||||
{/*<TabScroll>*/}
|
||||
{/* <TabWrapper>*/}
|
||||
{/* {TabUserList.map((el, idx) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <li key={idx}>*/}
|
||||
{/* <TabItem $state={activeTab === el.value ? 'active' : 'none'} onClick={e => handleTab(e, el.value)}>*/}
|
||||
{/* {el.name}*/}
|
||||
{/* </TabItem>*/}
|
||||
{/* </li>*/}
|
||||
{/* )*/}
|
||||
{/* })}*/}
|
||||
{/* </TabWrapper>*/}
|
||||
{/*</TabScroll>*/}
|
||||
{/*<UserTabInfo>*/}
|
||||
{/* {activeTab === 'BASIC' && <UserDefaultInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'AVATAR' && <UserAvatarInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'CLOTH' && <UserDressInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'TOOL' && <UserToolInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'INVENTORY' && <UserInventoryInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'MAIL' && <UserMailInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'MYHOME' && <UserMyHomeInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'FRIEND' && <UserFriendInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'TATTOO' && <UserTattooInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'QUEST' && <UserQuestInfo userInfo={resultData && resultData} />}*/}
|
||||
{/* {activeTab === 'CLAIM' && <UserClaimInfo userInfo={resultData && resultData} />}*/}
|
||||
{/*</UserTabInfo>*/}
|
||||
</UserWrapper>
|
||||
</AnimatedPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserView;
|
||||
export default withAuth(authType.userSearchRead)(UserView);
|
||||
|
||||
const UserTab = styled.li`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 120px;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: #888;
|
||||
}
|
||||
${props =>
|
||||
props.$state === 'active' &&
|
||||
`font-weight: 700;
|
||||
color: #fff;
|
||||
background: #888;`}
|
||||
`;
|
||||
|
||||
const UserTabWrapper = styled.ul`
|
||||
border-bottom: 1px solid #888888;
|
||||
display: flex;
|
||||
background: #fff;
|
||||
`;
|
||||
const UserWrapper = styled.div`
|
||||
display: ${props => props.display};
|
||||
${props => props.display && `flex-flow:column;`}
|
||||
min-height: calc(100vh - 345px);
|
||||
overflow: hidden;
|
||||
background: #f9f9f9;
|
||||
`;
|
||||
|
||||
const UserTabInfo = styled.div`
|
||||
padding: 50px;
|
||||
|
||||
overflow: auto;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #666666;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
`;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
//background: #f9f9f9;
|
||||
`;
|
||||
@@ -1,52 +1,43 @@
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
EventDelete,
|
||||
EventDetailView, LogHistory,
|
||||
} from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { authType, commonStatus, modalTypes, eventStatus } from '../../assets/data';
|
||||
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper, TextInput, InputItem } from '../../styles/Components';
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import EventDetailModal from '../../components/modal/EventDetailModal';
|
||||
import Pagination from '../../components/common/Pagination/Pagination';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import DynamicModal from '../../components/common/modal/DynamicModal';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import ViewTableInfo from '../../components/common/Table/ViewTableInfo';
|
||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
||||
import tableInfo from '../../assets/data/pages/eventTable.json'
|
||||
|
||||
import {
|
||||
ModalInputItem,
|
||||
ModalSubText,
|
||||
RegistInputItem,
|
||||
StatusLabel, StatusWapper,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
||||
import { useModal, useTable } from '../../hooks/hook';
|
||||
authType, commonStatus as CommonStatus,
|
||||
} from '../../assets/data';
|
||||
import { Title, FormWrapper} from '../../styles/Components';
|
||||
import {
|
||||
Pagination,
|
||||
CaliTable, TableHeader,
|
||||
} from '../../components/common';
|
||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import {
|
||||
EventActionView, EventDelete,
|
||||
EventDetailView,
|
||||
LogHistory,
|
||||
} from '../../apis';
|
||||
import { CommonSearchBar } from '../../components/ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||
import { historyTables } from '../../assets/data/data';
|
||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||
import {AnimatedPageWrapper} from '../../components/common/Layout';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||
import EventModal from '../../components/modal/EventModal';
|
||||
|
||||
const Event = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const {withLoading} = useLoading();
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const tableRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const [detailData, setDetailData] = useState('');
|
||||
const { showToast, showModal } = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [detailData, setDetailData] = useState({});
|
||||
const [historyData, setHistoryData] = useState({});
|
||||
const [modalType, setModalType] = useState('regist');
|
||||
|
||||
const {
|
||||
modalState,
|
||||
@@ -54,10 +45,8 @@ const Event = () => {
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
detail: 'hidden',
|
||||
delete: 'hidden',
|
||||
history: 'hidden'
|
||||
history: 'hidden',
|
||||
});
|
||||
const [deleteDesc, setDeleteDesc] = useState('');
|
||||
|
||||
const {
|
||||
config,
|
||||
@@ -65,35 +54,34 @@ const Event = () => {
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
} = useCommonSearchOld("eventSearch");
|
||||
loading,
|
||||
configLoaded,
|
||||
handlePageChange,
|
||||
handlePageSizeChange
|
||||
} = useEnhancedCommonSearch("eventSearch");
|
||||
|
||||
const {
|
||||
data: eventActionData
|
||||
} = useDataFetch(() => EventActionView(token), [token]);
|
||||
|
||||
const {
|
||||
selectedRows,
|
||||
handleSelectRow,
|
||||
isRowSelected
|
||||
} = useTable(dataList?.list || [], {mode: 'single'});
|
||||
|
||||
// 상세보기 호출
|
||||
const handleDetailModal = async (e, id) => {
|
||||
await EventDetailView(token, id).then(data => {
|
||||
setDetailData(data);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
handleModalView('detail');
|
||||
};
|
||||
|
||||
|
||||
const handleModalSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
const handleAction = async (action, item = null) => {
|
||||
switch (action) {
|
||||
case "regist":
|
||||
setModalType('regist');
|
||||
handleModalView('detail');
|
||||
break;
|
||||
case "history":
|
||||
const params = {};
|
||||
params.db_type = "MYSQL"
|
||||
params.sql_id = param.id;
|
||||
params.sql_id = item.id;
|
||||
params.table_name = historyTables.event
|
||||
|
||||
await LogHistory(token, params).then(data => {
|
||||
@@ -101,191 +89,124 @@ const Event = () => {
|
||||
handleModalView('history');
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
const delete_check = selectedRows.every(row => {
|
||||
const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date));
|
||||
return row.add_flag || (timeDiff < 30);
|
||||
case "detail":
|
||||
await EventDetailView(token, item.id).then(data => {
|
||||
setDetailData(data.detail);
|
||||
setModalType('modify');
|
||||
handleModalView('detail');
|
||||
});
|
||||
if(delete_check){
|
||||
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "delete":
|
||||
|
||||
handleModalView('delete');
|
||||
showModal('EVENT_SELECT_DELETE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('deleteConfirm')
|
||||
});
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
let list = [];
|
||||
const low = selectedRows[0];
|
||||
|
||||
if(deleteDesc.length === 0){
|
||||
showToast('INPUT_REASON_EMPTY_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
let isChecked = false;
|
||||
|
||||
selectedRows.map(data => {
|
||||
const row = dataList.list.find(row => row.id === Number(data.id));
|
||||
if(row.status !== commonStatus.wait) isChecked = true;
|
||||
list.push({
|
||||
id: data.id,
|
||||
delete_desc: deleteDesc
|
||||
});
|
||||
});
|
||||
|
||||
handleModalClose('delete');
|
||||
setDeleteDesc('');
|
||||
|
||||
if(isChecked) {
|
||||
showToast('EVENT_WARNING_DELETE', {type: alertTypes.warning});
|
||||
if(low.status !== CommonStatus.wait) {
|
||||
showToast('DELETE_STATUS_ONLY_WAIT', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
await withLoading(async () => {
|
||||
return await EventDelete(token, list);
|
||||
return await EventDelete(token, low.id);
|
||||
}).then(data => {
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
}).catch(error => {
|
||||
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('DELETE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleSearch(updateSearchParams);
|
||||
})
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
|
||||
<AuthModal />
|
||||
) : (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>출석 보상 이벤트 관리</Title>
|
||||
<FormWrapper>
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventDelete) && (
|
||||
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
|
||||
)}
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
|
||||
<Button
|
||||
theme="primary"
|
||||
text="이벤트 등록"
|
||||
type="button"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
navigate('/servicemanage/event/eventregist');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ViewTableInfo>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40">
|
||||
</th>
|
||||
<th width="80">번호</th>
|
||||
<th width="100">이벤트 상태</th>
|
||||
<th width="210">시작 일시</th>
|
||||
<th width="210">종료 일시</th>
|
||||
<th>우편 제목</th>
|
||||
<th width="110">확인 / 수정</th>
|
||||
<th width="200">히스토리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList?.list?.map(event => (
|
||||
<Fragment key={event.row_num}>
|
||||
<tr>
|
||||
<td>
|
||||
<CheckBox name={'select'} id={event.id}
|
||||
setData={(e) => handleSelectRow(e, event)}
|
||||
checked={isRowSelected(event.id)} />
|
||||
</td>
|
||||
<td>{event.row_num}</td>
|
||||
<StatusWapper>
|
||||
<StatusLabel $status={event.status}>
|
||||
{eventStatus.map(data => data.value === event.status && data.name)}
|
||||
</StatusLabel>
|
||||
</StatusWapper>
|
||||
<td>{convertKTC(event.start_dt)}</td>
|
||||
<td>{convertKTC(event.end_dt)}</td>
|
||||
<MailTitle>{event.title}</MailTitle>
|
||||
<td>
|
||||
<Button theme="line" text="상세보기"
|
||||
handleClick={e => handleDetailModal(e, event.id)} />
|
||||
</td>
|
||||
<td><Button theme="line" text="히스토리"
|
||||
handleClick={e => handleModalSubmit('history', event)} />
|
||||
</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
<AnimatedPageWrapper>
|
||||
<Title>통합 이벤트 관리</Title>
|
||||
|
||||
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
|
||||
{/* 조회조건 */}
|
||||
<FormWrapper>
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
|
||||
{/*상세*/}
|
||||
<EventDetailModal
|
||||
detailView={modalState.detailModal}
|
||||
handleDetailView={() =>{
|
||||
handleModalClose('detail');
|
||||
handleSearch(updateSearchParams);
|
||||
}}
|
||||
content={detailData}
|
||||
setDetailData={setDetailData}
|
||||
/>
|
||||
{/* 조회헤더 */}
|
||||
<TableHeader
|
||||
config={tableInfo.header}
|
||||
total={dataList?.total}
|
||||
total_all={dataList?.total_all}
|
||||
handleOrderBy={handleOrderByChange}
|
||||
handlePageSize={handlePageSizeChange}
|
||||
selectedRows={selectedRows}
|
||||
onAction={handleAction}
|
||||
navigate={navigate}
|
||||
/>
|
||||
|
||||
<LogDetailModal
|
||||
viewMode="changed"
|
||||
detailView={modalState.historyModal}
|
||||
handleDetailView={() => handleModalClose('history')}
|
||||
changedData={historyData}
|
||||
title="히스토리"
|
||||
/>
|
||||
{/* 조회테이블 */}
|
||||
<CaliTable
|
||||
columns={tableInfo.columns}
|
||||
data={dataList?.list}
|
||||
selectedRows={selectedRows}
|
||||
onSelectRow={handleSelectRow}
|
||||
onAction={handleAction}
|
||||
refProp={tableRef}
|
||||
loading={loading}
|
||||
isRowSelected={isRowSelected}
|
||||
/>
|
||||
|
||||
<DynamicModal
|
||||
modalType={modalTypes.childOkCancel}
|
||||
view={modalState.deleteModal}
|
||||
handleCancel={() => handleModalClose('delete')}
|
||||
handleSubmit={() => handleModalSubmit('deleteConfirm')}
|
||||
>
|
||||
<ModalInputItem>
|
||||
{t('EVENT_SELECT_DELETE')}
|
||||
<RegistInputItem>
|
||||
<TextInput
|
||||
placeholder="사유 입력"
|
||||
maxLength="30"
|
||||
value={deleteDesc}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 30) return;
|
||||
setDeleteDesc(e.target.value.trimStart())
|
||||
}}
|
||||
/>
|
||||
</RegistInputItem>
|
||||
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
||||
</ModalInputItem>
|
||||
</DynamicModal>
|
||||
</AnimatedPageWrapper>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{/* 페이징 */}
|
||||
<Pagination
|
||||
postsPerPage={searchParams?.pageSize}
|
||||
totalPosts={dataList?.total_all}
|
||||
setCurrentPage={handlePageChange}
|
||||
currentPage={searchParams?.currentPage}
|
||||
pageLimit={INITIAL_PAGE_LIMIT}
|
||||
/>
|
||||
|
||||
{/* 상세 */}
|
||||
<EventModal
|
||||
modalType={modalType}
|
||||
detailView={modalState.detailModal}
|
||||
handleDetailView={() =>{
|
||||
handleModalClose('detail');
|
||||
handleSearch(updateSearchParams);
|
||||
}}
|
||||
content={detailData}
|
||||
setDetailData={setDetailData}
|
||||
eventActionData={eventActionData}
|
||||
/>
|
||||
|
||||
<LogDetailModal
|
||||
viewMode="changed"
|
||||
detailView={modalState.historyModal}
|
||||
handleDetailView={() => handleModalClose('history')}
|
||||
changedData={historyData}
|
||||
title="히스토리"
|
||||
/>
|
||||
|
||||
</AnimatedPageWrapper>
|
||||
)
|
||||
};
|
||||
|
||||
export default Event;
|
||||
export default withAuth(authType.worldEventRead)(Event);
|
||||
|
||||
218
src/pages/ServiceManage/Ranking.js
Normal file
218
src/pages/ServiceManage/Ranking.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
import {
|
||||
authType, commonStatus as CommonStatus,
|
||||
} from '../../assets/data';
|
||||
import { Title, FormWrapper} from '../../styles/Components';
|
||||
import {
|
||||
Pagination,
|
||||
CaliTable, TableHeader,
|
||||
} from '../../components/common';
|
||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
|
||||
import {
|
||||
EventActionView,
|
||||
LogHistory,
|
||||
RankingDataView, RankingScheduleDelete,
|
||||
RankingScheduleDetailView,
|
||||
} from '../../apis';
|
||||
import { CommonSearchBar } from '../../components/ServiceManage';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||
import { historyTables } from '../../assets/data/data';
|
||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||
import tableInfo from '../../assets/data/pages/rankingTable.json'
|
||||
import RankingModal from '../../components/modal/RankingModal';
|
||||
|
||||
const Ranking = () => {
|
||||
const tableRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const { showToast, showModal } = useAlert();
|
||||
const {withLoading} = useLoading();
|
||||
const token = sessionStorage.getItem('token');
|
||||
|
||||
const [detailData, setDetailData] = useState({});
|
||||
const [historyData, setHistoryData] = useState({});
|
||||
const [modalType, setModalType] = useState('regist');
|
||||
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
detail: 'hidden',
|
||||
history: 'hidden',
|
||||
});
|
||||
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
loading,
|
||||
configLoaded,
|
||||
handlePageChange,
|
||||
handlePageSizeChange
|
||||
} = useEnhancedCommonSearch("rankingSearch");
|
||||
|
||||
const {
|
||||
data: rankingData
|
||||
} = useDataFetch(() => RankingDataView(token), [token]);
|
||||
|
||||
const {
|
||||
data: eventActionData
|
||||
} = useDataFetch(() => EventActionView(token), [token]);
|
||||
|
||||
const {
|
||||
selectedRows,
|
||||
handleSelectRow,
|
||||
isRowSelected
|
||||
} = useTable(dataList?.list || [], {mode: 'single'});
|
||||
|
||||
const handleAction = async (action, item = null) => {
|
||||
switch (action) {
|
||||
case "regist":
|
||||
setModalType('regist');
|
||||
handleModalView('detail');
|
||||
break;
|
||||
case "history":
|
||||
const params = {};
|
||||
params.db_type = "MYSQL"
|
||||
params.sql_id = item.id;
|
||||
params.table_name = historyTables.rankingSchedule
|
||||
|
||||
await LogHistory(token, params).then(data => {
|
||||
setHistoryData(data);
|
||||
handleModalView('history');
|
||||
});
|
||||
break;
|
||||
case "detail":
|
||||
await RankingScheduleDetailView(token, item.id).then(data => {
|
||||
setDetailData(data.detail);
|
||||
setModalType('modify');
|
||||
handleModalView('detail');
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
|
||||
showModal('SCHEDULE_SELECT_DELETE', {
|
||||
type: alertTypes.confirm,
|
||||
onConfirm: () => handleAction('deleteConfirm')
|
||||
});
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
const low = selectedRows[0];
|
||||
|
||||
if(low.status !== CommonStatus.wait) {
|
||||
showToast('DELETE_STATUS_ONLY_WAIT', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RankingScheduleDelete(token, low.id);
|
||||
}).then(data => {
|
||||
if(data.result === "SUCCESS") {
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
}else{
|
||||
showToast('DELETE_FAIL', {type: alertTypes.error});
|
||||
}
|
||||
}).catch(reason => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
handleSearch(updateSearchParams);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>랭킹 스케줄러</Title>
|
||||
|
||||
{/* 조회조건 */}
|
||||
<FormWrapper>
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
|
||||
{/* 조회헤더 */}
|
||||
<TableHeader
|
||||
config={tableInfo.header}
|
||||
total={dataList?.total}
|
||||
total_all={dataList?.total_all}
|
||||
handleOrderBy={handleOrderByChange}
|
||||
handlePageSize={handlePageSizeChange}
|
||||
selectedRows={selectedRows}
|
||||
onAction={handleAction}
|
||||
navigate={navigate}
|
||||
/>
|
||||
|
||||
{/* 조회테이블 */}
|
||||
<CaliTable
|
||||
columns={tableInfo.columns}
|
||||
data={dataList?.list}
|
||||
selectedRows={selectedRows}
|
||||
onSelectRow={handleSelectRow}
|
||||
onAction={handleAction}
|
||||
refProp={tableRef}
|
||||
loading={loading}
|
||||
isRowSelected={isRowSelected}
|
||||
/>
|
||||
|
||||
{/* 페이징 */}
|
||||
<Pagination
|
||||
postsPerPage={searchParams?.pageSize}
|
||||
totalPosts={dataList?.total_all}
|
||||
setCurrentPage={handlePageChange}
|
||||
currentPage={searchParams?.currentPage}
|
||||
pageLimit={INITIAL_PAGE_LIMIT}
|
||||
/>
|
||||
|
||||
{/* 상세 */}
|
||||
<RankingModal
|
||||
modalType={modalType}
|
||||
detailView={modalState.detailModal}
|
||||
handleDetailView={() =>{
|
||||
handleModalClose('detail');
|
||||
handleSearch(updateSearchParams);
|
||||
}}
|
||||
content={detailData}
|
||||
setDetailData={setDetailData}
|
||||
rankingData={rankingData}
|
||||
eventActionData={eventActionData}
|
||||
/>
|
||||
|
||||
<LogDetailModal
|
||||
viewMode="changed"
|
||||
detailView={modalState.historyModal}
|
||||
handleDetailView={() => handleModalClose('history')}
|
||||
changedData={historyData}
|
||||
title="히스토리"
|
||||
/>
|
||||
|
||||
</AnimatedPageWrapper>
|
||||
)
|
||||
};
|
||||
|
||||
export default withAuth(authType.rankingRead)(Ranking);
|
||||
291
src/pages/ServiceManage/RewardEvent.js
Normal file
291
src/pages/ServiceManage/RewardEvent.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
RewardEventDelete,
|
||||
RewardEventDetailView, LogHistory,
|
||||
} from '../../apis';
|
||||
|
||||
import { authList } from '../../store/authList';
|
||||
import { authType, commonStatus, modalTypes, eventStatus } from '../../assets/data';
|
||||
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper, TextInput, InputItem } from '../../styles/Components';
|
||||
import CheckBox from '../../components/common/input/CheckBox';
|
||||
import Button from '../../components/common/button/Button';
|
||||
import RewardEventDetailModal from '../../components/modal/RewardEventDetailModal';
|
||||
import Pagination from '../../components/common/Pagination/Pagination';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import DynamicModal from '../../components/common/modal/DynamicModal';
|
||||
import AuthModal from '../../components/common/modal/AuthModal';
|
||||
import ViewTableInfo from '../../components/common/Table/ViewTableInfo';
|
||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
||||
import {
|
||||
ModalInputItem,
|
||||
ModalSubText,
|
||||
RegistInputItem,
|
||||
StatusLabel, StatusWapper,
|
||||
} from '../../styles/ModuleComponents';
|
||||
import { useLoading } from '../../context/LoadingProvider';
|
||||
import { useAlert } from '../../context/AlertProvider';
|
||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
||||
import { useModal, useTable } from '../../hooks/hook';
|
||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||
import { alertTypes } from '../../assets/data/types';
|
||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
import { historyTables } from '../../assets/data/data';
|
||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||
import {AnimatedPageWrapper} from '../../components/common/Layout';
|
||||
|
||||
const RewardEvent = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
const userInfo = useRecoilValue(authList);
|
||||
const { t } = useTranslation();
|
||||
const {withLoading} = useLoading();
|
||||
const {showToast} = useAlert();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [detailData, setDetailData] = useState('');
|
||||
const [historyData, setHistoryData] = useState({});
|
||||
|
||||
const {
|
||||
modalState,
|
||||
handleModalView,
|
||||
handleModalClose
|
||||
} = useModal({
|
||||
detail: 'hidden',
|
||||
delete: 'hidden',
|
||||
history: 'hidden'
|
||||
});
|
||||
const [deleteDesc, setDeleteDesc] = useState('');
|
||||
|
||||
const {
|
||||
config,
|
||||
searchParams,
|
||||
data: dataList,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleOrderByChange,
|
||||
updateSearchParams,
|
||||
configLoaded
|
||||
} = useCommonSearchOld("rewardEventSearch");
|
||||
const {
|
||||
selectedRows,
|
||||
handleSelectRow,
|
||||
isRowSelected
|
||||
} = useTable(dataList?.list || [], {mode: 'single'});
|
||||
|
||||
// 상세보기 호출
|
||||
const handleDetailModal = async (e, id) => {
|
||||
await RewardEventDetailView(token, id).then(data => {
|
||||
setDetailData(data);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
handleModalView('detail');
|
||||
};
|
||||
|
||||
|
||||
const handleModalSubmit = async (type, param = null) => {
|
||||
switch (type) {
|
||||
case "history":
|
||||
const params = {};
|
||||
params.db_type = "MYSQL"
|
||||
params.sql_id = param.id;
|
||||
params.table_name = historyTables.rewardEvent
|
||||
|
||||
await LogHistory(token, params).then(data => {
|
||||
setHistoryData(data);
|
||||
handleModalView('history');
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
const delete_check = selectedRows.every(row => {
|
||||
const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date));
|
||||
return row.add_flag || (timeDiff < 30);
|
||||
});
|
||||
if(delete_check){
|
||||
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
handleModalView('delete');
|
||||
break;
|
||||
case "deleteConfirm":
|
||||
let list = [];
|
||||
|
||||
if(deleteDesc.length === 0){
|
||||
showToast('INPUT_REASON_EMPTY_WARNING', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
let isChecked = false;
|
||||
|
||||
selectedRows.map(data => {
|
||||
const row = dataList.list.find(row => row.id === Number(data.id));
|
||||
if(row.status !== commonStatus.wait) isChecked = true;
|
||||
list.push({
|
||||
id: data.id,
|
||||
delete_desc: deleteDesc
|
||||
});
|
||||
});
|
||||
|
||||
handleModalClose('delete');
|
||||
setDeleteDesc('');
|
||||
|
||||
if(isChecked) {
|
||||
showToast('EVENT_WARNING_DELETE', {type: alertTypes.warning});
|
||||
return;
|
||||
}
|
||||
|
||||
await withLoading(async () => {
|
||||
return await RewardEventDelete(token, list);
|
||||
}).then(data => {
|
||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||
}).catch(error => {
|
||||
|
||||
}).finally(() => {
|
||||
handleSearch(updateSearchParams);
|
||||
})
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
|
||||
<AuthModal />
|
||||
) : (
|
||||
<AnimatedPageWrapper>
|
||||
<Title>출석 보상 이벤트 관리</Title>
|
||||
<FormWrapper>
|
||||
<CommonSearchBar
|
||||
config={config}
|
||||
searchParams={searchParams}
|
||||
onSearch={(newParams, executeSearch = true) => {
|
||||
if (executeSearch) {
|
||||
handleSearch(newParams);
|
||||
} else {
|
||||
updateSearchParams(newParams);
|
||||
}
|
||||
}}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
</FormWrapper>
|
||||
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventDelete) && (
|
||||
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
|
||||
)}
|
||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
|
||||
<Button
|
||||
theme="primary"
|
||||
text="이벤트 등록"
|
||||
type="button"
|
||||
handleClick={e => {
|
||||
e.preventDefault();
|
||||
navigate('/servicemanage/rewardevent/eventregist');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ViewTableInfo>
|
||||
<TableWrapper>
|
||||
<TableStyle>
|
||||
<caption></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40">
|
||||
</th>
|
||||
<th width="80">번호</th>
|
||||
<th width="100">이벤트 상태</th>
|
||||
<th width="210">시작 일시</th>
|
||||
<th width="210">종료 일시</th>
|
||||
<th>우편 제목</th>
|
||||
<th width="110">확인 / 수정</th>
|
||||
<th width="200">히스토리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataList?.list?.map(event => (
|
||||
<Fragment key={event.row_num}>
|
||||
<tr>
|
||||
<td>
|
||||
<CheckBox name={'select'} id={event.id}
|
||||
setData={(e) => handleSelectRow(e, event)}
|
||||
checked={isRowSelected(event.id)} />
|
||||
</td>
|
||||
<td>{event.row_num}</td>
|
||||
<StatusWapper>
|
||||
<StatusLabel $status={event.status}>
|
||||
{eventStatus.map(data => data.value === event.status && data.name)}
|
||||
</StatusLabel>
|
||||
</StatusWapper>
|
||||
<td>{convertKTC(event.start_dt)}</td>
|
||||
<td>{convertKTC(event.end_dt)}</td>
|
||||
<MailTitle>{event.title}</MailTitle>
|
||||
<td>
|
||||
<Button theme="line" text="상세보기"
|
||||
handleClick={e => handleDetailModal(e, event.id)} />
|
||||
</td>
|
||||
<td><Button theme="line" text="히스토리"
|
||||
handleClick={e => handleModalSubmit('history', event)} />
|
||||
</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
</TableWrapper>
|
||||
|
||||
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
|
||||
|
||||
{/*상세*/}
|
||||
<RewardEventDetailModal
|
||||
detailView={modalState.detailModal}
|
||||
handleDetailView={() =>{
|
||||
handleModalClose('detail');
|
||||
handleSearch(updateSearchParams);
|
||||
}}
|
||||
content={detailData}
|
||||
setDetailData={setDetailData}
|
||||
/>
|
||||
|
||||
<LogDetailModal
|
||||
viewMode="changed"
|
||||
detailView={modalState.historyModal}
|
||||
handleDetailView={() => handleModalClose('history')}
|
||||
changedData={historyData}
|
||||
title="히스토리"
|
||||
/>
|
||||
|
||||
<DynamicModal
|
||||
modalType={modalTypes.childOkCancel}
|
||||
view={modalState.deleteModal}
|
||||
handleCancel={() => handleModalClose('delete')}
|
||||
handleSubmit={() => handleModalSubmit('deleteConfirm')}
|
||||
>
|
||||
<ModalInputItem>
|
||||
{t('EVENT_SELECT_DELETE')}
|
||||
<RegistInputItem>
|
||||
<TextInput
|
||||
placeholder="사유 입력"
|
||||
maxLength="30"
|
||||
value={deleteDesc}
|
||||
onChange={e => {
|
||||
if (e.target.value.length > 30) return;
|
||||
setDeleteDesc(e.target.value.trimStart())
|
||||
}}
|
||||
/>
|
||||
</RegistInputItem>
|
||||
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
||||
</ModalInputItem>
|
||||
</DynamicModal>
|
||||
</AnimatedPageWrapper>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RewardEvent;
|
||||
467
src/pages/ServiceManage/RewardEventRegist.js
Normal file
467
src/pages/ServiceManage/RewardEventRegist.js
Normal file
@@ -0,0 +1,467 @@
|
||||
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, RewardEventSingleRegist } 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 RewardEventRegist = () => {
|
||||
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 RewardEventSingleRegist(token, resultData);
|
||||
}).then((result) => {
|
||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||
}).catch(() => {
|
||||
showToast('API_FAIL', {type: alertTypes.error});
|
||||
}).finally(() => {
|
||||
callbackPage();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const callbackPage = () => {
|
||||
navigate('/servicemanage/rewardevent');
|
||||
}
|
||||
|
||||
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 RewardEventRegist;
|
||||
|
||||
@@ -5,9 +5,11 @@ export { default as ReportList } from './ReportList';
|
||||
export { default as UserBlock } from './UserBlock';
|
||||
export { default as UserBlockRegist } from './UserBlockRegist';
|
||||
export { default as Items } from './Items';
|
||||
export { default as Event } from './Event';
|
||||
export { default as EventRegist } from './EventRegist';
|
||||
export { default as RewardEvent } from './RewardEvent';
|
||||
export { default as RewardEventRegist } from './RewardEventRegist';
|
||||
export { default as LandAuction} from './LandAuction'
|
||||
export { default as BattleEvent} from './BattleEvent'
|
||||
export { default as MenuBanner} from './MenuBanner'
|
||||
export { default as MenuBannerRegist} from './MenuBannerRegist'
|
||||
export { default as Ranking} from './Ranking'
|
||||
export { default as Event} from './Event'
|
||||
|
||||
@@ -35,6 +35,7 @@ import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||
import { historyTables } from '../../assets/data/data';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const CaliumRequest = () => {
|
||||
const token = sessionStorage.getItem('token');
|
||||
@@ -114,16 +115,8 @@ const CaliumRequest = () => {
|
||||
case "logMove":
|
||||
const searchParams = {
|
||||
action: LOG_ACTION_FAIL_CALIUM_ECHO,
|
||||
start_dt: (() => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
return date;
|
||||
})(),
|
||||
end_dt: (() => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
return date;
|
||||
})(),
|
||||
start_dt: dayjs().subtract(1, 'day').startOf('day'),
|
||||
end_dt: dayjs().subtract(1, 'day').endOf('day'),
|
||||
};
|
||||
|
||||
// 복사한 데이터를 세션 스토리지에 저장
|
||||
|
||||
@@ -6,12 +6,13 @@ import { Title, FormWrapper } from '../../styles/Components';
|
||||
import { authType } from '../../assets/data';
|
||||
import { useModal, withAuth } from '../../hooks/hook';
|
||||
import { CaliTable } from '../../components/common';
|
||||
import tableInfo from '../../assets/data/pages/historyTable.json';
|
||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||
import FrontPagination from '../../components/common/Pagination/FrontPagination';
|
||||
import { INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
|
||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||
|
||||
import tableInfo from '../../assets/data/pages/historyTable.json';
|
||||
|
||||
const LogView = () => {
|
||||
const [detailData, setDetailData] = useState({});
|
||||
const [currentPage, setCurrentPage] = useState(INITIAL_CURRENT_PAGE);
|
||||
|
||||
@@ -727,11 +727,13 @@ export const SearchRow = styled.div`
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 0;
|
||||
|
||||
&:last-child {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
${props => props.direction === 'column' && css`
|
||||
&:last-child {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const TabScroll = styled.div`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as optionsConfig from '../assets/data/options';
|
||||
import { logFieldLabels } from '../assets/data/data';
|
||||
import { FieldLabels } from '../assets/data/data';
|
||||
|
||||
export const convertKTC = (dt, nation = true) => {
|
||||
if (!dt) return "";
|
||||
@@ -77,12 +77,12 @@ export const loadConfig = async (configPath) => {
|
||||
};
|
||||
|
||||
export const getFieldLabel = (key, value) => {
|
||||
if (logFieldLabels[key]) {
|
||||
return logFieldLabels[key];
|
||||
if (FieldLabels[key]) {
|
||||
return FieldLabels[key];
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && logFieldLabels[value]) {
|
||||
return logFieldLabels[value];
|
||||
if (typeof value === 'string' && FieldLabels[value]) {
|
||||
return FieldLabels[value];
|
||||
}
|
||||
|
||||
return key;
|
||||
|
||||
Reference in New Issue
Block a user