Compare commits
10 Commits
9d06246aba
...
67c048a11d
| Author | SHA1 | Date | |
|---|---|---|---|
| 67c048a11d | |||
| f6a0319701 | |||
| 0368bf77e7 | |||
| b2b579ead1 | |||
| 495243a1a3 | |||
| 7993f37e26 | |||
| 93a7c087e1 | |||
| 38fa323db6 | |||
| 6f9f0307ac | |||
| dc7934d906 |
@@ -11,7 +11,7 @@ server {
|
|||||||
|
|
||||||
# api reverse proxy
|
# api reverse proxy
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://172.40.129.180:23450;
|
proxy_pass http://172.24.128.231:23450;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|||||||
@@ -3,13 +3,17 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.6.1",
|
||||||
"@hookform/resolvers": "^3.2.0",
|
"@hookform/resolvers": "^3.2.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
|
"antd": "^5.26.1",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"dotenv-cli": "^7.4.2",
|
"dotenv-cli": "^7.4.2",
|
||||||
|
"framer-motion": "^12.19.1",
|
||||||
"i18next": "^23.15.1",
|
"i18next": "^23.15.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
@@ -130,3 +130,20 @@ export const BattleRewardView = async (token) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GameModeView = async (token) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.get(
|
||||||
|
`/api/v1/battle/game-mode/list`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.data.game_mode_list;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('GameModeView Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
114
src/apis/Log.js
114
src/apis/Log.js
@@ -1,12 +1,13 @@
|
|||||||
//운영 정보 관리 - 로그 api 연결
|
//운영 정보 관리 - 로그 api 연결
|
||||||
|
|
||||||
import { Axios } from '../utils';
|
import { Axios, responseFileDownload } from '../utils';
|
||||||
|
|
||||||
// 비즈니스 로그 조회
|
// 비즈니스 로그 조회
|
||||||
export const BusinessLogList = async (token, params) => {
|
export const BusinessLogList = async (token, params) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.post(`/api/v1/log/generic/list`, params, {
|
const res = await Axios.post(`/api/v1/log/generic/list`, params, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
timeout: 600000
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -16,3 +17,114 @@ export const BusinessLogList = async (token, params) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BusinessLogExport = async (token, params) => {
|
||||||
|
try {
|
||||||
|
await Axios.post(`/api/v1/log/generic/excel-export`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'blob'
|
||||||
|
}).then(response => {
|
||||||
|
responseFileDownload(response, {
|
||||||
|
defaultFileName: 'businessLog'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('BusinessLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExcelProgress = async (token, taskId) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/progress/${taskId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Progress API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrencyList = async (token, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/currency/list?start_dt=${startDate}&end_dt=${endDate}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getCurrencyList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameCurrencyLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
await Axios.post(`/api/v1/log/currency/excel-export`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 300000
|
||||||
|
}).then(response => {
|
||||||
|
|
||||||
|
responseFileDownload(response, {
|
||||||
|
defaultFileName: fileName
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('GameCurrencyLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrencyDetailList = async (token, searchType, searchData, tranId, logAction, currencyType, amountDeltaType, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/currency/detail/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}
|
||||||
|
&log_action=${logAction}¤cy_type=${currencyType}&amount_delta_type=${amountDeltaType}&start_dt=${startDate}&end_dt=${endDate}
|
||||||
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('getCurrencyDetailList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
console.log(params);
|
||||||
|
await Axios.post(`/api/v1/log/currency/detail/excel-export`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 300000
|
||||||
|
}).then(response => {
|
||||||
|
|
||||||
|
responseFileDownload(response, {
|
||||||
|
defaultFileName: fileName
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('GameCurrencyDetailLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -17,6 +17,7 @@ export * from './Calium';
|
|||||||
export * from './Land';
|
export * from './Land';
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
export * from './OpenAI';
|
export * from './OpenAI';
|
||||||
|
export * from './Log';
|
||||||
|
|
||||||
const apiModules = {};
|
const apiModules = {};
|
||||||
const allApis = {};
|
const allApis = {};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const AUCTION_MIN_MINUTE_TIME = 15; // 15분
|
|||||||
export const IMAGE_MAX_SIZE = 5242880;
|
export const IMAGE_MAX_SIZE = 5242880;
|
||||||
export const STORAGE_MAIL_COPY = 'copyMailData';
|
export const STORAGE_MAIL_COPY = 'copyMailData';
|
||||||
export const STORAGE_BUSINESS_LOG_SEARCH = 'businessLogSearchParam';
|
export const STORAGE_BUSINESS_LOG_SEARCH = 'businessLogSearchParam';
|
||||||
|
export const STORAGE_GAME_LOG_CURRENCY_SEARCH = 'gameLogCurrencySearchParam';
|
||||||
export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem';
|
export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem';
|
||||||
|
|
||||||
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };
|
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export {authType, ivenTabType, modalTypes, TabList, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
export {authType, ivenTabType, modalTypes, TabUserList, tattooSlot, commonStatus, ViewTitleCountType, landAuctionStatusType} from './types'
|
||||||
export {
|
export {
|
||||||
mailSendType,
|
mailSendType,
|
||||||
mailType,
|
mailType,
|
||||||
@@ -7,7 +7,7 @@ export {
|
|||||||
adminLevelType,
|
adminLevelType,
|
||||||
logOption,
|
logOption,
|
||||||
eventStatus,
|
eventStatus,
|
||||||
currencyType,
|
currencyItemCode,
|
||||||
blockStatus,
|
blockStatus,
|
||||||
blockSanctions,
|
blockSanctions,
|
||||||
blockPeriod,
|
blockPeriod,
|
||||||
@@ -27,5 +27,6 @@ export {
|
|||||||
opYNType,
|
opYNType,
|
||||||
opUserSessionType,
|
opUserSessionType,
|
||||||
opMailType,
|
opMailType,
|
||||||
|
amountDeltaType
|
||||||
} from './options'
|
} from './options'
|
||||||
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'
|
export {benItems, MinuteList, HourList, caliumRequestInitData, STATUS_STYLES, months, PAGE_SIZE_OPTIONS, ORDER_OPTIONS} from './data'
|
||||||
@@ -204,16 +204,16 @@ export const menuConfig = {
|
|||||||
view: true,
|
view: true,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
},
|
},
|
||||||
// menubanner: {
|
menubanner: {
|
||||||
// title: '메뉴 배너 관리',
|
title: '메뉴 배너 관리',
|
||||||
// permissions: {
|
permissions: {
|
||||||
// read: authType.menuBannerRead,
|
read: authType.menuBannerRead,
|
||||||
// update: authType.menuBannerUpdate,
|
update: authType.menuBannerUpdate,
|
||||||
// delete: authType.menuBannerDelete
|
delete: authType.menuBannerDelete
|
||||||
// },
|
},
|
||||||
// view: true,
|
view: true,
|
||||||
// authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
// },
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4,6 +4,20 @@ export const languageType = [
|
|||||||
{ value: 'JA', name: '일본어' },
|
{ value: 'JA', name: '일본어' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TabGameLogList = [
|
||||||
|
{ value: 'CURRENCY', name: '재화 로그' },
|
||||||
|
// { value: 'ITEM', name: '아이템 로그' },
|
||||||
|
// { value: 'TRADE', name: '거래 로그' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TabEconomicIndexList = [
|
||||||
|
{ value: 'CURRENCY', name: '재화(유저)' },
|
||||||
|
// { value: 'ITEM', name: '아이템' },
|
||||||
|
// { value: 'VBP', name: 'VBP' },
|
||||||
|
// { value: 'deco', name: '의상/타투' },
|
||||||
|
// { value: 'instance', name: '인스턴스' },
|
||||||
|
];
|
||||||
|
|
||||||
export const mailSendType = [
|
export const mailSendType = [
|
||||||
{ value: 'ALL', name: '전체' },
|
{ value: 'ALL', name: '전체' },
|
||||||
{ value: 'RESERVE_SEND', name: '예약 발송' },
|
{ value: 'RESERVE_SEND', name: '예약 발송' },
|
||||||
@@ -81,7 +95,7 @@ export const landAuctionStatus = [
|
|||||||
{ value: 'FAIL', name: '실패' },
|
{ value: 'FAIL', name: '실패' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const currencyType = [
|
export const currencyItemCode = [
|
||||||
{ value: '19010001', name: '골드' },
|
{ value: '19010001', name: '골드' },
|
||||||
{ value: '19010002', name: '사파이어' },
|
{ value: '19010002', name: '사파이어' },
|
||||||
{ value: '19010005', name: '루비' },
|
{ value: '19010005', name: '루비' },
|
||||||
@@ -205,6 +219,12 @@ export const CurrencyType = [
|
|||||||
{value: 'Ruby', name: '루비' }
|
{value: 'Ruby', name: '루비' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const amountDeltaType = [
|
||||||
|
{value: 'Acquire', name: '획득' },
|
||||||
|
{value: 'Consume', name: '소모' },
|
||||||
|
{value: 'None', name: '' },
|
||||||
|
]
|
||||||
|
|
||||||
export const battleEventStatus = [
|
export const battleEventStatus = [
|
||||||
{ value: 'ALL', name: '전체' },
|
{ value: 'ALL', name: '전체' },
|
||||||
{ value: 'WAIT', name: '대기' },
|
{ value: 'WAIT', name: '대기' },
|
||||||
|
|||||||
96
src/assets/data/pages/businessLogSearch.json
Normal file
96
src/assets/data/pages/businessLogSearch.json
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"initialSearchParams": {
|
||||||
|
"searchType": "GUID",
|
||||||
|
"searchData": "",
|
||||||
|
"logAction": "None",
|
||||||
|
"logDomain": "BASE",
|
||||||
|
"tran_id": "",
|
||||||
|
"startDate": "",
|
||||||
|
"endDate": "",
|
||||||
|
"orderBy": "DESC",
|
||||||
|
"pageSize": 500,
|
||||||
|
"currentPage": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"searchFields": [
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"id": "searchType",
|
||||||
|
"optionsRef": "userSearchType2",
|
||||||
|
"col": 1,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "searchData",
|
||||||
|
"placeholder": "대상 입력",
|
||||||
|
"width": "300px",
|
||||||
|
"col": 1,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ export const adminAuthLevel = {
|
|||||||
DEVELOPER: "Developer",
|
DEVELOPER: "Developer",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TabList = [
|
export const TabUserList = [
|
||||||
{ title: '기본정보' },
|
{ title: '기본정보' },
|
||||||
{ title: '아바타' },
|
{ title: '아바타' },
|
||||||
{ title: '의상' },
|
{ title: '의상' },
|
||||||
@@ -159,3 +159,9 @@ export const currencyCodeTypes = {
|
|||||||
ruby: "19010005",
|
ruby: "19010005",
|
||||||
calium: "19010003"
|
calium: "19010003"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const languageNames = {
|
||||||
|
'KO': '한국어',
|
||||||
|
'EN': '영어',
|
||||||
|
'JA': '일본어',
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
import { styled } from 'styled-components';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
|
||||||
import { getMonth, getYear } from 'date-fns';
|
|
||||||
import range from 'lodash/range';
|
|
||||||
|
|
||||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
|
||||||
|
|
||||||
const GoodsLogSearchBar = () => {
|
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
|
||||||
const [endDate, setEndDate] = useState(new Date());
|
|
||||||
const [selectData, setSelectData] = useState('default');
|
|
||||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
|
||||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
|
||||||
|
|
||||||
const handleChange = e => {
|
|
||||||
setSelectData(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SearchbarStyle2>
|
|
||||||
<SearchRow>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 기간</InputLabel>
|
|
||||||
<DatePickerWrapper>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={startDate}
|
|
||||||
onChange={date => setStartDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
<span>~</span>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={endDate}
|
|
||||||
onChange={date => setEndDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
minDate = {startDate}
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</SearchItem>
|
|
||||||
</SearchRow>
|
|
||||||
<SearchRow>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 대상</InputLabel>
|
|
||||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
|
||||||
</SearchItem>
|
|
||||||
<BtnWrapper $gap="8px">
|
|
||||||
<Button theme="reset" />
|
|
||||||
<Button theme="gray" text="검색" />
|
|
||||||
</BtnWrapper>
|
|
||||||
</SearchRow>
|
|
||||||
</SearchbarStyle2>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GoodsLogSearchBar;
|
|
||||||
|
|
||||||
const SearchbarStyle = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
`;
|
|
||||||
const SearchbarStyle2 = styled(SearchbarStyle)`
|
|
||||||
flex-flow: column;
|
|
||||||
gap: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InputGroup = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin-right: 50px;
|
|
||||||
|
|
||||||
${TextInput}, ${SelectInput} {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
${TextInput} {
|
|
||||||
padding: 0 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
165
src/components/DataManage/CurrencyLogContent.js
Normal file
165
src/components/DataManage/CurrencyLogContent.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
CircularProgressWrapper,
|
||||||
|
FormWrapper,
|
||||||
|
TableStyle,
|
||||||
|
TableWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
import { amountDeltaType, CurrencyType } from '../../assets/data';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { CurrencyLogSearchBar, useCurrencyLogSearch } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import Pagination from '../common/Pagination/Pagination';
|
||||||
|
import {
|
||||||
|
INITIAL_PAGE_LIMIT,
|
||||||
|
STORAGE_BUSINESS_LOG_SEARCH,
|
||||||
|
STORAGE_GAME_LOG_CURRENCY_SEARCH,
|
||||||
|
} from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
|
||||||
|
const CurrencyLogContent = ({ active }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const tableRef = useRef(null);
|
||||||
|
const [downloadState, setDownloadState] = useState({
|
||||||
|
loading: false,
|
||||||
|
progress: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handlePageChange,
|
||||||
|
handleOrderByChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
updateSearchParams
|
||||||
|
} = useCurrencyLogSearch(token, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(active) {
|
||||||
|
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
||||||
|
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_CURRENCY_SEARCH);
|
||||||
|
|
||||||
|
if (paramsData) {
|
||||||
|
const searchData = JSON.parse(paramsData);
|
||||||
|
|
||||||
|
handleSearch({
|
||||||
|
start_dt: new Date(searchData.start_dt),
|
||||||
|
end_dt: new Date(searchData.end_dt),
|
||||||
|
search_data: searchData.guid
|
||||||
|
});
|
||||||
|
|
||||||
|
// 사용 후 세션 스토리지 데이터 삭제
|
||||||
|
sessionStorage.removeItem(STORAGE_GAME_LOG_CURRENCY_SEARCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
// { id: 'logDay', label: '일자', width: '120px' },
|
||||||
|
{ id: 'logTime', label: '일시', width: '120px' },
|
||||||
|
{ id: 'accountId', label: 'account ID', width: '80px' },
|
||||||
|
{ id: 'userGuid', label: 'GUID', width: '200px' },
|
||||||
|
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
||||||
|
{ id: 'tranId', label: '트랜잭션 ID', width: '200px' },
|
||||||
|
{ id: 'action', label: '액션', width: '150px' },
|
||||||
|
{ id: 'currencyType', label: '재화종류', width: '120px' },
|
||||||
|
{ id: 'amountDeltaType', label: '증감유형', width: '120px' },
|
||||||
|
{ id: 'deltaAmount', label: '수량', width: '120px' },
|
||||||
|
// { id: 'deltaAmount', label: '수량원본', width: '120px' },
|
||||||
|
{ id: 'currencyAmount', label: '잔량', width: '120px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormWrapper>
|
||||||
|
<CurrencyLogSearchBar
|
||||||
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
|
</FormWrapper>
|
||||||
|
<ViewTableInfo orderType="asc" pageType="B" total={dataList?.total} total_all={dataList?.total_all}>
|
||||||
|
<ExcelExportButton
|
||||||
|
functionName="GameCurrencyDetailLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_CURRENCY')}
|
||||||
|
onLoadingChange={setDownloadState}
|
||||||
|
dataSize={dataList?.total_all}
|
||||||
|
/>
|
||||||
|
{downloadState.loading && (
|
||||||
|
<CircularProgressWrapper>
|
||||||
|
<CircularProgress
|
||||||
|
progress={downloadState.progress}
|
||||||
|
size={36}
|
||||||
|
progressColor="#4A90E2"
|
||||||
|
/>
|
||||||
|
</CircularProgressWrapper>
|
||||||
|
)}
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
|
<TableWrapper>
|
||||||
|
<TableStyle ref={tableRef}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{tableHeaders.map(header => (
|
||||||
|
<th key={header.id} width={header.width}>{header.label}</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{dataList?.currency_detail_list?.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<tr>
|
||||||
|
<td>{item.logTime}</td>
|
||||||
|
<td>{item.accountId}</td>
|
||||||
|
<td>{item.userGuid}</td>
|
||||||
|
<td>{item.userNickname}</td>
|
||||||
|
<td>{item.tranId}</td>
|
||||||
|
<td>{item.action}</td>
|
||||||
|
<td>{CurrencyType.find(data => data.value === item.currencyType)?.name}</td>
|
||||||
|
<td>{amountDeltaType.find(data => data.value === item.amountDeltaType)?.name}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.deltaAmount)}</td>
|
||||||
|
{/*<td>{item.deltaAmount}</td>*/}
|
||||||
|
<td>{numberFormatter.formatCurrency(item.currencyAmount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.currency_detail_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CurrencyLogContent;
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { styled } from 'styled-components';
|
|
||||||
|
|
||||||
import RadioInput from '../common/input/Radio';
|
|
||||||
import Button from '../common/button/Button';
|
|
||||||
|
|
||||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
|
||||||
import { ko } from 'date-fns/esm/locale';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
|
||||||
import { getMonth, getYear } from 'date-fns';
|
|
||||||
import range from 'lodash/range';
|
|
||||||
|
|
||||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
|
||||||
|
|
||||||
const ItemLogSearchBar = () => {
|
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
|
||||||
const [endDate, setEndDate] = useState(new Date());
|
|
||||||
const [selectData, setSelectData] = useState('default');
|
|
||||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
|
||||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
|
||||||
|
|
||||||
const handleChange = e => {
|
|
||||||
setSelectData(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SearchbarStyle2>
|
|
||||||
<SearchRow>
|
|
||||||
<RadioGroup>
|
|
||||||
<RadioInput label="기본 조회" id="single" name="receiver" value="default" fontWeight="600" checked={selectData === 'default'} handleChange={handleChange} />
|
|
||||||
<RadioInput label="아이템 소유자 추적" id="multi" name="receiver" value="item" fontWeight="600" checked={selectData === 'item'} handleChange={handleChange} />
|
|
||||||
</RadioGroup>
|
|
||||||
</SearchRow>
|
|
||||||
<SearchRow>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 기간</InputLabel>
|
|
||||||
<DatePickerWrapper>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={startDate}
|
|
||||||
onChange={date => setStartDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
<span>~</span>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={endDate}
|
|
||||||
onChange={date => setEndDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
minDate = {startDate}
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</SearchItem>
|
|
||||||
</SearchRow>
|
|
||||||
<SearchRow>
|
|
||||||
{selectData === 'default' ? (
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 대상</InputLabel>
|
|
||||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
|
||||||
</SearchItem>
|
|
||||||
) : (
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>아이템 ID</InputLabel>
|
|
||||||
<TextInput type="text" placeholder="아이템의 GUID를 입력하세요." width="600px" />
|
|
||||||
</SearchItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<BtnWrapper $gap="8px">
|
|
||||||
<Button theme="reset" />
|
|
||||||
<Button theme="gray" text="검색" />
|
|
||||||
</BtnWrapper>
|
|
||||||
</SearchRow>
|
|
||||||
</SearchbarStyle2>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ItemLogSearchBar;
|
|
||||||
|
|
||||||
const SearchbarStyle = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
`;
|
|
||||||
const SearchbarStyle2 = styled(SearchbarStyle)`
|
|
||||||
flex-flow: column;
|
|
||||||
gap: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InputGroup = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RadioGroup = styled(InputGroup)`
|
|
||||||
gap: 30px;
|
|
||||||
height: 35px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin-right: 50px;
|
|
||||||
|
|
||||||
${TextInput}, ${SelectInput} {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
${TextInput} {
|
|
||||||
padding: 0 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
import { styled } from 'styled-components';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
|
||||||
import { getMonth, getYear } from 'date-fns';
|
|
||||||
import range from 'lodash/range';
|
|
||||||
|
|
||||||
import { TextInput, SelectInput, DatePickerWrapper, InputLabel, BtnWrapper } from '../../styles/Components';
|
|
||||||
|
|
||||||
const TradeLogSerchBar = () => {
|
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
|
||||||
const [endDate, setEndDate] = useState(new Date());
|
|
||||||
const [selectData, setSelectData] = useState('default');
|
|
||||||
const years = range(1990, getYear(new Date()) + 1, 1);
|
|
||||||
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
|
|
||||||
|
|
||||||
const handleChange = e => {
|
|
||||||
setSelectData(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SearchbarStyle2>
|
|
||||||
<SearchRow>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 기간</InputLabel>
|
|
||||||
<DatePickerWrapper>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={startDate}
|
|
||||||
onChange={date => setStartDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
<span>~</span>
|
|
||||||
<InputGroup>
|
|
||||||
<DatePicker
|
|
||||||
selected={endDate}
|
|
||||||
onChange={date => setEndDate(date)}
|
|
||||||
className="datepicker"
|
|
||||||
placeholderText="검색기간 선택"
|
|
||||||
calendarClassName="calendar"
|
|
||||||
dateFormat="yyyy - MM - dd"
|
|
||||||
minDate = {startDate}
|
|
||||||
locale="ko"
|
|
||||||
renderCustomHeader={({ date, changeYear, changeMonth, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
|
|
||||||
<div className="calendar-top">
|
|
||||||
<button
|
|
||||||
className="btn-prev"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
decreaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={prevMonthButtonDisabled}></button>
|
|
||||||
<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(value)}>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
.
|
|
||||||
<select value={months[getMonth(date)]} onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className="btn-next"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
increaseMonth();
|
|
||||||
}}
|
|
||||||
disabled={nextMonthButtonDisabled}></button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">00</option>
|
|
||||||
<option value="">01</option>
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</SearchItem>
|
|
||||||
</SearchRow>
|
|
||||||
<SearchRow>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>조회 대상</InputLabel>
|
|
||||||
<TextInput type="text" placeholder="조회 대상 유저의 GUID를 입력하세요." width="600px" />
|
|
||||||
</SearchItem>
|
|
||||||
<BtnWrapper $gap="8px">
|
|
||||||
<Button theme="reset" />
|
|
||||||
<Button theme="gray" text="검색" />
|
|
||||||
</BtnWrapper>
|
|
||||||
</SearchRow>
|
|
||||||
</SearchbarStyle2>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TradeLogSerchBar;
|
|
||||||
|
|
||||||
const SearchbarStyle = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
`;
|
|
||||||
const SearchbarStyle2 = styled(SearchbarStyle)`
|
|
||||||
flex-flow: column;
|
|
||||||
gap: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InputGroup = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin-right: 50px;
|
|
||||||
|
|
||||||
${TextInput}, ${SelectInput} {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
${TextInput} {
|
|
||||||
padding: 0 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,312 +1,221 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import React, { Fragment, useMemo, useRef, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { CurrencyIndexExport, CurrencyIndexView } from '../../apis';
|
import {
|
||||||
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel } from '../../styles/Components';
|
TableStyle,
|
||||||
|
FormWrapper,
|
||||||
|
TableWrapper, CircularProgressWrapper, TotalRow,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import CreditSeacrhBar from '../../components/IndexManage/CreditSearchBar';
|
import { useCurrencyIndexSearch } from '../searchBar';
|
||||||
import { uniqBy } from 'lodash';
|
import { Button, TopButton, ViewTableInfo } from '../common';
|
||||||
import { sumBy } from 'lodash';
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import {STORAGE_GAME_LOG_CURRENCY_SEARCH, } from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import CurrencyIndexSearchBar from '../searchBar/CurrencyIndexSearchBar';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const CreditContent = () => {
|
const CreditContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
let d = new Date();
|
const navigate = useNavigate();
|
||||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
const tableRef = useRef(null);
|
||||||
const END_DATE = new Date();
|
const [downloadState, setDownloadState] = useState({
|
||||||
const CURRENCY_LIST = [
|
loading: false,
|
||||||
{ "key": "Gold", "name": "골드" },
|
progress: 0
|
||||||
{ "key": "Sapphire", "name": "사파이어" },
|
});
|
||||||
{ "key": "Calium", "name": "칼리움" },
|
|
||||||
{ "key": "Onyxium", "name": "오닉시움" }
|
const {
|
||||||
|
searchParams,
|
||||||
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useCurrencyIndexSearch(token);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'accountId', label: 'account ID', width: '80px' },
|
||||||
|
{ id: 'userGuid', label: 'GUID', width: '200px' },
|
||||||
|
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
||||||
|
{ id: 'sapphireAcquired', label: '사파이어 획득량', width: '80px' },
|
||||||
|
{ id: 'sapphireConsumed', label: '사파이어 소모량', width: '80px' },
|
||||||
|
{ id: 'goldAcquired', label: '골드 획득량', width: '80px' },
|
||||||
|
{ id: 'goldConsumed', label: '골드 소모량', width: '80px' },
|
||||||
|
{ id: 'caliumAcquired', label: '칼리움 획득량', width: '80px' },
|
||||||
|
{ id: 'caliumConsumed', label: '칼리움 소모량', width: '80px' },
|
||||||
|
{ id: 'beamAcquired', label: 'BEAM 획득량', width: '80px' },
|
||||||
|
{ id: 'beamConsumed', label: 'BEAM 소모량', width: '80px' },
|
||||||
|
{ id: 'rubyAcquired', label: '루비 획득량', width: '80px' },
|
||||||
|
{ id: 'rubyConsumed', label: '루비 소모량', width: '80px' },
|
||||||
|
{ id: 'sapphireNet', label: '사파이어 계', width: '80px' },
|
||||||
|
{ id: 'goldNet', label: '골드 계', width: '80px' },
|
||||||
|
{ id: 'caliumNet', label: '칼리움 계', width: '80px' },
|
||||||
|
{ id: 'beamNet', label: 'BEAM 계', width: '80px' },
|
||||||
|
{ id: 'rubyNet', label: '루비 계', width: '80px' },
|
||||||
|
{ id: 'totalCurrencies', label: '활동 수', width: '80px' },
|
||||||
|
{ id: 'detail', label: '상세', width: '100px' },
|
||||||
];
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
const totals = useMemo(() => {
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
if (!dataList?.currency_list?.length) return null;
|
||||||
const [currencyType, setCurrencyType] = useState('Gold');
|
|
||||||
const [currencyText, setCurrencyText] = useState('골드');
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
return dataList.currency_list.reduce((acc, item) => {
|
||||||
const [routeData, setRouteData] = useState([]);
|
return {
|
||||||
|
sapphireAcquired: (acc.sapphireAcquired || 0) + (item.sapphireAcquired || 0),
|
||||||
|
sapphireConsumed: (acc.sapphireConsumed || 0) + (item.sapphireConsumed || 0),
|
||||||
|
goldAcquired: (acc.goldAcquired || 0) + (item.goldAcquired || 0),
|
||||||
|
goldConsumed: (acc.goldConsumed || 0) + (item.goldConsumed || 0),
|
||||||
|
caliumAcquired: (acc.caliumAcquired || 0) + (item.caliumAcquired || 0),
|
||||||
|
caliumConsumed: (acc.caliumConsumed || 0) + (item.caliumConsumed || 0),
|
||||||
|
beamAcquired: (acc.beamAcquired || 0) + (item.beamAcquired || 0),
|
||||||
|
beamConsumed: (acc.beamConsumed || 0) + (item.beamConsumed || 0),
|
||||||
|
rubyAcquired: (acc.rubyAcquired || 0) + (item.rubyAcquired || 0),
|
||||||
|
rubyConsumed: (acc.rubyConsumed || 0) + (item.rubyConsumed || 0),
|
||||||
|
sapphireNet: (acc.sapphireNet || 0) + (item.sapphireNet || 0),
|
||||||
|
goldNet: (acc.goldNet || 0) + (item.goldNet || 0),
|
||||||
|
caliumNet: (acc.caliumNet || 0) + (item.caliumNet || 0),
|
||||||
|
beamNet: (acc.beamNet || 0) + (item.beamNet || 0),
|
||||||
|
rubyNet: (acc.rubyNet || 0) + (item.rubyNet || 0),
|
||||||
|
totalCurrencies: (acc.totalCurrencies || 0) + (item.totalCurrencies || 0),
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
}, [dataList?.currency_list]);
|
||||||
|
|
||||||
|
const handleModalSubmit = async (type, param = null) => {
|
||||||
useEffect(() => {
|
switch (type) {
|
||||||
fetchData(sendDate, finishDate, currencyType);
|
case "detail":
|
||||||
}, [currencyType]);
|
const params = {
|
||||||
|
tab: "CURRENCY",
|
||||||
const fetchData = async (startDate, endDate) => {
|
start_dt: (() => {
|
||||||
const newStartDate = new Date(startDate);
|
const date = new Date(param.logDay);
|
||||||
const newEndDate = new Date(endDate);
|
return date;
|
||||||
|
})(),
|
||||||
const startDateToLocal =
|
end_dt: (() => {
|
||||||
newStartDate.getFullYear() +
|
const date = new Date(param.logDay);
|
||||||
'-' +
|
date.setDate(date.getDate() + 1);
|
||||||
(newStartDate.getMonth() + 1 < 9 ? '0' + (newStartDate.getMonth() + 1) : newStartDate.getMonth() + 1) +
|
return date;
|
||||||
'-' +
|
})(),
|
||||||
(newStartDate.getDate() < 9 ? '0' + newStartDate.getDate() : newStartDate.getDate());
|
guid: param.userGuid
|
||||||
|
|
||||||
const endDateToLocal =
|
|
||||||
newEndDate.getFullYear() +
|
|
||||||
'-' +
|
|
||||||
(newEndDate.getMonth() + 1 < 9 ? '0' + (newEndDate.getMonth() + 1) : newEndDate.getMonth() + 1) +
|
|
||||||
'-' +
|
|
||||||
(newEndDate.getDate() < 9 ? '0' + newEndDate.getDate() : newEndDate.getDate());
|
|
||||||
|
|
||||||
setDataList(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
|
|
||||||
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
setRoutArray(await CurrencyIndexView(token, startDateToLocal, endDateToLocal, currencyType));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCurrencyChange = e => {
|
// 복사한 데이터를 세션 스토리지에 저장
|
||||||
let value = e.target.value;
|
sessionStorage.setItem(STORAGE_GAME_LOG_CURRENCY_SEARCH, JSON.stringify(params));
|
||||||
setCurrencyType(value);
|
navigate('/datamanage/gamelogview');
|
||||||
CURRENCY_LIST.filter(data => data.key === value).map(data => {
|
break;
|
||||||
setCurrencyText(data.name);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//route data
|
|
||||||
const setRoutArray = async (data) => {
|
|
||||||
let routeArray = [];
|
|
||||||
let routeAcqArr = [];
|
|
||||||
let routeConArr = [];
|
|
||||||
|
|
||||||
//생산량 route
|
|
||||||
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'ACQUIRE').map(routeData => {
|
|
||||||
routeData.data.map(routeData => {
|
|
||||||
if(!routeAcqArr.includes(routeData.route) ){
|
|
||||||
routeAcqArr.push(routeData.route);
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
});
|
|
||||||
routeArray.ACQUIRE = routeAcqArr;
|
|
||||||
|
|
||||||
//소진량 route
|
|
||||||
data.list && data.list[0].daily_data.filter(routeData => routeData.delta_type === 'CONSUME').map(routeData => {
|
|
||||||
routeData.data.map(routeData => {
|
|
||||||
if(!routeConArr.includes(routeData.route) ){
|
|
||||||
routeConArr.push(routeData.route);
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
});
|
|
||||||
routeArray.CONSUME = routeConArr;
|
|
||||||
|
|
||||||
setRouteData(routeArray);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_Credit_Index.xlsx';
|
|
||||||
CurrencyIndexExport(token, fileName, sendDate, finishDate, currencyType);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<FormWrapper>
|
||||||
<CreditSeacrhBar fetchData={fetchData} />
|
<CurrencyIndexSearchBar
|
||||||
<TableInfo2>
|
searchParams={searchParams}
|
||||||
<SelectInput onChange={handleCurrencyChange}>
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
{CURRENCY_LIST.map((item, index) => (
|
if (executeSearch) {
|
||||||
<option value={item.key} key={index}>
|
handleSearch(newParams);
|
||||||
{item.name}
|
} else {
|
||||||
</option>
|
updateSearchParams(newParams);
|
||||||
))}
|
}
|
||||||
</SelectInput>
|
}}
|
||||||
<ListOption>
|
onReset={handleReset}
|
||||||
<Button theme="line" text="엑셀 다운로드" handleClick={handleXlsxExport} />
|
/>
|
||||||
</ListOption>
|
</FormWrapper>
|
||||||
</TableInfo2>
|
<ViewTableInfo >
|
||||||
|
<ExcelExportButton
|
||||||
|
functionName="GameCurrencyLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_CURRENCY_INDEX')}
|
||||||
|
onLoadingChange={setDownloadState}
|
||||||
|
dataSize={dataList?.length}
|
||||||
|
/>
|
||||||
|
{downloadState.loading && (
|
||||||
|
<CircularProgressWrapper>
|
||||||
|
<CircularProgress
|
||||||
|
progress={downloadState.progress}
|
||||||
|
size={36}
|
||||||
|
progressColor="#4A90E2"
|
||||||
|
/>
|
||||||
|
</CircularProgressWrapper>
|
||||||
|
)}
|
||||||
|
</ViewTableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
|
<>
|
||||||
<TableWrapper>
|
<TableWrapper>
|
||||||
<EconomicTable>
|
<TableStyle ref={tableRef}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colSpan="2" className="text-center" width="300">
|
{tableHeaders.map(header => (
|
||||||
Product
|
<th key={header.id} width={header.width}>{header.label}</th>
|
||||||
</th>
|
))}
|
||||||
{dataList.list && uniqBy(dataList.list, 'date').map(data =>
|
|
||||||
<th width="160" key={data.date}>{data.date}</th>
|
|
||||||
)}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{totals && (
|
||||||
<TableTitle colSpan="2">(Total) {currencyText} 생산량</TableTitle>
|
<TotalRow>
|
||||||
{dataList.list &&
|
<td colSpan="4">합계</td>
|
||||||
dataList.list.map((data) =>
|
<td>{numberFormatter.formatCurrency(totals.sapphireAcquired)}</td>
|
||||||
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE')
|
<td>{numberFormatter.formatCurrency(totals.sapphireConsumed)}</td>
|
||||||
.map((totalData, index) => (
|
<td>{numberFormatter.formatCurrency(totals.goldAcquired)}</td>
|
||||||
<TableData key={index}
|
<td>{numberFormatter.formatCurrency(totals.goldConsumed)}</td>
|
||||||
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}>
|
<td>{numberFormatter.formatCurrency(totals.caliumAcquired)}</td>
|
||||||
{totalData.quantity}
|
<td>{numberFormatter.formatCurrency(totals.caliumConsumed)}</td>
|
||||||
{
|
<td>{numberFormatter.formatCurrency(totals.beamAcquired)}</td>
|
||||||
totalData.dif !== "" && totalData.dif !== "Infinity"
|
<td>{numberFormatter.formatCurrency(totals.beamConsumed)}</td>
|
||||||
? (<span>({totalData.dif})</span>)
|
<td>{numberFormatter.formatCurrency(totals.rubyAcquired)}</td>
|
||||||
: ("")
|
<td>{numberFormatter.formatCurrency(totals.rubyConsumed)}</td>
|
||||||
}
|
<td>{numberFormatter.formatCurrency(totals.sapphireNet)}</td>
|
||||||
</TableData>
|
<td>{numberFormatter.formatCurrency(totals.goldNet)}</td>
|
||||||
)
|
<td>{numberFormatter.formatCurrency(totals.caliumNet)}</td>
|
||||||
))
|
<td>{numberFormatter.formatCurrency(totals.beamNet)}</td>
|
||||||
}
|
<td>{numberFormatter.formatCurrency(totals.rubyNet)}</td>
|
||||||
</tr>
|
<td>{totals.totalCurrencies}</td>
|
||||||
<tr>
|
<td>-</td>
|
||||||
<TableTitle colSpan="2">(Total) {currencyText} 소진량</TableTitle>
|
</TotalRow>
|
||||||
{dataList.list &&
|
|
||||||
dataList.list.map((data) =>
|
|
||||||
(data.total).filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME')
|
|
||||||
.map((totalData, index) => (
|
|
||||||
<TableData key={index}
|
|
||||||
$state={totalData.dif !== "" && totalData.dif !== "Infinity" ? "danger" : ""}>
|
|
||||||
{totalData.quantity}
|
|
||||||
{
|
|
||||||
totalData.dif !== "" && totalData.dif !== "Infinity"
|
|
||||||
? (<span>({totalData.dif})</span>)
|
|
||||||
: ("")
|
|
||||||
}
|
|
||||||
</TableData>
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<TableTitle colSpan="2">(Total) {currencyText} 보유량</TableTitle>
|
|
||||||
{dataList.list &&
|
|
||||||
dataList.list.map((data, index) => (
|
|
||||||
<TableData key={index}>
|
|
||||||
{sumBy(
|
|
||||||
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'ACQUIRE'),
|
|
||||||
'quantity',
|
|
||||||
) -
|
|
||||||
sumBy(
|
|
||||||
data.total.filter(totalData => data.date === totalData.date && totalData.delta_type === 'CONSUME'),
|
|
||||||
'quantity',
|
|
||||||
)}
|
)}
|
||||||
</TableData>
|
{dataList?.currency_list?.map((item, index) => (
|
||||||
))
|
<Fragment key={index}>
|
||||||
}
|
<tr>
|
||||||
|
<td>{item.logDay}</td>
|
||||||
|
<td>{item.accountId}</td>
|
||||||
|
<td>{item.userGuid}</td>
|
||||||
|
<td>{item.userNickname}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.sapphireAcquired)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.sapphireConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.goldAcquired)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.goldConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.caliumAcquired)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.caliumConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.beamAcquired)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.beamConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.rubyAcquired)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.rubyConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.sapphireNet)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.goldNet)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.caliumNet)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.beamNet)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.rubyNet)}</td>
|
||||||
|
<td>{item.totalCurrencies}</td>
|
||||||
|
<td>
|
||||||
|
<Button theme="line" text="상세보기"
|
||||||
|
handleClick={e => handleModalSubmit('detail', item)} />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/* 획득 GET */}
|
</Fragment>
|
||||||
{
|
))}
|
||||||
routeData.ACQUIRE && routeData.ACQUIRE.map((route, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<TableTitle>GET</TableTitle>
|
|
||||||
<TableTitle>{route}</TableTitle>
|
|
||||||
{dataList.list &&
|
|
||||||
dataList.list.map((data) =>
|
|
||||||
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'ACQUIRE')
|
|
||||||
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
|
|
||||||
.map((routeData, i) => (
|
|
||||||
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
|
|
||||||
)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{/* 소진 USE CONSUME */}
|
|
||||||
{
|
|
||||||
routeData.CONSUME && routeData.CONSUME.map((route, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<TableTitle>USE</TableTitle>
|
|
||||||
<TableTitle>{route}</TableTitle>
|
|
||||||
{dataList.list &&
|
|
||||||
dataList.list.map((data) =>
|
|
||||||
data.daily_data.filter(dailyData => data.date === dailyData.date && dailyData.delta_type === 'CONSUME')
|
|
||||||
.map(dailyData => (dailyData.data).filter(routeData => data.date === routeData.date && routeData.route === route)
|
|
||||||
.map((routeData, i) => (
|
|
||||||
<TableData key={i} data={routeData.date}>{routeData.quantity}</TableData>
|
|
||||||
)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</EconomicTable>
|
</TableStyle>
|
||||||
</TableWrapper>
|
</TableWrapper>
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreditContent;
|
export default CreditContent;
|
||||||
|
|
||||||
const TableWrapper = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
min-width: 680px;
|
|
||||||
overflow: auto;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #666666;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: #d9d9d9;
|
|
||||||
}
|
|
||||||
${TableStyle} {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 900px;
|
|
||||||
th {
|
|
||||||
&.cell-nru {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-left: 1px solid #aaa;
|
|
||||||
border-right: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
&.blank {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
&.cell-nru {
|
|
||||||
background: #fafafa;
|
|
||||||
border-left: 1px solid #aaa;
|
|
||||||
border-right: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableInfo2 = styled(TableInfo)`
|
|
||||||
justify-content: space-between;
|
|
||||||
${InputLabel} {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
${SelectInput} {
|
|
||||||
width: auto;
|
|
||||||
min-width: 100px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableDate = styled.th`
|
|
||||||
color: ${props => (props.$state === 'danger' ? '#d60000' : '#2c2c2c')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableData = styled.td`
|
|
||||||
|
|
||||||
background: ${props => (props.$state === 'danger' ? '#d60000' : props.$state === 'blank' ? '#F9F9F9' : 'transparent')};
|
|
||||||
color: ${props => (props.$state === 'danger' ? '#fff' : '#2c2c2c')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const perData = styled.span`
|
|
||||||
display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableTitle = styled.td`
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const EconomicTable = styled(TableStyle)`
|
|
||||||
${TableData} {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
tr:nth-child(1),
|
|
||||||
tr:nth-child(2),
|
|
||||||
tr:nth-child(3) {
|
|
||||||
background: #f5fcff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import DecoSearchBar from '../../components/IndexManage/DecoSearchBar';
|
import DecoSearchBar from '../searchBar/DecoSearchBar';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import InstanceSearchBar from '../../components/IndexManage/InstanceSearchBar';
|
import InstanceSearchBar from '../searchBar/InstanceSearchBar';
|
||||||
import { InstanceIndexExport, InstanceIndexView } from '../../apis';
|
import { InstanceIndexExport, InstanceIndexView } from '../../apis';
|
||||||
|
|
||||||
const InstanceContent = () => {
|
const InstanceContent = () => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Button from '../../components/common/button/Button';
|
|||||||
|
|
||||||
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel, InputGroup } from '../../styles/Components';
|
import { SelectInput, TableStyle, TableInfo, ListOption, InputLabel, InputGroup } from '../../styles/Components';
|
||||||
|
|
||||||
import ItemSearchBar from '../../components/IndexManage/ItemSearchBar';
|
import ItemSearchBar from '../searchBar/ItemSearchBar';
|
||||||
import { ItemIndexExport, ItemIndexView } from '../../apis';
|
import { ItemIndexExport, ItemIndexView } from '../../apis';
|
||||||
|
|
||||||
const ItemContent = () => {
|
const ItemContent = () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
import { TableStyle, TableInfo, ListOption } from '../../styles/Components';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import VBPSearchBar from '../../components/IndexManage/VBPSearchBar';
|
import VBPSearchBar from '../searchBar/VBPSearchBar';
|
||||||
import { VBPIndexExport, VbpIndexView } from '../../apis';
|
import { VBPIndexExport, VbpIndexView } from '../../apis';
|
||||||
|
|
||||||
const VBPContent = () => {
|
const VBPContent = () => {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import UserIndexSearchBar from "./UserIndexSearchBar";
|
import UserIndexSearchBar from "../searchBar/UserIndexSearchBar";
|
||||||
import RetentionSearchBar from "./RetentionSearchBar";
|
import RetentionSearchBar from "../searchBar/RetentionSearchBar";
|
||||||
import SegmentSearchBar from "./SegmentSearchBar";
|
import SegmentSearchBar from "../searchBar/SegmentSearchBar";
|
||||||
import DailyDashBoard from "./DailyDashBoard";
|
import DailyDashBoard from "./DailyDashBoard";
|
||||||
import PlayTimeSearchBar from "./PlayTimeSearchBar";
|
import PlayTimeSearchBar from "../searchBar/PlayTimeSearchBar";
|
||||||
import UserContent from "./UserContent";
|
import UserContent from "./UserContent";
|
||||||
import PlayTimeContent from "./PlayTimeContent";
|
import PlayTimeContent from "./PlayTimeContent";
|
||||||
import RetentionContent from "./RetentionContent";
|
import RetentionContent from "./RetentionContent";
|
||||||
import SegmentContent from "./SegmentContent";
|
import SegmentContent from "./SegmentContent";
|
||||||
import DailyActiveUserContent from "./DailyActiveUserContent";
|
import DailyActiveUserContent from "./DailyActiveUserContent";
|
||||||
import DailyMedalContent from "./DailyMedalContent";
|
import DailyMedalContent from "./DailyMedalContent";
|
||||||
import DailySearchBar from "./DailySearchBar";
|
import DailySearchBar from "../searchBar/DailySearchBar";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
UserIndexSearchBar,
|
UserIndexSearchBar,
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
import BoardInfoModal from './modal/BoardInfoModal';
|
import BoardInfoModal from '../modal/BoardInfoModal';
|
||||||
import BoardRegistModal from './modal/BoardRegistModal';
|
import BoardRegistModal from '../modal/BoardRegistModal';
|
||||||
import MailDetailModal from './modal/MailDetailModal';
|
import MailDetailModal from '../modal/MailDetailModal';
|
||||||
import LandAuctionModal from './modal/LandAuctionModal'
|
import LandAuctionModal from '../modal/LandAuctionModal'
|
||||||
import BattleEventModal from './modal/BattleEventModal'
|
import BattleEventModal from '../modal/BattleEventModal'
|
||||||
import ReportListAnswerModal from './modal/ReportListAnswerModal';
|
import ReportListAnswerModal from '../modal/ReportListAnswerModal';
|
||||||
import ReportListDetailModal from './modal/ReportListDetailModal';
|
import ReportListDetailModal from '../modal/ReportListDetailModal';
|
||||||
import UserBlockDetailModal from './modal/UserBlockDetailModal';
|
import UserBlockDetailModal from '../modal/UserBlockDetailModal';
|
||||||
import OwnerChangeModal from './modal/OwnerChangeModal';
|
import OwnerChangeModal from '../modal/OwnerChangeModal';
|
||||||
//searchbar
|
//searchbar
|
||||||
import SearchFilter from './searchBar/SearchFilter';
|
import SearchFilter from '../searchBar/SearchFilter';
|
||||||
import ReportListSearchBar from './searchBar/ReportListSearchBar';
|
import ReportListSearchBar from '../searchBar/ReportListSearchBar';
|
||||||
import UserBlockSearchBar from './searchBar/UserBlockSearchBar';
|
import UserBlockSearchBar from '../searchBar/UserBlockSearchBar';
|
||||||
import EventListSearchBar from './searchBar/EventListSearchBar';
|
import EventListSearchBar from '../searchBar/EventListSearchBar';
|
||||||
import LandAuctionSearchBar from './searchBar/LandAuctionSearchBar'
|
import LandAuctionSearchBar from '../searchBar/LandAuctionSearchBar'
|
||||||
import MailListSearchBar from './searchBar/MailListSearchBar';
|
import MailListSearchBar from '../searchBar/MailListSearchBar';
|
||||||
import LandInfoSearchBar from './searchBar/LandInfoSearchBar';
|
import LandInfoSearchBar from '../searchBar/LandInfoSearchBar';
|
||||||
import BusinessLogSearchBar from './searchBar/BusinessLogSearchBar';
|
import BusinessLogSearchBar from '../searchBar/BusinessLogSearchBar';
|
||||||
import DataInitSearchBar from './searchBar/DataInitSearchBar';
|
import DataInitSearchBar from '../searchBar/DataInitSearchBar';
|
||||||
import LogViewSearchBar from './searchBar/LogViewSearchBar';
|
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 CurrencyLogSearchBar from '../searchBar/CurrencyLogSearchBar';
|
||||||
|
|
||||||
import CommonSearchBar from './searchBar/CommonSearchBar';
|
import CommonSearchBar from '../searchBar/CommonSearchBar';
|
||||||
import useCommonSearch from '../../hooks/useCommonSearch';
|
import useCommonSearch from '../../hooks/useCommonSearch';
|
||||||
|
|
||||||
//etc
|
//etc
|
||||||
@@ -35,6 +36,7 @@ export {
|
|||||||
MailListSearchBar,
|
MailListSearchBar,
|
||||||
LandInfoSearchBar,
|
LandInfoSearchBar,
|
||||||
BusinessLogSearchBar,
|
BusinessLogSearchBar,
|
||||||
|
CurrencyLogSearchBar,
|
||||||
DataInitSearchBar,
|
DataInitSearchBar,
|
||||||
LogViewSearchBar,
|
LogViewSearchBar,
|
||||||
AdminViewSearchBar,
|
AdminViewSearchBar,
|
||||||
|
|||||||
301
src/components/common/Layout/DetailGrid.js
Normal file
301
src/components/common/Layout/DetailGrid.js
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Form, Input, Select, DatePicker, TimePicker, InputNumber, Switch, Button, Checkbox } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 위치 지정 가능한 그리드 형태 상세 정보 표시 컴포넌트
|
||||||
|
* @param {Array} items - 표시할 항목 배열 (row, col, rowSpan, colSpan 속성 추가)
|
||||||
|
* @param {Object} formData - 폼 데이터 객체
|
||||||
|
* @param {Function} onChange - 값 변경 시 호출할 함수
|
||||||
|
* @param {boolean} disabled - 전체 비활성화 여부
|
||||||
|
* @param {number} columns - 그리드의 총 컬럼 수 (기본값: 4)
|
||||||
|
*/
|
||||||
|
const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }) => {
|
||||||
|
// 항목을 행과 열 위치별로 그룹화
|
||||||
|
const positionedItems = {};
|
||||||
|
|
||||||
|
// 각 항목의 위치 및 span 정보 처리
|
||||||
|
items.forEach(item => {
|
||||||
|
const rowIndex = item.row || 0;
|
||||||
|
const colIndex = item.col || 0;
|
||||||
|
|
||||||
|
if (!positionedItems[rowIndex]) {
|
||||||
|
positionedItems[rowIndex] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
positionedItems[rowIndex][colIndex] = {
|
||||||
|
...item,
|
||||||
|
rowSpan: item.rowSpan || 1,
|
||||||
|
colSpan: item.colSpan || 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 행 번호 목록 (정렬)
|
||||||
|
const rows = Object.keys(positionedItems).map(Number).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// 항목에 따른 컴포넌트 렌더링 함수
|
||||||
|
const renderComponent = (item) => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
key,
|
||||||
|
keys,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
placeholder,
|
||||||
|
disabled: itemDisabled,
|
||||||
|
width,
|
||||||
|
handler,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
format,
|
||||||
|
required,
|
||||||
|
showTime
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
// 현재 값 가져오기 (formData에서 또는 항목에서)
|
||||||
|
const currentValue = formData[key] !== undefined ? formData[key] : value;
|
||||||
|
|
||||||
|
// 컴포넌트 공통 속성
|
||||||
|
const commonProps = {
|
||||||
|
id: key,
|
||||||
|
disabled: disabled || itemDisabled,
|
||||||
|
style: width ? { width, fontSize: '15px' } : { width: '100%', fontSize: '15px' }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 항목 타입에 따른 컴포넌트 렌더링
|
||||||
|
switch (type) {
|
||||||
|
case 'text':
|
||||||
|
return <Input
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue}
|
||||||
|
onChange={(e) => onChange(key, e.target.value, handler)}
|
||||||
|
placeholder={placeholder || `${label} 입력`}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return <InputNumber
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
onChange={(value) => onChange(key, value, handler)}
|
||||||
|
placeholder={placeholder || `${label} 입력`}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
case 'select':
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue}
|
||||||
|
onChange={(value) => onChange(key, value, handler)}
|
||||||
|
placeholder={placeholder || `${label} 선택`}
|
||||||
|
>
|
||||||
|
{options && options.map((option) => (
|
||||||
|
<Select.Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue ? dayjs(currentValue) : null}
|
||||||
|
format={format || 'YYYY-MM-DD'}
|
||||||
|
onChange={(date) => onChange(key, date, handler)}
|
||||||
|
placeholder={placeholder || `${label} 선택`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'dateRange':
|
||||||
|
return (
|
||||||
|
<RangePicker
|
||||||
|
{...commonProps}
|
||||||
|
showTime={showTime !== false}
|
||||||
|
value={keys ? [
|
||||||
|
formData[keys.start] ? dayjs(formData[keys.start]) : null,
|
||||||
|
formData[keys.end] ? dayjs(formData[keys.end]) : null
|
||||||
|
] : (currentValue ? [
|
||||||
|
currentValue.start ? dayjs(currentValue.start) : null,
|
||||||
|
currentValue.end ? dayjs(currentValue.end) : null
|
||||||
|
] : null)}
|
||||||
|
format={format || 'YYYY-MM-DD HH:mm:ss'}
|
||||||
|
onChange={(dates, dateStrings) => {
|
||||||
|
if (dates) {
|
||||||
|
// 두 개의 별도 필드에 각각 업데이트
|
||||||
|
if (item.keys) {
|
||||||
|
onChange(item.keys.start, dates[0], handler);
|
||||||
|
onChange(item.keys.end, dates[1], handler);
|
||||||
|
} else {
|
||||||
|
// 기존 방식 지원 (하위 호환성)
|
||||||
|
onChange(key, {
|
||||||
|
start: dates[0],
|
||||||
|
end: dates[1]
|
||||||
|
}, handler);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 두 필드 모두 비우기
|
||||||
|
if (item.keys) {
|
||||||
|
onChange(item.keys.start, null, handler);
|
||||||
|
onChange(item.keys.end, null, handler);
|
||||||
|
} else {
|
||||||
|
onChange(key, { start: null, end: null }, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={[
|
||||||
|
item.startLabel || '시작 일시',
|
||||||
|
item.endLabel || '종료 일시'
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'time':
|
||||||
|
return (
|
||||||
|
<TimePicker
|
||||||
|
{...commonProps}
|
||||||
|
value={currentValue ? dayjs(currentValue, 'HH:mm') : null}
|
||||||
|
format={format || 'HH:mm'}
|
||||||
|
onChange={(time) => onChange(key, time, handler)}
|
||||||
|
placeholder={placeholder || `${label} 선택`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'switch':
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={currentValue}
|
||||||
|
onChange={(checked) => onChange(key, checked, handler)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'checkbox':
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
checked={currentValue}
|
||||||
|
disabled={disabled || itemDisabled}
|
||||||
|
onChange={(e) => onChange(key, e.target.checked, handler)}
|
||||||
|
>
|
||||||
|
{item.checkboxLabel}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
return <StatusDisplay status={currentValue} />;
|
||||||
|
|
||||||
|
case 'tab':
|
||||||
|
return <AnimatedTabs
|
||||||
|
items={tabItems}
|
||||||
|
activeKey={activeLanguage}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
case 'custom':
|
||||||
|
return item.render ? item.render(formData, onChange) : null;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <div>{currentValue}</div>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 각 셀의 폭 계산 (Ant Design의 24-컬럼 시스템 기준)
|
||||||
|
const colWidth = 24 / columns;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridContainer>
|
||||||
|
<Form layout="horizontal">
|
||||||
|
{rows.map(rowIndex => {
|
||||||
|
const rowItems = positionedItems[rowIndex];
|
||||||
|
const cols = Object.keys(rowItems).map(Number).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row key={`row-${rowIndex}`} gutter={[16, 16]}>
|
||||||
|
{cols.map(colIndex => {
|
||||||
|
const item = rowItems[colIndex];
|
||||||
|
const itemColSpan = Math.min(item.colSpan * colWidth, 24);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
key={`${item.key}-${rowIndex}-${colIndex}`}
|
||||||
|
span={itemColSpan}
|
||||||
|
xs={24}
|
||||||
|
sm={itemColSpan}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label={item.label}
|
||||||
|
required={item.required}
|
||||||
|
tooltip={item.tooltip}
|
||||||
|
>
|
||||||
|
{renderComponent(item)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Form>
|
||||||
|
</GridContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 상태 표시 컴포넌트
|
||||||
|
const StatusDisplay = ({ status }) => {
|
||||||
|
let color = '';
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'wait':
|
||||||
|
color = '#faad14';
|
||||||
|
text = '대기';
|
||||||
|
break;
|
||||||
|
case 'running':
|
||||||
|
color = '#52c41a';
|
||||||
|
text = '진행중';
|
||||||
|
break;
|
||||||
|
case 'finish':
|
||||||
|
color = '#d9d9d9';
|
||||||
|
text = '만료';
|
||||||
|
break;
|
||||||
|
case 'fail':
|
||||||
|
color = '#ff4d4f';
|
||||||
|
text = '실패';
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
color = '#ff4d4f';
|
||||||
|
text = '삭제';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = '#1890ff';
|
||||||
|
text = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StatusTag color={color}>
|
||||||
|
{text}
|
||||||
|
</StatusTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GridContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 0;
|
||||||
|
font-size: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StatusTag = styled.div`
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: ${props => props.color};
|
||||||
|
color: white;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default DetailGrid;
|
||||||
127
src/components/common/Layout/DetailLayout.js
Normal file
127
src/components/common/Layout/DetailLayout.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import DetailGrid from './DetailGrid';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ant Design 방식의 DetailModalWrapper 컴포넌트
|
||||||
|
* @param {Array} itemGroups - 표시할 항목 그룹 배열
|
||||||
|
* @param {Object} formData - 폼 데이터 객체
|
||||||
|
* @param {Function} onChange - 값 변경 시 호출할 함수
|
||||||
|
* @param {boolean} disabled - 전체 비활성화 여부
|
||||||
|
* @param {number} columnCount - 한 행에 표시할 컬럼 수 (기본값: 4)
|
||||||
|
* @param {ReactNode} children - 추가 컨텐츠
|
||||||
|
*/
|
||||||
|
const DetailLayout = ({
|
||||||
|
itemGroups,
|
||||||
|
formData,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
columnCount = 4,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
// 값 변경 핸들러
|
||||||
|
const handleChange = (key, value, handler) => {
|
||||||
|
// 핸들러가 있으면 핸들러 실행
|
||||||
|
if (handler) {
|
||||||
|
handler(value, key, formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 키가 점 표기법이면 중첩 객체 업데이트
|
||||||
|
if (key.includes('.')) {
|
||||||
|
const [parentKey, childKey] = key.split('.');
|
||||||
|
onChange({
|
||||||
|
...formData,
|
||||||
|
[parentKey]: {
|
||||||
|
...formData[parentKey],
|
||||||
|
[childKey]: value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 일반 키는 직접 업데이트
|
||||||
|
onChange({ ...formData, [key]: value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailWrapper>
|
||||||
|
{itemGroups.map((group, index) => (
|
||||||
|
<Card
|
||||||
|
key={`group-${index}`}
|
||||||
|
title={group.title}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<DetailGrid
|
||||||
|
items={group.items}
|
||||||
|
formData={formData}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={disabled || group.disabled}
|
||||||
|
columnCount={group.columnCount || columnCount}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{children}
|
||||||
|
</DetailWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default DetailLayout;
|
||||||
|
|
||||||
|
//예시
|
||||||
|
// const itemGroupsExample = [
|
||||||
|
// {
|
||||||
|
// title: '기본 정보',
|
||||||
|
// items: [
|
||||||
|
// {
|
||||||
|
// row: 0,
|
||||||
|
// col: 0,
|
||||||
|
// colSpan: 2,
|
||||||
|
// type: 'text',
|
||||||
|
// key: 'title',
|
||||||
|
// label: '제목',
|
||||||
|
// required: true,
|
||||||
|
// disabled: !isView('title'),
|
||||||
|
// width: '300px',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// row: 0,
|
||||||
|
// col: 2,
|
||||||
|
// colSpan: 2,
|
||||||
|
// type: 'number',
|
||||||
|
// key: 'order_id',
|
||||||
|
// label: '순서',
|
||||||
|
// required: true,
|
||||||
|
// disabled: !isView('order_id'),
|
||||||
|
// width: '200px',
|
||||||
|
// min: 1,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// row: 1,
|
||||||
|
// col: 0,
|
||||||
|
// colSpan: 2,
|
||||||
|
// type: 'status',
|
||||||
|
// key: 'status',
|
||||||
|
// label: '상태',
|
||||||
|
// value: resultData.status,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// row: 1,
|
||||||
|
// col: 2,
|
||||||
|
// colSpan: 2,
|
||||||
|
// type: 'switch',
|
||||||
|
// key: 'is_link',
|
||||||
|
// label: '링크 사용',
|
||||||
|
// disabled: !isView('is_link'),
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: ''
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
84
src/components/common/button/AntButton.js
Normal file
84
src/components/common/button/AntButton.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button as AntBtn} from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ant Design의 Button을 사용한 버튼 컴포넌트
|
||||||
|
* @param {string} text - 버튼 텍스트
|
||||||
|
* @param {string} type - 버튼 타입 (primary, default, dashed, link, text)
|
||||||
|
* @param {function} onClick - 클릭 이벤트 핸들러
|
||||||
|
* @param {string} theme - 커스텀 테마 (primary, line, disable, reset, gray, search, find)
|
||||||
|
* @param {boolean} disabled - 비활성화 여부
|
||||||
|
* @param {string} name - 버튼 이름
|
||||||
|
* @param {string} width - 버튼 너비
|
||||||
|
* @param {string} height - 버튼 높이
|
||||||
|
*/
|
||||||
|
const AntButton = ({
|
||||||
|
text,
|
||||||
|
type = 'button',
|
||||||
|
onClick,
|
||||||
|
theme,
|
||||||
|
disabled,
|
||||||
|
name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
// theme에 따른 Ant Design 버튼 타입 설정
|
||||||
|
let buttonType = 'default';
|
||||||
|
let shape = 'default';
|
||||||
|
let icon = '';
|
||||||
|
let color = 'default';
|
||||||
|
let variant = 'outlined';
|
||||||
|
let buttonProps = {
|
||||||
|
disabled,
|
||||||
|
onClick,
|
||||||
|
name,
|
||||||
|
style: {
|
||||||
|
width: width || 'auto',
|
||||||
|
height: height || '35px',
|
||||||
|
minWidth: 'fit-content',
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
|
...props
|
||||||
|
};
|
||||||
|
|
||||||
|
// 테마에 따른 스타일 설정
|
||||||
|
switch (theme) {
|
||||||
|
case 'primary':
|
||||||
|
case 'submit':
|
||||||
|
case 'line':
|
||||||
|
buttonType = 'default';
|
||||||
|
break;
|
||||||
|
case 'disable':
|
||||||
|
buttonProps.disabled = true;
|
||||||
|
break;
|
||||||
|
case 'gray':
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
case 'find':
|
||||||
|
icon = <SearchOutlined />;
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
color = 'danger';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AntBtn
|
||||||
|
type={buttonType}
|
||||||
|
shape={shape}
|
||||||
|
icon={icon}
|
||||||
|
variant={variant}
|
||||||
|
color={color}
|
||||||
|
htmlType={type === 'submit' ? 'submit' : 'button'}
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</AntBtn>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AntButton;
|
||||||
157
src/components/common/button/ExcelExportButton.js
Normal file
157
src/components/common/button/ExcelExportButton.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { ExcelDownButton } from '../../../styles/ModuleComponents';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { BusinessLogExport, getExcelProgress } from '../../../apis/Log';
|
||||||
|
import { useAlert } from '../../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../../assets/data/types';
|
||||||
|
import * as APIs from '../../../apis';
|
||||||
|
import { loadConfig } from '../../../utils';
|
||||||
|
|
||||||
|
const ExcelDownloadButton = ({ functionName, params, fileName = 'download.xlsx', sheetName = 'Sheet1', onLoadingChange, disabled, dataSize }) => {
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const taskIdRef = useRef(null);
|
||||||
|
const intervalRef = useRef(null);
|
||||||
|
|
||||||
|
// 진행률 폴링 함수
|
||||||
|
const pollProgress = useCallback(async (taskId) => {
|
||||||
|
try {
|
||||||
|
const response = await getExcelProgress(token, taskId);
|
||||||
|
// console.log(response.data);
|
||||||
|
if (response.data.exists) {
|
||||||
|
const { percentage, message } = response.data;
|
||||||
|
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({
|
||||||
|
loading: true,
|
||||||
|
progress: percentage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({ loading: true, progress: percentage });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 100% 완료 시 폴링 중지
|
||||||
|
if (percentage >= 100) {
|
||||||
|
// console.log("pollProgress data exists polling stop");
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 진행률 정보가 없으면 폴링 중지
|
||||||
|
console.log("pollProgress data not exists polling stop");
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({ loading: false, progress: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Progress polling error:', error);
|
||||||
|
// 에러 발생 시 폴링 중지
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
|
||||||
|
setIsDownloading(false);
|
||||||
|
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({ loading: false, progress: 0 });
|
||||||
|
showToast('DOWNLOAD_FAIL', { type: alertTypes.error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [onLoadingChange]);
|
||||||
|
|
||||||
|
// 컴포넌트 언마운트 시 정리
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
console.log("unmount polling stop");
|
||||||
|
onLoadingChange({ loading: false, progress: 0 });
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({ loading: false, progress: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDownload = useCallback(async () => {
|
||||||
|
if (isDownloading) return; // 이미 다운로드 중이면 중복 실행 방지
|
||||||
|
if (dataSize > 200000){
|
||||||
|
showToast('EXCEL_EXPORT_LENGTH_LIMIT_WARNING', {type: alertTypes.warning});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const start = Date.now();
|
||||||
|
const taskId = `excel_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||||
|
taskIdRef.current = taskId;
|
||||||
|
|
||||||
|
setIsDownloading(true);
|
||||||
|
if (onLoadingChange) onLoadingChange({loading: true, progress: 0});
|
||||||
|
|
||||||
|
// 진행률 폴링 시작 (1초마다)
|
||||||
|
intervalRef.current = setInterval(() => {
|
||||||
|
pollProgress(taskId);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APIs[functionName](token, {...params, taskId}, fileName);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await pollProgress(taskId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Final progress check error:', error);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('BusinessLogExport API error:', error);
|
||||||
|
showToast(error, {type: alertTypes.error});
|
||||||
|
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
setIsDownloading(false);
|
||||||
|
if (onLoadingChange) {
|
||||||
|
onLoadingChange({
|
||||||
|
loading: false,
|
||||||
|
progress: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!intervalRef.current) return;
|
||||||
|
|
||||||
|
// 2초 후 상태 리셋
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsDownloading(false);
|
||||||
|
|
||||||
|
// console.log("handleDownload finally polling stop");
|
||||||
|
|
||||||
|
onLoadingChange({ loading: false, progress: 100 });
|
||||||
|
showToast('DOWNLOAD_COMPLETE', { type: alertTypes.success });
|
||||||
|
|
||||||
|
// 폴링 완전 중지
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const end = Date.now();
|
||||||
|
// showToast(`처리 시간: ${end - start}ms`, { type: alertTypes.info });
|
||||||
|
// console.log(`처리 시간: ${end - start}ms`);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, [params, isDownloading, onLoadingChange, dataSize, pollProgress, showToast]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExcelDownButton onClick={handleDownload} disabled={isDownloading || dataSize === 0}>
|
||||||
|
{isDownloading ? '다운로드 중...' : '엑셀 다운로드'}
|
||||||
|
</ExcelDownButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExcelDownloadButton;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
width: 50px;
|
width: 40px;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #666666;
|
background-color: #666666;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -54,7 +55,7 @@ const TopButton = () => {
|
|||||||
onClick={scrollToTop}
|
onClick={scrollToTop}
|
||||||
title="맨 위로 이동"
|
title="맨 위로 이동"
|
||||||
>
|
>
|
||||||
↑
|
<VerticalAlignTopOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
78
src/components/common/control/AnimatedTabs.js
Normal file
78
src/components/common/control/AnimatedTabs.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
|
// 통합된 애니메이션 탭 컴포넌트
|
||||||
|
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||||
|
return (
|
||||||
|
<StyledTabs
|
||||||
|
activeKey={activeKey}
|
||||||
|
onChange={onChange}
|
||||||
|
centered={true}
|
||||||
|
>
|
||||||
|
{items.map(item => (
|
||||||
|
<Tabs.TabPane
|
||||||
|
tab={item.label}
|
||||||
|
key={item.key}
|
||||||
|
>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={activeKey}
|
||||||
|
initial={{ opacity: 0, x: 50 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -50 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.children}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
))}
|
||||||
|
</StyledTabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledTabs = styled(Tabs)`
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.ant-tabs-nav {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-wrap {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-content-holder {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default AnimatedTabs;
|
||||||
@@ -19,6 +19,8 @@ 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 AntButton from './button/AntButton';
|
||||||
|
import DetailLayout from './Layout/DetailLayout';
|
||||||
|
|
||||||
import CaliTable from './Custom/CaliTable'
|
import CaliTable from './Custom/CaliTable'
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ export { DateTimeInput,
|
|||||||
CheckBox,
|
CheckBox,
|
||||||
Radio,
|
Radio,
|
||||||
Button,
|
Button,
|
||||||
|
AntButton,
|
||||||
ExcelDownButton,
|
ExcelDownButton,
|
||||||
AuthModal,
|
AuthModal,
|
||||||
CompletedModal,
|
CompletedModal,
|
||||||
@@ -52,5 +55,6 @@ export { DateTimeInput,
|
|||||||
DynamoPagination,
|
DynamoPagination,
|
||||||
FrontPagination,
|
FrontPagination,
|
||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
CaliTable
|
CaliTable,
|
||||||
|
DetailLayout
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,34 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Modal as AntModal } from 'antd';
|
||||||
|
|
||||||
const ModalBg = styled.div`
|
// const ModalBg = styled.div`
|
||||||
|
// position: fixed;
|
||||||
|
// background: ${props => props.$bgcolor || 'rgba(0, 0, 0, 0.5)'};
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100%;
|
||||||
|
// top: 0;
|
||||||
|
// left: 0;
|
||||||
|
// min-width: 1080px;
|
||||||
|
// display: ${props => (props.$view === 'hidden' ? 'none' : 'block')};
|
||||||
|
// z-index: 20;
|
||||||
|
// `;
|
||||||
|
//
|
||||||
|
// const ModalWrapper = styled.div`
|
||||||
|
// position: absolute;
|
||||||
|
// background: #fff;
|
||||||
|
// left: 50%;
|
||||||
|
// top: 50%;
|
||||||
|
// transform: translate(-50%, -50%);
|
||||||
|
// min-width: ${props => props.min || 'auto'};
|
||||||
|
// padding: ${props => props.$padding || '30px'};
|
||||||
|
// border-radius: 30px;
|
||||||
|
// max-height: 90%;
|
||||||
|
// overflow: auto;
|
||||||
|
// box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
// `;
|
||||||
|
|
||||||
|
const ModalBg = styled(motion.div)`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background: ${props => props.$bgcolor || 'rgba(0, 0, 0, 0.5)'};
|
background: ${props => props.$bgcolor || 'rgba(0, 0, 0, 0.5)'};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -12,12 +40,15 @@ const ModalBg = styled.div`
|
|||||||
z-index: 20;
|
z-index: 20;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ModalWrapper = styled.div`
|
const ModalContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: #fff;
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ModalWrapper = styled(motion.div)`
|
||||||
|
background: #fff;
|
||||||
min-width: ${props => props.min || 'auto'};
|
min-width: ${props => props.min || 'auto'};
|
||||||
padding: ${props => props.$padding || '30px'};
|
padding: ${props => props.$padding || '30px'};
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
@@ -27,15 +58,52 @@ const ModalWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
|
const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
|
||||||
|
const isVisible = $view !== 'hidden';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ModalBg $view={$view} $bgcolor={$bgcolor}>
|
<AnimatePresence>
|
||||||
<ModalWrapper $padding={$padding} min={min}>
|
{isVisible && (
|
||||||
|
<ModalBg
|
||||||
|
$bgcolor={$bgcolor}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<ModalContainer>
|
||||||
|
<ModalWrapper
|
||||||
|
$padding={$padding}
|
||||||
|
min={min}
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
exit={{ scale: 0.9, opacity: 0 }}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
|
</ModalContainer>
|
||||||
</ModalBg>
|
</ModalBg>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const Modal = ({ children, $padding, min, $view, $bgcolor }) => {
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// <ModalBg $view={$view} $bgcolor={$bgcolor}>
|
||||||
|
// <ModalWrapper $padding={$padding} min={min}>
|
||||||
|
// {children}
|
||||||
|
// </ModalWrapper>
|
||||||
|
// </ModalBg>
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
export default Modal;
|
export default Modal;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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 {
|
import {
|
||||||
Title,
|
Title,
|
||||||
BtnWrapper,
|
BtnWrapper,
|
||||||
SearchBarAlert, SelectInput,
|
SearchBarAlert, SelectInput,
|
||||||
} from '../../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormInput,
|
FormInput,
|
||||||
@@ -17,23 +17,23 @@ import {
|
|||||||
FormStatusLabel,
|
FormStatusLabel,
|
||||||
FormStatusWarning,
|
FormStatusWarning,
|
||||||
FormButtonContainer,
|
FormButtonContainer,
|
||||||
} from '../../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { Modal, SingleDatePicker, SingleTimePicker } from '../../common';
|
import { 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 { convertKTCDate } from '../../../utils';
|
import { convertKTCDate } from '../../utils';
|
||||||
import {
|
import {
|
||||||
battleEventHotTime,
|
battleEventHotTime,
|
||||||
battleEventRoundCount,
|
battleEventRoundCount,
|
||||||
battleEventStatus,
|
battleEventStatus,
|
||||||
battleRepeatType,
|
battleRepeatType,
|
||||||
} from '../../../assets/data/options';
|
} from '../../assets/data/options';
|
||||||
import { BattleEventModify, BattleEventSingleRegist } from '../../../apis/Battle';
|
import { BattleEventModify, BattleEventSingleRegist } from '../../apis/Battle';
|
||||||
import { alertTypes, 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 { useAlert } from '../../context/AlertProvider';
|
||||||
import { useLoading } from '../../../context/LoadingProvider';
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
|
||||||
const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
|
const BattleEventModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData, gameModeData }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
const { showToast, showModal } = useAlert();
|
const { showToast, showModal } = useAlert();
|
||||||
@@ -46,7 +46,8 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
if(modalType === TYPE_MODIFY && content && Object.keys(content).length > 0){
|
||||||
setResultData({
|
setResultData({
|
||||||
group_id: content.group_id,
|
group_id: content.group_id,
|
||||||
event_id: content.event_id,
|
id: content.id,
|
||||||
|
event_id: content.id,
|
||||||
event_name: content.event_name,
|
event_name: content.event_name,
|
||||||
repeat_type: content.repeat_type,
|
repeat_type: content.repeat_type,
|
||||||
config_id: content.config_id,
|
config_id: content.config_id,
|
||||||
@@ -54,6 +55,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
round_count: content.round_count,
|
round_count: content.round_count,
|
||||||
hot_time: content.hot_time,
|
hot_time: content.hot_time,
|
||||||
round_time: content.round_time,
|
round_time: content.round_time,
|
||||||
|
game_mode_id: content.game_mode_id,
|
||||||
status: content.status,
|
status: content.status,
|
||||||
event_start_dt: convertKTCDate(content.event_start_dt),
|
event_start_dt: convertKTCDate(content.event_start_dt),
|
||||||
event_end_dt: content.event_end_dt,
|
event_end_dt: content.event_end_dt,
|
||||||
@@ -262,6 +264,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
case "reward":
|
case "reward":
|
||||||
case "round":
|
case "round":
|
||||||
case "hot":
|
case "hot":
|
||||||
|
case "mode":
|
||||||
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === battleEventStatusType.stop));
|
return modalType === TYPE_REGISTRY || (modalType === TYPE_MODIFY &&(content?.status === battleEventStatusType.stop));
|
||||||
default:
|
default:
|
||||||
return modalType === TYPE_MODIFY && (content?.status !== battleEventStatusType.stop);
|
return modalType === TYPE_MODIFY && (content?.status !== battleEventStatusType.stop);
|
||||||
@@ -326,14 +329,14 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
</FormRowGroup>
|
</FormRowGroup>
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
<FormLabel>라운드 시간</FormLabel>
|
{/*<FormLabel>라운드 시간</FormLabel>*/}
|
||||||
<SelectInput value={resultData.config_id} onChange={handleConfigChange} disabled={!isView('config')} width="200px">
|
{/*<SelectInput value={resultData.config_id} onChange={handleConfigChange} disabled={!isView('config')} width="200px">*/}
|
||||||
{configData && configData?.map((data, index) => (
|
{/* {configData && configData?.map((data, index) => (*/}
|
||||||
<option key={index} value={data.id}>
|
{/* <option key={index} value={data.id}>*/}
|
||||||
{data.desc}({data.id})
|
{/* {data.desc}({data.id})*/}
|
||||||
</option>
|
{/* </option>*/}
|
||||||
))}
|
{/* ))}*/}
|
||||||
</SelectInput>
|
{/*</SelectInput>*/}
|
||||||
<FormLabel>라운드 수</FormLabel>
|
<FormLabel>라운드 수</FormLabel>
|
||||||
<SelectInput value={resultData.round_count} onChange={e => setResultData({ ...resultData, round_count: e.target.value })} disabled={!isView('round')} width="100px">
|
<SelectInput value={resultData.round_count} onChange={e => setResultData({ ...resultData, round_count: e.target.value })} disabled={!isView('round')} width="100px">
|
||||||
{battleEventRoundCount.map((data, index) => (
|
{battleEventRoundCount.map((data, index) => (
|
||||||
@@ -344,11 +347,19 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
</SelectInput>
|
</SelectInput>
|
||||||
</FormRowGroup>
|
</FormRowGroup>
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
<FormLabel>배정 포드</FormLabel>
|
{/*<FormLabel>배정 포드</FormLabel>*/}
|
||||||
<SelectInput value={resultData.reward_group_id} onChange={e => setResultData({ ...resultData, reward_group_id: e.target.value })} disabled={!isView('reward')} width="200px">
|
{/*<SelectInput value={resultData.reward_group_id} onChange={e => setResultData({ ...resultData, reward_group_id: e.target.value })} disabled={!isView('reward')} width="200px">*/}
|
||||||
{rewardData && rewardData?.map((data, index) => (
|
{/* {rewardData && rewardData?.map((data, index) => (*/}
|
||||||
<option key={index} value={data.group_id}>
|
{/* <option key={index} value={data.group_id}>*/}
|
||||||
{data.desc}({data.group_id})
|
{/* {data.desc}({data.group_id})*/}
|
||||||
|
{/* </option>*/}
|
||||||
|
{/* ))}*/}
|
||||||
|
{/*</SelectInput>*/}
|
||||||
|
<FormLabel>게임 모드</FormLabel>
|
||||||
|
<SelectInput value={resultData.game_mode_id} onChange={e => setResultData({ ...resultData, game_mode_id: e.target.value })} disabled={!isView('mode')} width="200px">
|
||||||
|
{gameModeData && gameModeData?.map((data, index) => (
|
||||||
|
<option key={index} value={data.id}>
|
||||||
|
{data.desc}({data.id})
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</SelectInput>
|
</SelectInput>
|
||||||
@@ -421,6 +432,7 @@ export const initData = {
|
|||||||
reward_group_id: 1,
|
reward_group_id: 1,
|
||||||
round_count: 1,
|
round_count: 1,
|
||||||
hot_time: 1,
|
hot_time: 1,
|
||||||
|
game_mode_id: 1,
|
||||||
event_start_dt: '',
|
event_start_dt: '',
|
||||||
event_end_dt: ''
|
event_end_dt: ''
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { useState, useRef, Fragment, useEffect } from 'react';
|
import { useState, useRef, Fragment, useEffect } from 'react';
|
||||||
|
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import CheckBox from '../../common/input/CheckBox';
|
import CheckBox from '../common/input/CheckBox';
|
||||||
import Modal from '../../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
|
|
||||||
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../../styles/Components';
|
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../styles/Components';
|
||||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||||
import DatePickerComponent from '../../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
import { HourList, MinuteList } from '../../../assets/data';
|
import { HourList, MinuteList } from '../../assets/data';
|
||||||
import { NoticeModify } from '../../../apis';
|
import { NoticeModify } from '../../apis';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { convertKTC, convertKTCDate } from '../../../utils';
|
import { convertKTC, convertKTCDate } from '../../utils';
|
||||||
import { languageType } from '../../../assets/data/options';
|
import { languageType } from '../../assets/data/options';
|
||||||
import { CopyBtn } from '../../../styles/ModuleComponents';
|
import { CopyBtn } from '../../styles/ModuleComponents';
|
||||||
|
|
||||||
const BoardInfoModal = ({ detailView, setDetailView, content, id, setIsCopyData, openRegistModal, userInfo }) => {
|
const BoardInfoModal = ({ detailView, setDetailView, content, id, setIsCopyData, openRegistModal, userInfo }) => {
|
||||||
let viewOnly = userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 17); // 조회만 가능 권한
|
let viewOnly = userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 17); // 조회만 가능 권한
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { useState, useRef, Fragment, useEffect } from 'react';
|
import { useState, useRef, Fragment, useEffect } from 'react';
|
||||||
|
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import CheckBox from '../../common/input/CheckBox';
|
import CheckBox from '../common/input/CheckBox';
|
||||||
import Modal from '../../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
|
|
||||||
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../../styles/Components';
|
import { Title, BtnWrapper, SelectInput, TextInput, DatePickerWrapper, InputLabel, Textarea, ModalText, SearchBarAlert } from '../../styles/Components';
|
||||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||||
import DatePickerComponent from '../../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
import { HourList, MinuteList, modalTypes } from '../../../assets/data';
|
import { HourList, MinuteList, modalTypes } from '../../assets/data';
|
||||||
import { NoticeRegist } from '../../../apis';
|
import { NoticeRegist } from '../../apis';
|
||||||
import { convertKTC, convertKTCDate } from '../../../utils';
|
import { convertKTC, convertKTCDate } from '../../utils';
|
||||||
import DynamicModal from '../../common/modal/DynamicModal';
|
import DynamicModal from '../common/modal/DynamicModal';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
BoxWrapper, InputGroup2,
|
BoxWrapper, InputGroup2,
|
||||||
@@ -20,8 +20,8 @@ import {
|
|||||||
NoticeInputRow, NoticeInputRow2,
|
NoticeInputRow, NoticeInputRow2,
|
||||||
NoticeRegistGroup,
|
NoticeRegistGroup,
|
||||||
RegistInputItem, RepeatTime, SubText, SubTextRow, TitleLang,
|
RegistInputItem, RepeatTime, SubText, SubTextRow, TitleLang,
|
||||||
} from '../../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { languageType } from '../../../assets/data/options';
|
import { languageType } from '../../assets/data/options';
|
||||||
|
|
||||||
const BoardRegistModal = ({ registView, setRegistView, copyData, setIsCopyData }) => {
|
const BoardRegistModal = ({ registView, setRegistView, copyData, setIsCopyData }) => {
|
||||||
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
const [doubleSubmitFlag, setDoubleSubmitFlag] = useState(false);
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
import { useState, useEffect, Fragment } from 'react';
|
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 } 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, currencyType } from '../../../assets/data';
|
import { authType, benItems, commonStatus, currencyItemCode } 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 { 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 { useLoading } from '../../context/LoadingProvider';
|
||||||
import { useAlert } from '../../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../../assets/data/types';
|
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);
|
||||||
@@ -177,7 +177,7 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||||
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||||
} else {
|
} else {
|
||||||
const name = currencyType.find(well => well.value === resource).name;
|
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -447,7 +447,7 @@ const EventDetailModal = ({ detailView, handleDetailView, content, setDetailData
|
|||||||
<td>
|
<td>
|
||||||
<DetailInputItem>
|
<DetailInputItem>
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isReadOnly}>
|
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isReadOnly}>
|
||||||
{currencyType.map((data, index) => (
|
{currencyItemCode.map((data, index) => (
|
||||||
<option key={index} value={data.value}>
|
<option key={index} value={data.value}>
|
||||||
{data.name}
|
{data.name}
|
||||||
</option>
|
</option>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useState, Fragment, useEffect } from 'react';
|
import { 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 Loading from '../common/Loading';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
BtnWrapper,
|
BtnWrapper,
|
||||||
SearchBarAlert, SelectInput, InputLabel,
|
SearchBarAlert, SelectInput, InputLabel,
|
||||||
} from '../../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
@@ -19,23 +19,23 @@ import {
|
|||||||
FormRowGroup,
|
FormRowGroup,
|
||||||
NoticeInputRow2,
|
NoticeInputRow2,
|
||||||
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
NoticeInputItem2, BoxWrapper, FormStatusBar, FormStatusLabel, FormStatusWarning, FormButtonContainer,
|
||||||
} from '../../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { modalTypes } from '../../../assets/data';
|
import { modalTypes } from '../../assets/data';
|
||||||
import {DynamicModal, Modal, DateTimeRangePicker} from '../../common';
|
import {DynamicModal, Modal, DateTimeRangePicker} from '../common';
|
||||||
import { LandAuctionModify, LandAuctionSingleRegist } from '../../../apis';
|
import { LandAuctionModify, LandAuctionSingleRegist } from '../../apis';
|
||||||
import {
|
import {
|
||||||
AUCTION_MIN_MINUTE_TIME,
|
AUCTION_MIN_MINUTE_TIME,
|
||||||
ONE_MINUTE,
|
ONE_MINUTE,
|
||||||
ONE_MINUTE_MS,
|
ONE_MINUTE_MS,
|
||||||
TYPE_MODIFY,
|
TYPE_MODIFY,
|
||||||
TYPE_REGISTRY,
|
TYPE_REGISTRY,
|
||||||
} from '../../../assets/data/adminConstants';
|
} from '../../assets/data/adminConstants';
|
||||||
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../../assets/data';
|
import { landAuctionStatus, landAuctionStatusType, languageType, CurrencyType } from '../../assets/data';
|
||||||
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 { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../../assets/data/types';
|
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();
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import RadioInput from '../../common/input/Radio';
|
import RadioInput from '../common/input/Radio';
|
||||||
import React, { useState, useEffect, Fragment } from 'react';
|
import React, { useState, useEffect, Fragment } from 'react';
|
||||||
import CheckBox from '../../common/input/CheckBox';
|
import CheckBox from '../common/input/CheckBox';
|
||||||
|
|
||||||
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea} from '../../../styles/Components';
|
import { Title, SelectInput, BtnWrapper, TextInput, Label, InputLabel, DatePickerWrapper, Textarea} 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 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 DatePickerComponent from '../../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
import MailRegistUploadBtn from '../MailRegistUploadBtn';
|
import MailRegistUploadBtn from '../ServiceManage/MailRegistUploadBtn';
|
||||||
import {
|
import {
|
||||||
authType,
|
authType,
|
||||||
benItems, commonStatus,
|
benItems, commonStatus,
|
||||||
@@ -18,20 +18,20 @@ import {
|
|||||||
mailType,
|
mailType,
|
||||||
MinuteList,
|
MinuteList,
|
||||||
userType,
|
userType,
|
||||||
currencyType,
|
currencyItemCode,
|
||||||
} from '../../../assets/data';
|
} from '../../assets/data';
|
||||||
import { MailCaliumTotalView, MailIsItem, MailModify } from '../../../apis';
|
import { MailCaliumTotalView, MailIsItem, MailModify } from '../../apis';
|
||||||
|
|
||||||
import { authList } from '../../../store/authList';
|
import { authList } from '../../store/authList';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { convertKTC, convertKTCDate } from '../../../utils';
|
import { convertKTC, convertKTCDate } from '../../utils';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useDataFetch } from '../../../hooks/hook';
|
import { useDataFetch } from '../../hooks/hook';
|
||||||
import { useAlert } from '../../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { useLoading } from '../../../context/LoadingProvider';
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
import { alertTypes, currencyCodeTypes } from '../../../assets/data/types';
|
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
||||||
import { userType2 } from '../../../assets/data/options';
|
import { userType2 } from '../../assets/data/options';
|
||||||
import { STORAGE_MAIL_COPY } from '../../../assets/data/adminConstants';
|
import { STORAGE_MAIL_COPY } from '../../assets/data/adminConstants';
|
||||||
|
|
||||||
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
@@ -219,7 +219,7 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const name = currencyType.find(well => well.value === resource).name;
|
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -618,7 +618,7 @@ const MailDetailModal = ({ detailView, handleDetailView, content }) => {
|
|||||||
<td>
|
<td>
|
||||||
<InputItem>
|
<InputItem>
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isView}>
|
<SelectInput onChange={e => setResource(e.target.value)} value={resource} disabled={isView}>
|
||||||
{currencyType.map((data, index) => (
|
{currencyItemCode.map((data, index) => (
|
||||||
<option key={index} value={data.value}>
|
<option key={index} value={data.value}>
|
||||||
{data.name}
|
{data.name}
|
||||||
</option>
|
</option>
|
||||||
611
src/components/modal/MenuBannerDetailModal.js
Normal file
611
src/components/modal/MenuBannerDetailModal.js
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
import React, { useState, useEffect, Fragment } from 'react';
|
||||||
|
import styled, { css, keyframes } from 'styled-components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Title,
|
||||||
|
SelectInput,
|
||||||
|
BtnWrapper,
|
||||||
|
TextInput,
|
||||||
|
Label,
|
||||||
|
InputLabel,
|
||||||
|
Textarea,
|
||||||
|
SearchBarAlert,
|
||||||
|
ButtonGroupWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
import Button from '../common/button/Button';
|
||||||
|
import Modal from '../common/modal/Modal';
|
||||||
|
import { EventIsItem, EventModify, MenuBannerModify } from '../../apis';
|
||||||
|
|
||||||
|
import { authList } from '../../store/authList';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
||||||
|
import {
|
||||||
|
DetailInputItem, DetailInputRow,
|
||||||
|
DetailModalWrapper, RegistGroup, DetailRegistInfo, DetailState, FormRowGroup, FormLabel, FormInput,
|
||||||
|
} from '../../styles/ModuleComponents';
|
||||||
|
import { convertKTC, combineDateTime, timeDiffMinute, convertKTCDate } from '../../utils';
|
||||||
|
import DateTimeInput from '../common/input/DateTimeInput';
|
||||||
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes, battleEventStatusType, languageNames } from '../../assets/data/types';
|
||||||
|
import { Tabs, Image as AntImage, Spin } from 'antd';
|
||||||
|
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
||||||
|
import { AntButton, DateTimeRangePicker, DetailLayout, SingleTimePicker } from '../common';
|
||||||
|
import AnimatedTabs from '../common/control/AnimatedTabs';
|
||||||
|
|
||||||
|
function renderImageContent(imageData) {
|
||||||
|
if (!imageData) {
|
||||||
|
return <NoImagePlaceholder>이미지가 없습니다</NoImagePlaceholder>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ImageWrapper>
|
||||||
|
<AntImage
|
||||||
|
src={imageData.title}
|
||||||
|
alt={`${imageData.language} 배너 이미지`}
|
||||||
|
style={{ width: '100%', maxHeight: '300px', objectFit: 'contain' }}
|
||||||
|
placeholder={
|
||||||
|
<AntImage
|
||||||
|
preview={false}
|
||||||
|
src={imageData.title}
|
||||||
|
width={300}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
// preview={{
|
||||||
|
// mask: '미리보기',
|
||||||
|
// maskClassName: 'custom-mask',
|
||||||
|
// }}
|
||||||
|
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
|
||||||
|
/>
|
||||||
|
</ImageWrapper>
|
||||||
|
{imageData.content &&
|
||||||
|
<ImageUrlInfo>
|
||||||
|
<UrlLink
|
||||||
|
href={imageData.content}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{imageData.content}
|
||||||
|
</UrlLink>
|
||||||
|
</ImageUrlInfo>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||||
|
const userInfo = useRecoilValue(authList);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
const {withLoading} = useLoading();
|
||||||
|
const {showModal, showToast} = useAlert();
|
||||||
|
|
||||||
|
const id = content && content.id;
|
||||||
|
const updateAuth = userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.menuBannerUpdate);
|
||||||
|
|
||||||
|
const [time, setTime] = useState({
|
||||||
|
start_hour: '00',
|
||||||
|
start_min: '00',
|
||||||
|
end_hour: '00',
|
||||||
|
end_min: '00',
|
||||||
|
}); //시간 정보
|
||||||
|
|
||||||
|
const [resultData, setResultData] = useState(initData);
|
||||||
|
const [activeLanguage, setActiveLanguage] = useState('KO');
|
||||||
|
// 이미지 프리로드를 위한 상태
|
||||||
|
const [allImagesLoaded, setAllImagesLoaded] = useState(false);
|
||||||
|
const [showTabContent, setShowTabContent] = useState(false);
|
||||||
|
const [loadedImages, setLoadedImages] = useState([]);
|
||||||
|
const [totalImageCount, setTotalImageCount] = useState(0);
|
||||||
|
|
||||||
|
const [tabItems, setTabItems] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(content){
|
||||||
|
console.log(content);
|
||||||
|
const start_dt_KTC = convertKTCDate(content.start_dt);
|
||||||
|
const end_dt_KTC = convertKTCDate(content.end_dt);
|
||||||
|
|
||||||
|
setResultData({
|
||||||
|
id: content.id,
|
||||||
|
title: content.title,
|
||||||
|
start_dt: start_dt_KTC,
|
||||||
|
end_dt: end_dt_KTC,
|
||||||
|
status: content.status,
|
||||||
|
order_id: content.order_id,
|
||||||
|
is_link: content.is_link,
|
||||||
|
image_list: content.image_list,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTime({ ...time,
|
||||||
|
start_hour: String(start_dt_KTC.getHours()).padStart(2, '0'),
|
||||||
|
start_min: String(start_dt_KTC.getMinutes()).padStart(2, '0'),
|
||||||
|
end_hour: String(end_dt_KTC.getHours()).padStart(2, '0'),
|
||||||
|
end_min: String(end_dt_KTC.getMinutes()).padStart(2, '0')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (content && content.image_list) {
|
||||||
|
// 초기화
|
||||||
|
setAllImagesLoaded(false);
|
||||||
|
setShowTabContent(false);
|
||||||
|
setLoadedImages([]);
|
||||||
|
|
||||||
|
// 이미지 개수 설정
|
||||||
|
setTotalImageCount(content.image_list ? content.image_list.length : 0);
|
||||||
|
|
||||||
|
// 첫 번째 언어를 활성 언어로 설정
|
||||||
|
if (content.image_list && content.image_list.length > 0) {
|
||||||
|
setActiveLanguage(content.image_list[0].language);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 동적으로 탭 아이템 생성
|
||||||
|
const newTabItems = content.image_list ? content.image_list.map(imageData => ({
|
||||||
|
key: imageData.language,
|
||||||
|
label: languageNames[imageData.language] || imageData.language,
|
||||||
|
children: (
|
||||||
|
<ImageContainer>
|
||||||
|
{renderImageContent(imageData)}
|
||||||
|
</ImageContainer>
|
||||||
|
)
|
||||||
|
})) : [];
|
||||||
|
|
||||||
|
setTabItems(newTabItems);
|
||||||
|
|
||||||
|
// 모든 이미지 프리로딩 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
preloadAllImages();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
const preloadAllImages = () => {
|
||||||
|
if (!content || !content.image_list || content.image_list.length === 0) {
|
||||||
|
// 이미지가 없는 경우 바로 로딩 완료 처리
|
||||||
|
// console.log('이미지가 없습니다. 로딩 완료 처리합니다.');
|
||||||
|
setAllImagesLoaded(true);
|
||||||
|
setShowTabContent(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(`총 ${content.image_list.length}개의 이미지 로딩을 시작합니다.`);
|
||||||
|
|
||||||
|
// 이미지 개수가 0이면 로딩 완료 처리
|
||||||
|
if (content.image_list.length === 0) {
|
||||||
|
setAllImagesLoaded(true);
|
||||||
|
setShowTabContent(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadedCount = 0;
|
||||||
|
|
||||||
|
// 이미지 로드 완료 이벤트 핸들러
|
||||||
|
const handleImageLoad = (url) => {
|
||||||
|
loadedCount++;
|
||||||
|
// console.log(`이미지 로드 완료 (${loadedCount}/${content.image_list.length}): ${url}`);
|
||||||
|
|
||||||
|
// 모든 이미지가 로드되었는지 확인
|
||||||
|
if (loadedCount >= content.image_list.length) {
|
||||||
|
// console.log('모든 이미지 로딩 완료!');
|
||||||
|
setAllImagesLoaded(true);
|
||||||
|
setShowTabContent(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 각 이미지에 대해 프리로드 객체 생성
|
||||||
|
content.image_list.forEach(img => {
|
||||||
|
if (img.title) {
|
||||||
|
// console.log(`이미지 로딩 시작: ${img.title}`);
|
||||||
|
const image = new Image();
|
||||||
|
image.onload = () => handleImageLoad(img.title);
|
||||||
|
image.onerror = () => {
|
||||||
|
console.log(`이미지 로드 실패: ${img.title}`);
|
||||||
|
handleImageLoad(img.title); // 오류 시에도 카운트
|
||||||
|
};
|
||||||
|
image.src = img.title; // src 속성은 onload/onerror 핸들러 설정 후에 설정
|
||||||
|
} else {
|
||||||
|
// console.log('이미지 URL이 없습니다.');
|
||||||
|
handleImageLoad('empty'); // URL이 없는 경우에도 카운트
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 안전장치: 5초 후에도 로딩이 완료되지 않으면 강제로 완료 처리
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!allImagesLoaded) {
|
||||||
|
// console.log('시간 초과로 로딩 강제 완료');
|
||||||
|
setAllImagesLoaded(true);
|
||||||
|
setShowTabContent(true);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 날짜 처리
|
||||||
|
// const handleDateChange = (data, type) => {
|
||||||
|
// const date = new Date(data);
|
||||||
|
// setResultData({
|
||||||
|
// ...resultData,
|
||||||
|
// [`${type}_dt`]: combineDateTime(date, time[`${type}_hour`], time[`${type}_min`]),
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 시간 처리
|
||||||
|
const handleTimeChange = (e, type) => {
|
||||||
|
const { id, value } = e.target;
|
||||||
|
const newTime = { ...time, [`${type}_${id}`]: value };
|
||||||
|
setTime(newTime);
|
||||||
|
|
||||||
|
const date = resultData[`${type}_dt`] ? new Date(resultData[`${type}_dt`]) : new Date();
|
||||||
|
|
||||||
|
setResultData({
|
||||||
|
...resultData,
|
||||||
|
[`${type}_dt`]: combineDateTime(date, newTime[`${type}_hour`], newTime[`${type}_min`]),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDateChange = {
|
||||||
|
start: (date) => {
|
||||||
|
setResultData(prev => ({ ...prev, start_dt: date }));
|
||||||
|
},
|
||||||
|
end: (date) => {
|
||||||
|
setResultData(prev => ({ ...prev, end_dt: date }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 확인 버튼 후 다 초기화
|
||||||
|
const handleReset = () => {
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkCondition = () => {
|
||||||
|
return (
|
||||||
|
(resultData.start_dt.length !== 0) &&
|
||||||
|
(resultData.end_dt.length !== 0) &&
|
||||||
|
resultData.title !== '' &&
|
||||||
|
resultData.order_id !== ''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 탭 변경 핸들러
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
setActiveLanguage(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (type, param = null) => {
|
||||||
|
switch (type) {
|
||||||
|
case "submit":
|
||||||
|
if (!checkCondition()) return;
|
||||||
|
|
||||||
|
showModal('MENU_BANNER_UPDATE_SAVE', {
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => handleSubmit('updateConfirm')
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "updateConfirm":
|
||||||
|
withLoading( async () => {
|
||||||
|
return await MenuBannerModify(token, id, resultData);
|
||||||
|
}).catch(error => {
|
||||||
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
|
}).finally(() => {
|
||||||
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||||
|
handleDetailView();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailState = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case commonStatus.wait:
|
||||||
|
return <DetailState>대기</DetailState>;
|
||||||
|
case commonStatus.running:
|
||||||
|
return <DetailState>진행중</DetailState>;
|
||||||
|
case commonStatus.finish:
|
||||||
|
return <DetailState result={commonStatus.finish}>만료</DetailState>;
|
||||||
|
case commonStatus.fail:
|
||||||
|
return <DetailState result={commonStatus.fail}>실패</DetailState>;
|
||||||
|
case commonStatus.delete:
|
||||||
|
return <DetailState result={commonStatus.delete}>삭제</DetailState>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//true 수정불가, false 수정가능
|
||||||
|
const isView = (fieldName) => {
|
||||||
|
if (!updateAuth) return false;
|
||||||
|
|
||||||
|
if (fieldName === 'editButton') {
|
||||||
|
// updateAuth가 없거나 FINISH 상태면 수정 버튼 숨김 (false 반환)
|
||||||
|
return !updateAuth || content?.status === commonStatus.finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (content?.status) {
|
||||||
|
case commonStatus.running:
|
||||||
|
// RUNNING 상태일 때는 end_dt와 order_id만 수정 가능
|
||||||
|
return fieldName !== 'date' && fieldName !== 'order_id';
|
||||||
|
case commonStatus.wait:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemGroups = [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'text',
|
||||||
|
key: 'title',
|
||||||
|
label: '제목',
|
||||||
|
disabled: !isView('title'),
|
||||||
|
width: '250px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 0,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'number',
|
||||||
|
key: 'order_id',
|
||||||
|
label: '순서',
|
||||||
|
disabled: !isView('order_id'),
|
||||||
|
width: '100px',
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 1,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'status',
|
||||||
|
key: 'status',
|
||||||
|
label: '상태',
|
||||||
|
value: resultData.status,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'dateRange',
|
||||||
|
key: 'dateRange',
|
||||||
|
keys: {start: 'start_dt', end: 'end_dt'},
|
||||||
|
label: '기간',
|
||||||
|
disabled: !isView('date'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal min="960px" $view={detailView}>
|
||||||
|
<Title $align="center">배너 상세 정보</Title>
|
||||||
|
<DetailLayout
|
||||||
|
itemGroups={itemGroups}
|
||||||
|
formData={resultData}
|
||||||
|
onChange={setResultData}
|
||||||
|
disabled={!updateAuth}
|
||||||
|
columnCount={4}
|
||||||
|
/>
|
||||||
|
{/*<DetailModalWrapper>*/}
|
||||||
|
{/* {content &&*/}
|
||||||
|
{/* <RegistGroup>*/}
|
||||||
|
{/* <FormRowGroup>*/}
|
||||||
|
{/* <DetailInputItem>*/}
|
||||||
|
{/* <FormLabel>제목</FormLabel>*/}
|
||||||
|
{/* <FormInput*/}
|
||||||
|
{/* type="text"*/}
|
||||||
|
{/* value={content.title}*/}
|
||||||
|
{/* disabled={isView('title')}*/}
|
||||||
|
{/* onChange={e => setResultData({ ...resultData, title: e.target.value })}*/}
|
||||||
|
{/* width="300px"*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* </DetailInputItem>*/}
|
||||||
|
{/* <DetailInputItem>*/}
|
||||||
|
{/* <FormLabel>순서</FormLabel>*/}
|
||||||
|
{/* <FormInput*/}
|
||||||
|
{/* placeholder="순서번호"*/}
|
||||||
|
{/* type="number"*/}
|
||||||
|
{/* value={content.order_id}*/}
|
||||||
|
{/* disabled={isView('order_id')}*/}
|
||||||
|
{/* onChange={e => setResultData({ ...resultData, order_id: e.target.value })}*/}
|
||||||
|
{/* width="200px"*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* </DetailInputItem>*/}
|
||||||
|
{/* </FormRowGroup>*/}
|
||||||
|
{/* <FormRowGroup>*/}
|
||||||
|
{/* <DateTimeRangePicker*/}
|
||||||
|
{/* label="예약기간"*/}
|
||||||
|
{/* startDate={resultData.start_dt}*/}
|
||||||
|
{/* endDate={resultData.end_dt}*/}
|
||||||
|
{/* onStartDateChange={handleDateChange.start}*/}
|
||||||
|
{/* onEndDateChange={handleDateChange.end}*/}
|
||||||
|
{/* pastDate={new Date()}*/}
|
||||||
|
{/* disabled={isView('date')}*/}
|
||||||
|
{/* startLabel="시작 일자"*/}
|
||||||
|
{/* endLabel="종료 일자"*/}
|
||||||
|
{/* // reset={resetDateTime}*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* </FormRowGroup>*/}
|
||||||
|
{/* <FormRowGroup>*/}
|
||||||
|
{/* <DetailInputItem>*/}
|
||||||
|
{/* <FormLabel>상태</FormLabel>*/}
|
||||||
|
{/* <div>{detailState(content.status)}</div>*/}
|
||||||
|
{/* </DetailInputItem>*/}
|
||||||
|
{/* </FormRowGroup>*/}
|
||||||
|
{/* {content.image_list && content.image_list.length > 0 && (*/}
|
||||||
|
{/* <FormRowGroup style={{display: 'flex', justifyContent: 'center', width: '100%'}}>*/}
|
||||||
|
{/* <DetailInputItem style={{width: '100%'}}>*/}
|
||||||
|
{/* {!showTabContent ? (*/}
|
||||||
|
{/* <LoadingContainer>*/}
|
||||||
|
{/* <Spin size="large" tip="이미지 로딩 중..." />*/}
|
||||||
|
{/* </LoadingContainer>*/}
|
||||||
|
{/* ) : (*/}
|
||||||
|
{/* <ContentWrapper $isLoaded={showTabContent}>*/}
|
||||||
|
{/* <AnimatedTabs*/}
|
||||||
|
{/* items={tabItems}*/}
|
||||||
|
{/* activeKey={activeLanguage}*/}
|
||||||
|
{/* onChange={handleTabChange}*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* </ContentWrapper>*/}
|
||||||
|
{/* )}*/}
|
||||||
|
|
||||||
|
{/* </DetailInputItem>*/}
|
||||||
|
{/* </FormRowGroup>*/}
|
||||||
|
{/* )}*/}
|
||||||
|
|
||||||
|
{/* </RegistGroup>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/*</DetailModalWrapper>*/}
|
||||||
|
<ButtonGroupWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||||
|
<AntButton
|
||||||
|
text="확인"
|
||||||
|
theme="line"
|
||||||
|
name="확인버튼"
|
||||||
|
onClick={() => {
|
||||||
|
handleDetailView();
|
||||||
|
handleReset();
|
||||||
|
setDetailData('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isView('editButton') && (
|
||||||
|
<AntButton
|
||||||
|
type="submit"
|
||||||
|
text="수정"
|
||||||
|
id="수정버튼"
|
||||||
|
theme={checkCondition() ? 'primary' : 'disable'}
|
||||||
|
onClick={() => handleSubmit('submit')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ButtonGroupWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MenuBannerDetailModal;
|
||||||
|
|
||||||
|
const initData = {
|
||||||
|
title: '',
|
||||||
|
is_link: false,
|
||||||
|
start_dt: '',
|
||||||
|
end_dt: '',
|
||||||
|
image_list: [
|
||||||
|
{ language: 'KO', content: '', title: '' },
|
||||||
|
{ language: 'EN', content: '', title: '' },
|
||||||
|
{ language: 'JA', content: '', title: '' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledTabs = styled(Tabs)`
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.ant-tabs-nav {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-wrap {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-content-holder {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageContainer = styled.div`
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageUrlInfo = styled.div`
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-all;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UrlLink = styled.a`
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const NoImagePlaceholder = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
color: #8c8c8c;
|
||||||
|
border-radius: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 로딩 인디케이터를 위한 컨테이너
|
||||||
|
const LoadingContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 컨텐츠 래퍼 - 로딩 상태에 따라 가시성 설정
|
||||||
|
const ContentWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
opacity: ${props => props.$isLoaded ? 1 : 0};
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
height: ${props => props.$isLoaded ? 'auto' : '0'};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
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 Loading from '../common/Loading';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
BtnWrapper,
|
BtnWrapper,
|
||||||
SearchBarAlert, SelectInput,
|
SearchBarAlert, SelectInput,
|
||||||
} from '../../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormInput,
|
FormInput,
|
||||||
@@ -18,21 +18,21 @@ import {
|
|||||||
FormStatusLabel,
|
FormStatusLabel,
|
||||||
FormStatusWarning,
|
FormStatusWarning,
|
||||||
FormButtonContainer,
|
FormButtonContainer,
|
||||||
} from '../../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { modalTypes } from '../../../assets/data';
|
import { modalTypes } from '../../assets/data';
|
||||||
import { DynamicModal, 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 { useModal } from '../../hooks/hook';
|
||||||
import { convertKTCDate } from '../../../utils';
|
import { convertKTCDate } from '../../utils';
|
||||||
import {
|
import {
|
||||||
battleEventHotTime,
|
battleEventHotTime,
|
||||||
battleEventRoundCount,
|
battleEventRoundCount,
|
||||||
battleEventStatus,
|
battleEventStatus,
|
||||||
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 { battleEventStatusType } from '../../assets/data/types';
|
||||||
import { isValidDayRange } from '../../../utils/date';
|
import { isValidDayRange } from '../../utils/date';
|
||||||
|
|
||||||
const MenuBannerModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
|
const MenuBannerModal = ({ modalType, detailView, handleDetailView, content, setDetailData, configData, rewardData }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
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 Loading from '../common/Loading';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
BtnWrapper,
|
BtnWrapper,
|
||||||
SearchBarAlert, SelectInput,
|
SearchBarAlert, SelectInput,
|
||||||
} from '../../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormInput,
|
FormInput,
|
||||||
@@ -18,19 +18,19 @@ import {
|
|||||||
FormStatusLabel,
|
FormStatusLabel,
|
||||||
FormStatusWarning,
|
FormStatusWarning,
|
||||||
FormButtonContainer, FormGroup, FormItemGroup, SubText,
|
FormButtonContainer, FormGroup, FormItemGroup, SubText,
|
||||||
} from '../../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import { modalTypes } from '../../../assets/data';
|
import { modalTypes } from '../../assets/data';
|
||||||
import { DynamicModal, 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 { 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 { alertTypes, 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 { useLoading } from '../../context/LoadingProvider';
|
||||||
import { useAlert } from '../../../context/AlertProvider';
|
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();
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem, ModalText, SearchBarAlert } from '../../../styles/Components';
|
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem, ModalText, SearchBarAlert } from '../../styles/Components';
|
||||||
|
|
||||||
import { RepostReplyMessage } from '../../../apis/Report';
|
import { RepostReplyMessage } from '../../apis/Report';
|
||||||
|
|
||||||
import Modal from '../../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
import CloseIcon from '../../assets/img/icon/icon-close.png';
|
||||||
|
|
||||||
const ReportListAnswerModal = ({ answerView, setAnswerView, detailData, replyData, pkId, skId }) => {
|
const ReportListAnswerModal = ({ answerView, setAnswerView, detailData, replyData, pkId, skId }) => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem } from '../../../styles/Components';
|
import { Title, BtnWrapper, TextInput, Label, Textarea, InputItem } from '../../styles/Components';
|
||||||
|
|
||||||
import Modal from '../../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { convertKTC } from '../../../utils';
|
import { convertKTC } from '../../utils';
|
||||||
|
|
||||||
const ReportListDetailModal = ({ detailView, handleDetailView, handleReply, detailData, replyData, replyAuth }) => {
|
const ReportListDetailModal = ({ detailView, handleDetailView, handleReply, detailData, replyData, replyAuth }) => {
|
||||||
const [dataList, setDataList] = useState([]);
|
const [dataList, setDataList] = useState([]);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import { Title, TableStyle, BtnWrapper, TextInput } from '../../../styles/Components';
|
import { Title, TableStyle, BtnWrapper, TextInput } from '../../styles/Components';
|
||||||
import Modal from '../../common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { convertKTC } from '../../../utils';
|
import { convertKTC } from '../../utils';
|
||||||
import { blockPeriod, blockSanctions, blockStatus, blockType, commonStatus } from '../../../assets/data';
|
import { blockPeriod, blockSanctions, blockStatus, blockType, commonStatus } from '../../assets/data';
|
||||||
|
|
||||||
const UserBlockDetailModal = ({ stateModal, handleModal, data }) => {
|
const UserBlockDetailModal = ({ stateModal, handleModal, data }) => {
|
||||||
const [history, setHistory] = useState();
|
const [history, setHistory] = useState();
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import CheckBox from '../../common/input/CheckBox';
|
import CheckBox from '../common/input/CheckBox';
|
||||||
import { SearchBarLayout } from '../../common/SearchBar';
|
import { SearchBarLayout } from '../common/SearchBar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrentPage }) => {
|
const AdminViewSearchBar = ({ handleSearch, groupList, setResultData, setCurrentPage }) => {
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { BattleEventView } from '../../../apis/Battle';
|
import { BattleEventView } from '../../apis/Battle';
|
||||||
import {
|
import {
|
||||||
battleEventHotTime,
|
battleEventHotTime,
|
||||||
battleEventRoundCount,
|
battleEventRoundCount,
|
||||||
battleEventStatus,
|
battleEventStatus,
|
||||||
battleRepeatType,
|
battleRepeatType,
|
||||||
eventSearchType,
|
eventSearchType,
|
||||||
} from '../../../assets/data/options';
|
} from '../../assets/data/options';
|
||||||
|
|
||||||
export const useBattleEventSearch = (token, initialPageSize) => {
|
export const useBattleEventSearch = (token, initialPageSize) => {
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
@@ -126,7 +126,7 @@ export const useBattleEventSearch = (token, initialPageSize) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const BattleEventSearchBar = ({ searchParams, onSearch, onReset, configData, rewardData }) => {
|
const BattleEventSearchBar = ({ searchParams, onSearch, onReset, configData, rewardData, gameModeData }) => {
|
||||||
const handleSubmit = event => {
|
const handleSubmit = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -153,22 +153,33 @@ const BattleEventSearchBar = ({ searchParams, onSearch, onReset, configData, rew
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</>,
|
</>,
|
||||||
<>
|
<>
|
||||||
<InputLabel>라운드 시간</InputLabel>
|
{/*<InputLabel>라운드 시간</InputLabel>*/}
|
||||||
|
{/*<InputGroup>*/}
|
||||||
|
{/* <SelectInput value={searchParams.configId} onChange={e => onSearch({ configId: e.target.value })}>*/}
|
||||||
|
{/* <option value="ALL">전체</option>*/}
|
||||||
|
{/* {configData?.map((data, index) => (*/}
|
||||||
|
{/* <option key={index} value={data.id}>*/}
|
||||||
|
{/* {data.desc}*/}
|
||||||
|
{/* </option>*/}
|
||||||
|
{/* ))}*/}
|
||||||
|
{/* </SelectInput>*/}
|
||||||
|
{/*</InputGroup>*/}
|
||||||
|
{/*<InputLabel>배정 포드</InputLabel>*/}
|
||||||
|
{/*<InputGroup>*/}
|
||||||
|
{/* <SelectInput value={searchParams.rewardId} onChange={e => onSearch({ rewardId: e.target.value })}>*/}
|
||||||
|
{/* <option value='ALL'>전체</option>*/}
|
||||||
|
{/* {rewardData?.map((data, index) => (*/}
|
||||||
|
{/* <option key={index} value={data.id}>*/}
|
||||||
|
{/* {data.desc}*/}
|
||||||
|
{/* </option>*/}
|
||||||
|
{/* ))}*/}
|
||||||
|
{/* </SelectInput>*/}
|
||||||
|
{/*</InputGroup>*/}
|
||||||
|
<InputLabel>게임모드</InputLabel>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<SelectInput value={searchParams.configId} onChange={e => onSearch({ configId: e.target.value })}>
|
<SelectInput value={searchParams.game_mode_id} onChange={e => onSearch({ game_mode_id: e.target.value })}>
|
||||||
<option value="ALL">전체</option>
|
|
||||||
{configData?.map((data, index) => (
|
|
||||||
<option key={index} value={data.id}>
|
|
||||||
{data.desc}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
</InputGroup>
|
|
||||||
<InputLabel>배정 포드</InputLabel>
|
|
||||||
<InputGroup>
|
|
||||||
<SelectInput value={searchParams.rewardId} onChange={e => onSearch({ rewardId: e.target.value })}>
|
|
||||||
<option value='ALL'>전체</option>
|
<option value='ALL'>전체</option>
|
||||||
{rewardData?.map((data, index) => (
|
{gameModeData?.map((data, index) => (
|
||||||
<option key={index} value={data.id}>
|
<option key={index} value={data.id}>
|
||||||
{data.desc}
|
{data.desc}
|
||||||
</option>
|
</option>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { logAction, logDomain, userSearchType2 } from '../../../assets/data/options';
|
import { logAction, logDomain, userSearchType2 } from '../../assets/data/options';
|
||||||
import { BusinessLogList } from '../../../apis/Log';
|
import { BusinessLogList } from '../../apis/Log';
|
||||||
import {SearchFilter} from '../';
|
import {SearchFilter} from '../ServiceManage';
|
||||||
import { useAlert } from '../../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
export const useBusinessLogSearch = (token, initialPageSize) => {
|
export const useBusinessLogSearch = (token, initialPageSize) => {
|
||||||
const {showToast} = useAlert();
|
const {showToast} = useAlert();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { caliumStatus } from '../../../assets/data/options';
|
import { caliumStatus } from '../../assets/data/options';
|
||||||
|
|
||||||
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
|
const CaliumRequestSearchBar = ({ handleSearch, setResultData }) => {
|
||||||
const [searchData, setSearchData] = useState({
|
const [searchData, setSearchData] = useState({
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { getOptionsArray } from '../../../utils';
|
import { getOptionsArray } from '../../utils';
|
||||||
import { PageSkeleton } from '../../Skeleton/SearchSkeleton';
|
import { PageSkeleton } from '../Skeleton/SearchSkeleton';
|
||||||
|
|
||||||
const renderSearchField = (field, searchParams, onSearch) => {
|
const renderSearchField = (field, searchParams, onSearch) => {
|
||||||
const { type, id, label, placeholder, width, optionsRef } = field;
|
const { type, id, label, placeholder, width, optionsRef } = field;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
151
src/components/searchBar/CurrencyIndexSearchBar.js
Normal file
151
src/components/searchBar/CurrencyIndexSearchBar.js
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { userSearchType2 } from '../../assets/data/options';
|
||||||
|
import { getCurrencyDetailList, getCurrencyList } from '../../apis/Log';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useCurrencyIndexSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
start_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate());
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialLoad = async () => {
|
||||||
|
await fetchData(searchParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
initialLoad();
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const fetchData = useCallback(async (params) => {
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await getCurrencyList(
|
||||||
|
token,
|
||||||
|
params.start_dt.toISOString(),
|
||||||
|
params.end_dt.toISOString(),
|
||||||
|
params.order_by,
|
||||||
|
params.page_size,
|
||||||
|
params.page_no
|
||||||
|
);
|
||||||
|
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
|
||||||
|
showToast('LOG_MEMORY_LIMIT_WARNING', {type: alertTypes.error});
|
||||||
|
}else if(result.result === "ERROR_MONGODB_QUERY"){
|
||||||
|
showToast('LOG_MONGGDB_QUERY_WARNING', {type: alertTypes.error});
|
||||||
|
}else if(result.result === "ERROR"){
|
||||||
|
showToast(result.result, {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
setData(result.data);
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', {type: alertTypes.error});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const updateSearchParams = useCallback((newParams) => {
|
||||||
|
setSearchParams(prev => ({
|
||||||
|
...prev,
|
||||||
|
...newParams
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||||
|
const updatedParams = {
|
||||||
|
...searchParams,
|
||||||
|
...newParams,
|
||||||
|
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||||
|
};
|
||||||
|
updateSearchParams(updatedParams);
|
||||||
|
|
||||||
|
if (executeSearch) {
|
||||||
|
return await fetchData(updatedParams);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(async () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 1);
|
||||||
|
const resetParams = {
|
||||||
|
start_dt: now,
|
||||||
|
end_dt: new Date(),
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
};
|
||||||
|
setSearchParams(resetParams);
|
||||||
|
return await fetchData(resetParams);
|
||||||
|
}, [initialPageSize, fetchData]);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(async (newPage) => {
|
||||||
|
return await handleSearch({ page_no: newPage }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||||
|
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||||
|
return await handleSearch({ order_by: newOrder }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams,
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
handleOrderByChange,
|
||||||
|
updateSearchParams
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const CurrencyIndexSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
|
<>
|
||||||
|
<InputLabel>일자</InputLabel>
|
||||||
|
<SearchPeriod
|
||||||
|
startDate={searchParams.start_dt}
|
||||||
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
|
endDate={searchParams.end_dt}
|
||||||
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyIndexSearchBar;
|
||||||
232
src/components/searchBar/CurrencyLogSearchBar.js
Normal file
232
src/components/searchBar/CurrencyLogSearchBar.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { amountDeltaType, CurrencyType, logAction, userSearchType2 } from '../../assets/data/options';
|
||||||
|
import { getCurrencyDetailList } from '../../apis/Log';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useCurrencyLogSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
search_type: 'GUID',
|
||||||
|
search_data: '',
|
||||||
|
tran_id: '',
|
||||||
|
log_action: 'None',
|
||||||
|
currency_type: 'None',
|
||||||
|
amount_delta_type: 'None',
|
||||||
|
start_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
return date;
|
||||||
|
})(),
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// const initialLoad = async () => {
|
||||||
|
// await fetchData(searchParams);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// initialLoad();
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const fetchData = useCallback(async (params) => {
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await getCurrencyDetailList(
|
||||||
|
token,
|
||||||
|
params.search_type,
|
||||||
|
params.search_data,
|
||||||
|
params.tran_id,
|
||||||
|
params.log_action,
|
||||||
|
params.currency_type,
|
||||||
|
params.amount_delta_type,
|
||||||
|
params.start_dt.toISOString(),
|
||||||
|
params.end_dt.toISOString(),
|
||||||
|
params.order_by,
|
||||||
|
params.page_size,
|
||||||
|
params.page_no
|
||||||
|
);
|
||||||
|
if(result.result === "ERROR_LOG_MEMORY_LIMIT"){
|
||||||
|
showToast('LOG_MEMORY_LIMIT_WARNING', {type: alertTypes.error});
|
||||||
|
}else if(result.result === "ERROR_MONGODB_QUERY"){
|
||||||
|
showToast('LOG_MONGGDB_QUERY_WARNING', {type: alertTypes.error});
|
||||||
|
}else if(result.result === "ERROR"){
|
||||||
|
showToast(result.result, {type: alertTypes.error});
|
||||||
|
}
|
||||||
|
setData(result.data);
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', {type: alertTypes.error});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const updateSearchParams = useCallback((newParams) => {
|
||||||
|
setSearchParams(prev => ({
|
||||||
|
...prev,
|
||||||
|
...newParams
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSearch = useCallback(async (newParams = {}, executeSearch = true) => {
|
||||||
|
const updatedParams = {
|
||||||
|
...searchParams,
|
||||||
|
...newParams,
|
||||||
|
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||||
|
};
|
||||||
|
updateSearchParams(updatedParams);
|
||||||
|
|
||||||
|
if (executeSearch) {
|
||||||
|
return await fetchData(updatedParams);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(async () => {
|
||||||
|
const now = new Date();
|
||||||
|
now.setDate(now.getDate() - 1);
|
||||||
|
const resetParams = {
|
||||||
|
search_type: 'GUID',
|
||||||
|
search_data: '',
|
||||||
|
tran_id: '',
|
||||||
|
log_action: 'None',
|
||||||
|
currency_type: 'None',
|
||||||
|
amount_delta_type: 'None',
|
||||||
|
start_dt: now,
|
||||||
|
end_dt: now,
|
||||||
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
};
|
||||||
|
setSearchParams(resetParams);
|
||||||
|
return await fetchData(resetParams);
|
||||||
|
}, [initialPageSize, fetchData]);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(async (newPage) => {
|
||||||
|
return await handleSearch({ page_no: newPage }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handlePageSizeChange = useCallback(async (newSize) => {
|
||||||
|
return await handleSearch({ page_size: newSize, page_no: 1 }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
const handleOrderByChange = useCallback(async (newOrder) => {
|
||||||
|
return await handleSearch({ order_by: newOrder }, true);
|
||||||
|
}, [handleSearch]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams,
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
handleOrderByChange,
|
||||||
|
updateSearchParams
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const CurrencyLogSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
|
<>
|
||||||
|
<InputGroup>
|
||||||
|
<SelectInput value={searchParams.search_type} onChange={e => onSearch({search_type: e.target.value }, false)}>
|
||||||
|
{userSearchType2.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
placeholder={searchParams.search_type === 'GUID' ? 'GUID ID 입력' : searchParams.search_type === 'NICKNAME' ? '아바타명 입력' :'Account ID 입력'}
|
||||||
|
value={searchParams.search_data}
|
||||||
|
width="260px"
|
||||||
|
onChange={e => onSearch({ search_data: e.target.value }, false)}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>트랜잭션 ID</InputLabel>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
placeholder='트랜잭션 ID 입력'
|
||||||
|
value={searchParams.tran_id}
|
||||||
|
width="300px"
|
||||||
|
onChange={e => onSearch({ tran_id: e.target.value }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
const optionList = [
|
||||||
|
<>
|
||||||
|
<InputLabel>일자</InputLabel>
|
||||||
|
<SearchPeriod
|
||||||
|
startDate={searchParams.start_dt}
|
||||||
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
|
endDate={searchParams.end_dt}
|
||||||
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>액션</InputLabel>
|
||||||
|
<SelectInput value={searchParams.log_action} onChange={e => onSearch({ log_action: e.target.value }, false)} >
|
||||||
|
{logAction.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>재화종류</InputLabel>
|
||||||
|
<SelectInput value={searchParams.currency_type} onChange={e => onSearch({ currency_type: e.target.value }, false)} >
|
||||||
|
<option value="None">전체</option>
|
||||||
|
{CurrencyType.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>증감유형</InputLabel>
|
||||||
|
<SelectInput value={searchParams.amount_delta_type} onChange={e => onSearch({ amount_delta_type: e.target.value }, false)} >
|
||||||
|
<option value="None">전체</option>
|
||||||
|
{amountDeltaType.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CurrencyLogSearchBar;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { InputLabel, TextInput } from '../../../styles/Components';
|
import { InputLabel, TextInput } from '../../styles/Components';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { InitHistoryList } from '../../../apis/Data';
|
import { InitHistoryList } from '../../apis/Data';
|
||||||
import { useAlert } from '../../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
export const useDataInitSearch = (token) => {
|
export const useDataInitSearch = (token) => {
|
||||||
const {showToast} = useAlert();
|
const {showToast} = useAlert();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { eventStatus } from '../../../assets/data';
|
import { eventStatus } from '../../assets/data';
|
||||||
|
|
||||||
const EventListSearchBar = ({ handleSearch, setResultData }) => {
|
const EventListSearchBar = ({ handleSearch, setResultData }) => {
|
||||||
const [searchData, setSearchData] = useState({
|
const [searchData, setSearchData] = useState({
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { LandAuctionView } from '../../../apis';
|
import { LandAuctionView } from '../../apis';
|
||||||
import { landAuctionStatus, landSearchType, landSize, userType } from '../../../assets/data';
|
import { landAuctionStatus, landSearchType, landSize, userType } from '../../assets/data';
|
||||||
|
|
||||||
export const useLandAuctionSearch = (token, initialPageSize) => {
|
export const useLandAuctionSearch = (token, initialPageSize) => {
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { LandAuctionView, LandInfoData } from '../../../apis';
|
import { LandAuctionView, LandInfoData } from '../../apis';
|
||||||
import { landAuctionStatus, landSearchType, landSize, opLandCategoryType } from '../../../assets/data';
|
import { landAuctionStatus, landSearchType, landSize, opLandCategoryType } from '../../assets/data';
|
||||||
import { opLandInfoStatusType } from '../../../assets/data/options';
|
import { opLandInfoStatusType } from '../../assets/data/options';
|
||||||
|
|
||||||
export const useLandInfoSearch = (token, initialPageSize) => {
|
export const useLandInfoSearch = (token, initialPageSize) => {
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../../styles/Components';
|
import { TextInput, InputLabel, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { opHistoryType } from '../../../assets/data/options';
|
import { opHistoryType } from '../../assets/data/options';
|
||||||
|
|
||||||
const LogViewSearchBar = ({ handleSearch, resultData }) => {
|
const LogViewSearchBar = ({ handleSearch, resultData }) => {
|
||||||
const [searchData, setSearchData] = useState({
|
const [searchData, setSearchData] = useState({
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../../assets/data';
|
import { mailReceiveType, mailSendStatus, mailSendType, mailType } from '../../assets/data';
|
||||||
|
|
||||||
const MailListSearchBar = ({ handleSearch, setResultData }) => {
|
const MailListSearchBar = ({ handleSearch, setResultData }) => {
|
||||||
const [searchData, setSearchData] = useState({
|
const [searchData, setSearchData] = useState({
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout, SearchPeriod } from '../../common/SearchBar';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
const ReportListSearchBar = ({ handleSearch, setResultData }) => {
|
const ReportListSearchBar = ({ handleSearch, setResultData }) => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper, AlertText } from '../../styles/Components';
|
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper, AlertText } from '../../styles/Components';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { TextInput, InputLabel, SelectInput } from '../../../styles/Components';
|
import { TextInput, InputLabel, SelectInput } from '../../styles/Components';
|
||||||
import { logDomain, opInputType } from '../../../assets/data/options';
|
import { logDomain, opInputType } from '../../assets/data/options';
|
||||||
|
|
||||||
const TextInputWithHelp = memo(({ helpText, ...props }) => {
|
const TextInputWithHelp = memo(({ helpText, ...props }) => {
|
||||||
return (
|
return (
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper } from '../../styles/Components';
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../../styles/Components';
|
import { TextInput, BtnWrapper, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
import Button from '../../common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import { SearchBarLayout } from '../../common/SearchBar';
|
import { SearchBarLayout } from '../common/SearchBar';
|
||||||
import { blockPeriod, blockSanctions, blockSearchType, blockStatus } from '../../../assets/data';
|
import { blockPeriod, blockSanctions, blockSearchType, blockStatus } from '../../assets/data';
|
||||||
import { userType } from '../../../assets/data/options';
|
import { userType } from '../../assets/data/options';
|
||||||
|
|
||||||
const UserBlockSearchBar = ({ handleSearch, setResultData }) => {
|
const UserBlockSearchBar = ({ handleSearch, setResultData }) => {
|
||||||
const [searchData, setSearchData] = useState({
|
const [searchData, setSearchData] = useState({
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper } from '../../styles/Components';
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { TextInput, SelectInput, InputLabel, FormWrapper, BtnWrapper, ButtonClose } from '../../styles/Components';
|
import { TextInput, SelectInput, InputLabel, FormWrapper, BtnWrapper, ButtonClose } from '../../styles/Components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import Modal from '../common/modal/Modal';
|
||||||
import { UserView } from '../../apis';
|
import { UserView } from '../../apis';
|
||||||
|
|
||||||
const UserViewSearchBar = ({ setInfoView, handleTab, setResultData, resultData }) => {
|
const UserViewSearchBar = ({ setInfoView, handleTab, setResultData, resultData }) => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../common/button/Button';
|
||||||
|
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
||||||
|
|
||||||
67
src/components/searchBar/index.js
Normal file
67
src/components/searchBar/index.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// 모든 검색바 컴포넌트 export
|
||||||
|
import SearchFilter from './SearchFilter';
|
||||||
|
import VBPSearchBar from './VBPSearchBar';
|
||||||
|
import DecoSearchBar from './DecoSearchBar';
|
||||||
|
import ItemSearchBar from './ItemSearchBar';
|
||||||
|
import LandSearchBar from './LandSearchBar';
|
||||||
|
import UserSearchBar from './UserSearchBar';
|
||||||
|
import DailySearchBar from './DailySearchBar';
|
||||||
|
import CommonSearchBar from './CommonSearchBar';
|
||||||
|
import CreditSearchBar from './CreditSearchBar';
|
||||||
|
import LogViewSearchBar from './LogViewSearchBar';
|
||||||
|
import SegmentSearchBar from './SegmentSearchBar';
|
||||||
|
import DataInitSearchBar from './DataInitSearchBar';
|
||||||
|
import InstanceSearchBar from './InstanceSearchBar';
|
||||||
|
import LandInfoSearchBar from './LandInfoSearchBar';
|
||||||
|
import MailListSearchBar from './MailListSearchBar';
|
||||||
|
import PlayTimeSearchBar from './PlayTimeSearchBar';
|
||||||
|
import UserViewSearchBar from './UserViewSearchBar';
|
||||||
|
import AdminViewSearchBar from './AdminViewSearchBar';
|
||||||
|
import EventListSearchBar from './EventListSearchBar';
|
||||||
|
import RetentionSearchBar from './RetentionSearchBar';
|
||||||
|
import UserBlockSearchBar from './UserBlockSearchBar';
|
||||||
|
import UserIndexSearchBar from './UserIndexSearchBar';
|
||||||
|
import ReportListSearchBar from './ReportListSearchBar';
|
||||||
|
import BattleEventSearchBar from './BattleEventSearchBar';
|
||||||
|
import BusinessLogSearchBar, { useBusinessLogSearch } from './BusinessLogSearchBar';
|
||||||
|
import CurrencyLogSearchBar, { useCurrencyLogSearch } from './CurrencyLogSearchBar';
|
||||||
|
import CurrencyIndexSearchBar, { useCurrencyIndexSearch } from './CurrencyIndexSearchBar';
|
||||||
|
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
||||||
|
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||||
|
|
||||||
|
// 모든 SearchBar 컴포넌트 export
|
||||||
|
export {
|
||||||
|
SearchFilter,
|
||||||
|
VBPSearchBar,
|
||||||
|
DecoSearchBar,
|
||||||
|
ItemSearchBar,
|
||||||
|
LandSearchBar,
|
||||||
|
UserSearchBar,
|
||||||
|
DailySearchBar,
|
||||||
|
CommonSearchBar,
|
||||||
|
CreditSearchBar,
|
||||||
|
LogViewSearchBar,
|
||||||
|
SegmentSearchBar,
|
||||||
|
DataInitSearchBar,
|
||||||
|
InstanceSearchBar,
|
||||||
|
LandInfoSearchBar,
|
||||||
|
MailListSearchBar,
|
||||||
|
PlayTimeSearchBar,
|
||||||
|
UserViewSearchBar,
|
||||||
|
AdminViewSearchBar,
|
||||||
|
EventListSearchBar,
|
||||||
|
RetentionSearchBar,
|
||||||
|
UserBlockSearchBar,
|
||||||
|
UserIndexSearchBar,
|
||||||
|
ReportListSearchBar,
|
||||||
|
BattleEventSearchBar,
|
||||||
|
BusinessLogSearchBar,
|
||||||
|
useBusinessLogSearch,
|
||||||
|
CurrencyLogSearchBar,
|
||||||
|
useCurrencyLogSearch,
|
||||||
|
LandAuctionSearchBar,
|
||||||
|
CaliumRequestSearchBar,
|
||||||
|
CurrencyIndexSearchBar,
|
||||||
|
useCurrencyIndexSearch
|
||||||
|
};
|
||||||
|
|
||||||
@@ -50,6 +50,9 @@ const resources = {
|
|||||||
DUPLICATE_USER: "중복된 유저 정보가 있습니다.",
|
DUPLICATE_USER: "중복된 유저 정보가 있습니다.",
|
||||||
COUNT_EMPTY_WARNING: "수량을 입력해주세요.",
|
COUNT_EMPTY_WARNING: "수량을 입력해주세요.",
|
||||||
UPLOAD_FILENAME_SAMPLE_WARNING: "파일명에 sample을 넣을 수 없습니다.\r\n파일명을 변경 후 다시 업로드 해주세요.",
|
UPLOAD_FILENAME_SAMPLE_WARNING: "파일명에 sample을 넣을 수 없습니다.\r\n파일명을 변경 후 다시 업로드 해주세요.",
|
||||||
|
EXCEL_EXPORT_LENGTH_LIMIT_WARNING: '엑셀 다운은 10만건 이하까지만 가능합니다.\r\n조건을 조정 후 다시 시도해주세요.',
|
||||||
|
DOWNLOAD_COMPLETE: '다운이 완료되었습니다.',
|
||||||
|
DOWNLOAD_FAIL: '다운이 실패하였습니다.',
|
||||||
//user
|
//user
|
||||||
NICKNAME_CHANGES_CONFIRM: '닉네임을 변경하시겠습니까?',
|
NICKNAME_CHANGES_CONFIRM: '닉네임을 변경하시겠습니까?',
|
||||||
NICKNAME_CHANGES_COMPLETE: '닉네임 변경이 완료되었습니다.',
|
NICKNAME_CHANGES_COMPLETE: '닉네임 변경이 완료되었습니다.',
|
||||||
@@ -136,6 +139,7 @@ const resources = {
|
|||||||
MENU_BANNER_REGIST_CONFIRM: "배너를 등록하시겠습니까?",
|
MENU_BANNER_REGIST_CONFIRM: "배너를 등록하시겠습니까?",
|
||||||
MENU_BANNER_SELECT_DELETE: "선택된 배너를 삭제하시겠습니까?",
|
MENU_BANNER_SELECT_DELETE: "선택된 배너를 삭제하시겠습니까?",
|
||||||
MENU_BANNER_REGIST_CANCEL: "배너 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
|
MENU_BANNER_REGIST_CANCEL: "배너 등록을 취소하시겠습니까?\n\r취소 시 설정된 값은 반영되지 않습니다.",
|
||||||
|
MENU_BANNER_UPDATE_SAVE: "배너 정보 수정사항을 \r\n저장하시겠습니까?",
|
||||||
//아이템
|
//아이템
|
||||||
ITEM_DELETE_CONFIRM: '해당 아이템을 삭제하시겠습니까?\r\n* 한번 삭제한 아이템은 다시 복구할 수 없습니다.',
|
ITEM_DELETE_CONFIRM: '해당 아이템을 삭제하시겠습니까?\r\n* 한번 삭제한 아이템은 다시 복구할 수 없습니다.',
|
||||||
ITEM_RESTORE_CONFIRM: '해당 아이템을 복구하시겠습니까?',
|
ITEM_RESTORE_CONFIRM: '해당 아이템을 복구하시겠습니까?',
|
||||||
@@ -157,6 +161,8 @@ const resources = {
|
|||||||
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
|
FILE_LAND_AUCTION: 'Caliverse_Land_Auction.xlsx',
|
||||||
FILE_BUSINESS_LOG: 'Caliverse_Log.xlsx',
|
FILE_BUSINESS_LOG: 'Caliverse_Log.xlsx',
|
||||||
FILE_BATTLE_EVENT: 'Caliverse_Battle_Event.xlsx',
|
FILE_BATTLE_EVENT: 'Caliverse_Battle_Event.xlsx',
|
||||||
|
FILE_GAME_LOG_CURRENCY: 'Caliverse_Game_Log_Currency',
|
||||||
|
FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index',
|
||||||
//서버 에러메시지
|
//서버 에러메시지
|
||||||
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
||||||
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
||||||
|
|||||||
@@ -10,33 +10,30 @@ import {
|
|||||||
TableDetailContainer,
|
TableDetailContainer,
|
||||||
TableDetailFlex,
|
TableDetailFlex,
|
||||||
TableDetailColumn,
|
TableDetailColumn,
|
||||||
DetailTableInfo,
|
DetailTableInfo, DownloadContainer, CircularProgressWrapper,
|
||||||
} from '../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
|
||||||
import { withAuth } from '../../hooks/hook';
|
import { withAuth } from '../../hooks/hook';
|
||||||
import {
|
import {
|
||||||
authType,
|
authType,
|
||||||
modalTypes,
|
|
||||||
} from '../../assets/data';
|
} from '../../assets/data';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
DynamicModal,
|
|
||||||
ExcelDownButton,
|
|
||||||
TopButton,
|
TopButton,
|
||||||
ViewTableInfo,
|
ViewTableInfo,
|
||||||
} from '../../components/common';
|
} from '../../components/common';
|
||||||
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
||||||
import BusinessLogSearchBar, { useBusinessLogSearch } from '../../components/ServiceManage/searchBar/BusinessLogSearchBar';
|
import BusinessLogSearchBar, { useBusinessLogSearch } from '../../components/searchBar/BusinessLogSearchBar';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import FrontPagination from '../../components/common/Pagination/FrontPagination';
|
|
||||||
import CircularProgress from '../../components/common/CircularProgress';
|
import CircularProgress from '../../components/common/CircularProgress';
|
||||||
// import MessageInput from '../../components/common/input/MessageInput';
|
// import MessageInput from '../../components/common/input/MessageInput';
|
||||||
// import { AnalyzeAI } from '../../apis';
|
// import { AnalyzeAI } from '../../apis';
|
||||||
import {
|
import {
|
||||||
INITIAL_CURRENT_PAGE,
|
|
||||||
INITIAL_PAGE_LIMIT,
|
INITIAL_PAGE_LIMIT,
|
||||||
STORAGE_BUSINESS_LOG_SEARCH,
|
STORAGE_BUSINESS_LOG_SEARCH,
|
||||||
} from '../../assets/data/adminConstants';
|
} from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../../components/common/button/ExcelExportButton';
|
||||||
|
import Pagination from '../../components/common/Pagination/Pagination';
|
||||||
|
|
||||||
const BusinessLogView = () => {
|
const BusinessLogView = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -49,10 +46,6 @@ const BusinessLogView = () => {
|
|||||||
progress: 0
|
progress: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(INITIAL_CURRENT_PAGE);
|
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(500);
|
|
||||||
const [displayData, setDisplayData] = useState([]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchParams,
|
searchParams,
|
||||||
loading: dataLoading,
|
loading: dataLoading,
|
||||||
@@ -61,6 +54,7 @@ const BusinessLogView = () => {
|
|||||||
handleReset,
|
handleReset,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handleOrderByChange,
|
handleOrderByChange,
|
||||||
|
handlePageSizeChange,
|
||||||
updateSearchParams
|
updateSearchParams
|
||||||
} = useBusinessLogSearch(token, 500);
|
} = useBusinessLogSearch(token, 500);
|
||||||
|
|
||||||
@@ -82,25 +76,6 @@ const BusinessLogView = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePageSizeChange = (newSize) => {
|
|
||||||
setItemsPerPage(newSize);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClientPageChange = useCallback((slicedData) => {
|
|
||||||
setDisplayData(slicedData);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentPage(1);
|
|
||||||
if (dataList?.generic_list && dataList.generic_list.length > 0) {
|
|
||||||
const initialData = dataList.generic_list.slice(0, itemsPerPage);
|
|
||||||
setDisplayData(initialData);
|
|
||||||
} else {
|
|
||||||
setDisplayData([]);
|
|
||||||
}
|
|
||||||
}, [dataList, itemsPerPage]);
|
|
||||||
|
|
||||||
const toggleRowExpand = (index) => {
|
const toggleRowExpand = (index) => {
|
||||||
setExpandedRows(prev => ({
|
setExpandedRows(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -209,10 +184,12 @@ const BusinessLogView = () => {
|
|||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<ViewTableInfo orderType="asc" pageType="B" total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
<ViewTableInfo orderType="asc" pageType="B" total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||||
<DownloadContainer>
|
<DownloadContainer>
|
||||||
<ExcelDownButton
|
<ExcelExportButton
|
||||||
data={dataList?.generic_list}
|
functionName="BusinessLogExport"
|
||||||
|
params={searchParams}
|
||||||
fileName={t('FILE_BUSINESS_LOG')}
|
fileName={t('FILE_BUSINESS_LOG')}
|
||||||
onLoadingChange={setDownloadState}
|
onLoadingChange={setDownloadState}
|
||||||
|
dataSize={dataList?.total_all}
|
||||||
/>
|
/>
|
||||||
{downloadState.loading && (
|
{downloadState.loading && (
|
||||||
<CircularProgressWrapper>
|
<CircularProgressWrapper>
|
||||||
@@ -238,7 +215,7 @@ const BusinessLogView = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{displayData?.map((item, index) => (
|
{dataList?.generic_list?.map((item, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{item.logTime}</td>
|
<td>{item.logTime}</td>
|
||||||
@@ -276,14 +253,14 @@ const BusinessLogView = () => {
|
|||||||
</TableStyle>
|
</TableStyle>
|
||||||
</TableWrapper>
|
</TableWrapper>
|
||||||
{dataList?.generic_list &&
|
{dataList?.generic_list &&
|
||||||
<FrontPagination
|
<Pagination
|
||||||
data={dataList.generic_list}
|
postsPerPage={searchParams.page_size}
|
||||||
itemsPerPage={itemsPerPage}
|
totalPosts={dataList?.total_all}
|
||||||
currentPage={currentPage}
|
setCurrentPage={handlePageChange}
|
||||||
setCurrentPage={setCurrentPage}
|
currentPage={searchParams.page_no}
|
||||||
pageLimit={INITIAL_PAGE_LIMIT}
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
onPageChange={handleClientPageChange}
|
/>
|
||||||
/>}
|
}
|
||||||
<TopButton />
|
<TopButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -292,15 +269,3 @@ const BusinessLogView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default withAuth(authType.businessLogRead)(BusinessLogView);
|
export default withAuth(authType.businessLogRead)(BusinessLogView);
|
||||||
|
|
||||||
const DownloadContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CircularProgressWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
|
||||||
@@ -4,7 +4,7 @@ import { Fragment, useState } from 'react';
|
|||||||
|
|
||||||
import { Title, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
import { Title, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
||||||
|
|
||||||
import LandSearchBar from '../../components/DataManage/LandSearchBar';
|
import LandSearchBar from '../../components/searchBar/LandSearchBar';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import QuestDetailModal from '../../components/DataManage/QuestDetailModal';
|
import QuestDetailModal from '../../components/DataManage/QuestDetailModal';
|
||||||
import LandDetailModal from '../../components/DataManage/LandDetailModal';
|
import LandDetailModal from '../../components/DataManage/LandDetailModal';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Fragment, useState } from 'react';
|
|||||||
|
|
||||||
import { Title, TableStyle, ButtonClose, ModalText, BtnWrapper } from '../../styles/Components';
|
import { Title, TableStyle, ButtonClose, ModalText, BtnWrapper } from '../../styles/Components';
|
||||||
|
|
||||||
import UserSearchBar from '../../components/DataManage/UserSearchBar';
|
import UserSearchBar from '../../components/searchBar/UserSearchBar';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import Modal from '../../components/common/modal/Modal';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import Pagination from '../../components/common/Pagination/Pagination';
|
import Pagination from '../../components/common/Pagination/Pagination';
|
||||||
|
|
||||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
import { registerLocale } from 'react-datepicker';
|
||||||
import { ko } from 'date-fns/esm/locale';
|
import { ko } from 'date-fns/esm/locale';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import { getMonth, getYear } from 'date-fns';
|
|
||||||
import range from 'lodash/range';
|
|
||||||
|
|
||||||
import { Title, SelectInput, BtnWrapper, TableStyle, TableInfo, ListCount, ListOption, ButtonClose, ModalText } from '../../styles/Components';
|
import {
|
||||||
|
Title,
|
||||||
|
SelectInput,
|
||||||
|
TableStyle,
|
||||||
|
TableInfo,
|
||||||
|
ListCount,
|
||||||
|
ListOption,
|
||||||
|
TabScroll, TabItem, TabWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import ItemLogSearchBar from '../../components/DataManage/ItemLogSearchBar';
|
import { withAuth } from '../../hooks/hook';
|
||||||
import GoodsLogSearchBar from '../../components/DataManage/CreditLogSearchBar';
|
import { authType } from '../../assets/data';
|
||||||
import TradeLogSerchBar from '../../components/DataManage/TradeLogSearchBar';
|
import { TabGameLogList } from '../../assets/data/options';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import CurrencyLogContent from '../../components/DataManage/CurrencyLogContent';
|
||||||
|
import { STORAGE_GAME_LOG_CURRENCY_SEARCH } from '../../assets/data/adminConstants';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
registerLocale('ko', ko);
|
registerLocale('ko', ko);
|
||||||
|
|
||||||
@@ -41,7 +43,6 @@ const ItemLogContent = () => {
|
|||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ItemLogSearchBar />
|
|
||||||
<TableInfo>
|
<TableInfo>
|
||||||
<ListCount>총 : 117건 / 000 건</ListCount>
|
<ListCount>총 : 117건 / 000 건</ListCount>
|
||||||
<ListOption>
|
<ListOption>
|
||||||
@@ -98,272 +99,49 @@ const ItemLogContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GoodsLogContent = () => {
|
|
||||||
const mokupData = [
|
|
||||||
{
|
|
||||||
date: '2023-08-05 12:11:32',
|
|
||||||
name: '홍길동',
|
|
||||||
id: '16CD2ECD-4798-46CE-9B6B-F952CF11F196',
|
|
||||||
gold: '99800',
|
|
||||||
blue: '400',
|
|
||||||
black: '500',
|
|
||||||
red: '500',
|
|
||||||
action: '소진',
|
|
||||||
location: '유저 거래',
|
|
||||||
goldchange: '-',
|
|
||||||
bluechange: '-100',
|
|
||||||
blackchange: '-',
|
|
||||||
redchange: '-',
|
|
||||||
key: 'User_trade_key',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<GoodsLogSearchBar />
|
|
||||||
<TableInfo>
|
|
||||||
<ListCount>총 : 117건 / 000 건</ListCount>
|
|
||||||
<ListOption>
|
|
||||||
<SelectInput name="" id="" className="input-select">
|
|
||||||
<option value="up">오름차순</option>
|
|
||||||
<option value="down">내림차순</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput name="" id="" className="input-select">
|
|
||||||
<option value="up">50개</option>
|
|
||||||
<option value="down">100개</option>
|
|
||||||
</SelectInput>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<TableWrapper>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="150">일자</th>
|
|
||||||
<th width="200">아바타명</th>
|
|
||||||
<th width="300">GUID</th>
|
|
||||||
<th width="100">
|
|
||||||
(Total)
|
|
||||||
<br />
|
|
||||||
골드
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(Total)
|
|
||||||
<br />
|
|
||||||
사파이어
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(Total)
|
|
||||||
<br />
|
|
||||||
칼리움
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(Total)
|
|
||||||
<br />
|
|
||||||
오닉시움
|
|
||||||
</th>
|
|
||||||
<th width="80">액션</th>
|
|
||||||
<th width="100">획득 / 소진처</th>
|
|
||||||
<th width="100">
|
|
||||||
(변화량)
|
|
||||||
<br />
|
|
||||||
골드
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(변화량)
|
|
||||||
<br />
|
|
||||||
사파이어
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(변화량)
|
|
||||||
<br />
|
|
||||||
칼리움
|
|
||||||
</th>
|
|
||||||
<th width="100">
|
|
||||||
(변화량)
|
|
||||||
<br />
|
|
||||||
오닉시움
|
|
||||||
</th>
|
|
||||||
<th width="300">거래 Key</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{mokupData.map((data, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<tr>
|
|
||||||
<td>{new Date(data.date).toLocaleString()}</td>
|
|
||||||
<td>{data.name}</td>
|
|
||||||
<td>{data.id}</td>
|
|
||||||
<td>{data.gold}</td>
|
|
||||||
<td>{data.blue}</td>
|
|
||||||
<td>{data.black}</td>
|
|
||||||
<td>{data.red}</td>
|
|
||||||
<td>{data.action}</td>
|
|
||||||
<td>{data.location}</td>
|
|
||||||
<td>{data.goldchange}</td>
|
|
||||||
{/* 변화량 0보다 작을 경우 StateDecrease 적용 */}
|
|
||||||
<td>
|
|
||||||
<StateDecrease>{data.bluechange}</StateDecrease>
|
|
||||||
</td>
|
|
||||||
<td>{data.blackchange}</td>
|
|
||||||
<td>{data.redchange}</td>
|
|
||||||
<td>{data.key}</td>
|
|
||||||
</tr>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</TableWrapper>
|
|
||||||
<Pagination />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TradeLogContent = () => {
|
|
||||||
const mokupData = [
|
|
||||||
{
|
|
||||||
date: '2023-08-05 12:11:32',
|
|
||||||
name: '홍길동',
|
|
||||||
trader: '칼리버스',
|
|
||||||
id: '16CD2ECD-4798-46CE-9B6B-F952CF11F196',
|
|
||||||
key: 'User_trade_key',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TradeLogSerchBar />
|
|
||||||
<TableInfo>
|
|
||||||
<ListCount>
|
|
||||||
총 : 117건 / <span>000</span> 건
|
|
||||||
</ListCount>
|
|
||||||
<ListOption>
|
|
||||||
<SelectInput name="" id="" className="input-select">
|
|
||||||
<option value="up">오름차순</option>
|
|
||||||
<option value="down">내림차순</option>
|
|
||||||
</SelectInput>
|
|
||||||
<SelectInput name="" id="" className="input-select">
|
|
||||||
<option value="up">50개</option>
|
|
||||||
<option value="down">100개</option>
|
|
||||||
</SelectInput>
|
|
||||||
<Button theme="line" text="엑셀 다운로드" />
|
|
||||||
</ListOption>
|
|
||||||
</TableInfo>
|
|
||||||
<TableWrapper>
|
|
||||||
<TableStyle>
|
|
||||||
<caption></caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="150">일자</th>
|
|
||||||
<th width="150">조회 아바타명</th>
|
|
||||||
<th width="150">거래 대상 아바타명</th>
|
|
||||||
<th width="300">거래 대상 GUID</th>
|
|
||||||
<th width="300">거래 Key</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{mokupData.map((data, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<tr>
|
|
||||||
<td>{new Date(data.date).toLocaleString()}</td>
|
|
||||||
<td>{data.name}</td>
|
|
||||||
<td>{data.trader}</td>
|
|
||||||
<td>{data.id}</td>
|
|
||||||
<td>{data.key}</td>
|
|
||||||
</tr>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</TableWrapper>
|
|
||||||
<Pagination />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const GameLogView = () => {
|
const GameLogView = () => {
|
||||||
const navigate = useNavigate();
|
const [activeTab, setActiveTab] = useState('CURRENCY');
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
const [activeTab, setActiveTab] = useState('itemlog');
|
useEffect(() => {
|
||||||
|
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_CURRENCY_SEARCH);
|
||||||
|
|
||||||
|
if (paramsData) {
|
||||||
|
const searchData = JSON.parse(paramsData);
|
||||||
|
|
||||||
|
setActiveTab(searchData.tab);
|
||||||
|
console.log(searchData);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleTab = (e, content) => {
|
const handleTab = (e, content) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setActiveTab(content);
|
setActiveTab(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 14) ? (
|
<Title>게임 로그 조회</Title>
|
||||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
|
<TabScroll>
|
||||||
<BtnWrapper $justify="flex-end">
|
|
||||||
<ButtonClose onClick={() => navigate(-1)} />
|
|
||||||
</BtnWrapper>
|
|
||||||
<ModalText $align="center">
|
|
||||||
해당 메뉴에 대한 조회 권한이 없습니다.
|
|
||||||
<br />
|
|
||||||
권한 등급을 변경 후 다시 이용해주세요.
|
|
||||||
</ModalText>
|
|
||||||
<BtnWrapper $gap="10px">
|
|
||||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
|
|
||||||
</BtnWrapper>
|
|
||||||
</Modal>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Title>게임로그조회</Title>
|
|
||||||
<TabWrapper>
|
<TabWrapper>
|
||||||
<li>
|
{TabGameLogList.map((el, idx) => {
|
||||||
<TabItem $state={activeTab === 'itemlog' ? 'active' : 'none'} onClick={e => handleTab(e, 'itemlog')}>
|
return (
|
||||||
아이템 로그
|
<li key={idx}>
|
||||||
</TabItem>
|
<TabItem $state={activeTab === el.value ? 'active' : 'none'} onClick={e => handleTab(e, el.value)}>
|
||||||
</li>
|
{el.name}
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'goodslog' ? 'active' : 'none'} onClick={e => handleTab(e, 'goodslog')}>
|
|
||||||
재화 로그
|
|
||||||
</TabItem>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'tradelog' ? 'active' : 'none'} onClick={e => handleTab(e, 'tradelog')}>
|
|
||||||
거래 로그
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</li>
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</TabWrapper>
|
</TabWrapper>
|
||||||
{activeTab === 'itemlog' && <ItemLogContent />}
|
</TabScroll>
|
||||||
{activeTab === 'goodslog' && <GoodsLogContent />}
|
{/*{activeTab === 'ITEM' && <ItemLogContent />}*/}
|
||||||
{activeTab === 'tradelog' && <TradeLogContent />}
|
<CurrencyLogContent active={activeTab === 'CURRENCY'} />
|
||||||
</>
|
{/*{activeTab === 'TRADE' && <TradeLogContent />}*/}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GameLogView;
|
export default withAuth(authType.gameLogRead)(GameLogView);
|
||||||
|
|
||||||
const TabItem = styled(Link)`
|
|
||||||
display: inline-flex;
|
|
||||||
width: 120px;
|
|
||||||
height: 30px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-left: 1px solid #d9d9d9;
|
|
||||||
&:hover {
|
|
||||||
background: #888;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
${props =>
|
|
||||||
props.$state === 'active' &&
|
|
||||||
`
|
|
||||||
background: #888;
|
|
||||||
color: #fff;`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TabWrapper = styled.ul`
|
|
||||||
display: flex;
|
|
||||||
li:first-child {
|
|
||||||
${TabItem} {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableWrapper = styled.div`
|
const TableWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -383,7 +161,3 @@ const TableWrapper = styled.div`
|
|||||||
min-width: max-content;
|
min-width: max-content;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StateDecrease = styled.span`
|
|
||||||
color: #d60000;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import {
|
|||||||
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE, TYPE_MODIFY } from '../../assets/data/adminConstants';
|
import { INITIAL_PAGE_LIMIT, INITIAL_PAGE_SIZE, TYPE_MODIFY } from '../../assets/data/adminConstants';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CheckBox, DynamicModal, ExcelDownButton, Pagination, ViewTableInfo } from '../../components/common';
|
import { CheckBox, DynamicModal, ExcelDownButton, Pagination, ViewTableInfo } from '../../components/common';
|
||||||
import LandInfoSearchBar, { useLandInfoSearch } from '../../components/ServiceManage/searchBar/LandInfoSearchBar';
|
import LandInfoSearchBar, { useLandInfoSearch } from '../../components/searchBar/LandInfoSearchBar';
|
||||||
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
import { TableSkeleton } from '../../components/Skeleton/TableSkeleton';
|
||||||
import OwnerChangeModal from '../../components/ServiceManage/modal/OwnerChangeModal';
|
import OwnerChangeModal from '../../components/modal/OwnerChangeModal';
|
||||||
import { opLandInfoStatusType } from '../../assets/data/options';
|
import { opLandInfoStatusType } from '../../assets/data/options';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState, Fragment } from 'react';
|
import { useState, Fragment } from 'react';
|
||||||
|
|
||||||
import { Title, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
import { TabScroll, Title } from '../../styles/Components';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import UserViewSearchBar from '../../components/DataManage/UserViewSearchBar';
|
import UserViewSearchBar from '../../components/searchBar/UserViewSearchBar';
|
||||||
import UserDefaultInfo from '../../components/DataManage/UserDefaultInfo';
|
import UserDefaultInfo from '../../components/DataManage/UserDefaultInfo';
|
||||||
import UserAvatarInfo from '../../components/DataManage/UserAvatarInfo';
|
import UserAvatarInfo from '../../components/DataManage/UserAvatarInfo';
|
||||||
import UserDressInfo from '../../components/DataManage/UserDressInfo';
|
import UserDressInfo from '../../components/DataManage/UserDressInfo';
|
||||||
@@ -16,17 +16,14 @@ import UserFriendInfo from '../../components/DataManage/UserFriendInfo';
|
|||||||
import UserTatttooInfo from '../../components/DataManage/UserTattooInfo';
|
import UserTatttooInfo from '../../components/DataManage/UserTattooInfo';
|
||||||
import UserQuestInfo from '../../components/DataManage/UserQuestInfo';
|
import UserQuestInfo from '../../components/DataManage/UserQuestInfo';
|
||||||
import UserClaimInfo from '../../components/DataManage/UserClaimInfo';
|
import UserClaimInfo from '../../components/DataManage/UserClaimInfo';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
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 { authType, TabList } from '../../assets/data';
|
import { authType, TabUserList } from '../../assets/data';
|
||||||
|
|
||||||
const UserView = () => {
|
const UserView = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
|
|
||||||
const [infoView, setInfoView] = useState('none');
|
const [infoView, setInfoView] = useState('none');
|
||||||
@@ -48,7 +45,7 @@ const UserView = () => {
|
|||||||
<UserWrapper display={infoView}>
|
<UserWrapper display={infoView}>
|
||||||
<TabScroll>
|
<TabScroll>
|
||||||
<UserTabWrapper>
|
<UserTabWrapper>
|
||||||
{TabList.map((el, idx) => {
|
{TabUserList.map((el, idx) => {
|
||||||
return (
|
return (
|
||||||
<UserTab key={idx} $state={el.title === activeContent ? 'active' : 'unactive'} onClick={() => handleTab(el.title)}>
|
<UserTab key={idx} $state={el.title === activeContent ? 'active' : 'unactive'} onClick={() => handleTab(el.title)}>
|
||||||
{el.title}
|
{el.title}
|
||||||
@@ -102,10 +99,7 @@ const UserTab = styled.li`
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
background: #888;`}
|
background: #888;`}
|
||||||
`;
|
`;
|
||||||
const TabScroll = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
`;
|
|
||||||
const UserTabWrapper = styled.ul`
|
const UserTabWrapper = styled.ul`
|
||||||
border-bottom: 1px solid #888888;
|
border-bottom: 1px solid #888888;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
|
|
||||||
import { Title, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
import { Title, BtnWrapper, ButtonClose, ModalText, TabItem, TabScroll, TabWrapper } from '../../styles/Components';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import Modal from '../../components/common/modal/Modal';
|
||||||
@@ -17,98 +17,40 @@ import VBPContent from '../../components/IndexManage/VBPContent';
|
|||||||
import ItemContent from '../../components/IndexManage/ItemContent';
|
import ItemContent from '../../components/IndexManage/ItemContent';
|
||||||
import InstanceContent from '../../components/IndexManage/InstanceContent';
|
import InstanceContent from '../../components/IndexManage/InstanceContent';
|
||||||
import DecoContent from '../../components/IndexManage/DecoContent';
|
import DecoContent from '../../components/IndexManage/DecoContent';
|
||||||
|
import { withAuth } from '../../hooks/hook';
|
||||||
|
import { authType } from '../../assets/data';
|
||||||
|
import { TabEconomicIndexList, TabGameLogList } from '../../assets/data/options';
|
||||||
|
|
||||||
const EconomicIndex = () => {
|
const EconomicIndex = () => {
|
||||||
const navigate = useNavigate();
|
const [activeTab, setActiveTab] = useState('CURRENCY');
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
const [activeTab, setActiveTab] = useState('credit');
|
|
||||||
|
|
||||||
const handleTab = (e, content) => {
|
const handleTab = (e, content) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setActiveTab(content);
|
setActiveTab(content);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 10) ? (
|
|
||||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={'view'}>
|
|
||||||
<BtnWrapper $justify="flex-end">
|
|
||||||
<ButtonClose onClick={() => navigate(-1)} />
|
|
||||||
</BtnWrapper>
|
|
||||||
<ModalText $align="center">
|
|
||||||
해당 메뉴에 대한 조회 권한이 없습니다.
|
|
||||||
<br />
|
|
||||||
권한 등급을 변경 후 다시 이용해주세요.
|
|
||||||
</ModalText>
|
|
||||||
<BtnWrapper $gap="10px">
|
|
||||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={() => navigate(-1)} />
|
|
||||||
</BtnWrapper>
|
|
||||||
</Modal>
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<Title>경제 지표</Title>
|
<Title>경제 지표</Title>
|
||||||
|
<TabScroll>
|
||||||
<TabWrapper>
|
<TabWrapper>
|
||||||
<li>
|
{TabEconomicIndexList.map((el, idx) => {
|
||||||
<TabItem $state={activeTab === 'credit' ? 'active' : 'none'} onClick={e => handleTab(e, 'credit')}>
|
return (
|
||||||
재화
|
<li key={idx}>
|
||||||
</TabItem>
|
<TabItem $state={activeTab === el.value ? 'active' : 'none'} onClick={e => handleTab(e, el.value)}>
|
||||||
</li>
|
{el.name}
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'vbp' ? 'active' : 'none'} onClick={e => handleTab(e, 'vbp')}>
|
|
||||||
VBP
|
|
||||||
</TabItem>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'item' ? 'active' : 'none'} onClick={e => handleTab(e, 'item')}>
|
|
||||||
아이템
|
|
||||||
</TabItem>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'instance' ? 'active' : 'none'} onClick={e => handleTab(e, 'instance')}>
|
|
||||||
인스턴스
|
|
||||||
</TabItem>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<TabItem $state={activeTab === 'deco' ? 'active' : 'none'} onClick={e => handleTab(e, 'deco')}>
|
|
||||||
의상 / 타투
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</li>
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</TabWrapper>
|
</TabWrapper>
|
||||||
{activeTab === 'credit' && <CreditContent />}
|
</TabScroll>
|
||||||
{activeTab === 'vbp' && <VBPContent />}
|
{activeTab === 'CURRENCY' && <CreditContent />}
|
||||||
{activeTab === 'item' && <ItemContent />}
|
{/*{activeTab === 'vbp' && <VBPContent />}*/}
|
||||||
{activeTab === 'instance' && <InstanceContent />}
|
{/*{activeTab === 'item' && <ItemContent />}*/}
|
||||||
{activeTab === 'deco' && <DecoContent />}
|
{/*{activeTab === 'instance' && <InstanceContent />}*/}
|
||||||
</>
|
{/*{activeTab === 'deco' && <DecoContent />}*/}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EconomicIndex;
|
export default withAuth(authType.economicIndicatorsRead)(EconomicIndex);
|
||||||
|
|
||||||
const TabItem = styled(Link)`
|
|
||||||
display: inline-flex;
|
|
||||||
width: 120px;
|
|
||||||
height: 30px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border-left: 1px solid #d9d9d9;
|
|
||||||
&:hover {
|
|
||||||
background: #888;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
${props =>
|
|
||||||
props.$state === 'active' &&
|
|
||||||
`background: #888;
|
|
||||||
color: #fff;`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TabWrapper = styled.ul`
|
|
||||||
display: flex;
|
|
||||||
li:first-child {
|
|
||||||
${TabItem} {
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
BattleEventDelete,
|
BattleEventDelete,
|
||||||
BattleEventDetailView, BattleEventStop,
|
BattleEventDetailView, BattleEventStop,
|
||||||
BattleEventView,
|
BattleEventView,
|
||||||
BattleRewardView,
|
BattleRewardView, GameModeView,
|
||||||
} from '../../apis/Battle';
|
} from '../../apis/Battle';
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
import { authList } from '../../store/authList';
|
||||||
@@ -36,7 +36,7 @@ import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
|||||||
import { battleEventStatus, battleRepeatType } from '../../assets/data/options';
|
import { battleEventStatus, battleRepeatType } from '../../assets/data/options';
|
||||||
import BattleEventSearchBar, {
|
import BattleEventSearchBar, {
|
||||||
useBattleEventSearch,
|
useBattleEventSearch,
|
||||||
} from '../../components/ServiceManage/searchBar/BattleEventSearchBar';
|
} from '../../components/searchBar/BattleEventSearchBar';
|
||||||
import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date';
|
import { getDateOnly, getTimeOnly, secondToMinutes } from '../../utils/date';
|
||||||
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
|
import { alertTypes, battleEventStatusType } from '../../assets/data/types';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
@@ -91,6 +91,10 @@ const BattleEvent = () => {
|
|||||||
data: battleRewardData
|
data: battleRewardData
|
||||||
} = useDataFetch(() => BattleRewardView(token), [token]);
|
} = useDataFetch(() => BattleRewardView(token), [token]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: gameModeData
|
||||||
|
} = useDataFetch(() => GameModeView(token), [token]);
|
||||||
|
|
||||||
const endTime = (start_dt, operation_time) =>{
|
const endTime = (start_dt, operation_time) =>{
|
||||||
const startDate = new Date(start_dt);
|
const startDate = new Date(start_dt);
|
||||||
|
|
||||||
@@ -262,6 +266,7 @@ const BattleEvent = () => {
|
|||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
configData={battleConfigData}
|
configData={battleConfigData}
|
||||||
rewardData={battleRewardData}
|
rewardData={battleRewardData}
|
||||||
|
gameModeData={gameModeData}
|
||||||
/>
|
/>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>
|
||||||
@@ -296,8 +301,9 @@ const BattleEvent = () => {
|
|||||||
<th width="100">이벤트 시작시간(KST)</th>
|
<th width="100">이벤트 시작시간(KST)</th>
|
||||||
<th width="100">이벤트 종료시간(KST)</th>
|
<th width="100">이벤트 종료시간(KST)</th>
|
||||||
<th width="90">이벤트 상태</th>
|
<th width="90">이벤트 상태</th>
|
||||||
<th width="90">라운드 시간</th>
|
<th width="90">게임모드</th>
|
||||||
<th width="90">배정포드</th>
|
{/*<th width="90">라운드 시간</th>*/}
|
||||||
|
{/*<th width="90">배정포드</th>*/}
|
||||||
<th width="70">라운드 수</th>
|
<th width="70">라운드 수</th>
|
||||||
<th width="70">핫타임</th>
|
<th width="70">핫타임</th>
|
||||||
<th width="100">확인 / 수정</th>
|
<th width="100">확인 / 수정</th>
|
||||||
@@ -313,7 +319,7 @@ const BattleEvent = () => {
|
|||||||
checked={isRowSelected(battle.id)} />
|
checked={isRowSelected(battle.id)} />
|
||||||
</td>
|
</td>
|
||||||
<td>{battle.group_id}</td>
|
<td>{battle.group_id}</td>
|
||||||
<td>{battle.event_id}</td>
|
<td>{battle.id}</td>
|
||||||
<td>{battle.event_name}</td>
|
<td>{battle.event_name}</td>
|
||||||
<StatusWapper>
|
<StatusWapper>
|
||||||
<StatusLabel $status={battle.repeat_type}>
|
<StatusLabel $status={battle.repeat_type}>
|
||||||
@@ -329,8 +335,9 @@ const BattleEvent = () => {
|
|||||||
{battleEventStatus.find(data => data.value === battle.status).name}
|
{battleEventStatus.find(data => data.value === battle.status).name}
|
||||||
</StatusLabel>
|
</StatusLabel>
|
||||||
</StatusWapper>
|
</StatusWapper>
|
||||||
<td>{secondToMinutes(battle.round_time)}분</td>
|
<td>{battle.game_mode_id}</td>
|
||||||
<td>{battle.reward_group_id}</td>
|
{/*<td>{secondToMinutes(battle.round_time)}분</td>*/}
|
||||||
|
{/*<td>{battle.reward_group_id}</td>*/}
|
||||||
<td>{battle.round_count}</td>
|
<td>{battle.round_count}</td>
|
||||||
<td>{battle.hot_time}</td>
|
<td>{battle.hot_time}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -359,6 +366,7 @@ const BattleEvent = () => {
|
|||||||
setDetailData={setDetailData}
|
setDetailData={setDetailData}
|
||||||
configData={battleConfigData}
|
configData={battleConfigData}
|
||||||
rewardData={battleRewardData}
|
rewardData={battleRewardData}
|
||||||
|
gameModeData={gameModeData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LogDetailModal
|
<LogDetailModal
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { authType, commonStatus, modalTypes, eventStatus } from '../../assets/da
|
|||||||
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper, TextInput, InputItem } from '../../styles/Components';
|
import { Title, FormWrapper, TableStyle, MailTitle, TableWrapper, TextInput, InputItem } from '../../styles/Components';
|
||||||
import CheckBox from '../../components/common/input/CheckBox';
|
import CheckBox from '../../components/common/input/CheckBox';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import EventDetailModal from '../../components/ServiceManage/modal/EventDetailModal';
|
import EventDetailModal from '../../components/modal/EventDetailModal';
|
||||||
import Pagination from '../../components/common/Pagination/Pagination';
|
import Pagination from '../../components/common/Pagination/Pagination';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import DynamicModal from '../../components/common/modal/DynamicModal';
|
import DynamicModal from '../../components/common/modal/DynamicModal';
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
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, currencyType } from '../../assets/data';
|
import { authType, benItems, currencyItemCode } from '../../assets/data';
|
||||||
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 { useAlert } from '../../context/AlertProvider';
|
||||||
@@ -209,7 +209,7 @@ const EventRegist = () => {
|
|||||||
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
const item_cnt = resultData.item_list[itemIndex].item_cnt;
|
||||||
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
resultData.item_list[itemIndex].item_cnt = Number(item_cnt) + Number(resourceCount);
|
||||||
} else {
|
} else {
|
||||||
const name = currencyType.find(well => well.value === resource).name;
|
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -403,7 +403,7 @@ const EventRegist = () => {
|
|||||||
<td>
|
<td>
|
||||||
<RegistInputItem>
|
<RegistInputItem>
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
|
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
|
||||||
{currencyType.filter(data => data.value !== currencyCodeTypes.calium).map((data, index) => (
|
{currencyItemCode.filter(data => data.value !== currencyCodeTypes.calium).map((data, index) => (
|
||||||
<option key={index} value={data.value}>
|
<option key={index} value={data.value}>
|
||||||
{data.name}
|
{data.name}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { convertKTC, timeDiffMinute } from '../../utils';
|
|||||||
import { LandAuctionModal, LandAuctionSearchBar } from '../../components/ServiceManage';
|
import { LandAuctionModal, LandAuctionSearchBar } from '../../components/ServiceManage';
|
||||||
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
||||||
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/searchBar/LandAuctionSearchBar';
|
||||||
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';
|
import { StatusWapper, ChargeBtn, StatusLabel } from '../../styles/ModuleComponents';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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/modal/MailDetailModal';
|
||||||
import Pagination from '../../components/common/Pagination/Pagination';
|
import Pagination from '../../components/common/Pagination/Pagination';
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
import { authList } from '../../store/authList';
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
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 { benItems, HourList, mailType, MinuteList, currencyType, userType } from '../../assets/data';
|
import { benItems, HourList, mailType, MinuteList, currencyItemCode, userType } 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';
|
||||||
@@ -243,7 +243,7 @@ const MailRegist = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const name = currencyType.find(well => well.value === resource).name;
|
const name = currencyItemCode.find(well => well.value === resource).name;
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -578,7 +578,7 @@ const MailRegist = () => {
|
|||||||
<td>
|
<td>
|
||||||
<InputItem>
|
<InputItem>
|
||||||
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
|
<SelectInput onChange={e => setResource(e.target.value)} value={resource}>
|
||||||
{currencyType.map((data, index) => (
|
{currencyItemCode.map((data, index) => (
|
||||||
<option key={index} value={data.value}>
|
<option key={index} value={data.value}>
|
||||||
{data.name}
|
{data.name}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
|||||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
||||||
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import MenuBannerModal from '../../components/ServiceManage/modal/MenuBannerModal';
|
|
||||||
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
||||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
import { useLoading } from '../../context/LoadingProvider';
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||||
|
import MenuBannerDetailModal from '../../components/modal/MenuBannerDetailModal';
|
||||||
|
|
||||||
const MenuBanner = () => {
|
const MenuBanner = () => {
|
||||||
const tableRef = useRef(null);
|
const tableRef = useRef(null);
|
||||||
@@ -38,7 +38,6 @@ const MenuBanner = () => {
|
|||||||
} = useModal({
|
} = useModal({
|
||||||
detail: 'hidden',
|
detail: 'hidden',
|
||||||
});
|
});
|
||||||
const [modalType, setModalType] = useState('regist');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
config,
|
config,
|
||||||
@@ -67,9 +66,8 @@ const MenuBanner = () => {
|
|||||||
const handleAction = async (action, item = null) => {
|
const handleAction = async (action, item = null) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "detail":
|
case "detail":
|
||||||
await MenuBannerDetailView(token, item).then(data => {
|
await MenuBannerDetailView(token, item.id).then(data => {
|
||||||
setDetailData(data.event_detail);
|
setDetailData(data.detail);
|
||||||
setModalType('modify');
|
|
||||||
handleModalView('detail');
|
handleModalView('detail');
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -183,8 +181,7 @@ const MenuBanner = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 상세 */}
|
{/* 상세 */}
|
||||||
<MenuBannerModal
|
<MenuBannerDetailModal
|
||||||
modalType={modalType}
|
|
||||||
detailView={modalState.detailModal}
|
detailView={modalState.detailModal}
|
||||||
handleDetailView={() => handleModalClose('detail')}
|
handleDetailView={() => handleModalClose('detail')}
|
||||||
content={detailData}
|
content={detailData}
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminCo
|
|||||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
||||||
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
||||||
import { opMenuBannerStatus } from '../../assets/data/options';
|
import { opMenuBannerStatus } from '../../assets/data/options';
|
||||||
import MenuBannerSearchBar, { useMenuBannerSearch } from '../../components/ServiceManage/searchBar/MenuBannerSearchBar';
|
import MenuBannerSearchBar, { useMenuBannerSearch } from '../../components/searchBar/MenuBannerSearchBar';
|
||||||
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import MenuBannerModal from '../../components/ServiceManage/modal/MenuBannerModal';
|
import MenuBannerModal from '../../components/modal/MenuBannerModal';
|
||||||
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
||||||
|
|
||||||
const MenuBanner = () => {
|
const MenuBanner = () => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { useModal, withAuth } from '../../hooks/hook';
|
|||||||
import { DynamicModal, TopButton } from '../../components/common';
|
import { DynamicModal, TopButton } from '../../components/common';
|
||||||
import { opInitDataType, opSuccessType } from '../../assets/data/options';
|
import { opInitDataType, opSuccessType } from '../../assets/data/options';
|
||||||
import { InitData } from '../../apis/Data';
|
import { InitData } from '../../apis/Data';
|
||||||
import DataInitSearchBar, { useDataInitSearch } from '../../components/ServiceManage/searchBar/DataInitSearchBar';
|
import DataInitSearchBar, { useDataInitSearch } from '../../components/searchBar/DataInitSearchBar';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { useLoading } from '../../context/LoadingProvider';
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|||||||
@@ -174,6 +174,21 @@ export const BtnWrapper = styled.div`
|
|||||||
padding-top: ${props => props.$paddingTop};
|
padding-top: ${props => props.$paddingTop};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 버튼 그룹을 위한 스타일드 컴포넌트
|
||||||
|
*/
|
||||||
|
export const ButtonGroupWrapper = styled.div`
|
||||||
|
width: ${props => props.width};
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-flow: ${props => props.$flow};
|
||||||
|
justify-content: ${props => props.$justify};
|
||||||
|
gap: ${props => props.$gap};
|
||||||
|
margin-top: ${props => props.$marginTop};
|
||||||
|
margin-bottom: ${props => props.$marginBottom};
|
||||||
|
padding-top: ${props => props.$paddingTop};
|
||||||
|
`;
|
||||||
|
|
||||||
export const Title = styled.h2`
|
export const Title = styled.h2`
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -679,6 +694,12 @@ export const SearchItem = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const DownloadContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const SearchRow = styled.div`
|
export const SearchRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -690,3 +711,60 @@ export const SearchRow = styled.div`
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const TabScroll = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TabItem = styled(Link)`
|
||||||
|
display: inline-flex;
|
||||||
|
width: 120px;
|
||||||
|
height: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-left: 1px solid #d9d9d9;
|
||||||
|
&:hover {
|
||||||
|
background: #888;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
${props =>
|
||||||
|
props.$state === 'active' &&
|
||||||
|
`
|
||||||
|
background: #888;
|
||||||
|
color: #fff;`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TabWrapper = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
li:first-child {
|
||||||
|
${TabItem} {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CircularProgressWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TotalRow = styled.tr`
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ImagePreview = styled.img`
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
`;
|
||||||
@@ -87,3 +87,139 @@ export const getFieldLabel = (key, value) => {
|
|||||||
|
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const numberFormatter = {
|
||||||
|
formatCurrency: (number, decimals = 2) => {
|
||||||
|
if (number === null || number === undefined) return '0';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const num = typeof number === 'string' ? parseFloat(number) : number;
|
||||||
|
if (isNaN(num)) return '0';
|
||||||
|
|
||||||
|
// 정수인지 확인
|
||||||
|
const isInteger = Number.isInteger(num);
|
||||||
|
|
||||||
|
// 작은 수이거나 소수점이 있는 경우
|
||||||
|
if ((Math.abs(num) < 1 && num !== 0) || !isInteger) {
|
||||||
|
return num.toFixed(decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 정수인 경우
|
||||||
|
return new Intl.NumberFormat('ko-KR', {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}).format(num);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Currency formatting error:', e);
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 응답으로부터 파일 다운로드를 처리하는 함수
|
||||||
|
* @param {Object} response - API 응답 객체
|
||||||
|
* @param {Object} response.data - 응답 데이터
|
||||||
|
* @param {Object} response.headers - 응답 헤더
|
||||||
|
* @param {Object} options - 추가 옵션
|
||||||
|
* @param {string} options.defaultFileName - 기본 파일명 (기본값: 'download')
|
||||||
|
* @returns {void}
|
||||||
|
* @throws {Error} 데이터가 없거나 파일 형식이 잘못된 경우
|
||||||
|
*/
|
||||||
|
export const responseFileDownload = (response, options = {}) => {
|
||||||
|
const { defaultFileName = 'download' } = options;
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
console.log(response);
|
||||||
|
throw new Error('No data received');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers['content-type'] || response.headers['Content-Type'];
|
||||||
|
const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition'];
|
||||||
|
|
||||||
|
// Excel, CSV, ZIP 파일 형식 검증 (CSV 추가)
|
||||||
|
const isValidType = contentType && (
|
||||||
|
contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') ||
|
||||||
|
contentType.includes('text/csv') ||
|
||||||
|
contentType.includes('application/zip')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isValidType) {
|
||||||
|
console.log('Invalid content type:', contentType);
|
||||||
|
console.log('Full response:', response);
|
||||||
|
throw new Error(`잘못된 파일 형식입니다. Content-Type: ${contentType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = defaultFileName;
|
||||||
|
let fileExtension = '.csv';
|
||||||
|
let mimeType = 'text/csv; charset=UTF-8';
|
||||||
|
|
||||||
|
// 파일 타입별 처리
|
||||||
|
if (contentType.includes('text/csv')) {
|
||||||
|
// CSV 파일 처리
|
||||||
|
fileExtension = '.csv';
|
||||||
|
mimeType = 'text/csv; charset=UTF-8';
|
||||||
|
fileName = `${defaultFileName}`;
|
||||||
|
} else if (contentType.includes('application/zip')) {
|
||||||
|
// ZIP 파일 처리
|
||||||
|
fileExtension = '.zip';
|
||||||
|
mimeType = 'application/zip';
|
||||||
|
fileName = `${defaultFileName}_multiple_files`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content-Disposition에서 파일명 추출
|
||||||
|
if (contentDisposition) {
|
||||||
|
const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||||
|
if (fileNameMatch && fileNameMatch[1]) {
|
||||||
|
let extractedFileName = fileNameMatch[1].replace(/['"]/g, '');
|
||||||
|
try {
|
||||||
|
fileName = decodeURIComponent(extractedFileName);
|
||||||
|
} catch (e) {
|
||||||
|
// decodeURIComponent 실패 시 원본 사용
|
||||||
|
fileName = extractedFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileName = fileName + fileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([response.data], { type: mimeType });
|
||||||
|
|
||||||
|
// 파일 유효성 검사 (CSV는 더 작을 수 있으므로 조건 완화)
|
||||||
|
if (blob.size === 0) {
|
||||||
|
throw new Error('다운로드된 파일이 비어있습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSV 파일은 크기 검사 완화
|
||||||
|
const minSize = contentType.includes('text/csv') ? 100 : 1024;
|
||||||
|
if (blob.size < minSize) {
|
||||||
|
throw new Error(`파일 크기가 너무 작습니다. 올바른 파일이 아닐 수 있습니다. (${blob.size} bytes)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파일 다운로드 실행
|
||||||
|
const href = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = href;
|
||||||
|
link.download = fileName;
|
||||||
|
link.style.display = 'none';
|
||||||
|
link.rel = 'noopener noreferrer';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 정리
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(href);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const calculateTotals = (data) => {
|
||||||
|
return data?.reduce((acc, row) => {
|
||||||
|
Object.entries(row).forEach(([key, value]) => {
|
||||||
|
if (!isNaN(value) && value !== '') {
|
||||||
|
acc[key] = (acc[key] || 0) + Number(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {}) || {};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user