diff --git a/src/assets/data/adminConstants.js b/src/assets/data/adminConstants.js index fe9cc86..6896528 100644 --- a/src/assets/data/adminConstants.js +++ b/src/assets/data/adminConstants.js @@ -5,6 +5,7 @@ export const TYPE_REGISTRY = 'regist'; export const TYPE_MODIFY = 'modify'; export const NONE = 'NONE'; export const ONE_MINUTE_MS = 60000; +export const ONE_MINUTE_SECOND = 60; export const AUCTION_MIN_MINUTE_TIME = 15; // 15분 export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT }; diff --git a/src/assets/data/types.js b/src/assets/data/types.js index 9ff8a13..cfa06c6 100644 --- a/src/assets/data/types.js +++ b/src/assets/data/types.js @@ -114,6 +114,8 @@ export const battleEventStatusType = { wait: "WAIT", register: "REGISTER", end: "END", + stop: "STOP", fail: "FAIL", cancel: "CANCEL", + running: "RUNNING", } \ No newline at end of file diff --git a/src/components/ServiceManage/modal/BattleEventModal.js b/src/components/ServiceManage/modal/BattleEventModal.js index d6fa995..283d143 100644 --- a/src/components/ServiceManage/modal/BattleEventModal.js +++ b/src/components/ServiceManage/modal/BattleEventModal.js @@ -6,31 +6,22 @@ import Loading from '../../common/Loading'; import { Title, BtnWrapper, - SearchBarAlert, SelectInput, InputLabel, DatePickerWrapper, + SearchBarAlert, SelectInput, } from '../../../styles/Components'; import { - FormHelperText, FormInput, FormLabel, - FormTextArea, - FormTextAreaWrapper, MessageWrapper, FormRowGroup, - NoticeInputRow2, - NoticeInputItem2, - BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer, - StyledSelectInput, TimeSeparator, TimeContainer, } from '../../../styles/ModuleComponents'; -import { HourList, MinuteList, modalTypes } from '../../../assets/data'; -import { DynamicModal, Modal, DateTimeRangePicker, SingleDatePicker, SingleTimePicker } from '../../common'; -import { LandAuctionModify, LandAuctionSingleRegist } from '../../../apis'; +import { modalTypes } from '../../../assets/data'; +import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common'; import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants'; -import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data'; import { useModal } from '../../../utils/hook'; import { convertKTCDate } from '../../../utils'; import { @@ -39,10 +30,9 @@ import { battleEventStatus, battleRepeatType, } from '../../../assets/data/options'; -import DatePickerComponent from '../../common/Date/DatePickerComponent'; import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle'; import { battleEventStatusType } from '../../../assets/data/types'; -import { result } from 'lodash'; +import { isValidDayRange } from '../../../utils/date'; const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => { const { t } = useTranslation(); @@ -105,6 +95,17 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se const newDate = new Date(date); + if(resultData.repeat_type !== NONE && resultData.event_end_dt){ + const endDate = new Date(resultData.event_end_dt); + const startDay = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()); + const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); + + if (endDay <= startDay) { + setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING')); + return; + } + } + setResultData(prev => ({ ...prev, event_start_dt: newDate @@ -134,9 +135,9 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se // 종료 날짜 변경 핸들러 const handleEndDateChange = (date) => { - if (!date || !resultData.start_dt) return; + if (!date || !resultData.event_start_dt) return; - const startDate = new Date(resultData.start_dt); + const startDate = new Date(resultData.event_start_dt); const endDate = new Date(date); // 일자만 비교하기 위해 년/월/일만 추출 @@ -144,22 +145,27 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); if (endDay <= startDay) { - setAlertMsg('종료일은 시작일보다 하루 이후여야 합니다.'); + setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING')); return; } - const newDate = new Date(date); - newDate.setHours(23, 59, 59, 0); - setResultData(prev => ({ ...prev, - event_end_dt: newDate + event_end_dt: endDate })); }; const handleConfigChange = (e) => { - const config = configData.find(data => data.id === e.target.value); - setResultData({ ...resultData, config_id: config.id, round_time: config.round_time }); + const config = configData.find(data => String(data.id) === String(e.target.value)); + if (config) { + setResultData({ + ...resultData, + config_id: config.id, + round_time: config.round_time + }); + } else { + console.warn('Config not found for value:', e.target.value); + } } const handleReset = () => { @@ -174,18 +180,20 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se if (!checkCondition()) return; const minAllowedTime = new Date(new Date().getTime() + 10 * 60000); - if (resultData.event_start_dt < minAllowedTime) { - setAlertMsg(t('BATTLE_EVENT_MADEL_START_DT_WARNING')); + const startDt = resultData.event_start_dt; + const endDt = resultData.event_end_dt; + if (startDt < minAllowedTime) { + setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING')); return; } - if(resultData.repeat_type !== 'NONE' && resultData.event_start_dt >= resultData.event_end_dt) { - setAlertMsg(t('LAND_AUCTION_MADEL_AUCTION_DIFF_AUCTION')) + if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) { + setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING')) return; } //화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단 - if (modalType === TYPE_MODIFY && resultData.event_start_dt < new Date()) { - setAlertMsg(t('LAND_AUCTION_MADEL_MODIFY_START')); + if (modalType === TYPE_MODIFY && startDt < new Date()) { + setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING')); return; } @@ -212,8 +220,6 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se handleModalClose('registConfirm'); if(data.result === "SUCCESS") { handleModalView('registComplete'); - }else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){ - setAlertMsg(t('LAND_AUCTION_ERROR_MODIFY_STATUS')); }else{ setAlertMsg(t('UPDATE_FAIL')); } @@ -227,8 +233,8 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se handleModalClose('registConfirm'); if(data.result === "SUCCESS") { handleModalView('registComplete'); - }else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){ - setAlertMsg(t('LAND_AUCTION_ERROR_PROGRESS')); + }else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){ + setAlertMsg(t('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING')); }else{ setAlertMsg(t('REGIST_FAIL')); } @@ -380,7 +386,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se 현재상태: {battleEventStatus.find(data => data.value === content?.status)?.name || "등록"} - {isView('registry') ? '' : t('LAND_AUCTION_MODAL_STATUS_WARNING')} + {isView('registry') ? '' : t('BATTLE_EVENT_MODAL_STATUS_WARNING')} @@ -415,7 +421,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se handleSubmit('registConfirm')} handleCancel={() => handleModalClose('registConfirm')} /> diff --git a/src/i18n.js b/src/i18n.js index c06c4e0..d215e94 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -10,6 +10,7 @@ const resources = { REGIST_COMPLTE: "등록이 완료되었습니다.", REGIST_FAIL: '등록에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', UPDATE_FAIL: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', + STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', DELETE_FAIL: '삭제에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', API_FAIL: '처리 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.', USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?', @@ -20,6 +21,7 @@ const resources = { CANCEL_COMPLETED: '취소가 완료되었습니다.', DEL_CONFIRM: "해당 아이템을 삭제하시겠습니까?", DEL_COMPLETE: '삭제가 완료되었습니다.', + STOP_COMPLETE: '중단이 완료되었습니다.', DEL_ITEM_COMPLETE: '아이템 삭제가 완료되었습니다.', REQUIRED_VALUE_CHECK: '필수값을 입력해주세요.', UPDATE_COMPLETED: '수정이 완료되었습니다.', @@ -78,7 +80,16 @@ const resources = { CHARGE_FINISH_FAIL: '충전 처리가 불가능합니다. 이미 충전이 완료되었거나, 관리자에 의해 요청 건에 대한 취소(반려) 처리된 것입니다. 해당 요청건에 대한 진행 상태를 다시 확인해 주세요.', SEARCH_LIMIT_FAIL: '인출 가능 수량 조회에 대한 요청 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.', //전투시스템 - BATTLE_EVENT_MADEL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.", + BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.", + BATTLE_EVENT_MODAL_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.", + BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.", + BATTLE_EVENT_REGIST_CONFIRM: "랜드 경매를 등록하시겠습니까?", + BATTLE_EVENT_UPDATE_CONFIRM: "랜드 경매를 수정하시겠습니까?", + BATTLE_EVENT_SELECT_DELETE: "선택된 이벤트를 삭제하시겠습니까?", + BATTLE_EVENT_SELECT_STOP: "선택된 이벤트를 중단하시겠습니까?", + BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.", + BATTLE_EVENT_STATUS_RUNNING_WARNING: "이벤트 진행중에는 중단할 수 없습니다.", + BATTLE_EVENT_MODAL_STATUS_WARNING: "이벤트가 진행중에는 변경이 불가합니다.", //파일명칭 FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx', FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx', diff --git a/src/pages/ServiceManage/BattleEvent.js b/src/pages/ServiceManage/BattleEvent.js index 6c17efd..4eaabe9 100644 --- a/src/pages/ServiceManage/BattleEvent.js +++ b/src/pages/ServiceManage/BattleEvent.js @@ -6,7 +6,7 @@ import 'react-datepicker/dist/react-datepicker.css'; import { BattleConfigView, BattleEventDelete, - BattleEventDetailView, + BattleEventDetailView, BattleEventStop, BattleEventView, BattleRewardView, } from '../../apis/Battle'; @@ -28,7 +28,7 @@ import { Pagination, ViewTableInfo, ExcelDownButton, } from '../../components/common'; -import { convertKTC, timeDiffMinute } from '../../utils'; +import { convertKTC, convertKTCDate, convertUTC, timeDiffMinute } from '../../utils'; import { BattleEventModal, LandAuctionModal, LandAuctionSearchBar } from '../../components/ServiceManage'; import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants'; import { useDataFetch, useModal, useTable, withAuth } from '../../utils/hook'; @@ -38,7 +38,8 @@ import { battleEventStatus, battleRepeatType } from '../../assets/data/options'; import BattleEventSearchBar, { useBattleEventSearch, } from '../../components/ServiceManage/searchBar/BattleEventSearchBar'; -import { getTimeOnly } from '../../utils/date'; +import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date'; +import { battleEventStatusType } from '../../assets/data/types'; const BattleEvent = () => { const token = sessionStorage.getItem('token'); @@ -53,6 +54,8 @@ const BattleEvent = () => { handleModalView, handleModalClose } = useModal({ + stopConfirm: 'hidden', + stopComplete: 'hidden', detail: 'hidden', deleteConfirm: 'hidden', deleteComplete: 'hidden' @@ -85,6 +88,41 @@ const BattleEvent = () => { data: battleRewardData } = useDataFetch(() => BattleRewardView(token), [token]); + const endTime = (start_dt, operation_time) =>{ + const startDate = new Date(start_dt); + + startDate.setSeconds(startDate.getSeconds() + operation_time); + + return startDate; + } + + const isStopMinutes = (time) => { + const givenDate = new Date(time); + + const currentDate = convertUTC(new Date()); + + // 시간만 비교하기 위해 년,월,일을 동일하게 설정 + givenDate.setFullYear(currentDate.getFullYear()); + givenDate.setMonth(currentDate.getMonth()); + givenDate.setDate(currentDate.getDate()); + + // 두 시간의 차이를 밀리초로 계산 + const timeDifference = currentDate.getTime() - givenDate.getTime(); + + // 밀리초로 변환 + const fiveMinutesInMs = 5 * 60 * 1000; + + return timeDifference < 0 && Math.abs(timeDifference) <= fiveMinutesInMs; + } + + const isRunningTime = (time, operation_time) =>{ + const startDate = new Date(time); + const endDate = new Date(startDate.getTime() + (operation_time * 1000)); + const currentDate = convertUTC(new Date()); + + return currentDate >= startDate && currentDate <= endDate; + } + const handleModalSubmit = async (type, param = null) => { switch (type) { case "regist": @@ -112,6 +150,42 @@ const BattleEvent = () => { return; } handleModalView('deleteConfirm'); + break; + case "stop": + const select_item = selectedRows[0]; + if(select_item.status === battleEventStatusType.running){ + setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING')); + return; + } + console.log(select_item) + const isStopTimeCheck = isStopMinutes(select_item.event_start_dt); + if(isStopTimeCheck){ + setAlertMsg(t('BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING')); + return; + } + if(isRunningTime(select_item.event_start_dt, select_item.event_operation_time)){ + setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING')); + return; + } + + handleModalView('stopConfirm'); + break; + case "stopConfirm": + const stop_item = selectedRows[0]; + + await BattleEventStop(token, stop_item.id, stop_item).then(data => { + handleModalClose('stopConfirm'); + if(data.result === "SUCCESS") { + handleModalView('stopComplete'); + }else if(data.result === "ERROR_BATTLE_EVENT_STATUS_START_IMPOSSIBLE"){ + setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING')); + }else{ + setAlertMsg(t('STOP_FAIL')); + } + }).catch(reason => { + setAlertMsg(t('API_FAIL')); + }); + break; case "deleteConfirm": let list = []; @@ -149,6 +223,10 @@ const BattleEvent = () => { handleModalClose('deleteComplete'); window.location.reload(); break; + case "stopComplete": + handleModalClose('stopComplete'); + window.location.reload(); + break; case "warning": setAlertMsg('') break; @@ -175,8 +253,11 @@ const BattleEvent = () => { + {/*{userInfo.auth_list?.some(auth => auth.id === authType.battleEventDelete) && (*/} + {/* handleModalSubmit('delete')} />*/} + {/*)}*/} {userInfo.auth_list?.some(auth => auth.id === authType.battleEventDelete) && ( - handleModalSubmit('delete')} /> + handleModalSubmit('stop')} /> )} {userInfo.auth_list?.some(auth => auth.id === authType.battleEventUpdate) && ( { 그룹 - 이벤트 ID - 이벤트명 + 이벤트 ID + 이벤트명 반복 - 이벤트 시작시간 - 이벤트 종료시간 + 이벤트 시작시간(KST) + 이벤트 종료시간(KST) 이벤트 상태 라운드 시간 - 라운드 수 - 핫타임 - 기간 시작일 - 기간 종료일 + 배정포드 + 라운드 수 + 핫타임 + 기간 시작일(KST) + 기간 종료일(KST) 확인 / 수정 히스토리 @@ -218,25 +300,26 @@ const BattleEvent = () => { checked={isRowSelected(battle.id)} /> {battle.group_id} - {battle.id} + {battle.event_id} {battle.event_name} {battleRepeatType.find(data => data.value === battle.repeat_type).name} - {getTimeOnly(battle.event_start_dt)} - {battle.end_time} + {getTimeOnly(convertKTCDate(battle.event_start_dt))} + {getTimeOnly(endTime(convertKTCDate(battle.event_start_dt), battle.event_operation_time))} {battleEventStatus.find(data => data.value === battle.status).name} - {battle.round_time} + {secondToMinutes(battle.round_time)}분 + {battle.reward_group_id} {battle.round_count} {battle.hot_time} - {convertKTC(battle.event_start_dt)} - {convertKTC(battle.event_end_dt)} + {getDateOnly(convertKTCDate(battle.event_start_dt))} + {getDateOnly(convertKTCDate(battle.event_end_dt))} handleModalSubmit('detail', battle.id)} /> @@ -253,13 +336,28 @@ const BattleEvent = () => { {/*상세*/} handleModalClose('detail')} content={detailData} setDetailData={setDetailData} configData={battleConfigData} rewardData={battleRewardData} /> + {/*중단 확인*/} + handleModalClose('stopConfirm')} + handleSubmit={() => handleModalSubmit('stopConfirm')} + modalText={t('BATTLE_EVENT_SELECT_STOP')} + /> + {/*중단 완료*/} + handleModalSubmit('stopComplete')} + modalText={t('STOP_COMPLETE')} + /> {/*삭제 확인*/} handleModalClose('deleteConfirm')} handleSubmit={() => handleModalSubmit('deleteConfirm')} - modalText={t('LAND_AUCTION_SELECT_DELETE')} + modalText={t('BATTLE_EVENT_SELECT_DELETE')} /> {/*삭제 완료*/}