toast 메시지 추가

alert 글로벌화
loading 글로벌화
This commit is contained in:
2025-04-25 15:33:21 +09:00
parent d2ac5b338e
commit 826459f304
50 changed files with 3211 additions and 2385 deletions

View File

@@ -5,16 +5,22 @@ import GlobalStyles from './styles/GlobalStyles';
import RouteInfo from './RouteInfo';
import './i18n';
import { AlertProvider } from './context/AlertProvider';
import { LoadingProvider } from './context/LoadingProvider';
function App() {
const isToken = sessionStorage.getItem('token') ? true : false;
return (
<BrowserRouter>
<GlobalStyles />
<ControllLink>{isToken ? <Link to="/main" /> : <Link to="/fail" />}</ControllLink>
<RouteInfo />
</BrowserRouter>
<AlertProvider>
<LoadingProvider>
<BrowserRouter>
<GlobalStyles />
<ControllLink>{isToken ? <Link to="/main" /> : <Link to="/fail" />}</ControllLink>
<RouteInfo />
</BrowserRouter>
</LoadingProvider>
</AlertProvider>
);
}

View File

@@ -67,7 +67,7 @@ export const BlackListRegist = async (token, params) => {
headers: { Authorization: `Bearer ${token}` },
});
return res;
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('BlacklistRegist', e);

View File

@@ -24,11 +24,39 @@ export const caliumRequestInitData = {
content: '',
}
export const ORDER_OPTIONS = {
asc: [
{ value: 'ASC', label: '오름차순' },
{ value: 'DESC', label: '내림차순' }
],
desc: [
{ value: 'DESC', label: '내림차순' },
{ value: 'ASC', label: '오름차순' }
],
};
export const PAGE_SIZE_OPTIONS = {
default: [
{ value: '50', label: '50개' },
{ value: '100', label: '100개' }
],
B: [
{ value: '500', label: '500개' },
{ value: '1000', label: '1000개' },
{ value: '5000', label: '5000개' },
{ value: '10000', label: '10000개' }
],
};
export const STATUS_STYLES = {
COMPLETE: {
background: '#58AB62',
color: 'white'
},
EXPIRATION: {
background: '#58AB62',
color: 'white'
},
WAIT: {
background: '#DEBB46',
color: 'black'
@@ -57,6 +85,10 @@ export const STATUS_STYLES = {
background: '#4287f5',
color: 'white'
},
INPROGRESS: {
background: '#4287f5',
color: 'white'
},
AUCTION_END: {
background: '#A37FB8',
color: 'white'

View File

@@ -28,4 +28,4 @@ export {
opUserSessionType,
opMailType,
} from './options'
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months} from './data'
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'

View File

@@ -10,6 +10,18 @@ export const mailSendType = [
{ value: 'DIRECT_SEND', name: '즉시 발송' },
];
export const message_type = [
{ value: 'CHATTING', name: '채팅 타입' },
{ value: 'CHATTING_TOAST', name: '채팅 + 토스트' },
];
export const sendStatus = [
{ value: 'WAIT', name: '대기' },
{ value: 'RUNNING', name: '송출중' },
{ value: 'FINISH', name: '완료' },
{ value: 'FAIL', name: '실패' },
];
export const mailSendStatus = [
{ value: 'ALL', name: '전체' },
{ value: 'WAIT', name: '대기' },

View File

@@ -0,0 +1,53 @@
{
"initialSearchParams": {
"searchContent": "",
"status": "ALL",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"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": "caliumStatus",
"col": 1
}
],
"apiInfo": {
"functionName": "CaliumRequestView",
"loadOnMount": true,
"paramsMapping": [
"searchContent",
"status",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View 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": "EventView",
"loadOnMount": true,
"paramsMapping": [
"searchTitle",
"searchContent",
"status",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,90 @@
{
"initialSearchParams": {
"searchTitle": "",
"searchContent": "",
"sendType": "ALL",
"status": "ALL",
"mailType": "ALL",
"receiveType": "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": "sendType",
"label": "발송 방식",
"optionsRef": "mailSendType",
"col": 2
},
{
"type": "select",
"id": "status",
"label": "발송 상태",
"optionsRef": "mailSendStatus",
"col": 2
},
{
"type": "select",
"id": "mailType",
"label": "우편 타입",
"optionsRef": "mailType",
"col": 2
},
{
"type": "select",
"id": "receiveType",
"label": "수신 대상",
"optionsRef": "mailReceiveType",
"col": 2
}
],
"apiInfo": {
"functionName": "MailView",
"loadOnMount": true,
"paramsMapping": [
"searchTitle",
"searchContent",
"sendType",
"status",
"mailType",
"receiveType",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,126 @@
{
"pageId": "menuBanner",
"title": "메뉴 배너 관리",
"endpoint": "/api/v1/menu/banner",
"permissions": ["battleEventRead", "battleEventUpdate", "battleEventDelete"],
"layout": {
"type": "standard",
"components": ["search", "table", "pagination"]
},
"actions": [
{
"id": "create",
"label": "배너 등록",
"type": "button",
"theme": "primary",
"permission": "battleEventUpdate",
"action": {
"type": "modal",
"target": "createModal"
}
},
{
"id": "delete",
"label": "선택 삭제",
"type": "button",
"theme": "line",
"permission": "battleEventDelete",
"requireSelection": true,
"action": {
"type": "modal",
"target": "deleteConfirmModal"
}
}
],
"modals": {
"createModal": {
"id": "createModal",
"type": "form",
"title": "메뉴 배너 등록",
"size": "medium",
"schema": "menuBannerForm",
"actions": [
{
"id": "cancel",
"label": "취소",
"type": "button",
"theme": "line",
"action": {
"type": "close"
}
},
{
"id": "submit",
"label": "등록",
"type": "button",
"theme": "primary",
"action": {
"type": "api",
"method": "POST",
"endpoint": "/api/v1/menu/banner",
"successAction": {
"type": "close",
"then": "refresh"
}
}
}
]
},
"detailModal": {
"id": "detailModal",
"type": "form",
"title": "메뉴 배너 상세",
"size": "medium",
"schema": "menuBannerForm",
"readOnly": true,
"dataSource": {
"type": "api",
"endpoint": "/api/v1/menu/banner/detail/{id}"
},
"actions": [
{
"id": "close",
"label": "확인",
"type": "button",
"theme": "line",
"action": {
"type": "close"
}
}
]
},
"deleteConfirmModal": {
"id": "deleteConfirmModal",
"type": "confirm",
"title": "배너 삭제",
"message": "선택한 배너를 삭제하시겠습니까?",
"actions": [
{
"id": "cancel",
"label": "취소",
"type": "button",
"theme": "line",
"action": {
"type": "close"
}
},
{
"id": "confirm",
"label": "삭제",
"type": "button",
"theme": "primary",
"action": {
"type": "api",
"method": "DELETE",
"endpoint": "/api/v1/menu/banner/delete",
"dataTransform": "selectedToRequestBody",
"successAction": {
"type": "close",
"then": "refresh"
}
}
}
]
}
}
}

View File

@@ -0,0 +1,176 @@
{
"modal": {
"titles": {
"create": "메뉴배너 등록",
"update": "메뉴배너 수정",
"view": "메뉴배너 상세"
},
"grid": {
"rows": 8,
"columns": 12
},
"fields": [
{
"id": "date_range",
"type": "dateTimeRange",
"label": "등록기간",
"position": { "row": 0, "col": 0, "width": 12 },
"startDateField": "start_dt",
"endDateField": "end_dt",
"startDateLabel": "시작일자",
"endDateLabel": "종료일자",
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"]
},
{
"id": "title",
"type": "text",
"label": "배너 제목",
"position": { "row": 1, "col": 0, "width": 6 },
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"],
"width": "100%"
},
{
"id": "image_ko",
"type": "imageUpload",
"label": "이미지 첨부 (KO)",
"position": { "row": 2, "col": 0, "width": 12 },
"language": "KO",
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"]
},
{
"id": "image_en",
"type": "imageUpload",
"label": "이미지 첨부 (EN)",
"position": { "row": 3, "col": 0, "width": 12 },
"language": "EN",
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"]
},
{
"id": "image_ja",
"type": "imageUpload",
"label": "이미지 첨부 (JA)",
"position": { "row": 4, "col": 0, "width": 12 },
"language": "JA",
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"]
},
{
"id": "is_link",
"type": "checkbox",
"label": "이미지 링크 여부",
"position": { "row": 5, "col": 0, "width": 12 },
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"]
},
{
"id": "link_ko",
"type": "textWithSuffix",
"label": "웹 링크",
"position": { "row": 6, "col": 0, "width": 12 },
"suffix": "KO",
"conditional": { "field": "is_link", "operator": "==", "value": true },
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"],
"width": "100%"
},
{
"id": "link_en",
"type": "textWithSuffix",
"label": "",
"position": { "row": 7, "col": 0, "width": 12 },
"suffix": "EN",
"conditional": { "field": "is_link", "operator": "==", "value": true },
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"],
"width": "100%"
},
{
"id": "link_ja",
"type": "textWithSuffix",
"label": "",
"position": { "row": 8, "col": 0, "width": 12 },
"suffix": "JA",
"conditional": { "field": "is_link", "operator": "==", "value": true },
"validations": ["required"],
"visibleOn": ["create", "update", "view"],
"editableOn": ["create", "update"],
"width": "100%"
}
],
"actions": {
"create": [
{ "id": "cancel", "label": "취소", "theme": "line", "action": "cancel" },
{ "id": "submit", "label": "등록", "theme": "primary", "action": "submit" }
],
"update": [
{ "id": "cancel", "label": "취소", "theme": "line", "action": "cancel" },
{ "id": "submit", "label": "수정", "theme": "primary", "action": "submit" }
],
"view": [
{ "id": "cancel", "label": "취소", "theme": "line", "action": "cancel" },
{ "id": "edit", "label": "수정", "theme": "primary", "action": "edit" }
]
},
"validations": {
"create": [
{
"condition": "start_dt < (new Date() + 60 * 60000)",
"message": "EVENT_TIME_LIMIT_ADD"
},
{
"condition": "end_dt <= start_dt",
"message": "DATE_START_DIFF_END_WARNING"
}
],
"update": [
{
"condition": "end_dt <= start_dt",
"message": "DATE_START_DIFF_END_WARNING"
}
]
},
"api": {
"create": {
"endpoint": "MenuBannerSingleRegist",
"errorMapping": {
"ERROR_API_FAIL": "API_FAIL",
"ERROR_REGIST_FAIL": "REGIST_FAIL"
}
},
"update": {
"endpoint": "MenuBannerUpdate",
"errorMapping": {
"ERROR_API_FAIL": "API_FAIL",
"ERROR_UPDATE_FAIL": "UPDATE_FAIL"
}
}
}
},
"initData": {
"title": "",
"is_link": false,
"start_dt": "",
"end_dt": "",
"image_list": [
{ "language": "KO", "content": "" },
{ "language": "EN", "content": "" },
{ "language": "JA", "content": "" }
],
"link_list": [
{ "language": "KO", "content": "" },
{ "language": "EN", "content": "" },
{ "language": "JA", "content": "" }
]
}
}

View File

@@ -0,0 +1,53 @@
{
"initialSearchParams": {
"searchData": "",
"status": "ALL",
"startDate": "",
"endDate": "",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "text",
"id": "searchData",
"label": "제목",
"placeholder": "제목 입력",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "status",
"label": "상태",
"optionsRef": "opMenuBannerStatus",
"col": 1
},
{
"type": "period",
"startDateId": "startDate",
"endDateId": "endDate",
"label": "기간",
"col": 1
}
],
"apiInfo": {
"functionName": "MenuBannerView",
"loadOnMount": true,
"paramsMapping": [
"searchData",
"status",
{"param": "startDate", "transform": "toISOString"},
{"param": "endDate", "transform": "toISOString"},
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,107 @@
{
"id": "menuBannerTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": [
{
"id": "delete",
"text": "선택 삭제",
"theme": "line",
"disableWhen": "noSelection",
"requiredAuth": "battleEventDelete",
"action": "delete"
},
{
"id": "register",
"text": "이미지 등록",
"theme": "primary",
"requiredAuth": "battleEventUpdate",
"action": "navigate",
"navigateTo": "/servicemanage/menubanner/menubannerregist"
}
]
},
"columns": [
{
"id": "checkbox",
"type": "checkbox",
"width": "40px",
"title": ""
},
{
"id": "row_num",
"type": "text",
"width": "70px",
"title": "번호"
},
{
"id": "status",
"type": "status",
"width": "100px",
"title": "등록 상태",
"option_name": "opMenuBannerStatus"
},
{
"id": "start_dt",
"type": "date",
"width": "200px",
"title": "시작일(KST)",
"format": {
"type": "function",
"name": "convertKTC"
}
},
{
"id": "end_dt",
"type": "date",
"width": "200px",
"title": "종료일(KST)",
"format": {
"type": "function",
"name": "convertKTC"
}
},
{
"id": "title",
"type": "text",
"title": "설명 제목"
},
{
"id": "is_link",
"type": "option",
"width": "90px",
"title": "링크여부",
"option_name": "opYNType"
},
{
"id": "detail",
"type": "button",
"width": "120px",
"title": "상세보기",
"text": "상세보기",
"action": {
"type": "modal",
"target": "detailModal",
"dataParam": {
"id": "id"
}
}
},
{
"id": "update_by",
"type": "text",
"width": "150px",
"title": "히스토리"
}
],
"sort": {
"defaultColumn": "row_num",
"defaultDirection": "desc"
}
}

View File

@@ -0,0 +1,78 @@
{
"initialSearchParams": {
"searchType": "GUID",
"searchData": "",
"email": "",
"status": "ALL",
"sanctions": "ALL",
"period": "ALL",
"orderBy": "DESC",
"pageSize": 50,
"currentPage": 1
},
"searchFields": [
{
"type": "select",
"id": "searchType",
"label": "대상",
"optionsRef": "eventStatus",
"col": 1
},
{
"type": "text",
"id": "searchData",
"placeholder": "대상 입력",
"width": "300px",
"col": 1
},
{
"type": "text",
"id": "email",
"label": "등록자",
"placeholder": "이메일 입력",
"width": "300px",
"col": 1
},
{
"type": "select",
"id": "status",
"label": "상태",
"optionsRef": "blockStatus",
"col": 2
},
{
"type": "select",
"id": "sanctions",
"label": "제재 사유",
"optionsRef": "blockSanctions",
"col": 2
},
{
"type": "select",
"id": "period",
"label": "제재 기간",
"optionsRef": "blockPeriod",
"col": 2
}
],
"apiInfo": {
"functionName": "BlackListView",
"loadOnMount": true,
"paramsMapping": [
"searchType",
"searchData",
"email",
"status",
"sanctions",
"period",
"orderBy",
"pageSize",
"currentPage"
],
"pageField": "currentPage",
"pageSizeField": "pageSize",
"orderField": "orderBy"
}
}

View File

@@ -0,0 +1,101 @@
{
"id": "userBlockTable",
"selection": {
"type": "single",
"idField": "id"
},
"header": {
"countType": "total",
"orderType": "desc",
"pageType": "default",
"buttons": [
{
"id": "delete",
"text": "선택 삭제",
"theme": "line",
"disableWhen": "noSelection",
"requiredAuth": "blackListDelete",
"action": "delete"
},
{
"id": "register",
"text": "제재 등록",
"theme": "primary",
"requiredAuth": "blackListUpdate",
"action": "navigate",
"navigateTo": "/servicemanage/userblock/userblockregist"
}
]
},
"columns": [
{
"id": "checkbox",
"type": "checkbox",
"width": "40px",
"title": ""
},
{
"id": "row_num",
"type": "text",
"width": "80px",
"title": "번호"
},
{
"id": "guid",
"type": "text",
"width": "20%",
"title": "GUID"
},
{
"id": "nickname",
"type": "text",
"width": "20%",
"title": "아바타명"
},
{
"id": "status",
"type": "status",
"width": "100px",
"title": "상태",
"option_name": "blockStatus"
},
{
"id": "period",
"type": "option",
"width": "100px",
"title": "제재 기간",
"option_name": "blockPeriod"
},
{
"id": "sanctions",
"type": "option",
"width": "250px",
"title": "제재 사유",
"option_name": "blockSanctions"
},
{
"id": "create_by",
"type": "text",
"width": "150px",
"title": "등록자"
},
{
"id": "detail",
"type": "button",
"width": "120px",
"title": "상세보기",
"text": "상세보기",
"action": {
"type": "modal",
"target": "detailModal",
"dataParam": {
"id": "id"
}
}
}
],
"sort": {
"defaultColumn": "row_num",
"defaultDirection": "desc"
}
}

View File

@@ -59,6 +59,15 @@ export const authType = {
levelDeveloper: 99999,
};
export const alertTypes = {
info: 0,
success: 1,
warning: 2,
error: 3,
confirm: 4,
confirmChildren: 5
}
export const adminAuthLevel = {
NONE: "None",
READER: "Reader",

View File

@@ -22,6 +22,9 @@ import LogViewSearchBar from './searchBar/LogViewSearchBar';
import AdminViewSearchBar from './searchBar/AdminViewSearchBar';
import CaliumRequestSearchBar from './searchBar/CaliumRequestSearchBar';
import CommonSearchBar from './searchBar/CommonSearchBar';
import useCommonSearch from './searchBar/useCommonSearch';
//etc
import ReportListSummary from './ReportListSummary';
import WhiteListSearchBar from './WhiteListRegistBar';
@@ -50,5 +53,7 @@ export {
LandAuctionSearchBar,
LandAuctionModal,
BattleEventModal,
OwnerChangeModal
OwnerChangeModal,
useCommonSearch,
CommonSearchBar
};

View File

@@ -1,7 +1,6 @@
import React, { useState, Fragment, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../common/button/Button';
import Loading from '../../common/Loading';
import {
Title,
@@ -19,10 +18,8 @@ import {
FormStatusWarning,
FormButtonContainer,
} from '../../../styles/ModuleComponents';
import { modalTypes } from '../../../assets/data';
import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils';
import {
battleEventHotTime,
@@ -31,28 +28,19 @@ import {
battleRepeatType,
} from '../../../assets/data/options';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
import { battleEventStatusType } from '../../../assets/data/types';
import { alertTypes, battleEventStatusType } from '../../../assets/data/types';
import { isValidDayRange } from '../../../utils/date';
import { useAlert } from '../../../context/AlertProvider';
import { useLoading } from '../../../context/LoadingProvider';
const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const { showToast, showModal } = useAlert();
const {withLoading} = useLoading();
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
const [isNullValue, setIsNullValue] = useState(false);
const [resultData, setResultData] = useState(initData);
useEffect(() => {
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
@@ -103,7 +91,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING'));
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
return;
}
}
@@ -147,7 +135,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING'));
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
return;
}
@@ -166,7 +154,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
round_time: config.round_time
});
} else {
console.warn('Config not found for value:', e.target.value);
showToast('Config not found for value:', e.target.value, {type: alertTypes.warning});
}
}
@@ -185,17 +173,17 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
const startDt = resultData.event_start_dt;
const endDt = resultData.event_end_dt;
if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING'));
showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
return;
}
if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING'))
showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
return;
}
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
if (modalType === TYPE_REGISTRY && startDt < new Date()) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DT_WARNING'));
showToast('BATTLE_EVENT_MODAL_START_DT_WARNING', {type: alertTypes.warning});
return;
}
@@ -204,57 +192,48 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
setResultData({ ...resultData, round_time: config.round_time });
}
handleModalView('registConfirm');
break;
case "cancel":
handleModalView('cancel');
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
showModal(isView('modify') ? 'BATTLE_EVENT_UPDATE_CONFIRM' : 'BATTLE_EVENT_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
});
break;
case "registConfirm":
setLoading(true);
if(isView('modify')){
await BattleEventModify(token, content?.id, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
await withLoading( async () => {
return await BattleEventModify(token, content?.id, resultData);
}).then(data => {
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){
setAlertMsg(t('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING'));
showToast('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING', {type: alertTypes.error});
}else{
setAlertMsg(t('UPDATE_FAIL'));
showToast('UPDATE_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
});
}
else{
await BattleEventSingleRegist(token, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
await withLoading( async () => {
return await BattleEventSingleRegist(token, resultData);
}).then(data => {
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){
setAlertMsg(t('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING'));
showToast('BATTLE_EVENT_MODAL_TIME_CHECK_WARNING', {type: alertTypes.error});
}else{
setAlertMsg(t('REGIST_FAIL'));
showToast('REGIST_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
});
}
break;
case "registComplete":
handleModalClose('registComplete');
handleReset();
window.location.reload();
break;
case "warning":
setAlertMsg('');
break;
}
}
@@ -404,7 +383,14 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
/>
:
<>
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
<Button
text="취소"
theme="line"
handleClick={() => showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleReset()
})}
/>
<Button
type="submit"
text={isView('modify') ? "수정" : "등록"}
@@ -421,38 +407,6 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
</FormButtonContainer>
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={isView('modify') ? t('BATTLE_EVENT_UPDATE_CONFIRM') : t('BATTLE_EVENT_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={isView('modify') ? t('UPDATE_COMPLETED') : t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('CANCEL_CONFIRM')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancelConfirm')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
);
};

View File

@@ -3,26 +3,30 @@ import { useState, useEffect, Fragment } from 'react';
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, Textarea, SearchBarAlert } from '../../../styles/Components';
import Button from '../../common/button/Button';
import Modal from '../../common/modal/Modal';
import { EventIsItem, EventModify, MailModify } from '../../../apis';
import { EventIsItem, EventModify } from '../../../apis';
import { authList } from '../../../store/authList';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import { authType, benItems, commonStatus, modalTypes, wellType } from '../../../assets/data';
import { authType, benItems, commonStatus, wellType } from '../../../assets/data';
import {
AppendRegistBox, AppendRegistTable, AreaBtnClose,
BtnDelete, DetailInputItem, DetailInputRow,
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState,
Item, ItemList, LangArea
} from '../../../styles/ModuleComponents';
import DynamicModal from '../../common/modal/DynamicModal';
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../../utils';
import DateTimeInput from '../../common/input/DateTimeInput';
import { useLoading } from '../../../context/LoadingProvider';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
const EventDetailModal = ({ 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);
@@ -39,15 +43,8 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
const [resource, setResource] = useState('19010001');
const [resourceCount, setResourceCount] = useState('');
const [modifyModal, setModifyModal] = useState('hidden');
const [completeModal, setCompleteModal] = useState('hidden');
const [resultData, setResultData] = useState({});
const [modalState, setModalState] = useState({
updateConfirmModal: 'hidden',
updateCompleteModal: 'hidden',
});
const [isNullValue, setIsNullValue] = useState(false);
// 과거 판단
const [isPast, setIsPast] = useState(false);
@@ -56,7 +53,6 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
const [btnValidation, setBtnValidation] = useState(false);
const [isReadOnly, setIsReadOnly] = useState(false);
const [itemCheckMsg, setItemCheckMsg] = useState('');
const [alertMsg, setAlertMsg] = useState('');
useEffect(() => {
if(content){
@@ -122,12 +118,11 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
// 아이템 추가
const handleItemList = async () => {
if(benItems.includes(item)){
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
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"){
@@ -240,45 +235,31 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
);
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case "submit":
if (!conditionCheck()) return;
handleModalView('updateConfirm');
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){
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
handleModalClose('updateConfirm');
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
return;
}
await EventModify(token, id, resultData);
handleModalClose('updateConfirm');
handleModalView('updateComplete');
break;
case "updateComplete":
handleModalClose('updateComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('');
withLoading( async () => {
return await EventModify(token, id, resultData);
}).catch(error => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
handleDetailView();
});
break;
}
}
@@ -533,28 +514,6 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
)}
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.updateConfirmModal}
modalText={t('MAIL_UPDATE_SAVE')}
handleCancel={() => handleModalClose('updateConfirm')}
handleSubmit={() => handleSubmit('updateConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.updateCompleteModal}
modalText={t('UPDATE_COMPLETED')}
handleSubmit={() => handleSubmit('updateComplete')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
</>
);
};

View File

@@ -34,25 +34,18 @@ import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType }
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils';
import { msToMinutes } from '../../../utils/date';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const { showToast, showModal } = useAlert();
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [loading, setLoading] = useState(false);
const [message_lang, setMessage_lang] = useState('KO');
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [selectLand, setSelectLand] = useState(initLandData);
const [resultData, setResultData] = useState(initData); //데이터 정보
@@ -181,41 +174,38 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
const minAllowedTime = new Date(new Date().getTime() + 5 * 60000);
if (isView('recv') && resultData.resv_start_dt < minAllowedTime) {
setAlertMsg(t('LAND_AUCTION_MODEL_RESV_START_WARNING'));
showToast('LAND_AUCTION_MODEL_RESV_START_WARNING', {type: alertTypes.warning});
return;
}
if (resultData.auction_start_dt < minAllowedTime) {
setAlertMsg(t('LAND_AUCTION_MODEL_AUCTION_START_WARNING'));
showToast('LAND_AUCTION_MODEL_AUCTION_START_WARNING', {type: alertTypes.warning});
return;
}
if(resultData.resv_start_dt >= resultData.auction_start_dt || resultData.resv_start_dt >= resultData.auction_end_dt) {
setAlertMsg(t('LAND_AUCTION_MODEL_AUCTION_DIFF_RESERVATION'))
showToast('LAND_AUCTION_MODEL_AUCTION_DIFF_RESERVATION', {type: alertTypes.warning});
return;
}
if(resultData.auction_start_dt >= resultData.auction_end_dt) {
setAlertMsg(t('LAND_AUCTION_MODEL_AUCTION_DIFF_AUCTION'))
showToast('LAND_AUCTION_MODEL_AUCTION_DIFF_AUCTION', {type: alertTypes.warning});
return;
}
const diffAuctionTime = resultData.auction_end_dt - resultData.auction_start_dt;
if(msToMinutes(diffAuctionTime) < AUCTION_MIN_MINUTE_TIME){
setAlertMsg(t('LAND_AUCTION_MODEL_MIN_TIME_WARNING'))
showToast('LAND_AUCTION_MODEL_MIN_TIME_WARNING', {type: alertTypes.warning});
return;
}
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 경매시작시간 지났을경우 차단
if (modalType === TYPE_MODIFY && resultData.auction_start_dt < new Date()) {
setAlertMsg(t('LAND_AUCTION_MADEL_MODIFY_START'));
showToast('LAND_AUCTION_MADEL_MODIFY_START', {type: alertTypes.warning});
return;
}
handleModalView('registConfirm');
break;
case "cancel":
handleModalView('cancel');
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
showModal(isView('modify') ? 'LAND_UPDATE_CONFIRM' : 'LAND_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
});
break;
case "registConfirm":
setLoading(true);
@@ -223,44 +213,38 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
if(isView('modify')){
await LandAuctionModify(token, content?.id, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_MODIFY_STATUS'));
showToast('LAND_AUCTION_ERROR_MODIFY_STATUS', {type: alertTypes.error});
}else{
setAlertMsg(t('UPDATE_FAIL'));
showToast('UPDATE_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
});
}
else{
await LandAuctionSingleRegist(token, resultData).then(data => {
setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_PROGRESS'));
showToast('LAND_AUCTION_ERROR_PROGRESS', {type: alertTypes.error});
}else if(data.result === "ERROR_AUCTION_LAND_OWNER"){
setAlertMsg(t('LAND_AUCTION_ERROR_OWNER'));
showToast('LAND_AUCTION_ERROR_OWNER', {type: alertTypes.error});
}else{
setAlertMsg(t('REGIST_FAIL'));
showToast('REGIST_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
});
}
break;
case "registComplete":
handleModalClose('registComplete');
handleReset();
window.location.reload();
break;
case "warning":
setAlertMsg('');
break;
}
}
@@ -371,7 +355,6 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
startLabel="시작 일자"
endLabel="종료 일자"
reset={resetDateTime}
setAlert={setAlertMsg}
/>
<DateTimeRangePicker
label="경매기간"
@@ -384,7 +367,6 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
startLabel="시작 일자"
endLabel="종료 일자"
reset={resetDateTime}
setAlert={setAlertMsg}
/>
{/*<NoticeInputRow2>*/}
@@ -444,7 +426,13 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
/>
:
<>
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
<Button
text="취소"
theme="line"
handleClick={() => showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleReset()
})} />
<Button
type="submit"
text={isView('modify') ? "수정" : "등록"}
@@ -462,36 +450,6 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={isView('modify') ? t('LAND_UPDATE_CONFIRM') : t('LAND_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={isView('modify') ? t('UPDATE_COMPLETED') : t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('CANCEL_CONFIRM')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancelConfirm')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
);

View File

@@ -25,28 +25,20 @@ import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminCons
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
import { battleEventStatusType } from '../../../assets/data/types';
import { alertTypes, battleEventStatusType } from '../../../assets/data/types';
import { isValidDayRange } from '../../../utils/date';
import CheckBox from '../../common/input/CheckBox';
import { LandOwnedChangesRegist, LandOwnerChangesDelete, UserInfoView } from '../../../apis';
import { useLoading } from '../../../context/LoadingProvider';
import { useAlert } from '../../../context/AlertProvider';
const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, setDetailData }) => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const [loading, setLoading] = useState(false); // 로딩 창
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
@@ -126,94 +118,87 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
case "submit":
if (!checkCondition()) return;
handleModalView('registConfirm');
showModal(isView() ? 'LAND_OWNED_CHANGES_SELECT_DELETE' : 'LAND_OWNED_CHANGES_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
});
break;
case "cancel":
handleModalView('cancel');
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleReset()
});
break;
case "user":
if(isView()) return;
const guid = resultData.user_guid;
if(!guid || guid.length !== 32){
setAlertMsg(t('WARNING_GUID_CHECK'))
showToast('WARNING_GUID_CHECK', {type: alertTypes.warning});
return;
}
setLoading(true);
await UserInfoView(token, guid).then(data => {
await withLoading(async () => {
return await UserInfoView(token, guid);
}).then(data => {
if(Object.keys(data).length === 0){
setAlertMsg(t('WARNING_GUID_CHECK'));
showToast('WARNING_GUID_CHECK', {type: alertTypes.error});
setResultData({ ...resultData, user_name: '' })
return;
}
const nickname = data.char_info.character_name;
setResultData({ ...resultData, user_name: nickname })
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
}).finally(()=>{
setLoading(false);
showToast('API_FAIL', {type: alertTypes.error});
});
break;
case "registConfirm":
setLoading(true);
if(isView()){
setLoading(false);
handleModalClose('registConfirm');
const resvDt = resultData.reservation_dt;
const now = new Date();
if(resvDt < now){
setAlertMsg(t('LAND_OWNED_CHANGES_DELETE_TIME_WARNING'));
showToast('LAND_OWNED_CHANGES_DELETE_TIME_WARNING', {type: alertTypes.warning});
handleReset();
return;
}
await LandOwnerChangesDelete(token, resultData).then(data => {
handleModalClose('registConfirm');
await withLoading(async () => {
return await LandOwnerChangesDelete(token, resultData);
}).then(data => {
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('CANCEL_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_LAND_OWNER_CHANGES_RESERVATION"){
setAlertMsg(t('LAND_OWNED_CHANGES_DELETE_STATUS_WARNING'));
showToast('LAND_OWNED_CHANGES_DELETE_STATUS_WARNING', {type: alertTypes.error});
}else{
setAlertMsg(t('DELETE_FAIL'));
showToast('DELETE_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
setLoading(false);
handleReset();
});
}else{
await LandOwnedChangesRegist(token, resultData).then(data => {
handleModalClose('registConfirm');
await withLoading(async () => {
return await LandOwnedChangesRegist(token, resultData);
}).then(data => {
if(data.result === "SUCCESS") {
handleModalView('registComplete');
showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "GUID_CHECK"){
setAlertMsg(t('WARNING_GUID_CHECK'));
showToast('WARNING_GUID_CHECK', {type: alertTypes.error});
}else if(data.result === "ERROR_LAND_OWNER_DUPLICATION"){
setAlertMsg(t('LAND_OWNER_DUPLICATION_WARNING'));
showToast('LAND_OWNER_DUPLICATION_WARNING', {type: alertTypes.error});
}else if(data.result === "ERROR_LAND_OWNER_CHANGES_DUPLICATION"){
setAlertMsg(t('LAND_OWNED_CHANGES_REGIST_DUPLICATION_WARNING'));
showToast('LAND_OWNED_CHANGES_REGIST_DUPLICATION_WARNING', {type: alertTypes.error});
}else{
setAlertMsg(t('REGIST_FAIL'));
showToast('REGIST_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
setLoading(false);
handleReset();
});
}
break;
case "registComplete":
handleModalClose('registComplete');
handleReset();
break;
case "warning":
setAlertMsg('');
break;
}
}
@@ -347,37 +332,6 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={isView() ? t('LAND_OWNED_CHANGES_SELECT_DELETE') : t('LAND_OWNED_CHANGES_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={isView() ? t('CANCEL_COMPLETED') : t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('CANCEL_CONFIRM')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancelConfirm')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
);
};

View File

@@ -76,15 +76,9 @@ const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrent
<>
{/*<CheckBox id="input-check" label="가입 신청" checked={searchData.joinCheck} setData={e => setSearchData({ ...searchData, joinCheck: e.target.checked })} />*/}
</>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
];
return <SearchBarLayout firstColumnData={searchList} />;
return <SearchBarLayout firstColumnData={searchList} direction={'column'} handleSubmit={handleSubmit} onReset={handleReset} />;
};
export default AdminViewSearchBar;

View File

@@ -0,0 +1,134 @@
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
import { Fragment } from 'react';
import { getOptionsArray } from '../../../utils';
const renderSearchField = (field, searchParams, onSearch) => {
const { type, id, label, placeholder, width, optionsRef } = field;
switch (type) {
case 'text':
return (
<>
{label && <InputLabel>{label}</InputLabel>}
<TextInput
type="text"
placeholder={placeholder || ''}
value={searchParams[id] || ''}
width={width || '100%'}
onChange={e => onSearch({ [id]: e.target.value }, false)}
/>
</>
);
case 'select':
return (
<>
{label && <InputLabel>{label}</InputLabel>}
<SelectInput
value={searchParams[id] || ''}
onChange={e => onSearch({ [id]: e.target.value }, false)}
>
{getOptionsArray(optionsRef).map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
</>
);
case 'period':
return (
<>
{label && <InputLabel>{label}</InputLabel>}
<SearchPeriod
startDate={searchParams[field.startDateId]}
handleStartDate={date => onSearch({ [field.startDateId]: date }, false)}
endDate={searchParams[field.endDateId]}
handleEndDate={date => onSearch({ [field.endDateId]: date }, false)}
/>
</>
);
case 'searchGroup':
// 검색 타입과 입력이 결합된 컴포넌트
return (
<InputGroup>
<SelectInput
value={searchParams[field.selectId] || ''}
onChange={e => onSearch({ [field.selectId]: e.target.value }, false)}
>
{getOptionsArray(field.optionsRef).map((data, index) => (
<option key={index} value={data.value}>
{data.name}
</option>
))}
</SelectInput>
<TextInput
type="text"
placeholder={
field.placeholderMapping &&
field.placeholderMapping[searchParams[field.selectId]] ?
field.placeholderMapping[searchParams[field.selectId]] :
field.placeholderMapping?.default || ''
}
value={searchParams[field.inputId] || ''}
width={field.width || '100%'}
onChange={e => onSearch({ [field.inputId]: e.target.value }, false)}
/>
</InputGroup>
);
default:
return null;
}
};
const CommonSearchBar = ({ config, searchParams, onSearch, onReset, customProps }) => {
if (!config || !config.searchFields) {
return <div>Loading search configuration...</div>;
}
const handleSubmit = event => {
event.preventDefault();
onSearch(searchParams, true);
};
// 검색 필드를 컬럼별로 분류
const firstColumnFields = config.searchFields.filter(field => field.col === 1);
const secondColumnFields = config.searchFields.filter(field => field.col === 2);
const filterField = config.searchFields.find(field => field.col === 'filter');
const firstColumnData = firstColumnFields.map((field, index) => (
<Fragment key={`first-${index}`}>
{renderSearchField(field, searchParams, onSearch)}
</Fragment>
));
const secondColumnData = secondColumnFields.length > 0 ?
secondColumnFields.map((field, index) => (
<Fragment key={`second-${index}`}>
{renderSearchField(field, searchParams, onSearch)}
</Fragment>
)) :
undefined;
const filter = filterField ?
renderSearchField(filterField, searchParams, onSearch) :
undefined;
return (
<SearchBarLayout
firstColumnData={firstColumnData}
secondColumnData={secondColumnData}
filter={filter}
direction={'column'}
onReset={onReset}
handleSubmit={handleSubmit}
{...customProps}
/>
);
};
export default CommonSearchBar;

View File

@@ -129,7 +129,7 @@ const LandInfoSearchBar = ({ searchParams, onSearch, onReset }) => {
const searchList = [
<>
<InputGroup>
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value })}>
<SelectInput value={searchParams.landType} onChange={e => onSearch({landType: e.target.value }, false)}>
{landSearchType.map((data, index) => (
<option key={index} value={data.value}>
{data.name}
@@ -141,7 +141,7 @@ const LandInfoSearchBar = ({ searchParams, onSearch, onReset }) => {
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
value={searchParams.landData}
width="300px"
onChange={e => onSearch({ landData: e.target.value })}
onChange={e => onSearch({ landData: e.target.value }, false)}
/>
</InputGroup>
</>,

View File

@@ -109,15 +109,9 @@ const LogViewSearchBar = ({ handleSearch, resultData }) => {
maxDate={new Date()}
/>
</>,
<>
<BtnWrapper $gap="8px">
<Button theme="reset" handleClick={handleReset} type="button" />
<Button theme="search" text="검색" handleClick={handleSubmit} type="submit" />
</BtnWrapper>
</>,
];
return <SearchBarLayout firstColumnData={searchList} secondColumnData={periodList} direction={'column'} />;
return <SearchBarLayout firstColumnData={searchList} secondColumnData={periodList} direction={'column'} handleSubmit={handleSubmit} onReset={handleReset} />;
};
export default LogViewSearchBar;

View File

@@ -0,0 +1,182 @@
import { useCallback, useEffect, useState } from 'react';
import { loadConfig } from '../../../utils';
import * as APIs from '../../../apis';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
export const useCommonSearch = (token, configPath) => {
const [config, setConfig] = useState(null);
const [searchParams, setSearchParams] = useState({});
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const [configLoaded, setConfigLoaded] = useState(false);
const { showToast } = useAlert();
// 설정 파일 로드
useEffect(() => {
const fetchConfig = async () => {
try {
const configData = await loadConfig(configPath);
setConfig(configData);
// 초기 검색 파라미터 설정
if (configData.initialSearchParams) {
setSearchParams(configData.initialSearchParams);
}
setConfigLoaded(true);
} catch (error) {
console.error('Error loading search configuration:', error);
}
};
fetchConfig();
}, [configPath]);
// 파라미터 값 변환 (날짜 등)
const transformParam = (param, value) => {
if (typeof param === 'object' && param.transform) {
if (param.transform === 'toISOString' && value) {
return new Date(value).toISOString();
}
}
return value;
};
// API 호출에 필요한 파라미터 준비
const prepareApiParams = useCallback((params) => {
if (!config || !config.apiInfo || !config.apiInfo.paramsMapping) {
return [token, params];
}
// 파라미터 배열 매핑
return config.apiInfo.paramsMapping.map(param => {
if (param === 'token') return token;
if (typeof param === 'object') {
return transformParam(param, params[param.param]);
}
return params[param];
});
}, [token, config]);
// 데이터 가져오기
const fetchData = useCallback(async (params) => {
if (!token || !config || !config.apiInfo || !config.apiInfo.functionName) return;
try {
setLoading(true);
const apiParams = prepareApiParams(params);
// API 호출
const functionName = config.apiInfo.functionName;
if (!APIs[functionName]) {
console.error(`API function ${functionName} not found!`);
return;
}
const result = await APIs[functionName](token, ...apiParams);
// 에러 처리
if (result.result && result.result.startsWith('ERROR_')) {
showToast('SEARCH_FAIL',{type: alertTypes.error})
}
setData(result.data || result);
return result.data || result;
} catch (error) {
console.error(`Error fetching data:`, error);
throw error;
} finally {
setLoading(false);
}
}, [token, config, prepareApiParams]);
// 초기 데이터 로드
useEffect(() => {
if (configLoaded && config && searchParams && config.apiInfo?.loadOnMount) {
fetchData(searchParams);
}
}, [configLoaded, config, searchParams, fetchData]);
// 검색 파라미터 업데이트
const updateSearchParams = useCallback((newParams) => {
setSearchParams(prev => ({
...prev,
...newParams
}));
}, []);
// 검색 처리
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
if (!config) return null;
const pageField = config.apiInfo?.pageField || 'currentPage';
const updatedParams = {
...searchParams,
...newParams,
[pageField]: newParams[pageField] || 1 // 새 검색 시 첫 페이지로 리셋
};
updateSearchParams(updatedParams);
if (executeSearch) {
return await fetchData(updatedParams);
}
return null;
}, [searchParams, fetchData, config, updateSearchParams]);
// 검색 초기화
const handleReset = useCallback(async () => {
if (!config || !config.initialSearchParams) return null;
setSearchParams(config.initialSearchParams);
return await fetchData(config.initialSearchParams);
}, [config, fetchData]);
// 페이지 변경
const handlePageChange = useCallback(async (newPage) => {
if (!config) return null;
const pageField = config.apiInfo?.pageField || 'currentPage';
return await handleSearch({ [pageField]: newPage }, true);
}, [handleSearch, config]);
// 페이지 크기 변경
const handlePageSizeChange = useCallback(async (newSize) => {
if (!config) return null;
const pageField = config.apiInfo?.pageField || 'currentPage';
const pageSizeField = config.apiInfo?.pageSizeField || 'pageSize';
return await handleSearch({
[pageSizeField]: newSize,
[pageField]: 1
}, true);
}, [handleSearch, config]);
// 정렬 방식 변경
const handleOrderByChange = useCallback(async (newOrder) => {
if (!config) return null;
const orderField = config.apiInfo?.orderField || 'orderBy';
return await handleSearch({ [orderField]: newOrder }, true);
}, [handleSearch, config]);
return {
config,
searchParams,
loading,
data,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams,
configLoaded
};
};
export default useCommonSearch;

View File

@@ -33,7 +33,7 @@ const AuthRegistBar = ({ handleRegistModalClose, $isNullValue, registData, setRe
</>,
];
return <SearchBarLayout firstColumnData={searchList} />;
return <SearchBarLayout firstColumnData={searchList} isSearch={false} direction='column' />;
};
export default AuthRegistBar;

View File

@@ -1,7 +1,6 @@
import { useState, Fragment, useEffect } from 'react';
import React, { useState, Fragment, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../common/button/Button';
import Loading from '../common/Loading';
import {
Title,
@@ -13,23 +12,20 @@ import {
FormGroup, FormHelperText, FormInput, FormLabel,
FormTextArea, FormTextAreaWrapper, MessageWrapper, FormRowGroup, FormRowInput,
} from '../../styles/ModuleComponents';
import { modalTypes, caliumRequestInitData } from '../../assets/data';
import {DynamicModal, Modal} from '../common';
import { caliumRequestInitData } from '../../assets/data';
import { Modal} from '../common';
import { CaliumLimitCount, CaliumRequestRegist } from '../../apis/Calium';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { alertTypes } from '../../assets/data/types';
const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const [loading, setLoading] = useState(false); // 로딩 창
const [modalState, setModalState] = useState({
cancelModal: 'hidden',
registConfirmModal: 'hidden',
registCompleteModal: 'hidden',
}); // 모달 관리
const {showModal, showToast } = useAlert();
const {withLoading} = useLoading();
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(caliumRequestInitData); //데이터 정보
const [maxCount, setMaxCount] = useState(0)
@@ -75,69 +71,42 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
}));
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const initData = () =>{
const handleReset = () =>{
setMaxCount(0);
setResultData(caliumRequestInitData);
setRegistView();
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case "maxCount":
setLoading(true);
await CaliumLimitCount(token, resultData).then(data => {
await withLoading(async () => {
return await CaliumLimitCount(token, resultData);
}).then(data => {
setMaxCount(data.reward_total_count);
setLoading(false);
}).catch(reason => {
console.log(reason);
setLoading(false);
setAlertMsg(t('SEARCH_LIMIT_FAIL'));
})
showToast('SEARCH_LIMIT_FAIL', {type: alertTypes.error})
});
break;
case "submit":
if (!checkCondition()) return;
handleModalView('registConfirm');
break;
case "cancel":
handleModalView('cancel');
break;
case "cancelConfirm":
initData();
handleModalClose('cancel');
setRegistView();
showModal('CALIUM_REGIST_CONFIRM',{
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
})
break;
case "registConfirm":
setLoading(true);
await CaliumRequestRegist(token, resultData).then(data => {
setLoading(false);
await withLoading(async () => {
return await CaliumRequestRegist(token, resultData);
}).then(data => {
showToast('CALIUM_REGIST_COMPLTE', {type: alertTypes.success});
}).catch(error => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
});
handleModalClose('registConfirm');
handleModalView('registComplete');
break;
case "registComplete":
initData();
handleModalClose('registComplete');
setRegistView();
window.location.reload();
break;
case "warning":
setAlertMsg('');
break;
}
}
@@ -224,7 +193,14 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
</MessageWrapper>
<BtnWrapper $gap="10px" $justify="center" $marginTop="20px">
<Button text="취소" theme="line" handleClick={() => handleSubmit('cancel')} />
<Button
text="취소"
theme="line"
handleClick={() => showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleReset()
})}
/>
<Button
type="submit"
text="등록"
@@ -239,37 +215,6 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={t('CALIUM_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={t('CALIUM_REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('CALIUM_REGIST_CANCEL')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancelConfirm')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
);
};

View File

@@ -0,0 +1,112 @@
import { DetailMessage, TableStyle, TableWrapper } from '../../../styles/Components';
import { StatusLabel } from '../../../styles/ModuleComponents';
import { Button, CheckBox } from '../index';
import { convertKTC, getOptionsArray } from '../../../utils';
import { styled } from 'styled-components';
const CaliTable = ({
columns,
data,
selectedRows = [],
onSelectRow,
onAction,
refProp
}) => {
const renderCell = (column, item) => {
const { type, id, option_name, format, action } = column;
const value = item[id];
const options = getOptionsArray(option_name);
switch (type) {
case 'text':
return value;
case 'date':
return convertKTC(value);
case 'status':
const statusOption = options.find(opt => opt.value === value);
return (
<StatusWapper>
<StatusLabel $status={value}>
{statusOption ? statusOption.name : value}
</StatusLabel>
</StatusWapper>
);
case 'button':
return (
<Button
theme="line"
text={column.text || "액션"}
handleClick={() => onAction(id, item)}
/>
);
case 'checkbox':
return (
<CheckBox
name={column.name || 'select'}
id={item.id}
setData={(e) => onSelectRow(e, item)}
checked={selectedRows.some(row => row.id === item.id)}
/>
);
case 'option':
const dataOption = options.find(opt => opt.value === value);
return (
dataOption ? dataOption.name : value
);
case "link":
return (
<DetailMessage onClick={() => onAction(action)}>
{value.content.length > 20 ? value.content.slice(0, 20) + '...' : value.content || ''}
</DetailMessage>
);
default:
return value;
}
};
return (
<TableWrapper>
<TableStyle ref={refProp}>
<caption></caption>
<thead>
<tr>
{columns.map((column, index) => (
<th key={index} width={column.width || 'auto'}>
{column.title}
</th>
))}
</tr>
</thead>
<tbody>
{data?.map((item, rowIndex) => (
<tr key={rowIndex}>
{columns.map((column, colIndex) => (
<td key={colIndex}>
{renderCell(column, item)}
</td>
))}
</tr>
))}
</tbody>
</TableStyle>
</TableWrapper>
);
};
export default CaliTable;
const StatusWapper = styled.div`
display: flex;
gap: 0.35rem;
align-items: center;
justify-content: center;
`;

View File

@@ -8,6 +8,8 @@ import {
} from '../../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../../assets/data';
import { useTranslation } from 'react-i18next';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
const DateTimeRangePicker = ({
label,
@@ -19,10 +21,11 @@ const DateTimeRangePicker = ({
disabled,
startLabel = '시작 일자',
endLabel = '종료 일자',
reset = false,
setAlert
reset = false
}) => {
const { t } = useTranslation();
const { showToast } = useAlert();
const [startHour, setStartHour] = useState('00');
const [startMin, setStartMin] = useState('00');
const [endHour, setEndHour] = useState('00');
@@ -64,7 +67,7 @@ const DateTimeRangePicker = ({
newDate.setHours(parseInt(endHour), parseInt(endMin));
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
showToast('TIME_START_DIFF_END', {type: alertTypes.warning});
newDate = new Date(startDate);
}
@@ -99,7 +102,7 @@ const DateTimeRangePicker = ({
}
if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END'));
showToast('TIME_START_DIFF_END', {type: alertTypes.warning});
newDate = new Date(startDate)
}

View File

@@ -2,7 +2,7 @@ import { styled } from 'styled-components';
import { TextInput, SelectInput, SearchBarAlert, BtnWrapper } from '../../../styles/Components';
import Button from '../button/Button';
const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit }) => {
const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit, isSearch = true }) => {
return (
<SearchbarStyle direction={direction}>
<SearchRow>
@@ -22,12 +22,14 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
{filter}
</SearchRow>
)}
<SearchRow>
<BtnWrapper $gap="8px">
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
<Button theme="reset" handleClick={onReset} type="button" />
</BtnWrapper>
</SearchRow>
{isSearch &&
<SearchRow>
<BtnWrapper $gap="8px">
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
<Button theme="reset" handleClick={onReset} type="button" />
</BtnWrapper>
</SearchRow>
}
</SearchbarStyle>
);
};

View File

@@ -0,0 +1,80 @@
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import { authList } from '../../../store/authList';
import { authType } from '../../../assets/data';
import { Button, ExcelDownButton, ViewTableInfo } from '../index';
const TableHeader = ({
config,
tableRef,
total,
total_all,
handleOrderBy,
handlePageSize,
selectedRows = [],
onAction,
navigate
}) => {
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const handleButtonClick = (button, e) => {
e?.preventDefault();
if (button.action === 'navigate' && button.navigateTo && navigate) {
navigate(button.navigateTo);
return;
}
if (onAction) {
onAction(button.action, button.id);
}
};
const renderButton = (button, index) => {
const hasAuth = button.requiredAuth ?
userInfo.auth_list?.some(auth => auth.id === authType[button.requiredAuth]) :
true;
if (!hasAuth) return null;
if (button.component === 'ExcelDownButton') {
return (
<ExcelDownButton
key={index}
tableRef={tableRef}
fileName={button.props?.fileName ? t(button.props.fileName) : ''}
/>
);
}
const buttonTheme = button.disableWhen === 'noSelection' && selectedRows.length === 0
? 'disable'
: button.theme;
return (
<Button
key={index}
theme={buttonTheme}
text={button.text}
handleClick={(e) => handleButtonClick(button, e)}
/>
);
};
return (
<ViewTableInfo
total={total}
total_all={total_all}
handleOrderBy={handleOrderBy}
handlePageSize={handlePageSize}
orderType={config.orderType}
pageType={config.pageType}
countType={config.countType}
>
{config.buttons.map(renderButton)}
</ViewTableInfo>
);
};
export default TableHeader;

View File

@@ -4,28 +4,28 @@ import {
SelectInput,
TableInfo,
} from '../../../styles/Components';
import { ViewTitleCountType } from '../../../assets/data';
import { ORDER_OPTIONS, PAGE_SIZE_OPTIONS, ViewTitleCountType } from '../../../assets/data';
import { TitleItem, TitleItemLabel, TitleItemValue } from '../../../styles/ModuleComponents';
const ViewTableInfo = ({children, total, total_all, orderType, handleOrderBy, pageType, handlePageSize, countType = ViewTitleCountType.total}) => {
const ViewTableInfo = ({
children,
total,
total_all,
orderType = 'desc',
handleOrderBy,
pageType = 'default',
handlePageSize,
countType = ViewTitleCountType.total
}) => {
return (
<TableInfo>
{total !== undefined && total_all !== undefined &&
<ListCount>
{ countType === ViewTitleCountType.total && `총 : ${total ?? 0} 건 / ${total_all ?? 0}`}
{ countType === ViewTitleCountType.calium &&
<>
<TitleItem>
<TitleItemLabel>누적 충전</TitleItemLabel>
<TitleItemValue color='#b7e0c3' fontWeight='bold'>{total_all ?? 0}</TitleItemValue>
</TitleItem>
<TitleItem>
<TitleItemLabel>잔여 수량</TitleItemLabel>
<TitleItemValue color='#B39063' fontWeight='bold'>{total ?? 0}</TitleItemValue>
</TitleItem>
</>
}
</ListCount>}
<ListCount>
{COUNT_TYPE_RENDERERS[countType] ?
COUNT_TYPE_RENDERERS[countType](total, total_all) :
COUNT_TYPE_RENDERERS[ViewTitleCountType.total](total, total_all)}
</ListCount>
}
<ListOption>
<OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} />
<PageSelect pageType={pageType} handlePageSize={handlePageSize} />
@@ -35,36 +35,44 @@ const ViewTableInfo = ({children, total, total_all, orderType, handleOrderBy, pa
);
};
const OrderBySelect = ({orderType, handleOrderBy}) => {
return(
orderType === "asc" ?
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
<option value="ASC">오름차순</option>
<option value="DESC">내림차순</option>
</SelectInput>
:
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
<option value="DESC">내림차순</option>
<option value="ASC">오름차순</option>
</SelectInput>
);
}
const COUNT_TYPE_RENDERERS = {
[ViewTitleCountType.total]: (total, total_all) => `총 : ${total ?? 0} 건 / ${total_all ?? 0}`,
[ViewTitleCountType.calium]: (total, total_all) => (
<>
<TitleItem>
<TitleItemLabel>누적 충전</TitleItemLabel>
<TitleItemValue color='#b7e0c3' fontWeight='bold'>{total_all ?? 0}</TitleItemValue>
</TitleItem>
<TitleItem>
<TitleItemLabel>잔여 수량</TitleItemLabel>
<TitleItemValue color='#B39063' fontWeight='bold'>{total ?? 0}</TitleItemValue>
</TitleItem>
</>
),
};
const PageSelect = ({pageType, handlePageSize}) => {
return(
pageType === "B" ?
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="5000">5000</option>
<option value="10000">10000</option>
</SelectInput>
:
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}>
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
const OrderBySelect = ({ orderType, handleOrderBy }) => {
const options = ORDER_OPTIONS[orderType] || ORDER_OPTIONS.desc;
return (
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
{options.map(option => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</SelectInput>
);
}
};
const PageSelect = ({ pageType, handlePageSize }) => {
const options = PAGE_SIZE_OPTIONS[pageType] || PAGE_SIZE_OPTIONS.default;
return (
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}>
{options.map(option => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</SelectInput>
);
};
export default ViewTableInfo;

View File

@@ -0,0 +1,156 @@
import React, { useEffect, useState } from 'react';
import styled, { keyframes, css, createGlobalStyle } from 'styled-components';
import { alertTypes } from '../../../assets/data/types';
const ToastAlert = ({ id, message, type = alertTypes.info, position = 'top-center', onClose }) => {
const [isVisible, setIsVisible] = useState(false);
const handleClose = () => {
setIsVisible(true);
setTimeout(() => {
onClose();
}, 300);
};
return (
<ToastContainer $type={type} $position={position} $isVisible={isVisible}>
<IconWrapper $type={type}>
<ToastIcon type={type} />
</IconWrapper>
<ToastMessage>{message}</ToastMessage>
<CloseButton onClick={handleClose}>×</CloseButton>
</ToastContainer>
);
};
const ToastIcon = ({ type }) => {
switch (type) {
case alertTypes.success:
return <span></span>;
case alertTypes.error:
return <span></span>;
case alertTypes.warning:
return <span></span>;
case alertTypes.info:
default:
return <span></span>;
}
};
const fadeIn = keyframes`
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
`;
const fadeOut = keyframes`
from { opacity: 1; transform: translateX(-50%) translateY(0); }
to { opacity: 0; transform: translateX(-50%) translateY(-20px); }
`;
// 위치에 따른 스타일 지정 함수
const getPositionStyle = (position) => {
const positions = {
'top-left': css`
top: 20px;
left: 20px;
`,
'top-center': css`
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(0);
`,
'top-right': css`
top: 20px;
right: 20px;
`,
'bottom-left': css`
bottom: 20px;
left: 20px;
`,
'bottom-center': css`
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(0);
`,
'bottom-right': css`
bottom: 20px;
right: 20px;
`
};
return positions[position] || positions['top-center'];
};
// 타입에 따른 스타일 지정 함수
const getTypeStyle = (type) => {
const types = {
[alertTypes.success]: css`
background-color: #d4edda;
color: #155724;
border-color: #c3e6cb;
`,
[alertTypes.error]: css`
background-color: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
`,
[alertTypes.warning]: css`
background-color: #fff3cd;
color: #856404;
border-color: #ffeeba;
`,
[alertTypes.info]: css`
background-color: #d1ecf1;
color: #0c5460;
border-color: #bee5eb;
`
};
return types[type] || types['info'];
};
const ToastContainer = styled.div`
position: fixed;
display: flex;
align-items: center;
min-width: 250px;
max-width: 450px;
padding: 12px 15px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
z-index: 9999;
animation: ${props => props.$isExiting ? fadeOut : fadeIn} 0.3s ease forwards;
${props => getPositionStyle(props.$position)}
${props => getTypeStyle(props.$type)}
`;
const IconWrapper = styled.div`
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
`;
const ToastMessage = styled.div`
flex: 1;
padding-right: 10px;
`;
const CloseButton = styled.button`
background: transparent;
border: none;
font-size: 18px;
cursor: pointer;
color: inherit;
opacity: 0.7;
&:hover {
opacity: 1;
}
`;
export default ToastAlert;

View File

@@ -14,10 +14,14 @@ import Pagination from './Pagination/Pagination';
import DynamoPagination from './Pagination/DynamoPagination';
import FrontPagination from './Pagination/FrontPagination';
import ViewTableInfo from './Table/ViewTableInfo';
import TableHeader from './Table/TableHeader';
import Loading from './Loading';
import DownloadProgress from './DownloadProgress';
import CDivider from './CDivider';
import TopButton from './button/TopButton';
import CaliTable from './Custom/CaliTable'
export {
DatePickerComponent,
DateTimeRangePicker,
@@ -41,10 +45,12 @@ export { DateTimeInput,
Modal,
Pagination,
ViewTableInfo,
TableHeader,
Loading,
CDivider,
TopButton,
DynamoPagination,
FrontPagination,
DownloadProgress
DownloadProgress,
CaliTable
};

View File

@@ -53,10 +53,6 @@ const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, c
);
case modalTypes.childOkCancel:
return (
// <ModalWrapper view={view} modalText={modalText} handleCancel={handleCancel} children={children} >
// <CancelButton handleClick={handleCancel} />
// <OkButton handleClick={handleSubmit} />
// </ModalWrapper>
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCancel} />

View File

@@ -0,0 +1,155 @@
import React, { createContext, useContext, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import DynamicModal from '../components/common/modal/DynamicModal';
import { modalTypes } from '../assets/data';
import ToastAlert from '../components/common/alert/ToastAlert';
import { alertTypes } from '../assets/data/types';
const AlertContext = createContext();
export const AlertProvider = ({ children }) => {
const { t } = useTranslation();
// 모달 알림 상태
const [modalState, setModalState] = useState({
isVisible: false,
message: '',
type: alertTypes.info,
onConfirm: null,
onCancel: null,
children: null
});
// 토스트 알림 상태 (여러 개를 관리하기 위해 배열 사용)
const [toasts, setToasts] = useState([]);
// 토스트 알림 표시 함수
const showToast = useCallback((message, options = {}) => {
const {
type = alertTypes.info,
duration = 5000,
translateKey = true,
position = 'top-center' // 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'
} = options;
const id = Date.now();
const newToast = {
id,
message: translateKey ? t(message) : message,
type,
duration,
position
};
setToasts(prev => [...prev, newToast]);
// 자동 소멸
if (duration > 0) {
setTimeout(() => {
removeToast(id);
}, duration);
}
}, [t]);
// 토스트 알림 제거 함수
const removeToast = useCallback((id) => {
setToasts(prev => prev.filter(toast => toast.id !== id));
}, []);
// 모달 알림 표시 함수
const showModal = useCallback((message, options = {}) => {
const {
type = alertTypes.info,
onConfirm = null,
onCancel = null,
translateKey = true,
children = null
} = options;
setModalState({
isVisible: true,
message: translateKey ? t(message) : message,
type,
onConfirm,
onCancel,
children
});
}, [t]);
// 모달 알림 숨기기 함수
const hideModal = useCallback(() => {
setModalState(prev => ({
...prev,
isVisible: false
}));
}, []);
// 모달 확인 핸들러
const handleConfirm = useCallback(() => {
if (modalState.onConfirm) {
modalState.onConfirm();
}
hideModal();
}, [modalState.onConfirm, hideModal]);
// 모달 취소 핸들러
const handleCancel = useCallback(() => {
if (modalState.onCancel) {
modalState.onCancel();
}
hideModal();
}, [modalState.onCancel, hideModal]);
// 모달 타입에 따른 모달 타입 결정
const getModalType = () => {
switch (modalState.type) {
case alertTypes.confirm:
return modalTypes.confirmOkCancel;
case alertTypes.confirmChildren:
return modalTypes.childOkCancel;
case alertTypes.success:
case alertTypes.warning:
case alertTypes.error:
case alertTypes.info:
default:
return modalTypes.completed;
}
};
return (
<AlertContext.Provider value={{ showToast, removeToast, showModal, hideModal }}>
{children}
{/* 토스트 알림 컨테이너 */}
<div className="toast-container">
{toasts.map(toast => (
<ToastAlert
key={toast.id}
id={toast.id}
message={toast.message}
type={toast.type}
position={toast.position}
onClose={() => removeToast(toast.id)}
/>
))}
</div>
{/* 모달 알림 */}
<DynamicModal
modalType={getModalType()}
view={modalState.isVisible ? 'view' : 'hidden'}
modalText={modalState.message}
handleSubmit={handleConfirm}
handleCancel={modalState.type === alertTypes.confirm || modalState.type === alertTypes.confirmChildren ? handleCancel : null}
children={modalState.children}
/>
</AlertContext.Provider>
);
};
export const useAlert = () => {
const context = useContext(AlertContext);
if (!context) {
throw new Error('useAlert must be used within an AlertProvider');
}
return context;
};

View File

@@ -0,0 +1,37 @@
import React, { createContext, useContext, useState } from 'react';
import Loading from '../components/common/Loading';
const LoadingContext = createContext();
export const LoadingProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(false);
const showLoading = () => setIsLoading(true);
const hideLoading = () => setIsLoading(false);
const withLoading = async (asyncFunction) => {
const startTime = Date.now();
showLoading();
try {
return await asyncFunction();
} finally {
const processTime = Date.now() - startTime;
if(processTime < 1000){
await new Promise(resolve => setTimeout(resolve, 500));
}
hideLoading();
}
};
return (
<LoadingContext.Provider value={{ isLoading, showLoading, hideLoading, withLoading }}>
{children}
{isLoading && <Loading />}
</LoadingContext.Provider>
);
};
export const useLoading = () => useContext(LoadingContext);

View File

@@ -13,7 +13,8 @@ const resources = {
UPDATE_FAIL: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
DELETE_FAIL: '삭제에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
API_FAIL: '처리 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.',
API_FAIL: '처리 중 오류가 발생하였습니다. 새로고침 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.',
SEARCH_FAIL: '조회 중 에러가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.',
USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?',
USER_GM_CHANGE: 'GM 권한을 변경하시겠습니까?',
USER_GM_CHANGE_COMPLETE: '권한변경을 완료하였습니다.',
@@ -42,6 +43,9 @@ const resources = {
WARNING_EMAIL_CHECK: '이메일을 확인해주세요.',
WARNING_TYPE_CHECK: '타입을 확인해주세요.',
DATE_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.",
//table
TABLE_ITEM_DELETE_TITLE: "선택 삭제",
TABLE_BUTTON_DETAIL_TITLE: "상세보기",
//db
LOG_MEMORY_LIMIT_WARNING: '데이터가 너무 많아 조회할 수 없습니다.\n조회조건 조정 후 다시 조회해주세요.',
LOG_MONGGDB_QUERY_WARNING: '조회 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.\n오류가 지속될 경우, 담당자에게 문의해주세요.',
@@ -89,7 +93,13 @@ const resources = {
MAIL_ITEM_ADD_DUPL: '이미 추가된 아이템입니다. 삭제 후 다시 추가해주세요.',
MAIL_ITEM_ADD_BEN: '첨부 할 수 없는 아이템입니다.',
MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING: '첨부 가능한 칼리움 수량을 초과 하였습니다.',
MAIL_SEND_STATUS_WARNING: '발송 처리가 완료된 우편은 삭제할 수 없습니다.',
MAIL_REGIST_CONFIRM: '우편을 등록하시겠습니까?',
MAIL_REGIST_COMPLETE: '우편이 정상 등록되었습니다.',
MAIL_REGIST_CANCEL: "우편 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
MAIL_CANCEL: '우편 등록이 취소되었습니다.',
//인게임 메시지
BOARD_DELETE_CONFIRM: "선택된 인게임 메세지를 삭제하시겠습니까?\r\n삭제 시 설정 정보가 제거됩니다.",
//칼리움 요청
CHARGE_COMPLTED: '해당 건에 대한 충전 처리가 완료되었습니다.',
CALIUM_CHARGE_CONFIRM: '선택건에 대한 충전을 진행하시겠습니까?',
@@ -110,10 +120,17 @@ const resources = {
BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.",
BATTLE_EVENT_STATUS_RUNNING_WARNING: "이벤트 진행중에는 중단할 수 없습니다.",
BATTLE_EVENT_MODAL_STATUS_WARNING: "이벤트가 중단일때만 수정이 가능합니다.",
//메뉴
//메뉴 배너
MENU_BANNER_TITLE: "메뉴 배너 관리",
MENU_BANNER_CREATE: "메뉴 배너 등록",
MENU_BANNER_REGIST_CONFIRM: "배너를 등록하시겠습니까?",
MENU_BANNER_SELECT_DELETE: "선택된 배너를 삭제하시겠습니까?",
MENU_BANNER_REGIST_CANCEL: "배너 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
// 이용자 제재
USER_BLOCK_VALIDATION_WARNING: '유효성 체크가 통과되지 않은 항목이 존재합니다.\r\n수정 후 재등록 해주세요.',
USER_BLOCK_REGIST_DUPLE_WARNING: '이미 제재가 등록된 유저입니다.',
USER_BLOCK_DELETE_CONFIRM: '제재 대상을 삭제하시겠습니까?\r\n삭제 시 제재가 해제됩니다.',
USER_BLOCK_REGIST_CONFIRM: '이용자 제재 명단에\r\n등록하시겠습니까?',
//파일
FILE_IMAGE_EXTENSION_WARNING: "png, jpg 확장자의 이미지만 업로드 가능합니다.",
FILE_IMAGE_UPLOAD_ERROR: "이미지 업로드 중 오류가 발생했습니다.",

View File

@@ -29,13 +29,15 @@ import LandInfoSearchBar, { useLandInfoSearch } from '../../components/ServiceMa
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
import OwnerChangeModal from '../../components/ServiceManage/modal/OwnerChangeModal';
import { opLandInfoStatusType } from '../../assets/data/options';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
const LandInfoView = () => {
const token = sessionStorage.getItem('token');
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const {showToast} = useAlert();
const [detailData, setDetailData] = useState({});
@@ -48,7 +50,6 @@ const LandInfoView = () => {
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
@@ -80,7 +81,7 @@ const LandInfoView = () => {
setModalType('regist');
const selectRow = selectedRows[0];
if(!selectRow.owned) {
setAlertMsg(t('LAND_OWNED_CHANGES_WARNING'))
showToast('LAND_OWNED_CHANGES_WARNING', {type:alertTypes.warning});
return;
}
const owner_changes = selectRow.owner_changes;
@@ -147,15 +148,12 @@ const LandInfoView = () => {
// // fetchData(option);
// window.location.reload();
// break;
case "warning":
setAlertMsg('')
break;
}
}
const handleDetailView = () => {
handleModalClose('detail');
handleSearch();
handleSearch(updateSearchParams);
removeSelectedRows();
}
@@ -235,14 +233,12 @@ const LandInfoView = () => {
</>
}
<OwnerChangeModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleDetailView()} content={detailData} setDetailData={setDetailData} />
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
<OwnerChangeModal
modalType={modalType}
detailView={modalState.detailModal}
handleDetailView={() => handleDetailView()}
content={detailData}
setDetailData={setDetailData}
/>
</>
);

View File

@@ -38,13 +38,17 @@ import BattleEventSearchBar, {
useBattleEventSearch,
} from '../../components/ServiceManage/searchBar/BattleEventSearchBar';
import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date';
import { battleEventStatusType } from '../../assets/data/types';
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
const BattleEvent = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const { showToast, showModal } = useAlert();
const {withLoading} = useLoading();
const [detailData, setDetailData] = useState({});
@@ -53,13 +57,8 @@ const BattleEvent = () => {
handleModalView,
handleModalClose
} = useModal({
stopConfirm: 'hidden',
stopComplete: 'hidden',
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
@@ -141,50 +140,61 @@ const BattleEvent = () => {
return timeDiff < 3;
});
if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.warning});
return;
}
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
showToast('LAND_AUCTION_DELETE_STATUS_WARNING', {type: alertTypes.warning});
return;
}
handleModalView('deleteConfirm');
showModal('BATTLE_EVENT_SELECT_DELETE', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break;
case "stop":
const select_item = selectedRows[0];
if(select_item.status === battleEventStatusType.running){
setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING'));
showToast('BATTLE_EVENT_STATUS_RUNNING_WARNING', {type: alertTypes.warning});
return;
}
const isStopTimeCheck = isStopMinutes(select_item.event_start_dt);
if(isStopTimeCheck){
setAlertMsg(t('BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING'));
showToast('BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING', {type: alertTypes.warning});
return;
}
if(isRunningTime(select_item.event_start_dt, select_item.event_operation_time)){
setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING'));
showToast('BATTLE_EVENT_STATUS_RUNNING_WARNING', {type: alertTypes.warning});
return;
}
handleModalView('stopConfirm');
showModal('BATTLE_EVENT_SELECT_STOP', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('stopConfirm')
});
break;
case "stopConfirm":
const stop_item = selectedRows[0];
await BattleEventStop(token, stop_item.id, stop_item).then(data => {
handleModalClose('stopConfirm');
await withLoading(async () => {
return await BattleEventStop(token, stop_item.id, stop_item);
}).then(data => {
if(data.result === "SUCCESS") {
handleModalView('stopComplete');
showToast('STOP_COMPLETE', {type: alertTypes.success});
handleSearch(updateSearchParams);
}else if(data.result === "ERROR_BATTLE_EVENT_STATUS_START_IMPOSSIBLE"){
setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING'));
showToast('BATTLE_EVENT_STATUS_RUNNING_WARNING', {type: alertTypes.error});
}else{
setAlertMsg(t('STOP_FAIL'));
showToast('STOP_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
});
break;
case "deleteConfirm":
let list = [];
let isChecked = false;
@@ -198,35 +208,24 @@ const BattleEvent = () => {
});
if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
handleModalClose('deleteConfirm');
showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
return;
}
await BattleEventDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") {
handleModalView('deleteComplete');
showToast('DEL_COMPLETE', {type: alertTypes.success});
handleSearch(updateSearchParams);
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
showToast('LAND_AUCTION_ERROR_DELETE_STATUS', {type: alertTypes.error});
}else{
setAlertMsg(t('DELETE_FAIL'));
showToast('DELETE_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
});
break;
case "deleteComplete":
handleModalClose('deleteComplete');
window.location.reload();
break;
case "stopComplete":
handleModalClose('stopComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
}
}
@@ -332,45 +331,19 @@ const BattleEvent = () => {
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
{/*상세*/}
<BattleEventModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} configData={battleConfigData} rewardData={battleRewardData} />
<BattleEventModal
modalType={modalType}
detailView={modalState.detailModal}
handleDetailView={() =>{
handleModalClose('detail');
handleSearch(updateSearchParams);
}}
content={detailData}
setDetailData={setDetailData}
configData={battleConfigData}
rewardData={battleRewardData}
/>
{/*중단 확인*/}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.stopConfirmModal}
handleCancel={() => handleModalClose('stopConfirm')}
handleSubmit={() => handleModalSubmit('stopConfirm')}
modalText={t('BATTLE_EVENT_SELECT_STOP')}
/>
{/*중단 완료*/}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.stopCompleteModal}
handleSubmit={() => handleModalSubmit('stopComplete')}
modalText={t('STOP_COMPLETE')}
/>
{/*삭제 확인*/}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.deleteConfirmModal}
handleCancel={() => handleModalClose('deleteConfirm')}
handleSubmit={() => handleModalSubmit('deleteConfirm')}
modalText={t('BATTLE_EVENT_SELECT_DELETE')}
/>
{/*삭제 완료*/}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.deleteCompleteModal}
handleSubmit={() => handleModalSubmit('deleteComplete')}
modalText={t('DEL_COMPLETE')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
)
};

View File

@@ -24,15 +24,21 @@ import { BoardInfoModal, BoardRegistModal } from '../../components/ServiceManage
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { useNavigate } from 'react-router-dom';
import { convertKTC } from '../../utils';
import { convertKTC, timeDiffMinute } from '../../utils';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data';
import { authType, landAuctionStatusType } from '../../assets/data';
import { useAlert } from '../../context/AlertProvider';
import { message_type, sendStatus } from '../../assets/data/options';
import { BattleEventDelete, BattleEventDetailView, BattleEventStop } from '../../apis/Battle';
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
import { useModal, useTable, withAuth } from '../../hooks/hook';
import { useLoading } from '../../context/LoadingProvider';
const Board = () => {
const navigate = useNavigate();
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert()
const {withLoading} = useLoading();
const [isCopyData, setIsCopyData] = useState(false);
@@ -41,21 +47,12 @@ const Board = () => {
const [registView, setRegistView] = useState('hidden');
const [detailData, setDetailData] = useState('');
const [detailId, setDetailId] = useState('');
const [selectedRow, setSelectedRow] = useState([]);
const [deleteModalClose, setDeleteModalClose] = useState('hidden');
const [confirmModalClose, setConfirmModalClose] = useState('hidden');
const message_type = [
{ value: 'CHATTING', name: '채팅 타입' },
{ value: 'CHATTING_TOAST', name: '채팅 + 토스트' },
];
const sendStatus = [
{ value: 'WAIT', name: '대기' },
{ value: 'RUNNING', name: '송출중' },
{ value: 'FINISH', name: '완료' },
{ value: 'FAIL', name: '실패' },
];
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList || [], {mode: 'single'});
const fetchData = async () => {
setDataList(await NoticeListView(token));
@@ -65,201 +62,136 @@ const Board = () => {
fetchData();
}, []);
// 체크박스 선택 리스트
const handleSelectCheckBox = e => {
let list = [...selectedRow];
if (e.target.checked) {
list.push(e.target.id);
setSelectedRow(list);
} else {
const filterList = list.filter(data => e.target.id !== data);
setSelectedRow(filterList);
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "detail":
await NoticeDetailView(token, param.id).then(data => {
setDetailData(data);
setDetailId(param.id);
setDetailView('view');
});
break;
case "delete":
if(selectedRows.length === 0) return;
showModal('BOARD_DELETE_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break;
case "deleteConfirm":
let list = [];
selectedRows.map(data =>
list.push({
message_id: data.id,
}),
);
await withLoading(async () => {
return await NoticeDelete(token, list);
}).then(data => {
showToast('DEL_COMPLETE', {type: alertTypes.success});
}).catch(reason => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
fetchData();
});
break;
}
};
// 전체 선택 구현
const handleAllSelect = () => {
let list = [];
if (document.getElementById('check-all').checked === true) {
dataList.map((data, index) => {
document.getElementsByName('select')[index].checked = true;
list.push(String(data.id));
});
} else if (document.getElementById('check-all').checked === false) {
for (let i = 0; i < dataList.length; i++) {
dataList.map((data, index) => (document.getElementsByName('select')[index].checked = false));
list = [];
}
}
setSelectedRow(list);
};
// 선택 삭제 함수
const handleSelectedDelete = () => {
let list = [];
selectedRow.map(data =>
list.push({
message_id: data,
}),
);
NoticeDelete(token, list);
handleDeleteModalClose();
handleConfirmeModalClose();
};
// 선택 삭제 모달
const handleDeleteModalClose = () => {
if (selectedRow.length !== 0 && deleteModalClose === 'hidden') {
setDeleteModalClose('view');
} else {
setDeleteModalClose('hidden');
}
};
// 삭제, 승인, 저장 확인 모달창
const handleConfirmeModalClose = () => {
if (confirmModalClose === 'hidden') {
setConfirmModalClose('view');
} else {
setConfirmModalClose('hidden');
window.location.reload();
}
};
const handleDetailModal = async (e, id) => {
setDetailData(await NoticeDetailView(token, id));
setDetailId(id);
e.preventDefault();
setDetailView('view');
};
const handleCountSelectedRow = () => {
return dataList && document.querySelectorAll('input[name="select"]:checked').length === dataList.length;
};
}
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.inGameRead) ? (
<AuthModal/>
) : (
<>
<Title>인게임 메시지</Title>
<TableInfo>
<ListOption>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameDelete) && (
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={handleDeleteModalClose} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameUpdate) && (
<Button
theme="primary"
text="메시지 등록"
value="2"
handleClick={e => {
e.preventDefault();
setRegistView('view');
}}
/>
)}
</ListOption>
</TableInfo>
<TableWrapper>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="40">
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
</th>
<th width="80">번호</th>
<th width="100">채팅 타입</th>
<th width="200">송출 일자</th>
<th>메시지</th>
<th width="80">송출 횟수</th>
<th width="100">송출 상태</th>
<th width="120">등록자</th>
<th width="200">등록자(이메일주소)</th>
<th width="200">등록일자</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.map(notice => (
<Fragment key={notice.id}>
<tr>
<td>
<CheckBox name={'select'} id={notice.id} setData={e => handleSelectCheckBox(e)} />
</td>
<td>{notice.row_num}</td>
<td>{message_type.map(item => item.value === notice.message_type && item.name)}</td>
<td>{convertKTC(notice.send_dt)}</td>
<td>
<DetailMessage onClick={e => handleDetailModal(e, notice.id)}>
{notice.content.length > 20 ? notice.content.slice(0, 20) + '...' : notice.content || ''}
</DetailMessage>
</td>
<td>
{/*{notice.send_cnt} / {notice.repeat_cnt}*/}
{notice.send_cnt}
</td>
<td>
{sendStatus.map(data => data.value === notice.send_status && data.name)}
</td>
<td>{notice.create_name}</td>
<td>{notice.create_by}</td>
<td>{convertKTC(notice.create_dt)}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
{/* 인게임 메세지 정보 */}
<BoardInfoModal
detailView={detailView}
setDetailView={setDetailView}
content={detailData}
id={detailId}
setIsCopyData={setIsCopyData}
openRegistModal={setRegistView}
userInfo={userInfo}
<Title>인게임 메시지</Title>
<TableInfo>
<ListOption>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameDelete) && (
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameUpdate) && (
<Button
theme="primary"
text="메시지 등록"
value="2"
handleClick={e => {
e.preventDefault();
setRegistView('view');
}}
/>
{/* 인게임 메세지 등록 */}
<BoardRegistModal registView={registView} setRegistView={setRegistView} copyData={isCopyData ? detailData : ''} setIsCopyData={setIsCopyData} userInfo={userInfo} />
</TableWrapper>
{/* 선택 삭제 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={deleteModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleDeleteModalClose} />
</BtnWrapper>
<ModalText $align="center">
선택된 인게임 메세지를 삭제하시겠습니까?
<br />
삭제 설정 정보가 제거됩니다.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleDeleteModalClose} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleSelectedDelete} />
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleConfirmeModalClose} />
</BtnWrapper>
<ModalText $align="center">삭제가 완료되었습니다.</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmeModalClose} />
</BtnWrapper>
</Modal>
</>
)}
)}
</ListOption>
</TableInfo>
<TableWrapper>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="40">
</th>
<th width="80">번호</th>
<th width="100">채팅 타입</th>
<th width="200">송출 일자</th>
<th>메시지</th>
<th width="80">송출 횟수</th>
<th width="100">송출 상태</th>
<th width="120">등록자</th>
<th width="200">등록자(이메일주소)</th>
<th width="200">등록일자</th>
</tr>
</thead>
<tbody>
{dataList &&
dataList.map(notice => (
<Fragment key={notice.id}>
<tr>
<td>
<CheckBox name={'select'} id={notice.id}
setData={(e) => handleSelectRow(e, notice)}
checked={isRowSelected(notice.id)} />
</td>
<td>{notice.row_num}</td>
<td>{message_type.find(item => item.value === notice.message_type)?.name}</td>
<td>{convertKTC(notice.send_dt)}</td>
<td>
<DetailMessage
onClick={() => handleModalSubmit('detail', { id: notice.id })}>
{notice.content.length > 20 ? notice.content.slice(0, 20) + '...' : notice.content || ''}
</DetailMessage>
</td>
<td>
{/*{notice.send_cnt} / {notice.repeat_cnt}*/}
{notice.send_cnt}
</td>
<td>
{sendStatus.find(data => data.value === notice.send_status)?.name}
</td>
<td>{notice.create_name}</td>
<td>{notice.create_by}</td>
<td>{convertKTC(notice.create_dt)}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
{/* 인게임 메세지 정보 */}
<BoardInfoModal
detailView={detailView}
setDetailView={setDetailView}
content={detailData}
id={detailId}
setIsCopyData={setIsCopyData}
openRegistModal={setRegistView}
userInfo={userInfo}
/>
{/* 인게임 메세지 등록 */}
<BoardRegistModal registView={registView} setRegistView={setRegistView} copyData={isCopyData ? detailData : ''} setIsCopyData={setIsCopyData} userInfo={userInfo} />
</TableWrapper>
</>
);
};
export default Board;
export default withAuth(authType.inGameRead)(Board);

View File

@@ -1,12 +1,11 @@
import { useState, useEffect, Fragment, memo } from 'react';
import { useState, Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import {
EventDelete,
EventDetailView,
EventView,
EventDetailView
} from '../../apis';
import { authList } from '../../store/authList';
@@ -21,106 +20,56 @@ 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 EventListSearchBar from '../../components/ServiceManage/searchBar/EventListSearchBar';
import CustomConfirmModal from '../../components/common/modal/CustomConfirmModal';
import {
ModalInputItem,
ModalSubText,
RegistInputItem,
RegistNotice,
StatusLabel, StatusWapper,
SubText,
} 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';
const Event = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const {withLoading} = useLoading();
const {showToast} = useAlert();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [dataList, setDataList] = useState([]);
const [detailData, setDetailData] = useState('');
const [selectedRow, setSelectedRow] = useState([]);
const [searchData, setSearchData] = useState({
title: '',
content: '',
status: 'ALL',
startDate: '',
endDate: '',
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
delete: 'hidden',
});
const [orderBy, setOrderBy] = useState('DESC');
const [modalState, setModalState] = useState({
detailModal: 'hidden',
deleteConfirmModal: 'hidden',
deleteCompleteModal: 'hidden',
});
const [alertMsg, setAlertMsg] = useState('');
const [deleteDesc, setDeleteDesc] = useState('');
useEffect(() => {
fetchData('', '', 'ALL', '', '');
setSelectedRow([]);
}, [currentPage]);
// 리스트 조회
const fetchData = async (title, content, status, startDate, endDate, order, size) => {
setDataList(
await EventView(
token,
title,
content,
status,
startDate && new Date(startDate).toISOString(),
endDate && new Date(endDate).toISOString(),
order ? order : orderBy,
size ? size : pageSize,
currentPage,
),
);
};
// 검색 함수
const handleSearch = (title, content, status, startDate, endDate) => {
fetchData(title, content, status, startDate && new Date(startDate).toISOString(), endDate && new Date(endDate).toISOString());
};
// 오름차순 내림차순
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order);
fetchData(
searchData.title,
searchData.content,
searchData.status,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
order,
pageSize,
);
};
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
fetchData(
searchData.title,
searchData.content,
searchData.status,
searchData.startDate && new Date(searchData.startDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
size,
1,
);
};
const {
config,
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams,
configLoaded
} = useCommonSearch(token, "eventSearch");
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.list || [], {mode: 'single'});
// 상세보기 호출
const handleDetailModal = async (e, id) => {
@@ -132,68 +81,27 @@ const Event = () => {
handleModalView('detail');
};
// 체크박스 선택 리스트
const handleSelectCheckBox = (e, event) => {
let list = [...selectedRow];
if (e.target.checked) {
list.push(event);
setSelectedRow(list);
} else {
const filterList = list.filter(data => e.target.id !== data);
setSelectedRow(filterList);
}
};
// 전체 선택 구현
const handleAllSelect = () => {
let list = [];
if (document.getElementById('check-all').checked === true) {
dataList.list.map((data, index) => {
document.getElementsByName('select')[index].checked = true;
list.push(String(data.id));
});
} else if (document.getElementById('check-all').checked === false) {
for (let i = 0; i < dataList.list.length; i++) {
dataList.list.map((data, index) => (document.getElementsByName('select')[index].checked = false));
list = [];
}
}
setSelectedRow(list);
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "delete":
const delete_check = selectedRow.every(row => {
const delete_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date));
return row.add_flag || (timeDiff < 30);
});
if(delete_check){
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE'));
showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
return;
}
handleModalView('deleteConfirm');
handleModalView('delete');
break;
case "deleteConfirm":
let list = [];
let isChecked = false;
selectedRow.map(data => {
selectedRows.map(data => {
const row = dataList.list.find(row => row.id === Number(data.id));
if(row.status !== commonStatus.wait) isChecked = true;
list.push({
@@ -202,32 +110,28 @@ const Event = () => {
});
});
handleModalClose('delete');
setDeleteDesc('');
if(isChecked) {
setAlertMsg(t('EVENT_WARNING_DELETE'))
handleModalClose('deleteConfirm');
showToast('EVENT_WARNING_DELETE', {type: alertTypes.warning});
return;
}
EventDelete(token, list);
await withLoading(async () => {
return await EventDelete(token, list);
}).then(data => {
showToast('DEL_COMPLETE', {type: alertTypes.success});
}).catch(error => {
}).finally(() => {
handleSearch(updateSearchParams);
})
handleModalClose('deleteConfirm');
handleModalView('deleteComplete');
break;
case "deleteComplete":
handleModalClose('deleteComplete');
// fetchData(option);
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
}
}
const handleCountSelectedRow = () => {
return currentPage > (dataList && dataList.total) / pageSize ? selectedRow.length === dataList.total % pageSize : selectedRow.length === Number(pageSize);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
@@ -236,11 +140,22 @@ const Event = () => {
<>
<Title>출석 보상 이벤트 관리</Title>
<FormWrapper>
<EventListSearchBar handleSearch={handleSearch} setResultData={setSearchData} />
<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={handleOrderBy} handlePageSize={handlePageSize}>
<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={selectedRow.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
<Button
@@ -260,7 +175,6 @@ const Event = () => {
<thead>
<tr>
<th width="40">
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
</th>
<th width="80">번호</th>
<th width="100">이벤트 상태</th>
@@ -272,12 +186,13 @@ const Event = () => {
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map(event => (
{dataList?.list?.map(event => (
<Fragment key={event.row_num}>
<tr>
<td>
<CheckBox name={'select'} id={event.id} setData={e => handleSelectCheckBox(e, event)} />
<CheckBox name={'select'} id={event.id}
setData={(e) => handleSelectRow(e, event)}
checked={isRowSelected(event.id)} />
</td>
<td>{event.row_num}</td>
<StatusWapper>
@@ -289,26 +204,34 @@ const Event = () => {
<td>{convertKTC(event.end_dt)}</td>
<MailTitle>{event.title}</MailTitle>
<td>
<Button theme="line" text="상세보기" handleClick={e => handleDetailModal(e, event.id)} />
<Button theme="line" text="상세보기"
handleClick={e => handleDetailModal(e, event.id)} />
</td>
<td>{event.create_by}</td>
</tr>
</Fragment>
))}
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
{/*상세*/}
<EventDetailModal detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData}/>
<EventDetailModal
detailView={modalState.detailModal}
handleDetailView={() =>{
handleModalClose('detail');
handleSearch(updateSearchParams);
}}
content={detailData}
setDetailData={setDetailData}
/>
{/*삭제 확인*/}
<DynamicModal
modalType={modalTypes.childOkCancel}
view={modalState.deleteConfirmModal}
handleCancel={() => handleModalClose('deleteConfirm')}
view={modalState.deleteModal}
handleCancel={() => handleModalClose('delete')}
handleSubmit={() => handleModalSubmit('deleteConfirm')}
>
<ModalInputItem>
@@ -327,20 +250,6 @@ const Event = () => {
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30)</ModalSubText>
</ModalInputItem>
</DynamicModal>
{/*삭제 완료*/}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.deleteCompleteModal}
handleSubmit={() => handleModalSubmit('deleteComplete')}
modalText={t('DEL_COMPLETE')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
)}
</>

View File

@@ -1,6 +1,5 @@
import { useState, Fragment, useEffect } from 'react';
import React, { useState, Fragment, useEffect } from 'react';
import Button from '../../components/common/button/Button';
import Loading from '../../components/common/Loading';
import {
Title,
@@ -23,38 +22,34 @@ import {
AppendRegistBox, AppendRegistTable, AreaBtnClose,
BtnDelete,
Item,
ItemList, LangArea, ModalInputItem, ModalItem, ModalItemList, ModalSubText, RegistGroup,
ItemList, LangArea, ModalItem, ModalItemList, RegistGroup,
RegistInputItem,
RegistInputRow, RegistNotice, RegistTable,
} from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType, benItems, modalTypes, wellType } from '../../assets/data';
import DynamicModal from '../../components/common/modal/DynamicModal';
import { authType, benItems, wellType } from '../../assets/data';
import DateTimeInput from '../../components/common/input/DateTimeInput';
import { timeDiffMinute } from '../../utils';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
const EventRegist = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const token = sessionStorage.getItem('token');
const [loading, setLoading] = useState(false); // 로딩 창
const [modalState, setModalState] = useState({
cancelModal: 'hidden',
registConfirmModal: 'hidden',
registCompleteModal: 'hidden',
}); // 모달 관리
const { showToast, showModal } = useAlert();
const { withLoading} = useLoading();
const [item, setItem] = useState(''); // 아이템 값
const [itemCount, setItemCount] = useState(''); // 아이템 개수
const [resource, setResource] = useState('19010001'); // 자원 값
const [resourceCount, setResourceCount] = useState(''); // 자원 개수
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [btnValidation, setBtnValidation] = useState(false); // 입력창 버튼
const [isNullValue, setIsNullValue] = useState(false);
const [btnValidation, setBtnValidation] = useState(false);
const [itemCheckMsg, setItemCheckMsg] = useState('');
const [alertMsg, setAlertMsg] = useState('');
const [time, setTime] = useState({
start_hour: '00',
@@ -145,7 +140,7 @@ const EventRegist = () => {
// 아이템 추가
const handleItemList = async () => {
if(benItems.includes(item)){
setAlertMsg(t('MAIL_ITEM_ADD_BEN'))
showToast('MAIL_ITEM_ADD_BEN', {type: alertTypes.warning});
return;
}
if(item.length === 0 || itemCount.length === 0) return;
@@ -160,7 +155,7 @@ const EventRegist = () => {
const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
if (itemIndex !== -1) {
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL'));
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 };
@@ -221,56 +216,56 @@ const EventRegist = () => {
setResourceCount('');
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case "submit":
if (!checkCondition()) return;
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
if(timeDiff < 60) {
setAlertMsg(t('EVENT_TIME_LIMIT_ADD'));
showToast('EVENT_TIME_LIMIT_ADD', {type: alertTypes.warning});
return;
}
handleModalView('registConfirm');
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 "cancel":
handleModalView('deleteSubmit');
break;
case "registConfirm":
setLoading(true);
const result = await EventSingleRegist(token, resultData);
setLoading(false);
handleModalClose('registConfirm');
handleModalView('registComplete');
break;
case "registComplete":
handleModalClose('registComplete');
navigate('/servicemanage/event');
break;
case "warning":
setAlertMsg('');
await withLoading(async () => {
return await EventSingleRegist(token, resultData);
}).then((result) => {
showToast('REGIST_COMPLTE', {type: alertTypes.success});
}).catch(() => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
callbackPage();
});
break;
}
}
const callbackPage = () => {
navigate('/servicemanage/event');
}
const checkCondition = () => {
return (
resultData.mail_list.every(data => data.content !== '' && data.title !== '') &&
@@ -446,7 +441,14 @@ const EventRegist = () => {
)}
<BtnWrapper $justify="flex-end" $gap="10px">
<Button text="취소" theme="line" handleClick={() => handleModalView('cancel')} />
<Button
text="취소"
theme="line"
handleClick={() => showModal('EVENT_REGIST_CANCEL', {
type: alertTypes.confirm,
onConfirm: () => callbackPage()
})}
/>
<Button
type="submit"
text="등록"
@@ -454,61 +456,6 @@ const EventRegist = () => {
handleClick={() => handleSubmit('submit')}
/>
</BtnWrapper>
{/* 등록 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={t('EVENT_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
<DynamicModal
modalType={modalTypes.childOkCancel}
view={modalState.registConfirmModal}
handleCancel={() => handleModalClose('registConfirm')}
handleSubmit={() => handleSubmit('registConfirm')}
>
<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>
</DynamicModal>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('EVENT_REGIST_CANCEL')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancel')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
)}
</>

View File

@@ -32,12 +32,15 @@ import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminCo
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar';
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';
import { alertTypes } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
const LandAuction = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const { showToast, showModal } = useAlert();
const [detailData, setDetailData] = useState({});
@@ -47,8 +50,6 @@ const LandAuction = () => {
handleModalClose
} = useModal({
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
@@ -97,14 +98,17 @@ const LandAuction = () => {
return timeDiff < 3;
});
if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.warning});
return;
}
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
showToast('LAND_AUCTION_DELETE_STATUS_WARNING', {type: alertTypes.warning});
return;
}
handleModalView('deleteConfirm');
showModal('LAND_AUCTION_SELECT_DELETE', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break;
case "deleteConfirm":
let list = [];
@@ -119,31 +123,24 @@ const LandAuction = () => {
});
if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
handleModalClose('deleteConfirm');
showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
return;
}
await LandAuctionDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") {
handleModalView('deleteComplete');
showToast('DEL_COMPLETE', {type: alertTypes.success});
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
showToast('LAND_AUCTION_ERROR_DELETE_STATUS', {type: alertTypes.error});
}else{
setAlertMsg(t('DELETE_FAIL'));
showToast('DELETE_FAIL', {type: alertTypes.error});
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleSearch(updateSearchParams);
});
break;
case "deleteComplete":
handleModalClose('deleteComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
}
}
@@ -243,30 +240,19 @@ const LandAuction = () => {
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
{/*상세*/}
<LandAuctionModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} landData={landData} buildingData={buildingData} />
<LandAuctionModal
modalType={modalType}
detailView={modalState.detailModal}
handleDetailView={() => {
handleModalClose('detail');
handleSearch(updateSearchParams);
}}
content={detailData}
setDetailData={setDetailData}
landData={landData}
buildingData={buildingData}
/>
{/*삭제 확인*/}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.deleteConfirmModal}
handleCancel={() => handleModalClose('deleteConfirm')}
handleSubmit={() => handleModalSubmit('deleteConfirm')}
modalText={t('LAND_AUCTION_SELECT_DELETE')}
/>
{/*삭제 완료*/}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.deleteCompleteModal}
handleSubmit={() => handleModalSubmit('deleteComplete')}
modalText={t('DEL_COMPLETE')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleModalSubmit('warning')}
/>
</>
)
};

View File

@@ -1,329 +1,221 @@
import styled from 'styled-components';
import { useState, useEffect, Fragment } from 'react';
import { useState, Fragment } from 'react';
import CheckBox from '../../components/common/input/CheckBox';
import Modal from '../../components/common/modal/Modal';
import { MailDelete, MailDetailView, MailView } from '../../apis';
import { MailDelete, MailDetailView } from '../../apis';
import { Title, FormWrapper, TableInfo, ListCount, ListOption, TableStyle, SelectInput, MailTitle, TableWrapper, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper } from '../../styles/Components';
import Button from '../../components/common/button/Button';
import 'react-datepicker/dist/react-datepicker.css';
import { useNavigate } from 'react-router-dom';
import MailDetailModal from '../../components/ServiceManage/modal/MailDetailModal';
import MailListSearchBar from '../../components/ServiceManage/searchBar/MailListSearchBar';
import Pagination from '../../components/common/Pagination/Pagination';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { authType, mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../assets/data';
import AuthModal from '../../components/common/modal/AuthModal';
import {
authType,
mailReceiveType,
mailSendStatus,
mailSendType,
mailType,
} from '../../assets/data';
import ViewTableInfo from '../../components/common/Table/ViewTableInfo';
import { convertKTC} from '../../utils';
import { convertKTC } from '../../utils';
import { StatusLabel, StatusWapper } from '../../styles/ModuleComponents';
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { alertTypes } from '../../assets/data/types';
import { useModal, useTable, withAuth } from '../../hooks/hook';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
const Mail = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [dataList, setDataList] = useState([]);
const [detailView, setDetailView] = useState('hidden');
const [detailData, setDetailData] = useState('');
const [selectedRow, setSelectedRow] = useState([]);
const [searchData, setSearchData] = useState({
mailTitle: '',
content: '',
sendType: 'ALL',
sendStatus: 'ALL',
mailType: 'ALL',
receiveType: 'ALL',
sendDate: '',
endDate: '',
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
});
const [orderBy, setOrderBy] = useState('DESC');
const [deleteModalClose, setDeleteModalClose] = useState('hidden');
const [confirmModalClose, setConfirmModalClose] = useState('hidden');
const {
config,
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams,
configLoaded
} = useCommonSearch(token, "mailSearch");
// console.log(dataList);
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.list || [], {mode: 'single'});
useEffect(() => {
fetchData('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', '');
setSelectedRow([]);
}, [currentPage]);
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "detail":
await MailDetailView(token, param).then(data => {
setDetailData(data);
handleModalView('detail');
});
break;
case "delete":
if(selectedRows.length === 0) return;
// 리스트 조회
const fetchData = async (mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate, endDate, order, size) => {
setDataList(
await MailView(
token,
mailTitle,
content,
sendType,
sendStatus,
mailType,
receiveType,
sendDate && new Date(sendDate).toISOString(),
endDate && new Date(endDate).toISOString(),
order ? order : orderBy,
size ? size : pageSize,
currentPage,
),
);
};
showModal('MAIL_SELECT_DELETE', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break;
// 검색 함수
const handleSearch = (mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate, endDate) => {
fetchData(mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate && new Date(sendDate).toISOString(), endDate && new Date(endDate).toISOString());
};
case "deleteConfirm":
let list = [];
// 오름차순 내림차순
const handleOrderBy = e => {
const order = e.target.value;
let isChecked = false;
setOrderBy(order);
fetchData(
searchData.mailTitle,
searchData.content,
searchData.sendType,
searchData.sendStatus,
searchData.mailType,
searchData.receiveType,
searchData.sendDate && new Date(searchData.sendDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
order,
pageSize,
);
};
selectedRows.map(data => {
const row = dataList.list.find(row => row.id === Number(data.id));
if(row.send_status === "FINISH" || row.send_status === "RUNNING" || row.send_status === "FAIL") isChecked = true;
list.push({
id: data.id,
});
});
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
if(isChecked) {
showToast('MAIL_SEND_STATUS_WARNING', {type: alertTypes.warning});
return;
}
fetchData(
searchData.mailTitle,
searchData.content,
searchData.sendType,
searchData.sendStatus,
searchData.mailType,
searchData.receiveType,
searchData.sendDate && new Date(searchData.sendDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
orderBy,
size,
1,
);
};
withLoading( async () => {
return await MailDelete(token, list);
}).then(data => {
showToast('DEL_COMPLETE', {type: alertTypes.success});
}).catch(reason => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleSearch(updateSearchParams);
});
const handleDetailView = () => {
if (detailView === 'hidden') setDetailView('view');
else setDetailView('hidden');
};
const handleDetailModal = async (e, id) => {
setDetailData(await MailDetailView(token, id));
e.preventDefault();
handleDetailView();
};
// 체크박스 선택 리스트
const handleSelectCheckBox = e => {
let list = [...selectedRow];
if (e.target.checked) {
list.push(e.target.id);
setSelectedRow(list);
} else {
const filterList = list.filter(data => e.target.id !== data);
setSelectedRow(filterList);
break;
}
};
// 전체 선택 구현
const handleAllSelect = () => {
let list = [];
if (document.getElementById('check-all').checked === true) {
dataList.list.map((data, index) => {
document.getElementsByName('select')[index].checked = true;
list.push(String(data.id));
});
} else if (document.getElementById('check-all').checked === false) {
for (let i = 0; i < dataList.list.length; i++) {
dataList.list.map((data, index) => (document.getElementsByName('select')[index].checked = false));
list = [];
}
}
setSelectedRow(list);
};
// 선택 삭제 함수
const handleSelectedDelete = () => {
let list = [];
let isChecked = false;
selectedRow.map(data => {
const row = dataList.list.find(row => row.id === Number(data));
if(row.send_status === "FINISH" || row.send_status === "RUNNING") isChecked = true;
list.push({
id: data,
});
});
if(isChecked) {
alert("발송 완료한 우편은 삭제할 수 없습니다.")
handleDeleteModalClose();
return;
}
MailDelete(token, list);
handleDeleteModalClose();
handleConfirmeModalClose();
};
// 선택 삭제 모달
const handleDeleteModalClose = () => {
if (selectedRow.length !== 0 && deleteModalClose === 'hidden') {
setDeleteModalClose('view');
} else {
setDeleteModalClose('hidden');
}
};
// 삭제, 승인, 저장 확인 모달창
const handleConfirmeModalClose = () => {
if (confirmModalClose === 'hidden') {
setConfirmModalClose('view');
} else {
setConfirmModalClose('hidden');
window.location.reload();
}
};
const handleCountSelectedRow = () => {
return currentPage > (dataList && dataList.total) / pageSize ? selectedRow.length === dataList.total % pageSize : selectedRow.length === Number(pageSize);
};
}
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailRead) ? (
<AuthModal/>
) : (
<>
<Title>우편 조회 발송 관리</Title>
<FormWrapper>
<MailListSearchBar handleSearch={handleSearch} setResultData={setSearchData} />
</FormWrapper>
<ViewTableInfo total={dataList.total} total_all={dataList.total_all} handleOrderBy={handleOrderBy} handlePageSize={handlePageSize}>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailDelete) && (
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={handleDeleteModalClose} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) && (
<Button
theme="primary"
text="우편 등록"
type="button"
handleClick={e => {
e.preventDefault();
navigate('/servicemanage/mail/mailregist');
}}
/>
)}
</ViewTableInfo>
<TableWrapper>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="40">
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
</th>
<th width="80">번호</th>
<th width="210">등록 일시</th>
<th width="210">발송 일시</th>
<th width="100">발송 방식</th>
<th width="100">발송 상태</th>
<th width="100">우편 타입</th>
<th width="100">수신 대상</th>
<th>우편 제목</th>
<th width="110">확인 / 수정</th>
<th width="200">등록자(처리자)</th>
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map(mail => (
<Fragment key={mail.row_num}>
<tr>
<td>
<CheckBox name={'select'} id={mail.id} setData={e => handleSelectCheckBox(e)} />
</td>
<td>{mail.row_num}</td>
<td>{convertKTC(mail.create_dt)}</td>
<td>{convertKTC(mail.send_dt)}</td>
<td>{mailSendType.map(data => data.value === mail.send_type && data.name)}</td>
<StatusWapper>
<StatusLabel $status={mail.send_status}>
{mailSendStatus.map(data => data.value === mail.send_status && data.name)}
</StatusLabel>
</StatusWapper>
<td>{mailType.map(data => data.value === mail.mail_type && data.name)}</td>
<td>{mailReceiveType.map(data => data.value === mail.receive_type && data.name)}</td>
<MailTitle>{mail.title}</MailTitle>
<td>
<Button theme="line" text="상세보기" handleClick={e => handleDetailModal(e, mail.id)} />
</td>
<td>{mail.create_by}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<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.mailDelete) && (
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) && (
<Button
theme="primary"
text="우편 등록"
type="button"
handleClick={e => {
e.preventDefault();
navigate('/servicemanage/mail/mailregist');
}}
/>
)}
</ViewTableInfo>
<TableWrapper>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="40">
</th>
<th width="80">번호</th>
<th width="210">등록 일시</th>
<th width="210">발송 일시</th>
<th width="100">발송 방식</th>
<th width="100">발송 상태</th>
<th width="100">우편 타입</th>
<th width="100">수신 대상</th>
<th>우편 제목</th>
<th width="110">확인 / 수정</th>
<th width="200">등록자(처리자)</th>
</tr>
</thead>
<tbody>
{dataList?.list?.map(mail => (
<Fragment key={mail.row_num}>
<tr>
<td>
<CheckBox name={'select'} id={mail.id}
setData={(e) => handleSelectRow(e, mail)}
checked={isRowSelected(mail.id)} />
</td>
<td>{mail.row_num}</td>
<td>{convertKTC(mail.create_dt)}</td>
<td>{convertKTC(mail.send_dt)}</td>
<td>{mailSendType.map(data => data.value === mail.send_type && data.name)}</td>
<StatusWapper>
<StatusLabel $status={mail.send_status}>
{mailSendStatus.map(data => data.value === mail.send_status && data.name)}
</StatusLabel>
</StatusWapper>
<td>{mailType.map(data => data.value === mail.mail_type && data.name)}</td>
<td>{mailReceiveType.map(data => data.value === mail.receive_type && data.name)}</td>
<MailTitle>{mail.title}</MailTitle>
<td>
<Button theme="line" text="상세보기" handleClick={e => handleModalSubmit('detail', mail.id)} />
</td>
<td>{mail.create_by}</td>
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
<MailDetailModal
detailView={modalState.detailModal}
handleDetailView={() =>{
handleModalClose('detail');
handleSearch(updateSearchParams);
}}
content={detailData}
/>
<MailDetailModal detailView={detailView} handleDetailView={handleDetailView} content={detailData} />
{/* 선택 삭제 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={deleteModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleDeleteModalClose} />
</BtnWrapper>
<ModalText $align="center">
선택된 우편을 삭제하시겠습니까?
<br />
삭제 설정 정보가 제거됩니다.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleDeleteModalClose} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleSelectedDelete} />
</BtnWrapper>
</Modal>
{/* 확인 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleConfirmeModalClose} />
</BtnWrapper>
<ModalText $align="center">삭제가 완료되었습니다.</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmeModalClose} />
</BtnWrapper>
</Modal>
</>
)}
</>
);
};
export default Mail;
export default withAuth(authType.mailRead)(Mail);
const ListState = styled.span`
color: #d60000;

View File

@@ -1,40 +1,48 @@
import { useState, Fragment } from 'react';
import React, { useState, Fragment, useEffect } from 'react';
import Button from '../../components/common/button/Button';
import RadioInput from '../../components/common/input/Radio';
import CheckBox from '../../components/common/input/CheckBox';
import Loading from '../../components/common/Loading';
import styled from 'styled-components';
import { Title, BtnWrapper, TextInput, SelectInput, Label, InputLabel, DatePickerWrapper, Textarea, ModalText, ButtonClose, SearchBarAlert, AlertText } from '../../styles/Components';
import {
Title,
BtnWrapper,
TextInput,
SelectInput,
Label,
InputLabel,
DatePickerWrapper,
Textarea,
SearchBarAlert,
} from '../../styles/Components';
import IconDelete from '../../assets/img/icon/icon-delete.png';
import CloseIcon from '../../assets/img/icon/icon-close.png';
import { HourList, MinuteList, modalTypes, wellType } from '../../assets/data';
import { benItems, HourList, MinuteList, wellType } from '../../assets/data';
import { useNavigate } from 'react-router-dom';
import MailRegistUploadBtn from '../../components/ServiceManage/MailRegistUploadBtn';
import DatePickerComponent from '../../components/common/Date/DatePickerComponent';
import Modal from '../../components/common/modal/Modal';
import { MailCaliumTotalView, MailIsItem, MailMultiRegsit, MailSingleRegist } from '../../apis';
import { MailCaliumTotalView, MailIsItem, MailSingleRegist } from '../../apis';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data';
import { useDataFetch } from '../../hooks/hook';
import { BattleConfigView } from '../../apis/Battle';
import { currencyCodeTypes } from '../../assets/data/types';
import { DynamicModal } from '../../components/common';
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
import { useAlert } from '../../context/AlertProvider';
const MailRegist = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const environment = process.env.REACT_APP_ENV;
const token = sessionStorage.getItem('token');
const { showModal, showToast } = useAlert();
const { withLoading } = useLoading();
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
@@ -46,9 +54,6 @@ const MailRegist = () => {
const [resource, setResource] = useState('19010001');
const [resourceCount, setResourceCount] = useState(0);
const [cancelModalClose, setCancelModalClose] = useState('hidden');
const [submitModal, setSubmitModal] = useState('hidden');
const [completeModal, setCompleteModal] = useState('hidden');
const [isNullValue, setIsNullValue] = useState(false);
const [btnValidation, setBtnValidation] = useState(false);
@@ -57,13 +62,10 @@ const MailRegist = () => {
const [isItemNullValue, setIsItemNullValue] = useState(false);
const [isResourceNullValue, setIsResourceNullValue] = useState(false);
const [confirmText, setConfirmText] = useState('');
const [alertMessage, setAlertMessage] = useState('');
const [loading, setLoading] = useState(false);
const [alertMsg, setAlertMsg] = useState('');
const {
data: caliumTotalData
data: caliumTotalData,
} = useDataFetch(() => MailCaliumTotalView(token), [token]);
const [resultData, setResultData] = useState({
@@ -87,16 +89,20 @@ const MailRegist = () => {
title: '',
content: '',
language: 'JA',
}
},
],
item_list: [],
resource_list: [],
guid: '',
});
const benItems = [
"19010003"
];
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
// 아이템 수량 숫자 체크
const handleItemCount = e => {
@@ -113,17 +119,17 @@ const MailRegist = () => {
// 아이템 추가
const handleItemList = async () => {
if(benItems.includes(item) && environment === "live"){
alert(t('MAIL_ITEM_ADD_BEN'))
if (benItems.includes(item) && environment === 'live') {
showToast('MAIL_ITEM_ADD_BEN', { type: alertTypes.warning });
return;
}
item.length === 0 || itemCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
// const token = sessionStorage.getItem('token');
const result = await MailIsItem(token, {item: item});
const result = await MailIsItem(token, { item: item });
if(result.data.result === "ERROR"){
alert(result.data.data.message);
if (result.data.result === 'ERROR') {
showToast(result.data.data.message, { type: alertTypes.warning });
return;
}
@@ -132,11 +138,11 @@ const MailRegist = () => {
} else if (item.length !== 0) {
setIsItemNullValue(false);
const itemIndex = resultData.item_list.findIndex(
(item) => item.item === resource
(item) => item.item === resource,
);
if (itemIndex !== -1) {
alert(t('MAIL_ITEM_ADD_DUPL'));
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 };
@@ -188,22 +194,22 @@ const MailRegist = () => {
setIsItemNullValue(false);
const itemIndex = resultData.item_list.findIndex(
(item) => item.item === resource
(item) => item.item === resource,
);
if (itemIndex !== -1) {
const item_cnt = resultData.item_list[itemIndex].item_cnt;
if(resource === currencyCodeTypes.calium){
if((Number(resourceCount) + Number(item_cnt)) > caliumTotalData){
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING'))
if (resource === currencyCodeTypes.calium) {
if ((Number(resourceCount) + Number(item_cnt)) > caliumTotalData) {
showToast('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING', { type: alertTypes.warning });
return;
}
}
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
} else {
if(resource === currencyCodeTypes.calium){
if(Number(resourceCount) > caliumTotalData){
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING'))
if (resource === currencyCodeTypes.calium) {
if (Number(resourceCount) > caliumTotalData) {
showToast('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING', { type: alertTypes.warning });
return;
}
}
@@ -211,7 +217,7 @@ const MailRegist = () => {
const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
resultData.item_list.push(newItem);
}
setResourceCount('');
}
};
@@ -248,72 +254,51 @@ const MailRegist = () => {
setResultData({ ...resultData, send_dt: result });
};
const handleSubmitModal = () => {
if (
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) ||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
(resultData.is_reserve && resultData.send_dt.length === 0) ||
resultData.mail_type === 'SELECT' ||
alertMessage
) {
setIsNullValue(true);
} else if (submitModal === 'hidden') {
setIsNullValue(false);
setSubmitModal('view');
} else {
setSubmitModal('hidden');
const handleSubmit = async (type, param = null) => {
switch (type) {
case 'submit':
if (!checkCondition()) return;
showModal('MAIL_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm'),
});
break;
case 'registConfirm':
await withLoading(async () => {
return await MailSingleRegist(token, resultData);
}).then(data => {
if (data.result === 'ERROR') {
if (data.data.message === 'ERROR_MAIL_ITEM_CALIUM_OVER') {
showToast('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING', { type: alertTypes.error });
} else if (data.data.message === 'NOT_EXIT_EXCEL') {
showToast('EXCEL_SELECT', { type: alertTypes.error });
} else if (data.data.message === 'GUID_CHECK') {
showToast('WARNING_GUID_CHECK', { type: alertTypes.error });
} else if (data.data.message === 'NICKNAME_CHECK') {
showToast('WARNING_NICKNAME_CHECK', { type: alertTypes.error });
} else if (data.data.message === 'EMAIL_CHECK') {
showToast('WARNING_EMAIL_CHECK', { type: alertTypes.error });
} else if (data.data.message === 'USERTYPE_CHECK_EXCEL') {
showToast('WARNING_TYPE_CHECK', { type: alertTypes.error });
} else {
showToast(data.data.message, { type: alertTypes.error });
}
} else {
showToast('MAIL_REGIST_COMPLETE', { type: alertTypes.success });
}
}).catch(error => {
showToast('API_FAIL', { type: alertTypes.error });
}).finally(() => {
callbackPage();
});
break;
}
};
// 취소 모달
const handleCancelModalClose = () => {
if (cancelModalClose === 'hidden') {
setCancelModalClose('view');
} else {
setCancelModalClose('hidden');
setConfirmText(t('MAIL_CANCEL'));
}
};
// 완료 모달창
const handleCompleteModal = () => {
if (completeModal === 'hidden') {
setCompleteModal('view');
} else {
setCompleteModal('hidden');
navigate('/servicemanage/mail');
}
};
const handleRegistMail = async () => {
setLoading(true);
await MailSingleRegist(token, resultData).then(data => {
setLoading(false);
handleSubmitModal();
if(data.result === "ERROR"){
if(data.data.message === "ERROR_MAIL_ITEM_CALIUM_OVER"){
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING'));
}else if(data.data.message === "NOT_EXIT_EXCEL"){
setAlertMsg(t('EXCEL_SELECT'));
}else if(data.data.message === "GUID_CHECK"){
setAlertMsg(t('WARNING_GUID_CHECK'));
}else if(data.data.message === "NICKNAME_CHECK"){
setAlertMsg(t('WARNING_NICKNAME_CHECK'));
}else if(data.data.message === "EMAIL_CHECK"){
setAlertMsg(t('WARNING_EMAIL_CHECK'));
}else if(data.data.message === "USERTYPE_CHECK_EXCEL") {
setAlertMsg(t('WARNING_TYPE_CHECK'));
}else{
setAlertMsg(data.data.message);
}
}else{
setConfirmText('우편이 정상 등록되었습니다.');
handleCompleteModal();
}
}).catch(error => {
setAlertMsg(t('API_FAIL'));
});
const callbackPage = () => {
navigate('/servicemanage/mail');
};
const handleSingleBtn = () => {
@@ -328,13 +313,19 @@ const MailRegist = () => {
const handleMultiBtn = () => {
delete resultData.guid;
};
// console.log('alertMessage', alertMessage);
// console.log('resultData: ', resultData);
const checkCondition = () => {
return resultData.mail_list.every(data => data.content !== '' && data.title !== '') &&
(resultData.receive_type === 'MULTIPLE' ? excelFile !== null : resultData.guid !== '') &&
(resultData.is_reserve ? resultData.send_dt.length !== 0 : resultData.send_dt.length === 0) &&
resultData.mail_type !== 'SELECT'
&& !alertMessage
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) ? (
<AuthModal/>
<AuthModal />
) : (
<>
<Title>우편 등록</Title>
@@ -345,7 +336,11 @@ const MailRegist = () => {
label="예약 발송"
id="reserve"
checked={resultData.is_reserve}
setData={e => setResultData({ ...resultData, is_reserve: e.target.checked, send_dt: new Date() })}
setData={e => setResultData({
...resultData,
is_reserve: e.target.checked,
send_dt: new Date(),
})}
/>
<InputItem>
{resultData.is_reserve && (
@@ -353,7 +348,9 @@ const MailRegist = () => {
<InputLabel>발송 시간</InputLabel>
<InputGroup>
<DatePickerWrapper>
<DatePickerComponent name="시작 일자" selectedDate={resultData.send_dt} handleSelectedDate={data => handleSelectedDate(data)} pastDate={new Date()} />
<DatePickerComponent name="시작 일자" selectedDate={resultData.send_dt}
handleSelectedDate={data => handleSelectedDate(data)}
pastDate={new Date()} />
</DatePickerWrapper>
<SelectInput onChange={e => handleSendTime(e)} id="hour">
{HourList.map(hour => (
@@ -375,7 +372,8 @@ const MailRegist = () => {
</InputItem>
<InputItem>
<InputLabel>우편 타입</InputLabel>
<SelectInput onChange={e => setResultData({ ...resultData, mail_type: e.target.value })} value={resultData.mail_type}>
<SelectInput onChange={e => setResultData({ ...resultData, mail_type: e.target.value })}
value={resultData.mail_type}>
<option value="SELECT">타입 선택</option>
<option value="SYSTEM_GUID">시스템 안내</option>
<option value="INSPECTION_COMPENSATION">점검 보상</option>
@@ -389,7 +387,9 @@ const MailRegist = () => {
<InputItem>
<InputLabel>수신대상</InputLabel>
<InputItem>
<SelectInput onChange={e => setResultData({ ...resultData, user_type: e.target.value })} value={resultData.user_type} disabled={resultData.receive_type !== 'SINGLE'}>
<SelectInput
onChange={e => setResultData({ ...resultData, user_type: e.target.value })}
value={resultData.user_type} disabled={resultData.receive_type !== 'SINGLE'}>
<option value="GUID">GUID</option>
<option value="NICKNAME">아바타명</option>
<option value="EMAIL">이메일</option>
@@ -404,11 +404,14 @@ const MailRegist = () => {
value="SINGLE"
fontWeight="600"
checked={resultData.receive_type === 'SINGLE'}
handleChange={e => setResultData({ ...resultData, receive_type: e.target.id })}
handleChange={e => setResultData({
...resultData,
receive_type: e.target.id,
})}
handleClick={handleSingleBtn}
/>
<TextInput
placeholder={resultData.user_type === "GUID" ? "GUID 입력" : resultData.user_type === "NICKNAME" ? "아바타명 입력" : "이메일 입력"}
placeholder={resultData.user_type === 'GUID' ? 'GUID 입력' : resultData.user_type === 'NICKNAME' ? '아바타명 입력' : '이메일 입력'}
disabled={resultData.receive_type !== 'SINGLE'}
onChange={e => setResultData({ ...resultData, guid: e.target.value })}
value={resultData.receive_type === 'SINGLE' && resultData.guid ? resultData.guid : ''}
@@ -422,7 +425,10 @@ const MailRegist = () => {
value="MULTIPLE"
fontWeight="600"
checked={resultData.receive_type === 'MULTIPLE'}
handleChange={e => setResultData({ ...resultData, receive_type: e.target.id })}
handleChange={e => setResultData({
...resultData,
receive_type: e.target.id,
})}
handleClick={handleMultiBtn}
/>
<MailRegistUploadBtn
@@ -460,55 +466,57 @@ const MailRegist = () => {
</LangArea>
<MailRegistTable>
<tbody>
<tr>
<th width="120">
<Label>제목</Label>
</th>
<td>
<InputItem>
<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 });
}}
/>
</InputItem>
<MailNotice $color={data.title.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({data.title.length}/30)</MailNotice>
</td>
</tr>
<tr>
<th>
<Label>내용</Label>
</th>
<td>
<Textarea
maxLength="2000"
value={data.content}
<tr>
<th width="120">
<Label>제목</Label>
</th>
<td>
<InputItem>
<TextInput
placeholder="우편 제목 입력"
maxLength="30"
id={data.language}
value={data.title}
onChange={e => {
if (e.target.value.length > 2000) {
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].content = e.target.value.trimStart();
list[findIndex].title = e.target.value.trimStart();
setResultData({ ...resultData, mail_list: list });
}}
/>
<MailNotice $color={data.content.length > 1999 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({data.content.length}/2000)</MailNotice>
</td>
</tr>
</InputItem>
<MailNotice $color={data.title.length > 29 ? 'red' : '#666'}>* 최대 등록 가능
글자수 ({data.title.length}/30)</MailNotice>
</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 });
}}
/>
<MailNotice $color={data.content.length > 1999 ? 'red' : '#666'}>* 최대 등록
가능 글자수 ({data.content.length}/2000)</MailNotice>
</td>
</tr>
</tbody>
</MailRegistTable>
</MailRegistBox>
@@ -519,17 +527,21 @@ const MailRegist = () => {
<MailRegistBox>
<MailRegistTable>
<tbody>
<tr>
<th width="120">
<Label>아이템 첨부</Label>
</th>
<td>
<InputItem>
<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" />
</InputItem>
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
<tr>
<th width="120">
<Label>아이템 첨부</Label>
</th>
<td>
<InputItem>
<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" />
</InputItem>
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
<div>
{resultData.item_list && (
@@ -547,44 +559,48 @@ const MailRegist = () => {
</ItemList>
)}
</div> */}
</td>
</tr>
<tr>
<th width="120">
<Label>자원 첨부</Label>
</th>
<td>
<InputItem>
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
<option value="19010001">골드</option>
<option value="19010002">사파이어</option>
<option value="19010005">루비</option>
<option value="19010003">칼리움</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" />
{resource === currencyCodeTypes.calium && <Label>(잔여 수량: {caliumTotalData})</Label>}
</InputItem>
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
</td>
</tr>
<tr>
<th width="120">
<Label>자원 첨부</Label>
</th>
<td>
<InputItem>
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
<option value="19010001">골드</option>
<option value="19010002">사파이어</option>
<option value="19010005">루비</option>
<option value="19010003">칼리움</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" />
{resource === currencyCodeTypes.calium &&
<Label>(잔여 수량: {caliumTotalData})</Label>}
</InputItem>
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
<div>
{resultData.item_list && (
<ItemList>
{resultData.item_list.map((data, index) => {
return (
<Item key={index}>
<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>
<BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>
</Item>
);
})}
</ItemList>
)}
</div>
</td>
</tr>
</tbody>
</MailRegistTable>
</MailRegistBox>
@@ -595,83 +611,21 @@ const MailRegist = () => {
)}
<BtnWrapper $justify="flex-end" $gap="10px">
<Button text="취소" theme="line" handleClick={handleCancelModalClose} />
<Button
text="취소"
theme="line"
handleClick={() => showModal('MAIL_REGIST_CANCEL', {
type: alertTypes.confirm,
onConfirm: () => callbackPage(),
})}
/>
<Button
type="submit"
text="등록"
theme={
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) ||
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') ||
(resultData.is_reserve && resultData.send_dt.length === 0) ||
resultData.mail_type === 'SELECT' ||
alertMessage
? 'disable'
: 'primary'
}
handleClick={handleSubmitModal}
theme={checkCondition() ? 'primary' : 'disable'}
handleClick={() => handleSubmit('submit')}
/>
</BtnWrapper>
{/* 등록 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={submitModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleSubmitModal} />
</BtnWrapper>
<ModalText $align="center">우편을 등록하시겠습니까?</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleSubmitModal} />
<Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={() => {handleRegistMail();}}
/>
</BtnWrapper>
</Modal>
{/* 완료 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCompleteModal} />
</BtnWrapper>
<ModalText $align="center">{confirmText}</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
</BtnWrapper>
</Modal>
{/* 취소 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={cancelModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCancelModalClose} />
</BtnWrapper>
<ModalText $align="center">
우편 등록을 취소하시겠습니까?
<br />
취소 설정된 값은 반영되지 않습니다.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleCancelModalClose} />
<Button
text="확인"
theme="primary"
type="submit"
size="large"
width="100%"
handleClick={() => {
handleCancelModalClose();
handleCompleteModal();
}}
/>
</BtnWrapper>
</Modal>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => setAlertMsg('')}
/>
{loading && <Loading/>}
</>
)}
</>
@@ -681,126 +635,134 @@ const MailRegist = () => {
export default MailRegist;
const InputRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 10px 50px;
display: flex;
flex-wrap: wrap;
gap: 10px 50px;
`;
const InputItem = styled.div`
display: flex;
align-items: center;
gap: 20px;
${TextInput},${SelectInput} {
height: 35px;
font-size: 14px;
}
${TextInput} {
padding: 0 20px;
}
${SelectInput} {
width: max-content;
}
display: flex;
align-items: center;
gap: 20px;
${TextInput}, ${SelectInput} {
height: 35px;
font-size: 14px;
}
${TextInput} {
padding: 0 20px;
}
${SelectInput} {
width: max-content;
}
`;
const InputGroup = styled.div`
display: flex;
gap: 5px;
align-items: center;
display: flex;
gap: 5px;
align-items: center;
`;
const MailNotice = styled.span`
font-size: 12px;
font-weight: 300;
color: ${props => props.$color || '#999'};
margin-top: 10px;
display: block;
font-size: 12px;
font-weight: 300;
color: ${props => props.$color || '#999'};
margin-top: 10px;
display: block;
`;
const RegistGroup = styled.div`
display: flex;
width: 100%;
flex-flow: column;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
font-size: 14px;
margin-bottom: 40px;
display: flex;
width: 100%;
flex-flow: column;
padding: 20px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
font-size: 14px;
margin-bottom: 40px;
${MailNotice} {
margin-bottom: 20px;
}
${MailNotice} {
margin-bottom: 20px;
}
`;
const BtnClose = styled.button`
width: 16px;
height: 16px;
background: url(${CloseIcon}) 50% 50% no-repeat;
position: absolute;
top: 50%;
transform: translate(0, -50%);
right: 20px;
opacity: ${props => props.opacity};
width: 16px;
height: 16px;
background: url(${CloseIcon}) 50% 50% no-repeat;
position: absolute;
top: 50%;
transform: translate(0, -50%);
right: 20px;
opacity: ${props => props.opacity};
`;
const LangArea = styled.div`
background: #f9f9f9;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
position: relative;
background: #f9f9f9;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
position: relative;
`;
const MailRegistBox = styled.div`
margin-bottom: 20px;
border-top: 1px solid #999;
border-bottom: 1px solid #999;
margin-bottom: 20px;
border-top: 1px solid #999;
border-bottom: 1px solid #999;
`;
const MailRegistTable = styled.table`
th {
vertical-align: top;
line-height: 35px;
}
th,
td {
padding: 15px 0;
border-bottom: 1px solid #f6f6f6;
}
td {
${InputItem} {
gap: 5px;
}
${TextInput} {
max-width: 600px;
padding: 0 15px;
}
${Textarea} {
width: 100%;
border: 1px solid #d9d9d9;
border-radius: 5px;
height: 255px;
padding: 15px;
&:focus {
border: 1px solid #2c2c2c;
}
}
}
th {
vertical-align: top;
line-height: 35px;
}
th,
td {
padding: 15px 0;
border-bottom: 1px solid #f6f6f6;
}
td {
${InputItem} {
gap: 5px;
}
${TextInput} {
max-width: 600px;
padding: 0 15px;
}
${Textarea} {
width: 100%;
border: 1px solid #d9d9d9;
border-radius: 5px;
height: 255px;
padding: 15px;
&:focus {
border: 1px solid #2c2c2c;
}
}
}
`;
const ItemList = styled.ul`
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 10px 20px;
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 10px 20px;
`;
const Item = styled.li`
display: flex;
gap: 5px;
align-items: center;
display: flex;
gap: 5px;
align-items: center;
`;
const BtnDelete = styled.button`
width: 12px;
height: 12px;
background: url(${IconDelete}) 50% 50% no-repeat;
width: 12px;
height: 12px;
background: url(${IconDelete}) 50% 50% no-repeat;
`;

View File

@@ -1,339 +1,145 @@
import { useState, Fragment, useEffect } from 'react';
import CheckBox from '../../components/common/input/CheckBox';
import Modal from '../../components/common/modal/Modal';
import Button from '../../components/common/button/Button';
import styled from 'styled-components';
import { Title, FormWrapper, TableInfo, ListCount, ListOption, TableStyle, SelectInput, TableWrapper, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
import { useState, Fragment, useRef } from 'react';
import { Title, FormWrapper } from '../../styles/Components';
import { useNavigate } from 'react-router-dom';
import { UserBlockDetailModal, UserBlockSearchBar } from '../../components/ServiceManage/';
import { BlackListView, BlackListDetail, BlackListDelete } from '../../apis';
import {
CommonSearchBar,
useCommonSearch,
UserBlockDetailModal,
} from '../../components/ServiceManage/';
import { BlackListDetail, BlackListDelete } from '../../apis';
import Pagination from '../../components/common/Pagination/Pagination';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import { authType, blockPeriod, blockSanctions, blockStatus } from '../../assets/data';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data';
import { CaliTable, TableHeader } from '../../components/common';
import { useModal, useTable, withAuth } from '../../hooks/hook';
import { alertTypes } from '../../assets/data/types';
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import tableInfo from '../../assets/data/pages/userBlockTable.json'
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
const UserBlock = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const navigate = useNavigate();
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
// 페이지 네이션 관련
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
// 데이터 조회 관련
const [dataList, setDataList] = useState([]);
const [detailView, setDetailView] = useState('hidden');
const [selectedRow, setSelectedRow] = useState([]);
const [selectedData, setSelectedData] = useState([]);
const [detail, setDetail] = useState([]);
const [requestValue, setRequestValue] = useState([]);
// 모달 관련 변수
const [confirmModal, setConfirmModal] = useState('hidden');
const [completeModal, setCompleteModal] = useState('hidden');
// 검색 관련 변수
const [orderBy, setOrderBy] = useState('DESC');
const [searchData, setSearchData] = useState({
searchType: 'GUID',
searchKey: '',
status: 'ALL',
sanctions: 'ALL',
period: 'ALL',
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
});
// 등록하기 모달 연결
const handleDetail = async dataValue => {
if (detailView === 'hidden') {
setDetailView('block');
} else {
setDetailView('hidden');
setDetail([]);
const {
config,
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams,
configLoaded
} = useCommonSearch(token, "userBlockSearch");
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.list || [], {mode: 'single'});
const handleAction = async (action, item = null) => {
switch (action) {
case "detail":
await BlackListDetail(token, item.id).then(data => {
setDetail(data);
handleModalView('detail');
});
break;
case "delete":
showModal('USER_BLOCK_DELETE_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleAction('deleteConfirm')
});
break;
case "deleteConfirm":
let list = [];
selectedRows.map(data => {
list.push({
id: data.id,
});
});
await withLoading(async () => {
return BlackListDelete(token, list);
}).then(data => {
showToast('DEL_COMPLETE', {type: alertTypes.success});
}).catch(reason => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleSearch(updateSearchParams);
});
break;
}
setSelectedData(dataValue);
const id = dataValue.id;
if (id) {
setDetail(await BlackListDetail(token, id));
}
};
const fetchData = async (searchType, data, email, status, sanctions, period, order, size) => {
setDataList(await BlackListView(token, searchType, data, email, status, sanctions, period, order ? order : orderBy, size ? size : pageSize, currentPage));
};
// 검색 기능
const handleSearch = (searchType, data, email, status, sanctions, period) => {
fetchData(searchType, data, email, status, sanctions, period);
};
// 배열 정렬
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order);
fetchData(searchData.searchType, searchData.searchKey, searchData.email, searchData.status, searchData.sanctions, searchData.period, order, pageSize);
};
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
fetchData(searchData.searchType, searchData.searchKey, searchData.email, searchData.status, searchData.sanctions, searchData.period, orderBy, size, 1);
};
useEffect(() => {
fetchData(searchData.searchType, searchData.searchKey, searchData.email, searchData.status, searchData.sanctions, searchData.period, orderBy, pageSize);
setSelectedRow([]);
}, [currentPage]);
// 전체 선택
const handleAllSelect = () => {
const checkAll = document.getElementById('check-all');
let list = [];
if (checkAll.checked === true) {
dataList.list.map((data, index) => {
document.getElementsByName('select')[index].checked = true;
list.push(String(data.id));
});
} else if (checkAll.checked === false) {
for (let i = 0; i < dataList.list.length; i++) {
dataList.list.map((data, index) => (document.getElementsByName('select')[index].checked = false));
list = [];
}
}
setSelectedRow(list);
};
// 일부 선택
const handleSelectCheckBox = e => {
let list = [...selectedRow];
if (e.target.checked) {
list.push(e.target.id);
setSelectedRow(list);
} else {
const filterList = list.filter(data => e.target.id !== data);
setSelectedRow(filterList);
}
};
// 삭제 여부 모달
const handleConfirmeModalClose = () => {
if (confirmModal === 'hidden') {
setConfirmModal('view');
} else {
setConfirmModal('hidden');
setRequestValue([]);
}
};
// 선택, 복수 삭제 모달창
const handleConfirmModal = data => {
if (data.id) {
setSelectedData(data.id);
if (selectedData) {
setRequestValue(data.id);
}
} else if (selectedRow) {
setRequestValue(selectedRow);
}
handleConfirmeModalClose();
};
// 완료 확인 모달
const handleCompleteModal = () => {
if (completeModal === 'hidden') {
setCompleteModal('view');
} else {
setCompleteModal('hidden');
setRequestValue([]);
handleConfirmeModalClose();
window.location.reload();
}
};
// 삭제 기능 구현
const handleDelete = () => {
let list = [];
if (requestValue === selectedRow) {
selectedRow.map(data =>
list.push({
id: data,
}),
);
BlackListDelete(token, list);
// console.log(list);
// console.log(selectedRow);
handleCompleteModal();
} else if (requestValue !== selectedRow) {
list.push({ id: selectedData });
// console.log(list);
BlackListDelete(token, list);
handleCompleteModal();
}
};
const handleCountSelectedRow = () => {
return currentPage > (dataList && dataList.total) / pageSize ? selectedRow.length === dataList.total % pageSize : selectedRow.length === Number(pageSize);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListRead) ? (
<AuthModal/>
) : (
<>
<Title>이용자 제재 조회 등록</Title>
<FormWrapper>
<UserBlockSearchBar handleSearch={handleSearch} setResultData={setSearchData} />
</FormWrapper>
<TableInfo>
<ListCount>
: {dataList && dataList.total} / {dataList && dataList.total_all}
</ListCount>
<ListOption>
<SelectInput
name=""
id=""
className="input-select"
onChange={e => {
handleOrderBy(e);
}}>
<option value="DESC">내림차순</option>
<option value="ASC">오름차순</option>
</SelectInput>
<SelectInput
name=""
id=""
onChange={e => {
handlePageSize(e);
}}
className="input-select">
<option value="50">50</option>
<option value="100">100</option>
</SelectInput>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && (
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} type="submit" text="선택 삭제" id={selectedRow} handleClick={() => handleConfirmModal(selectedRow)} />
)}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 25) && (
<Button
theme="primary"
type="submit"
text="제재 등록"
handleClick={e => {
e.preventDefault();
navigate('/servicemanage/userblock/userblockregist');
}}
/>
)}
</ListOption>
</TableInfo>
<TableWrapper>
<TableStyle>
<caption></caption>
<thead>
<tr>
<th width="40">
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
</th>
<th width="80">번호</th>
<th width="20%">GUID</th>
<th width="20%">아바타명</th>
<th width="100">상태</th>
<th width="100">제재 기간</th>
<th width="250">제재 사유</th>
<th width="150">등록자</th>
<th width="110">상세정보</th>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && <th width="80">삭제</th>}
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map(data => (
<Fragment key={data.id}>
<tr>
<td>
<CheckBox name={'select'} id={data.id} setData={e => handleSelectCheckBox(e)} />
</td>
<td>{data.row_num}</td>
<td>{data.guid}</td>
<td>{data.nickname}</td>
{data.status === 'INPROGRESS' ? (
<td>
<ListState>{blockStatus.map(item => item.value === data.status && item.name)}</ListState>
</td>
) : (
<td>{blockStatus.map(item => item.value === data.status && item.name)}</td>
)}
<td>{blockPeriod.map(item => item.value === data.period && item.name)}</td>
<td>{blockSanctions.map(item => item.value === data.sanctions && item.name)}</td>
<td>{data.create_by}</td>
<td>
<Button theme="line" text="상세보기" handleClick={() => handleDetail(data)} />
</td>
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && (
<td>
<Button theme="line" id={data.id} name="single" text="삭제" handleClick={() => handleConfirmModal(data)} />
</td>
)}
</tr>
</Fragment>
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<UserBlockDetailModal stateModal={detailView} data={detail} handleModal={handleDetail} />
<Title>이용자 제재 조회 등록</Title>
<FormWrapper>
<CommonSearchBar
config={config}
searchParams={searchParams}
onSearch={(newParams, executeSearch = true) => {
if (executeSearch) {
handleSearch(newParams);
} else {
updateSearchParams(newParams);
}
}}
onReset={handleReset}
/>
</FormWrapper>
<TableHeader
config={tableInfo.header}
total={dataList?.total}
total_all={dataList?.total_all}
handleOrderBy={handleOrderByChange}
handlePageSize={handlePageSizeChange}
selectedRows={selectedRows}
onAction={handleAction}
navigate={navigate}
/>
<CaliTable
columns={tableInfo.columns}
data={dataList?.list}
selectedRows={selectedRows}
onSelectRow={handleSelectRow}
onAction={handleAction}
/>
<Pagination
postsPerPage={searchParams.pageSize}
totalPosts={dataList?.total_all}
setCurrentPage={handlePageChange}
currentPage={searchParams.currentPage}
pageLimit={INITIAL_PAGE_LIMIT}
/>
{/* 선택 삭제 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleConfirmeModalClose} />
</BtnWrapper>
<ModalText $align="center">
제재 대상을 삭제하시겠습니까?
<br />
삭제 제재가 해제됩니다.
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleConfirmeModalClose} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleDelete} />
</BtnWrapper>
</Modal>
{/* 완료창 모달 */}
{/* 삭제 완료 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModal}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCompleteModal} />
</BtnWrapper>
<ModalText $align="center">삭제가 완료되었습니다.</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModal} />
</BtnWrapper>
</Modal>
</>
)}
<UserBlockDetailModal
stateModal={modalState.detailModal}
data={detail}
handleModal={() => handleModalClose('detail')}
/>
</>
);
};
export default UserBlock;
const ListState = styled.span`
color: #d60000;
`;
export default withAuth(authType.blackListRead)(UserBlock);

View File

@@ -1,12 +1,11 @@
import { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import Button from '../../components/common/button/Button';
import RadioInput from '../../components/common/input/Radio';
import styled from 'styled-components';
import { Title, BtnWrapper, TextInput, SelectInput, DatePickerWrapper, ButtonClose, ModalText } from '../../styles/Components';
import { Title, BtnWrapper, TextInput, SelectInput, DatePickerWrapper } from '../../styles/Components';
import Modal from '../../components/common/modal/Modal';
import DatePickerComponent from '../../components/common/Date/DatePickerComponent';
import { authType, blockPeriod, blockSanctions, blockType, HourList, MinuteList } from '../../assets/data';
@@ -17,19 +16,23 @@ import { getMonth, getYear, setHours } from 'date-fns';
import range from 'lodash/range';
import { useNavigate } from 'react-router-dom';
import UserBlockUploadBtn from '../../components/ServiceManage/UserBlockUploadBtn';
import { BlackListRegist, BlackListMultipleUpload } from '../../apis';
import { BlackListRegist} from '../../apis';
import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil';
import AuthModal from '../../components/common/modal/AuthModal';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { alertTypes } from '../../assets/data/types';
registerLocale('ko', ko);
const UserBlockRegist = () => {
const token = sessionStorage.getItem('token');
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
//시간 관련 변수
const [sendHour, setSendHour] = useState('00');
@@ -44,8 +47,6 @@ const UserBlockRegist = () => {
const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const [registModalClose, setRegistModalClose] = useState('hidden');
const [completeModalClose, setCompleteModalClose] = useState('hidden');
const [isNullValue, setIsNullValue] = useState(false);
const [guidList, setGuidList] = useState([]);
@@ -58,7 +59,6 @@ const UserBlockRegist = () => {
start_dt: '',
end_dt: '',
});
const [confirmText, setConfirmText] = useState('');
const handleChange = e => {
setSelectData(e.target.value);
@@ -76,12 +76,6 @@ const UserBlockRegist = () => {
});
};
const [previewModal, setPreviewModal] = useState('hidden');
const handlePreview = e => {
e.preventDefault();
previewModal === 'hidden' ? setPreviewModal('view') : setPreviewModal('hidden');
};
useEffect(() => {
if (selectData === 'single') {
setSelectSingle(false);
@@ -113,6 +107,14 @@ const UserBlockRegist = () => {
}
}, [selectData, guidList]);
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
// 제재 시간 세팅 로직
const handleSendTime = e => {
let sendDate = new Date(resultData.start_dt);
@@ -212,57 +214,54 @@ const UserBlockRegist = () => {
if (resultData.period === 'WARNING') setEndHour('01');
};
// 등록 확인 모달
let notNull = resultData.list.length > 0 && resultData.type && resultData.period && resultData.sanctions && resultData.start_dt && resultData.end_dt;
const callbackPage = () => {
navigate('/servicemanage/userblock');
}
const handleRegistModalClose = () => {
if(selectData === 'multi'){
const guidChk = guidList.some(guid => guid.validate === false);
if(guidChk) {
alert("유효성 체크가 통과되지 않은 항목이 존재합니다. \r\n수정 후 재등록 해주세요.")
return;
}
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case 'submit':
if (!checkCondition()) return;
if(selectData === 'multi'){
const guidChk = guidList.some(guid => guid.validate === false);
if(guidChk) {
showToast('USER_BLOCK_VALIDATION_WARNING', {type: alertTypes.warning});
return;
}
};
showModal('USER_BLOCK_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm'),
});
break;
if (notNull) {
registModalClose === 'hidden' ? setRegistModalClose('view') : setRegistModalClose('hidden');
setIsNullValue(false);
} else {
setIsNullValue(true);
case 'registConfirm':
await withLoading(async () => {
return await BlackListRegist(token, resultData);
}).then(data => {
if(data.result === 'ERROR'){
showToast(data.data.message, {type: alertTypes.error});
}else{
showToast('REGIST_COMPLTE', {type: alertTypes.success});
}
}).catch(error => {
showToast('API_FAIL', { type: alertTypes.error });
}).finally(() => {
callbackPage();
});
break;
}
};
// 등록 완료 모달
const handleCompleteModalClose = () => {
if (completeModalClose === 'hidden') {
setCompleteModalClose('view');
} else {
setCompleteModalClose('hidden');
handleReset();
handleRegistModalClose();
navigate('/servicemanage/userblock');
window.location.reload();
}
const checkCondition = () => {
return resultData.list.length > 0 &&
resultData.type &&
resultData.period &&
resultData.sanctions &&
resultData.start_dt &&
resultData.end_dt;
};
// 등록 api 연동
const handleRegist = async () => {
const message = await BlackListRegist(token, resultData);
message.data.data.message === '등록 하였습니다.' ?
setConfirmText('등록이 완료되었습니다.') :
message.data.data.message === 'admindb_exit_error' ?
setConfirmText("이미 등록된 유저입니다.") : setConfirmText(message.data.data.message);
handleRegistModalClose();
handleCompleteModalClose();
};
// console.log('DataList: ', resultData);
// console.log('resultData.period :', resultData.period);
// console.log(resultData.period.length);
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListUpdate) ? (
@@ -547,43 +546,23 @@ const UserBlockRegist = () => {
{isNullValue && <SearchBarAlert>설정값을 모두 입력해주세요.</SearchBarAlert>}
<BtnWrapper $justify="flex-end" $gap="10px">
<Button
text="취소"
theme="line"
handleClick={e => {
e.preventDefault();
navigate('/servicemanage/userblock');
}}
/>
<Button type="submit" text="등록" name="등록버튼" handleClick={handleRegistModalClose} theme={notNull ? 'primary' : 'disable'} />
<BtnWrapper $justify="flex-end" $gap="10px">
<Button
text="취소"
theme="line"
handleClick={() => showModal('MAIL_REGIST_CANCEL', {
type: alertTypes.confirm,
onConfirm: () => callbackPage(),
})}
/>
<Button
type="submit"
text="등록"
theme={checkCondition() ? 'primary' : 'disable'}
handleClick={() => handleSubmit('submit')}
/>
</BtnWrapper>
</BtnWrapper>
{/* 일괄등록 모달창 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={registModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleRegistModalClose} />
</BtnWrapper>
<ModalText $align="center">
이용자 제재 명단에
<br />
등록하시겠습니까?
</ModalText>
<BtnWrapper $gap="10px">
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleRegistModalClose} />
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleRegist} />
</BtnWrapper>
</Modal>
{/* 등록 완료 모달 */}
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={completeModalClose}>
<BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCompleteModalClose} />
</BtnWrapper>
<ModalText $align="center">{confirmText}</ModalText>
<BtnWrapper $gap="10px">
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleCompleteModalClose} />
</BtnWrapper>
</Modal>
</>
)}
</>

View File

@@ -10,14 +10,17 @@ import { Title, FormWrapper, BtnWrapper, TableStyle } from '../../styles/Compone
import { GroupDetailViewList, GroupModify } from '../../apis';
import Button from '../../components/common/button/Button';
import AuthModal from '../../components/common/modal/AuthModal';
import DynamicModal from '../../components/common/modal/DynamicModal';
import { AuthGroupRows } from '../../components/UserManage';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
const AuthSettingUpdate = () => {
const location = useLocation();
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const { showModal, showToast } = useAlert();
const { withLoading } = useLoading();
const id = location.pathname.split('/')[3];
const [msg, setMsg] = useState('');
@@ -62,27 +65,15 @@ const AuthSettingUpdate = () => {
});
};
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
const handleNavigate = () => {
navigate('/usermanage/authsetting');
}
const handleSubmit = async (type, param = null) => {
switch (type) {
case "cancelConfirm":
setMsg(t('CANCEL_COMPLETED'));
handleModalClose('cancelConfirm');
handleModalView('complete');
showToast('CANCEL_COMPLETED', {type: alertTypes.success});
handleNavigate();
break;
case "registConfirm":
const token = sessionStorage.getItem('token');
@@ -90,18 +81,16 @@ const AuthSettingUpdate = () => {
.flat()
.map(permissionId => ({ auth_id: permissionId }));
await GroupModify(token, id, resultList);
await withLoading(async () => {
return await GroupModify(token, id, resultList);
}).then(data => {
showToast('SAVE_COMPLETED', {type: alertTypes.success});
}).catch(error => {
setMsg(t('SAVE_COMPLETED'));
}).finally(() => {
handleNavigate();
});
handleModalClose('registConfirm');
handleModalView('complete');
break;
case "complete":
handleModalClose('complete');
setMsg('');
navigate('/usermanage/authsetting');
// window.location.reload();
break;
}
}
@@ -140,40 +129,25 @@ const AuthSettingUpdate = () => {
<BtnWrapper $justify="flex-end" $gap="5px">
<Button
theme="line"
handleClick={() => handleModalView('cancelConfirm')}
handleClick={() =>
showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('cancelConfirm')
})
}
text="취소"
/>
<Button
theme="primary"
handleClick={() => handleModalView('registConfirm')}
handleClick={() => showModal('SAVE_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
})}
text="저장"
/>
</BtnWrapper>
</FormWrapper>
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelConfirmModal}
modalText={t('CANCEL_CONFIRM')}
handleSubmit={() => handleSubmit('cancelConfirm')}
handleCancel={() => handleModalClose('cancelConfirm')}
/>
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={t('SAVE_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
<DynamicModal
modalType={modalTypes.completed}
view={modalState.completeModal}
modalText={msg}
handleSubmit={() => handleSubmit('complete')}
/>
</>
)}
</>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, Fragment, useRef } from 'react';
import { useState, Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css';
@@ -7,7 +7,6 @@ import { authList } from '../../store/authList';
import {
authType,
commonStatus,
modalTypes,
ViewTitleCountType,
caliumStatus
} from '../../assets/data';
@@ -17,159 +16,79 @@ import {
ChargeBtn,
StatusLabel,
} from '../../styles/ModuleComponents';
import {Button, ExcelDownButton, Pagination, DynamicModal, ViewTableInfo, Loading} from '../../components/common';
import {Button, ExcelDownButton, Pagination, ViewTableInfo} from '../../components/common';
import { convertKTC, truncateText } from '../../utils';
import { CaliumRequestRegistModal } from '../../components/UserManage';
import { CaliumCharge, CaliumRequestView } from '../../apis';
import { withAuth } from '../../hooks/hook';
import { convertEndDateToISO, convertStartDateToISO } from '../../utils/date';
import {CaliumRequestSearchBar} from '../../components/ServiceManage';
import { CaliumCharge } from '../../apis';
import { useModal, withAuth } from '../../hooks/hook';
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { useAlert } from '../../context/AlertProvider';
import { useLoading } from '../../context/LoadingProvider';
import { alertTypes } from '../../assets/data/types';
const CaliumRequest = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const { showModal, showToast } = useAlert();
const {withLoading} = useLoading();
const tableRef = useRef(null);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [dataList, setDataList] = useState([]);
const [detailData, setDetailData] = useState('');
const [loading, setLoading] = useState(false);
const [alertMsg, setAlertMsg] = useState('');
const [selectCharge, setSelectCharge] = useState({});
const [searchData, setSearchData] = useState({
content: '',
status: 'ALL',
startDate: '',
endDate: '',
});
const [orderBy, setOrderBy] = useState('DESC');
const [modalState, setModalState] = useState({
detailModal: 'hidden',
registerModal: 'hidden',
chargedCompleteModal: 'hidden',
chargedConfirmModal: 'hidden'
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
register: 'hidden',
});
useEffect(() => {
fetchData('', 'ALL', '', '');
}, [currentPage]);
// 리스트 조회
const fetchData = async (content, status, startDate, endDate, order, size) => {
setDataList(
await CaliumRequestView(
token,
content,
status,
startDate && convertStartDateToISO(startDate),
endDate && convertEndDateToISO(endDate),
order ? order : orderBy,
size ? size : pageSize,
currentPage,
),
);
};
// 검색 함수
const handleSearch = (content, status, startDate, endDate) => {
fetchData(content, status, startDate, endDate);
};
// 오름차순 내림차순
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order);
fetchData(
searchData.content,
searchData.status,
searchData.startDate,
searchData.endDate,
order,
pageSize,
);
};
const handlePageSize = e => {
const size = e.target.value;
setPageSize(size);
setCurrentPage(1);
fetchData(
searchData.content,
searchData.status,
searchData.startDate,
searchData.endDate,
orderBy,
size,
1,
);
};
// 상세보기 호출
// const handleDetailModal = async (e, id) => {
// await EventDetailView(token, id).then(data => {
// setDetailData(data);
// });
//
// e.preventDefault();
// handleModalView('detail');
// };
const handleModalView = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const {
config,
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams,
configLoaded
} = useCommonSearch(token, "caliumRequestSearch");
const handleSubmit = async (type, param = null) => {
switch (type) {
case "detail":
setDetailData(param);
handleModalView('detail');
showModal(param, {
type: alertTypes.info
});
break;
case "chargedConfirm":
setSelectCharge({id: param.id, count: param.count});
handleModalView('chargedConfirm');
break;
case "charged":
handleModalClose('chargedConfirm');
setLoading(true);
await CaliumCharge(token, selectCharge).then(data => {
setLoading(false);
if(data.result === "SUCCESS") {
setAlertMsg(t('CHARGE_COMPLTED'));
handleModalView('chargedComplete');
}else if(data.result === "ERROR_CALIUM_FINISH") {
setAlertMsg(t('CHARGE_FINISH_FAIL'));
handleModalView('chargedComplete');
}else{
setAlertMsg(t('CHARGE_FAIL'));
handleModalView('chargedComplete');
}
setSelectCharge({});
showModal('CALIUM_CHARGE_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('charged')
});
break;
case "chargedComplete":
handleModalClose('chargedComplete');
await fetchData(
searchData.content,
searchData.status,
searchData.startDate,
searchData.endDate
);
case "charged":
await withLoading( async () => {
return await CaliumCharge(token, selectCharge);
}).then(data => {
if(data.result === "SUCCESS") {
showToast('CHARGE_COMPLTED', {type: alertTypes.success});
}else if(data.result === "ERROR_CALIUM_FINISH") {
showToast('CHARGE_FINISH_FAIL', {type: alertTypes.error});
}else{
showToast('CHARGE_FAIL', {type: alertTypes.error});
}
}).catch(error => {
showToast('API_FAIL', {type: alertTypes.error});
}).finally(() =>{
setSelectCharge({});
handleSearch(updateSearchParams);
});
break;
}
}
@@ -178,9 +97,20 @@ const CaliumRequest = () => {
<>
<Title>칼리움 사용 수량 요청</Title>
<FormWrapper>
<CaliumRequestSearchBar handleSearch={handleSearch} setResultData={setSearchData} />
<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={handleOrderBy} handlePageSize={handlePageSize} countType={ViewTitleCountType.calium}>
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange} countType={ViewTitleCountType.calium}>
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_CALIUM_REQUEST')} />
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
<Button
@@ -210,8 +140,7 @@ const CaliumRequest = () => {
</tr>
</thead>
<tbody>
{dataList.list &&
dataList.list.map(calium => (
{dataList?.list?.map(calium => (
<Fragment key={calium.row_num}>
<tr>
{/*<td>{calium.row_num}</td>*/}
@@ -238,39 +167,23 @@ const CaliumRequest = () => {
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} />
<Pagination
postsPerPage={searchParams.pageSize}
totalPosts={dataList?.total_all}
setCurrentPage={handlePageChange}
currentPage={searchParams.currentPage}
pageLimit={INITIAL_PAGE_LIMIT}
/>
{/*상세*/}
{/*<EventDetailModal detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData}/>*/}
<CaliumRequestRegistModal
registView={modalState.registerModal}
setRegistView={() => {
handleModalClose('register');
handleSearch(updateSearchParams);
}}
userInfo={userInfo}
/>
<CaliumRequestRegistModal registView={modalState.registerModal} setRegistView={() => handleModalClose('register')} userInfo={userInfo} />
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.chargedConfirmModal}
handleCancel={() => handleModalClose('chargedConfirm')}
handleSubmit={() => handleSubmit('charged')}
modalText={t('CALIUM_CHARGE_CONFIRM')}
/>
<DynamicModal
modalType={modalTypes.completed}
view={modalState.chargedCompleteModal}
modalText={alertMsg}
handleSubmit={() => handleSubmit('chargedComplete')}
/>
<DynamicModal
modalType={modalTypes.completed}
view={modalState.chargedCompleteModal}
modalText={alertMsg}
handleSubmit={() => handleSubmit('chargedComplete')}
/>
<DynamicModal
modalType={modalTypes.completed}
view={modalState.detailModal}
modalText={detailData}
handleSubmit={() => handleModalClose('detail')}
/>
{loading && <Loading/>}
</>
);
};