Compare commits

...

3 Commits

Author SHA1 Message Date
3e5c3f0167 ai 연동 2025-04-25 15:56:56 +09:00
826459f304 toast 메시지 추가
alert 글로벌화
loading 글로벌화
2025-04-25 15:33:21 +09:00
d2ac5b338e 메뉴 배너 관리 2025-04-21 14:14:34 +09:00
64 changed files with 4390 additions and 2390 deletions

View File

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

View File

@@ -26,7 +26,7 @@ import {
UserBlockRegist, UserBlockRegist,
WhiteList, WhiteList,
LandAuction, LandAuction,
BattleEvent BattleEvent, MenuBanner, MenuBannerRegist,
} from './pages/ServiceManage'; } from './pages/ServiceManage';
const RouteInfo = () => { const RouteInfo = () => {
@@ -75,6 +75,8 @@ const RouteInfo = () => {
<Route path="event/eventregist" element={<EventRegist />} /> <Route path="event/eventregist" element={<EventRegist />} />
<Route path="landauction" element={<LandAuction />} /> <Route path="landauction" element={<LandAuction />} />
<Route path="battleevent" element={<BattleEvent />} /> <Route path="battleevent" element={<BattleEvent />} />
<Route path="menubanner" element={<MenuBanner />} />
<Route path="menubanner/menubannerregist" element={<MenuBannerRegist />} />
</Route> </Route>
</Route> </Route>
</Routes> </Routes>

View File

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

114
src/apis/Menu.js Normal file
View File

@@ -0,0 +1,114 @@
//운영서비스 관리 - 메뉴배너 api 연결
import { Axios } from '../utils';
// 리스트 조회
export const MenuBannerView = async (token, searchData, status, startDate, endDate, order, size, currentPage) => {
try {
const res = await Axios.get(
`/api/v1/menu/banner/list?search_data=${searchData}&status=${status}&start_dt=${startDate}&end_dt=${endDate}
&orderby=${order}&page_no=${currentPage}&page_size=${size}`,
{
headers: { Authorization: `Bearer ${token}` },
},
);
return res.data.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuBannerView Error', e);
}
}
};
// 상세보기
export const MenuBannerDetailView = async (token, id) => {
try {
const res = await Axios.get(`/api/v1/menu/banner/detail/${id}`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuBannerDetailView Error', e);
}
}
};
// 등록
export const MenuBannerSingleRegist = async (token, params) => {
try {
const res = await Axios.post(`/api/v1/menu/banner`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuBannerSingleRegist Error', e);
}
}
};
// 수정
export const MenuBannerModify = async (token, id, params) => {
try {
const res = await Axios.put(`/api/v1/menu/banner/${id}`, params, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuBannerModify Error', e);
}
}
};
// 삭제
export const MenuBannerDelete = async (token, params) => {
try {
const res = await Axios.delete(`/api/v1/menu/banner/delete`, {
headers: { Authorization: `Bearer ${token}` },
data: { list: params },
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuBannerDelete Error', e);
}
}
};
export const MenuImageUpload = async (token, file) => {
const exelFile = new FormData();
exelFile.append('file', file);
try {
const res = await Axios.post(`/api/v1/menu/image-upload`, exelFile, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${token}`,
},
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuImageUpload', e);
}
}
};
export const MenuImageDelete = async (token, filename) => {
try {
const res = await Axios.get(`/api/v1/menu/image-delete?file=${filename}`, {
headers: {Authorization: `Bearer ${token}`},
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('MenuImageDelete', e);
}
}
};

21
src/apis/OpenAI.js Normal file
View File

@@ -0,0 +1,21 @@
//AI api 연결
import { Axios } from '../utils';
export const AnalyzeAI = async (token, params) => {
try {
const res = await Axios.post('/api/v1/ai/analyze', params, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
});
return res.data;
} catch (e) {
if (e instanceof Error) {
throw new Error('analyzeAI Error', e);
}
}
};

View File

@@ -12,3 +12,5 @@ export * from './Item';
export * from './Event'; export * from './Event';
export * from './Calium'; export * from './Calium';
export * from './Land'; export * from './Land';
export * from './Menu';
export * from './OpenAI';

View File

@@ -7,5 +7,6 @@ export const NONE = 'NONE';
export const ONE_MINUTE_MS = 60000; export const ONE_MINUTE_MS = 60000;
export const ONE_MINUTE_SECOND = 60; export const ONE_MINUTE_SECOND = 60;
export const AUCTION_MIN_MINUTE_TIME = 15; // 15분 export const AUCTION_MIN_MINUTE_TIME = 15; // 15분
export const IMAGE_MAX_SIZE = 5242880;
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT }; export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };

View File

@@ -24,11 +24,39 @@ export const caliumRequestInitData = {
content: '', 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 = { export const STATUS_STYLES = {
COMPLETE: { COMPLETE: {
background: '#58AB62', background: '#58AB62',
color: 'white' color: 'white'
}, },
EXPIRATION: {
background: '#58AB62',
color: 'white'
},
WAIT: { WAIT: {
background: '#DEBB46', background: '#DEBB46',
color: 'black' color: 'black'
@@ -57,6 +85,10 @@ export const STATUS_STYLES = {
background: '#4287f5', background: '#4287f5',
color: 'white' color: 'white'
}, },
INPROGRESS: {
background: '#4287f5',
color: 'white'
},
AUCTION_END: { AUCTION_END: {
background: '#A37FB8', background: '#A37FB8',
color: 'white' color: 'white'
@@ -73,6 +105,10 @@ export const STATUS_STYLES = {
background: '#FFB59B', background: '#FFB59B',
color: 'white' color: 'white'
}, },
DELETE: {
background: '#FFB59B',
color: 'white'
},
RUNNING: { RUNNING: {
background: '#4287f5', background: '#4287f5',
color: 'white' color: 'white'

View File

@@ -28,4 +28,4 @@ export {
opUserSessionType, opUserSessionType,
opMailType, opMailType,
} from './options' } 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

@@ -194,6 +194,16 @@ export const menuConfig = {
view: true, view: true,
authLevel: adminAuthLevel.NONE authLevel: adminAuthLevel.NONE
}, },
menubanner: {
title: '메뉴 배너 관리',
permissions: {
read: authType.menuBannerRead,
update: authType.menuBannerUpdate,
delete: authType.menuBannerDelete
},
view: true,
authLevel: adminAuthLevel.NONE
},
} }
} }
}; };

View File

@@ -10,6 +10,18 @@ export const mailSendType = [
{ value: 'DIRECT_SEND', name: '즉시 발송' }, { 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 = [ export const mailSendStatus = [
{ value: 'ALL', name: '전체' }, { value: 'ALL', name: '전체' },
{ value: 'WAIT', name: '대기' }, { value: 'WAIT', name: '대기' },
@@ -278,6 +290,13 @@ export const opInputType = [
{ value: 'Boolean', name: '부울' }, { value: 'Boolean', name: '부울' },
]; ];
export const opMenuBannerStatus = [
{ value: 'ALL', name: '전체' },
{ value: 'WAIT', name: '대기' },
{ value: 'RUNNING', name: '진행중' },
{ value: 'FINISH', name: '만료' },
];
// export const logAction = [ // export const logAction = [
// { value: "None", name: "ALL" }, // { value: "None", name: "ALL" },
// { value: "AIChatDeleteCharacter", name: "NPC 삭제" }, // { value: "AIChatDeleteCharacter", name: "NPC 삭제" },

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

@@ -49,7 +49,9 @@ export const authType = {
battleEventUpdate: 47, battleEventUpdate: 47,
battleEventDelete: 48, battleEventDelete: 48,
businessLogRead: 49, businessLogRead: 49,
menuBannerRead: 50,
menuBannerUpdate: 51,
menuBannerDelete: 52,
levelReader: 999, levelReader: 999,
@@ -57,6 +59,15 @@ export const authType = {
levelDeveloper: 99999, levelDeveloper: 99999,
}; };
export const alertTypes = {
info: 0,
success: 1,
warning: 2,
error: 3,
confirm: 4,
confirmChildren: 5
}
export const adminAuthLevel = { export const adminAuthLevel = {
NONE: "None", NONE: "None",
READER: "Reader", READER: "Reader",

View File

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

View File

@@ -1,7 +1,6 @@
import React, { useState, Fragment, useEffect } from 'react'; import React, { useState, Fragment, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Button from '../../common/button/Button'; import Button from '../../common/button/Button';
import Loading from '../../common/Loading';
import { import {
Title, Title,
@@ -19,10 +18,8 @@ import {
FormStatusWarning, FormStatusWarning,
FormButtonContainer, FormButtonContainer,
} from '../../../styles/ModuleComponents'; } from '../../../styles/ModuleComponents';
import { modalTypes } from '../../../assets/data'; import { Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { DynamicModal, Modal, SingleDatePicker, SingleTimePicker } from '../../common';
import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants'; import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminConstants';
import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils'; import { convertKTCDate } from '../../../utils';
import { import {
battleEventHotTime, battleEventHotTime,
@@ -31,28 +28,19 @@ import {
battleRepeatType, battleRepeatType,
} from '../../../assets/data/options'; } from '../../../assets/data/options';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle'; 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 { isValidDayRange } from '../../../utils/date';
import { useAlert } from '../../../context/AlertProvider';
import { useLoading } from '../../../context/LoadingProvider';
const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => { const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { showToast, showModal } = useAlert();
const {withLoading} = useLoading();
const [loading, setLoading] = useState(false); // 로딩 창 const [isNullValue, setIsNullValue] = useState(false);
const { const [resultData, setResultData] = useState(initData);
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
useEffect(() => { useEffect(() => {
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){ 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()); const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) { if (endDay <= startDay) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING')); showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
return; return;
} }
} }
@@ -147,7 +135,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) { if (endDay <= startDay) {
setAlertMsg(t('BATTLE_EVENT_MODAL_START_DIFF_END_WARNING')); showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning});
return; return;
} }
@@ -166,7 +154,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
round_time: config.round_time round_time: config.round_time
}); });
} else { } 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 startDt = resultData.event_start_dt;
const endDt = resultData.event_end_dt; const endDt = resultData.event_end_dt;
if (modalType === TYPE_REGISTRY && startDt < minAllowedTime) { 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; return;
} }
if(resultData.repeat_type !== 'NONE' && !isValidDayRange(startDt, endDt)) { 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; return;
} }
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단 //화면에 머물면서 상태는 안바꼈을 경우가 있기에 시작시간 지났을경우 차단
if (modalType === TYPE_REGISTRY && startDt < new Date()) { 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; return;
} }
@@ -204,57 +192,48 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
setResultData({ ...resultData, round_time: config.round_time }); setResultData({ ...resultData, round_time: config.round_time });
} }
handleModalView('registConfirm'); showModal(isView('modify') ? 'BATTLE_EVENT_UPDATE_CONFIRM' : 'BATTLE_EVENT_REGIST_CONFIRM', {
break; type: alertTypes.confirm,
case "cancel": onConfirm: () => handleSubmit('registConfirm')
handleModalView('cancel'); });
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
break; break;
case "registConfirm": case "registConfirm":
setLoading(true);
if(isView('modify')){ if(isView('modify')){
await BattleEventModify(token, content?.id, resultData).then(data => { await withLoading( async () => {
setLoading(false); return await BattleEventModify(token, content?.id, resultData);
handleModalClose('registConfirm'); }).then(data => {
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('UPDATE_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){ }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{ }else{
setAlertMsg(t('UPDATE_FAIL')); showToast('UPDATE_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
}); });
} }
else{ else{
await BattleEventSingleRegist(token, resultData).then(data => { await withLoading( async () => {
setLoading(false); return await BattleEventSingleRegist(token, resultData);
handleModalClose('registConfirm'); }).then(data => {
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "ERROR_BATTLE_EVENT_TIME_OVER"){ }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{ }else{
setAlertMsg(t('REGIST_FAIL')); showToast('REGIST_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
}); });
} }
break; 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 <Button
type="submit" type="submit"
text={isView('modify') ? "수정" : "등록"} text={isView('modify') ? "수정" : "등록"}
@@ -421,38 +407,6 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
</FormButtonContainer> </FormButtonContainer>
</BtnWrapper> </BtnWrapper>
</Modal> </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 { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, Textarea, SearchBarAlert } from '../../../styles/Components';
import Button from '../../common/button/Button'; import Button from '../../common/button/Button';
import Modal from '../../common/modal/Modal'; import Modal from '../../common/modal/Modal';
import { EventIsItem, EventModify, MailModify } from '../../../apis'; import { EventIsItem, EventModify } from '../../../apis';
import { authList } from '../../../store/authList'; import { authList } from '../../../store/authList';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { authType, benItems, commonStatus, modalTypes, wellType } from '../../../assets/data'; import { authType, benItems, commonStatus, wellType } from '../../../assets/data';
import { import {
AppendRegistBox, AppendRegistTable, AreaBtnClose, AppendRegistBox, AppendRegistTable, AreaBtnClose,
BtnDelete, DetailInputItem, DetailInputRow, BtnDelete, DetailInputItem, DetailInputRow,
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState, DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState,
Item, ItemList, LangArea Item, ItemList, LangArea
} from '../../../styles/ModuleComponents'; } from '../../../styles/ModuleComponents';
import DynamicModal from '../../common/modal/DynamicModal';
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../../utils'; import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../../utils';
import DateTimeInput from '../../common/input/DateTimeInput'; 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 EventDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const {withLoading} = useLoading();
const {showModal, showToast} = useAlert();
const id = content && content.id; const id = content && content.id;
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate); 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 [resource, setResource] = useState('19010001');
const [resourceCount, setResourceCount] = useState(''); const [resourceCount, setResourceCount] = useState('');
const [modifyModal, setModifyModal] = useState('hidden');
const [completeModal, setCompleteModal] = useState('hidden');
const [resultData, setResultData] = useState({}); const [resultData, setResultData] = useState({});
const [modalState, setModalState] = useState({
updateConfirmModal: 'hidden',
updateCompleteModal: 'hidden',
});
const [isNullValue, setIsNullValue] = useState(false); const [isNullValue, setIsNullValue] = useState(false);
// 과거 판단 // 과거 판단
const [isPast, setIsPast] = useState(false); const [isPast, setIsPast] = useState(false);
@@ -56,7 +53,6 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
const [btnValidation, setBtnValidation] = useState(false); const [btnValidation, setBtnValidation] = useState(false);
const [isReadOnly, setIsReadOnly] = useState(false); const [isReadOnly, setIsReadOnly] = useState(false);
const [itemCheckMsg, setItemCheckMsg] = useState(''); const [itemCheckMsg, setItemCheckMsg] = useState('');
const [alertMsg, setAlertMsg] = useState('');
useEffect(() => { useEffect(() => {
if(content){ if(content){
@@ -122,12 +118,11 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
// 아이템 추가 // 아이템 추가
const handleItemList = async () => { const handleItemList = async () => {
if(benItems.includes(item)){ if(benItems.includes(item)){
setAlertMsg(t('MAIL_ITEM_ADD_BEN')) showToast('MAIL_ITEM_ADD_BEN', {type: alertTypes.warning});
return; return;
} }
if(item.length === 0 || itemCount.length === 0) return; if(item.length === 0 || itemCount.length === 0) return;
const token = sessionStorage.getItem('token');
const result = await EventIsItem(token, {item: item}); const result = await EventIsItem(token, {item: item});
if(result.data.result === "ERROR"){ 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) => { const handleSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "submit": case "submit":
if (!conditionCheck()) return; if (!conditionCheck()) return;
handleModalView('updateConfirm'); showModal('MAIL_UPDATE_SAVE', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('updateConfirm')
});
break; break;
case "updateConfirm": case "updateConfirm":
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date)) const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
// 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다. // 이벤트 시작 30분전이나 이미 SystemMail이 add된 상태에서는 수정할 수 없다.
if(content.add_flag || timeDiff <= 30){ if(content.add_flag || timeDiff <= 30){
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE')); showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
handleModalClose('updateConfirm');
return; return;
} }
await EventModify(token, id, resultData); withLoading( async () => {
handleModalClose('updateConfirm'); return await EventModify(token, id, resultData);
handleModalView('updateComplete'); }).catch(error => {
break; showToast('API_FAIL', {type: alertTypes.error});
case "updateComplete": }).finally(() => {
handleModalClose('updateComplete'); showToast('UPDATE_COMPLETED', {type: alertTypes.success});
window.location.reload(); handleDetailView();
break; });
case "warning":
setAlertMsg('');
break; break;
} }
} }
@@ -533,28 +514,6 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
)} )}
</BtnWrapper> </BtnWrapper>
</Modal> </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 { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils'; import { convertKTCDate } from '../../../utils';
import { msToMinutes } from '../../../utils/date'; 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 LandAuctionModal = ({ modalType, detailView, handleDetailView, content, setDetailData, landData, buildingData }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { showToast, showModal } = useAlert();
const [loading, setLoading] = useState(false); // 로딩 창 const [loading, setLoading] = useState(false);
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [message_lang, setMessage_lang] = useState('KO'); const [message_lang, setMessage_lang] = useState('KO');
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크 const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [selectLand, setSelectLand] = useState(initLandData); const [selectLand, setSelectLand] = useState(initLandData);
const [resultData, setResultData] = useState(initData); //데이터 정보 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); const minAllowedTime = new Date(new Date().getTime() + 5 * 60000);
if (isView('recv') && resultData.resv_start_dt < minAllowedTime) { 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; return;
} }
if (resultData.auction_start_dt < minAllowedTime) { 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; return;
} }
if(resultData.resv_start_dt >= resultData.auction_start_dt || resultData.resv_start_dt >= resultData.auction_end_dt) { 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; return;
} }
if(resultData.auction_start_dt >= resultData.auction_end_dt) { 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; return;
} }
const diffAuctionTime = resultData.auction_end_dt - resultData.auction_start_dt; const diffAuctionTime = resultData.auction_end_dt - resultData.auction_start_dt;
if(msToMinutes(diffAuctionTime) < AUCTION_MIN_MINUTE_TIME){ 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; return;
} }
//화면에 머물면서 상태는 안바꼈을 경우가 있기에 경매시작시간 지났을경우 차단 //화면에 머물면서 상태는 안바꼈을 경우가 있기에 경매시작시간 지났을경우 차단
if (modalType === TYPE_MODIFY && resultData.auction_start_dt < new Date()) { 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; return;
} }
handleModalView('registConfirm'); showModal(isView('modify') ? 'LAND_UPDATE_CONFIRM' : 'LAND_REGIST_CONFIRM', {
break; type: alertTypes.confirm,
case "cancel": onConfirm: () => handleSubmit('registConfirm')
handleModalView('cancel'); });
break;
case "cancelConfirm":
handleModalClose('cancel');
handleReset();
break; break;
case "registConfirm": case "registConfirm":
setLoading(true); setLoading(true);
@@ -223,44 +213,38 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
if(isView('modify')){ if(isView('modify')){
await LandAuctionModify(token, content?.id, resultData).then(data => { await LandAuctionModify(token, content?.id, resultData).then(data => {
setLoading(false); setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('UPDATE_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){ }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{ }else{
setAlertMsg(t('UPDATE_FAIL')); showToast('UPDATE_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
}); });
} }
else{ else{
await LandAuctionSingleRegist(token, resultData).then(data => { await LandAuctionSingleRegist(token, resultData).then(data => {
setLoading(false); setLoading(false);
handleModalClose('registConfirm');
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "ERROR_LAND_AUCTION_IMPOSSIBLE"){ }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"){ }else if(data.result === "ERROR_AUCTION_LAND_OWNER"){
setAlertMsg(t('LAND_AUCTION_ERROR_OWNER')); showToast('LAND_AUCTION_ERROR_OWNER', {type: alertTypes.error});
}else{ }else{
setAlertMsg(t('REGIST_FAIL')); showToast('REGIST_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => {
handleReset();
}); });
} }
break; 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="시작 일자" startLabel="시작 일자"
endLabel="종료 일자" endLabel="종료 일자"
reset={resetDateTime} reset={resetDateTime}
setAlert={setAlertMsg}
/> />
<DateTimeRangePicker <DateTimeRangePicker
label="경매기간" label="경매기간"
@@ -384,7 +367,6 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
startLabel="시작 일자" startLabel="시작 일자"
endLabel="종료 일자" endLabel="종료 일자"
reset={resetDateTime} reset={resetDateTime}
setAlert={setAlertMsg}
/> />
{/*<NoticeInputRow2>*/} {/*<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 <Button
type="submit" type="submit"
text={isView('modify') ? "수정" : "등록"} text={isView('modify') ? "수정" : "등록"}
@@ -462,36 +450,6 @@ const LandAuctionModal = ({ modalType, detailView, handleDetailView, content, se
</BtnWrapper> </BtnWrapper>
</Modal> </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/>} {loading && <Loading/>}
</> </>
); );

View File

@@ -25,28 +25,20 @@ import { NONE, TYPE_MODIFY, TYPE_REGISTRY } from '../../../assets/data/adminCons
import { useModal } from '../../../hooks/hook'; import { useModal } from '../../../hooks/hook';
import { convertKTCDate } from '../../../utils'; import { convertKTCDate } from '../../../utils';
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle'; 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 { isValidDayRange } from '../../../utils/date';
import CheckBox from '../../common/input/CheckBox'; import CheckBox from '../../common/input/CheckBox';
import { LandOwnedChangesRegist, LandOwnerChangesDelete, UserInfoView } from '../../../apis'; import { LandOwnedChangesRegist, LandOwnerChangesDelete, UserInfoView } from '../../../apis';
import { useLoading } from '../../../context/LoadingProvider';
import { useAlert } from '../../../context/AlertProvider';
const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, setDetailData }) => { const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, setDetailData }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const {showModal, showToast} = useAlert();
const [loading, setLoading] = useState(false); // 로딩 창 const {withLoading} = useLoading();
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
cancel: 'hidden',
registConfirm: 'hidden',
registComplete: 'hidden'
});
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크 const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보 const [resultData, setResultData] = useState(initData); //데이터 정보
@@ -126,94 +118,87 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
case "submit": case "submit":
if (!checkCondition()) return; if (!checkCondition()) return;
handleModalView('registConfirm'); showModal(isView() ? 'LAND_OWNED_CHANGES_SELECT_DELETE' : 'LAND_OWNED_CHANGES_REGIST_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
});
break; break;
case "cancel": case "cancel":
handleModalView('cancel'); showModal('CANCEL_CONFIRM', {
break; type: alertTypes.confirm,
case "cancelConfirm": onConfirm: () => handleReset()
handleModalClose('cancel'); });
handleReset();
break; break;
case "user": case "user":
if(isView()) return; if(isView()) return;
const guid = resultData.user_guid; const guid = resultData.user_guid;
if(!guid || guid.length !== 32){ if(!guid || guid.length !== 32){
setAlertMsg(t('WARNING_GUID_CHECK')) showToast('WARNING_GUID_CHECK', {type: alertTypes.warning});
return; 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){ if(Object.keys(data).length === 0){
setAlertMsg(t('WARNING_GUID_CHECK')); showToast('WARNING_GUID_CHECK', {type: alertTypes.error});
setResultData({ ...resultData, user_name: '' }) setResultData({ ...resultData, user_name: '' })
return; return;
} }
const nickname = data.char_info.character_name; const nickname = data.char_info.character_name;
setResultData({ ...resultData, user_name: nickname }) setResultData({ ...resultData, user_name: nickname })
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(()=>{
setLoading(false);
}); });
break; break;
case "registConfirm": case "registConfirm":
setLoading(true);
if(isView()){ if(isView()){
setLoading(false);
handleModalClose('registConfirm');
const resvDt = resultData.reservation_dt; const resvDt = resultData.reservation_dt;
const now = new Date(); const now = new Date();
if(resvDt < now){ if(resvDt < now){
setAlertMsg(t('LAND_OWNED_CHANGES_DELETE_TIME_WARNING')); showToast('LAND_OWNED_CHANGES_DELETE_TIME_WARNING', {type: alertTypes.warning});
handleReset(); handleReset();
return; return;
} }
await LandOwnerChangesDelete(token, resultData).then(data => { await withLoading(async () => {
handleModalClose('registConfirm'); return await LandOwnerChangesDelete(token, resultData);
}).then(data => {
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('CANCEL_COMPLETED', {type: alertTypes.success});
}else if(data.result === "ERROR_LAND_OWNER_CHANGES_RESERVATION"){ }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{ }else{
setAlertMsg(t('DELETE_FAIL')); showToast('DELETE_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => { }).finally(() => {
setLoading(false); handleReset();
}); });
}else{ }else{
await LandOwnedChangesRegist(token, resultData).then(data => { await withLoading(async () => {
handleModalClose('registConfirm'); return await LandOwnedChangesRegist(token, resultData);
}).then(data => {
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('registComplete'); showToast('REGIST_COMPLTE', {type: alertTypes.success});
}else if(data.result === "GUID_CHECK"){ }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"){ }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"){ }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{ }else{
setAlertMsg(t('REGIST_FAIL')); showToast('REGIST_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}).finally(() => { }).finally(() => {
setLoading(false); handleReset();
}); });
} }
break;
case "registComplete":
handleModalClose('registComplete');
handleReset();
break;
case "warning":
setAlertMsg('');
break; break;
} }
} }
@@ -347,37 +332,6 @@ const OwnerChangeModal = ({ modalType, detailView, handleDetailView, content, se
</BtnWrapper> </BtnWrapper>
</Modal> </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 })} />*/} {/*<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; 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 = [ const searchList = [
<> <>
<InputGroup> <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) => ( {landSearchType.map((data, index) => (
<option key={index} value={data.value}> <option key={index} value={data.value}>
{data.name} {data.name}
@@ -141,7 +141,7 @@ const LandInfoSearchBar = ({ searchParams, onSearch, onReset }) => {
placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'} placeholder={searchParams.landType === 'ID' ? '랜드 ID 입력' : '랜드명 입력'}
value={searchParams.landData} value={searchParams.landData}
width="300px" width="300px"
onChange={e => onSearch({ landData: e.target.value })} onChange={e => onSearch({ landData: e.target.value }, false)}
/> />
</InputGroup> </InputGroup>
</>, </>,

View File

@@ -109,15 +109,9 @@ const LogViewSearchBar = ({ handleSearch, resultData }) => {
maxDate={new Date()} 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; 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; 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 { useTranslation } from 'react-i18next';
import Button from '../common/button/Button'; import Button from '../common/button/Button';
import Loading from '../common/Loading';
import { import {
Title, Title,
@@ -13,23 +12,20 @@ import {
FormGroup, FormHelperText, FormInput, FormLabel, FormGroup, FormHelperText, FormInput, FormLabel,
FormTextArea, FormTextAreaWrapper, MessageWrapper, FormRowGroup, FormRowInput, FormTextArea, FormTextAreaWrapper, MessageWrapper, FormRowGroup, FormRowInput,
} from '../../styles/ModuleComponents'; } from '../../styles/ModuleComponents';
import { modalTypes, caliumRequestInitData } from '../../assets/data'; import { caliumRequestInitData } from '../../assets/data';
import {DynamicModal, Modal} from '../common'; import { Modal} from '../common';
import { CaliumLimitCount, CaliumRequestRegist } from '../../apis/Calium'; 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 CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const {showModal, showToast } = useAlert();
const [loading, setLoading] = useState(false); // 로딩 창 const {withLoading} = useLoading();
const [modalState, setModalState] = useState({
cancelModal: 'hidden',
registConfirmModal: 'hidden',
registCompleteModal: 'hidden',
}); // 모달 관리
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크 const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(caliumRequestInitData); //데이터 정보 const [resultData, setResultData] = useState(caliumRequestInitData); //데이터 정보
const [maxCount, setMaxCount] = useState(0) const [maxCount, setMaxCount] = useState(0)
@@ -75,69 +71,42 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
})); }));
}; };
const handleModalView = (type) => { const handleReset = () =>{
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
}
const initData = () =>{
setMaxCount(0); setMaxCount(0);
setResultData(caliumRequestInitData); setResultData(caliumRequestInitData);
setRegistView();
} }
const handleSubmit = async (type, param = null) => { const handleSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "maxCount": case "maxCount":
setLoading(true); await withLoading(async () => {
await CaliumLimitCount(token, resultData).then(data => { return await CaliumLimitCount(token, resultData);
}).then(data => {
setMaxCount(data.reward_total_count); setMaxCount(data.reward_total_count);
setLoading(false);
}).catch(reason => { }).catch(reason => {
console.log(reason); showToast('SEARCH_LIMIT_FAIL', {type: alertTypes.error})
setLoading(false); });
setAlertMsg(t('SEARCH_LIMIT_FAIL'));
})
break; break;
case "submit": case "submit":
if (!checkCondition()) return; if (!checkCondition()) return;
handleModalView('registConfirm'); showModal('CALIUM_REGIST_CONFIRM',{
break; type: alertTypes.confirm,
case "cancel": onConfirm: () => handleSubmit('registConfirm')
handleModalView('cancel'); })
break;
case "cancelConfirm":
initData();
handleModalClose('cancel');
setRegistView();
break; break;
case "registConfirm": case "registConfirm":
setLoading(true); await withLoading(async () => {
return await CaliumRequestRegist(token, resultData);
await CaliumRequestRegist(token, resultData).then(data => { }).then(data => {
setLoading(false); 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; break;
} }
} }
@@ -224,7 +193,14 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
</MessageWrapper> </MessageWrapper>
<BtnWrapper $gap="10px" $justify="center" $marginTop="20px"> <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 <Button
type="submit" type="submit"
text="등록" text="등록"
@@ -239,37 +215,6 @@ const CaliumRequestRegistModal = ({ registView, setRegistView, userInfo }) => {
</BtnWrapper> </BtnWrapper>
</Modal> </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'; } from '../../../styles/ModuleComponents';
import { HourList, MinuteList } from '../../../assets/data'; import { HourList, MinuteList } from '../../../assets/data';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAlert } from '../../../context/AlertProvider';
import { alertTypes } from '../../../assets/data/types';
const DateTimeRangePicker = ({ const DateTimeRangePicker = ({
label, label,
@@ -19,10 +21,11 @@ const DateTimeRangePicker = ({
disabled, disabled,
startLabel = '시작 일자', startLabel = '시작 일자',
endLabel = '종료 일자', endLabel = '종료 일자',
reset = false, reset = false
setAlert
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { showToast } = useAlert();
const [startHour, setStartHour] = useState('00'); const [startHour, setStartHour] = useState('00');
const [startMin, setStartMin] = useState('00'); const [startMin, setStartMin] = useState('00');
const [endHour, setEndHour] = useState('00'); const [endHour, setEndHour] = useState('00');
@@ -64,7 +67,7 @@ const DateTimeRangePicker = ({
newDate.setHours(parseInt(endHour), parseInt(endMin)); newDate.setHours(parseInt(endHour), parseInt(endMin));
if (startDate && newDate < startDate) { if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END')); showToast('TIME_START_DIFF_END', {type: alertTypes.warning});
newDate = new Date(startDate); newDate = new Date(startDate);
} }
@@ -99,7 +102,7 @@ const DateTimeRangePicker = ({
} }
if (startDate && newDate < startDate) { if (startDate && newDate < startDate) {
setAlert(t('TIME_START_DIFF_END')); showToast('TIME_START_DIFF_END', {type: alertTypes.warning});
newDate = new Date(startDate) newDate = new Date(startDate)
} }

View File

@@ -2,7 +2,7 @@ import { styled } from 'styled-components';
import { TextInput, SelectInput, SearchBarAlert, BtnWrapper } from '../../../styles/Components'; import { TextInput, SelectInput, SearchBarAlert, BtnWrapper } from '../../../styles/Components';
import Button from '../button/Button'; import Button from '../button/Button';
const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit }) => { const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction, onReset, handleSubmit, isSearch = true }) => {
return ( return (
<SearchbarStyle direction={direction}> <SearchbarStyle direction={direction}>
<SearchRow> <SearchRow>
@@ -22,12 +22,14 @@ const SearchBarLayout = ({ firstColumnData, secondColumnData, filter, direction,
{filter} {filter}
</SearchRow> </SearchRow>
)} )}
<SearchRow> {isSearch &&
<BtnWrapper $gap="8px"> <SearchRow>
<Button theme="search" text="검색" handleClick={handleSubmit} type="button" /> <BtnWrapper $gap="8px">
<Button theme="reset" handleClick={onReset} type="button" /> <Button theme="search" text="검색" handleClick={handleSubmit} type="button" />
</BtnWrapper> <Button theme="reset" handleClick={onReset} type="button" />
</SearchRow> </BtnWrapper>
</SearchRow>
}
</SearchbarStyle> </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, SelectInput,
TableInfo, TableInfo,
} from '../../../styles/Components'; } 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'; 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 ( return (
<TableInfo> <TableInfo>
{total !== undefined && total_all !== undefined && {total !== undefined && total_all !== undefined &&
<ListCount> <ListCount>
{ countType === ViewTitleCountType.total && `총 : ${total ?? 0} 건 / ${total_all ?? 0}`} {COUNT_TYPE_RENDERERS[countType] ?
{ countType === ViewTitleCountType.calium && COUNT_TYPE_RENDERERS[countType](total, total_all) :
<> COUNT_TYPE_RENDERERS[ViewTitleCountType.total](total, total_all)}
<TitleItem> </ListCount>
<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>}
<ListOption> <ListOption>
<OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} /> <OrderBySelect orderType={orderType} handleOrderBy={handleOrderBy} />
<PageSelect pageType={pageType} handlePageSize={handlePageSize} /> <PageSelect pageType={pageType} handlePageSize={handlePageSize} />
@@ -35,36 +35,44 @@ const ViewTableInfo = ({children, total, total_all, orderType, handleOrderBy, pa
); );
}; };
const OrderBySelect = ({orderType, handleOrderBy}) => { const COUNT_TYPE_RENDERERS = {
return( [ViewTitleCountType.total]: (total, total_all) => `총 : ${total ?? 0} 건 / ${total_all ?? 0}`,
orderType === "asc" ? [ViewTitleCountType.calium]: (total, total_all) => (
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}> <>
<option value="ASC">오름차순</option> <TitleItem>
<option value="DESC">내림차순</option> <TitleItemLabel>누적 충전</TitleItemLabel>
</SelectInput> <TitleItemValue color='#b7e0c3' fontWeight='bold'>{total_all ?? 0}</TitleItemValue>
: </TitleItem>
<SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}> <TitleItem>
<option value="DESC">내림차순</option> <TitleItemLabel>잔여 수량</TitleItemLabel>
<option value="ASC">오름차순</option> <TitleItemValue color='#B39063' fontWeight='bold'>{total ?? 0}</TitleItemValue>
</SelectInput> </TitleItem>
); </>
} ),
};
const PageSelect = ({pageType, handlePageSize}) => { const OrderBySelect = ({ orderType, handleOrderBy }) => {
return( const options = ORDER_OPTIONS[orderType] || ORDER_OPTIONS.desc;
pageType === "B" ?
<SelectInput name="" id="" className="input-select" onChange={e => handlePageSize(e.target.value)}> return (
<option value="500">500</option> <SelectInput className="input-select" onChange={e => handleOrderBy(e.target.value)}>
<option value="1000">1000</option> {options.map(option => (
<option value="5000">5000</option> <option key={option.value} value={option.value}>{option.label}</option>
<option value="10000">10000</option> ))}
</SelectInput> </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 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; 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

@@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
const DotsButton = styled.button`
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #f0f0f0;
border: none;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
&:hover {
background-color: #e0e0e0;
}
/* 점 스타일링 */
.dot {
width: 3px;
height: 3px;
border-radius: 50%;
background-color: #333;
margin: 2px 0;
}
`;
const VerticalDotsButton = ({ text, type = 'button', errorMessage, handleClick, size, width, height, borderColor, disabled, name }) => {
return (
<DotsButton
onSubmit={e => e.preventDefault()}
type={type}
disabled={disabled}
onClick={handleClick}
size={size}
bordercolor={borderColor}
width={width}
height={height}
name={name}
>
<div className="dot"></div>
<div className="dot"></div>
<div className="dot"></div>
</DotsButton>
);
};
export default VerticalDotsButton;

View File

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

View File

@@ -0,0 +1,231 @@
import { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
const AIMessageInput = ({ onSendMessage }) => {
const [isOpen, setIsOpen] = useState(false);
const [message, setMessage] = useState('');
const [isSending, setIsSending] = useState(false);
const textareaRef = useRef(null);
const modalRef = useRef(null);
// 텍스트 영역 높이 자동 조절
useEffect(() => {
if (textareaRef.current && isOpen) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
}
}, [message, isOpen]);
// 모달 외부 클릭시 닫기
useEffect(() => {
const handleClickOutside = (event) => {
if (modalRef.current && !modalRef.current.contains(event.target)) {
closeModal();
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
// 모달 열기
const openModal = () => {
setIsOpen(true);
// 모달이 열린 후 텍스트 영역에 포커스
setTimeout(() => {
if (textareaRef.current) {
textareaRef.current.focus();
}
}, 100);
};
// 모달 닫기
const closeModal = () => {
setIsOpen(false);
setMessage('');
};
// 메시지 전송 처리
const handleSendMessage = () => {
if (message.trim() && !isSending) {
setIsSending(true);
// 메시지 전송 처리
if (onSendMessage) {
onSendMessage(message);
}
// 입력 초기화 및 상태 업데이트
setMessage('');
setIsSending(false);
// 모달 닫기
closeModal();
}
};
// 엔터 키 처리 (Shift+Enter 줄바꿈, Enter 전송)
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
return (
<>
{/* 메뉴 버튼 */}
<MenuButton onClick={openModal}>
<div className="dot"></div>
<div className="dot"></div>
<div className="dot"></div>
</MenuButton>
{/* 모달 오버레이 */}
<ModalOverlay isOpen={isOpen}>
<InputContainer ref={modalRef} isOpen={isOpen}>
<MessageInput
ref={textareaRef}
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="메시지를 입력하세요..."
rows={1}
/>
<SendButton
onClick={handleSendMessage}
disabled={!message.trim() || isSending}
>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
</SendButton>
</InputContainer>
</ModalOverlay>
</>
);
};
export default AIMessageInput;
const ModalOverlay = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: ${props => props.isOpen ? 'flex' : 'none'};
justify-content: center;
align-items: center;
z-index: 1000;
`;
// 메인 컨테이너
const InputContainer = styled.div`
width: 90%;
max-width: 600px;
border: 1px solid #e0e0e0;
border-radius: 12px;
background-color: #ffffff;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
position: relative;
animation: ${props => props.isOpen ? 'slideUp 0.3s ease-out' : 'none'};
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
// 메시지 입력 영역
const MessageInput = styled.textarea`
width: 100%;
min-height: 60px;
max-height: 200px;
padding: 16px 60px 16px 16px;
border: none;
outline: none;
resize: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-size: 16px;
line-height: 1.5;
background: transparent;
&::placeholder {
color: #9e9ea7;
}
`;
// 전송 버튼
const SendButton = styled.button`
position: absolute;
bottom: 12px;
right: 12px;
width: 38px;
height: 38px;
border-radius: 50%;
background-color: #5436DA;
color: white;
border: none;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background-color: #4527D0;
}
&:disabled {
background-color: #DADCE0;
cursor: not-allowed;
}
svg {
width: 18px;
height: 18px;
fill: white;
}
`;
// 메뉴 버튼 (세로 점 세개)
const MenuButton = styled.button`
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #f0f0f0;
border: none;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background-color: #e0e0e0;
}
.dot {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #666;
margin: 2px 0;
}
`;

View File

@@ -53,10 +53,6 @@ const DynamicModal = ({modalType, view, handleSubmit, handleCancel, modalText, c
); );
case modalTypes.childOkCancel: case modalTypes.childOkCancel:
return ( 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}> <Modal min="440px" $padding="40px" $bgcolor="transparent" $view={view}>
<BtnWrapper $justify="flex-end"> <BtnWrapper $justify="flex-end">
<ButtonClose onClick={handleCancel} /> <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: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', UPDATE_FAIL: '수정에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', STOP_FAIL: '중단에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
DELETE_FAIL: '삭제에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.', DELETE_FAIL: '삭제에 실패하였습니다. 잠시 후 다시 한번 진행해 주세요.',
API_FAIL: '처리 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.', API_FAIL: '처리 중 오류가 발생하였습니다. 새로고침 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.',
SEARCH_FAIL: '조회 중 에러가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.',
USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?', USER_MAIL_DEL_CONFIRM: '해당 우편을 삭제하시겠습니까?',
USER_GM_CHANGE: 'GM 권한을 변경하시겠습니까?', USER_GM_CHANGE: 'GM 권한을 변경하시겠습니까?',
USER_GM_CHANGE_COMPLETE: '권한변경을 완료하였습니다.', USER_GM_CHANGE_COMPLETE: '권한변경을 완료하였습니다.',
@@ -41,6 +42,10 @@ const resources = {
WARNING_NICKNAME_CHECK: '닉네임을 확인해주세요.', WARNING_NICKNAME_CHECK: '닉네임을 확인해주세요.',
WARNING_EMAIL_CHECK: '이메일을 확인해주세요.', WARNING_EMAIL_CHECK: '이메일을 확인해주세요.',
WARNING_TYPE_CHECK: '타입을 확인해주세요.', WARNING_TYPE_CHECK: '타입을 확인해주세요.',
DATE_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.",
//table
TABLE_ITEM_DELETE_TITLE: "선택 삭제",
TABLE_BUTTON_DETAIL_TITLE: "상세보기",
//db //db
LOG_MEMORY_LIMIT_WARNING: '데이터가 너무 많아 조회할 수 없습니다.\n조회조건 조정 후 다시 조회해주세요.', LOG_MEMORY_LIMIT_WARNING: '데이터가 너무 많아 조회할 수 없습니다.\n조회조건 조정 후 다시 조회해주세요.',
LOG_MONGGDB_QUERY_WARNING: '조회 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.\n오류가 지속될 경우, 담당자에게 문의해주세요.', LOG_MONGGDB_QUERY_WARNING: '조회 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요.\n오류가 지속될 경우, 담당자에게 문의해주세요.',
@@ -88,7 +93,13 @@ const resources = {
MAIL_ITEM_ADD_DUPL: '이미 추가된 아이템입니다. 삭제 후 다시 추가해주세요.', MAIL_ITEM_ADD_DUPL: '이미 추가된 아이템입니다. 삭제 후 다시 추가해주세요.',
MAIL_ITEM_ADD_BEN: '첨부 할 수 없는 아이템입니다.', MAIL_ITEM_ADD_BEN: '첨부 할 수 없는 아이템입니다.',
MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING: '첨부 가능한 칼리움 수량을 초과 하였습니다.', MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING: '첨부 가능한 칼리움 수량을 초과 하였습니다.',
MAIL_SEND_STATUS_WARNING: '발송 처리가 완료된 우편은 삭제할 수 없습니다.',
MAIL_REGIST_CONFIRM: '우편을 등록하시겠습니까?',
MAIL_REGIST_COMPLETE: '우편이 정상 등록되었습니다.',
MAIL_REGIST_CANCEL: "우편 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
MAIL_CANCEL: '우편 등록이 취소되었습니다.', MAIL_CANCEL: '우편 등록이 취소되었습니다.',
//인게임 메시지
BOARD_DELETE_CONFIRM: "선택된 인게임 메세지를 삭제하시겠습니까?\r\n삭제 시 설정 정보가 제거됩니다.",
//칼리움 요청 //칼리움 요청
CHARGE_COMPLTED: '해당 건에 대한 충전 처리가 완료되었습니다.', CHARGE_COMPLTED: '해당 건에 대한 충전 처리가 완료되었습니다.',
CALIUM_CHARGE_CONFIRM: '선택건에 대한 충전을 진행하시겠습니까?', CALIUM_CHARGE_CONFIRM: '선택건에 대한 충전을 진행하시겠습니까?',
@@ -101,7 +112,6 @@ const resources = {
SEARCH_LIMIT_FAIL: '인출 가능 수량 조회에 대한 요청 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.', SEARCH_LIMIT_FAIL: '인출 가능 수량 조회에 대한 요청 중 오류가 발생하였습니다. 잠시 후 다시 한번 진행해 주세요. 오류가 지속될 경우, 담당자에게 문의해주세요.',
//전투시스템 //전투시스템
BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.", BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.",
BATTLE_EVENT_MODAL_START_DIFF_END_WARNING :"종료일은 시작일보다 하루 이후여야 합니다.",
BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.", BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.",
BATTLE_EVENT_REGIST_CONFIRM: "이벤트를 등록하시겠습니까?", BATTLE_EVENT_REGIST_CONFIRM: "이벤트를 등록하시겠습니까?",
BATTLE_EVENT_UPDATE_CONFIRM: "이벤트를 수정하시겠습니까?", BATTLE_EVENT_UPDATE_CONFIRM: "이벤트를 수정하시겠습니까?",
@@ -110,6 +120,22 @@ const resources = {
BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.", BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING: "이벤트 시작 5분 전에는 중단할 수 없습니다.",
BATTLE_EVENT_STATUS_RUNNING_WARNING: "이벤트 진행중에는 중단할 수 없습니다.", BATTLE_EVENT_STATUS_RUNNING_WARNING: "이벤트 진행중에는 중단할 수 없습니다.",
BATTLE_EVENT_MODAL_STATUS_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: "이미지 업로드 중 오류가 발생했습니다.",
FILE_NOT_EXIT_ERROR: "유효하지 않은 파일입니다.",
FILE_SIZE_OVER_ERROR: "파일의 사이즈가 5MB를 초과하였습니다.",
//파일명칭 //파일명칭
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx', FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx', FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',

View File

@@ -21,6 +21,7 @@ import {
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants'; import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE } from '../../assets/data/adminConstants';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Button,
DownloadProgress, DownloadProgress,
DynamicModal, DynamicModal,
ExcelDownButton, ExcelDownButton,
@@ -34,6 +35,9 @@ import styled from 'styled-components';
import FrontPagination from '../../components/common/Pagination/FrontPagination'; import FrontPagination from '../../components/common/Pagination/FrontPagination';
import Loading from '../../components/common/Loading'; import Loading from '../../components/common/Loading';
import CircularProgress from '../../components/common/CircularProgress'; import CircularProgress from '../../components/common/CircularProgress';
import VerticalDotsButton from '../../components/common/button/VerticalDotsButton';
import MessageInput from '../../components/common/input/MessageInput';
import { AnalyzeAI } from '../../apis';
const BusinessLogView = () => { const BusinessLogView = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
@@ -171,6 +175,15 @@ const BusinessLogView = () => {
} }
} }
const handleMessage = (message) => {
const params = {}
params.message = message;
params.type = 'BUSINESS_LOG'
params.conditions = searchParams;
AnalyzeAI(token, params);
}
return ( return (
<> <>
<Title>비즈니스 로그 조회</Title> <Title>비즈니스 로그 조회</Title>
@@ -204,6 +217,7 @@ const BusinessLogView = () => {
</CircularProgressWrapper> </CircularProgressWrapper>
)} )}
</DownloadContainer> </DownloadContainer>
<MessageInput onSendMessage={handleMessage} />
</ViewTableInfo> </ViewTableInfo>
{dataLoading ? <TableSkeleton width='100%' count={15} /> : {dataLoading ? <TableSkeleton width='100%' count={15} /> :
<> <>

View File

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

View File

@@ -38,13 +38,17 @@ import BattleEventSearchBar, {
useBattleEventSearch, useBattleEventSearch,
} from '../../components/ServiceManage/searchBar/BattleEventSearchBar'; } from '../../components/ServiceManage/searchBar/BattleEventSearchBar';
import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date'; 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 BattleEvent = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const tableRef = useRef(null); const tableRef = useRef(null);
const { showToast, showModal } = useAlert();
const {withLoading} = useLoading();
const [detailData, setDetailData] = useState({}); const [detailData, setDetailData] = useState({});
@@ -53,13 +57,8 @@ const BattleEvent = () => {
handleModalView, handleModalView,
handleModalClose handleModalClose
} = useModal({ } = useModal({
stopConfirm: 'hidden',
stopComplete: 'hidden',
detail: 'hidden', detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
}); });
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist'); const [modalType, setModalType] = useState('regist');
const { const {
@@ -141,50 +140,61 @@ const BattleEvent = () => {
return timeDiff < 3; return timeDiff < 3;
}); });
if(date_check){ if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING')); showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.warning});
return; return;
} }
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){ 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; return;
} }
handleModalView('deleteConfirm'); showModal('BATTLE_EVENT_SELECT_DELETE', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break; break;
case "stop": case "stop":
const select_item = selectedRows[0]; const select_item = selectedRows[0];
if(select_item.status === battleEventStatusType.running){ if(select_item.status === battleEventStatusType.running){
setAlertMsg(t('BATTLE_EVENT_STATUS_RUNNING_WARNING')); showToast('BATTLE_EVENT_STATUS_RUNNING_WARNING', {type: alertTypes.warning});
return; return;
} }
const isStopTimeCheck = isStopMinutes(select_item.event_start_dt); const isStopTimeCheck = isStopMinutes(select_item.event_start_dt);
if(isStopTimeCheck){ if(isStopTimeCheck){
setAlertMsg(t('BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING')); showToast('BATTLE_EVENT_STOP_5MINUTES_DATE_WARNING', {type: alertTypes.warning});
return; return;
} }
if(isRunningTime(select_item.event_start_dt, select_item.event_operation_time)){ 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; return;
} }
handleModalView('stopConfirm'); showModal('BATTLE_EVENT_SELECT_STOP', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('stopConfirm')
});
break; break;
case "stopConfirm": case "stopConfirm":
const stop_item = selectedRows[0]; const stop_item = selectedRows[0];
await BattleEventStop(token, stop_item.id, stop_item).then(data => { await withLoading(async () => {
handleModalClose('stopConfirm'); return await BattleEventStop(token, stop_item.id, stop_item);
}).then(data => {
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('stopComplete'); showToast('STOP_COMPLETE', {type: alertTypes.success});
handleSearch(updateSearchParams);
}else if(data.result === "ERROR_BATTLE_EVENT_STATUS_START_IMPOSSIBLE"){ }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{ }else{
setAlertMsg(t('STOP_FAIL')); showToast('STOP_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).catch(reason => {
setAlertMsg(t('API_FAIL')); showToast('API_FAIL', {type: alertTypes.error});
}); });
break; break;
case "deleteConfirm": case "deleteConfirm":
let list = []; let list = [];
let isChecked = false; let isChecked = false;
@@ -198,35 +208,24 @@ const BattleEvent = () => {
}); });
if(isChecked) { if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE')) showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
handleModalClose('deleteConfirm');
return; return;
} }
await BattleEventDelete(token, list).then(data => { await BattleEventDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('deleteComplete'); showToast('DEL_COMPLETE', {type: alertTypes.success});
handleSearch(updateSearchParams);
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){ }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{ }else{
setAlertMsg(t('DELETE_FAIL')); showToast('DELETE_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).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; break;
} }
} }
@@ -332,45 +331,19 @@ const BattleEvent = () => {
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} /> <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 { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { convertKTC } from '../../utils'; import { convertKTC, timeDiffMinute } from '../../utils';
import AuthModal from '../../components/common/modal/AuthModal'; 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 Board = () => {
const navigate = useNavigate();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert()
const {withLoading} = useLoading();
const [isCopyData, setIsCopyData] = useState(false); const [isCopyData, setIsCopyData] = useState(false);
@@ -41,21 +47,12 @@ const Board = () => {
const [registView, setRegistView] = useState('hidden'); const [registView, setRegistView] = useState('hidden');
const [detailData, setDetailData] = useState(''); const [detailData, setDetailData] = useState('');
const [detailId, setDetailId] = useState(''); const [detailId, setDetailId] = useState('');
const [selectedRow, setSelectedRow] = useState([]);
const [deleteModalClose, setDeleteModalClose] = useState('hidden');
const [confirmModalClose, setConfirmModalClose] = useState('hidden');
const message_type = [ const {
{ value: 'CHATTING', name: '채팅 타입' }, selectedRows,
{ value: 'CHATTING_TOAST', name: '채팅 + 토스트' }, handleSelectRow,
]; isRowSelected
} = useTable(dataList || [], {mode: 'single'});
const sendStatus = [
{ value: 'WAIT', name: '대기' },
{ value: 'RUNNING', name: '송출중' },
{ value: 'FINISH', name: '완료' },
{ value: 'FAIL', name: '실패' },
];
const fetchData = async () => { const fetchData = async () => {
setDataList(await NoticeListView(token)); setDataList(await NoticeListView(token));
@@ -65,201 +62,136 @@ const Board = () => {
fetchData(); fetchData();
}, []); }, []);
// 체크박스 선택 리스트 const handleModalSubmit = async (type, param = null) => {
const handleSelectCheckBox = e => { switch (type) {
let list = [...selectedRow]; case "detail":
if (e.target.checked) { await NoticeDetailView(token, param.id).then(data => {
list.push(e.target.id); setDetailData(data);
setSelectedRow(list); setDetailId(param.id);
} else { setDetailView('view');
const filterList = list.filter(data => e.target.id !== data); });
setSelectedRow(filterList); 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 ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.inGameRead) ? ( <Title>인게임 메시지</Title>
<AuthModal/> <TableInfo>
) : ( <ListOption>
<> {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameDelete) && (
<Title>인게임 메시지</Title> <Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
<TableInfo> )}
<ListOption> {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameUpdate) && (
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameDelete) && ( <Button
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={handleDeleteModalClose} /> theme="primary"
)} text="메시지 등록"
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.inGameUpdate) && ( value="2"
<Button handleClick={e => {
theme="primary" e.preventDefault();
text="메시지 등록" setRegistView('view');
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}
/> />
{/* 인게임 메세지 등록 */} )}
<BoardRegistModal registView={registView} setRegistView={setRegistView} copyData={isCopyData ? detailData : ''} setIsCopyData={setIsCopyData} userInfo={userInfo} /> </ListOption>
</TableWrapper> </TableInfo>
<TableWrapper>
{/* 선택 삭제 모달 */} <TableStyle>
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={deleteModalClose}> <caption></caption>
<BtnWrapper $justify="flex-end"> <thead>
<ButtonClose onClick={handleDeleteModalClose} /> <tr>
</BtnWrapper> <th width="40">
<ModalText $align="center"> </th>
선택된 인게임 메세지를 삭제하시겠습니까? <th width="80">번호</th>
<br /> <th width="100">채팅 타입</th>
삭제 설정 정보가 제거됩니다. <th width="200">송출 일자</th>
</ModalText> <th>메시지</th>
<BtnWrapper $gap="10px"> <th width="80">송출 횟수</th>
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleDeleteModalClose} /> <th width="100">송출 상태</th>
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleSelectedDelete} /> <th width="120">등록자</th>
</BtnWrapper> <th width="200">등록자(이메일주소)</th>
</Modal> <th width="200">등록일자</th>
{/* 확인 모달 */} </tr>
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModalClose}> </thead>
<BtnWrapper $justify="flex-end"> <tbody>
<ButtonClose onClick={handleConfirmeModalClose} /> {dataList &&
</BtnWrapper> dataList.map(notice => (
<ModalText $align="center">삭제가 완료되었습니다.</ModalText> <Fragment key={notice.id}>
<BtnWrapper $gap="10px"> <tr>
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmeModalClose} /> <td>
</BtnWrapper> <CheckBox name={'select'} id={notice.id}
</Modal> 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 { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
EventDelete, EventDelete,
EventDetailView, EventDetailView
EventView,
} from '../../apis'; } from '../../apis';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
@@ -21,99 +20,56 @@ import DynamicModal from '../../components/common/modal/DynamicModal';
import AuthModal from '../../components/common/modal/AuthModal'; import AuthModal from '../../components/common/modal/AuthModal';
import ViewTableInfo from '../../components/common/Table/ViewTableInfo'; import ViewTableInfo from '../../components/common/Table/ViewTableInfo';
import { convertKTC, timeDiffMinute } from '../../utils'; import { convertKTC, timeDiffMinute } from '../../utils';
import EventListSearchBar from '../../components/ServiceManage/searchBar/EventListSearchBar'; import {
import CustomConfirmModal from '../../components/common/modal/CustomConfirmModal'; ModalInputItem,
import { ModalInputItem, ModalSubText, RegistInputItem, RegistNotice, SubText } from '../../styles/ModuleComponents'; ModalSubText,
RegistInputItem,
StatusLabel, StatusWapper,
} from '../../styles/ModuleComponents';
import { useLoading } from '../../context/LoadingProvider';
import { useAlert } from '../../context/AlertProvider';
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
import { useModal, useTable } from '../../hooks/hook';
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { alertTypes } from '../../assets/data/types';
const Event = () => { const Event = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const {withLoading} = useLoading();
const {showToast} = useAlert();
const navigate = useNavigate(); const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [dataList, setDataList] = useState([]);
const [detailData, setDetailData] = useState(''); const [detailData, setDetailData] = useState('');
const [selectedRow, setSelectedRow] = useState([]); const {
const [searchData, setSearchData] = useState({ modalState,
title: '', handleModalView,
content: '', handleModalClose
status: 'ALL', } = useModal({
startDate: '', detail: 'hidden',
endDate: '', 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(''); const [deleteDesc, setDeleteDesc] = useState('');
useEffect(() => { const {
fetchData('', '', 'ALL', '', ''); config,
setSelectedRow([]); searchParams,
}, [currentPage]); data: dataList,
handleSearch,
// 리스트 조회 handleReset,
const fetchData = async (title, content, status, startDate, endDate, order, size) => { handlePageChange,
setDataList( handlePageSizeChange,
await EventView( handleOrderByChange,
token, updateSearchParams,
title, configLoaded
content, } = useCommonSearch(token, "eventSearch");
status, const {
startDate && new Date(startDate).toISOString(), selectedRows,
endDate && new Date(endDate).toISOString(), handleSelectRow,
order ? order : orderBy, isRowSelected
size ? size : pageSize, } = useTable(dataList?.list || [], {mode: 'single'});
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 handleDetailModal = async (e, id) => { const handleDetailModal = async (e, id) => {
@@ -125,68 +81,27 @@ const Event = () => {
handleModalView('detail'); 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) => { const handleModalSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "delete": case "delete":
const delete_check = selectedRow.every(row => { const delete_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date)); const timeDiff = timeDiffMinute(convertKTC(row.start_dt), (new Date));
return row.add_flag || (timeDiff < 30); return row.add_flag || (timeDiff < 30);
}); });
if(delete_check){ if(delete_check){
setAlertMsg(t('EVENT_TIME_LIMIT_UPDATE')); showToast('EVENT_TIME_LIMIT_UPDATE', {type: alertTypes.warning});
return; return;
} }
handleModalView('deleteConfirm');
handleModalView('delete');
break; break;
case "deleteConfirm": case "deleteConfirm":
let list = []; let list = [];
let isChecked = false; let isChecked = false;
selectedRow.map(data => { selectedRows.map(data => {
const row = dataList.list.find(row => row.id === Number(data.id)); const row = dataList.list.find(row => row.id === Number(data.id));
if(row.status !== commonStatus.wait) isChecked = true; if(row.status !== commonStatus.wait) isChecked = true;
list.push({ list.push({
@@ -195,32 +110,28 @@ const Event = () => {
}); });
}); });
handleModalClose('delete');
setDeleteDesc('');
if(isChecked) { if(isChecked) {
setAlertMsg(t('EVENT_WARNING_DELETE')) showToast('EVENT_WARNING_DELETE', {type: alertTypes.warning});
handleModalClose('deleteConfirm');
return; 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; break;
} }
} }
const handleCountSelectedRow = () => {
return currentPage > (dataList && dataList.total) / pageSize ? selectedRow.length === dataList.total % pageSize : selectedRow.length === Number(pageSize);
};
return ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? ( {userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
@@ -229,11 +140,22 @@ const Event = () => {
<> <>
<Title>출석 보상 이벤트 관리</Title> <Title>출석 보상 이벤트 관리</Title>
<FormWrapper> <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> </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) && ( {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) && ( {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
<Button <Button
@@ -253,7 +175,6 @@ const Event = () => {
<thead> <thead>
<tr> <tr>
<th width="40"> <th width="40">
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} />
</th> </th>
<th width="80">번호</th> <th width="80">번호</th>
<th width="100">이벤트 상태</th> <th width="100">이벤트 상태</th>
@@ -265,39 +186,52 @@ const Event = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{dataList.list && {dataList?.list?.map(event => (
dataList.list.map(event => (
<Fragment key={event.row_num}> <Fragment key={event.row_num}>
<tr> <tr>
<td> <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>
<td>{event.row_num}</td> <td>{event.row_num}</td>
<td>{eventStatus.map(data => data.value === event.status && data.name)}</td> <StatusWapper>
<StatusLabel $status={event.status}>
{eventStatus.map(data => data.value === event.status && data.name)}
</StatusLabel>
</StatusWapper>
<td>{convertKTC(event.start_dt)}</td> <td>{convertKTC(event.start_dt)}</td>
<td>{convertKTC(event.end_dt)}</td> <td>{convertKTC(event.end_dt)}</td>
<MailTitle>{event.title}</MailTitle> <MailTitle>{event.title}</MailTitle>
<td> <td>
<Button theme="line" text="상세보기" handleClick={e => handleDetailModal(e, event.id)} /> <Button theme="line" text="상세보기"
handleClick={e => handleDetailModal(e, event.id)} />
</td> </td>
<td>{event.create_by}</td> <td>{event.create_by}</td>
</tr> </tr>
</Fragment> </Fragment>
))} ))}
</tbody> </tbody>
</TableStyle> </TableStyle>
</TableWrapper> </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 <DynamicModal
modalType={modalTypes.childOkCancel} modalType={modalTypes.childOkCancel}
view={modalState.deleteConfirmModal} view={modalState.deleteModal}
handleCancel={() => handleModalClose('deleteConfirm')} handleCancel={() => handleModalClose('delete')}
handleSubmit={() => handleModalSubmit('deleteConfirm')} handleSubmit={() => handleModalSubmit('deleteConfirm')}
> >
<ModalInputItem> <ModalInputItem>
@@ -316,20 +250,6 @@ const Event = () => {
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30)</ModalSubText> <ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30)</ModalSubText>
</ModalInputItem> </ModalInputItem>
</DynamicModal> </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 Button from '../../components/common/button/Button';
import Loading from '../../components/common/Loading';
import { import {
Title, Title,
@@ -23,38 +22,34 @@ import {
AppendRegistBox, AppendRegistTable, AreaBtnClose, AppendRegistBox, AppendRegistTable, AreaBtnClose,
BtnDelete, BtnDelete,
Item, Item,
ItemList, LangArea, ModalInputItem, ModalItem, ModalItemList, ModalSubText, RegistGroup, ItemList, LangArea, ModalItem, ModalItemList, RegistGroup,
RegistInputItem, RegistInputItem,
RegistInputRow, RegistNotice, RegistTable, RegistInputRow, RegistNotice, RegistTable,
} from '../../styles/ModuleComponents'; } from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal'; import AuthModal from '../../components/common/modal/AuthModal';
import { authType, benItems, modalTypes, wellType } from '../../assets/data'; import { authType, benItems, wellType } from '../../assets/data';
import DynamicModal from '../../components/common/modal/DynamicModal';
import DateTimeInput from '../../components/common/input/DateTimeInput'; import DateTimeInput from '../../components/common/input/DateTimeInput';
import { timeDiffMinute } from '../../utils'; import { timeDiffMinute } from '../../utils';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
const EventRegist = () => { const EventRegist = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { showToast, showModal } = useAlert();
const [loading, setLoading] = useState(false); // 로딩 창 const { withLoading} = useLoading();
const [modalState, setModalState] = useState({
cancelModal: 'hidden',
registConfirmModal: 'hidden',
registCompleteModal: 'hidden',
}); // 모달 관리
const [item, setItem] = useState(''); // 아이템 값 const [item, setItem] = useState(''); // 아이템 값
const [itemCount, setItemCount] = useState(''); // 아이템 개수 const [itemCount, setItemCount] = useState(''); // 아이템 개수
const [resource, setResource] = useState('19010001'); // 자원 값 const [resource, setResource] = useState('19010001'); // 자원 값
const [resourceCount, setResourceCount] = useState(''); // 자원 개수 const [resourceCount, setResourceCount] = useState(''); // 자원 개수
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크 const [isNullValue, setIsNullValue] = useState(false);
const [btnValidation, setBtnValidation] = useState(false); // 입력창 버튼 const [btnValidation, setBtnValidation] = useState(false);
const [itemCheckMsg, setItemCheckMsg] = useState(''); const [itemCheckMsg, setItemCheckMsg] = useState('');
const [alertMsg, setAlertMsg] = useState('');
const [time, setTime] = useState({ const [time, setTime] = useState({
start_hour: '00', start_hour: '00',
@@ -145,7 +140,7 @@ const EventRegist = () => {
// 아이템 추가 // 아이템 추가
const handleItemList = async () => { const handleItemList = async () => {
if(benItems.includes(item)){ if(benItems.includes(item)){
setAlertMsg(t('MAIL_ITEM_ADD_BEN')) showToast('MAIL_ITEM_ADD_BEN', {type: alertTypes.warning});
return; return;
} }
if(item.length === 0 || itemCount.length === 0) 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); const itemIndex = resultData.item_list.findIndex((data) => data.item === item);
if (itemIndex !== -1) { if (itemIndex !== -1) {
setItemCheckMsg(t('MAIL_ITEM_ADD_DUPL')); showToast('MAIL_ITEM_ADD_DUPL', {type: alertTypes.warning});
return; return;
} }
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name }; const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
@@ -221,56 +216,56 @@ const EventRegist = () => {
setResourceCount(''); 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) => { const handleSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "submit": case "submit":
if (!checkCondition()) return; if (!checkCondition()) return;
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date)) const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
if(timeDiff < 60) { if(timeDiff < 60) {
setAlertMsg(t('EVENT_TIME_LIMIT_ADD')); showToast('EVENT_TIME_LIMIT_ADD', {type: alertTypes.warning});
return; 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; break;
case "cancel":
handleModalView('deleteSubmit');
break;
case "registConfirm": case "registConfirm":
setLoading(true); await withLoading(async () => {
return await EventSingleRegist(token, resultData);
const result = await EventSingleRegist(token, resultData); }).then((result) => {
showToast('REGIST_COMPLTE', {type: alertTypes.success});
setLoading(false); }).catch(() => {
handleModalClose('registConfirm'); showToast('API_FAIL', {type: alertTypes.error});
handleModalView('registComplete'); }).finally(() => {
break; callbackPage();
case "registComplete": });
handleModalClose('registComplete');
navigate('/servicemanage/event');
break;
case "warning":
setAlertMsg('');
break; break;
} }
} }
const callbackPage = () => {
navigate('/servicemanage/event');
}
const checkCondition = () => { const checkCondition = () => {
return ( return (
resultData.mail_list.every(data => data.content !== '' && data.title !== '') && resultData.mail_list.every(data => data.content !== '' && data.title !== '') &&
@@ -446,7 +441,14 @@ const EventRegist = () => {
)} )}
<BtnWrapper $justify="flex-end" $gap="10px"> <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 <Button
type="submit" type="submit"
text="등록" text="등록"
@@ -454,61 +456,6 @@ const EventRegist = () => {
handleClick={() => handleSubmit('submit')} handleClick={() => handleSubmit('submit')}
/> />
</BtnWrapper> </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 { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar'; import { useLandAuctionSearch } from '../../components/ServiceManage/searchBar/LandAuctionSearchBar';
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents'; import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';
import { alertTypes } from '../../assets/data/types';
import { useAlert } from '../../context/AlertProvider';
const LandAuction = () => { const LandAuction = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const tableRef = useRef(null); const tableRef = useRef(null);
const { showToast, showModal } = useAlert();
const [detailData, setDetailData] = useState({}); const [detailData, setDetailData] = useState({});
@@ -47,8 +50,6 @@ const LandAuction = () => {
handleModalClose handleModalClose
} = useModal({ } = useModal({
detail: 'hidden', detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
}); });
const [alertMsg, setAlertMsg] = useState(''); const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist'); const [modalType, setModalType] = useState('regist');
@@ -97,14 +98,17 @@ const LandAuction = () => {
return timeDiff < 3; return timeDiff < 3;
}); });
if(date_check){ if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING')); showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.warning});
return; return;
} }
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){ 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; return;
} }
handleModalView('deleteConfirm'); showModal('LAND_AUCTION_SELECT_DELETE', {
type: alertTypes.confirm,
onConfirm: () => handleModalSubmit('deleteConfirm')
});
break; break;
case "deleteConfirm": case "deleteConfirm":
let list = []; let list = [];
@@ -119,31 +123,24 @@ const LandAuction = () => {
}); });
if(isChecked) { if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE')) showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
handleModalClose('deleteConfirm');
return; return;
} }
await LandAuctionDelete(token, list).then(data => { await LandAuctionDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") { if(data.result === "SUCCESS") {
handleModalView('deleteComplete'); showToast('DEL_COMPLETE', {type: alertTypes.success});
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){ }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{ }else{
setAlertMsg(t('DELETE_FAIL')); showToast('DELETE_FAIL', {type: alertTypes.error});
} }
}).catch(reason => { }).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; break;
} }
} }
@@ -243,30 +240,19 @@ const LandAuction = () => {
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} /> <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,330 +1,221 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { useState, useEffect, Fragment } from 'react'; import { useState, Fragment } from 'react';
import CheckBox from '../../components/common/input/CheckBox'; 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 Button from '../../components/common/button/Button';
import 'react-datepicker/dist/react-datepicker.css'; import 'react-datepicker/dist/react-datepicker.css';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import MailDetailModal from '../../components/ServiceManage/modal/MailDetailModal'; import MailDetailModal from '../../components/ServiceManage/modal/MailDetailModal';
import MailListSearchBar from '../../components/ServiceManage/searchBar/MailListSearchBar';
import Pagination from '../../components/common/Pagination/Pagination'; import Pagination from '../../components/common/Pagination/Pagination';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { authType, mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../assets/data'; import {
import AuthModal from '../../components/common/modal/AuthModal'; authType,
mailReceiveType,
mailSendStatus,
mailSendType,
mailType,
} from '../../assets/data';
import ViewTableInfo from '../../components/common/Table/ViewTableInfo'; 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 Mail = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
const navigate = useNavigate(); 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 [detailData, setDetailData] = useState('');
const [selectedRow, setSelectedRow] = useState([]); const {
const [searchData, setSearchData] = useState({ modalState,
mailTitle: '', handleModalView,
content: '', handleModalClose
sendType: 'ALL', } = useModal({
sendStatus: 'ALL', detail: 'hidden',
mailType: 'ALL',
receiveType: 'ALL',
sendDate: '',
endDate: '',
}); });
const [orderBy, setOrderBy] = useState('DESC');
const [deleteModalClose, setDeleteModalClose] = useState('hidden'); const {
const [confirmModalClose, setConfirmModalClose] = useState('hidden'); 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(() => { const handleModalSubmit = async (type, param = null) => {
fetchData('', '', 'ALL', 'ALL', 'ALL', 'ALL', '', ''); switch (type) {
setSelectedRow([]); case "detail":
}, [currentPage]); await MailDetailView(token, param).then(data => {
setDetailData(data);
handleModalView('detail');
});
break;
case "delete":
if(selectedRows.length === 0) return;
// 리스트 조회 showModal('MAIL_SELECT_DELETE', {
const fetchData = async (mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate, endDate, order, size) => { type: alertTypes.confirm,
setDataList( onConfirm: () => handleModalSubmit('deleteConfirm')
await MailView( });
token, break;
mailTitle,
content,
sendType,
sendStatus,
mailType,
receiveType,
sendDate && new Date(sendDate).toISOString(),
endDate && new Date(endDate).toISOString(),
order ? order : orderBy,
size ? size : pageSize,
currentPage,
),
);
};
// 검색 함수 case "deleteConfirm":
const handleSearch = (mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate, endDate) => { let list = [];
fetchData(mailTitle, content, sendType, sendStatus, mailType, receiveType, sendDate && new Date(sendDate).toISOString(), endDate && new Date(endDate).toISOString());
};
// 오름차순 내림차순 let isChecked = false;
const handleOrderBy = e => {
const order = e.target.value;
setOrderBy(order); selectedRows.map(data => {
fetchData( const row = dataList.list.find(row => row.id === Number(data.id));
searchData.mailTitle, if(row.send_status === "FINISH" || row.send_status === "RUNNING" || row.send_status === "FAIL") isChecked = true;
searchData.content, list.push({
searchData.sendType, id: data.id,
searchData.sendStatus, });
searchData.mailType, });
searchData.receiveType,
searchData.sendDate && new Date(searchData.sendDate).toISOString(),
searchData.endDate && new Date(searchData.endDate).toISOString(),
order,
pageSize,
);
};
const handlePageSize = e => { if(isChecked) {
const size = e.target.value; showToast('MAIL_SEND_STATUS_WARNING', {type: alertTypes.warning});
setPageSize(size); return;
setCurrentPage(1); }
fetchData( withLoading( async () => {
searchData.mailTitle, return await MailDelete(token, list);
searchData.content, }).then(data => {
searchData.sendType, showToast('DEL_COMPLETE', {type: alertTypes.success});
searchData.sendStatus, }).catch(reason => {
searchData.mailType, showToast('API_FAIL', {type: alertTypes.error});
searchData.receiveType, }).finally(() => {
searchData.sendDate && new Date(searchData.sendDate).toISOString(), handleSearch(updateSearchParams);
searchData.endDate && new Date(searchData.endDate).toISOString(), });
orderBy,
size,
1,
);
};
const handleDetailView = () => { break;
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);
} }
}; }
// 전체 선택 구현
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 ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailRead) ? ( <Title>우편 조회 발송 관리</Title>
<AuthModal/> <FormWrapper>
) : ( <CommonSearchBar
<> config={config}
<Title>우편 조회 발송 관리</Title> searchParams={searchParams}
<FormWrapper> onSearch={(newParams, executeSearch = true) => {
<MailListSearchBar handleSearch={handleSearch} setResultData={setSearchData} /> if (executeSearch) {
</FormWrapper> handleSearch(newParams);
<ViewTableInfo total={dataList.total} total_all={dataList.total_all} handleOrderBy={handleOrderBy} handlePageSize={handlePageSize}> } else {
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailDelete) && ( updateSearchParams(newParams);
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={handleDeleteModalClose} /> }
)} }}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) && ( onReset={handleReset}
<Button />
theme="primary" </FormWrapper>
text="우편 등록" <ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
type="button" {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailDelete) && (
handleClick={e => { <Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
e.preventDefault(); )}
navigate('/servicemanage/mail/mailregist'); {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) && (
}} <Button
/> theme="primary"
)} text="우편 등록"
</ViewTableInfo> type="button"
<TableWrapper> handleClick={e => {
<TableStyle> e.preventDefault();
<caption></caption> navigate('/servicemanage/mail/mailregist');
<thead> }}
<tr> />
<th width="40"> )}
<CheckBox id="check-all" handleCheck={handleAllSelect} checked={handleCountSelectedRow()} /> </ViewTableInfo>
</th> <TableWrapper>
<th width="80">번호</th> <TableStyle>
<th width="210">등록 일시</th> <caption></caption>
<th width="210">발송 일시</th> <thead>
<th width="100">발송 방식</th> <tr>
<th width="100">발송 상태</th> <th width="40">
<th width="100">우편 타입</th> </th>
<th width="100">수신 대상</th> <th width="80">번호</th>
<th>우편 제목</th> <th width="210">등록 일시</th>
<th width="110">확인 / 수정</th> <th width="210">발송 일시</th>
<th width="200">등록자(처리자)</th> <th width="100">발송 방식</th>
</tr> <th width="100">발송 상태</th>
</thead> <th width="100">우편 타입</th>
<tbody> <th width="100">수신 대상</th>
{dataList.list && <th>우편 제목</th>
dataList.list.map(mail => ( <th width="110">확인 / 수정</th>
<Fragment key={mail.row_num}> <th width="200">등록자(처리자)</th>
<tr> </tr>
<td> </thead>
<CheckBox name={'select'} id={mail.id} setData={e => handleSelectCheckBox(e)} /> <tbody>
</td> {dataList?.list?.map(mail => (
<td>{mail.row_num}</td> <Fragment key={mail.row_num}>
<td>{convertKTC(mail.create_dt)}</td> <tr>
<td>{convertKTC(mail.send_dt)}</td> <td>
<td>{mailSendType.map(data => data.value === mail.send_type && data.name)}</td> <CheckBox name={'select'} id={mail.id}
<td> setData={(e) => handleSelectRow(e, mail)}
{mail.send_status === 'FAIL' ? ( checked={isRowSelected(mail.id)} />
<ListState>{mailSendStatus.map(data => data.value === mail.send_status && data.name)}</ListState> </td>
) : ( <td>{mail.row_num}</td>
mailSendStatus.map(data => data.value === mail.send_status && data.name) <td>{convertKTC(mail.create_dt)}</td>
)} <td>{convertKTC(mail.send_dt)}</td>
</td> <td>{mailSendType.map(data => data.value === mail.send_type && data.name)}</td>
<td>{mailType.map(data => data.value === mail.mail_type && data.name)}</td> <StatusWapper>
<td>{mailReceiveType.map(data => data.value === mail.receive_type && data.name)}</td> <StatusLabel $status={mail.send_status}>
<MailTitle>{mail.title}</MailTitle> {mailSendStatus.map(data => data.value === mail.send_status && data.name)}
<td> </StatusLabel>
<Button theme="line" text="상세보기" handleClick={e => handleDetailModal(e, mail.id)} /> </StatusWapper>
</td> <td>{mailType.map(data => data.value === mail.mail_type && data.name)}</td>
<td>{mail.create_by}</td> <td>{mailReceiveType.map(data => data.value === mail.receive_type && data.name)}</td>
</tr> <MailTitle>{mail.title}</MailTitle>
</Fragment> <td>
))} <Button theme="line" text="상세보기" handleClick={e => handleModalSubmit('detail', mail.id)} />
</tbody> </td>
</TableStyle> <td>{mail.create_by}</td>
</TableWrapper> </tr>
<Pagination postsPerPage={pageSize} totalPosts={dataList && dataList.total_all} setCurrentPage={setCurrentPage} currentPage={currentPage} pageLimit={10} /> </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` const ListState = styled.span`
color: #d60000; 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 Button from '../../components/common/button/Button';
import RadioInput from '../../components/common/input/Radio'; import RadioInput from '../../components/common/input/Radio';
import CheckBox from '../../components/common/input/CheckBox'; import CheckBox from '../../components/common/input/CheckBox';
import Loading from '../../components/common/Loading';
import styled from 'styled-components'; 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 IconDelete from '../../assets/img/icon/icon-delete.png';
import CloseIcon from '../../assets/img/icon/icon-close.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 { useNavigate } from 'react-router-dom';
import MailRegistUploadBtn from '../../components/ServiceManage/MailRegistUploadBtn'; import MailRegistUploadBtn from '../../components/ServiceManage/MailRegistUploadBtn';
import DatePickerComponent from '../../components/common/Date/DatePickerComponent'; import DatePickerComponent from '../../components/common/Date/DatePickerComponent';
import Modal from '../../components/common/modal/Modal'; import { MailCaliumTotalView, MailIsItem, MailSingleRegist } from '../../apis';
import { MailCaliumTotalView, MailIsItem, MailMultiRegsit, MailSingleRegist } from '../../apis';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents'; import { MailReceiver, RegistInputRow } from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal'; import AuthModal from '../../components/common/modal/AuthModal';
import { authType } from '../../assets/data'; import { authType } from '../../assets/data';
import { useDataFetch } from '../../hooks/hook'; import { useDataFetch } from '../../hooks/hook';
import { BattleConfigView } from '../../apis/Battle'; import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
import { currencyCodeTypes } from '../../assets/data/types'; import { useLoading } from '../../context/LoadingProvider';
import { DynamicModal } from '../../components/common'; import { useAlert } from '../../context/AlertProvider';
const MailRegist = () => { const MailRegist = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const environment = process.env.REACT_APP_ENV; const environment = process.env.REACT_APP_ENV;
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const { showModal, showToast } = useAlert();
const { withLoading } = useLoading();
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false); const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
@@ -46,9 +54,6 @@ const MailRegist = () => {
const [resource, setResource] = useState('19010001'); const [resource, setResource] = useState('19010001');
const [resourceCount, setResourceCount] = useState(0); 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 [isNullValue, setIsNullValue] = useState(false);
const [btnValidation, setBtnValidation] = useState(false); const [btnValidation, setBtnValidation] = useState(false);
@@ -57,13 +62,10 @@ const MailRegist = () => {
const [isItemNullValue, setIsItemNullValue] = useState(false); const [isItemNullValue, setIsItemNullValue] = useState(false);
const [isResourceNullValue, setIsResourceNullValue] = useState(false); const [isResourceNullValue, setIsResourceNullValue] = useState(false);
const [confirmText, setConfirmText] = useState('');
const [alertMessage, setAlertMessage] = useState(''); const [alertMessage, setAlertMessage] = useState('');
const [loading, setLoading] = useState(false);
const [alertMsg, setAlertMsg] = useState('');
const { const {
data: caliumTotalData data: caliumTotalData,
} = useDataFetch(() => MailCaliumTotalView(token), [token]); } = useDataFetch(() => MailCaliumTotalView(token), [token]);
const [resultData, setResultData] = useState({ const [resultData, setResultData] = useState({
@@ -87,16 +89,20 @@ const MailRegist = () => {
title: '', title: '',
content: '', content: '',
language: 'JA', language: 'JA',
} },
], ],
item_list: [], item_list: [],
resource_list: [], resource_list: [],
guid: '', guid: '',
}); });
const benItems = [ useEffect(() => {
"19010003" if (checkCondition()) {
]; setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
// 아이템 수량 숫자 체크 // 아이템 수량 숫자 체크
const handleItemCount = e => { const handleItemCount = e => {
@@ -113,17 +119,17 @@ const MailRegist = () => {
// 아이템 추가 // 아이템 추가
const handleItemList = async () => { const handleItemList = async () => {
if(benItems.includes(item) && environment === "live"){ if (benItems.includes(item) && environment === 'live') {
alert(t('MAIL_ITEM_ADD_BEN')) showToast('MAIL_ITEM_ADD_BEN', { type: alertTypes.warning });
return; return;
} }
item.length === 0 || itemCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false); item.length === 0 || itemCount.length === 0 ? setIsItemNullValue(true) : setIsItemNullValue(false);
// const token = sessionStorage.getItem('token'); // const token = sessionStorage.getItem('token');
const result = await MailIsItem(token, {item: item}); const result = await MailIsItem(token, { item: item });
if(result.data.result === "ERROR"){ if (result.data.result === 'ERROR') {
alert(result.data.data.message); showToast(result.data.data.message, { type: alertTypes.warning });
return; return;
} }
@@ -132,11 +138,11 @@ const MailRegist = () => {
} else if (item.length !== 0) { } else if (item.length !== 0) {
setIsItemNullValue(false); setIsItemNullValue(false);
const itemIndex = resultData.item_list.findIndex( const itemIndex = resultData.item_list.findIndex(
(item) => item.item === resource (item) => item.item === resource,
); );
if (itemIndex !== -1) { if (itemIndex !== -1) {
alert(t('MAIL_ITEM_ADD_DUPL')); showToast('MAIL_ITEM_ADD_DUPL', { type: alertTypes.warning });
return; return;
} }
const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name }; const newItem = { item: item, item_cnt: itemCount, item_name: result.data.data.item_info.item_name };
@@ -188,22 +194,22 @@ const MailRegist = () => {
setIsItemNullValue(false); setIsItemNullValue(false);
const itemIndex = resultData.item_list.findIndex( const itemIndex = resultData.item_list.findIndex(
(item) => item.item === resource (item) => item.item === resource,
); );
if (itemIndex !== -1) { if (itemIndex !== -1) {
const item_cnt = resultData.item_list[itemIndex].item_cnt; const item_cnt = resultData.item_list[itemIndex].item_cnt;
if(resource === currencyCodeTypes.calium){ if (resource === currencyCodeTypes.calium) {
if((Number(resourceCount) + Number(item_cnt)) > caliumTotalData){ if ((Number(resourceCount) + Number(item_cnt)) > caliumTotalData) {
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING')) showToast('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING', { type: alertTypes.warning });
return; return;
} }
} }
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount); resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
} else { } else {
if(resource === currencyCodeTypes.calium){ if (resource === currencyCodeTypes.calium) {
if(Number(resourceCount) > caliumTotalData){ if (Number(resourceCount) > caliumTotalData) {
setAlertMsg(t('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING')) showToast('MAIL_ITEM_CALIUM_TOTAL_OVER_WARNING', { type: alertTypes.warning });
return; return;
} }
} }
@@ -211,7 +217,7 @@ const MailRegist = () => {
const newItem = { item: resource, item_cnt: resourceCount, item_name: name }; const newItem = { item: resource, item_cnt: resourceCount, item_name: name };
resultData.item_list.push(newItem); resultData.item_list.push(newItem);
} }
setResourceCount(''); setResourceCount('');
} }
}; };
@@ -248,72 +254,51 @@ const MailRegist = () => {
setResultData({ ...resultData, send_dt: result }); setResultData({ ...resultData, send_dt: result });
}; };
const handleSubmitModal = () => { const handleSubmit = async (type, param = null) => {
if ( switch (type) {
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) || case 'submit':
(resultData.receive_type === 'MULTIPLE' ? excelFile === null : resultData.guid === '') || if (!checkCondition()) return;
(resultData.is_reserve && resultData.send_dt.length === 0) ||
resultData.mail_type === 'SELECT' || showModal('MAIL_REGIST_CONFIRM', {
alertMessage type: alertTypes.confirm,
) { onConfirm: () => handleSubmit('registConfirm'),
setIsNullValue(true); });
} else if (submitModal === 'hidden') { break;
setIsNullValue(false);
setSubmitModal('view'); case 'registConfirm':
} else { await withLoading(async () => {
setSubmitModal('hidden'); 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 callbackPage = () => {
const handleCancelModalClose = () => { navigate('/servicemanage/mail');
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 handleSingleBtn = () => { const handleSingleBtn = () => {
@@ -328,13 +313,19 @@ const MailRegist = () => {
const handleMultiBtn = () => { const handleMultiBtn = () => {
delete resultData.guid; 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 ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) ? ( {userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) ? (
<AuthModal/> <AuthModal />
) : ( ) : (
<> <>
<Title>우편 등록</Title> <Title>우편 등록</Title>
@@ -345,7 +336,11 @@ const MailRegist = () => {
label="예약 발송" label="예약 발송"
id="reserve" id="reserve"
checked={resultData.is_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> <InputItem>
{resultData.is_reserve && ( {resultData.is_reserve && (
@@ -353,7 +348,9 @@ const MailRegist = () => {
<InputLabel>발송 시간</InputLabel> <InputLabel>발송 시간</InputLabel>
<InputGroup> <InputGroup>
<DatePickerWrapper> <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> </DatePickerWrapper>
<SelectInput onChange={e => handleSendTime(e)} id="hour"> <SelectInput onChange={e => handleSendTime(e)} id="hour">
{HourList.map(hour => ( {HourList.map(hour => (
@@ -375,7 +372,8 @@ const MailRegist = () => {
</InputItem> </InputItem>
<InputItem> <InputItem>
<InputLabel>우편 타입</InputLabel> <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="SELECT">타입 선택</option>
<option value="SYSTEM_GUID">시스템 안내</option> <option value="SYSTEM_GUID">시스템 안내</option>
<option value="INSPECTION_COMPENSATION">점검 보상</option> <option value="INSPECTION_COMPENSATION">점검 보상</option>
@@ -389,7 +387,9 @@ const MailRegist = () => {
<InputItem> <InputItem>
<InputLabel>수신대상</InputLabel> <InputLabel>수신대상</InputLabel>
<InputItem> <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="GUID">GUID</option>
<option value="NICKNAME">아바타명</option> <option value="NICKNAME">아바타명</option>
<option value="EMAIL">이메일</option> <option value="EMAIL">이메일</option>
@@ -404,11 +404,14 @@ const MailRegist = () => {
value="SINGLE" value="SINGLE"
fontWeight="600" fontWeight="600"
checked={resultData.receive_type === 'SINGLE'} 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} handleClick={handleSingleBtn}
/> />
<TextInput <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'} disabled={resultData.receive_type !== 'SINGLE'}
onChange={e => setResultData({ ...resultData, guid: e.target.value })} onChange={e => setResultData({ ...resultData, guid: e.target.value })}
value={resultData.receive_type === 'SINGLE' && resultData.guid ? resultData.guid : ''} value={resultData.receive_type === 'SINGLE' && resultData.guid ? resultData.guid : ''}
@@ -422,7 +425,10 @@ const MailRegist = () => {
value="MULTIPLE" value="MULTIPLE"
fontWeight="600" fontWeight="600"
checked={resultData.receive_type === 'MULTIPLE'} 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} handleClick={handleMultiBtn}
/> />
<MailRegistUploadBtn <MailRegistUploadBtn
@@ -460,55 +466,57 @@ const MailRegist = () => {
</LangArea> </LangArea>
<MailRegistTable> <MailRegistTable>
<tbody> <tbody>
<tr> <tr>
<th width="120"> <th width="120">
<Label>제목</Label> <Label>제목</Label>
</th> </th>
<td> <td>
<InputItem> <InputItem>
<TextInput <TextInput
placeholder="우편 제목 입력" placeholder="우편 제목 입력"
maxLength="30" 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}
id={data.language} id={data.language}
value={data.title}
onChange={e => { onChange={e => {
if (e.target.value.length > 2000) { if (e.target.value.length > 30) {
return; return;
} }
let list = [...resultData.mail_list]; let list = [...resultData.mail_list];
let findIndex = resultData.mail_list && resultData.mail_list.findIndex(item => item.language === e.target.id); 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 }); setResultData({ ...resultData, mail_list: list });
}} }}
/> />
<MailNotice $color={data.content.length > 1999 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({data.content.length}/2000)</MailNotice> </InputItem>
</td> <MailNotice $color={data.title.length > 29 ? 'red' : '#666'}>* 최대 등록 가능
</tr> 글자수 ({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> </tbody>
</MailRegistTable> </MailRegistTable>
</MailRegistBox> </MailRegistBox>
@@ -519,17 +527,21 @@ const MailRegist = () => {
<MailRegistBox> <MailRegistBox>
<MailRegistTable> <MailRegistTable>
<tbody> <tbody>
<tr> <tr>
<th width="120"> <th width="120">
<Label>아이템 첨부</Label> <Label>아이템 첨부</Label>
</th> </th>
<td> <td>
<InputItem> <InputItem>
<TextInput placeholder="Item Meta id 입력" value={item} onChange={e => setItem(e.target.value.trimStart())} /> <TextInput placeholder="Item Meta id 입력" value={item}
<TextInput placeholder="수량" type="number" value={itemCount} onChange={e => handleItemCount(e)} width="100px" /> onChange={e => setItem(e.target.value.trimStart())} />
<Button text="추가" theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'} handleClick={handleItemList} width="100px" height="35px" /> <TextInput placeholder="수량" type="number" value={itemCount}
</InputItem> onChange={e => handleItemCount(e)} width="100px" />
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>} <Button text="추가"
theme={itemCount.length === 0 || item.length === 0 ? 'disable' : 'search'}
handleClick={handleItemList} width="100px" height="35px" />
</InputItem>
{/* {isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
<div> <div>
{resultData.item_list && ( {resultData.item_list && (
@@ -547,44 +559,48 @@ const MailRegist = () => {
</ItemList> </ItemList>
)} )}
</div> */} </div> */}
</td> </td>
</tr> </tr>
<tr> <tr>
<th width="120"> <th width="120">
<Label>자원 첨부</Label> <Label>자원 첨부</Label>
</th> </th>
<td> <td>
<InputItem> <InputItem>
<SelectInput onChange={e => setResource(e.target.value)} value={resource}> <SelectInput onChange={e => setResource(e.target.value)} value={resource}>
<option value="19010001">골드</option> <option value="19010001">골드</option>
<option value="19010002">사파이어</option> <option value="19010002">사파이어</option>
<option value="19010005">루비</option> <option value="19010005">루비</option>
<option value="19010003">칼리움</option> <option value="19010003">칼리움</option>
</SelectInput> </SelectInput>
<TextInput placeholder="수량" type="number" value={resourceCount} onChange={e => handleResourceCount(e)} width="200px" /> <TextInput placeholder="수량" type="number" value={resourceCount}
<Button text="추가" theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'} handleClick={handleResourceList} width="100px" height="35px" /> onChange={e => handleResourceCount(e)} width="200px" />
{resource === currencyCodeTypes.calium && <Label>(잔여 수량: {caliumTotalData})</Label>} <Button text="추가"
</InputItem> theme={resourceCount.length === 0 || resource.length === 0 ? 'disable' : 'search'}
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>} handleClick={handleResourceList} width="100px" height="35px" />
{resource === currencyCodeTypes.calium &&
<Label>(잔여 수량: {caliumTotalData})</Label>}
</InputItem>
{isItemNullValue && <SearchBarAlert $marginTop="15px">필수값을 입력해주세요.</SearchBarAlert>}
<div> <div>
{resultData.item_list && ( {resultData.item_list && (
<ItemList> <ItemList>
{resultData.item_list.map((data, index) => { {resultData.item_list.map((data, index) => {
return ( return (
<Item key={index}> <Item key={index}>
<span> <span>
{data.item_name}[{data.item}] ({data.item_cnt}) {data.item_name}[{data.item}] ({data.item_cnt})
</span> </span>
<BtnDelete onClick={() => onItemRemove(index)}></BtnDelete> <BtnDelete onClick={() => onItemRemove(index)}></BtnDelete>
</Item> </Item>
); );
})} })}
</ItemList> </ItemList>
)} )}
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</MailRegistTable> </MailRegistTable>
</MailRegistBox> </MailRegistBox>
@@ -595,83 +611,21 @@ const MailRegist = () => {
)} )}
<BtnWrapper $justify="flex-end" $gap="10px"> <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 <Button
type="submit" type="submit"
text="등록" text="등록"
theme={ theme={checkCondition() ? 'primary' : 'disable'}
resultData.mail_list.map(data => data.content === '' || data.title === '').includes(true) || handleClick={() => handleSubmit('submit')}
(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}
/> />
</BtnWrapper> </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; export default MailRegist;
const InputRow = styled.div` const InputRow = styled.div`
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px 50px; gap: 10px 50px;
`; `;
const InputItem = styled.div` const InputItem = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
${TextInput},${SelectInput} {
height: 35px; ${TextInput}, ${SelectInput} {
font-size: 14px; height: 35px;
} font-size: 14px;
${TextInput} { }
padding: 0 20px;
} ${TextInput} {
${SelectInput} { padding: 0 20px;
width: max-content; }
}
${SelectInput} {
width: max-content;
}
`; `;
const InputGroup = styled.div` const InputGroup = styled.div`
display: flex; display: flex;
gap: 5px; gap: 5px;
align-items: center; align-items: center;
`; `;
const MailNotice = styled.span` const MailNotice = styled.span`
font-size: 12px; font-size: 12px;
font-weight: 300; font-weight: 300;
color: ${props => props.$color || '#999'}; color: ${props => props.$color || '#999'};
margin-top: 10px; margin-top: 10px;
display: block; display: block;
`; `;
const RegistGroup = styled.div` const RegistGroup = styled.div`
display: flex; display: flex;
width: 100%; width: 100%;
flex-flow: column; flex-flow: column;
padding: 20px; padding: 20px;
border-top: 1px solid #000; border-top: 1px solid #000;
border-bottom: 1px solid #000; border-bottom: 1px solid #000;
font-size: 14px; font-size: 14px;
margin-bottom: 40px; margin-bottom: 40px;
${MailNotice} { ${MailNotice} {
margin-bottom: 20px; margin-bottom: 20px;
} }
`; `;
const BtnClose = styled.button` const BtnClose = styled.button`
width: 16px; width: 16px;
height: 16px; height: 16px;
background: url(${CloseIcon}) 50% 50% no-repeat; background: url(${CloseIcon}) 50% 50% no-repeat;
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translate(0, -50%); transform: translate(0, -50%);
right: 20px; right: 20px;
opacity: ${props => props.opacity}; opacity: ${props => props.opacity};
`; `;
const LangArea = styled.div` const LangArea = styled.div`
background: #f9f9f9; background: #f9f9f9;
padding: 10px 20px; padding: 10px 20px;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
position: relative; position: relative;
`; `;
const MailRegistBox = styled.div` const MailRegistBox = styled.div`
margin-bottom: 20px; margin-bottom: 20px;
border-top: 1px solid #999; border-top: 1px solid #999;
border-bottom: 1px solid #999; border-bottom: 1px solid #999;
`; `;
const MailRegistTable = styled.table` const MailRegistTable = styled.table`
th { th {
vertical-align: top; vertical-align: top;
line-height: 35px; line-height: 35px;
} }
th,
td { th,
padding: 15px 0; td {
border-bottom: 1px solid #f6f6f6; padding: 15px 0;
} border-bottom: 1px solid #f6f6f6;
td { }
${InputItem} {
gap: 5px; td {
} ${InputItem} {
${TextInput} { gap: 5px;
max-width: 600px; }
padding: 0 15px;
} ${TextInput} {
${Textarea} { max-width: 600px;
width: 100%; padding: 0 15px;
border: 1px solid #d9d9d9; }
border-radius: 5px;
height: 255px; ${Textarea} {
padding: 15px; width: 100%;
&:focus { border: 1px solid #d9d9d9;
border: 1px solid #2c2c2c; border-radius: 5px;
} height: 255px;
} padding: 15px;
}
&:focus {
border: 1px solid #2c2c2c;
}
}
}
`; `;
const ItemList = styled.ul` const ItemList = styled.ul`
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 20px; gap: 20px;
padding: 10px 20px; padding: 10px 20px;
`; `;
const Item = styled.li` const Item = styled.li`
display: flex; display: flex;
gap: 5px; gap: 5px;
align-items: center; align-items: center;
`; `;
const BtnDelete = styled.button` const BtnDelete = styled.button`
width: 12px; width: 12px;
height: 12px; height: 12px;
background: url(${IconDelete}) 50% 50% no-repeat; background: url(${IconDelete}) 50% 50% no-repeat;
`; `;

View File

@@ -0,0 +1,247 @@
import { useState, Fragment, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css';
import { authList } from '../../store/authList';
import {
authType,
modalTypes,
landAuctionStatusType, opYNType,
} from '../../assets/data';
import { Title, FormWrapper, TableStyle, TableWrapper} from '../../styles/Components';
import {
CheckBox,
Button,
DynamicModal,
Pagination,
ViewTableInfo,
} from '../../components/common';
import { convertKTC, timeDiffMinute } from '../../utils';
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
import { useModal, useTable, withAuth } from '../../hooks/hook';
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
import { opMenuBannerStatus } from '../../assets/data/options';
import MenuBannerSearchBar, { useMenuBannerSearch } from '../../components/ServiceManage/searchBar/MenuBannerSearchBar';
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
import { useNavigate } from 'react-router-dom';
import MenuBannerModal from '../../components/ServiceManage/modal/MenuBannerModal';
const MenuBanner = () => {
const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const { t } = useTranslation();
const tableRef = useRef(null);
const navigate = useNavigate();
const [detailData, setDetailData] = useState({});
const {
modalState,
handleModalView,
handleModalClose
} = useModal({
detail: 'hidden',
deleteConfirm: 'hidden',
deleteComplete: 'hidden'
});
const [alertMsg, setAlertMsg] = useState('');
const [modalType, setModalType] = useState('regist');
const {
searchParams,
data: dataList,
handleSearch,
handleReset,
handlePageChange,
handlePageSizeChange,
handleOrderByChange,
updateSearchParams
} = useMenuBannerSearch(token, INITIAL_PAGE_SIZE);
const {
selectedRows,
handleSelectRow,
isRowSelected
} = useTable(dataList?.event_list || [], {mode: 'single'});
const handleModalSubmit = async (type, param = null) => {
switch (type) {
case "regist":
setModalType('regist');
handleModalView('detail');
break;
case "detail":
await MenuBannerDetailView(token, param).then(data => {
setDetailData(data.event_detail);
setModalType('modify');
handleModalView('detail');
});
break;
case "delete":
const date_check = selectedRows.every(row => {
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
return timeDiff < 3;
});
if(date_check){
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
return;
}
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
return;
}
handleModalView('deleteConfirm');
break;
case "deleteConfirm":
let list = [];
let isChecked = false;
selectedRows.map(data => {
// const row = dataList.list.find(row => row.id === Number(data.id));
// if(row.status !== commonStatus.wait) isChecked = true;
list.push({
id: data.id,
});
});
if(isChecked) {
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
handleModalClose('deleteConfirm');
return;
}
await MenuBannerDelete(token, list).then(data => {
handleModalClose('deleteConfirm');
if(data.result === "SUCCESS") {
handleModalView('deleteComplete');
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
}else{
setAlertMsg(t('DELETE_FAIL'));
}
}).catch(reason => {
setAlertMsg(t('API_FAIL'));
});
break;
case "deleteComplete":
handleModalClose('deleteComplete');
window.location.reload();
break;
case "warning":
setAlertMsg('')
break;
}
}
return (
<>
<Title>메뉴 배너 관리</Title>
<FormWrapper>
<MenuBannerSearchBar
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?.some(auth => auth.id === authType.battleEventDelete) && (
<Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />
)}
{userInfo.auth_list?.some(auth => auth.id === authType.battleEventUpdate) && (
<Button
theme="primary"
text="이미지 등록"
type="button"
handleClick={e => {
e.preventDefault();
navigate('/servicemanage/menubanner/menubannerregist');
}}
/>
)}
</ViewTableInfo>
<TableWrapper>
<TableStyle ref={tableRef}>
<caption></caption>
<thead>
<tr>
<th width="40"></th>
<th width="70">번호</th>
<th width="80">등록 상태</th>
<th width="150">시작일(KST)</th>
<th width="150">종료일(KST)</th>
<th width="300">설명 제목</th>
<th width="90">링크여부</th>
<th width="100">상세보기</th>
<th width="150">히스토리</th>
</tr>
</thead>
<tbody>
{dataList?.list?.map(banner => (
<tr key={banner.row_num}>
<td>
<CheckBox name={'select'} id={banner.id}
setData={(e) => handleSelectRow(e, banner)}
checked={isRowSelected(banner.id)} />
</td>
<td>{banner.row_num}</td>
<StatusWapper>
<StatusLabel $status={banner.status}>
{opMenuBannerStatus.find(data => data.value === banner.status)?.name}
</StatusLabel>
</StatusWapper>
<td>{convertKTC(banner.start_dt)}</td>
<td>{convertKTC(banner.end_dt)}</td>
<td>{banner.title}</td>
<td>{opYNType.find(data => data.value === banner.is_link)?.name}</td>
<td>
<Button theme="line" text="상세보기"
handleClick={e => handleModalSubmit('detail', banner.id)} />
</td>
<td>{banner.update_by}</td>
</tr>
))}
</tbody>
</TableStyle>
</TableWrapper>
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
{/*상세*/}
<MenuBannerModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} />
{/*삭제 확인*/}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.deleteConfirmModal}
handleCancel={() => handleModalClose('deleteConfirm')}
handleSubmit={() => handleModalSubmit('deleteConfirm')}
modalText={t('MENU_BANNER_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')}
/>
</>
)
};
export default withAuth(authType.battleEventRead)(MenuBanner);

View File

@@ -0,0 +1,415 @@
import React, { useState, Fragment, useEffect } from 'react';
import styled from 'styled-components';
import { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next';
import Button from '../../components/common/button/Button';
import Loading from '../../components/common/Loading';
import {
Title,
BtnWrapper,
SearchBarAlert,
} from '../../styles/Components';
import { useNavigate } from 'react-router-dom';
import { MenuBannerSingleRegist } from '../../apis';
import { authList } from '../../store/authList';
import {
FormInput, FormInputSuffix, FormInputSuffixWrapper, FormLabel, FormRowGroup,RegistGroup,
} from '../../styles/ModuleComponents';
import AuthModal from '../../components/common/modal/AuthModal';
import { authType, modalTypes } from '../../assets/data';
import DynamicModal from '../../components/common/modal/DynamicModal';
import { timeDiffMinute } from '../../utils';
import { SingleDatePicker, SingleTimePicker } from '../../components/common';
import CheckBox from '../../components/common/input/CheckBox';
import ImageUploadBtn from '../../components/ServiceManage/ImageUploadBtn';
import { useModal } from '../../hooks/hook';
const MenuBannerRegist = () => {
const navigate = useNavigate();
const userInfo = useRecoilValue(authList);
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 [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
const [alertMsg, setAlertMsg] = useState('');
const [resultData, setResultData] = useState(initData); //데이터 정보
const [resetDateTime, setResetDateTime] = useState(false);
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
useEffect(() => {
if (resetDateTime) {
setResetDateTime(false);
}
}, [resetDateTime]);
// 시작 날짜 변경 핸들러
const handleStartDateChange = (date) => {
if (!date) return;
const newDate = new Date(date);
if(resultData.end_dt){
const endDate = new Date(resultData.end_dt);
const startDay = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate());
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) {
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
return;
}
}
setResultData(prev => ({
...prev,
start_dt: newDate
}));
};
// 시작 시간 변경 핸들러
const handleStartTimeChange = (time) => {
if (!time) return;
const newDateTime = resultData.start_dt
? new Date(resultData.start_dt)
: new Date();
newDateTime.setHours(
time.getHours(),
time.getMinutes(),
0,
0
);
setResultData(prev => ({
...prev,
start_dt: newDateTime
}));
};
// 종료 날짜 변경 핸들러
const handleEndDateChange = (date) => {
if (!date || !resultData.start_dt) return;
const startDate = new Date(resultData.start_dt);
const endDate = new Date(date);
// 일자만 비교하기 위해 년/월/일만 추출
const startDay = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (endDay <= startDay) {
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
return;
}
setResultData(prev => ({
...prev,
end_dt: endDate
}));
};
// 종료 시간 변경 핸들러
const handleEndTimeChange = (time) => {
if (!time) return;
const newDateTime = resultData.end_dt
? new Date(resultData.end_dt)
: new Date();
newDateTime.setHours(
time.getHours(),
time.getMinutes(),
0,
0
);
setResultData(prev => ({
...prev,
end_dt: newDateTime
}));
};
// 이미지 업로드
const handleImageUpload = (language, file, fileName) => {
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
if (imageIndex !== -1) {
const updatedImageList = [...resultData.image_list];
updatedImageList[imageIndex] = {
...updatedImageList[imageIndex],
content: fileName,
};
setResultData({
...resultData,
image_list: updatedImageList
});
}
};
// 이미지 삭제
const handleImageDelete = (language) => {
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
if (imageIndex !== -1) {
const updatedImageList = [...resultData.image_list];
updatedImageList[imageIndex] = {
...updatedImageList[imageIndex],
content: '',
};
setResultData({
...resultData,
image_list: updatedImageList
});
}
};
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'));
return;
}
handleModalView('registConfirm');
break;
case "cancel":
handleModalClose('cancel');
navigate('/servicemanage/menubanner');
break;
case "registConfirm":
setLoading(true);
const result = await MenuBannerSingleRegist(token, resultData);
setLoading(false);
handleModalClose('registConfirm');
handleModalView('registComplete');
break;
case "registComplete":
handleModalClose('registComplete');
navigate('/servicemanage/menubanner');
break;
case "warning":
setAlertMsg('');
break;
}
}
const checkCondition = () => {
return (
(resultData.start_dt.length !== 0) &&
(resultData.end_dt.length !== 0) &&
resultData.title !== '' &&
resultData.image_list.every(data => data.content !== '') &&
(resultData.is_link === false || (resultData.is_link === true && resultData.link_list.every(data => data.content !== '')))
);
};
return (
<>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
<AuthModal/>
) : (
<>
<Title>메뉴배너 등록</Title>
<RegistGroup>
<FormRowGroup>
<FormLabel>등록기간</FormLabel>
<SingleDatePicker
label="시작일자"
dateLabel="시작 일자"
onDateChange={handleStartDateChange}
selectedDate={resultData?.start_dt}
/>
<SingleTimePicker
selectedTime={resultData?.start_dt}
onTimeChange={handleStartTimeChange}
/>
<SingleDatePicker
label="종료일자"
dateLabel="종료 일자"
onDateChange={handleEndDateChange}
selectedDate={resultData?.end_dt}
/>
<SingleTimePicker
selectedTime={resultData?.end_dt}
onTimeChange={handleEndTimeChange}
/>
</FormRowGroup>
<FormRowGroup>
<FormLabel>배너 제목</FormLabel>
<FormInput
type="text"
width='50%'
value={resultData?.title}
onChange={e => setResultData({ ...resultData, title: e.target.value })}
/>
</FormRowGroup>
<FormLabel>이미지 첨부</FormLabel>
{resultData.image_list.map((data, idx) => (
<LanguageWrapper key={idx}>
<LanguageLabel>{data.language}</LanguageLabel>
<ImageUploadBtn
onImageUpload={(file, fileName) => handleImageUpload(data.language, file, fileName)}
onFileDelete={() => handleImageDelete(data.language)}
fileName={data.content}
setAlertMessage={setAlertMsg}
/>
</LanguageWrapper>
))}
<FormRowGroup>
<CheckBox
label="이미지 링크 여부"
id="reserve"
checked={resultData.is_link}
setData={e => setResultData({ ...resultData, is_link: e.target.checked })}
/>
</FormRowGroup>
{resultData?.is_link &&
<>
<FormRowGroup>
<FormLabel> 링크</FormLabel>
<LanguageWrapper width="50%" >
{resultData.link_list.map((data, idx) => (
<FormInputSuffixWrapper>
<FormInput
type="text"
value={resultData?.link_list[idx].content}
onChange={e => {
const updatedLinkList = [...resultData.link_list];
updatedLinkList[idx] = { ...updatedLinkList[idx], content: e.target.value };
setResultData({ ...resultData, link_list: updatedLinkList });
}}
suffix="true"
/>
<FormInputSuffix>{data.language}</FormInputSuffix>
</FormInputSuffixWrapper>
))}
</LanguageWrapper>
</FormRowGroup>
</>
}
</RegistGroup>
{isNullValue && (
<SearchBarAlert $align="right" $padding="0 0 15px">
{t('NULL_MSG')}
</SearchBarAlert>
)}
<BtnWrapper $justify="flex-end" $gap="10px">
<Button text="취소" theme="line" handleClick={() => handleModalView('cancel')} />
<Button
type="submit"
text="등록"
theme={checkCondition() ? 'primary' : 'disable'}
handleClick={() => handleSubmit('submit')}
/>
</BtnWrapper>
{/* 등록 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.registConfirmModal}
modalText={t('MENU_BANNER_REGIST_CONFIRM')}
handleSubmit={() => handleSubmit('registConfirm')}
handleCancel={() => handleModalClose('registConfirm')}
/>
{/* 완료 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={modalState.registCompleteModal}
modalText={t('REGIST_COMPLTE')}
handleSubmit={() => handleSubmit('registComplete')}
/>
{/* 취소 모달 */}
<DynamicModal
modalType={modalTypes.confirmOkCancel}
view={modalState.cancelModal}
modalText={t('MENU_BANNER_REGIST_CANCEL')}
handleCancel={() => handleModalClose('cancel')}
handleSubmit={() => handleSubmit('cancel')}
/>
{/* 경고 모달 */}
<DynamicModal
modalType={modalTypes.completed}
view={alertMsg ? 'view' : 'hidden'}
modalText={alertMsg}
handleSubmit={() => handleSubmit('warning')}
/>
{loading && <Loading/>}
</>
)}
</>
);
};
const 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: '' },
],
}
export default MenuBannerRegist;
const LanguageWrapper = styled.div`
width: ${props => props.width || '100%'};
//margin-bottom: 20px;
padding-bottom: 20px;
padding-left: 90px;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
`;
const LanguageLabel = styled.h4`
color: #444;
margin: 0 0 10px 20px;
font-size: 16px;
font-weight: 500;
`;

View File

@@ -1,339 +1,145 @@
import { useState, Fragment, useEffect } from 'react'; import { useState, Fragment, useRef } from 'react';
import CheckBox from '../../components/common/input/CheckBox'; import { Title, FormWrapper } from '../../styles/Components';
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 { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { UserBlockDetailModal, UserBlockSearchBar } from '../../components/ServiceManage/'; import {
import { BlackListView, BlackListDetail, BlackListDelete } from '../../apis'; CommonSearchBar,
useCommonSearch,
UserBlockDetailModal,
} from '../../components/ServiceManage/';
import { BlackListDetail, BlackListDelete } from '../../apis';
import Pagination from '../../components/common/Pagination/Pagination'; import Pagination from '../../components/common/Pagination/Pagination';
import { authList } from '../../store/authList'; import { authType } from '../../assets/data';
import { useRecoilValue } from 'recoil'; import { CaliTable, TableHeader } from '../../components/common';
import { authType, blockPeriod, blockSanctions, blockStatus } from '../../assets/data'; import { useModal, useTable, withAuth } from '../../hooks/hook';
import AuthModal from '../../components/common/modal/AuthModal'; 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 UserBlock = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList);
const navigate = useNavigate(); 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 [detail, setDetail] = useState([]);
const [requestValue, setRequestValue] = useState([]); const {
modalState,
// 모달 관련 변수 handleModalView,
const [confirmModal, setConfirmModal] = useState('hidden'); handleModalClose
const [completeModal, setCompleteModal] = useState('hidden'); } = useModal({
detail: 'hidden',
// 검색 관련 변수
const [orderBy, setOrderBy] = useState('DESC');
const [searchData, setSearchData] = useState({
searchType: 'GUID',
searchKey: '',
status: 'ALL',
sanctions: 'ALL',
period: 'ALL',
}); });
// 등록하기 모달 연결
const handleDetail = async dataValue => { const {
if (detailView === 'hidden') { config,
setDetailView('block'); searchParams,
} else { data: dataList,
setDetailView('hidden'); handleSearch,
setDetail([]); 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 ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListRead) ? ( <Title>이용자 제재 조회 등록</Title>
<AuthModal/> <FormWrapper>
) : ( <CommonSearchBar
<> config={config}
<Title>이용자 제재 조회 등록</Title> searchParams={searchParams}
<FormWrapper> onSearch={(newParams, executeSearch = true) => {
<UserBlockSearchBar handleSearch={handleSearch} setResultData={setSearchData} /> if (executeSearch) {
</FormWrapper> handleSearch(newParams);
<TableInfo> } else {
<ListCount> updateSearchParams(newParams);
: {dataList && dataList.total} / {dataList && dataList.total_all} }
</ListCount> }}
<ListOption> onReset={handleReset}
<SelectInput />
name="" </FormWrapper>
id="" <TableHeader
className="input-select" config={tableInfo.header}
onChange={e => { total={dataList?.total}
handleOrderBy(e); total_all={dataList?.total_all}
}}> handleOrderBy={handleOrderByChange}
<option value="DESC">내림차순</option> handlePageSize={handlePageSizeChange}
<option value="ASC">오름차순</option> selectedRows={selectedRows}
</SelectInput> onAction={handleAction}
<SelectInput navigate={navigate}
name="" />
id="" <CaliTable
onChange={e => { columns={tableInfo.columns}
handlePageSize(e); data={dataList?.list}
}} selectedRows={selectedRows}
className="input-select"> onSelectRow={handleSelectRow}
<option value="50">50</option> onAction={handleAction}
<option value="100">100</option> />
</SelectInput> <Pagination
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 30) && ( postsPerPage={searchParams.pageSize}
<Button theme={selectedRow.length === 0 ? 'disable' : 'line'} type="submit" text="선택 삭제" id={selectedRow} handleClick={() => handleConfirmModal(selectedRow)} /> totalPosts={dataList?.total_all}
)} setCurrentPage={handlePageChange}
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === 25) && ( currentPage={searchParams.currentPage}
<Button pageLimit={INITIAL_PAGE_LIMIT}
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} />
{/* 선택 삭제 모달 */} <UserBlockDetailModal
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={confirmModal}> stateModal={modalState.detailModal}
<BtnWrapper $justify="flex-end"> data={detail}
<ButtonClose onClick={handleConfirmeModalClose} /> handleModal={() => handleModalClose('detail')}
</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>
</>
)}
</> </>
); );
}; };
export default UserBlock; export default withAuth(authType.blackListRead)(UserBlock);
const ListState = styled.span`
color: #d60000;
`;

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 Button from '../../components/common/button/Button';
import RadioInput from '../../components/common/input/Radio'; import RadioInput from '../../components/common/input/Radio';
import styled from 'styled-components'; 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 DatePickerComponent from '../../components/common/Date/DatePickerComponent';
import { authType, blockPeriod, blockSanctions, blockType, HourList, MinuteList } from '../../assets/data'; 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 range from 'lodash/range';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import UserBlockUploadBtn from '../../components/ServiceManage/UserBlockUploadBtn'; import UserBlockUploadBtn from '../../components/ServiceManage/UserBlockUploadBtn';
import { BlackListRegist, BlackListMultipleUpload } from '../../apis'; import { BlackListRegist} from '../../apis';
import { authList } from '../../store/authList'; import { authList } from '../../store/authList';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import AuthModal from '../../components/common/modal/AuthModal'; 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); registerLocale('ko', ko);
const UserBlockRegist = () => { const UserBlockRegist = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const navigate = useNavigate(); const navigate = useNavigate();
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const {showModal, showToast} = useAlert();
const {withLoading} = useLoading();
//시간 관련 변수 //시간 관련 변수
const [sendHour, setSendHour] = useState('00'); const [sendHour, setSendHour] = useState('00');
@@ -44,8 +47,6 @@ const UserBlockRegist = () => {
const years = range(1990, getYear(new Date()) + 1, 1); const years = range(1990, getYear(new Date()) + 1, 1);
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']; 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 [isNullValue, setIsNullValue] = useState(false);
const [guidList, setGuidList] = useState([]); const [guidList, setGuidList] = useState([]);
@@ -58,7 +59,6 @@ const UserBlockRegist = () => {
start_dt: '', start_dt: '',
end_dt: '', end_dt: '',
}); });
const [confirmText, setConfirmText] = useState('');
const handleChange = e => { const handleChange = e => {
setSelectData(e.target.value); 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(() => { useEffect(() => {
if (selectData === 'single') { if (selectData === 'single') {
setSelectSingle(false); setSelectSingle(false);
@@ -113,6 +107,14 @@ const UserBlockRegist = () => {
} }
}, [selectData, guidList]); }, [selectData, guidList]);
useEffect(() => {
if (checkCondition()) {
setIsNullValue(false);
} else {
setIsNullValue(true);
}
}, [resultData]);
// 제재 시간 세팅 로직 // 제재 시간 세팅 로직
const handleSendTime = e => { const handleSendTime = e => {
let sendDate = new Date(resultData.start_dt); let sendDate = new Date(resultData.start_dt);
@@ -212,57 +214,54 @@ const UserBlockRegist = () => {
if (resultData.period === 'WARNING') setEndHour('01'); if (resultData.period === 'WARNING') setEndHour('01');
}; };
// 등록 확인 모달 const callbackPage = () => {
let notNull = resultData.list.length > 0 && resultData.type && resultData.period && resultData.sanctions && resultData.start_dt && resultData.end_dt; navigate('/servicemanage/userblock');
}
const handleRegistModalClose = () => { const handleSubmit = async (type, param = null) => {
if(selectData === 'multi'){ switch (type) {
const guidChk = guidList.some(guid => guid.validate === false); case 'submit':
if(guidChk) { if (!checkCondition()) return;
alert("유효성 체크가 통과되지 않은 항목이 존재합니다. \r\n수정 후 재등록 해주세요.") if(selectData === 'multi'){
return; 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) { case 'registConfirm':
registModalClose === 'hidden' ? setRegistModalClose('view') : setRegistModalClose('hidden'); await withLoading(async () => {
setIsNullValue(false); return await BlackListRegist(token, resultData);
} else { }).then(data => {
setIsNullValue(true); 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 checkCondition = () => {
const handleCompleteModalClose = () => { return resultData.list.length > 0 &&
if (completeModalClose === 'hidden') { resultData.type &&
setCompleteModalClose('view'); resultData.period &&
} else { resultData.sanctions &&
setCompleteModalClose('hidden'); resultData.start_dt &&
handleReset(); resultData.end_dt;
handleRegistModalClose();
navigate('/servicemanage/userblock');
window.location.reload();
}
}; };
// 등록 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 ( return (
<> <>
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListUpdate) ? ( {userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListUpdate) ? (
@@ -547,43 +546,23 @@ const UserBlockRegist = () => {
{isNullValue && <SearchBarAlert>설정값을 모두 입력해주세요.</SearchBarAlert>} {isNullValue && <SearchBarAlert>설정값을 모두 입력해주세요.</SearchBarAlert>}
<BtnWrapper $justify="flex-end" $gap="10px"> <BtnWrapper $justify="flex-end" $gap="10px">
<Button <BtnWrapper $justify="flex-end" $gap="10px">
text="취소" <Button
theme="line" text="취소"
handleClick={e => { theme="line"
e.preventDefault(); handleClick={() => showModal('MAIL_REGIST_CANCEL', {
navigate('/servicemanage/userblock'); type: alertTypes.confirm,
}} onConfirm: () => callbackPage(),
/> })}
<Button type="submit" text="등록" name="등록버튼" handleClick={handleRegistModalClose} theme={notNull ? 'primary' : 'disable'} /> />
<Button
type="submit"
text="등록"
theme={checkCondition() ? 'primary' : 'disable'}
handleClick={() => handleSubmit('submit')}
/>
</BtnWrapper>
</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,3 +10,5 @@ export { default as Event } from './Event';
export { default as EventRegist } from './EventRegist'; export { default as EventRegist } from './EventRegist';
export { default as LandAuction} from './LandAuction' export { default as LandAuction} from './LandAuction'
export { default as BattleEvent} from './BattleEvent' export { default as BattleEvent} from './BattleEvent'
export { default as MenuBanner} from './MenuBanner'
export { default as MenuBannerRegist} from './MenuBannerRegist'

View File

@@ -10,14 +10,17 @@ import { Title, FormWrapper, BtnWrapper, TableStyle } from '../../styles/Compone
import { GroupDetailViewList, GroupModify } from '../../apis'; import { GroupDetailViewList, GroupModify } from '../../apis';
import Button from '../../components/common/button/Button'; import Button from '../../components/common/button/Button';
import AuthModal from '../../components/common/modal/AuthModal'; import AuthModal from '../../components/common/modal/AuthModal';
import DynamicModal from '../../components/common/modal/DynamicModal';
import { AuthGroupRows } from '../../components/UserManage'; import { AuthGroupRows } from '../../components/UserManage';
import { useAlert } from '../../context/AlertProvider';
import { alertTypes } from '../../assets/data/types';
import { useLoading } from '../../context/LoadingProvider';
const AuthSettingUpdate = () => { const AuthSettingUpdate = () => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { showModal, showToast } = useAlert();
const { withLoading } = useLoading();
const id = location.pathname.split('/')[3]; const id = location.pathname.split('/')[3];
const [msg, setMsg] = useState(''); const [msg, setMsg] = useState('');
@@ -62,27 +65,15 @@ const AuthSettingUpdate = () => {
}); });
}; };
const handleModalView = (type) => { const handleNavigate = () => {
setModalState((prevState) => ({ navigate('/usermanage/authsetting');
...prevState,
[`${type}Modal`]: 'view',
}));
}
const handleModalClose = (type) => {
setModalState((prevState) => ({
...prevState,
[`${type}Modal`]: 'hidden',
}));
} }
const handleSubmit = async (type, param = null) => { const handleSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "cancelConfirm": case "cancelConfirm":
setMsg(t('CANCEL_COMPLETED')); showToast('CANCEL_COMPLETED', {type: alertTypes.success});
handleNavigate();
handleModalClose('cancelConfirm');
handleModalView('complete');
break; break;
case "registConfirm": case "registConfirm":
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
@@ -90,18 +81,16 @@ const AuthSettingUpdate = () => {
.flat() .flat()
.map(permissionId => ({ auth_id: permissionId })); .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; break;
} }
} }
@@ -140,40 +129,25 @@ const AuthSettingUpdate = () => {
<BtnWrapper $justify="flex-end" $gap="5px"> <BtnWrapper $justify="flex-end" $gap="5px">
<Button <Button
theme="line" theme="line"
handleClick={() => handleModalView('cancelConfirm')} handleClick={() =>
showModal('CANCEL_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('cancelConfirm')
})
}
text="취소" text="취소"
/> />
<Button <Button
theme="primary" theme="primary"
handleClick={() => handleModalView('registConfirm')} handleClick={() => showModal('SAVE_CONFIRM', {
type: alertTypes.confirm,
onConfirm: () => handleSubmit('registConfirm')
})}
text="저장" text="저장"
/> />
</BtnWrapper> </BtnWrapper>
</FormWrapper> </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 { useRecoilValue } from 'recoil';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import 'react-datepicker/dist/react-datepicker.css'; import 'react-datepicker/dist/react-datepicker.css';
@@ -7,7 +7,6 @@ import { authList } from '../../store/authList';
import { import {
authType, authType,
commonStatus, commonStatus,
modalTypes,
ViewTitleCountType, ViewTitleCountType,
caliumStatus caliumStatus
} from '../../assets/data'; } from '../../assets/data';
@@ -17,159 +16,79 @@ import {
ChargeBtn, ChargeBtn,
StatusLabel, StatusLabel,
} from '../../styles/ModuleComponents'; } 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 { convertKTC, truncateText } from '../../utils';
import { CaliumRequestRegistModal } from '../../components/UserManage'; import { CaliumRequestRegistModal } from '../../components/UserManage';
import { CaliumCharge, CaliumRequestView } from '../../apis'; import { CaliumCharge } from '../../apis';
import { withAuth } from '../../hooks/hook'; import { useModal, withAuth } from '../../hooks/hook';
import { convertEndDateToISO, convertStartDateToISO } from '../../utils/date'; import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
import {CaliumRequestSearchBar} 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 CaliumRequest = () => {
const token = sessionStorage.getItem('token'); const token = sessionStorage.getItem('token');
const userInfo = useRecoilValue(authList); const userInfo = useRecoilValue(authList);
const { t } = useTranslation(); const { t } = useTranslation();
const { showModal, showToast } = useAlert();
const {withLoading} = useLoading();
const tableRef = useRef(null); 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 [selectCharge, setSelectCharge] = useState({});
const [searchData, setSearchData] = useState({ const {
content: '', modalState,
status: 'ALL', handleModalView,
startDate: '', handleModalClose
endDate: '', } = useModal({
}); register: 'hidden',
const [orderBy, setOrderBy] = useState('DESC');
const [modalState, setModalState] = useState({
detailModal: 'hidden',
registerModal: 'hidden',
chargedCompleteModal: 'hidden',
chargedConfirmModal: 'hidden'
}); });
useEffect(() => { const {
fetchData('', 'ALL', '', ''); config,
}, [currentPage]); searchParams,
data: dataList,
// 리스트 조회 handleSearch,
const fetchData = async (content, status, startDate, endDate, order, size) => { handleReset,
setDataList( handlePageChange,
await CaliumRequestView( handlePageSizeChange,
token, handleOrderByChange,
content, updateSearchParams,
status, configLoaded
startDate && convertStartDateToISO(startDate), } = useCommonSearch(token, "caliumRequestSearch");
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 handleSubmit = async (type, param = null) => { const handleSubmit = async (type, param = null) => {
switch (type) { switch (type) {
case "detail": case "detail":
setDetailData(param); showModal(param, {
handleModalView('detail'); type: alertTypes.info
});
break; break;
case "chargedConfirm": case "chargedConfirm":
setSelectCharge({id: param.id, count: param.count}); setSelectCharge({id: param.id, count: param.count});
handleModalView('chargedConfirm'); showModal('CALIUM_CHARGE_CONFIRM', {
break; type: alertTypes.confirm,
case "charged": onConfirm: () => handleSubmit('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({});
}); });
break; break;
case "chargedComplete": case "charged":
handleModalClose('chargedComplete'); await withLoading( async () => {
await fetchData( return await CaliumCharge(token, selectCharge);
searchData.content, }).then(data => {
searchData.status, if(data.result === "SUCCESS") {
searchData.startDate, showToast('CHARGE_COMPLTED', {type: alertTypes.success});
searchData.endDate }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; break;
} }
} }
@@ -178,9 +97,20 @@ const CaliumRequest = () => {
<> <>
<Title>칼리움 사용 수량 요청</Title> <Title>칼리움 사용 수량 요청</Title>
<FormWrapper> <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> </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')} /> <ExcelDownButton tableRef={tableRef} fileName={t('FILE_CALIUM_REQUEST')} />
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && ( {userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) && (
<Button <Button
@@ -210,8 +140,7 @@ const CaliumRequest = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{dataList.list && {dataList?.list?.map(calium => (
dataList.list.map(calium => (
<Fragment key={calium.row_num}> <Fragment key={calium.row_num}>
<tr> <tr>
{/*<td>{calium.row_num}</td>*/} {/*<td>{calium.row_num}</td>*/}
@@ -238,39 +167,23 @@ const CaliumRequest = () => {
</TableStyle> </TableStyle>
</TableWrapper> </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}
/>
{/*상세*/} <CaliumRequestRegistModal
{/*<EventDetailModal detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData}/>*/} 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/>}
</> </>
); );
}; };

View File

@@ -618,4 +618,5 @@ export const TableActionButton = styled.button`
&:hover { &:hover {
background: #3a70bc; background: #3a70bc;
} }
`; `;

View File

@@ -614,6 +614,10 @@ export const FormInput = styled.input`
color: ${props => props.color || '#cccccc'}; color: ${props => props.color || '#cccccc'};
background: ${props => props.background_color || '#f6f6f6'}; background: ${props => props.background_color || '#f6f6f6'};
} }
${props => props.suffix && `
padding-right: 60px; // 라벨 영역을 위한 여백 확보
`}
`; `;
export const FormRowInput = styled.input` export const FormRowInput = styled.input`
@@ -691,6 +695,30 @@ export const FormTextAreaWrapper = styled.div`
position: relative; position: relative;
`; `;
export const FormInputSuffixWrapper = styled.div`
position: relative;
width: ${props => props.width || '100%'};
display: inline-block;
`;
export const FormInputSuffix = styled.div`
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f1f5f9;
color: #475569;
font-size: 14px;
font-weight: 500;
padding: 0 12px;
border-radius: 0 8px 8px 0;
border-left: 1px solid #e2e8f0;
`;
export const TitleItem = styled.div` export const TitleItem = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@@ -740,6 +768,7 @@ export const StatusWapper = styled.td`
display: flex; display: flex;
gap: 0.35rem; gap: 0.35rem;
align-items: center; align-items: center;
justify-content: center;
`; `;
export const ExcelDownButton = styled.button` export const ExcelDownButton = styled.button`