칼리움 요청 날짜처리 변경

랭킹 스케줄 추가
This commit is contained in:
2025-09-15 16:40:04 +09:00
parent 5d2e1918d1
commit 3169055646
18 changed files with 1151 additions and 43 deletions

View File

@@ -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,9 +26,9 @@ import {
UserBlockRegist,
LandAuction,
BattleEvent,
MenuBanner, MenuBannerRegist,
MenuBanner, MenuBannerRegist, Ranking,
Event
} from './pages/ServiceManage';
import MetaItemView from './pages/DataManage/MetaItemView';
const RouteInfo = () => {
return (
@@ -60,9 +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 />} />
@@ -72,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>

99
src/apis/Rank.js Normal file
View 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);
}
}
};

View File

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

View File

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

View 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"]
}
}
}

View File

@@ -209,10 +209,12 @@ 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',
}

View File

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

View File

@@ -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: {
@@ -122,7 +114,15 @@ export const menuConfig = {
itemdictionary: {
title: '아이템 백과사전 조회',
permissions: {
read: authType.businessLogRead
read: authType.itemDictionaryRead
},
view: true,
authLevel: adminAuthLevel.NONE
},
rankmanage: {
title: '랭킹 점수 관리',
permissions: {
read: authType.rankManagerRead
},
view: true,
authLevel: adminAuthLevel.NONE
@@ -172,7 +172,7 @@ export const menuConfig = {
// view: true,
// authLevel: adminAuthLevel.NONE
// },
event: {
rewardevent: {
title: '보상 이벤트 관리',
permissions: {
read: authType.eventRead,
@@ -222,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
},
}
}
};

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

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

View 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;

View File

@@ -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' }}
/>
</>
);

View File

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

View 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;

View File

@@ -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';
@@ -37,8 +36,10 @@ import UserLoginLogSearchBar, { useUserLoginLogSearch } from './UserLoginLogSear
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 {
@@ -47,7 +48,6 @@ export {
DecoSearchBar,
ItemSearchBar,
LandSearchBar,
UserSearchBar,
DailySearchBar,
CommonSearchBar,
CreditSearchBar,
@@ -97,5 +97,9 @@ export {
useAssetsIndexSearch,
ItemDictionarySearchBar,
useItemDictionarySearch,
RankManageSearchBar,
useRankManageSearch,
UserSearchBar,
useUserSearch,
};

View 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);

View File

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

View File

@@ -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'),
};
// 복사한 데이터를 세션 스토리지에 저장