Compare commits
15 Commits
67c048a11d
...
d3470e3d03
| Author | SHA1 | Date | |
|---|---|---|---|
| d3470e3d03 | |||
| 99943c0b19 | |||
| 26114c9a9b | |||
| 952701f68b | |||
| 7041d4a649 | |||
| 7fa9abcad4 | |||
| 943b146496 | |||
| 88585c1b24 | |||
| 991462c0d7 | |||
| bab594918e | |||
| c4099c0cf0 | |||
| 0d8fb7b327 | |||
| d4db33bcf0 | |||
| 38dac99278 | |||
| 28094e1c48 |
@@ -3,6 +3,10 @@ server {
|
|||||||
listen [::]:8080;
|
listen [::]:8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 300s;
|
||||||
|
client_header_timeout 300s;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/admintool;
|
root /usr/share/nginx/admintool;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
@@ -16,6 +20,11 @@ server {
|
|||||||
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;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_request_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ server {
|
|||||||
listen [::]:8080;
|
listen [::]:8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 300s;
|
||||||
|
client_header_timeout 300s;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/admintool;
|
root /usr/share/nginx/admintool;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
@@ -16,6 +20,11 @@ server {
|
|||||||
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;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_request_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ server {
|
|||||||
listen [::]:8080;
|
listen [::]:8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 300s;
|
||||||
|
client_header_timeout 300s;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/admintool;
|
root /usr/share/nginx/admintool;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
@@ -16,6 +20,11 @@ server {
|
|||||||
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;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_request_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ server {
|
|||||||
listen [::]:8080;
|
listen [::]:8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
|
client_max_body_size 100M;
|
||||||
|
client_body_timeout 300s;
|
||||||
|
client_header_timeout 300s;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/admintool;
|
root /usr/share/nginx/admintool;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
@@ -16,6 +20,11 @@ server {
|
|||||||
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;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_request_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
1744
package-lock.json
generated
1744
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -62,10 +62,14 @@ export const userIndexExport = async (token, filename, sendDate, endDate) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Retention
|
// Retention
|
||||||
export const RetentionIndexView = async (token, start_dt, end_dt) => {
|
export const RetentionIndexView = async (token, startDate, endDate, order, size, currentPage) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.get(`/api/v1/indicators/retention/list?start_dt=${start_dt}&end_dt=${end_dt}`, {
|
const res = await Axios.get(`/api/v1/indicators/retention/list?start_dt=${startDate}&end_dt=${endDate}
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
&orderby=${order}&page_no=${currentPage}&page_size=${size}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data.data;
|
return res.data.data;
|
||||||
|
|||||||
151
src/apis/Log.js
151
src/apis/Log.js
@@ -128,3 +128,154 @@ export const GameCurrencyDetailLogExport = async (token, params, fileName) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getItemDetailList = async (token, searchType, searchData, tranId, logAction, itemLargeType, itemSmallType, countDeltaType, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/item/detail/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}
|
||||||
|
&log_action=${logAction}&item_large_type=${itemLargeType}&item_small_type=${itemSmallType}&count_delta_type=${countDeltaType}&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('getItemDetailList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameItemDetailLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
console.log(params);
|
||||||
|
await Axios.post(`/api/v1/log/item/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('GameItemDetailLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrencyItemList = async (token, searchType, searchData, tranId, logAction, currencyType, amountDeltaType, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/currency-item/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('getItemDetailList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameCurrencyItemLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
console.log(params);
|
||||||
|
await Axios.post(`/api/v1/log/currency-item/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('GameCurrencyItemLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserCreateList = async (token, searchType, searchData, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/user/create/list?search_type=${searchType}&search_data=${searchData}&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('getUserCreateList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserLoginDetailList = async (token, searchType, searchData, tranId, startDate, endDate, order, size, currentPage) => {
|
||||||
|
try {
|
||||||
|
const response = await Axios.get(`/api/v1/log/user/login/list?search_type=${searchType}&search_data=${searchData}&tran_id=${tranId}
|
||||||
|
&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('getUserLoginDetailList API error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameUserCreateLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
console.log(params);
|
||||||
|
await Axios.post(`/api/v1/log/user/create/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('GameUserCreateLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GameUserLoginLogExport = async (token, params, fileName) => {
|
||||||
|
try {
|
||||||
|
console.log(params);
|
||||||
|
await Axios.post(`/api/v1/log/user/login/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('GameUserLoginLogExport Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -67,11 +67,10 @@ export const MenuBannerModify = async (token, id, params) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 삭제
|
// 삭제
|
||||||
export const MenuBannerDelete = async (token, params) => {
|
export const MenuBannerDelete = async (token, id) => {
|
||||||
try {
|
try {
|
||||||
const res = await Axios.delete(`/api/v1/menu/banner/delete`, {
|
const res = await Axios.delete(`/api/v1/menu/banner/delete?id=${id}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
data: { list: params },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
@@ -185,6 +185,21 @@ export const UserQuestView = async (token, guid) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//퀘스트 테스크 완료
|
||||||
|
export const UserQuestTaskComplete = async (token, params) => {
|
||||||
|
try {
|
||||||
|
const res = await Axios.post(`/api/v1/users/quest/task`, params, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw new Error('UserQuestTaskComplete Error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 친구목록 조회
|
// 친구목록 조회
|
||||||
export const UserFriendListView = async (token, guid) => {
|
export const UserFriendListView = async (token, guid) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export * from './Event';
|
|||||||
export * from './Calium';
|
export * from './Calium';
|
||||||
export * from './Land';
|
export * from './Land';
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
export * from './OpenAI';
|
// export * from './OpenAI';
|
||||||
export * from './Log';
|
export * from './Log';
|
||||||
|
|
||||||
const apiModules = {};
|
const apiModules = {};
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ 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 STORAGE_GAME_LOG_CURRENCY_SEARCH = 'gameLogCurrencySearchParam';
|
||||||
|
export const STORAGE_GAME_LOG_ITEM_SEARCH = 'gameLogItemSearchParam';
|
||||||
|
export const STORAGE_GAME_LOG_USER_CREATE_SEARCH = 'gameLogUserCreateSearchParam';
|
||||||
|
export const STORAGE_GAME_LOG_USER_LOGIN_SEARCH = 'gameLogUserLoginSearchParam';
|
||||||
export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem';
|
export const LOG_ACTION_FAIL_CALIUM_ECHO = 'FailCaliumEchoSystem';
|
||||||
|
export const BATTLE_EVENT_OPERATION_TIME_WAIT_SECONDS = 300;
|
||||||
|
|
||||||
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };
|
export { INITIAL_PAGE_SIZE, INITIAL_CURRENT_PAGE, INITIAL_PAGE_LIMIT };
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ export const STATUS_STYLES = {
|
|||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
WAIT: {
|
WAIT: {
|
||||||
background: '#DEBB46',
|
background: '#FAAD14',
|
||||||
color: 'black'
|
color: 'black'
|
||||||
},
|
},
|
||||||
FAIL: {
|
FAIL: {
|
||||||
background: '#D33B27',
|
background: '#ff4d4f',
|
||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
FINISH: {
|
FINISH: {
|
||||||
@@ -78,11 +78,11 @@ export const STATUS_STYLES = {
|
|||||||
color: 'black'
|
color: 'black'
|
||||||
},
|
},
|
||||||
REJECT: {
|
REJECT: {
|
||||||
background: '#D33B27',
|
background: '#ff4d4f',
|
||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
CANCEL: {
|
CANCEL: {
|
||||||
background: '#D33B27',
|
background: '#ff4d4f',
|
||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
RESV_START: {
|
RESV_START: {
|
||||||
@@ -106,7 +106,7 @@ export const STATUS_STYLES = {
|
|||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
REGISTER: {
|
REGISTER: {
|
||||||
background: '#DEBB46',
|
background: '#FAAD14',
|
||||||
color: 'black'
|
color: 'black'
|
||||||
},
|
},
|
||||||
STOP: {
|
STOP: {
|
||||||
@@ -192,4 +192,5 @@ export const historyTables = {
|
|||||||
notice: 'notice',
|
notice: 'notice',
|
||||||
battleEvent: 'battle_event',
|
battleEvent: 'battle_event',
|
||||||
caliumRequest: 'calium_request',
|
caliumRequest: 'calium_request',
|
||||||
|
menuBanner: 'menu_banner',
|
||||||
}
|
}
|
||||||
@@ -103,14 +103,14 @@ export const menuConfig = {
|
|||||||
view: false,
|
view: false,
|
||||||
authLevel: adminAuthLevel.NONE
|
authLevel: adminAuthLevel.NONE
|
||||||
},
|
},
|
||||||
cryptview: {
|
// cryptview: {
|
||||||
title: '크립토 조회',
|
// title: '크립토 조회',
|
||||||
permissions: {
|
// permissions: {
|
||||||
read: authType.cryptoRead
|
// read: authType.cryptoRead
|
||||||
},
|
// },
|
||||||
view: false,
|
// view: false,
|
||||||
authLevel: adminAuthLevel.NONE
|
// authLevel: adminAuthLevel.NONE
|
||||||
},
|
// },
|
||||||
businesslogview: {
|
businesslogview: {
|
||||||
title: '비즈니스 로그 조회',
|
title: '비즈니스 로그 조회',
|
||||||
permissions: {
|
permissions: {
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ export const languageType = [
|
|||||||
|
|
||||||
export const TabGameLogList = [
|
export const TabGameLogList = [
|
||||||
{ value: 'CURRENCY', name: '재화 로그' },
|
{ value: 'CURRENCY', name: '재화 로그' },
|
||||||
// { value: 'ITEM', name: '아이템 로그' },
|
{ value: 'ITEM', name: '아이템 로그' },
|
||||||
// { value: 'TRADE', name: '거래 로그' },
|
{ value: 'CURRENCYITEM', name: '재화(아이템) 로그' },
|
||||||
|
{ value: 'USERCREATE', name: '유저생성 로그' },
|
||||||
|
{ value: 'USERLOGIN', name: '유저로그인 로그' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TabEconomicIndexList = [
|
export const TabEconomicIndexList = [
|
||||||
@@ -18,6 +20,13 @@ export const TabEconomicIndexList = [
|
|||||||
// { value: 'instance', name: '인스턴스' },
|
// { value: 'instance', name: '인스턴스' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TabUserIndexList = [
|
||||||
|
{ value: 'USER', name: '이용자 지표' },
|
||||||
|
{ value: 'RETENTION', name: '잔존율' },
|
||||||
|
// { value: 'SEGMENT', name: 'Segment' },
|
||||||
|
// { value: 'PLAYTIME', name: '플레이타임' },
|
||||||
|
];
|
||||||
|
|
||||||
export const mailSendType = [
|
export const mailSendType = [
|
||||||
{ value: 'ALL', name: '전체' },
|
{ value: 'ALL', name: '전체' },
|
||||||
{ value: 'RESERVE_SEND', name: '예약 발송' },
|
{ value: 'RESERVE_SEND', name: '예약 발송' },
|
||||||
@@ -95,6 +104,17 @@ export const landAuctionStatus = [
|
|||||||
{ value: 'FAIL', name: '실패' },
|
{ value: 'FAIL', name: '실패' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const questStatus = [
|
||||||
|
{ value: 'WAIT', name: '미완료' },
|
||||||
|
{ value: 'COMPLETE', name: '완료' },
|
||||||
|
{ value: 'RUNNING', name: '진행중' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const questCompleteStatusType = [
|
||||||
|
{ value: 0, name: '미완료' },
|
||||||
|
{ value: 1, name: '완료' }
|
||||||
|
]
|
||||||
|
|
||||||
export const currencyItemCode = [
|
export const currencyItemCode = [
|
||||||
{ value: '19010001', name: '골드' },
|
{ value: '19010001', name: '골드' },
|
||||||
{ value: '19010002', name: '사파이어' },
|
{ value: '19010002', name: '사파이어' },
|
||||||
@@ -225,6 +245,24 @@ export const amountDeltaType = [
|
|||||||
{value: 'None', name: '' },
|
{value: 'None', name: '' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const countDeltaType = [
|
||||||
|
{value: 'Acquire', name: '획득' },
|
||||||
|
{value: 'Consume', name: '소모' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const itemTypeLarge = [
|
||||||
|
{value: 'TOOL', name: '도구' },
|
||||||
|
{value: 'EXPENDABLE', name: '소모품' },
|
||||||
|
{value: 'TICKET', name: '티켓' },
|
||||||
|
{value: 'RAND_BOX', name: '랜덤 박스' },
|
||||||
|
{value: 'CLOTH', name: '의상' },
|
||||||
|
{value: 'AVATAR', name: '아바타' },
|
||||||
|
{value: 'PROP', name: '프랍(오브젝트)' },
|
||||||
|
{value: 'TATTOO', name: '타투' },
|
||||||
|
{value: 'CURRENCY', name: '재화' },
|
||||||
|
{value: 'SET_BOX', name: '세트 박스' }
|
||||||
|
]
|
||||||
|
|
||||||
export const battleEventStatus = [
|
export const battleEventStatus = [
|
||||||
{ value: 'ALL', name: '전체' },
|
{ value: 'ALL', name: '전체' },
|
||||||
{ value: 'WAIT', name: '대기' },
|
{ value: 'WAIT', name: '대기' },
|
||||||
|
|||||||
@@ -14,14 +14,14 @@
|
|||||||
"text": "선택 삭제",
|
"text": "선택 삭제",
|
||||||
"theme": "line",
|
"theme": "line",
|
||||||
"disableWhen": "noSelection",
|
"disableWhen": "noSelection",
|
||||||
"requiredAuth": "battleEventDelete",
|
"requiredAuth": "menuBannerDelete",
|
||||||
"action": "delete"
|
"action": "delete"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "register",
|
"id": "register",
|
||||||
"text": "이미지 등록",
|
"text": "이미지 등록",
|
||||||
"theme": "primary",
|
"theme": "primary",
|
||||||
"requiredAuth": "battleEventUpdate",
|
"requiredAuth": "menuBannerUpdate",
|
||||||
"action": "navigate",
|
"action": "navigate",
|
||||||
"navigateTo": "/servicemanage/menubanner/menubannerregist"
|
"navigateTo": "/servicemanage/menubanner/menubannerregist"
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,12 @@
|
|||||||
"width": "70px",
|
"width": "70px",
|
||||||
"title": "번호"
|
"title": "번호"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "order_id",
|
||||||
|
"type": "text",
|
||||||
|
"width": "70px",
|
||||||
|
"title": "순서"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "status",
|
"id": "status",
|
||||||
"type": "status",
|
"type": "status",
|
||||||
@@ -94,10 +100,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "update_by",
|
"id": "history",
|
||||||
"type": "text",
|
"type": "button",
|
||||||
"width": "150px",
|
"width": "120px",
|
||||||
"title": "히스토리"
|
"title": "히스토리",
|
||||||
|
"text": "히스토리"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort": {
|
"sort": {
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export const currencyCodeTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const languageNames = {
|
export const languageNames = {
|
||||||
'KO': '한국어',
|
'Ko': '한국어',
|
||||||
'EN': '영어',
|
'En': '영어',
|
||||||
'JA': '일본어',
|
'Ja': '일본어',
|
||||||
};
|
};
|
||||||
|
|||||||
168
src/components/DataManage/CurrencyItemLogContent.js
Normal file
168
src/components/DataManage/CurrencyItemLogContent.js
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
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 { CurrencyItemLogSearchBar, useCurrencyItemLogSearch } 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';
|
||||||
|
import { GameCurrencyItemLogExport } from '../../apis';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
|
const CurrencyItemLogContent = ({ 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
|
||||||
|
} = useCurrencyItemLogSearch(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: '150px' },
|
||||||
|
{ 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: '80px' },
|
||||||
|
// { id: 'deltaAmount', label: '수량원본', width: '120px' },
|
||||||
|
{ id: 'currencyAmount', label: '잔량', width: '80px' },
|
||||||
|
{ id: 'itemId', label: '아이템ID', width: '150px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<CurrencyItemLogSearchBar
|
||||||
|
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="GameCurrencyItemLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_CURRENCY_ITEM')}
|
||||||
|
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_item_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>{numberFormatter.formatCurrency(item.currencyAmount)}</td>
|
||||||
|
<td>{item.itemIDs}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.currency_item_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CurrencyItemLogContent;
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from '../../assets/data/adminConstants';
|
} from '../../assets/data/adminConstants';
|
||||||
import ExcelExportButton from '../common/button/ExcelExportButton';
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
import CircularProgress from '../common/CircularProgress';
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
const CurrencyLogContent = ({ active }) => {
|
const CurrencyLogContent = ({ active }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -83,7 +84,7 @@ const CurrencyLogContent = ({ active }) => {
|
|||||||
if(!active) return null;
|
if(!active) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CurrencyLogSearchBar
|
<CurrencyLogSearchBar
|
||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
@@ -159,7 +160,7 @@ const CurrencyLogContent = ({ active }) => {
|
|||||||
<TopButton />
|
<TopButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CurrencyLogContent;
|
export default CurrencyLogContent;
|
||||||
171
src/components/DataManage/ItemLogContent.js
Normal file
171
src/components/DataManage/ItemLogContent.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
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 { ItemLogSearchBar, useItemLogSearch } 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, STORAGE_GAME_LOG_ITEM_SEARCH,
|
||||||
|
} from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
import { countDeltaType, itemTypeLarge } from '../../assets/data/options';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
|
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
|
||||||
|
} = useItemLogSearch(token, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(active) {
|
||||||
|
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
||||||
|
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_ITEM_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_ITEM_SEARCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
// { id: 'logDay', label: '일자', width: '120px' },
|
||||||
|
{ id: 'logTime', label: '일시', width: '150px' },
|
||||||
|
{ 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: '120px' },
|
||||||
|
{ id: 'itemId', label: '아이템ID', width: '80px' },
|
||||||
|
{ id: 'itemName', label: '아이템명', width: '150px' },
|
||||||
|
{ id: 'itemTypeLarge', label: 'LargeType', width: '100px' },
|
||||||
|
{ id: 'itemTypeSmall', label: 'SmallType', width: '100px' },
|
||||||
|
{ id: 'countDeltaType', label: '증감유형', width: '80px' },
|
||||||
|
{ id: 'deltaCount', label: '수량', width: '80px' },
|
||||||
|
{ id: 'stackCount', label: '총량', width: '80px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<ItemLogSearchBar
|
||||||
|
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="GameItemDetailLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_ITEM')}
|
||||||
|
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?.item_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>{item.itemId}</td>
|
||||||
|
<td>{item.itemName}</td>
|
||||||
|
<td>{itemTypeLarge.find(data => data.value === item.itemTypeLarge)?.name || item.itemTypeLarge}</td>
|
||||||
|
<td>{item.itemTypeSmall}</td>
|
||||||
|
<td>{countDeltaType.find(data => data.value === item.countDeltaType)?.name || item.countDeltaType}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.deltaCount)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.stackCount)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.item_detail_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CurrencyLogContent;
|
||||||
@@ -5,15 +5,31 @@ import { BtnWrapper, TableStyle } from '../../styles/Components';
|
|||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import Modal from '../../components/common/modal/Modal';
|
||||||
import { useEffect, useState, Fragment } from 'react';
|
import { useEffect, useState, Fragment } from 'react';
|
||||||
|
import { questStatus } from '../../assets/data/options';
|
||||||
|
import { commonStatus } from '../../assets/data';
|
||||||
|
import { useModal } from '../../hooks/hook';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
|
||||||
|
const QuestDetailModal = ({ detailPop, handleClick, detailQuest, handleQuestComplete }) => {
|
||||||
|
const { showModal } = useAlert();
|
||||||
|
|
||||||
const QuestDetailModal = ({ detailPop, handleClick, detailQuest }) => {
|
|
||||||
const [detailList, setDetailList] = useState([])
|
const [detailList, setDetailList] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Array.isArray(detailQuest) && setDetailList(detailQuest)
|
Array.isArray(detailQuest.detailQuest) && setDetailList(detailQuest.detailQuest)
|
||||||
|
|
||||||
}, [detailQuest])
|
}, [detailQuest])
|
||||||
// const questlist = [{ taskNo: detailQuest.task_no, taskName: detailQuest.quest_name, counter: detailQuest.counter, state: detailQuest.status }];
|
|
||||||
|
const handleQuestCompleteConfirm = (data) => {
|
||||||
|
const params = {...data, quest_key: detailQuest.quest_key}
|
||||||
|
showModal('QUEST_TASK_COMPLETE_CONFIRM',{
|
||||||
|
type: alertTypes.confirm,
|
||||||
|
onConfirm: () => {
|
||||||
|
handleQuestComplete(params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal $view={detailPop} min="480px">
|
<Modal $view={detailPop} min="480px">
|
||||||
@@ -25,7 +41,8 @@ const QuestDetailModal = ({ detailPop, handleClick, detailQuest }) => {
|
|||||||
<th width="80">Task No</th>
|
<th width="80">Task No</th>
|
||||||
<th>Task Name</th>
|
<th>Task Name</th>
|
||||||
<th width="120">Counter</th>
|
<th width="120">Counter</th>
|
||||||
<th width="120">State</th>
|
<th width="120">상태</th>
|
||||||
|
<th width="120">완료처리</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -36,7 +53,10 @@ const QuestDetailModal = ({ detailPop, handleClick, detailQuest }) => {
|
|||||||
<td>{el.task_no}</td>
|
<td>{el.task_no}</td>
|
||||||
<td>{el.quest_name}</td>
|
<td>{el.quest_name}</td>
|
||||||
<td>{el.counter}</td>
|
<td>{el.counter}</td>
|
||||||
<td>{el.status}</td>
|
<td>{questStatus.find(data => data.value === el.status)?.name}</td>
|
||||||
|
<td>
|
||||||
|
{ el.status === commonStatus.running && <Button text="완료" theme="line" handleClick={() => handleQuestCompleteConfirm(el)} />}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
146
src/components/DataManage/UserCreateLogContent.js
Normal file
146
src/components/DataManage/UserCreateLogContent.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircularProgressWrapper,
|
||||||
|
FormWrapper,
|
||||||
|
TableStyle,
|
||||||
|
TableWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { useUserCreateLogSearch, UserCreateLogSearchBar } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import Pagination from '../common/Pagination/Pagination';
|
||||||
|
import {
|
||||||
|
INITIAL_PAGE_LIMIT,STORAGE_GAME_LOG_USER_CREATE_SEARCH,
|
||||||
|
} from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
|
const UserCreateLogContent = ({ 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,
|
||||||
|
updateSearchParams
|
||||||
|
} = useUserCreateLogSearch(token, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(active) {
|
||||||
|
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
||||||
|
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_USER_CREATE_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_USER_CREATE_SEARCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '120px' },
|
||||||
|
{ id: 'accountId', label: 'account ID', width: '80px' },
|
||||||
|
{ id: 'userGuid', label: 'GUID', width: '200px' },
|
||||||
|
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
||||||
|
{ id: 'createTime', label: '생성일시', width: '200px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<UserCreateLogSearchBar
|
||||||
|
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="GameUserCreateLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_USER_CREATE')}
|
||||||
|
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?.user_create_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>{item.createdTime}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.user_create_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UserCreateLogContent;
|
||||||
159
src/components/DataManage/UserLoginLogContent.js
Normal file
159
src/components/DataManage/UserLoginLogContent.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircularProgressWrapper,
|
||||||
|
FormWrapper,
|
||||||
|
TableStyle,
|
||||||
|
TableWrapper,
|
||||||
|
} from '../../styles/Components';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { useUserLoginLogSearch, UserLoginLogSearchBar } from '../searchBar';
|
||||||
|
import { TopButton, ViewTableInfo } from '../common';
|
||||||
|
import Pagination from '../common/Pagination/Pagination';
|
||||||
|
import {
|
||||||
|
INITIAL_PAGE_LIMIT, STORAGE_GAME_LOG_USER_LOGIN_SEARCH,
|
||||||
|
} from '../../assets/data/adminConstants';
|
||||||
|
import ExcelExportButton from '../common/button/ExcelExportButton';
|
||||||
|
import CircularProgress from '../common/CircularProgress';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
|
||||||
|
const UserLoginLogContent = ({ 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
|
||||||
|
} = useUserLoginLogSearch(token, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(active) {
|
||||||
|
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
||||||
|
const paramsData = sessionStorage.getItem(STORAGE_GAME_LOG_USER_LOGIN_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_USER_LOGIN_SEARCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
const tableHeaders = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ id: 'logDay', label: '일자', width: '100px' },
|
||||||
|
{ id: 'accountId', label: 'account ID', width: '80px' },
|
||||||
|
{ id: 'userGuid', label: 'GUID', width: '180px' },
|
||||||
|
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
||||||
|
{ id: 'tranId', label: '트랜잭션 ID', width: '200px' },
|
||||||
|
{ id: 'loginTime', label: '로그인시간', width: '150px' },
|
||||||
|
{ id: 'logoutTime', label: '로그아웃시간', width: '120px' },
|
||||||
|
{ id: 'serverType', label: '서버종류', width: '80px' },
|
||||||
|
{ id: 'languageType', label: '언어', width: '80px' },
|
||||||
|
{ id: 'playtime', label: '플레이시간(분)', width: '80px' },
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!active) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedPageWrapper>
|
||||||
|
<FormWrapper>
|
||||||
|
<UserLoginLogSearchBar
|
||||||
|
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="GameUserLoginLogExport"
|
||||||
|
params={searchParams}
|
||||||
|
fileName={t('FILE_GAME_LOG_USER_LOGIN')}
|
||||||
|
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?.user_login_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>{item.tranId}</td>
|
||||||
|
<td>{item.loginTime}</td>
|
||||||
|
<td>{item.logoutTime}</td>
|
||||||
|
<td>{item.serverType}</td>
|
||||||
|
<td>{item.languageType}</td>
|
||||||
|
<td>{numberFormatter.formatSecondToMinuts(item.playtime)}</td>
|
||||||
|
</tr>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</TableStyle>
|
||||||
|
</TableWrapper>
|
||||||
|
{dataList?.user_login_list &&
|
||||||
|
<Pagination
|
||||||
|
postsPerPage={searchParams.page_size}
|
||||||
|
totalPosts={dataList?.total_all}
|
||||||
|
setCurrentPage={handlePageChange}
|
||||||
|
currentPage={searchParams.page_no}
|
||||||
|
pageLimit={INITIAL_PAGE_LIMIT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<TopButton />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UserLoginLogContent;
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import Button from '../common/button/Button';
|
|
||||||
import { InfoSubTitle, UserDefaultTable, UserInfoTable, UserTableWrapper } from '../../styles/ModuleComponents';
|
import { InfoSubTitle, UserDefaultTable, UserInfoTable, UserTableWrapper } from '../../styles/ModuleComponents';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
import { UserInventoryView, UserMyhomeView } from '../../apis';
|
import { UserMyhomeView } from '../../apis';
|
||||||
|
import { SelectInput } from '../../styles/Components';
|
||||||
|
|
||||||
const UserMyHomeInfo = ({ userInfo }) => {
|
const UserMyHomeInfo = ({ userInfo }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [dataList, setDataList] = useState();
|
const [dataList, setDataList] = useState();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selectedHome, setSelectedHome] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||||
@@ -23,11 +21,18 @@ const UserMyHomeInfo = ({ userInfo }) => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
await UserMyhomeView(token, userInfo.guid).then(data => {
|
await UserMyhomeView(token, userInfo.guid).then(data => {
|
||||||
setDataList(data.myhome_info);
|
setDataList(data);
|
||||||
|
if (data.myhome_info && data.myhome_info.length > 0) {
|
||||||
|
setSelectedHome(data.myhome_info[0].myhome_guid);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleHomeChange = (e) => {
|
||||||
|
setSelectedHome(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
loading ? <TableSkeleton count={15}/> :
|
loading ? <TableSkeleton count={15}/> :
|
||||||
dataList &&
|
dataList &&
|
||||||
@@ -36,7 +41,13 @@ const UserMyHomeInfo = ({ userInfo }) => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>마이 홈명</th>
|
<th>마이 홈명</th>
|
||||||
<td>{dataList.myhome_name}</td>
|
<SelectInput onChange={handleHomeChange} value={selectedHome}>
|
||||||
|
{dataList.myhome_info && dataList.myhome_info.map((data, index) => (
|
||||||
|
<option key={index} value={data.myhome_guid}>
|
||||||
|
{data.myhome_name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</UserInfoTable>
|
</UserInfoTable>
|
||||||
@@ -51,15 +62,19 @@ const UserMyHomeInfo = ({ userInfo }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{dataList.prop_list && dataList.prop_list.map((el, idx) => {
|
{dataList.myhome_info.find(home => home.myhome_guid === selectedHome)?.prop_list?.map((el, idx) => (
|
||||||
return (
|
|
||||||
<tr key={idx}>
|
<tr key={idx}>
|
||||||
<td>{idx + 1}</td>
|
<td>{idx + 1}</td>
|
||||||
<td>{el.item_id}</td>
|
<td>{el.item_id}</td>
|
||||||
<td>{el.item_name}</td>
|
<td>{el.item_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
))}
|
||||||
})}
|
{(!dataList.myhome_info.find(home => home.myhome_guid === selectedHome)?.prop_list ||
|
||||||
|
dataList.myhome_info.find(home => home.myhome_guid === selectedHome)?.prop_list.length === 0) && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan="3" style={{textAlign: 'center'}}>{t('TABLE_DATA_NOT_FOUND')}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</UserDefaultTable>
|
</UserDefaultTable>
|
||||||
</UserTableWrapper>
|
</UserTableWrapper>
|
||||||
|
|||||||
@@ -3,15 +3,22 @@ import { useState, useEffect, Fragment } from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
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 { UserQuestView } from '../../apis/Users';
|
import { UserQuestTaskComplete, UserQuestView } from '../../apis/Users';
|
||||||
import { convertKTC } from '../../utils';
|
import { convertKTC } from '../../utils';
|
||||||
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { CaliumCharge } from '../../apis';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { questCompleteStatusType } from '../../assets/data/options';
|
||||||
|
|
||||||
const UserQuestInfo = ({ userInfo }) => {
|
const UserQuestInfo = ({ userInfo }) => {
|
||||||
const [detailPop, setDetailPop] = useState('hidden');
|
const [detailPop, setDetailPop] = useState('hidden');
|
||||||
const [dataList, setDataList] = useState({});
|
const [dataList, setDataList] = useState({});
|
||||||
const [detailQuest, setDetailQuest] = useState({});
|
const [detailQuest, setDetailQuest] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { showModal, showToast } = useAlert();
|
||||||
|
const { withLoading } = useLoading();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(userInfo && Object.keys(userInfo).length > 0) {
|
if(userInfo && Object.keys(userInfo).length > 0) {
|
||||||
@@ -30,10 +37,30 @@ const UserQuestInfo = ({ userInfo }) => {
|
|||||||
const handleClick = data => {
|
const handleClick = data => {
|
||||||
if (detailPop === 'hidden') {
|
if (detailPop === 'hidden') {
|
||||||
setDetailPop('view');
|
setDetailPop('view');
|
||||||
setDetailQuest(data.detailQuest);
|
setDetailQuest(data);
|
||||||
} else setDetailPop('hidden');
|
} else setDetailPop('hidden');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleQuestComplete = async data => {
|
||||||
|
const token = sessionStorage.getItem('token');
|
||||||
|
await withLoading(async () => {
|
||||||
|
const params = {...data, guid: userInfo.guid};
|
||||||
|
return await UserQuestTaskComplete(token, params);
|
||||||
|
}).then(data => {
|
||||||
|
if (data.result === "SUCCESS") {
|
||||||
|
showToast('QUEST_TASK_COMPLETE', { type: alertTypes.success });
|
||||||
|
} else {
|
||||||
|
showToast(data.data.message, { type: alertTypes.error });
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
showToast('API_FAIL', { type: alertTypes.error });
|
||||||
|
}).finally(() => {
|
||||||
|
handleClick();
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
loading ? <TableSkeleton /> :
|
loading ? <TableSkeleton /> :
|
||||||
<>
|
<>
|
||||||
@@ -59,7 +86,7 @@ const UserQuestInfo = ({ userInfo }) => {
|
|||||||
<td>{el.quest_id}</td>
|
<td>{el.quest_id}</td>
|
||||||
<td>{el.quest_name}</td>
|
<td>{el.quest_name}</td>
|
||||||
<td>{el.quest_type}</td>
|
<td>{el.quest_type}</td>
|
||||||
<td>{el.status}</td>
|
<td>{questCompleteStatusType.find(data => el.status === data.value).name || el.status}</td>
|
||||||
<td>{convertKTC(el.quest_assign_time, false)}</td>
|
<td>{convertKTC(el.quest_assign_time, false)}</td>
|
||||||
<td>{convertKTC(el.quest_complete_time, false)}</td>
|
<td>{convertKTC(el.quest_complete_time, false)}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -72,7 +99,7 @@ const UserQuestInfo = ({ userInfo }) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</QuestTable>
|
</QuestTable>
|
||||||
</UserTableWrapper>
|
</UserTableWrapper>
|
||||||
<QuestDetailModal detailPop={detailPop} handleClick={handleClick} detailQuest={detailQuest} />
|
<QuestDetailModal detailPop={detailPop} handleClick={handleClick} detailQuest={detailQuest} handleQuestComplete={handleQuestComplete} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import CircularProgress from '../common/CircularProgress';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CurrencyIndexSearchBar from '../searchBar/CurrencyIndexSearchBar';
|
import CurrencyIndexSearchBar from '../searchBar/CurrencyIndexSearchBar';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
const CreditContent = () => {
|
const CreditContent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -38,30 +39,47 @@ const CreditContent = () => {
|
|||||||
|
|
||||||
const tableHeaders = useMemo(() => {
|
const tableHeaders = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{ id: 'logDay', label: '일자', width: '100px' },
|
// 기본 컬럼 (rowSpan=2)
|
||||||
{ id: 'accountId', label: 'account ID', width: '80px' },
|
{ id: 'logDay', label: '일자', width: '100px', rowSpan: 2 },
|
||||||
{ id: 'userGuid', label: 'GUID', width: '200px' },
|
{ id: 'accountId', label: 'account ID', width: '80px', rowSpan: 2 },
|
||||||
{ id: 'userNickname', label: '아바타명', width: '150px' },
|
{ id: 'userGuid', label: 'GUID', width: '200px', rowSpan: 2 },
|
||||||
{ id: 'sapphireAcquired', label: '사파이어 획득량', width: '80px' },
|
{ id: 'userNickname', label: '아바타명', width: '150px', rowSpan: 2 },
|
||||||
{ id: 'sapphireConsumed', label: '사파이어 소모량', width: '80px' },
|
|
||||||
{ id: 'goldAcquired', label: '골드 획득량', width: '80px' },
|
// 획득량 그룹 헤더 (첫 번째 행에만 표시)
|
||||||
{ id: 'goldConsumed', label: '골드 소모량', width: '80px' },
|
{ id: 'acquired', label: '획득', width: '400px', colSpan: 5, groupHeader: true },
|
||||||
{ id: 'caliumAcquired', label: '칼리움 획득량', width: '80px' },
|
|
||||||
{ id: 'caliumConsumed', label: '칼리움 소모량', width: '80px' },
|
// 획득량 컬럼 (두 번째 행에만 표시)
|
||||||
{ id: 'beamAcquired', label: 'BEAM 획득량', width: '80px' },
|
{ id: 'sapphireAcquired', label: '사파이어', width: '80px', groupRow: true },
|
||||||
{ id: 'beamConsumed', label: 'BEAM 소모량', width: '80px' },
|
{ id: 'goldAcquired', label: '골드', width: '80px', groupRow: true },
|
||||||
{ id: 'rubyAcquired', label: '루비 획득량', width: '80px' },
|
{ id: 'caliumAcquired', label: '칼리움', width: '80px', groupRow: true },
|
||||||
{ id: 'rubyConsumed', label: '루비 소모량', width: '80px' },
|
{ id: 'beamAcquired', label: 'BEAM', width: '80px', groupRow: true },
|
||||||
{ id: 'sapphireNet', label: '사파이어 계', width: '80px' },
|
{ id: 'rubyAcquired', label: '루비', width: '80px', groupRow: true },
|
||||||
{ id: 'goldNet', label: '골드 계', width: '80px' },
|
|
||||||
{ id: 'caliumNet', label: '칼리움 계', width: '80px' },
|
// 소모량 그룹 헤더 (첫 번째 행에만 표시)
|
||||||
{ id: 'beamNet', label: 'BEAM 계', width: '80px' },
|
{ id: 'consumed', label: '소모', width: '400px', colSpan: 5, groupHeader: true },
|
||||||
{ id: 'rubyNet', label: '루비 계', width: '80px' },
|
|
||||||
{ id: 'totalCurrencies', label: '활동 수', width: '80px' },
|
// 소모량 컬럼 (두 번째 행에만 표시)
|
||||||
{ id: 'detail', label: '상세', width: '100px' },
|
{ id: 'sapphireConsumed', label: '사파이어', width: '80px', groupRow: true },
|
||||||
|
{ id: 'goldConsumed', label: '골드', width: '80px', groupRow: true },
|
||||||
|
{ id: 'caliumConsumed', label: '칼리움', width: '80px', groupRow: true },
|
||||||
|
{ id: 'beamConsumed', label: 'BEAM', width: '80px', groupRow: true },
|
||||||
|
{ id: 'rubyConsumed', label: '루비', width: '80px', groupRow: true },
|
||||||
|
|
||||||
|
// 계 컬럼 (rowSpan=2)
|
||||||
|
{ id: 'sapphireNet', label: '사파이어 계', width: '80px', rowSpan: 2 },
|
||||||
|
{ id: 'goldNet', label: '골드 계', width: '80px', rowSpan: 2 },
|
||||||
|
{ id: 'caliumNet', label: '칼리움 계', width: '80px', rowSpan: 2 },
|
||||||
|
{ id: 'beamNet', label: 'BEAM 계', width: '80px', rowSpan: 2 },
|
||||||
|
{ id: 'rubyNet', label: '루비 계', width: '80px', rowSpan: 2 },
|
||||||
|
|
||||||
|
// 기타 컬럼 (rowSpan=2)
|
||||||
|
{ id: 'totalCurrencies', label: '활동 수', width: '80px', rowSpan: 2 },
|
||||||
|
{ id: 'detail', label: '상세', width: '100px', rowSpan: 2 }
|
||||||
];
|
];
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const totals = useMemo(() => {
|
const totals = useMemo(() => {
|
||||||
if (!dataList?.currency_list?.length) return null;
|
if (!dataList?.currency_list?.length) return null;
|
||||||
|
|
||||||
@@ -112,7 +130,7 @@ const CreditContent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CurrencyIndexSearchBar
|
<CurrencyIndexSearchBar
|
||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
@@ -150,30 +168,62 @@ const CreditContent = () => {
|
|||||||
<TableStyle ref={tableRef}>
|
<TableStyle ref={tableRef}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{tableHeaders.map(header => (
|
{/* 첫 번째 행 - 기본 컬럼 + 그룹 헤더 + rowSpan=2 컬럼 */}
|
||||||
<th key={header.id} width={header.width}>{header.label}</th>
|
{tableHeaders.map(header => {
|
||||||
))}
|
if (header.groupRow) return null; // 두 번째 행의 컬럼은 첫 번째 행에서 건너뜀
|
||||||
|
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
width={header.width}
|
||||||
|
rowSpan={header.rowSpan}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{/* 두 번째 행 - 그룹 내 하위 컬럼만 */}
|
||||||
|
{tableHeaders.map(header => {
|
||||||
|
if (!header.groupRow) return null; // 첫 번째 행이나 rowSpan=2 컬럼은 두 번째 행에서 건너뜀
|
||||||
|
|
||||||
|
return (
|
||||||
|
<th key={header.id} width={header.width}>
|
||||||
|
{header.label}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{totals && (
|
{totals && (
|
||||||
<TotalRow>
|
<TotalRow>
|
||||||
<td colSpan="4">합계</td>
|
<td colSpan="4">합계</td>
|
||||||
|
{/* 획득 그룹 합계 */}
|
||||||
<td>{numberFormatter.formatCurrency(totals.sapphireAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(totals.sapphireAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.sapphireConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(totals.goldAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(totals.goldAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.goldConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(totals.caliumAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(totals.caliumAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.caliumConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(totals.beamAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(totals.beamAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.beamConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(totals.rubyAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(totals.rubyAcquired)}</td>
|
||||||
|
|
||||||
|
{/* 소모 그룹 합계 */}
|
||||||
|
<td>{numberFormatter.formatCurrency(totals.sapphireConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(totals.goldConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(totals.caliumConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(totals.beamConsumed)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.rubyConsumed)}</td>
|
<td>{numberFormatter.formatCurrency(totals.rubyConsumed)}</td>
|
||||||
|
|
||||||
|
{/* 계 합계 */}
|
||||||
<td>{numberFormatter.formatCurrency(totals.sapphireNet)}</td>
|
<td>{numberFormatter.formatCurrency(totals.sapphireNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.goldNet)}</td>
|
<td>{numberFormatter.formatCurrency(totals.goldNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.caliumNet)}</td>
|
<td>{numberFormatter.formatCurrency(totals.caliumNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.beamNet)}</td>
|
<td>{numberFormatter.formatCurrency(totals.beamNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(totals.rubyNet)}</td>
|
<td>{numberFormatter.formatCurrency(totals.rubyNet)}</td>
|
||||||
|
|
||||||
<td>{totals.totalCurrencies}</td>
|
<td>{totals.totalCurrencies}</td>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
</TotalRow>
|
</TotalRow>
|
||||||
@@ -181,25 +231,33 @@ const CreditContent = () => {
|
|||||||
{dataList?.currency_list?.map((item, index) => (
|
{dataList?.currency_list?.map((item, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<tr>
|
<tr>
|
||||||
|
{/* 기본 정보 */}
|
||||||
<td>{item.logDay}</td>
|
<td>{item.logDay}</td>
|
||||||
<td>{item.accountId}</td>
|
<td>{item.accountId}</td>
|
||||||
<td>{item.userGuid}</td>
|
<td>{item.userGuid}</td>
|
||||||
<td>{item.userNickname}</td>
|
<td>{item.userNickname}</td>
|
||||||
|
|
||||||
|
{/* 획득 그룹 */}
|
||||||
<td>{numberFormatter.formatCurrency(item.sapphireAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(item.sapphireAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.sapphireConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(item.goldAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(item.goldAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.goldConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(item.caliumAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(item.caliumAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.caliumConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(item.beamAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(item.beamAcquired)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.beamConsumed)}</td>
|
|
||||||
<td>{numberFormatter.formatCurrency(item.rubyAcquired)}</td>
|
<td>{numberFormatter.formatCurrency(item.rubyAcquired)}</td>
|
||||||
|
|
||||||
|
{/* 소모 그룹 */}
|
||||||
|
<td>{numberFormatter.formatCurrency(item.sapphireConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.goldConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.caliumConsumed)}</td>
|
||||||
|
<td>{numberFormatter.formatCurrency(item.beamConsumed)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.rubyConsumed)}</td>
|
<td>{numberFormatter.formatCurrency(item.rubyConsumed)}</td>
|
||||||
|
|
||||||
|
{/* 계 */}
|
||||||
<td>{numberFormatter.formatCurrency(item.sapphireNet)}</td>
|
<td>{numberFormatter.formatCurrency(item.sapphireNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.goldNet)}</td>
|
<td>{numberFormatter.formatCurrency(item.goldNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.caliumNet)}</td>
|
<td>{numberFormatter.formatCurrency(item.caliumNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.beamNet)}</td>
|
<td>{numberFormatter.formatCurrency(item.beamNet)}</td>
|
||||||
<td>{numberFormatter.formatCurrency(item.rubyNet)}</td>
|
<td>{numberFormatter.formatCurrency(item.rubyNet)}</td>
|
||||||
|
|
||||||
<td>{item.totalCurrencies}</td>
|
<td>{item.totalCurrencies}</td>
|
||||||
<td>
|
<td>
|
||||||
<Button theme="line" text="상세보기"
|
<Button theme="line" text="상세보기"
|
||||||
@@ -214,7 +272,7 @@ const CreditContent = () => {
|
|||||||
<TopButton />
|
<TopButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,108 +1,76 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import React, { Fragment, useRef } from 'react';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
import { TableStyle, TableInfo, ListOption, IndexTableWrap } from '../../styles/Components';
|
||||||
import { RetentionSearchBar } from '../../components/IndexManage/index';
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
import { RetentionIndexExport, RetentionIndexView } from '../../apis';
|
import { useRetentionSearch, RetentionSearchBar } from '../searchBar';
|
||||||
|
import { TableSkeleton } from '../Skeleton/TableSkeleton';
|
||||||
|
import { numberFormatter } from '../../utils';
|
||||||
|
import { ExcelDownButton } from '../common';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const RetentionContent = () => {
|
const RetentionContent = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tableRef = useRef(null);
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
let d = new Date();
|
|
||||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
|
||||||
const END_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(24, 0, 0, 0));
|
|
||||||
|
|
||||||
const [dataList, setDataList] = useState([]);
|
const {
|
||||||
const [resultData, setResultData] = useState([]);
|
searchParams,
|
||||||
const [retentionData, setRetention] = useState(1);
|
loading: dataLoading,
|
||||||
|
data: dataList,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
updateSearchParams
|
||||||
|
} = useRetentionSearch(token);
|
||||||
|
|
||||||
const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
const [excelBtn, setExcelBtn] = useState(true); //true 시 비활성화
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData(START_DATE, END_DATE);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Retention 지표 데이터
|
|
||||||
const fetchData = async (startDate, endDate) => {
|
|
||||||
const startDateToLocal =
|
|
||||||
startDate.getFullYear() +
|
|
||||||
'-' +
|
|
||||||
(startDate.getMonth() + 1 < 9 ? '0' + (startDate.getMonth() + 1) : startDate.getMonth() + 1) +
|
|
||||||
'-' +
|
|
||||||
(startDate.getDate() < 9 ? '0' + startDate.getDate() : startDate.getDate());
|
|
||||||
|
|
||||||
const endDateToLocal =
|
|
||||||
endDate.getFullYear() +
|
|
||||||
'-' +
|
|
||||||
(endDate.getMonth() + 1 < 9 ? '0' + (endDate.getMonth() + 1) : endDate.getMonth() + 1) +
|
|
||||||
'-' +
|
|
||||||
(endDate.getDate() < 9 ? '0' + endDate.getDate() : endDate.getDate());
|
|
||||||
|
|
||||||
setDataList(await RetentionIndexView(token, startDateToLocal, endDateToLocal));
|
|
||||||
|
|
||||||
console.log(dataList);
|
|
||||||
|
|
||||||
setSendDate(startDateToLocal);
|
|
||||||
setFinishDate(endDateToLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 검색 함수
|
|
||||||
const handleSearch = (send_dt, end_dt) => {
|
|
||||||
fetchData(send_dt, end_dt);
|
|
||||||
setRetention(resultData.retention);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
const handleXlsxExport = () => {
|
|
||||||
const fileName = 'Caliverse_Retention_Index.xlsx';
|
|
||||||
|
|
||||||
if(!excelBtn){
|
|
||||||
RetentionIndexExport(token, fileName, sendDate, finishDate);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<RetentionSearchBar setResultData={setResultData} resultData={resultData}
|
<RetentionSearchBar
|
||||||
handleSearch={handleSearch} fetchData={fetchData} setRetention={setRetention} setExcelBtn={setExcelBtn} />
|
searchParams={searchParams}
|
||||||
|
onSearch={(newParams, executeSearch = true) => {
|
||||||
|
if (executeSearch) {
|
||||||
|
handleSearch(newParams);
|
||||||
|
} else {
|
||||||
|
updateSearchParams(newParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReset={handleReset}
|
||||||
|
/>
|
||||||
<TableInfo>
|
<TableInfo>
|
||||||
<ListOption>
|
<ListOption>
|
||||||
<Button
|
<ExcelDownButton tableRef={tableRef} fileName={t('FILE_INDEX_USER_RETENTION')} />
|
||||||
theme={excelBtn === true ? "disable" : "line"}
|
|
||||||
text="엑셀 다운로드"
|
|
||||||
disabled={handleXlsxExport}
|
|
||||||
handleClick={handleXlsxExport} />
|
|
||||||
</ListOption>
|
</ListOption>
|
||||||
</TableInfo>
|
</TableInfo>
|
||||||
|
{dataLoading ? <TableSkeleton width='100%' count={15} /> :
|
||||||
<IndexTableWrap>
|
<IndexTableWrap>
|
||||||
<TableStyle>
|
<TableStyle ref={tableRef}>
|
||||||
<caption></caption>
|
<caption></caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{/* <th width="100">국가</th> */}
|
<th>일자</th>
|
||||||
<th width="150">일자</th>
|
<th>NRU</th>
|
||||||
<th className="cell-nru">NRU</th>
|
<th>D+1</th>
|
||||||
{[...Array(Number(retentionData))].map((value, index) => {
|
<th>D+7</th>
|
||||||
return <th key={index}>{`D+${index + 1}`}</th>;
|
<th>D+30</th>
|
||||||
})}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{dataList.retention &&
|
{dataList?.map((data, index) => (
|
||||||
dataList.retention.map(data => (
|
<Fragment key={index}>
|
||||||
<tr className="cell-nru-th" key={data.date}>
|
<tr>
|
||||||
<td>{data.date}</td>
|
<td>{data.logDay}</td>
|
||||||
{data['d-day'].map((day, index) => (
|
<td>{data.totalCreated}</td>
|
||||||
<td key={index}>{day.dif}</td>
|
<td>{numberFormatter.formatPercent(data.d1_rate)}</td>
|
||||||
))}
|
<td>{numberFormatter.formatPercent(data.d7_rate)}</td>
|
||||||
|
<td>{numberFormatter.formatPercent(data.d30_rate)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</TableStyle>
|
</TableStyle>
|
||||||
</IndexTableWrap>
|
</IndexTableWrap>
|
||||||
</>
|
}
|
||||||
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Loading from '../common/Loading';
|
|||||||
import { ExcelDownButton } from '../common';
|
import { ExcelDownButton } from '../common';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { formatStringDate } from '../../utils';
|
import { formatStringDate } from '../../utils';
|
||||||
|
import { AnimatedPageWrapper } from '../common/Layout';
|
||||||
|
|
||||||
const UserContent = () => {
|
const UserContent = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -24,20 +25,6 @@ const UserContent = () => {
|
|||||||
const [dataList, setDataList] = useState([]);
|
const [dataList, setDataList] = useState([]);
|
||||||
const [resultData, setResultData] = useState([]);
|
const [resultData, setResultData] = useState([]);
|
||||||
|
|
||||||
// const [sendDate, setSendDate] = useState(START_DATE);
|
|
||||||
// const [finishDate, setFinishDate] = useState(END_DATE);
|
|
||||||
|
|
||||||
const headers = [
|
|
||||||
{key: 'date', label: '일자'},
|
|
||||||
{key: 'nru', label: 'NRU'},
|
|
||||||
{key: 'ugqCreate', label: '일자'},
|
|
||||||
{key: 'dglc', label: '일자'},
|
|
||||||
{key: 'dau', label: '일자'},
|
|
||||||
{key: 'mcu', label: '일자'},
|
|
||||||
{key: 'date', label: '일자'},
|
|
||||||
{key: 'date', label: '일자'},
|
|
||||||
]
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData(START_DATE, END_DATE);
|
fetchData(START_DATE, END_DATE);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -54,8 +41,6 @@ const UserContent = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// setSendDate(startDateToLocal);
|
|
||||||
// setFinishDate(endDateToLocal);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 검색 함수
|
// 검색 함수
|
||||||
@@ -63,14 +48,8 @@ const UserContent = () => {
|
|||||||
fetchData(send_dt, end_dt);
|
fetchData(send_dt, end_dt);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 엑셀 다운로드
|
|
||||||
// const handleXlsxExport = () => {
|
|
||||||
// const fileName = 'Caliverse_User_Index.xlsx';
|
|
||||||
// userIndexExport(token, fileName, sendDate, finishDate);
|
|
||||||
// };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<DailyDashBoard />
|
<DailyDashBoard />
|
||||||
<UserIndexSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
<UserIndexSearchBar setResultData={setResultData} resultData={resultData} handleSearch={handleSearch} fetchData={fetchData} />
|
||||||
<TableInfo>
|
<TableInfo>
|
||||||
@@ -125,8 +104,7 @@ const UserContent = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</TableStyle>
|
</TableStyle>
|
||||||
</IndexTableWrap>
|
</IndexTableWrap>
|
||||||
{loading && <Loading/>}
|
</AnimatedPageWrapper>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { MenuImageDelete, MenuImageUpload } from '../../apis';
|
|||||||
import { IMAGE_MAX_SIZE } from '../../assets/data/adminConstants';
|
import { IMAGE_MAX_SIZE } from '../../assets/data/adminConstants';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { ImagePreview } from '../../styles/Components';
|
||||||
|
|
||||||
const ImageUploadBtn = ({ disabled,
|
const ImageUploadBtn = ({ disabled,
|
||||||
onImageUpload,
|
onImageUpload,
|
||||||
@@ -220,14 +221,6 @@ const PreviewContainer = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ImagePreview = styled.img`
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PreviewInfo = styled.div`
|
const PreviewInfo = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -1,526 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { getOptionsArray } from '../../../utils';
|
|
||||||
import {
|
|
||||||
SelectInput,
|
|
||||||
SearchBarAlert
|
|
||||||
} from '../../../styles/Components';
|
|
||||||
import {
|
|
||||||
FormInput, FormInputSuffix, FormInputSuffixWrapper,
|
|
||||||
FormLabel,
|
|
||||||
FormRowGroup,
|
|
||||||
FormStatusBar,
|
|
||||||
FormStatusLabel,
|
|
||||||
FormStatusWarning,
|
|
||||||
} from '../../../styles/ModuleComponents';
|
|
||||||
import { CheckBox, SingleDatePicker, SingleTimePicker } from '../../common';
|
|
||||||
import Button from '../../common/button/Button';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import ImageUploadBtn from '../../ServiceManage/ImageUploadBtn';
|
|
||||||
|
|
||||||
const CaliForm = ({
|
|
||||||
config, // 폼 설정 JSON
|
|
||||||
mode, // 'create', 'update', 'view' 중 하나
|
|
||||||
initialData, // 초기 데이터
|
|
||||||
externalData, // 외부 데이터(옵션 등)
|
|
||||||
onSubmit, // 제출 핸들러
|
|
||||||
onCancel, // 취소 핸들러
|
|
||||||
className, // 추가 CSS 클래스
|
|
||||||
onFieldValidation, // 필드 유효성 검사 콜백
|
|
||||||
formRef // 폼 ref
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [formData, setFormData] = useState({ ...(config?.initData || {}), ...(initialData || {}) });
|
|
||||||
const [errors, setErrors] = useState({});
|
|
||||||
const [isFormValid, setIsFormValid] = useState(false);
|
|
||||||
|
|
||||||
// 필드 변경 핸들러
|
|
||||||
const handleFieldChange = (fieldId, value) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[fieldId]: value
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 날짜 변경 핸들러
|
|
||||||
const handleDateChange = (fieldId, date) => {
|
|
||||||
if (!date) return;
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[fieldId]: date
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 시간 변경 핸들러
|
|
||||||
const handleTimeChange = (fieldId, time) => {
|
|
||||||
if (!time) return;
|
|
||||||
|
|
||||||
const newDateTime = formData[fieldId] ? new Date(formData[fieldId]) : new Date();
|
|
||||||
newDateTime.setHours(time.getHours(), time.getMinutes(), 0, 0);
|
|
||||||
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[fieldId]: newDateTime
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 폼 유효성 검사
|
|
||||||
useEffect(() => {
|
|
||||||
const validateForm = () => {
|
|
||||||
const newErrors = {};
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
if (!config) return false;
|
|
||||||
|
|
||||||
// 필수 필드 검사
|
|
||||||
const requiredFields = config.fields
|
|
||||||
.filter(f =>
|
|
||||||
f.visibleOn.includes(mode) &&
|
|
||||||
f.validations?.includes("required")
|
|
||||||
)
|
|
||||||
.map(f => f.id);
|
|
||||||
|
|
||||||
requiredFields.forEach(fieldId => {
|
|
||||||
if (!formData[fieldId] && formData[fieldId] !== 0) {
|
|
||||||
newErrors[fieldId] = t('REQUIRED_FIELD');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 조건부 유효성 검사
|
|
||||||
if (config.validations && config.validations[mode]) {
|
|
||||||
for (const validation of config.validations[mode]) {
|
|
||||||
const conditionResult = evaluateCondition(validation.condition, {
|
|
||||||
...formData,
|
|
||||||
current_time: new Date().getTime()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (conditionResult) {
|
|
||||||
// 전체 폼 검증 오류
|
|
||||||
newErrors._form = t(validation.message);
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setErrors(newErrors);
|
|
||||||
setIsFormValid(isValid);
|
|
||||||
|
|
||||||
if (onFieldValidation) {
|
|
||||||
onFieldValidation(isValid, newErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
};
|
|
||||||
|
|
||||||
validateForm();
|
|
||||||
}, [config, formData, mode, t, onFieldValidation]);
|
|
||||||
|
|
||||||
// 간단한 조건식 평가 함수
|
|
||||||
const evaluateCondition = (conditionStr, context) => {
|
|
||||||
try {
|
|
||||||
const fn = new Function(...Object.keys(context), `return ${conditionStr}`);
|
|
||||||
return fn(...Object.values(context));
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error evaluating condition:', e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 필드 렌더링
|
|
||||||
const renderField = (field) => {
|
|
||||||
const isEditable = field.editableOn.includes(mode);
|
|
||||||
const value = formData[field.id] !== undefined ? formData[field.id] : '';
|
|
||||||
const hasError = errors[field.id];
|
|
||||||
|
|
||||||
switch (field.type) {
|
|
||||||
case 'text':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
value={value}
|
|
||||||
onChange={e => handleFieldChange(field.id, e.target.value)}
|
|
||||||
disabled={!isEditable}
|
|
||||||
width={field.width}
|
|
||||||
className={hasError ? 'error' : ''}
|
|
||||||
/>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<FormInput
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
onChange={e => handleFieldChange(field.id, Number(e.target.value))}
|
|
||||||
disabled={!isEditable}
|
|
||||||
width={field.width}
|
|
||||||
min={field.min}
|
|
||||||
max={field.max}
|
|
||||||
step={field.step || 1}
|
|
||||||
className={hasError ? 'error' : ''}
|
|
||||||
/>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'select':
|
|
||||||
let options = [];
|
|
||||||
|
|
||||||
if (field.optionsKey) {
|
|
||||||
// 옵션 설정에서 가져오기
|
|
||||||
options = getOptionsArray(field.optionsKey);
|
|
||||||
} else if (field.dataSource && externalData) {
|
|
||||||
// 외부 데이터 소스 사용
|
|
||||||
const dataSource = externalData[field.dataSource] || [];
|
|
||||||
|
|
||||||
options = dataSource.map(item => ({
|
|
||||||
value: item[field.valueField],
|
|
||||||
label: field.displayFormat
|
|
||||||
? field.displayFormat.replace('{value}', item[field.valueField])
|
|
||||||
.replace('{display}', item[field.displayField])
|
|
||||||
: `${item[field.displayField]}(${item[field.valueField]})`
|
|
||||||
}));
|
|
||||||
} else if (field.options) {
|
|
||||||
options = field.options;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<SelectInput
|
|
||||||
value={value}
|
|
||||||
onChange={e => handleFieldChange(field.id, e.target.value)}
|
|
||||||
disabled={!isEditable}
|
|
||||||
width={field.width}
|
|
||||||
className={hasError ? 'error' : ''}
|
|
||||||
>
|
|
||||||
{options.map((option, index) => (
|
|
||||||
<option key={index} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</SelectInput>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'datePicker':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<SingleDatePicker
|
|
||||||
label={field.label}
|
|
||||||
disabled={!isEditable}
|
|
||||||
dateLabel={field.dateLabel}
|
|
||||||
onDateChange={date => handleDateChange(field.id, date)}
|
|
||||||
selectedDate={value}
|
|
||||||
minDate={field.minDate}
|
|
||||||
maxDate={field.maxDate}
|
|
||||||
className={hasError ? 'error' : ''}
|
|
||||||
/>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'timePicker':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<SingleTimePicker
|
|
||||||
label={field.label}
|
|
||||||
disabled={!isEditable}
|
|
||||||
selectedTime={value}
|
|
||||||
onTimeChange={time => handleTimeChange(field.id, time)}
|
|
||||||
className={hasError ? 'error' : ''}
|
|
||||||
/>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'status':
|
|
||||||
let statusText = "";
|
|
||||||
if (field.optionsKey && formData[field.statusField]) {
|
|
||||||
const statusOptions = getOptionsArray(field.optionsKey);
|
|
||||||
const statusItem = statusOptions.find(item => item.value === formData[field.statusField]);
|
|
||||||
statusText = statusItem ? statusItem.name : "등록";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<FormStatusBar>
|
|
||||||
<FormStatusLabel>
|
|
||||||
{field.label}: {statusText}
|
|
||||||
</FormStatusLabel>
|
|
||||||
{mode === 'update' && field.warningMessage && (
|
|
||||||
<FormStatusWarning>
|
|
||||||
{t(field.warningMessage)}
|
|
||||||
</FormStatusWarning>
|
|
||||||
)}
|
|
||||||
</FormStatusBar>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'dateTimeRange':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<div className="date-time-range">
|
|
||||||
<SingleDatePicker
|
|
||||||
label={field.startDateLabel}
|
|
||||||
disabled={!isEditable}
|
|
||||||
dateLabel={field.startDateLabel}
|
|
||||||
onDateChange={date => handleDateChange(field.startDateField, date)}
|
|
||||||
selectedDate={formData[field.startDateField]}
|
|
||||||
/>
|
|
||||||
<SingleTimePicker
|
|
||||||
disabled={!isEditable}
|
|
||||||
selectedTime={formData[field.startDateField]}
|
|
||||||
onTimeChange={time => handleTimeChange(field.startDateField, time)}
|
|
||||||
/>
|
|
||||||
<SingleDatePicker
|
|
||||||
label={field.endDateLabel}
|
|
||||||
disabled={!isEditable}
|
|
||||||
dateLabel={field.endDateLabel}
|
|
||||||
onDateChange={date => handleDateChange(field.endDateField, date)}
|
|
||||||
selectedDate={formData[field.endDateField]}
|
|
||||||
/>
|
|
||||||
<SingleTimePicker
|
|
||||||
disabled={!isEditable}
|
|
||||||
selectedTime={formData[field.endDateField]}
|
|
||||||
onTimeChange={time => handleTimeChange(field.endDateField, time)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'imageUpload':
|
|
||||||
const imageLanguage = field.language;
|
|
||||||
const imageList = formData.image_list || [];
|
|
||||||
const imageData = imageList.find(img => img.language === imageLanguage) || { content: '' };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<LanguageWrapper>
|
|
||||||
<LanguageLabel>{imageLanguage}</LanguageLabel>
|
|
||||||
<ImageUploadBtn
|
|
||||||
onImageUpload={(file, fileName) => {
|
|
||||||
const updatedImageList = [...imageList];
|
|
||||||
const index = updatedImageList.findIndex(img => img.language === imageLanguage);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
updatedImageList[index] = {
|
|
||||||
...updatedImageList[index],
|
|
||||||
content: fileName
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updatedImageList.push({
|
|
||||||
language: imageLanguage,
|
|
||||||
content: fileName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFieldChange('image_list', updatedImageList);
|
|
||||||
}}
|
|
||||||
onFileDelete={() => {
|
|
||||||
const updatedImageList = [...imageList];
|
|
||||||
const index = updatedImageList.findIndex(img => img.language === imageLanguage);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
updatedImageList[index] = {
|
|
||||||
...updatedImageList[index],
|
|
||||||
content: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFieldChange('image_list', updatedImageList);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
fileName={imageData.content}
|
|
||||||
disabled={!isEditable}
|
|
||||||
/>
|
|
||||||
</LanguageWrapper>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'checkbox':
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
<CheckBox
|
|
||||||
label={field.label}
|
|
||||||
id={field.id}
|
|
||||||
checked={formData[field.id] || false}
|
|
||||||
setData={e => handleFieldChange(field.id, e.target.checked)}
|
|
||||||
disabled={!isEditable}
|
|
||||||
/>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'textWithSuffix':
|
|
||||||
const linkLanguage = field.suffix;
|
|
||||||
const linkList = formData.link_list || [];
|
|
||||||
const linkData = linkList.find(link => link.language === linkLanguage) || { content: '' };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-field">
|
|
||||||
{field.label && <FormLabel>{field.label}</FormLabel>}
|
|
||||||
<FormInputSuffixWrapper>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
value={linkData.content}
|
|
||||||
onChange={e => {
|
|
||||||
const updatedLinkList = [...linkList];
|
|
||||||
const index = updatedLinkList.findIndex(link => link.language === linkLanguage);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
updatedLinkList[index] = {
|
|
||||||
...updatedLinkList[index],
|
|
||||||
content: e.target.value
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updatedLinkList.push({
|
|
||||||
language: linkLanguage,
|
|
||||||
content: e.target.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFieldChange('link_list', updatedLinkList);
|
|
||||||
}}
|
|
||||||
disabled={!isEditable}
|
|
||||||
width={field.width}
|
|
||||||
suffix="true"
|
|
||||||
/>
|
|
||||||
<FormInputSuffix>{linkLanguage}</FormInputSuffix>
|
|
||||||
</FormInputSuffixWrapper>
|
|
||||||
{hasError && <div className="field-error">{hasError}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 조건부 렌더링을 위한 필드 필터링
|
|
||||||
const getVisibleFields = () => {
|
|
||||||
if (!config) return [];
|
|
||||||
|
|
||||||
return config.fields.filter(field => {
|
|
||||||
if (!field.visibleOn.includes(mode)) return false;
|
|
||||||
|
|
||||||
// 조건부 표시 필드 처리
|
|
||||||
if (field.conditional) {
|
|
||||||
const { field: condField, operator, value } = field.conditional;
|
|
||||||
|
|
||||||
if (operator === "==" && formData[condField] !== value) return false;
|
|
||||||
if (operator === "!=" && formData[condField] === value) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 그리드 기반 필드 렌더링
|
|
||||||
const renderGridFields = () => {
|
|
||||||
if (!config) return null;
|
|
||||||
|
|
||||||
const visibleFields = getVisibleFields();
|
|
||||||
const { rows, columns } = config.grid;
|
|
||||||
|
|
||||||
// 그리드 레이아웃 생성
|
|
||||||
return (
|
|
||||||
<div className="form-grid" style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: '10px' }}>
|
|
||||||
{visibleFields.map((field) => {
|
|
||||||
const { row, col, width } = field.position;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={field.id}
|
|
||||||
className="form-cell"
|
|
||||||
style={{
|
|
||||||
gridRow: row + 1,
|
|
||||||
gridColumn: `${col + 1} / span ${width}`,
|
|
||||||
padding: '5px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>{field.label}{field.validations?.includes("required") && <span className="required">*</span>}</FormLabel>
|
|
||||||
{renderField(field)}
|
|
||||||
</FormRowGroup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 버튼 렌더링
|
|
||||||
const renderButtons = () => {
|
|
||||||
if (!config || !config.actions || !config.actions[mode]) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form-actions">
|
|
||||||
{config.actions[mode].map(action => (
|
|
||||||
<Button
|
|
||||||
key={action.id}
|
|
||||||
text={action.label}
|
|
||||||
theme={action.theme}
|
|
||||||
handleClick={() => {
|
|
||||||
if (action.action === 'submit') {
|
|
||||||
if (isFormValid) {
|
|
||||||
onSubmit(formData);
|
|
||||||
}
|
|
||||||
} else if (action.action === 'close' || action.action === 'cancel') {
|
|
||||||
onCancel();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={action.action === 'submit' && !isFormValid}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!config) return <div>로딩 중...</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`json-config-form ${className || ''}`} ref={formRef}>
|
|
||||||
<div className="form-content">
|
|
||||||
{renderGridFields()}
|
|
||||||
|
|
||||||
{errors._form && (
|
|
||||||
<SearchBarAlert $marginTop="15px" $align="right">
|
|
||||||
{errors._form}
|
|
||||||
</SearchBarAlert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-footer">
|
|
||||||
{renderButtons()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CaliForm;
|
|
||||||
|
|
||||||
const LanguageWrapper = styled.div`
|
|
||||||
width: ${props => props.width || '100%'};
|
|
||||||
//margin-bottom: 20px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
padding-left: 90px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LanguageLabel = styled.h4`
|
|
||||||
color: #444;
|
|
||||||
margin: 0 0 10px 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NavLink, useNavigate } from 'react-router-dom';
|
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import arrowIcon from '../../../assets/img/icon/icon-tab.png';
|
import { ConfigProvider, Menu, theme } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { authList } from '../../../store/authList';
|
import { authList } from '../../../store/authList';
|
||||||
@@ -7,7 +7,6 @@ import Modal from '../modal/Modal';
|
|||||||
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
|
import { BtnWrapper, ButtonClose, ModalText } from '../../../styles/Components';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Button from '../button/Button';
|
import Button from '../button/Button';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { AuthInfo } from '../../../apis';
|
import { AuthInfo } from '../../../apis';
|
||||||
import { getMenuConfig } from '../../../utils';
|
import { getMenuConfig } from '../../../utils';
|
||||||
import { adminAuthLevel } from '../../../assets/data/types';
|
import { adminAuthLevel } from '../../../assets/data/types';
|
||||||
@@ -15,47 +14,55 @@ import { adminAuthLevel } from '../../../assets/data/types';
|
|||||||
const Navi = () => {
|
const Navi = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
const menu = getMenuConfig(userInfo);
|
const menuConfig = getMenuConfig(userInfo);
|
||||||
|
|
||||||
const [modalClose, setModalClose] = useState('hidden');
|
|
||||||
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [modalClose, setModalClose] = useState('hidden');
|
||||||
|
const [logoutModalClose, setLogoutModalClose] = useState('hidden');
|
||||||
|
const [openKeys, setOpenKeys] = useState([]);
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState([]);
|
||||||
|
|
||||||
|
// 현재 경로에 따라 선택된 메뉴와 열린 서브메뉴 설정
|
||||||
|
useEffect(() => {
|
||||||
|
const path = location.pathname.split('/');
|
||||||
|
if (path.length > 1) {
|
||||||
|
// 첫 번째 경로(예: /usermanage)를 기반으로 openKeys 설정
|
||||||
|
setOpenKeys([path[1]]);
|
||||||
|
|
||||||
|
// 전체 경로(예: /usermanage/adminview)를 기반으로 selectedKeys 설정
|
||||||
|
if (path.length > 2) {
|
||||||
|
setSelectedKeys([`${path[1]}/${path[2]}`]);
|
||||||
|
} else {
|
||||||
|
setSelectedKeys([path[1]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
const handleToken = async () => {
|
const handleToken = async () => {
|
||||||
const tokenStatus = await AuthInfo(token);
|
const tokenStatus = await AuthInfo(token);
|
||||||
|
if (tokenStatus.message === '잘못된 타입의 토큰입니다.') {
|
||||||
tokenStatus.message === '잘못된 타입의 토큰입니다.' && setLogoutModalClose('view');
|
setLogoutModalClose('view');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleToken();
|
handleToken();
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
const handleTopMenu = e => {
|
// 메뉴 아이템 클릭 핸들러
|
||||||
e.preventDefault();
|
const handleMenuClick = ({ key }) => {
|
||||||
e.target.classList.toggle('active');
|
handleToken();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLink = e => {
|
// 서브메뉴 열기/닫기 핸들러
|
||||||
let topActive = document.querySelectorAll('nav .active');
|
const handleOpenChange = (keys) => {
|
||||||
let currentTopMenu = e.target.closest('ul').previousSibling;
|
setOpenKeys(keys);
|
||||||
for (let i = 0; i < topActive.length; i++) {
|
|
||||||
if (topActive[i] !== currentTopMenu) {
|
|
||||||
topActive[i].classList.remove('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToken();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 등록 완료 모달
|
// 등록 완료 모달
|
||||||
const handleModalClose = () => {
|
const handleModalClose = () => {
|
||||||
if (modalClose === 'hidden') {
|
setModalClose(modalClose === 'hidden' ? 'view' : 'hidden');
|
||||||
setModalClose('view');
|
|
||||||
} else {
|
|
||||||
setModalClose('hidden');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 로그아웃 안내 모달
|
// 로그아웃 안내 모달
|
||||||
@@ -65,7 +72,6 @@ const Navi = () => {
|
|||||||
} else {
|
} else {
|
||||||
setLogoutModalClose('hidden');
|
setLogoutModalClose('hidden');
|
||||||
sessionStorage.removeItem('token');
|
sessionStorage.removeItem('token');
|
||||||
|
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -79,41 +85,56 @@ const Navi = () => {
|
|||||||
default:
|
default:
|
||||||
return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
|
return submenu.authLevel === adminAuthLevel.NONE && userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === submenu.id);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMenuItems = () => {
|
||||||
|
return menuConfig
|
||||||
|
.filter(item => item.access)
|
||||||
|
.map(item => ({
|
||||||
|
key: item.link.substring(1),
|
||||||
|
label: item.title,
|
||||||
|
children: item.submenu.map(submenu => ({
|
||||||
|
key: `${item.link.substring(1)}/${submenu.link.split('/').pop()}`,
|
||||||
|
label: (
|
||||||
|
<MenuItemLink
|
||||||
|
to={isClickable(submenu) ? submenu.link : location.pathname}
|
||||||
|
$isclickable={isClickable(submenu) ? 'true' : 'false'}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (!isClickable(submenu)) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleModalClose();
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{submenu.title}
|
||||||
|
</MenuItemLink>
|
||||||
|
),
|
||||||
|
disabled: !isClickable(submenu)
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav>
|
<StyledNavWrapper>
|
||||||
<ul>
|
<ConfigProvider
|
||||||
{menu.map((item, idx) => {
|
theme={{
|
||||||
return (
|
algorithm: theme.darkAlgorithm,
|
||||||
<li key={idx}>
|
}}
|
||||||
{item.access && (
|
>
|
||||||
<TopMenu to={item.link} onClick={handleTopMenu}>
|
<StyledMenu
|
||||||
{item.title}
|
theme="dark"
|
||||||
</TopMenu>
|
mode="inline"
|
||||||
)}
|
openKeys={openKeys}
|
||||||
<SubMenu>
|
selectedKeys={selectedKeys}
|
||||||
{item.submenu && userInfo &&
|
onOpenChange={handleOpenChange}
|
||||||
item.submenu.map((submenu, idx) => {
|
onClick={handleMenuClick}
|
||||||
return (
|
items={getMenuItems()}
|
||||||
<SubMenuItem key={idx} $isclickable={isClickable(submenu) ? 'true' : 'false'}>
|
multiple={true}
|
||||||
<NavLink
|
/>
|
||||||
to={isClickable(submenu) ? submenu.link : location.pathname}
|
</ConfigProvider>
|
||||||
onClick={e => {
|
</StyledNavWrapper>
|
||||||
isClickable(submenu) ? handleLink(e) : handleModalClose();
|
|
||||||
}}>
|
|
||||||
{submenu.title}
|
|
||||||
</NavLink>
|
|
||||||
</SubMenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SubMenu>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{/* 접근 불가 모달 */}
|
{/* 접근 불가 모달 */}
|
||||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
|
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={modalClose}>
|
||||||
<BtnWrapper $justify="flex-end">
|
<BtnWrapper $justify="flex-end">
|
||||||
@@ -145,61 +166,15 @@ const Navi = () => {
|
|||||||
|
|
||||||
export default Navi;
|
export default Navi;
|
||||||
|
|
||||||
const TopMenu = styled(NavLink)`
|
const StyledNavWrapper = styled.div`
|
||||||
padding: 16px 30px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #888;
|
|
||||||
position: relative;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
position: absolute;
|
|
||||||
right: 30px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
background: url('${arrowIcon}') -12px 0 no-repeat;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
background: #444;
|
|
||||||
}
|
|
||||||
&.active ~ ul {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
&.active:before {
|
|
||||||
background: url('${arrowIcon}') 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SubMenu = styled.ul`
|
const StyledMenu = styled(Menu)`
|
||||||
display: none;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SubMenuItem = styled.li`
|
const MenuItemLink = styled(NavLink)`
|
||||||
background: #eee;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
color: #2c2c2c;
|
|
||||||
a {
|
|
||||||
width: 100%;
|
|
||||||
padding: 16px 30px;
|
|
||||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
|
||||||
text-align: left;
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
&.active {
|
||||||
color: ${props => (props.$isclickable === 'false' ? '#818181' : '#2c2c2c')};
|
|
||||||
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
font-weight: ${props => (props.$isclickable === 'false' ? 400 : 600)};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const BackGround = styled.div`
|
|
||||||
background: #eee2;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 100;
|
|
||||||
`;
|
`;
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import UserIcon from '../../../assets/img/icon/icon-profile.png';
|
import { Layout, Avatar, Button as AntButton, Typography, Tooltip, Breadcrumb } from 'antd';
|
||||||
|
import { UserOutlined, LogoutOutlined, HomeOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Modal from '../modal/Modal';
|
|
||||||
import CloseIcon from '../../../assets/img/icon/icon-close.png';
|
|
||||||
import Button from '../../common/button/Button';
|
|
||||||
|
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { AuthLogout, AuthInfo } from '../../../apis';
|
import { AuthLogout, AuthInfo } from '../../../apis';
|
||||||
import { BtnWrapper, ModalText } from '../../../styles/Components';
|
|
||||||
import { authList } from '../../../store/authList';
|
import { authList } from '../../../store/authList';
|
||||||
|
import { alertTypes } from '../../../assets/data/types';
|
||||||
|
import { useAlert } from '../../../context/AlertProvider';
|
||||||
|
import { menuConfig } from '../../../assets/data/menuConfig';
|
||||||
|
|
||||||
|
const { Header } = Layout;
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const { showModal } = useAlert();
|
||||||
const [infoData, setInfoData] = useRecoilState(authList);
|
const [infoData, setInfoData] = useRecoilState(authList);
|
||||||
const [errorModal, setErrorModal] = useState('hidden');
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -40,91 +44,123 @@ const Profile = () => {
|
|||||||
|
|
||||||
// 필수값 입력 모달창
|
// 필수값 입력 모달창
|
||||||
const handleErrorModal = () => {
|
const handleErrorModal = () => {
|
||||||
if (errorModal === 'hidden') {
|
showModal('USER_LOGOUT_CONFIRM', {
|
||||||
setErrorModal('view');
|
type: alertTypes.confirm,
|
||||||
} else {
|
onConfirm: () => handleLogout()
|
||||||
setErrorModal('hidden');
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 카테고리별 첫 번째 아이템 링크 찾기
|
||||||
|
const getFirstItemLink = (categoryKey) => {
|
||||||
|
const category = menuConfig[categoryKey];
|
||||||
|
if (!category || !category.items) return `/${categoryKey}`;
|
||||||
|
|
||||||
|
// 첫 번째 visible 아이템 찾기
|
||||||
|
const firstVisibleItem = Object.entries(category.items)
|
||||||
|
.find(([_, item]) => item.view !== false);
|
||||||
|
|
||||||
|
if (!firstVisibleItem) return `/${categoryKey}`;
|
||||||
|
return `/${categoryKey}/${firstVisibleItem[0]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pathSnippets = location.pathname.split('/').filter(i => i);
|
||||||
|
|
||||||
|
const breadcrumbItems = [
|
||||||
|
{
|
||||||
|
title: <Link to="/"><HomeOutlined /></Link>,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (pathSnippets.length > 0) {
|
||||||
|
// 첫 번째 경로 (메인 카테고리)
|
||||||
|
const mainCategory = pathSnippets[0];
|
||||||
|
if (menuConfig[mainCategory]) {
|
||||||
|
const firstItemLink = getFirstItemLink(mainCategory);
|
||||||
|
|
||||||
|
breadcrumbItems.push({
|
||||||
|
title: <Link to={firstItemLink}>{menuConfig[mainCategory].title}</Link>
|
||||||
|
});
|
||||||
|
|
||||||
|
// 두 번째 경로 (서브 카테고리)
|
||||||
|
if (pathSnippets.length > 1) {
|
||||||
|
const subCategory = pathSnippets[1];
|
||||||
|
if (menuConfig[mainCategory].items[subCategory]) {
|
||||||
|
breadcrumbItems.push({
|
||||||
|
title: menuConfig[mainCategory].items[subCategory].title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProfileWrapper>
|
<StyledHeader>
|
||||||
<UserWrapper>{infoData.name && <Username>{infoData.name.length > 20 ? infoData.name.slice(0, 20) + '...' : infoData.name}님</Username>}</UserWrapper>
|
<StyledBreadcrumb items={breadcrumbItems} />
|
||||||
<Link>
|
<ProfileContainer>
|
||||||
<LogoutBtn onClick={handleErrorModal}>로그아웃</LogoutBtn>
|
<StyledAvatar
|
||||||
</Link>
|
size={32}
|
||||||
</ProfileWrapper>
|
icon={<UserOutlined />}
|
||||||
{/* 로그아웃 확인 모달 */}
|
/>
|
||||||
<Modal min="440px" $padding="40px" $bgcolor="transparent" $view={errorModal}>
|
{infoData.name &&
|
||||||
<BtnWrapper $justify="flex-end">
|
<StyledUsername>
|
||||||
<ButtonClose onClick={handleErrorModal} />
|
{infoData.name.length > 20 ? infoData.name.slice(0, 20) + '...' : infoData.name}님
|
||||||
</BtnWrapper>
|
</StyledUsername>
|
||||||
<ModalText $align="center">
|
}
|
||||||
로그아웃 하시겠습니까?
|
<Tooltip title="로그아웃">
|
||||||
<br />
|
<StyledLogoutButton
|
||||||
(로그아웃 시 저장되지 않은 값은 초기화 됩니다.)
|
type="text"
|
||||||
</ModalText>
|
icon={<LogoutOutlined />}
|
||||||
<BtnWrapper $gap="10px">
|
onClick={handleErrorModal}
|
||||||
<Button text="취소" theme="line" size="large" width="100%" handleClick={handleErrorModal} />
|
/>
|
||||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleLogout} />
|
</Tooltip>
|
||||||
</BtnWrapper>
|
</ProfileContainer>
|
||||||
</Modal>
|
|
||||||
|
</StyledHeader>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
|
|
||||||
const ProfileWrapper = styled.div`
|
const StyledHeader = styled(Header)`
|
||||||
background: #f6f6f6;
|
background: #f6f6f6;
|
||||||
padding: 20px;
|
padding: 0 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
justify-content: space-between;
|
||||||
gap: 30px;
|
|
||||||
word-break: break-all;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: 64px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LogoutBtn = styled.button`
|
const StyledBreadcrumb = styled(Breadcrumb)`
|
||||||
color: #2c2c2c;
|
font-size: 15px;
|
||||||
line-height: 1;
|
font-weight: 600;
|
||||||
border-bottom: 0.5px solid #2c2c2c;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 300;
|
|
||||||
border-radius: 0;
|
|
||||||
letter-spacing: 0;
|
|
||||||
width: max-content;
|
|
||||||
height: max-content;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UserWrapper = styled.div`
|
const ProfileContainer = styled.div`
|
||||||
padding-left: 35px;
|
|
||||||
position: relative;
|
|
||||||
font-size: 18px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
&:before {
|
const StyledAvatar = styled(Avatar)`
|
||||||
background: url('${UserIcon}') 50% 50% no-repeat;
|
`;
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
const StyledUsername = styled(Text)`
|
||||||
content: '';
|
font-weight: 600;
|
||||||
display: block;
|
font-size: 18px;
|
||||||
position: absolute;
|
color: rgba(0, 0, 0, 0.85);
|
||||||
left: 0;
|
`;
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
const StyledLogoutButton = styled(AntButton)`
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
transition: color 0.3s;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #1677ff;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
|
||||||
const Username = styled.div`
|
|
||||||
font-weight: 700;
|
|
||||||
padding-right: 3px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ButtonClose = styled.button`
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background: url(${CloseIcon}) 50% 50% no-repeat;
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
41
src/components/common/Layout/AnimatedPageWrapper.js
Normal file
41
src/components/common/Layout/AnimatedPageWrapper.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
const pageVariants = {
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
x: 20
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
opacity: 1,
|
||||||
|
x: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "easeInOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
x: -20,
|
||||||
|
transition: {
|
||||||
|
duration: 0.2,
|
||||||
|
ease: "easeInOut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnimatedPageWrapper = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
exit="exit"
|
||||||
|
variants={pageVariants}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimatedPageWrapper;
|
||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Row, Col, Form, Input, Select, DatePicker, TimePicker, InputNumber, Switch, Button, Checkbox } from 'antd';
|
import { Row, Col, Form, Input, Select, DatePicker, TimePicker, InputNumber, Switch, Button, Checkbox } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { AnimatedTabs } from '../index';
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +53,10 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
max,
|
max,
|
||||||
format,
|
format,
|
||||||
required,
|
required,
|
||||||
showTime
|
showTime,
|
||||||
|
tabItems,
|
||||||
|
activeKey,
|
||||||
|
onTabChange
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
// 현재 값 가져오기 (formData에서 또는 항목에서)
|
// 현재 값 가져오기 (formData에서 또는 항목에서)
|
||||||
@@ -105,6 +109,8 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
|
allowClear={false}
|
||||||
|
showTime={showTime || false}
|
||||||
value={currentValue ? dayjs(currentValue) : null}
|
value={currentValue ? dayjs(currentValue) : null}
|
||||||
format={format || 'YYYY-MM-DD'}
|
format={format || 'YYYY-MM-DD'}
|
||||||
onChange={(date) => onChange(key, date, handler)}
|
onChange={(date) => onChange(key, date, handler)}
|
||||||
@@ -191,8 +197,8 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 }
|
|||||||
case 'tab':
|
case 'tab':
|
||||||
return <AnimatedTabs
|
return <AnimatedTabs
|
||||||
items={tabItems}
|
items={tabItems}
|
||||||
activeKey={activeLanguage}
|
activeKey={activeKey}
|
||||||
onChange={handleTabChange}
|
onChange={onTabChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
case 'custom':
|
case 'custom':
|
||||||
@@ -249,13 +255,15 @@ const StatusDisplay = ({ status }) => {
|
|||||||
let color = '';
|
let color = '';
|
||||||
let text = '';
|
let text = '';
|
||||||
|
|
||||||
switch (status) {
|
const lowerStatus = typeof status === 'string' ? status.toLowerCase() : status;
|
||||||
|
|
||||||
|
switch (lowerStatus) {
|
||||||
case 'wait':
|
case 'wait':
|
||||||
color = '#faad14';
|
color = '#FAAD14';
|
||||||
text = '대기';
|
text = '대기';
|
||||||
break;
|
break;
|
||||||
case 'running':
|
case 'running':
|
||||||
color = '#52c41a';
|
color = '#4287f5';
|
||||||
text = '진행중';
|
text = '진행중';
|
||||||
break;
|
break;
|
||||||
case 'finish':
|
case 'finish':
|
||||||
@@ -271,7 +279,7 @@ const StatusDisplay = ({ status }) => {
|
|||||||
text = '삭제';
|
text = '삭제';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color = '#1890ff';
|
color = '#DEBB46';
|
||||||
text = status;
|
text = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import LoginLayout from './LoginLayout';
|
import LoginLayout from './LoginLayout';
|
||||||
import MainLayout from './MainLayout';
|
import MainLayout from './MainLayout';
|
||||||
|
import AnimatedPageWrapper from './AnimatedPageWrapper';
|
||||||
|
import DetailGrid from './DetailGrid';
|
||||||
|
import DetailLayout from './DetailLayout';
|
||||||
|
|
||||||
export { Layout, LoginLayout, MainLayout };
|
export { Layout, LoginLayout, MainLayout, AnimatedPageWrapper, DetailGrid, DetailLayout };
|
||||||
|
|||||||
@@ -5,17 +5,11 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||||||
|
|
||||||
// 통합된 애니메이션 탭 컴포넌트
|
// 통합된 애니메이션 탭 컴포넌트
|
||||||
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
||||||
return (
|
// 각 항목의 children을 애니메이션 래퍼로 감싸기
|
||||||
<StyledTabs
|
const tabItems = items.map(item => ({
|
||||||
activeKey={activeKey}
|
key: item.key,
|
||||||
onChange={onChange}
|
label: item.label,
|
||||||
centered={true}
|
children: (
|
||||||
>
|
|
||||||
{items.map(item => (
|
|
||||||
<Tabs.TabPane
|
|
||||||
tab={item.label}
|
|
||||||
key={item.key}
|
|
||||||
>
|
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key={activeKey}
|
key={activeKey}
|
||||||
@@ -31,12 +25,52 @@ const AnimatedTabs = ({ items, activeKey, onChange }) => {
|
|||||||
{item.children}
|
{item.children}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</Tabs.TabPane>
|
)
|
||||||
))}
|
}));
|
||||||
</StyledTabs>
|
|
||||||
|
return (
|
||||||
|
<StyledTabs
|
||||||
|
activeKey={activeKey}
|
||||||
|
onChange={onChange}
|
||||||
|
centered={true}
|
||||||
|
items={tabItems}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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)`
|
const StyledTabs = styled(Tabs)`
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import CDivider from './CDivider';
|
|||||||
import TopButton from './button/TopButton';
|
import TopButton from './button/TopButton';
|
||||||
import AntButton from './button/AntButton';
|
import AntButton from './button/AntButton';
|
||||||
import DetailLayout from './Layout/DetailLayout';
|
import DetailLayout from './Layout/DetailLayout';
|
||||||
|
import AnimatedTabs from './control/AnimatedTabs';
|
||||||
|
|
||||||
import CaliTable from './Custom/CaliTable'
|
import CaliTable from './Custom/CaliTable'
|
||||||
|
|
||||||
@@ -56,5 +57,6 @@ export { DateTimeInput,
|
|||||||
FrontPagination,
|
FrontPagination,
|
||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
CaliTable,
|
CaliTable,
|
||||||
DetailLayout
|
DetailLayout,
|
||||||
|
AnimatedTabs
|
||||||
};
|
};
|
||||||
@@ -64,15 +64,15 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
}, [modalType, content]);
|
}, [modalType, content]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if(modalType === TYPE_REGISTRY && configData?.length > 0){
|
// if(modalType === TYPE_REGISTRY && configData?.length > 0){
|
||||||
setResultData(prev => ({
|
// setResultData(prev => ({
|
||||||
...prev,
|
// ...prev,
|
||||||
round_count: configData[0].default_round_count,
|
// round_count: configData[0].default_round_count,
|
||||||
round_time: configData[0].round_time
|
// round_time: configData[0].round_time
|
||||||
}));
|
// }));
|
||||||
}
|
// }
|
||||||
}, [modalType, configData]);
|
// }, [modalType, configData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checkCondition()) {
|
if (checkCondition()) {
|
||||||
@@ -126,6 +126,26 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEndTimeChange = (time) => {
|
||||||
|
if (!time) return;
|
||||||
|
|
||||||
|
const newDateTime = resultData.event_end_time
|
||||||
|
? new Date(resultData.event_end_time)
|
||||||
|
: new Date();
|
||||||
|
|
||||||
|
newDateTime.setHours(
|
||||||
|
time.getHours(),
|
||||||
|
time.getMinutes(),
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
setResultData(prev => ({
|
||||||
|
...prev,
|
||||||
|
event_end_time: newDateTime
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
// 종료 날짜 변경 핸들러
|
// 종료 날짜 변경 핸들러
|
||||||
const handleEndDateChange = (date) => {
|
const handleEndDateChange = (date) => {
|
||||||
if (!date || !resultData.event_start_dt) return;
|
if (!date || !resultData.event_start_dt) return;
|
||||||
@@ -190,6 +210,18 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//최소 진행시간
|
||||||
|
if(resultData.event_operation_time < 10){
|
||||||
|
showToast('BATTLE_EVENT_MODAL_OPERATION_TIME_MIN_CHECK_WARNING', {type: alertTypes.warning});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//최대 진행시간
|
||||||
|
if(resultData.repeat_type !== 'NONE' && resultData.event_operation_time > 1400){
|
||||||
|
showToast('BATTLE_EVENT_MODAL_OPERATION_TIME_MAX_CHECK_WARNING', {type: alertTypes.warning});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if(resultData.round_time === 0){
|
// if(resultData.round_time === 0){
|
||||||
// const config = configData.find(data => data.id === resultData.config_id);
|
// const config = configData.find(data => data.id === resultData.config_id);
|
||||||
// setResultData({ ...resultData, round_time: config.round_time });
|
// setResultData({ ...resultData, round_time: config.round_time });
|
||||||
@@ -202,9 +234,15 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case "registConfirm":
|
case "registConfirm":
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...resultData,
|
||||||
|
event_operation_time: resultData.event_operation_time * 60
|
||||||
|
};
|
||||||
|
|
||||||
if(isView('modify')){
|
if(isView('modify')){
|
||||||
await withLoading( async () => {
|
await withLoading( async () => {
|
||||||
return await BattleEventModify(token, content?.id, resultData);
|
return await BattleEventModify(token, content?.id, params);
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
if(data.result === "SUCCESS") {
|
if(data.result === "SUCCESS") {
|
||||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
||||||
@@ -221,7 +259,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
await withLoading( async () => {
|
await withLoading( async () => {
|
||||||
return await BattleEventSingleRegist(token, resultData);
|
return await BattleEventSingleRegist(token, params);
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
if(data.result === "SUCCESS") {
|
if(data.result === "SUCCESS") {
|
||||||
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
showToast('REGIST_COMPLTE', {type: alertTypes.success});
|
||||||
@@ -265,6 +303,7 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
case "round":
|
case "round":
|
||||||
case "hot":
|
case "hot":
|
||||||
case "mode":
|
case "mode":
|
||||||
|
case "operation_time":
|
||||||
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);
|
||||||
@@ -302,12 +341,23 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
onDateChange={handleStartDateChange}
|
onDateChange={handleStartDateChange}
|
||||||
selectedDate={resultData?.event_start_dt}
|
selectedDate={resultData?.event_start_dt}
|
||||||
/>
|
/>
|
||||||
|
</FormRowGroup>
|
||||||
|
<FormRowGroup>
|
||||||
<SingleTimePicker
|
<SingleTimePicker
|
||||||
label="시작시간"
|
label="시작시간"
|
||||||
disabled={!isView('start_dt')}
|
disabled={!isView('start_dt')}
|
||||||
selectedTime={resultData?.event_start_dt}
|
selectedTime={resultData?.event_start_dt}
|
||||||
onTimeChange={handleStartTimeChange}
|
onTimeChange={handleStartTimeChange}
|
||||||
/>
|
/>
|
||||||
|
<FormLabel>진행시간(분)</FormLabel>
|
||||||
|
<FormInput
|
||||||
|
type="number"
|
||||||
|
disabled={!isView('operation_time')}
|
||||||
|
width='100px'
|
||||||
|
min={10}
|
||||||
|
value={resultData?.event_operation_time}
|
||||||
|
onChange={e => setResultData({ ...resultData, event_operation_time: e.target.value })}
|
||||||
|
/>
|
||||||
</FormRowGroup>
|
</FormRowGroup>
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
<FormLabel>반복</FormLabel>
|
<FormLabel>반복</FormLabel>
|
||||||
@@ -328,7 +378,7 @@ 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) => (*/}
|
||||||
@@ -337,15 +387,15 @@ const BattleEventModal = ({ modalType, detailView, handleDetailView, content, se
|
|||||||
{/* </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) => (*/}
|
||||||
<option key={index} value={data}>
|
{/* <option key={index} value={data}>*/}
|
||||||
{data}
|
{/* {data}*/}
|
||||||
</option>
|
{/* </option>*/}
|
||||||
))}
|
{/* ))}*/}
|
||||||
</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">*/}
|
||||||
@@ -434,7 +484,8 @@ export const initData = {
|
|||||||
hot_time: 1,
|
hot_time: 1,
|
||||||
game_mode_id: 1,
|
game_mode_id: 1,
|
||||||
event_start_dt: '',
|
event_start_dt: '',
|
||||||
event_end_dt: ''
|
event_end_dt: '',
|
||||||
|
event_operation_time: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BattleEventModal;
|
export default BattleEventModal;
|
||||||
|
|||||||
@@ -1,38 +1,19 @@
|
|||||||
import React, { useState, useEffect, Fragment } from 'react';
|
import React, { useState, useEffect, Fragment } from 'react';
|
||||||
import styled, { css, keyframes } from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import {
|
import { Title, ButtonGroupWrapper, } from '../../styles/Components';
|
||||||
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 Modal from '../common/modal/Modal';
|
||||||
import { EventIsItem, EventModify, MenuBannerModify } from '../../apis';
|
import { MenuBannerModify } 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 { authType, commonStatus } from '../../assets/data';
|
||||||
import { authType, benItems, commonStatus, currencyItemCode } from '../../assets/data';
|
import { convertKTCDate } from '../../utils';
|
||||||
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 { useLoading } from '../../context/LoadingProvider';
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes, battleEventStatusType, languageNames } from '../../assets/data/types';
|
import { alertTypes, languageNames } from '../../assets/data/types';
|
||||||
import { Tabs, Image as AntImage, Spin } from 'antd';
|
import { Image as AntImage } from 'antd';
|
||||||
import { TYPE_MODIFY, TYPE_REGISTRY } from '../../assets/data/adminConstants';
|
import { AntButton, DetailLayout } from '../common';
|
||||||
import { AntButton, DateTimeRangePicker, DetailLayout, SingleTimePicker } from '../common';
|
|
||||||
import AnimatedTabs from '../common/control/AnimatedTabs';
|
|
||||||
|
|
||||||
function renderImageContent(imageData) {
|
function renderImageContent(imageData) {
|
||||||
if (!imageData) {
|
if (!imageData) {
|
||||||
@@ -77,7 +58,6 @@ function renderImageContent(imageData) {
|
|||||||
|
|
||||||
const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetailData }) => {
|
||||||
const userInfo = useRecoilValue(authList);
|
const userInfo = useRecoilValue(authList);
|
||||||
const { t } = useTranslation();
|
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
const {withLoading} = useLoading();
|
const {withLoading} = useLoading();
|
||||||
const {showModal, showToast} = useAlert();
|
const {showModal, showToast} = useAlert();
|
||||||
@@ -94,17 +74,12 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
|
|
||||||
const [resultData, setResultData] = useState(initData);
|
const [resultData, setResultData] = useState(initData);
|
||||||
const [activeLanguage, setActiveLanguage] = useState('KO');
|
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([]);
|
const [tabItems, setTabItems] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(content){
|
if(content){
|
||||||
console.log(content);
|
// console.log(content);
|
||||||
const start_dt_KTC = convertKTCDate(content.start_dt);
|
const start_dt_KTC = convertKTCDate(content.start_dt);
|
||||||
const end_dt_KTC = convertKTCDate(content.end_dt);
|
const end_dt_KTC = convertKTCDate(content.end_dt);
|
||||||
|
|
||||||
@@ -131,13 +106,6 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (content && content.image_list) {
|
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) {
|
if (content.image_list && content.image_list.length > 0) {
|
||||||
@@ -156,105 +124,9 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
})) : [];
|
})) : [];
|
||||||
|
|
||||||
setTabItems(newTabItems);
|
setTabItems(newTabItems);
|
||||||
|
|
||||||
// 모든 이미지 프리로딩 시작
|
|
||||||
setTimeout(() => {
|
|
||||||
preloadAllImages();
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
}, [content]);
|
}, [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 handleReset = () => {
|
||||||
@@ -279,6 +151,7 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
case "submit":
|
case "submit":
|
||||||
if (!checkCondition()) return;
|
if (!checkCondition()) return;
|
||||||
|
|
||||||
|
// console.log(resultData);
|
||||||
showModal('MENU_BANNER_UPDATE_SAVE', {
|
showModal('MENU_BANNER_UPDATE_SAVE', {
|
||||||
type: alertTypes.confirm,
|
type: alertTypes.confirm,
|
||||||
onConfirm: () => handleSubmit('updateConfirm')
|
onConfirm: () => handleSubmit('updateConfirm')
|
||||||
@@ -287,51 +160,44 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
case "updateConfirm":
|
case "updateConfirm":
|
||||||
withLoading( async () => {
|
withLoading( async () => {
|
||||||
return await MenuBannerModify(token, id, resultData);
|
return await MenuBannerModify(token, id, resultData);
|
||||||
|
}).then(result => {
|
||||||
|
if(result.result === 'ERROR'){
|
||||||
|
showToast(result.data.message, {
|
||||||
|
type: alertTypes.error
|
||||||
|
});
|
||||||
|
}else if(result.result === 'SUCCESS'){
|
||||||
|
showToast('UPDATE_COMPLETED', {type: alertTypes.success, duration: 4000});
|
||||||
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
showToast('API_FAIL', {type: alertTypes.error});
|
showToast('API_FAIL', {type: alertTypes.error});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
showToast('UPDATE_COMPLETED', {type: alertTypes.success});
|
|
||||||
handleDetailView();
|
handleDetailView();
|
||||||
});
|
});
|
||||||
break;
|
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) => {
|
const isView = (fieldName) => {
|
||||||
if (!updateAuth) return false;
|
|
||||||
|
|
||||||
if (fieldName === 'editButton') {
|
if (fieldName === 'editButton') {
|
||||||
// updateAuth가 없거나 FINISH 상태면 수정 버튼 숨김 (false 반환)
|
// updateAuth가 없거나 FINISH 상태면 수정 버튼 숨김
|
||||||
return !updateAuth || content?.status === commonStatus.finish;
|
return !updateAuth || content?.status === commonStatus.finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (content?.status) {
|
if (!updateAuth) return false;
|
||||||
case commonStatus.running:
|
|
||||||
// RUNNING 상태일 때는 end_dt와 order_id만 수정 가능
|
if(content.status === commonStatus.wait){
|
||||||
return fieldName !== 'date' && fieldName !== 'order_id';
|
return true;
|
||||||
case commonStatus.wait:
|
}else if(content.status === commonStatus.running){
|
||||||
|
switch(fieldName){
|
||||||
|
case 'order_id':
|
||||||
|
case 'end_dt':
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemGroups = [
|
const itemGroups = [
|
||||||
@@ -371,12 +237,35 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
row: 2,
|
row: 2,
|
||||||
col: 0,
|
col: 0,
|
||||||
colSpan: 2,
|
colSpan: 2,
|
||||||
type: 'dateRange',
|
type: 'date',
|
||||||
key: 'dateRange',
|
key: 'start_dt',
|
||||||
keys: {start: 'start_dt', end: 'end_dt'},
|
label: '시작일',
|
||||||
label: '기간',
|
disabled: !isView('start_dt'),
|
||||||
disabled: !isView('date'),
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
format: 'YYYY-MM-DD HH:mm'
|
width: '250px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 2,
|
||||||
|
col: 2,
|
||||||
|
colSpan: 2,
|
||||||
|
type: 'date',
|
||||||
|
key: 'end_dt',
|
||||||
|
label: '종료일',
|
||||||
|
disabled: !isView('end_dt'),
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
width: '250px',
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
row: 3,
|
||||||
|
col: 0,
|
||||||
|
colSpan: 4,
|
||||||
|
type: 'tab',
|
||||||
|
key: 'languageTabs',
|
||||||
|
tabItems: tabItems,
|
||||||
|
activeKey: activeLanguage,
|
||||||
|
onTabChange: handleTabChange
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -393,76 +282,6 @@ const MenuBannerDetailModal = ({ detailView, handleDetailView, content, setDetai
|
|||||||
disabled={!updateAuth}
|
disabled={!updateAuth}
|
||||||
columnCount={4}
|
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">
|
<ButtonGroupWrapper $justify="flex-end" $gap="10px" $paddingTop="20px">
|
||||||
<AntButton
|
<AntButton
|
||||||
text="확인"
|
text="확인"
|
||||||
@@ -503,43 +322,6 @@ const initData = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
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`
|
const ImageContainer = styled.div`
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -590,22 +372,3 @@ const NoImagePlaceholder = styled.div`
|
|||||||
border-radius: 8px;
|
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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -186,17 +186,17 @@ const BattleEventSearchBar = ({ searchParams, onSearch, onReset, configData, rew
|
|||||||
))}
|
))}
|
||||||
</SelectInput>
|
</SelectInput>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<InputLabel>라운드 수</InputLabel>
|
{/*<InputLabel>라운드 수</InputLabel>*/}
|
||||||
<InputGroup>
|
{/*<InputGroup>*/}
|
||||||
<SelectInput value={searchParams.roundCount} onChange={e => onSearch({ roundCount: e.target.value })}>
|
{/* <SelectInput value={searchParams.roundCount} onChange={e => onSearch({ roundCount: e.target.value })}>*/}
|
||||||
<option value='ALL'>전체</option>
|
{/* <option value='ALL'>전체</option>*/}
|
||||||
{battleEventRoundCount.map((data, index) => (
|
{/* {battleEventRoundCount.map((data, index) => (*/}
|
||||||
<option key={index} value={data}>
|
{/* <option key={index} value={data}>*/}
|
||||||
{data}
|
{/* {data}*/}
|
||||||
</option>
|
{/* </option>*/}
|
||||||
))}
|
{/* ))}*/}
|
||||||
</SelectInput>
|
{/* </SelectInput>*/}
|
||||||
</InputGroup>
|
{/*</InputGroup>*/}
|
||||||
</>
|
</>
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
import { InputLabel } 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 { userSearchType2 } from '../../assets/data/options';
|
import { getCurrencyList } from '../../apis/Log';
|
||||||
import { getCurrencyDetailList, getCurrencyList } from '../../apis/Log';
|
|
||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes } from '../../assets/data/types';
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
|||||||
241
src/components/searchBar/CurrencyItemLogSearchBar.js
Normal file
241
src/components/searchBar/CurrencyItemLogSearchBar.js
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
amountDeltaType,
|
||||||
|
countDeltaType,
|
||||||
|
CurrencyType,
|
||||||
|
itemTypeLarge,
|
||||||
|
logAction,
|
||||||
|
userSearchType2,
|
||||||
|
} from '../../assets/data/options';
|
||||||
|
import {
|
||||||
|
getCurrencyItemList,
|
||||||
|
} from '../../apis/Log';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useCurrencyItemLogSearch = (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 getCurrencyItemList(
|
||||||
|
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 CurrencyItemLogSearchBar = ({ 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 CurrencyItemLogSearchBar;
|
||||||
252
src/components/searchBar/ItemLogSearchBar.js
Normal file
252
src/components/searchBar/ItemLogSearchBar.js
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import { TextInput, InputLabel, SelectInput, InputGroup } from '../../styles/Components';
|
||||||
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
amountDeltaType,
|
||||||
|
countDeltaType,
|
||||||
|
CurrencyType,
|
||||||
|
itemTypeLarge,
|
||||||
|
logAction,
|
||||||
|
userSearchType2,
|
||||||
|
} from '../../assets/data/options';
|
||||||
|
import { getCurrencyDetailList, getItemDetailList } from '../../apis/Log';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useItemLogSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
search_type: 'GUID',
|
||||||
|
search_data: '',
|
||||||
|
tran_id: '',
|
||||||
|
log_action: 'None',
|
||||||
|
item_large_type: 'None',
|
||||||
|
item_small_type: '',
|
||||||
|
count_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 getItemDetailList(
|
||||||
|
token,
|
||||||
|
params.search_type,
|
||||||
|
params.search_data,
|
||||||
|
params.tran_id,
|
||||||
|
params.log_action,
|
||||||
|
params.item_large_type,
|
||||||
|
params.item_small_type,
|
||||||
|
params.count_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',
|
||||||
|
item_large_type: 'None',
|
||||||
|
item_small_type: '',
|
||||||
|
count_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 ItemLogSearchBar = ({ 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)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>LargeType</InputLabel>
|
||||||
|
<SelectInput value={searchParams.item_large_type} onChange={e => onSearch({ item_large_type: e.target.value }, false)} >
|
||||||
|
<option value="None">전체</option>
|
||||||
|
{itemTypeLarge.map((data, index) => (
|
||||||
|
<option key={index} value={data.value}>
|
||||||
|
{data.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</SelectInput>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
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>SmallType</InputLabel>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
placeholder='Small Type 입력'
|
||||||
|
value={searchParams.item_small_type}
|
||||||
|
width="150px"
|
||||||
|
onChange={e => onSearch({ item_small_type: e.target.value }, false)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
<>
|
||||||
|
<InputLabel>증감유형</InputLabel>
|
||||||
|
<SelectInput value={searchParams.count_delta_type} onChange={e => onSearch({ count_delta_type: e.target.value }, false)} >
|
||||||
|
<option value="None">전체</option>
|
||||||
|
{countDeltaType.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 ItemLogSearchBar;
|
||||||
@@ -1,166 +1,151 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { InputLabel } from '../../styles/Components';
|
||||||
import { styled } from 'styled-components';
|
import { SearchBarLayout, SearchPeriod } from '../common/SearchBar';
|
||||||
import Button from '../common/button/Button';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import DatePickerComponent from '../common/Date/DatePickerComponent';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
import { RetentionIndexView } from '../../apis';
|
||||||
|
|
||||||
import { FormWrapper, InputLabel, TextInput, SelectInput, BtnWrapper, InputGroup, DatePickerWrapper, AlertText } from '../../styles/Components';
|
export const useRetentionSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
const RetentionSearchBar = ({ resultData, setResultData, handleSearch, fetchData, setRetention, setExcelBtn }) => {
|
const [searchParams, setSearchParams] = useState({
|
||||||
// 초기 날짜 세팅
|
start_dt: (() => {
|
||||||
let d = new Date();
|
const date = new Date();
|
||||||
const START_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(0, 0, 0, 0));
|
date.setDate(date.getDate() - 1);
|
||||||
const END_DATE = new Date(new Date(d.setDate(d.getDate() - 1)).setHours(24, 0, 0, 0));
|
return date;
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
})(),
|
||||||
const [period, setPeriod] = useState(0);
|
end_dt: (() => {
|
||||||
|
const date = new Date();
|
||||||
// resultData에 임의 날짜 넣어주기
|
date.setDate(date.getDate());
|
||||||
useEffect(() => {
|
return date;
|
||||||
setResultData({
|
})(),
|
||||||
send_dt: START_DATE,
|
order_by: 'ASC',
|
||||||
end_dt: '',
|
page_size: initialPageSize,
|
||||||
retention: 0,
|
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 RetentionIndexView(
|
||||||
|
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.retention);
|
||||||
|
return result.retention;
|
||||||
|
} 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 handleSelectedDate = data => {
|
const updatedParams = {
|
||||||
const sendDate = new Date(data);
|
...searchParams,
|
||||||
const resultSendData = new Date(sendDate.getFullYear(), sendDate.getMonth(), sendDate.getDate());
|
...newParams,
|
||||||
|
page_no: newParams.page_no || 1 // Reset to first page on new search
|
||||||
const resultEndDate = new Date(resultSendData);
|
|
||||||
resultEndDate.setDate(resultEndDate.getDate() + Number(resultData.retention));
|
|
||||||
|
|
||||||
setResultData({ ...resultData, send_dt: resultSendData, end_dt: resultEndDate });
|
|
||||||
};
|
};
|
||||||
|
updateSearchParams(updatedParams);
|
||||||
|
|
||||||
// // 발송 날짜 세팅 로직
|
if (executeSearch) {
|
||||||
// const handleEndDate = data => {
|
return await fetchData(updatedParams);
|
||||||
// const endDate = new Date(data);
|
|
||||||
// const resultSendData = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
|
||||||
|
|
||||||
// setResultData({ ...resultData, end_dt: resultSendData });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Retention 세팅 로직
|
|
||||||
const handleRetention = e => {
|
|
||||||
const value = e.target.value;
|
|
||||||
|
|
||||||
const resultEndDate = new Date(resultData.send_dt);
|
|
||||||
resultEndDate.setDate(resultEndDate.getDate() + Number(value));
|
|
||||||
|
|
||||||
setResultData({ ...resultData, end_dt: resultEndDate, retention: value });
|
|
||||||
setPeriod(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Retention 범위 선택 후 disable 처리 로직
|
|
||||||
const handleSearchBtn = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if(period == 0) {
|
|
||||||
setErrorMessage("필수값을 선택하세요.");
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
setErrorMessage("");
|
|
||||||
setExcelBtn(false); //활성화
|
|
||||||
handleSearch(resultData.send_dt, resultData.end_dt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}, [searchParams, fetchData]);
|
||||||
|
|
||||||
const handleReset = e => {
|
const handleReset = useCallback(async () => {
|
||||||
e.preventDefault();
|
const now = new Date();
|
||||||
setResultData({ send_dt: START_DATE, end_dt: '', retention: 0 });
|
now.setDate(now.getDate() - 1);
|
||||||
setRetention(1);
|
const resetParams = {
|
||||||
setErrorMessage("");
|
start_dt: now,
|
||||||
setPeriod(1);
|
end_dt: new Date(),
|
||||||
setExcelBtn(true); //비활성화
|
order_by: 'ASC',
|
||||||
|
page_size: initialPageSize,
|
||||||
|
page_no: 1
|
||||||
|
};
|
||||||
|
setSearchParams(resetParams);
|
||||||
|
return await fetchData(resetParams);
|
||||||
|
}, [initialPageSize, fetchData]);
|
||||||
|
|
||||||
fetchData(START_DATE, END_DATE);
|
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
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const RetentionSearchBar = ({ searchParams, onSearch, onReset }) => {
|
||||||
|
const handleSubmit = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onSearch(searchParams, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchList = [
|
||||||
<>
|
<>
|
||||||
<FormWrapper>
|
<InputLabel>일자</InputLabel>
|
||||||
<SearchbarStyle>
|
<SearchPeriod
|
||||||
<SearchItem>
|
startDate={searchParams.start_dt}
|
||||||
<InputLabel>집계 기준일</InputLabel>
|
handleStartDate={date => onSearch({ start_dt: date }, false)}
|
||||||
<InputGroup>
|
endDate={searchParams.end_dt}
|
||||||
<DatePickerWrapper>
|
handleEndDate={date => onSearch({ end_dt: date }, false)}
|
||||||
<DatePickerComponent
|
|
||||||
name="시작 일자" selectedDate={resultData.send_dt}
|
|
||||||
handleSelectedDate={data => handleSelectedDate(data)}
|
|
||||||
maxDate={new Date()} />
|
|
||||||
<span>~</span>
|
|
||||||
<DatePickerComponent
|
|
||||||
name="종료 일자"
|
|
||||||
selectedDate={resultData.end_dt}
|
|
||||||
maxDate={new Date()}
|
|
||||||
readOnly={true}
|
|
||||||
disabled={true}
|
|
||||||
type="retention" />
|
|
||||||
</DatePickerWrapper>
|
|
||||||
</InputGroup>
|
|
||||||
</SearchItem>
|
|
||||||
<SearchItem>
|
|
||||||
<InputLabel>Retention 범위</InputLabel>
|
|
||||||
<SelectInput
|
|
||||||
onChange={e => handleRetention(e)} value={resultData.retention}>
|
|
||||||
<option value={0}>선택</option>
|
|
||||||
<option value={1}>D+1</option>
|
|
||||||
<option value={7}>D+7</option>
|
|
||||||
<option value={30}>D+30</option>
|
|
||||||
</SelectInput>
|
|
||||||
</SearchItem>
|
|
||||||
{/* 기획 보류 */}
|
|
||||||
{/* <SearchItem>
|
|
||||||
<InputLabel>조회 국가</InputLabel>
|
|
||||||
<SelectInput>
|
|
||||||
<option value="">ALL</option>
|
|
||||||
<option value="">KR</option>
|
|
||||||
<option value="">EN</option>
|
|
||||||
<option value="">JP</option>
|
|
||||||
<option value="">TH</option>
|
|
||||||
</SelectInput>
|
|
||||||
</SearchItem> */}
|
|
||||||
<BtnWrapper $gap="8px">
|
|
||||||
<Button theme="reset" handleClick={handleReset} />
|
|
||||||
<Button
|
|
||||||
theme="search"
|
|
||||||
text="검색"
|
|
||||||
handleClick={handleSearchBtn}
|
|
||||||
/>
|
/>
|
||||||
<AlertText>{errorMessage}</AlertText>
|
</>,
|
||||||
</BtnWrapper>
|
];
|
||||||
</SearchbarStyle>
|
|
||||||
</FormWrapper>
|
return <SearchBarLayout firstColumnData={searchList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RetentionSearchBar;
|
export default RetentionSearchBar;
|
||||||
|
|
||||||
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 SearchItem = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin-right: 50px;
|
|
||||||
|
|
||||||
${TextInput}, ${SelectInput} {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
${TextInput} {
|
|
||||||
padding: 0 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
175
src/components/searchBar/UserCreateLogSearchBar.js
Normal file
175
src/components/searchBar/UserCreateLogSearchBar.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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 { getUserCreateList } from '../../apis';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useUserCreateLogSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
search_type: 'GUID',
|
||||||
|
search_data: '',
|
||||||
|
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 getUserCreateList(
|
||||||
|
token,
|
||||||
|
params.search_type,
|
||||||
|
params.search_data,
|
||||||
|
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: '',
|
||||||
|
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 UserCreateLogSearchBar = ({ 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>일자</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 UserCreateLogSearchBar;
|
||||||
191
src/components/searchBar/UserLoginLogSearchBar.js
Normal file
191
src/components/searchBar/UserLoginLogSearchBar.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
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 { getUserLoginDetailList } from '../../apis';
|
||||||
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
|
import { alertTypes } from '../../assets/data/types';
|
||||||
|
|
||||||
|
export const useUserLoginLogSearch = (token, initialPageSize) => {
|
||||||
|
const {showToast} = useAlert();
|
||||||
|
|
||||||
|
const [searchParams, setSearchParams] = useState({
|
||||||
|
search_type: 'GUID',
|
||||||
|
search_data: '',
|
||||||
|
tran_id: '',
|
||||||
|
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 getUserLoginDetailList(
|
||||||
|
token,
|
||||||
|
params.search_type,
|
||||||
|
params.search_data,
|
||||||
|
params.tran_id,
|
||||||
|
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: '',
|
||||||
|
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 UserLoginLogSearchBar = ({ 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)}
|
||||||
|
/>
|
||||||
|
</>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return <SearchBarLayout firstColumnData={searchList} secondColumnData={optionList} direction={'column'} onReset={onReset} handleSubmit={handleSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserLoginLogSearchBar;
|
||||||
@@ -18,14 +18,18 @@ import PlayTimeSearchBar from './PlayTimeSearchBar';
|
|||||||
import UserViewSearchBar from './UserViewSearchBar';
|
import UserViewSearchBar from './UserViewSearchBar';
|
||||||
import AdminViewSearchBar from './AdminViewSearchBar';
|
import AdminViewSearchBar from './AdminViewSearchBar';
|
||||||
import EventListSearchBar from './EventListSearchBar';
|
import EventListSearchBar from './EventListSearchBar';
|
||||||
import RetentionSearchBar from './RetentionSearchBar';
|
import RetentionSearchBar, {useRetentionSearch} from './RetentionSearchBar';
|
||||||
import UserBlockSearchBar from './UserBlockSearchBar';
|
import UserBlockSearchBar from './UserBlockSearchBar';
|
||||||
import UserIndexSearchBar from './UserIndexSearchBar';
|
import UserIndexSearchBar from './UserIndexSearchBar';
|
||||||
import ReportListSearchBar from './ReportListSearchBar';
|
import ReportListSearchBar from './ReportListSearchBar';
|
||||||
import BattleEventSearchBar from './BattleEventSearchBar';
|
import BattleEventSearchBar from './BattleEventSearchBar';
|
||||||
import BusinessLogSearchBar, { useBusinessLogSearch } from './BusinessLogSearchBar';
|
import BusinessLogSearchBar, { useBusinessLogSearch } from './BusinessLogSearchBar';
|
||||||
import CurrencyLogSearchBar, { useCurrencyLogSearch } from './CurrencyLogSearchBar';
|
import CurrencyLogSearchBar, { useCurrencyLogSearch } from './CurrencyLogSearchBar';
|
||||||
|
import ItemLogSearchBar, { useItemLogSearch } from './ItemLogSearchBar';
|
||||||
|
import CurrencyItemLogSearchBar, { useCurrencyItemLogSearch } from './CurrencyItemLogSearchBar';
|
||||||
import CurrencyIndexSearchBar, { useCurrencyIndexSearch } from './CurrencyIndexSearchBar';
|
import CurrencyIndexSearchBar, { useCurrencyIndexSearch } from './CurrencyIndexSearchBar';
|
||||||
|
import UserCreateLogSearchBar, { useUserCreateLogSearch } from './UserCreateLogSearchBar';
|
||||||
|
import UserLoginLogSearchBar, { useUserLoginLogSearch } from './UserLoginLogSearchBar';
|
||||||
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
import LandAuctionSearchBar from './LandAuctionSearchBar';
|
||||||
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
import CaliumRequestSearchBar from './CaliumRequestSearchBar';
|
||||||
|
|
||||||
@@ -51,6 +55,7 @@ export {
|
|||||||
AdminViewSearchBar,
|
AdminViewSearchBar,
|
||||||
EventListSearchBar,
|
EventListSearchBar,
|
||||||
RetentionSearchBar,
|
RetentionSearchBar,
|
||||||
|
useRetentionSearch,
|
||||||
UserBlockSearchBar,
|
UserBlockSearchBar,
|
||||||
UserIndexSearchBar,
|
UserIndexSearchBar,
|
||||||
ReportListSearchBar,
|
ReportListSearchBar,
|
||||||
@@ -62,6 +67,14 @@ export {
|
|||||||
LandAuctionSearchBar,
|
LandAuctionSearchBar,
|
||||||
CaliumRequestSearchBar,
|
CaliumRequestSearchBar,
|
||||||
CurrencyIndexSearchBar,
|
CurrencyIndexSearchBar,
|
||||||
useCurrencyIndexSearch
|
useCurrencyIndexSearch,
|
||||||
|
useItemLogSearch,
|
||||||
|
ItemLogSearchBar,
|
||||||
|
CurrencyItemLogSearchBar,
|
||||||
|
useCurrencyItemLogSearch,
|
||||||
|
UserCreateLogSearchBar,
|
||||||
|
useUserCreateLogSearch,
|
||||||
|
UserLoginLogSearchBar,
|
||||||
|
useUserLoginLogSearch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
15
src/i18n.js
15
src/i18n.js
@@ -5,6 +5,7 @@ const resources = {
|
|||||||
ko: {
|
ko: {
|
||||||
translation: {
|
translation: {
|
||||||
//메시지
|
//메시지
|
||||||
|
USER_LOGOUT_CONFIRM: '로그아웃 하시겠습니까?\n(로그아웃 시 저장되지 않은 값은 초기화 됩니다.)',
|
||||||
NULL_MSG: '필수값을 입력해주세요.',
|
NULL_MSG: '필수값을 입력해주세요.',
|
||||||
DATE_KTC: '* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)',
|
DATE_KTC: '* UTC+9 한국시간 기준으로 설정 (UTC+0 자동 반영처리)',
|
||||||
NOT_ITEM: '존재하지 않는 아이템코드입니다.',
|
NOT_ITEM: '존재하지 않는 아이템코드입니다.',
|
||||||
@@ -53,9 +54,13 @@ const resources = {
|
|||||||
EXCEL_EXPORT_LENGTH_LIMIT_WARNING: '엑셀 다운은 10만건 이하까지만 가능합니다.\r\n조건을 조정 후 다시 시도해주세요.',
|
EXCEL_EXPORT_LENGTH_LIMIT_WARNING: '엑셀 다운은 10만건 이하까지만 가능합니다.\r\n조건을 조정 후 다시 시도해주세요.',
|
||||||
DOWNLOAD_COMPLETE: '다운이 완료되었습니다.',
|
DOWNLOAD_COMPLETE: '다운이 완료되었습니다.',
|
||||||
DOWNLOAD_FAIL: '다운이 실패하였습니다.',
|
DOWNLOAD_FAIL: '다운이 실패하였습니다.',
|
||||||
|
DELETE_STATUS_ONLY_WAIT: '대기상태의 데이터만 삭제가 가능합니다.',
|
||||||
|
TABLE_DATA_NOT_FOUND: '데이터가 없습니다.',
|
||||||
//user
|
//user
|
||||||
NICKNAME_CHANGES_CONFIRM: '닉네임을 변경하시겠습니까?',
|
NICKNAME_CHANGES_CONFIRM: '닉네임을 변경하시겠습니까?',
|
||||||
NICKNAME_CHANGES_COMPLETE: '닉네임 변경이 완료되었습니다.',
|
NICKNAME_CHANGES_COMPLETE: '닉네임 변경이 완료되었습니다.',
|
||||||
|
QUEST_TASK_COMPLETE_CONFIRM: '퀘스트를 강제 완료 처리하시겠습니까?',
|
||||||
|
QUEST_TASK_COMPLETE: '퀘스트를 강제 완료 요청처리가 되었습니다.\n재 조회후 상태를 확인해주세요.\n(최대 3분정도 소요)',
|
||||||
//table
|
//table
|
||||||
TABLE_ITEM_DELETE_TITLE: "선택 삭제",
|
TABLE_ITEM_DELETE_TITLE: "선택 삭제",
|
||||||
TABLE_BUTTON_DETAIL_TITLE: "상세보기",
|
TABLE_BUTTON_DETAIL_TITLE: "상세보기",
|
||||||
@@ -126,6 +131,8 @@ const resources = {
|
|||||||
//전투시스템
|
//전투시스템
|
||||||
BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.",
|
BATTLE_EVENT_MODAL_START_DT_WARNING: "시작 시간은 현재 시간으로부터 10분 이후부터 가능합니다.",
|
||||||
BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.",
|
BATTLE_EVENT_MODAL_TIME_CHECK_WARNING :"해당 시간에 속하는 이벤트가 존재합니다.",
|
||||||
|
BATTLE_EVENT_MODAL_OPERATION_TIME_MIN_CHECK_WARNING :"진행시간은 최소 10분 이상이여야 합니다.",
|
||||||
|
BATTLE_EVENT_MODAL_OPERATION_TIME_MAX_CHECK_WARNING :"진행시간은 최대 1400분까지 가능합니다.",
|
||||||
BATTLE_EVENT_REGIST_CONFIRM: "이벤트를 등록하시겠습니까?",
|
BATTLE_EVENT_REGIST_CONFIRM: "이벤트를 등록하시겠습니까?",
|
||||||
BATTLE_EVENT_UPDATE_CONFIRM: "이벤트를 수정하시겠습니까?",
|
BATTLE_EVENT_UPDATE_CONFIRM: "이벤트를 수정하시겠습니까?",
|
||||||
BATTLE_EVENT_SELECT_DELETE: "선택된 이벤트를 삭제하시겠습니까?",
|
BATTLE_EVENT_SELECT_DELETE: "선택된 이벤트를 삭제하시겠습니까?",
|
||||||
@@ -157,18 +164,24 @@ const resources = {
|
|||||||
FILE_SIZE_OVER_ERROR: "파일의 사이즈가 5MB를 초과하였습니다.",
|
FILE_SIZE_OVER_ERROR: "파일의 사이즈가 5MB를 초과하였습니다.",
|
||||||
//파일명칭
|
//파일명칭
|
||||||
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
|
FILE_INDEX_USER_CONTENT: 'Caliverse_User_Index.xlsx',
|
||||||
|
FILE_INDEX_USER_RETENTION: 'Caliverse_Retention.xlsx',
|
||||||
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',
|
FILE_CALIUM_REQUEST: 'Caliverse_Calium_Request.xlsx',
|
||||||
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_GAME_LOG_CURRENCY: 'Caliverse_Game_Log_Currency',
|
||||||
|
FILE_GAME_LOG_USER_CREATE: 'Caliverse_Game_Log_User_Create',
|
||||||
|
FILE_GAME_LOG_USER_LOGIN: 'Caliverse_Game_Log_User_Login',
|
||||||
|
FILE_GAME_LOG_ITEM: 'Caliverse_Game_Log_Item',
|
||||||
|
FILE_GAME_LOG_CURRENCY_ITEM: 'Caliverse_Game_Log_Currecy_Item',
|
||||||
FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index',
|
FILE_CURRENCY_INDEX: 'Caliverse_Currency_Index',
|
||||||
//서버 에러메시지
|
//서버 에러메시지
|
||||||
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
DYNAMODB_NOT_USER: '유저 정보를 확인해주세요.',
|
||||||
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
NICKNAME_EXIT_ERROR: '해당 닉네임이 존재합니다.',
|
||||||
NICKNAME_NUMBER_ERROR: '닉네임은 첫번째 글자에 숫자를 허용하지 않습니다.',
|
NICKNAME_NUMBER_ERROR: '닉네임은 첫번째 글자에 숫자를 허용하지 않습니다.',
|
||||||
NICKNAME_SPECIALCHAR_ERROR: '닉네임은 특수문자를 사용할 수 없습니다.',
|
NICKNAME_SPECIALCHAR_ERROR: '닉네임은 특수문자를 사용할 수 없습니다.',
|
||||||
NICKNAME_LANGTH_ERROR: '닉네임은 최소 2글자에서 최대 12글자까지 허용 합니다.'
|
NICKNAME_LANGTH_ERROR: '닉네임은 최소 2글자에서 최대 12글자까지 허용 합니다.',
|
||||||
|
dynamoDB_connection_error: '운영DB 작업중 에러가 발생하였습니다.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
TableStyle,
|
TableStyle,
|
||||||
@@ -58,6 +59,13 @@ const BusinessLogView = () => {
|
|||||||
updateSearchParams
|
updateSearchParams
|
||||||
} = useBusinessLogSearch(token, 500);
|
} = useBusinessLogSearch(token, 500);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
setDownloadState({
|
||||||
|
loading: false,
|
||||||
|
progress: 0
|
||||||
|
});
|
||||||
|
},[dataList]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
// 세션 스토리지에서 복사된 메일 데이터 가져오기
|
||||||
const paramsData = sessionStorage.getItem(STORAGE_BUSINESS_LOG_SEARCH);
|
const paramsData = sessionStorage.getItem(STORAGE_BUSINESS_LOG_SEARCH);
|
||||||
@@ -167,7 +175,7 @@ const BusinessLogView = () => {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>비즈니스 로그 조회</Title>
|
<Title>비즈니스 로그 조회</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<BusinessLogSearchBar
|
<BusinessLogSearchBar
|
||||||
@@ -264,7 +272,7 @@ const BusinessLogView = () => {
|
|||||||
<TopButton />
|
<TopButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
import { styled } from 'styled-components';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Fragment, useState } from 'react';
|
|
||||||
|
|
||||||
import { Title, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
|
||||||
|
|
||||||
import LandSearchBar from '../../components/searchBar/LandSearchBar';
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
import QuestDetailModal from '../../components/DataManage/QuestDetailModal';
|
|
||||||
import LandDetailModal from '../../components/DataManage/LandDetailModal';
|
|
||||||
import Modal from '../../components/common/modal/Modal';
|
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
const ContentsView = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
|
|
||||||
const [detailPop, setDetailPop] = useState('hidden');
|
|
||||||
const mokupData = [
|
|
||||||
{
|
|
||||||
landId: '2000515223',
|
|
||||||
ownerNick: '칼리버스F',
|
|
||||||
ownerId: '23d59e868ca342198f6a653d957914a5',
|
|
||||||
lockInDate: '2023-08-11 15:32:07',
|
|
||||||
landUrl: '0x5765eB84ab55369f430DdA0d0C2b443FB9372DB3',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
if (detailPop === 'hidden') setDetailPop('view');
|
|
||||||
else setDetailPop('hidden');
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 13) ? (
|
|
||||||
<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>
|
|
||||||
<LandSearchBar />
|
|
||||||
<TableWrapper>
|
|
||||||
<TableStyle>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="150">랜드 ID</th>
|
|
||||||
<th>소유자 아바타명</th>
|
|
||||||
<th>소유쟈 GUID</th>
|
|
||||||
<th width="200">Lock IN 처리 일자</th>
|
|
||||||
<th>랜드 URL</th>
|
|
||||||
<th width="150">상세보기</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{mokupData.map((data, index) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<tr>
|
|
||||||
<td>{data.landId}</td>
|
|
||||||
<td>{data.ownerNick}</td>
|
|
||||||
<td>{data.ownerId}</td>
|
|
||||||
<td>{new Date(data.lockInDate).toLocaleString()}</td>
|
|
||||||
<td>
|
|
||||||
<LandLink to={data.landUrl}>{data.landUrl}</LandLink>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Button theme="line" text="상세보기" handleClick={handleClick} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</TableWrapper>
|
|
||||||
<LandDetailModal detailPop={detailPop} handleClick={handleClick} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContentsView;
|
|
||||||
|
|
||||||
const TableWrapper = styled.div`
|
|
||||||
overflow: auto;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #666666;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: #d9d9d9;
|
|
||||||
}
|
|
||||||
thead {
|
|
||||||
th {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${TableStyle} {
|
|
||||||
min-width: 1000px;
|
|
||||||
th {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LandLink = styled(Link)`
|
|
||||||
color: #61a2d0;
|
|
||||||
text-decoration: underline;
|
|
||||||
`;
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import Pagination from '../../components/common/Pagination/Pagination';
|
|
||||||
|
|
||||||
import { registerLocale } from 'react-datepicker';
|
import { registerLocale } from 'react-datepicker';
|
||||||
import { ko } from 'date-fns/esm/locale';
|
import { ko } from 'date-fns/esm/locale';
|
||||||
@@ -9,96 +8,21 @@ import 'react-datepicker/dist/react-datepicker.css';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
SelectInput,
|
|
||||||
TableStyle,
|
|
||||||
TableInfo,
|
|
||||||
ListCount,
|
|
||||||
ListOption,
|
|
||||||
TabScroll, TabItem, TabWrapper,
|
TabScroll, TabItem, TabWrapper,
|
||||||
} from '../../styles/Components';
|
} from '../../styles/Components';
|
||||||
import { styled } from 'styled-components';
|
|
||||||
|
|
||||||
import { withAuth } from '../../hooks/hook';
|
import { withAuth } from '../../hooks/hook';
|
||||||
import { authType } from '../../assets/data';
|
import { authType } from '../../assets/data';
|
||||||
import { TabGameLogList } from '../../assets/data/options';
|
import { TabGameLogList } from '../../assets/data/options';
|
||||||
import CurrencyLogContent from '../../components/DataManage/CurrencyLogContent';
|
import CurrencyLogContent from '../../components/DataManage/CurrencyLogContent';
|
||||||
import { STORAGE_GAME_LOG_CURRENCY_SEARCH } from '../../assets/data/adminConstants';
|
import { STORAGE_GAME_LOG_CURRENCY_SEARCH } from '../../assets/data/adminConstants';
|
||||||
|
import ItemLogContent from '../../components/DataManage/ItemLogContent';
|
||||||
|
import CurrencyItemLogContent from '../../components/DataManage/CurrencyItemLogContent';
|
||||||
|
import UserCreateLogContent from '../../components/DataManage/UserCreateLogContent';
|
||||||
|
import UserLoginLogContent from '../../components/DataManage/UserLoginLogContent';
|
||||||
|
|
||||||
registerLocale('ko', ko);
|
registerLocale('ko', ko);
|
||||||
|
|
||||||
const ItemLogContent = () => {
|
|
||||||
const mokupData = [
|
|
||||||
{
|
|
||||||
date: '2023-08-05 12:11:32',
|
|
||||||
name: '칼리버스',
|
|
||||||
id: '16CD2ECD-4798-46CE-9B6B-F952CF11F196',
|
|
||||||
action: '획득',
|
|
||||||
route: '아이템 제작',
|
|
||||||
itemName: 'Item_name',
|
|
||||||
serialNumber: 'Serial_number',
|
|
||||||
itemCode: 'Item_code',
|
|
||||||
count: 1,
|
|
||||||
key: 'User_trade_key',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<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">액션</th>
|
|
||||||
<th width="150">획득/소진경로</th>
|
|
||||||
<th width="200">아이템명</th>
|
|
||||||
<th width="200">시리얼 넘버</th>
|
|
||||||
<th width="200">고유코드</th>
|
|
||||||
<th width="80">개수</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.action}</td>
|
|
||||||
<td>{data.route}</td>
|
|
||||||
<td>{data.itemName}e</td>
|
|
||||||
<td>{data.serialNumber}</td>
|
|
||||||
<td>{data.itemCode}</td>
|
|
||||||
<td>{data.count}</td>
|
|
||||||
<td>{data.key}</td>
|
|
||||||
</tr>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</TableStyle>
|
|
||||||
</TableWrapper>
|
|
||||||
<Pagination />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const GameLogView = () => {
|
const GameLogView = () => {
|
||||||
const [activeTab, setActiveTab] = useState('CURRENCY');
|
const [activeTab, setActiveTab] = useState('CURRENCY');
|
||||||
|
|
||||||
@@ -109,7 +33,6 @@ const GameLogView = () => {
|
|||||||
const searchData = JSON.parse(paramsData);
|
const searchData = JSON.parse(paramsData);
|
||||||
|
|
||||||
setActiveTab(searchData.tab);
|
setActiveTab(searchData.tab);
|
||||||
console.log(searchData);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -119,7 +42,7 @@ const GameLogView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>게임 로그 조회</Title>
|
<Title>게임 로그 조회</Title>
|
||||||
<TabScroll>
|
<TabScroll>
|
||||||
<TabWrapper>
|
<TabWrapper>
|
||||||
@@ -134,30 +57,13 @@ const GameLogView = () => {
|
|||||||
})}
|
})}
|
||||||
</TabWrapper>
|
</TabWrapper>
|
||||||
</TabScroll>
|
</TabScroll>
|
||||||
{/*{activeTab === 'ITEM' && <ItemLogContent />}*/}
|
|
||||||
<CurrencyLogContent active={activeTab === 'CURRENCY'} />
|
<CurrencyLogContent active={activeTab === 'CURRENCY'} />
|
||||||
{/*{activeTab === 'TRADE' && <TradeLogContent />}*/}
|
<ItemLogContent active={activeTab === 'ITEM'} />
|
||||||
</>
|
<CurrencyItemLogContent active={activeTab === 'CURRENCYITEM'} />
|
||||||
|
<UserCreateLogContent active={activeTab === 'USERCREATE'} />
|
||||||
|
<UserLoginLogContent active={activeTab === 'USERLOGIN'} />
|
||||||
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withAuth(authType.gameLogRead)(GameLogView);
|
export default withAuth(authType.gameLogRead)(GameLogView);
|
||||||
|
|
||||||
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: max-content;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import React, { Fragment, useRef, useState } from 'react';
|
import React, { Fragment, useRef, useState } from 'react';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
@@ -158,7 +159,7 @@ const LandInfoView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>랜드 정보 조회</Title>
|
<Title>랜드 정보 조회</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<LandInfoSearchBar
|
<LandInfoSearchBar
|
||||||
@@ -240,7 +241,7 @@ const LandInfoView = () => {
|
|||||||
content={detailData}
|
content={detailData}
|
||||||
setDetailData={setDetailData}
|
setDetailData={setDetailData}
|
||||||
/>
|
/>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, Fragment } from 'react';
|
import { useState, Fragment } from 'react';
|
||||||
|
|
||||||
import { TabScroll, Title } from '../../styles/Components';
|
import { TabScroll, Title } from '../../styles/Components';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ const UserView = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.userSearchRead) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.userSearchRead) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>유저조회</Title>
|
<Title>유저조회</Title>
|
||||||
<UserViewSearchBar setInfoView={setInfoView} resultData={resultData} handleTab={handleTab} setResultData={setResultData} />
|
<UserViewSearchBar setInfoView={setInfoView} resultData={resultData} handleTab={handleTab} setResultData={setResultData} />
|
||||||
<UserWrapper display={infoView}>
|
<UserWrapper display={infoView}>
|
||||||
@@ -68,7 +69,7 @@ const UserView = () => {
|
|||||||
{activeContent === '클레임' && <UserClaimInfo userInfo={resultData && resultData} />}
|
{activeContent === '클레임' && <UserClaimInfo userInfo={resultData && resultData} />}
|
||||||
</UserTabInfo>
|
</UserTabInfo>
|
||||||
</UserWrapper>
|
</UserWrapper>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -127,30 +128,3 @@ const UserTabInfo = styled.div`
|
|||||||
background: #d9d9d9;
|
background: #d9d9d9;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UserDefaultTable = styled.table`
|
|
||||||
border: 1px solid #e8eaec;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
th {
|
|
||||||
background: #efefef;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 12px;
|
|
||||||
text-align: center;
|
|
||||||
border-left: 1px solid #e8eaec;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
background: #fff;
|
|
||||||
border-bottom: 1px solid #e8eaec;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
height: 24px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ const EconomicIndex = () => {
|
|||||||
setActiveTab(content);
|
setActiveTab(content);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>경제 지표</Title>
|
<Title>경제 지표</Title>
|
||||||
<TabScroll>
|
<TabScroll>
|
||||||
<TabWrapper>
|
<TabWrapper>
|
||||||
@@ -49,7 +50,7 @@ const EconomicIndex = () => {
|
|||||||
{/*{activeTab === 'item' && <ItemContent />}*/}
|
{/*{activeTab === 'item' && <ItemContent />}*/}
|
||||||
{/*{activeTab === 'instance' && <InstanceContent />}*/}
|
{/*{activeTab === 'instance' && <InstanceContent />}*/}
|
||||||
{/*{activeTab === 'deco' && <DecoContent />}*/}
|
{/*{activeTab === 'deco' && <DecoContent />}*/}
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,50 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
import { Title } from '../../styles/Components';
|
||||||
import Modal from '../../components/common/modal/Modal';
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
|
|
||||||
import { Title, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import { userIndexView, userTotalIndex } from '../../apis';
|
|
||||||
|
|
||||||
import { UserContent, SegmentContent, PlayTimeContent, RetentionContent, DailyActiveUserContent, DailyMedalContent } from '../../components/IndexManage/index';
|
import { UserContent, SegmentContent, PlayTimeContent, RetentionContent, DailyActiveUserContent, DailyMedalContent } from '../../components/IndexManage/index';
|
||||||
import AuthModal from '../../components/common/modal/AuthModal';
|
|
||||||
import { authType } from '../../assets/data';
|
import { authType } from '../../assets/data';
|
||||||
|
import { withAuth } from '../../hooks/hook';
|
||||||
|
import { TabUserIndexList } from '../../assets/data/options';
|
||||||
|
|
||||||
const UserIndex = () => {
|
const UserIndex = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const [activeTab, setActiveTab] = useState('USER');
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
const [activeTab, setActiveTab] = useState('이용자 지표');
|
|
||||||
|
|
||||||
const handleTab = (e, content) => {
|
const handleTab = (e, content) => {
|
||||||
// e.preventDefault();
|
e.preventDefault();
|
||||||
setActiveTab(content);
|
setActiveTab(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabList = [
|
|
||||||
{ title: '이용자 지표' },
|
|
||||||
// { title: 'Retention' },
|
|
||||||
// { title: 'Segment' },
|
|
||||||
// { title: '플레이타임' },
|
|
||||||
// { title: 'DAU' },
|
|
||||||
// { title: '메달' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.userIndicatorsRead) ? (
|
|
||||||
<AuthModal/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Title>유저 지표</Title>
|
<Title>유저 지표</Title>
|
||||||
<TabWrapper>
|
<TabWrapper>
|
||||||
{TabList.map((el, idx) => {
|
{TabUserIndexList.map((el, idx) => {
|
||||||
return (
|
return (
|
||||||
<TabItem
|
<li key={idx}>
|
||||||
key={idx}
|
<TabItem $state={activeTab === el.value ? 'active' : 'none'} onClick={e => handleTab(e, el.value)}>
|
||||||
$state={el.title === activeTab ? 'active' : 'unactive'}
|
{el.name}
|
||||||
onClick={(e) => handleTab(e, el.title)}
|
|
||||||
>
|
|
||||||
{el.title}
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
);
|
</li>
|
||||||
|
)
|
||||||
})}
|
})}
|
||||||
</TabWrapper>
|
</TabWrapper>
|
||||||
|
|
||||||
{/*{activeTab === 'DAU' && <DailyActiveUserContent />}*/}
|
{/*{activeTab === 'DAU' && <DailyActiveUserContent />}*/}
|
||||||
{activeTab === '이용자 지표' && <UserContent />}
|
{activeTab === 'USER' && <UserContent />}
|
||||||
{activeTab === 'Retention' && <RetentionContent />}
|
{activeTab === 'RETENTION' && <RetentionContent />}
|
||||||
{activeTab === 'Segment' && <SegmentContent />}
|
{activeTab === 'SEGMENT' && <SegmentContent />}
|
||||||
{activeTab === '플레이타임' && <PlayTimeContent />}
|
{activeTab === 'PLAYTIME' && <PlayTimeContent />}
|
||||||
{/*{activeTab === '메달' && <DailyMedalContent />}*/}
|
{/*{activeTab === '메달' && <DailyMedalContent />}*/}
|
||||||
|
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserIndex;
|
export default withAuth(authType.userIndicatorsRead)(UserIndex);
|
||||||
|
|
||||||
const TabItem = styled(Link)`
|
const TabItem = styled(Link)`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ import {
|
|||||||
} from '../../components/common';
|
} from '../../components/common';
|
||||||
import { convertKTC, convertKTCDate, convertUTC, timeDiffMinute } from '../../utils';
|
import { convertKTC, convertKTCDate, convertUTC, timeDiffMinute } from '../../utils';
|
||||||
import { BattleEventModal } from '../../components/ServiceManage';
|
import { BattleEventModal } from '../../components/ServiceManage';
|
||||||
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
import {
|
||||||
|
INITIAL_PAGE_SIZE,
|
||||||
|
INITIAL_PAGE_LIMIT,
|
||||||
|
BATTLE_EVENT_OPERATION_TIME_WAIT_SECONDS,
|
||||||
|
} from '../../assets/data/adminConstants';
|
||||||
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
|
import { useDataFetch, useModal, useTable, withAuth } from '../../hooks/hook';
|
||||||
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
||||||
import { battleEventStatus, battleRepeatType } from '../../assets/data/options';
|
import { battleEventStatus, battleRepeatType } from '../../assets/data/options';
|
||||||
@@ -44,6 +48,7 @@ import { useLoading } from '../../context/LoadingProvider';
|
|||||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
import { historyTables } from '../../assets/data/data';
|
import { historyTables } from '../../assets/data/data';
|
||||||
import { LogHistory } from '../../apis';
|
import { LogHistory } from '../../apis';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const BattleEvent = () => {
|
const BattleEvent = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -98,7 +103,7 @@ const BattleEvent = () => {
|
|||||||
const endTime = (start_dt, operation_time) =>{
|
const endTime = (start_dt, operation_time) =>{
|
||||||
const startDate = new Date(start_dt);
|
const startDate = new Date(start_dt);
|
||||||
|
|
||||||
startDate.setSeconds(startDate.getSeconds() + operation_time);
|
startDate.setSeconds(startDate.getSeconds() + operation_time + BATTLE_EVENT_OPERATION_TIME_WAIT_SECONDS);
|
||||||
|
|
||||||
return startDate;
|
return startDate;
|
||||||
}
|
}
|
||||||
@@ -251,7 +256,7 @@ const BattleEvent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>전투시스템 타임 스케줄러</Title>
|
<Title>전투시스템 타임 스케줄러</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<BattleEventSearchBar
|
<BattleEventSearchBar
|
||||||
@@ -295,16 +300,16 @@ const BattleEvent = () => {
|
|||||||
<th width="90">그룹</th>
|
<th width="90">그룹</th>
|
||||||
<th width="70">이벤트 ID</th>
|
<th width="70">이벤트 ID</th>
|
||||||
<th width="200">이벤트명</th>
|
<th width="200">이벤트명</th>
|
||||||
|
<th width="90">게임모드</th>
|
||||||
<th width="80">반복</th>
|
<th width="80">반복</th>
|
||||||
<th width="100">기간 시작일(KST)</th>
|
<th width="100">기간 시작일(KST)</th>
|
||||||
<th width="100">기간 종료일(KST)</th>
|
<th width="100">기간 종료일(KST)</th>
|
||||||
<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>
|
||||||
<th width="150">히스토리</th>
|
<th width="150">히스토리</th>
|
||||||
@@ -321,6 +326,7 @@ const BattleEvent = () => {
|
|||||||
<td>{battle.group_id}</td>
|
<td>{battle.group_id}</td>
|
||||||
<td>{battle.id}</td>
|
<td>{battle.id}</td>
|
||||||
<td>{battle.event_name}</td>
|
<td>{battle.event_name}</td>
|
||||||
|
<td>{battle.game_mode_id}</td>
|
||||||
<StatusWapper>
|
<StatusWapper>
|
||||||
<StatusLabel $status={battle.repeat_type}>
|
<StatusLabel $status={battle.repeat_type}>
|
||||||
{battleRepeatType.find(data => data.value === battle.repeat_type).name}
|
{battleRepeatType.find(data => data.value === battle.repeat_type).name}
|
||||||
@@ -335,10 +341,9 @@ const BattleEvent = () => {
|
|||||||
{battleEventStatus.find(data => data.value === battle.status).name}
|
{battleEventStatus.find(data => data.value === battle.status).name}
|
||||||
</StatusLabel>
|
</StatusLabel>
|
||||||
</StatusWapper>
|
</StatusWapper>
|
||||||
<td>{battle.game_mode_id}</td>
|
|
||||||
{/*<td>{secondToMinutes(battle.round_time)}분</td>*/}
|
{/*<td>{secondToMinutes(battle.round_time)}분</td>*/}
|
||||||
{/*<td>{battle.reward_group_id}</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>
|
||||||
<Button theme="line" text="상세보기"
|
<Button theme="line" text="상세보기"
|
||||||
@@ -377,7 +382,7 @@ const BattleEvent = () => {
|
|||||||
title="히스토리"
|
title="히스토리"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { useLoading } from '../../context/LoadingProvider';
|
|||||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
import { historyTables } from '../../assets/data/data';
|
import { historyTables } from '../../assets/data/data';
|
||||||
import { LogHistory } from '../../apis';
|
import { LogHistory } from '../../apis';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const Board = () => {
|
const Board = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -127,7 +128,7 @@ const Board = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>인게임 메시지</Title>
|
<Title>인게임 메시지</Title>
|
||||||
<TableInfo>
|
<TableInfo>
|
||||||
<ListOption>
|
<ListOption>
|
||||||
@@ -217,7 +218,7 @@ const Board = () => {
|
|||||||
title="히스토리"
|
title="히스토리"
|
||||||
/>
|
/>
|
||||||
</TableWrapper>
|
</TableWrapper>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { alertTypes } from '../../assets/data/types';
|
|||||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||||
import { historyTables } from '../../assets/data/data';
|
import { historyTables } from '../../assets/data/data';
|
||||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
|
import {AnimatedPageWrapper} from '../../components/common/Layout';
|
||||||
|
|
||||||
const Event = () => {
|
const Event = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -158,7 +159,7 @@ const Event = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventRead) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>출석 보상 이벤트 관리</Title>
|
<Title>출석 보상 이벤트 관리</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CommonSearchBar
|
<CommonSearchBar
|
||||||
@@ -281,7 +282,7 @@ const Event = () => {
|
|||||||
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
||||||
</ModalInputItem>
|
</ModalInputItem>
|
||||||
</DynamicModal>
|
</DynamicModal>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { timeDiffMinute } from '../../utils';
|
|||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
import { alertTypes, currencyCodeTypes } from '../../assets/data/types';
|
||||||
import { useLoading } from '../../context/LoadingProvider';
|
import { useLoading } from '../../context/LoadingProvider';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const EventRegist = () => {
|
const EventRegist = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -275,7 +276,7 @@ const EventRegist = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
||||||
<AuthModal/>
|
<AuthModal/>
|
||||||
) : (
|
) : (
|
||||||
@@ -458,7 +459,7 @@ const EventRegist = () => {
|
|||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useLoading } from '../../context/LoadingProvider';
|
|||||||
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
import useEnhancedCommonSearch from '../../hooks/useEnhancedCommonSearch';
|
||||||
import CustomConfirmModal from '../../components/common/modal/CustomConfirmModal';
|
import CustomConfirmModal from '../../components/common/modal/CustomConfirmModal';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const Items = () => {
|
const Items = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -155,7 +156,7 @@ const Items = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>아이템 관리</Title>
|
<Title>아이템 관리</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CommonSearchBar
|
<CommonSearchBar
|
||||||
@@ -199,7 +200,7 @@ const Items = () => {
|
|||||||
handleCancel={() => handleModalClose('delete')}
|
handleCancel={() => handleModalClose('delete')}
|
||||||
handleClose={() => handleModalClose('delete')}
|
handleClose={() => handleModalClose('delete')}
|
||||||
/>
|
/>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { alertTypes } from '../../assets/data/types';
|
|||||||
import { useAlert } from '../../context/AlertProvider';
|
import { useAlert } from '../../context/AlertProvider';
|
||||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
import { historyTables } from '../../assets/data/data';
|
import { historyTables } from '../../assets/data/data';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const LandAuction = () => {
|
const LandAuction = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -161,7 +162,7 @@ const LandAuction = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>랜드 경매 관리</Title>
|
<Title>랜드 경매 관리</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<LandAuctionSearchBar
|
<LandAuctionSearchBar
|
||||||
@@ -277,7 +278,7 @@ const LandAuction = () => {
|
|||||||
title="히스토리"
|
title="히스토리"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { useLoading } from '../../context/LoadingProvider';
|
|||||||
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
import useCommonSearchOld from '../../hooks/useCommonSearchOld';
|
||||||
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
import { historyTables } from '../../assets/data/data';
|
import { historyTables } from '../../assets/data/data';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const Mail = () => {
|
const Mail = () => {
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
@@ -134,7 +135,7 @@ const Mail = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>우편 조회 및 발송 관리</Title>
|
<Title>우편 조회 및 발송 관리</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CommonSearchBar
|
<CommonSearchBar
|
||||||
@@ -238,7 +239,7 @@ const Mail = () => {
|
|||||||
title="히스토리"
|
title="히스토리"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
SearchBarAlert,
|
SearchBarAlert,
|
||||||
} from '../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
|
|
||||||
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';
|
||||||
@@ -351,7 +352,7 @@ const MailRegist = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.mailUpdate) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>우편 등록</Title>
|
<Title>우편 등록</Title>
|
||||||
|
|
||||||
<RegistGroup>
|
<RegistGroup>
|
||||||
@@ -637,7 +638,7 @@ const MailRegist = () => {
|
|||||||
handleClick={() => handleSubmit('submit')}
|
handleClick={() => handleSubmit('submit')}
|
||||||
/>
|
/>
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { useState, Fragment, useRef } from 'react';
|
import React, { useState, Fragment, useRef } from 'react';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
authType,
|
authType, commonStatus as CommonStatus,
|
||||||
} from '../../assets/data';
|
} from '../../assets/data';
|
||||||
import { Title, FormWrapper} from '../../styles/Components';
|
import { Title, FormWrapper} from '../../styles/Components';
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
CaliTable, TableHeader,
|
CaliTable, TableHeader,
|
||||||
} from '../../components/common';
|
} from '../../components/common';
|
||||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
|
||||||
import { INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
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 { LogHistory, MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
||||||
import { CommonSearchBar, useCommonSearch } from '../../components/ServiceManage';
|
import { CommonSearchBar } 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';
|
import MenuBannerDetailModal from '../../components/modal/MenuBannerDetailModal';
|
||||||
|
import { historyTables } from '../../assets/data/data';
|
||||||
|
import LogDetailModal from '../../components/common/modal/LogDetailModal';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
const MenuBanner = () => {
|
const MenuBanner = () => {
|
||||||
const tableRef = useRef(null);
|
const tableRef = useRef(null);
|
||||||
@@ -30,6 +32,7 @@ const MenuBanner = () => {
|
|||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
|
|
||||||
const [detailData, setDetailData] = useState({});
|
const [detailData, setDetailData] = useState({});
|
||||||
|
const [historyData, setHistoryData] = useState({});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
modalState,
|
modalState,
|
||||||
@@ -37,6 +40,7 @@ const MenuBanner = () => {
|
|||||||
handleModalClose
|
handleModalClose
|
||||||
} = useModal({
|
} = useModal({
|
||||||
detail: 'hidden',
|
detail: 'hidden',
|
||||||
|
history: 'hidden',
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -49,10 +53,6 @@ const MenuBanner = () => {
|
|||||||
updateSearchParams,
|
updateSearchParams,
|
||||||
loading,
|
loading,
|
||||||
configLoaded,
|
configLoaded,
|
||||||
paginationType,
|
|
||||||
pagination,
|
|
||||||
goToNextPage,
|
|
||||||
goToPrevPage,
|
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePageSizeChange
|
handlePageSizeChange
|
||||||
} = useEnhancedCommonSearch("menuBannerSearch");
|
} = useEnhancedCommonSearch("menuBannerSearch");
|
||||||
@@ -65,6 +65,17 @@ const MenuBanner = () => {
|
|||||||
|
|
||||||
const handleAction = async (action, item = null) => {
|
const handleAction = async (action, item = null) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case "history":
|
||||||
|
const params = {};
|
||||||
|
params.db_type = "MYSQL"
|
||||||
|
params.sql_id = item.id;
|
||||||
|
params.table_name = historyTables.menuBanner
|
||||||
|
|
||||||
|
await LogHistory(token, params).then(data => {
|
||||||
|
setHistoryData(data);
|
||||||
|
handleModalView('history');
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "detail":
|
case "detail":
|
||||||
await MenuBannerDetailView(token, item.id).then(data => {
|
await MenuBannerDetailView(token, item.id).then(data => {
|
||||||
setDetailData(data.detail);
|
setDetailData(data.detail);
|
||||||
@@ -72,38 +83,22 @@ const MenuBanner = () => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
const date_check = selectedRows.every(row => {
|
|
||||||
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
|
|
||||||
return timeDiff < 3;
|
|
||||||
});
|
|
||||||
if(date_check){
|
|
||||||
showToast('LAND_AUCTION_DELETE_DATE_WARNING', {type: alertTypes.warning});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showModal('MENU_BANNER_SELECT_DELETE', {
|
showModal('MENU_BANNER_SELECT_DELETE', {
|
||||||
type: alertTypes.confirm,
|
type: alertTypes.confirm,
|
||||||
onConfirm: () => handleAction('deleteConfirm')
|
onConfirm: () => handleAction('deleteConfirm')
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "deleteConfirm":
|
case "deleteConfirm":
|
||||||
let list = [];
|
const low = selectedRows[0];
|
||||||
let isChecked = false;
|
|
||||||
|
|
||||||
selectedRows.map(data => {
|
if(low.status !== CommonStatus.wait) {
|
||||||
// const row = dataList.list.find(row => row.id === Number(data.id));
|
showToast('DELETE_STATUS_ONLY_WAIT', {type: alertTypes.warning});
|
||||||
// if(row.status !== commonStatus.wait) isChecked = true;
|
|
||||||
list.push({
|
|
||||||
id: data.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if(isChecked) {
|
|
||||||
showToast('LAND_AUCTION_WARNING_DELETE', {type: alertTypes.warning});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await withLoading(async () => {
|
await withLoading(async () => {
|
||||||
return await MenuBannerDelete(token, list);
|
return await MenuBannerDelete(token, low.id);
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
if(data.result === "SUCCESS") {
|
if(data.result === "SUCCESS") {
|
||||||
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
showToast('DEL_COMPLETE', {type: alertTypes.success});
|
||||||
@@ -119,7 +114,6 @@ const MenuBanner = () => {
|
|||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
handleSearch(updateSearchParams);
|
handleSearch(updateSearchParams);
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -128,7 +122,7 @@ const MenuBanner = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>메뉴 배너 관리</Title>
|
<Title>메뉴 배너 관리</Title>
|
||||||
|
|
||||||
{/* 조회조건 */}
|
{/* 조회조건 */}
|
||||||
@@ -183,13 +177,24 @@ const MenuBanner = () => {
|
|||||||
{/* 상세 */}
|
{/* 상세 */}
|
||||||
<MenuBannerDetailModal
|
<MenuBannerDetailModal
|
||||||
detailView={modalState.detailModal}
|
detailView={modalState.detailModal}
|
||||||
handleDetailView={() => handleModalClose('detail')}
|
handleDetailView={() => {
|
||||||
|
handleModalClose('detail');
|
||||||
|
handleSearch(updateSearchParams);
|
||||||
|
}}
|
||||||
content={detailData}
|
content={detailData}
|
||||||
setDetailData={setDetailData}
|
setDetailData={setDetailData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
<LogDetailModal
|
||||||
|
viewMode="changed"
|
||||||
|
detailView={modalState.historyModal}
|
||||||
|
handleDetailView={() => handleModalClose('history')}
|
||||||
|
changedData={historyData}
|
||||||
|
title="히스토리"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</AnimatedPageWrapper>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withAuth(authType.battleEventRead)(MenuBanner);
|
export default withAuth(authType.menuBannerRead)(MenuBanner);
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import Loading from '../../components/common/Loading';
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
BtnWrapper,
|
BtnWrapper,
|
||||||
SearchBarAlert,
|
SearchBarAlert,
|
||||||
} from '../../styles/Components';
|
} from '../../styles/Components';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { MenuBannerSingleRegist } from '../../apis';
|
import { MenuBannerSingleRegist } from '../../apis';
|
||||||
@@ -19,14 +19,14 @@ import {
|
|||||||
FormInput, FormInputSuffix, FormInputSuffixWrapper, FormLabel, FormRowGroup,RegistGroup,
|
FormInput, FormInputSuffix, FormInputSuffixWrapper, FormLabel, FormRowGroup,RegistGroup,
|
||||||
} from '../../styles/ModuleComponents';
|
} from '../../styles/ModuleComponents';
|
||||||
import AuthModal from '../../components/common/modal/AuthModal';
|
import AuthModal from '../../components/common/modal/AuthModal';
|
||||||
import { authType, modalTypes } from '../../assets/data';
|
import { authType } from '../../assets/data';
|
||||||
import { loadConfig, timeDiffMinute } from '../../utils';
|
import { loadConfig } from '../../utils';
|
||||||
import { SingleDatePicker, SingleTimePicker } from '../../components/common';
|
import { SingleDatePicker, SingleTimePicker } from '../../components/common';
|
||||||
import CheckBox from '../../components/common/input/CheckBox';
|
import CheckBox from '../../components/common/input/CheckBox';
|
||||||
import ImageUploadBtn from '../../components/ServiceManage/ImageUploadBtn';
|
import ImageUploadBtn from '../../components/ServiceManage/ImageUploadBtn';
|
||||||
import CaliForm from '../../components/common/Custom/CaliForm';
|
|
||||||
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';
|
||||||
|
|
||||||
const MenuBannerRegist = () => {
|
const MenuBannerRegist = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -34,8 +34,7 @@ const MenuBannerRegist = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const token = sessionStorage.getItem('token');
|
const token = sessionStorage.getItem('token');
|
||||||
const { showToast, showModal } = useAlert();
|
const { showToast, showModal } = useAlert();
|
||||||
|
const {withLoading} = useLoading();
|
||||||
const [loading, setLoading] = useState(false); // 로딩 창
|
|
||||||
|
|
||||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
||||||
const [alertMsg, setAlertMsg] = useState('');
|
const [alertMsg, setAlertMsg] = useState('');
|
||||||
@@ -45,7 +44,6 @@ const MenuBannerRegist = () => {
|
|||||||
|
|
||||||
const [pageConfig, setPageConfig] = useState(null);
|
const [pageConfig, setPageConfig] = useState(null);
|
||||||
const [formData, setFormData] = useState({});
|
const [formData, setFormData] = useState({});
|
||||||
const [isFormValid, setIsFormValid] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(alertMsg){
|
if(alertMsg){
|
||||||
@@ -69,19 +67,6 @@ const MenuBannerRegist = () => {
|
|||||||
loadPageConfig();
|
loadPageConfig();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleFieldValidation = (isValid, errors) => {
|
|
||||||
setIsFormValid(isValid);
|
|
||||||
|
|
||||||
if (errors._form) {
|
|
||||||
setAlertMsg(t(errors._form));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 폼 제출 핸들러
|
|
||||||
const handleFormSubmit = (data) => {
|
|
||||||
setFormData(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checkCondition()) {
|
if (checkCondition()) {
|
||||||
@@ -109,7 +94,7 @@ const MenuBannerRegist = () => {
|
|||||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||||
|
|
||||||
if (endDay <= startDay) {
|
if (endDay <= startDay) {
|
||||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning} );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +138,7 @@ const MenuBannerRegist = () => {
|
|||||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||||
|
|
||||||
if (endDay <= startDay) {
|
if (endDay <= startDay) {
|
||||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
showToast('DATE_START_DIFF_END_WARNING', {type: alertTypes.warning} );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,27 +211,43 @@ const MenuBannerRegist = () => {
|
|||||||
navigate('/servicemanage/menubanner');
|
navigate('/servicemanage/menubanner');
|
||||||
break;
|
break;
|
||||||
case "registConfirm":
|
case "registConfirm":
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const result = await MenuBannerSingleRegist(token, resultData);
|
await withLoading(async () => {
|
||||||
|
return await MenuBannerSingleRegist(token, resultData);
|
||||||
|
}).then(result => {
|
||||||
|
// console.log(result);
|
||||||
|
if(result.result === 'ERROR'){
|
||||||
|
setResultData(prevData => ({
|
||||||
|
...prevData,
|
||||||
|
image_list: prevData.image_list.map(img => ({
|
||||||
|
...img,
|
||||||
|
content: ''
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
setLoading(false);
|
showToast(result.data.message, {
|
||||||
|
type: alertTypes.error
|
||||||
|
});
|
||||||
|
}else if(result.result === 'SUCCESS'){
|
||||||
showToast('REGIST_COMPLTE', {
|
showToast('REGIST_COMPLTE', {
|
||||||
type: alertTypes.success,
|
type: alertTypes.success,
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
});
|
});
|
||||||
navigate('/servicemanage/menubanner');
|
navigate('/servicemanage/menubanner');
|
||||||
break;
|
}
|
||||||
case "warning":
|
}).catch(error => {
|
||||||
setAlertMsg('');
|
showToast(error, {type: alertTypes.error} );
|
||||||
|
}).finally(() => {
|
||||||
|
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkCondition = () => {
|
const checkCondition = () => {
|
||||||
return (
|
return (
|
||||||
(resultData.start_dt.length !== 0) &&
|
(resultData.start_dt && resultData.start_dt.length !== 0) &&
|
||||||
(resultData.end_dt.length !== 0) &&
|
(resultData.end_dt && resultData.end_dt.length !== 0) &&
|
||||||
resultData.title !== '' &&
|
resultData.title !== '' &&
|
||||||
resultData.image_list.every(data => data.content !== '') &&
|
resultData.image_list.every(data => data.content !== '') &&
|
||||||
(resultData.is_link === false || (resultData.is_link === true && resultData.link_list.every(data => data.content !== '')))
|
(resultData.is_link === false || (resultData.is_link === true && resultData.link_list.every(data => data.content !== '')))
|
||||||
@@ -258,7 +259,7 @@ const MenuBannerRegist = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
||||||
<AuthModal/>
|
<AuthModal/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>메뉴배너 등록</Title>
|
<Title>메뉴배너 등록</Title>
|
||||||
<RegistGroup>
|
<RegistGroup>
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
@@ -315,12 +316,11 @@ const MenuBannerRegist = () => {
|
|||||||
/>
|
/>
|
||||||
</FormRowGroup>
|
</FormRowGroup>
|
||||||
{resultData?.is_link &&
|
{resultData?.is_link &&
|
||||||
<>
|
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
<FormLabel>웹 링크</FormLabel>
|
<FormLabel>웹 링크</FormLabel>
|
||||||
<LanguageWrapper width="50%" >
|
<LanguageWrapper width="50%" >
|
||||||
{resultData.link_list.map((data, idx) => (
|
{resultData.link_list.map((data, idx) => (
|
||||||
<FormInputSuffixWrapper>
|
<FormInputSuffixWrapper key={idx}>
|
||||||
<FormInput
|
<FormInput
|
||||||
type="text"
|
type="text"
|
||||||
value={resultData?.link_list[idx].content}
|
value={resultData?.link_list[idx].content}
|
||||||
@@ -336,7 +336,6 @@ const MenuBannerRegist = () => {
|
|||||||
))}
|
))}
|
||||||
</LanguageWrapper>
|
</LanguageWrapper>
|
||||||
</FormRowGroup>
|
</FormRowGroup>
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
</RegistGroup>
|
</RegistGroup>
|
||||||
|
|
||||||
@@ -367,9 +366,7 @@ const MenuBannerRegist = () => {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
|
</AnimatedPageWrapper>
|
||||||
{loading && <Loading/>}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -381,14 +378,14 @@ const initData = {
|
|||||||
start_dt: '',
|
start_dt: '',
|
||||||
end_dt: '',
|
end_dt: '',
|
||||||
image_list: [
|
image_list: [
|
||||||
{ language: 'KO', content: '' },
|
{ language: 'Ko', content: '' },
|
||||||
{ language: 'EN', content: '' },
|
{ language: 'En', content: '' },
|
||||||
{ language: 'JA', content: '' },
|
{ language: 'Ja', content: '' },
|
||||||
],
|
],
|
||||||
link_list: [
|
link_list: [
|
||||||
{ language: 'KO', content: '' },
|
{ language: 'Ko', content: '' },
|
||||||
{ language: 'EN', content: '' },
|
{ language: 'En', content: '' },
|
||||||
{ language: 'JA', content: '' },
|
{ language: 'Ja', content: '' },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,433 +0,0 @@
|
|||||||
import React, { useState, Fragment, useEffect } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
|
||||||
import Loading from '../../components/common/Loading';
|
|
||||||
import {
|
|
||||||
Title,
|
|
||||||
BtnWrapper,
|
|
||||||
SearchBarAlert,
|
|
||||||
} from '../../styles/Components';
|
|
||||||
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { MenuBannerSingleRegist } from '../../apis';
|
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import {
|
|
||||||
FormInput, FormInputSuffix, FormInputSuffixWrapper, FormLabel, FormRowGroup,RegistGroup,
|
|
||||||
} from '../../styles/ModuleComponents';
|
|
||||||
import AuthModal from '../../components/common/modal/AuthModal';
|
|
||||||
import { authType, modalTypes } from '../../assets/data';
|
|
||||||
import DynamicModal from '../../components/common/modal/DynamicModal';
|
|
||||||
import { loadConfig, timeDiffMinute } from '../../utils';
|
|
||||||
import { SingleDatePicker, SingleTimePicker } from '../../components/common';
|
|
||||||
import CheckBox from '../../components/common/input/CheckBox';
|
|
||||||
import ImageUploadBtn from '../../components/ServiceManage/ImageUploadBtn';
|
|
||||||
import { useModal } from '../../hooks/hook';
|
|
||||||
|
|
||||||
const MenuBannerRegist = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const token = sessionStorage.getItem('token');
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false); // 로딩 창
|
|
||||||
const {
|
|
||||||
modalState,
|
|
||||||
handleModalView,
|
|
||||||
handleModalClose
|
|
||||||
} = useModal({
|
|
||||||
cancel: 'hidden',
|
|
||||||
registConfirm: 'hidden',
|
|
||||||
registComplete: 'hidden'
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isNullValue, setIsNullValue] = useState(false); // 데이터 값 체크
|
|
||||||
const [alertMsg, setAlertMsg] = useState('');
|
|
||||||
|
|
||||||
const [resultData, setResultData] = useState(initData); //데이터 정보
|
|
||||||
const [resetDateTime, setResetDateTime] = useState(false);
|
|
||||||
|
|
||||||
const [pageConfig, setPageConfig] = useState(null);
|
|
||||||
const [formData, setFormData] = useState({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadPageConfig = async () => {
|
|
||||||
try {
|
|
||||||
const config = await loadConfig('menuBannerRegist');
|
|
||||||
setPageConfig(config);
|
|
||||||
setFormData(config.initData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load page configuration', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadPageConfig();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (checkCondition()) {
|
|
||||||
setIsNullValue(false);
|
|
||||||
} else {
|
|
||||||
setIsNullValue(true);
|
|
||||||
}
|
|
||||||
}, [resultData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (resetDateTime) {
|
|
||||||
setResetDateTime(false);
|
|
||||||
}
|
|
||||||
}, [resetDateTime]);
|
|
||||||
|
|
||||||
// 시작 날짜 변경 핸들러
|
|
||||||
const handleStartDateChange = (date) => {
|
|
||||||
if (!date) return;
|
|
||||||
|
|
||||||
const newDate = new Date(date);
|
|
||||||
|
|
||||||
if(resultData.end_dt){
|
|
||||||
const endDate = new Date(resultData.end_dt);
|
|
||||||
const startDay = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate());
|
|
||||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
|
||||||
|
|
||||||
if (endDay <= startDay) {
|
|
||||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
start_dt: newDate
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 시작 시간 변경 핸들러
|
|
||||||
const handleStartTimeChange = (time) => {
|
|
||||||
if (!time) return;
|
|
||||||
|
|
||||||
const newDateTime = resultData.start_dt
|
|
||||||
? new Date(resultData.start_dt)
|
|
||||||
: new Date();
|
|
||||||
|
|
||||||
newDateTime.setHours(
|
|
||||||
time.getHours(),
|
|
||||||
time.getMinutes(),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
start_dt: newDateTime
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 종료 날짜 변경 핸들러
|
|
||||||
const handleEndDateChange = (date) => {
|
|
||||||
if (!date || !resultData.start_dt) return;
|
|
||||||
|
|
||||||
const startDate = new Date(resultData.start_dt);
|
|
||||||
const endDate = new Date(date);
|
|
||||||
|
|
||||||
// 일자만 비교하기 위해 년/월/일만 추출
|
|
||||||
const startDay = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
|
||||||
const endDay = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
|
||||||
|
|
||||||
if (endDay <= startDay) {
|
|
||||||
setAlertMsg(t('DATE_START_DIFF_END_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
end_dt: endDate
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 종료 시간 변경 핸들러
|
|
||||||
const handleEndTimeChange = (time) => {
|
|
||||||
if (!time) return;
|
|
||||||
|
|
||||||
const newDateTime = resultData.end_dt
|
|
||||||
? new Date(resultData.end_dt)
|
|
||||||
: new Date();
|
|
||||||
|
|
||||||
newDateTime.setHours(
|
|
||||||
time.getHours(),
|
|
||||||
time.getMinutes(),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
setResultData(prev => ({
|
|
||||||
...prev,
|
|
||||||
end_dt: newDateTime
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 이미지 업로드
|
|
||||||
const handleImageUpload = (language, file, fileName) => {
|
|
||||||
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
|
|
||||||
|
|
||||||
if (imageIndex !== -1) {
|
|
||||||
const updatedImageList = [...resultData.image_list];
|
|
||||||
updatedImageList[imageIndex] = {
|
|
||||||
...updatedImageList[imageIndex],
|
|
||||||
content: fileName,
|
|
||||||
};
|
|
||||||
|
|
||||||
setResultData({
|
|
||||||
...resultData,
|
|
||||||
image_list: updatedImageList
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 이미지 삭제
|
|
||||||
const handleImageDelete = (language) => {
|
|
||||||
const imageIndex = resultData.image_list.findIndex(img => img.language === language);
|
|
||||||
|
|
||||||
if (imageIndex !== -1) {
|
|
||||||
const updatedImageList = [...resultData.image_list];
|
|
||||||
updatedImageList[imageIndex] = {
|
|
||||||
...updatedImageList[imageIndex],
|
|
||||||
content: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
setResultData({
|
|
||||||
...resultData,
|
|
||||||
image_list: updatedImageList
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (type, param = null) => {
|
|
||||||
switch (type) {
|
|
||||||
case "submit":
|
|
||||||
if (!checkCondition()) return;
|
|
||||||
const timeDiff = timeDiffMinute(resultData.start_dt, (new Date))
|
|
||||||
if(timeDiff < 60) {
|
|
||||||
setAlertMsg(t('EVENT_TIME_LIMIT_ADD'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModalView('registConfirm');
|
|
||||||
break;
|
|
||||||
case "cancel":
|
|
||||||
|
|
||||||
handleModalClose('cancel');
|
|
||||||
navigate('/servicemanage/menubanner');
|
|
||||||
break;
|
|
||||||
case "registConfirm":
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const result = await MenuBannerSingleRegist(token, resultData);
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
handleModalClose('registConfirm');
|
|
||||||
handleModalView('registComplete');
|
|
||||||
break;
|
|
||||||
case "registComplete":
|
|
||||||
handleModalClose('registComplete');
|
|
||||||
|
|
||||||
navigate('/servicemanage/menubanner');
|
|
||||||
break;
|
|
||||||
case "warning":
|
|
||||||
setAlertMsg('');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkCondition = () => {
|
|
||||||
return (
|
|
||||||
(resultData.start_dt.length !== 0) &&
|
|
||||||
(resultData.end_dt.length !== 0) &&
|
|
||||||
resultData.title !== '' &&
|
|
||||||
resultData.image_list.every(data => data.content !== '') &&
|
|
||||||
(resultData.is_link === false || (resultData.is_link === true && resultData.link_list.every(data => data.content !== '')))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.eventUpdate) ? (
|
|
||||||
<AuthModal/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Title>메뉴배너 등록</Title>
|
|
||||||
<RegistGroup>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>등록기간</FormLabel>
|
|
||||||
<SingleDatePicker
|
|
||||||
label="시작일자"
|
|
||||||
dateLabel="시작 일자"
|
|
||||||
onDateChange={handleStartDateChange}
|
|
||||||
selectedDate={resultData?.start_dt}
|
|
||||||
/>
|
|
||||||
<SingleTimePicker
|
|
||||||
selectedTime={resultData?.start_dt}
|
|
||||||
onTimeChange={handleStartTimeChange}
|
|
||||||
/>
|
|
||||||
<SingleDatePicker
|
|
||||||
label="종료일자"
|
|
||||||
dateLabel="종료 일자"
|
|
||||||
onDateChange={handleEndDateChange}
|
|
||||||
selectedDate={resultData?.end_dt}
|
|
||||||
/>
|
|
||||||
<SingleTimePicker
|
|
||||||
selectedTime={resultData?.end_dt}
|
|
||||||
onTimeChange={handleEndTimeChange}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>배너 제목</FormLabel>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
width='50%'
|
|
||||||
value={resultData?.title}
|
|
||||||
onChange={e => setResultData({ ...resultData, title: e.target.value })}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
<FormLabel>이미지 첨부</FormLabel>
|
|
||||||
{resultData.image_list.map((data, idx) => (
|
|
||||||
<LanguageWrapper key={idx}>
|
|
||||||
<LanguageLabel>{data.language}</LanguageLabel>
|
|
||||||
<ImageUploadBtn
|
|
||||||
onImageUpload={(file, fileName) => handleImageUpload(data.language, file, fileName)}
|
|
||||||
onFileDelete={() => handleImageDelete(data.language)}
|
|
||||||
fileName={data.content}
|
|
||||||
setAlertMessage={setAlertMsg}
|
|
||||||
/>
|
|
||||||
</LanguageWrapper>
|
|
||||||
))}
|
|
||||||
<FormRowGroup>
|
|
||||||
<CheckBox
|
|
||||||
label="이미지 링크 여부"
|
|
||||||
id="reserve"
|
|
||||||
checked={resultData.is_link}
|
|
||||||
setData={e => setResultData({ ...resultData, is_link: e.target.checked })}
|
|
||||||
/>
|
|
||||||
</FormRowGroup>
|
|
||||||
{resultData?.is_link &&
|
|
||||||
<>
|
|
||||||
<FormRowGroup>
|
|
||||||
<FormLabel>웹 링크</FormLabel>
|
|
||||||
<LanguageWrapper width="50%" >
|
|
||||||
{resultData.link_list.map((data, idx) => (
|
|
||||||
<FormInputSuffixWrapper>
|
|
||||||
<FormInput
|
|
||||||
type="text"
|
|
||||||
value={resultData?.link_list[idx].content}
|
|
||||||
onChange={e => {
|
|
||||||
const updatedLinkList = [...resultData.link_list];
|
|
||||||
updatedLinkList[idx] = { ...updatedLinkList[idx], content: e.target.value };
|
|
||||||
setResultData({ ...resultData, link_list: updatedLinkList });
|
|
||||||
}}
|
|
||||||
suffix="true"
|
|
||||||
/>
|
|
||||||
<FormInputSuffix>{data.language}</FormInputSuffix>
|
|
||||||
</FormInputSuffixWrapper>
|
|
||||||
))}
|
|
||||||
</LanguageWrapper>
|
|
||||||
</FormRowGroup>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</RegistGroup>
|
|
||||||
|
|
||||||
{isNullValue && (
|
|
||||||
<SearchBarAlert $align="right" $padding="0 0 15px">
|
|
||||||
{t('NULL_MSG')}
|
|
||||||
</SearchBarAlert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<BtnWrapper $justify="flex-end" $gap="10px">
|
|
||||||
<Button text="취소" theme="line" handleClick={() => handleModalView('cancel')} />
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
text="등록"
|
|
||||||
theme={checkCondition() ? 'primary' : 'disable'}
|
|
||||||
handleClick={() => handleSubmit('submit')}
|
|
||||||
/>
|
|
||||||
</BtnWrapper>
|
|
||||||
|
|
||||||
{/* 등록 모달 */}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.confirmOkCancel}
|
|
||||||
view={modalState.registConfirmModal}
|
|
||||||
modalText={t('MENU_BANNER_REGIST_CONFIRM')}
|
|
||||||
handleSubmit={() => handleSubmit('registConfirm')}
|
|
||||||
handleCancel={() => handleModalClose('registConfirm')}
|
|
||||||
/>
|
|
||||||
{/* 완료 모달 */}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.completed}
|
|
||||||
view={modalState.registCompleteModal}
|
|
||||||
modalText={t('REGIST_COMPLTE')}
|
|
||||||
handleSubmit={() => handleSubmit('registComplete')}
|
|
||||||
/>
|
|
||||||
{/* 취소 모달 */}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.confirmOkCancel}
|
|
||||||
view={modalState.cancelModal}
|
|
||||||
modalText={t('MENU_BANNER_REGIST_CANCEL')}
|
|
||||||
handleCancel={() => handleModalClose('cancel')}
|
|
||||||
handleSubmit={() => handleSubmit('cancel')}
|
|
||||||
/>
|
|
||||||
{/* 경고 모달 */}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.completed}
|
|
||||||
view={alertMsg ? 'view' : 'hidden'}
|
|
||||||
modalText={alertMsg}
|
|
||||||
handleSubmit={() => handleSubmit('warning')}
|
|
||||||
/>
|
|
||||||
{loading && <Loading/>}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initData = {
|
|
||||||
title: '',
|
|
||||||
is_link: false,
|
|
||||||
start_dt: '',
|
|
||||||
end_dt: '',
|
|
||||||
image_list: [
|
|
||||||
{ language: 'KO', content: '' },
|
|
||||||
{ language: 'EN', content: '' },
|
|
||||||
{ language: 'JA', content: '' },
|
|
||||||
],
|
|
||||||
link_list: [
|
|
||||||
{ language: 'KO', content: '' },
|
|
||||||
{ language: 'EN', content: '' },
|
|
||||||
{ language: 'JA', content: '' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MenuBannerRegist;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const LanguageWrapper = styled.div`
|
|
||||||
width: ${props => props.width || '100%'};
|
|
||||||
//margin-bottom: 20px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
padding-left: 90px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LanguageLabel = styled.h4`
|
|
||||||
color: #444;
|
|
||||||
margin: 0 0 10px 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
import { useState, Fragment, useRef } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
|
||||||
import {
|
|
||||||
authType,
|
|
||||||
modalTypes,
|
|
||||||
landAuctionStatusType, opYNType,
|
|
||||||
} from '../../assets/data';
|
|
||||||
import { Title, FormWrapper, TableStyle, TableWrapper} from '../../styles/Components';
|
|
||||||
import {
|
|
||||||
CheckBox,
|
|
||||||
Button,
|
|
||||||
DynamicModal,
|
|
||||||
Pagination,
|
|
||||||
ViewTableInfo, CaliTable, TableHeader,
|
|
||||||
} from '../../components/common';
|
|
||||||
import { convertKTC, timeDiffMinute } from '../../utils';
|
|
||||||
import { INITIAL_PAGE_SIZE, INITIAL_PAGE_LIMIT } from '../../assets/data/adminConstants';
|
|
||||||
import { useModal, useTable, withAuth } from '../../hooks/hook';
|
|
||||||
import { StatusWapper, StatusLabel } from '../../styles/ModuleComponents';
|
|
||||||
import { opMenuBannerStatus } from '../../assets/data/options';
|
|
||||||
import MenuBannerSearchBar, { useMenuBannerSearch } from '../../components/searchBar/MenuBannerSearchBar';
|
|
||||||
import { MenuBannerDelete, MenuBannerDetailView } from '../../apis';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import MenuBannerModal from '../../components/modal/MenuBannerModal';
|
|
||||||
import tableInfo from '../../assets/data/pages/menuBannerTable.json'
|
|
||||||
|
|
||||||
const MenuBanner = () => {
|
|
||||||
const token = sessionStorage.getItem('token');
|
|
||||||
const userInfo = useRecoilValue(authList);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const tableRef = useRef(null);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [detailData, setDetailData] = useState({});
|
|
||||||
|
|
||||||
const {
|
|
||||||
modalState,
|
|
||||||
handleModalView,
|
|
||||||
handleModalClose
|
|
||||||
} = useModal({
|
|
||||||
detail: 'hidden',
|
|
||||||
deleteConfirm: 'hidden',
|
|
||||||
deleteComplete: 'hidden'
|
|
||||||
});
|
|
||||||
const [alertMsg, setAlertMsg] = useState('');
|
|
||||||
const [modalType, setModalType] = useState('regist');
|
|
||||||
|
|
||||||
const {
|
|
||||||
searchParams,
|
|
||||||
data: dataList,
|
|
||||||
handleSearch,
|
|
||||||
handleReset,
|
|
||||||
handlePageChange,
|
|
||||||
handlePageSizeChange,
|
|
||||||
handleOrderByChange,
|
|
||||||
updateSearchParams
|
|
||||||
} = useMenuBannerSearch(token, INITIAL_PAGE_SIZE);
|
|
||||||
|
|
||||||
const {
|
|
||||||
selectedRows,
|
|
||||||
handleSelectRow,
|
|
||||||
isRowSelected
|
|
||||||
} = useTable(dataList?.event_list || [], {mode: 'single'});
|
|
||||||
|
|
||||||
|
|
||||||
const handleModalSubmit = async (type, param = null) => {
|
|
||||||
switch (type) {
|
|
||||||
case "regist":
|
|
||||||
setModalType('regist');
|
|
||||||
handleModalView('detail');
|
|
||||||
break;
|
|
||||||
case "detail":
|
|
||||||
await MenuBannerDetailView(token, param).then(data => {
|
|
||||||
setDetailData(data.event_detail);
|
|
||||||
setModalType('modify');
|
|
||||||
handleModalView('detail');
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
const date_check = selectedRows.every(row => {
|
|
||||||
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
|
|
||||||
return timeDiff < 3;
|
|
||||||
});
|
|
||||||
if(date_check){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleModalView('deleteConfirm');
|
|
||||||
break;
|
|
||||||
case "deleteConfirm":
|
|
||||||
let list = [];
|
|
||||||
let isChecked = false;
|
|
||||||
|
|
||||||
selectedRows.map(data => {
|
|
||||||
// const row = dataList.list.find(row => row.id === Number(data.id));
|
|
||||||
// if(row.status !== commonStatus.wait) isChecked = true;
|
|
||||||
list.push({
|
|
||||||
id: data.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if(isChecked) {
|
|
||||||
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
|
|
||||||
handleModalClose('deleteConfirm');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MenuBannerDelete(token, list).then(data => {
|
|
||||||
handleModalClose('deleteConfirm');
|
|
||||||
if(data.result === "SUCCESS") {
|
|
||||||
handleModalView('deleteComplete');
|
|
||||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
|
|
||||||
}else{
|
|
||||||
setAlertMsg(t('DELETE_FAIL'));
|
|
||||||
}
|
|
||||||
}).catch(reason => {
|
|
||||||
setAlertMsg(t('API_FAIL'));
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "deleteComplete":
|
|
||||||
handleModalClose('deleteComplete');
|
|
||||||
window.location.reload();
|
|
||||||
break;
|
|
||||||
case "warning":
|
|
||||||
setAlertMsg('')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAction = async (action, item = null) => {
|
|
||||||
switch (action) {
|
|
||||||
case "regist":
|
|
||||||
setModalType('regist');
|
|
||||||
handleModalView('detail');
|
|
||||||
break;
|
|
||||||
case "detail":
|
|
||||||
await MenuBannerDetailView(token, item).then(data => {
|
|
||||||
setDetailData(data.event_detail);
|
|
||||||
setModalType('modify');
|
|
||||||
handleModalView('detail');
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
const date_check = selectedRows.every(row => {
|
|
||||||
const timeDiff = timeDiffMinute(convertKTC(row.auction_start_dt), (new Date));
|
|
||||||
return timeDiff < 3;
|
|
||||||
});
|
|
||||||
if(date_check){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_DELETE_DATE_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(selectedRows[0].status === landAuctionStatusType.auction_start || selectedRows[0].status === landAuctionStatusType.stl_end){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_DELETE_STATUS_WARNING'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleModalView('deleteConfirm');
|
|
||||||
break;
|
|
||||||
case "deleteConfirm":
|
|
||||||
let list = [];
|
|
||||||
let isChecked = false;
|
|
||||||
|
|
||||||
selectedRows.map(data => {
|
|
||||||
// const row = dataList.list.find(row => row.id === Number(data.id));
|
|
||||||
// if(row.status !== commonStatus.wait) isChecked = true;
|
|
||||||
list.push({
|
|
||||||
id: data.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if(isChecked) {
|
|
||||||
setAlertMsg(t('LAND_AUCTION_WARNING_DELETE'))
|
|
||||||
handleModalClose('deleteConfirm');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MenuBannerDelete(token, list).then(data => {
|
|
||||||
handleModalClose('deleteConfirm');
|
|
||||||
if(data.result === "SUCCESS") {
|
|
||||||
handleModalView('deleteComplete');
|
|
||||||
}else if(data.result === "ERROR_AUCTION_STATUS_IMPOSSIBLE"){
|
|
||||||
setAlertMsg(t('LAND_AUCTION_ERROR_DELETE_STATUS'));
|
|
||||||
}else{
|
|
||||||
setAlertMsg(t('DELETE_FAIL'));
|
|
||||||
}
|
|
||||||
}).catch(reason => {
|
|
||||||
setAlertMsg(t('API_FAIL'));
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "deleteComplete":
|
|
||||||
handleModalClose('deleteComplete');
|
|
||||||
window.location.reload();
|
|
||||||
break;
|
|
||||||
case "warning":
|
|
||||||
setAlertMsg('')
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title>메뉴 배너 관리</Title>
|
|
||||||
<FormWrapper>
|
|
||||||
<MenuBannerSearchBar
|
|
||||||
searchParams={searchParams}
|
|
||||||
onSearch={(newParams, executeSearch = true) => {
|
|
||||||
if (executeSearch) {
|
|
||||||
handleSearch(newParams);
|
|
||||||
} else {
|
|
||||||
updateSearchParams(newParams);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onReset={handleReset}
|
|
||||||
/>
|
|
||||||
</FormWrapper>
|
|
||||||
{/*<ViewTableInfo total={dataList?.total} total_all={dataList?.total_all} handleOrderBy={handleOrderByChange} handlePageSize={handlePageSizeChange}>*/}
|
|
||||||
{/* {userInfo.auth_list?.some(auth => auth.id === authType.battleEventDelete) && (*/}
|
|
||||||
{/* <Button theme={selectedRows.length === 0 ? 'disable' : 'line'} text="선택 삭제" handleClick={() => handleModalSubmit('delete')} />*/}
|
|
||||||
{/* )}*/}
|
|
||||||
{/* {userInfo.auth_list?.some(auth => auth.id === authType.battleEventUpdate) && (*/}
|
|
||||||
{/* <Button*/}
|
|
||||||
{/* theme="primary"*/}
|
|
||||||
{/* text="이미지 등록"*/}
|
|
||||||
{/* type="button"*/}
|
|
||||||
{/* handleClick={e => {*/}
|
|
||||||
{/* e.preventDefault();*/}
|
|
||||||
{/* navigate('/servicemanage/menubanner/menubannerregist');*/}
|
|
||||||
{/* }}*/}
|
|
||||||
{/* />*/}
|
|
||||||
{/* )}*/}
|
|
||||||
{/*</ViewTableInfo>*/}
|
|
||||||
<TableHeader
|
|
||||||
config={tableInfo.header}
|
|
||||||
total={dataList?.total}
|
|
||||||
total_all={dataList?.total_all}
|
|
||||||
handleOrderBy={handleOrderByChange}
|
|
||||||
handlePageSize={handlePageSizeChange}
|
|
||||||
selectedRows={selectedRows}
|
|
||||||
onAction={handleAction}
|
|
||||||
navigate={navigate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CaliTable
|
|
||||||
columns={tableInfo.columns}
|
|
||||||
data={dataList?.list}
|
|
||||||
selectedRows={selectedRows}
|
|
||||||
onSelectRow={handleSelectRow}
|
|
||||||
onAction={handleAction}
|
|
||||||
refProp={tableRef}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*<TableWrapper>*/}
|
|
||||||
{/* <TableStyle ref={tableRef}>*/}
|
|
||||||
{/* <caption></caption>*/}
|
|
||||||
{/* <thead>*/}
|
|
||||||
{/* <tr>*/}
|
|
||||||
{/* <th width="40"></th>*/}
|
|
||||||
{/* <th width="70">번호</th>*/}
|
|
||||||
{/* <th width="80">등록 상태</th>*/}
|
|
||||||
{/* <th width="150">시작일(KST)</th>*/}
|
|
||||||
{/* <th width="150">종료일(KST)</th>*/}
|
|
||||||
{/* <th width="300">설명 제목</th>*/}
|
|
||||||
{/* <th width="90">링크여부</th>*/}
|
|
||||||
{/* <th width="100">상세보기</th>*/}
|
|
||||||
{/* <th width="150">히스토리</th>*/}
|
|
||||||
{/* </tr>*/}
|
|
||||||
{/* </thead>*/}
|
|
||||||
{/* <tbody>*/}
|
|
||||||
{/* {dataList?.list?.map(banner => (*/}
|
|
||||||
{/* <tr key={banner.row_num}>*/}
|
|
||||||
{/* <td>*/}
|
|
||||||
{/* <CheckBox name={'select'} id={banner.id}*/}
|
|
||||||
{/* setData={(e) => handleSelectRow(e, banner)}*/}
|
|
||||||
{/* checked={isRowSelected(banner.id)} />*/}
|
|
||||||
{/* </td>*/}
|
|
||||||
{/* <td>{banner.row_num}</td>*/}
|
|
||||||
{/* <StatusWapper>*/}
|
|
||||||
{/* <StatusLabel $status={banner.status}>*/}
|
|
||||||
{/* {opMenuBannerStatus.find(data => data.value === banner.status)?.name}*/}
|
|
||||||
{/* </StatusLabel>*/}
|
|
||||||
{/* </StatusWapper>*/}
|
|
||||||
{/* <td>{convertKTC(banner.start_dt)}</td>*/}
|
|
||||||
{/* <td>{convertKTC(banner.end_dt)}</td>*/}
|
|
||||||
{/* <td>{banner.title}</td>*/}
|
|
||||||
{/* <td>{opYNType.find(data => data.value === banner.is_link)?.name}</td>*/}
|
|
||||||
{/* <td>*/}
|
|
||||||
{/* <Button theme="line" text="상세보기"*/}
|
|
||||||
{/* handleClick={e => handleModalSubmit('detail', banner.id)} />*/}
|
|
||||||
{/* </td>*/}
|
|
||||||
{/* <td>{banner.update_by}</td>*/}
|
|
||||||
{/* </tr>*/}
|
|
||||||
{/* ))}*/}
|
|
||||||
{/* </tbody>*/}
|
|
||||||
{/* </TableStyle>*/}
|
|
||||||
{/*</TableWrapper>*/}
|
|
||||||
|
|
||||||
<Pagination postsPerPage={searchParams.pageSize} totalPosts={dataList?.total_all} setCurrentPage={handlePageChange} currentPage={searchParams.currentPage} pageLimit={INITIAL_PAGE_LIMIT} />
|
|
||||||
|
|
||||||
{/*상세*/}
|
|
||||||
<MenuBannerModal modalType={modalType} detailView={modalState.detailModal} handleDetailView={() => handleModalClose('detail')} content={detailData} setDetailData={setDetailData} />
|
|
||||||
|
|
||||||
{/*삭제 확인*/}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.confirmOkCancel}
|
|
||||||
view={modalState.deleteConfirmModal}
|
|
||||||
handleCancel={() => handleModalClose('deleteConfirm')}
|
|
||||||
handleSubmit={() => handleModalSubmit('deleteConfirm')}
|
|
||||||
modalText={t('MENU_BANNER_SELECT_DELETE')}
|
|
||||||
/>
|
|
||||||
{/*삭제 완료*/}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.completed}
|
|
||||||
view={modalState.deleteCompleteModal}
|
|
||||||
handleSubmit={() => handleModalSubmit('deleteComplete')}
|
|
||||||
modalText={t('DEL_COMPLETE')}
|
|
||||||
/>
|
|
||||||
{/* 경고 모달 */}
|
|
||||||
<DynamicModal
|
|
||||||
modalType={modalTypes.completed}
|
|
||||||
view={alertMsg ? 'view' : 'hidden'}
|
|
||||||
modalText={alertMsg}
|
|
||||||
handleSubmit={() => handleModalSubmit('warning')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withAuth(authType.battleEventRead)(MenuBanner);
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
import CheckBox from '../../components/common/input/CheckBox';
|
import CheckBox from '../../components/common/input/CheckBox';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Title, FormWrapper, TableInfo, ListCount, ListOption, TableStyle, SelectInput, TableWrapper, ButtonClose, ModalText, BtnWrapper } from '../../styles/Components';
|
import { Title, FormWrapper, TableInfo, ListCount, ListOption, TableStyle, SelectInput, TableWrapper, ButtonClose, ModalText, BtnWrapper } from '../../styles/Components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ const ReportList = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.reportRead) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.reportRead) ? (
|
||||||
<AuthModal/>
|
<AuthModal/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>신고내역 조회 및 답변</Title>
|
<Title>신고내역 조회 및 답변</Title>
|
||||||
<ReportListSummary />
|
<ReportListSummary />
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
@@ -331,7 +332,7 @@ const ReportList = () => {
|
|||||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmeModalClose} />
|
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handleConfirmeModalClose} />
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, Fragment } from 'react';
|
import React, { useState, Fragment } from 'react';
|
||||||
import { Title, FormWrapper, TextInput } from '../../styles/Components';
|
import { Title, FormWrapper, TextInput } from '../../styles/Components';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
import {
|
import {
|
||||||
CommonSearchBar,
|
CommonSearchBar,
|
||||||
UserBlockDetailModal,
|
UserBlockDetailModal,
|
||||||
@@ -130,7 +131,7 @@ const UserBlock = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>이용자 제재 조회 및 등록</Title>
|
<Title>이용자 제재 조회 및 등록</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CommonSearchBar
|
<CommonSearchBar
|
||||||
@@ -209,7 +210,7 @@ const UserBlock = () => {
|
|||||||
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
<ModalSubText $color={deleteDesc.length > 29 ? 'red' : '#666'}>* 최대 등록 가능 글자수 ({deleteDesc.length}/30자)</ModalSubText>
|
||||||
</ModalInputItem>
|
</ModalInputItem>
|
||||||
</DynamicModal>
|
</DynamicModal>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import {AnimatedPageWrapper} from '../../components/common/Layout'
|
||||||
|
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import RadioInput from '../../components/common/input/Radio';
|
import RadioInput from '../../components/common/input/Radio';
|
||||||
|
|
||||||
@@ -267,7 +269,7 @@ const UserBlockRegist = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListUpdate) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.blackListUpdate) ? (
|
||||||
<AuthModal/>
|
<AuthModal/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>이용자 제재 등록</Title>
|
<Title>이용자 제재 등록</Title>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -563,7 +565,7 @@ const UserBlockRegist = () => {
|
|||||||
/>
|
/>
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import CheckBox from '../../components/common/input/CheckBox';
|
import CheckBox from '../../components/common/input/CheckBox';
|
||||||
import { Title, FormWrapper, SelectInput, BtnWrapper, TableInfo, ListCount, ListOption, TableStyle, State, ButtonClose, ModalText } from '../../styles/Components';
|
import { Title, FormWrapper, SelectInput, BtnWrapper, TableInfo, ListCount, ListOption, TableStyle, State, ButtonClose, ModalText } from '../../styles/Components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
@@ -292,7 +293,7 @@ function AdminView() {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 1) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === 1) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>운영자 조회</Title>
|
<Title>운영자 조회</Title>
|
||||||
<FormWrapper action="" $flow="row">
|
<FormWrapper action="" $flow="row">
|
||||||
<AdminViewSearchBar handleSearch={handleSearch} groupList={groupList} setResultData={setSearchData} setCurrentPage={setCurrentPage} />
|
<AdminViewSearchBar handleSearch={handleSearch} groupList={groupList} setResultData={setSearchData} setCurrentPage={setCurrentPage} />
|
||||||
@@ -492,7 +493,7 @@ function AdminView() {
|
|||||||
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handlePasswordInitialize} />
|
<Button text="확인" theme="primary" type="submit" size="large" width="100%" handleClick={handlePasswordInitialize} />
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import CheckBox from '../../components/common/input/CheckBox';
|
import CheckBox from '../../components/common/input/CheckBox';
|
||||||
|
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import Modal from '../../components/common/modal/Modal';
|
import Modal from '../../components/common/modal/Modal';
|
||||||
import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
import { Title, FormWrapper, SelectInput, TableInfo, ListCount, ListOption, TableStyle, BtnWrapper, ButtonClose, ModalText } from '../../styles/Components';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
@@ -168,7 +169,7 @@ const AuthSetting = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.authoritySettingRead) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.authoritySettingRead) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>권한 설정</Title>
|
<Title>권한 설정</Title>
|
||||||
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.authoritySettingUpdate) && (
|
{userInfo.auth_list && userInfo.auth_list.some(auth => auth.id === authType.authoritySettingUpdate) && (
|
||||||
<FormWrapper action="" $flow="row">
|
<FormWrapper action="" $flow="row">
|
||||||
@@ -270,7 +271,7 @@ const AuthSetting = () => {
|
|||||||
/>
|
/>
|
||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout';
|
||||||
|
|
||||||
import { authList } from '../../store/authList';
|
import { authList } from '../../store/authList';
|
||||||
import { authType, modalTypes } from '../../assets/data';
|
import { authType, modalTypes } from '../../assets/data';
|
||||||
@@ -100,7 +101,7 @@ const AuthSettingUpdate = () => {
|
|||||||
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.authoritySettingUpdate) ? (
|
{userInfo.auth_list && !userInfo.auth_list.some(auth => auth.id === authType.authoritySettingUpdate) ? (
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>권한 설정</Title>
|
<Title>권한 설정</Title>
|
||||||
<FormWrapper $flow="column">
|
<FormWrapper $flow="column">
|
||||||
<TableStyle>
|
<TableStyle>
|
||||||
@@ -148,7 +149,7 @@ const AuthSettingUpdate = () => {
|
|||||||
</BtnWrapper>
|
</BtnWrapper>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import { authList } from '../../store/authList';
|
import { authList } from '../../store/authList';
|
||||||
import {
|
import {
|
||||||
authType,
|
authType,
|
||||||
@@ -136,7 +137,7 @@ const CaliumRequest = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>칼리움 사용 수량 요청</Title>
|
<Title>칼리움 사용 수량 요청</Title>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<CommonSearchBar
|
<CommonSearchBar
|
||||||
@@ -250,7 +251,7 @@ const CaliumRequest = () => {
|
|||||||
title="히스토리"
|
title="히스토리"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, Fragment, useMemo } from 'react';
|
import React, { useState, Fragment, useMemo } from 'react';
|
||||||
import Button from '../../components/common/button/Button';
|
import Button from '../../components/common/button/Button';
|
||||||
import Loading from '../../components/common/Loading';
|
import Loading from '../../components/common/Loading';
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
@@ -139,7 +140,7 @@ const DataInitView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>데이터 초기화</Title>
|
<Title>데이터 초기화</Title>
|
||||||
<MessageWrapper>
|
<MessageWrapper>
|
||||||
<FormRowGroup>
|
<FormRowGroup>
|
||||||
@@ -227,7 +228,7 @@ const DataInitView = () => {
|
|||||||
handleCancel={() => handleModalClose('registConfirm')}
|
handleCancel={() => handleModalClose('registConfirm')}
|
||||||
/>
|
/>
|
||||||
<TopButton />
|
<TopButton />
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { Fragment, useCallback, useState } from 'react';
|
import React, { Fragment, useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { AnimatedPageWrapper } from '../../components/common/Layout'
|
||||||
import { CommonSearchBar } from '../../components/ServiceManage';
|
import { CommonSearchBar } from '../../components/ServiceManage';
|
||||||
import { Title, FormWrapper } from '../../styles/Components';
|
import { Title, FormWrapper } from '../../styles/Components';
|
||||||
import { authType } from '../../assets/data';
|
import { authType } from '../../assets/data';
|
||||||
@@ -51,7 +52,7 @@ const LogView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<AnimatedPageWrapper>
|
||||||
<Title>사용 이력 조회</Title>
|
<Title>사용 이력 조회</Title>
|
||||||
|
|
||||||
{/* 조회조건 */}
|
{/* 조회조건 */}
|
||||||
@@ -99,7 +100,7 @@ const LogView = () => {
|
|||||||
title="상세정보"
|
title="상세정보"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</>
|
</AnimatedPageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const Container = styled.div`
|
|||||||
export const HeaderContainer = styled.div`
|
export const HeaderContainer = styled.div`
|
||||||
width: 280px;
|
width: 280px;
|
||||||
flex: 0 0 280px;
|
flex: 0 0 280px;
|
||||||
background: #666666;
|
background: #141414;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
`;
|
`;
|
||||||
@@ -719,7 +719,7 @@ export const TabScroll = styled.div`
|
|||||||
|
|
||||||
export const TabItem = styled(Link)`
|
export const TabItem = styled(Link)`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 120px;
|
width: 150px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -114,7 +114,48 @@ export const numberFormatter = {
|
|||||||
console.error('Currency formatting error:', e);
|
console.error('Currency formatting error:', e);
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
formatPercent: (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 valueToFormat = num / 100;
|
||||||
|
|
||||||
|
return new Intl.NumberFormat('ko-KR', {
|
||||||
|
style: 'percent',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: decimals
|
||||||
|
}).format(valueToFormat);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Currency formatting error:', e);
|
||||||
|
return '0%';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
formatSecondToMinuts: (seconds) => {
|
||||||
|
if (seconds === null || seconds === undefined) return '0:00';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const num = typeof seconds === 'string' ? parseFloat(seconds) : seconds;
|
||||||
|
if (isNaN(num)) return '0:00';
|
||||||
|
|
||||||
|
// 총 초를 분과 초로 변환
|
||||||
|
const minutes = Math.floor(num / 60);
|
||||||
|
const remainingSeconds = Math.floor(num % 60);
|
||||||
|
|
||||||
|
// 초가 10보다 작으면 앞에 0을 붙여 두 자리로 표시
|
||||||
|
const formattedSeconds = remainingSeconds < 10 ? `0${remainingSeconds}` : remainingSeconds;
|
||||||
|
|
||||||
|
return `${minutes}:${formattedSeconds}`;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Seconds to minutes formatting error:', e);
|
||||||
|
return '0:00';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user