event > rewardEvent 변경
월드이벤트(event) 추가
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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,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);
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user